diff --git a/README.md b/README.md
index a5e63b8..0ee3547 100644
--- a/README.md
+++ b/README.md
@@ -27,6 +27,7 @@ Learn more and get started here:
- **Artifact Search**: Execute powerful AQL queries to search for artifacts and builds
- **Catalog and Curation**: Access package information, versions, vulnerabilities, and check curation status
- **Xray**: Access scan artifacts summary, group by severity per artifact
+- **Workers**: Create and manage JFrog Workers with best practices (note: update and delete operations are considered critical and are intentionally not implemented in this MCP server for safety reasons)
## Tools
@@ -233,6 +234,69 @@ Learn more and get started here:
- Returns: A summary based on vulnerability count per severity for each artifact in the provided array plus the total issues
+
+Workers
+
+23. `jfrog_workers_check_availability`
+ - Check if JFrog Worker Service is ready and functioning
+ - Inputs: None
+ - Returns: Worker service readiness status
+
+24. `jfrog_list_workers_actions`
+ - List all available actions for JFrog Workers with optional project filtering
+ - Inputs:
+ - `projectKey` (optional string): The project key for which you want to retrieve available actions
+ - Returns: List of available Worker actions with descriptions, sample payloads, and code templates
+
+25. `jfrog_generate_worker_code`
+ - Generate a worker script template for a specified action and purpose
+ - Inputs:
+ - `action` (string): The action name defined in the ListActionsResponseSchema
+ - `intendedPurpose` (string): The intended purpose of the Worker
+ - Returns: Code template, type definitions, and implementation instructions
+
+26. `jfrog_create_worker`
+ - Create a new worker based on a provided description of its intended purpose
+ - Inputs:
+ - `projectKey` (optional string): The project context in which the Worker should belong to
+ - `name` (string): The name of the Worker (2-40 characters, letters and dashes only)
+ - `action` (object): The action on which the Worker belongs to
+ - `name` (string): The name of the action
+ - `application` (string): The application that triggers workers
+ - `workerCode` (string): The TS/JS code of the Worker
+ - `settings` (optional object): Define settings regarding a Worker
+ - `description` (optional string): Describe what this worker does
+ - `repositories` (optional string[]): List of repositories that will trigger this Worker (for FILTER_REPO actions)
+ - `includePatterns` (optional string[]): Patterns that trigger the Worker when they match an artifact path
+ - `excludePatterns` (optional string[]): Patterns that prevent the Worker from being triggered
+ - `anyLocal` (optional boolean): Whether the Worker should trigger when any local repository is affected
+ - `anyRemote` (optional boolean): Whether the Worker should trigger when any remote repository is affected
+ - `anyFederated` (optional boolean): Whether the Worker should trigger when any federated repository is affected
+ - `cronExpression` (optional string): Cron expression for scheduled Workers (for SCHEDULE actions)
+ - `timezone` (optional string): Timezone for scheduled Workers
+ - `secrets` (optional array): List of secrets that will be used by the Worker
+ - `name` (string): The name of the secret
+ - `value` (string): The value of the secret
+ - `properties` (optional array): List of properties that will be used by the Worker
+ - `name` (string): The name of the property
+ - `value` (string): The value of the property
+ - `showStatusOfSuccessfulExecutions` (optional boolean): Whether to record successful execution results
+ - `allowOtherUsersToExecuteTheWorker` (optional boolean): Allow non-admin users to trigger this Worker
+ - Returns: Created worker details
+
+27. `jfrog_get_worker_best_practices`
+ - Get best practices and recommendations for creating efficient JFrog Workers
+ - Inputs:
+ - `action` (optional string): Optional action name to get specific recommendations
+ - Returns: Best practices, recommendations, and specific advice for Worker creation
+
+28. `jfrog_list_all_workers`
+ - List all JFrog Workers with optional project filtering
+ - Inputs:
+ - `projectKey` (optional string): The project key for which you want to retrieve workers. If not provided, all workers will be retrieved. Note: without a projectKey, the token must have full admin rights; with a projectKey, project admin rights or higher are sufficient
+ - Returns: List of all workers with their details including name, action, settings, and status
+
+
## Setup
### Installing via Smithery
@@ -533,10 +597,9 @@ For Claude Desktop with SSE transport:
}
}
```
-```
-
+```
## License
-
This MCP server is licensed under the Apache License 2.0. This means you are free to use, modify, and distribute the software, subject to the terms and conditions of the Apache License 2.0. For more details, please see the LICENSE.md file in the project repository.
+```
diff --git a/common/utils.ts b/common/utils.ts
index 8e8bde7..d9bba0c 100644
--- a/common/utils.ts
+++ b/common/utils.ts
@@ -40,6 +40,10 @@ export function buildUrl(baseUrl: string, params: Record;
+}
+
+interface PlatformClients {
+ /**
+ * HTTP client to perform requests to your JFrog platform
+ */
+ platformHttp: PlatformHttpClient
+ /**
+ * HTTP client (axios) to perform requests to the outside
+ */
+ axios: AxiosInstance
+}
+
+interface PlatformSecrets {
+ /**
+ * Retrieves a secret by its key
+ * @param {string} secretKey - The secret key
+ */
+ get(secretKey: string): string;
+}
+
+interface PlatformProperties {
+ /**
+ * Retrieves a Worker's property by its key
+ * @param {string} propertyKey - The property key
+ */
+ get(propertyKey: string): string;
+}
+
+interface PlatformHttpClient {
+ /**
+ * Perform http GET request to JFrog platform
+ * @param {string} endpoint - API endpoint. E.g. /artifactory/api/repositories
+ * @param {Record} headers - additional headers used in the request
+ */
+ get(endpoint: string, headers?: Record): Promise;
+
+ /**
+ * Perform http POST request to JFrog platform
+ * @param {string} endpoint - API endpoint. E.g. /artifactory/api/repositories
+ * @param {any} requestData - data sent in request body
+ * @param {Record} headers - additional headers used in the request
+ */
+ post(endpoint: string, requestData?: any, headers?: Record): Promise;
+
+ /**
+ * Perform http PUT request to JFrog platform
+ * @param {string} endpoint - API endpoint. E.g. /artifactory/api/repositories
+ * @param {any} requestData - data sent in request body
+ * @param {Record} headers - additional headers used in the request
+ */
+ put(endpoint: string, requestData?: any, headers?: Record): Promise;
+
+ /**
+ * Perform http PATCH request to JFrog platform
+ * @param {string} endpoint - API endpoint. E.g. /artifactory/api/repositories
+ * @param {any} requestData - data sent in request body
+ * @param {Record} headers - additional headers used in the request
+ */
+ patch(endpoint: string, requestData?: any, headers?: Record): Promise;
+
+ /**
+ * Perform http DELETE request to JFrog platform
+ * @param {string} endpoint - API endpoint. E.g. /artifactory/api/repositories
+ * @param {Record} headers - additional headers used in the request
+ */
+ delete(endpoint: string, headers?: Record): Promise;
+}
+
+interface IPlatformHttpResponse {
+ /**
+ * Http status
+ */
+ status: number;
+ /**
+ * Response headers
+ */
+ headers: Record;
+ /**
+ * Parsed response body (as json)
+ */
+ data: any;
+}
+
+interface PlatformHttpClientError {
+ /**
+ * The reason of the error
+ */
+ message: string;
+
+ /**
+ * Http status
+ */
+ status: number;
+}
diff --git a/schemas/workers.ts b/schemas/workers.ts
new file mode 100644
index 0000000..ff66320
--- /dev/null
+++ b/schemas/workers.ts
@@ -0,0 +1,139 @@
+/* Schema Section */
+import { z } from "zod";
+
+export const ActionFilterTypeEnum = z.enum(["NO_FILTERS", "FILTER_REPO", "SCHEDULE"]);
+
+export const JFrogWorkersReadinessSchema= z.object({
+ code: z.string()
+});
+
+export const ListActionsParamSchema = z.object({
+ projectKey: z.string().optional().describe("The project key for which you want to retrieve available actions. If not provided, all actions will be retrieved. Note: without a projectKey, the token must have full admin rights; with a projectKey, project admin rights or higher are sufficient")
+});
+
+export const ListWorkersParamSchema = z.object({
+ projectKey: z.string().optional().describe("The project key for which you want to retrieve available workers. If not provided, all workers will be retrieved. Note: without a projectKey, the token must have full admin rights; with a projectKey, project admin rights or higher are sufficient")
+});
+
+export const ListWorkersResponseSchema = z.object({
+ workers: z.array(
+ z.object({
+ key: z.string().describe("The name of the worker"),
+ application: z.string().describe("The application that triggers workers that belong to this action"),
+ description: z.string().optional().describe("Describes what this worker does"),
+ enabled: z.boolean().describe("Whether the worker is enabled. If true,the Worker can be triggered by the action"),
+ sourceCode: z.string().describe("The source code of the worker"),
+ action: z.string().describe("The action that triggers this worker"),
+ filterCriteria: z.union(
+ [
+ z.object({
+ artifactFilterCriteria: z.object({
+ repoKeys: z.array(z.string()).optional().default([]).describe("The list of repositories that will trigger this Worker when the specified action occurs. Only for Workers hooked to an action with a filterType="+ActionFilterTypeEnum.Values.FILTER_REPO),
+ includePatterns: z.array(z.string()).optional().default([]).describe("Patterns that trigger the Worker when they match an artifact path. Supports simple wildcard patterns for repository artifact paths (without a leading slash) using Ant-style expressions (*, **, ?). Example: org/apache/**. Only for Workers hooked to an action with a filterType="+ActionFilterTypeEnum.Values.FILTER_REPO),
+ excludePatterns: z.array(z.string()).optional().default([]).describe("Patterns that prevent the Worker from being triggered when they match an artifact path. These take precedence over include patterns and support simple wildcard matching for repository artifact paths (without a leading slash) using Ant-style expressions (*, **, ?). Example: org/apache/**. Only for Workers hooked to an action with a filterType="+ActionFilterTypeEnum.Values.FILTER_REPO),
+ anyLocal: z.boolean().optional().default(false).describe("When true, the Worker will trigger when any local repository is affected. Only for Workers hooked to an action with a filterType="+ActionFilterTypeEnum.Values.FILTER_REPO),
+ anyRemote: z.boolean().optional().default(false).describe("When true, the Worker will trigger when any remote repository is affected. Only for Workers hooked to an action with a filterType="+ActionFilterTypeEnum.Values.FILTER_REPO),
+ anyFederated: z.boolean().optional().default(false).describe("When true, the Worker will trigger when any federated repository is affected. Only for Workers hooked to an action with a filterType="+ActionFilterTypeEnum.Values.FILTER_REPO)
+ }),
+ }).optional().describe("The filter criteria that will trigger this Worker based on a repository. Only for Worker with filterType="+ActionFilterTypeEnum.Values.FILTER_REPO),
+ z.object({
+ scheduleFilterCriteria: z.object({
+ cronExpression: z.string().describe("The cron expression specifying when this Worker should be triggered. Only for Worker of type SCHEDULE"),
+ timezone: z.string().describe("String representation of the timezone in which the Worker is scheduled to run. Must be compatible with Date from the JS API. Only for Worker of type SCHEDULE")
+ })
+ }).optional().describe("The filter criteria that will trigger this Worker based on a schedule. Only for Worker with filterType="+ActionFilterTypeEnum.Values.SCHEDULE),
+ z.object({}).describe("No filter criteria for Worker with filterType="+ActionFilterTypeEnum.Values.NO_FILTERS),
+ z.undefined().describe("No filter criteria for Worker with filterType="+ActionFilterTypeEnum.Values.NO_FILTERS),
+ ]
+ ).optional().describe("The filter criteria that will trigger this Worker when the specified action occurs"),
+ secrets: z.array(z.object({
+ key: z.string().describe("The name of the secret"),
+ value: z.string().optional().describe("The value of the secret")
+ }).optional()).optional().describe("The list of the secrets stored along side of the Worker"),
+ properties: z.array(z.object({
+ key: z.string().describe("The name of the property"),
+ value: z.string().optional().describe("The value of the property"),
+ }).optional()).optional().describe("The list of properties stored along side of the Worker"),
+ shared: z.boolean().optional().describe("Whether the Worker can be triggered by non admin users"),
+ debug: z.boolean().optional().describe("Whether the execution result of this Worker should be recorded when the execution is successful"),
+ projectKey: z.string().optional().describe("The project key in which the Worker belongs to"),
+ currentVersion: z.object({
+ modifiedAt: z.number().describe("The timestamp of the last modification of the Worker"),
+ modifiedBy: z.string().describe("The user who last modified the Worker"),
+ }).optional().describe("The current version of the Worker"),
+ })
+ ),
+});
+
+export const WorkerSettingsType = z.object({
+ description: z.string().optional().describe("Describes what this worker does"),
+ repositories: z.array(z.string()).optional().describe("CRITICAL FOR PERFORMANCE: The list of repositories that will trigger this Worker when the specified action occurs. Use this instead of checking repository types in code for better performance. Only for Workers hooked to an action with a filterType="+ActionFilterTypeEnum.Values.FILTER_REPO),
+ includePatterns: z.array(z.string()).optional().describe("Patterns that trigger the Worker when they match an artifact path. Supports simple wildcard patterns for repository artifact paths (without a leading slash) using Ant-style expressions (*, **, ?). Example: org/apache/**. Only for Workers hooked to an action with a filterType="+ActionFilterTypeEnum.Values.FILTER_REPO),
+ excludePatterns: z.array(z.string()).optional().describe("Patterns that prevent the Worker from being triggered when they match an artifact path. These take precedence over include patterns and support simple wildcard matching for repository artifact paths (without a leading slash) using Ant-style expressions (*, **, ?). Example: org/apache/**. Only for Workers hooked to an action with a filterType="+ActionFilterTypeEnum.Values.FILTER_REPO),
+ anyLocal: z.boolean().optional().default(false).describe("Whether the Worker should trigger when any local repository is affected. Only for Workers hooked to an action with a filterType="+ActionFilterTypeEnum.Values.FILTER_REPO),
+ anyRemote: z.boolean().optional().default(false).describe("Whether the Worker should trigger when any remote repository is affected. Only for Workers hooked to an action with a filterType="+ActionFilterTypeEnum.Values.FILTER_REPO),
+ anyFederated: z.boolean().optional().default(false).describe("Whether the Worker should trigger when any federated repository is affected. Only for Workers hooked to an action with a filterType="+ActionFilterTypeEnum.Values.FILTER_REPO),
+ cronExpression: z.string().optional().describe("The cron expression specifying when this Worker should be triggered. Only for Worker of type SCHEDULE Only for Workers hooked to an action with a filterType="+ActionFilterTypeEnum.Values.SCHEDULE),
+ timezone: z.string().optional().describe("String representation of the timezone in which the Worker is scheduled to run. Must be compatible with Date from the JS API. Only for Worker of type SCHEDULE"),
+ secrets: z.array(z.object({
+ name: z.string().describe("The name of the secret"),
+ value: z.string().describe("The value of the secret"),
+ })).optional().default([]).describe("The list of secrets that will be used by the Worker. These secrets are cyphered and may be used in the Worker code with context.secrets.get(secretName)"),
+ properties: z.array(z.object({
+ name: z.string().describe("The name of the property"),
+ value: z.string().describe("The value of the property"),
+ })).optional().default([]).describe("The list of properties that will be used by the Worker. These properties are not cyphered and may be used in the Worker code with context.properties.get(propertyName)"),
+ showStatusOfSuccessfulExecutions: z.boolean().optional().default(false).describe("Indicates whether the execution result of this Worker should be recorded when the execution is successful"),
+ allowOtherUsersToExecuteTheWorker: z.boolean().optional().default(false).describe("Allow non admin users to trigger this Worker. Only for HTTP-Trigerred workers"),
+});
+
+export const CreateWorkerParamSchema = z.object({
+ projectKey: z.string().optional().describe("The project context in which the Worker should belong to"),
+ name: z.string().describe("The name of the Worker. It must start with a letter, may include dashes, and must be between 2 and 40 characters in length. Special characters other than dashes are not allowed"),
+ action: z.object({
+ name: z.string().describe("The name of the action"),
+ application: z.string().describe("The application that triggers workers that belong to this action"),
+ }).describe("The action on which the Worker belongs to. These informations are retrieved from the MCP command jfrog_list_workers_actions"),
+ workerCode: z.string().describe("The TS/JS code of the Worker"),
+ settings: WorkerSettingsType.optional().describe("Define settings regarding a Worker"),
+});
+
+export const CreateWorkerResponseSchema = z.object({
+ success: z.boolean().describe("Whether the worker was created successfully"),
+ message: z.string().describe("The message to display to the user when the worker is created"),
+ data: z.any().describe("The response of the HTTP call"),
+ workerKey: z.string().describe("The name of the created Worker"),
+ editUrl: z.string().describe("The URL to edit the worker"),
+ disclaimer: z.string().describe("Critical information to display to the user when the worker is created"),
+});
+
+export const GenerateWorkerCodeParamSchema = z.object({
+ intendedPurpose: z.string().describe("The intended purpose of the Worker"),
+ action: z.string().describe("This is the action name defined in the ListActionsResponseSchema"),
+});
+
+export const ListActionsResponseSchema = z.array(
+ z.object({
+ action: z.object({
+ name: z.string().describe("The name of the action"),
+ application: z.string().describe("The application that triggers workers that belong to this action"),
+ }),
+ description: z.string().describe("A short description of the action. 600 characters max"),
+ samplePayload: z.string().describe("Payload to put on the test panel"),
+ sampleCode: z.string().describe("Code added to editor when we create a new worker, should show input/output, very basic code"),
+ typesDefinitions: z.string().optional().default("").describe("A compilable javascript code defining types and interfaces used for autocompletion when editing the worker"),
+ supportProject: z.boolean().optional().default(false).describe("Whether the worker should refuse to run if the filter is not set"),
+ wikiUrl: z.string().describe("Link to the documentation of the worker of that action"),
+ async: z.boolean().optional().default(false).describe("Whether the Worker is async (executed in background of the action)"),
+ filterType: ActionFilterTypeEnum.optional().describe("Kind of filters supported by the action"),
+ executionRequestType: z.string().optional().default("any").describe("The type of the input payload (data) to be sent. It must be defined in the typesDefinition"),
+ }));
+
+export const PromptToGenerateWorkerCode = z.object({
+ codeTemplate: z.string().describe("The template to use for the code generation"),
+ typesDefinitions: z.string().describe("A compilable javascript code defining types and interfaces used for autocompletion when editing the worker"),
+ platformContextDefinitions: z.string().describe("Defines the interfaces for the internal APIs used by Workers"),
+ instructions: z.string().describe("IMPORTANT: These are the instructions that the AI agent must follow in order to replace the placeholders in the template")
+});
+
+/* End of Schema Section */
\ No newline at end of file
diff --git a/tools/index.ts b/tools/index.ts
index c0a662a..b73b753 100644
--- a/tools/index.ts
+++ b/tools/index.ts
@@ -9,6 +9,7 @@ import { CatalogTools } from "./catalog.js";
import { CurationTools } from "./curation.js";
import { PermissionsTools } from "./permissions.js";
import { ArtifactSecurityTools } from "./security.js";
+import { WorkersTools } from "./workers.js";
export const tools =[
...RepositoryTools,
@@ -20,6 +21,7 @@ export const tools =[
...CurationTools,
...PermissionsTools,
...ArtifactSecurityTools,
+ ...WorkersTools,
];
// A function that given a tool name, executes the handler with the arguments and returns the result
diff --git a/tools/workers.ts b/tools/workers.ts
new file mode 100644
index 0000000..943b674
--- /dev/null
+++ b/tools/workers.ts
@@ -0,0 +1,318 @@
+import { z } from "zod";
+import { zodToJsonSchema } from "zod-to-json-schema";
+import { getJFrogBaseUrl, jfrogRequest } from "../common/utils.js";
+import { readFileSync } from "fs";
+import {
+ ListActionsResponseSchema,
+ JFrogWorkersReadinessSchema,
+ ListActionsParamSchema,
+ PromptToGenerateWorkerCode,
+ GenerateWorkerCodeParamSchema,
+ CreateWorkerParamSchema,
+ ListWorkersResponseSchema,
+ ListWorkersParamSchema,
+} from "../schemas/workers.js";
+import path from "path";
+import { fileURLToPath } from "url";
+import { JFrogError } from "../common/errors.js";
+
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = path.dirname(__filename);
+const PLATFORM_CONTEXT_DEFINITIONS = readFileSync(path.join(__dirname, "../schemas/types/PlatformContextTypeDefinition.d.ts"), { encoding: "utf8" });
+
+/* Api Calls Section */
+
+export async function checkWorkersReadiness() {
+ const response = await jfrogRequest("/worker/api/v1/system/readiness", {
+ method: "GET",
+ });
+
+ return JFrogWorkersReadinessSchema.parse(response);
+}
+
+
+export async function listActions(projectKey?: string) {
+ const response = await jfrogRequest(`/worker/api/v2/actions${projectKey ? "?projectKey="+projectKey : ""}`, {
+ method: "GET"
+ });
+
+ return ListActionsResponseSchema.parse(response);
+}
+
+export async function getAllWorkers(projectKey?: string) {
+ const response = await jfrogRequest("/worker/api/v1/workers" + (projectKey ? "?projectKey=" + projectKey : ""), {
+ method: "GET"
+ });
+
+ return ListWorkersResponseSchema.parse(response);
+}
+
+export async function createWorker(workerParams: z.infer) {
+ const filterCriteria = {};
+
+ if (workerParams.action.name === "SCHEDULED_EVENT") {
+ (filterCriteria as any).schedule = {
+ cron: workerParams.settings?.cronExpression || "",
+ timezone: workerParams.settings?.timezone || "UTC",
+ };
+ } else if (workerParams.action.name !== "GENERIC_EVENT") {
+ (filterCriteria as any).artifactFilterCriteria = {
+ repoKeys: workerParams.settings?.repositories || [],
+ includePatterns: workerParams.settings?.includePatterns || [],
+ excludePatterns: workerParams.settings?.excludePatterns || [],
+ anyLocal: workerParams.settings?.anyLocal || false,
+ anyRemote: workerParams.settings?.anyRemote || false,
+ anyFederated: workerParams.settings?.anyFederated || false,
+ };
+ }
+
+ return await jfrogRequest("/worker/api/v2/workers", {
+ method: "POST",
+ body: {
+ enabled: false, // The AI agent should not be able to enable the worker, it should be done by the user for responsability reasons
+ key: workerParams.name,
+ action: workerParams.action,
+ sourceCode: workerParams.workerCode,
+ filterCriteria,
+ secrets: workerParams.settings?.secrets || [],
+ properties: workerParams.settings?.properties || [],
+ description: workerParams.settings?.description || "",
+ shared: workerParams.settings?.allowOtherUsersToExecuteTheWorker || false,
+ debug: workerParams.settings?.showStatusOfSuccessfulExecutions || false,
+ projectKey: workerParams.projectKey || "",
+ }
+ });
+}
+
+export async function getMinimalWorkerCodeTemplate(payloadType: string, responseType: string) {
+ return `
+ export default async (context: PlatformContext, data: ${payloadType}): Promise<${responseType}> => {
+ // __WORKER_IMPLEMENTATION__
+ }
+ `;
+}
+
+export async function getIntructionsForWorkerCodeGeneration(action: string, purpose: string) {
+ const actions = await listActions();
+ const actionToUse = actions.find((a) => a.action.name === action);
+
+ if (!actionToUse) {
+ console.warn(`No action defined in Worker service: '${action}'`);
+ throw new Error(`The action '${action}' does not exist`);
+ }
+
+ // Trying to guess the responseType, otherwise use any as fallback
+ let responseType = actionToUse.executionRequestType.replace("Request", "Response");
+ if (!actionToUse.typesDefinitions.includes(responseType)) {
+ console.warn(`The type '${responseType}' does not seem to be defined in the typesDefinition, will use any instead`);
+ responseType = "any";
+ }
+
+ // Enhanced instructions with best practices
+ const enhancedInstructions = `IMPORTANT: Follow these best practices when implementing the Worker:
+
+1. **PRIORITY: Use Repository Filters** - If the action supports FILTER_REPO (filterType: "FILTER_REPO"), ALWAYS configure the repositories list in the Worker settings instead of checking repository types in the code. This is more efficient and maintainable.
+
+2. **Repository Type Checking** - Only implement repository type checking in the Worker code if the action does NOT support FILTER_REPO or if you need dynamic filtering logic.
+
+3. **Performance Optimization** - Avoid unnecessary API calls. If you can filter by repository in settings, do it there rather than in the code.
+
+4. **Error Handling** - Always implement proper error handling with try-catch blocks and appropriate status responses.
+
+5. **Logging** - Include meaningful console.log statements for monitoring and debugging.
+
+6. **Axios Usage** - When configuring axios options, only set the "headers" or "Content-Type" property; NEVER modify other axios options.
+
+7. **setTimeout Usage** - Use "await context.wait(ms);" instead of "setTimeout(ms, callback)"
+
+Replace // __WORKER_IMPLEMENTATION__ according to the typeDefinitions, platformContextDefinitions and with the following logic: ${purpose}
+CRITICAL: When creating JFrog Workers, ALWAYS display the disclaimer from the response.`;
+
+ return {
+ codeTemplate: await getMinimalWorkerCodeTemplate(actionToUse.executionRequestType, responseType),
+ typesDefinitions: actionToUse.typesDefinitions,
+ platformContextDefinitions: PLATFORM_CONTEXT_DEFINITIONS,
+ instructions: enhancedInstructions
+ } as z.infer;
+}
+
+/* End of Api Calls Section */
+
+
+/* Tools Section */
+
+const checkJfrogWorkersAvailabilityTool = {
+ name: "jfrog_workers_check_availability",
+ description: "Check if JFrog Worker Servcie is ready and functioning or not",
+ inputSchema: zodToJsonSchema(z.object({})),
+ //outputSchema: zodToJsonSchema(JFrogWorkersReadinessSchema),
+ handler: async () => {
+ return await checkWorkersReadiness();
+ }
+};
+
+const listWorkersActionsTool = {
+ name: "jfrog_list_workers_actions",
+ description: "List all available actions for JFrog Workers for optional project",
+ inputSchema: zodToJsonSchema(ListActionsParamSchema),
+ //outputSchema: zodToJsonSchema(ListActionsResponseSchema),
+ handler: async (args: any) => {
+ const parsedArgs = ListActionsParamSchema.parse(args);
+ try {
+ return await listActions(parsedArgs.projectKey);
+ } catch(error) {
+ if (error instanceof JFrogError) {
+ return {
+ error: error.message,
+ status: error.status,
+ response: error.response
+ };
+ } else {
+ return "An error occurred while listing the workers actions: " + (error as Error).message;
+ }
+ }
+ }
+};
+
+const listAllWorkersTool = {
+ name: "jfrog_list_all_workers",
+ description: "List all JFrog Workers for optional project",
+ inputSchema: zodToJsonSchema(ListWorkersParamSchema),
+ //outputSchema: zodToJsonSchema(ListWorkersResponseSchema),
+ handler: async (args: any) => {
+ const parsedArgs = ListWorkersParamSchema.parse(args);
+ try {
+ return await getAllWorkers(parsedArgs.projectKey);
+ } catch(error) {
+ if (error instanceof JFrogError) {
+ return {
+ error: error.message,
+ status: error.status,
+ response: error.response
+ };
+ } else {
+ return "An error occurred while listing the workers: " + (error as Error).message;
+ }
+ }
+ }
+};
+
+const generateWorkerCodeTool = {
+ name: "jfrog_generate_worker_code",
+ description: "Generate a worker script for a specified action and settings, based on a provided description of its intended purpose",
+ inputSchema: zodToJsonSchema(GenerateWorkerCodeParamSchema),
+ //outputSchema: zodToJsonSchema(PromptToGenerateWorkerCode),
+ handler: async (args: any) => {
+ const parsedArgs = GenerateWorkerCodeParamSchema.parse(args);
+ return await getIntructionsForWorkerCodeGeneration(parsedArgs.action, parsedArgs.intendedPurpose);
+ }
+};
+
+const createWorkerTool = {
+ name: "jfrog_create_worker",
+ description: "Create a new worker based on a provided description of its intended purpose",
+ inputSchema: zodToJsonSchema(CreateWorkerParamSchema),
+ //outputSchema: zodToJsonSchema(CreateWorkerResponseSchema),
+ handler: async (args: any) => {
+ const parsedArgs = CreateWorkerParamSchema.parse(args);
+ try {
+ const response = await createWorker(parsedArgs);
+
+ // Success response
+ return {
+ success: true,
+ message: `Worker '${parsedArgs.name}' created successfully, but not enabled for security reason (see disclaimer)`,
+ data: response,
+ workerKey: parsedArgs.name,
+ editUrl: `${getJFrogBaseUrl()}ui/admin/workers/${parsedArgs.name}/edit`,
+ disclaimer: `CRITICAL_SECURITY_WARNING: The Worker is created but not enabled for security reason. You need to enable it manually.
+Before enabling it, please check what has been generated and test it, never trust AI generated code as it can have some mistakes.
+You can review the worker code using this link: ${getJFrogBaseUrl()}ui/admin/workers/${parsedArgs.name}/edit
+Here are some guidelines to help you:
+ - Check all HTTP requests made but the Worker, espacially those against the JFrog API.
+ - Mind the loops that can overwhelm the JPD API.`
+ };
+ } catch(error) {
+ // Error response
+ if (error instanceof JFrogError) {
+ return {
+ success: false,
+ error: error.message,
+ status: error.status,
+ details: {
+ response: error.response
+ }
+ };
+ } else {
+ return {
+ success: false,
+ error: "An error occurred while creating the worker: " + (error as Error).message,
+ status: 500
+ };
+ }
+ }
+ }
+};
+
+const getWorkerBestPracticesTool = {
+ name: "jfrog_get_worker_best_practices",
+ description: "Get best practices and recommendations for creating efficient JFrog Workers",
+ inputSchema: zodToJsonSchema(z.object({
+ action: z.string().optional().describe("Optional action name to get specific recommendations")
+ })),
+ handler: async (args: any) => {
+ const action = args.action;
+ let specificAdvice = "";
+
+ if (action) {
+ try {
+ const actions = await listActions();
+ const actionToUse = actions.find((a) => a.action.name === action);
+ if (actionToUse) {
+ if (actionToUse.filterType === "FILTER_REPO") {
+ specificAdvice = `\n\nSPECIFIC ADVICE FOR ACTION "${action}":\n- This action supports FILTER_REPO, so ALWAYS use the repositories list in Worker settings\n- Avoid checking repository types in your Worker code\n- Configure repositories: ["repo1", "repo2"] in settings for better performance`;
+ } else {
+ specificAdvice = `\n\nSPECIFIC ADVICE FOR ACTION "${action}":\n- This action does NOT support FILTER_REPO\n- You may need to implement repository filtering in your Worker code if required`;
+ }
+ }
+ } catch (error) {
+ specificAdvice = `\n\nCould not get specific advice for action "${action}": ${error}`;
+ }
+ }
+
+ return {
+ bestPractices: [
+ "🚀 PERFORMANCE: Use repository filters in settings instead of code checking when possible",
+ "🔧 MAINTENANCE: Configure repositories list in Worker settings for easy updates",
+ "⚡ EFFICIENCY: Avoid unnecessary API calls in Worker code",
+ "🛡️ RELIABILITY: Always implement proper error handling with try-catch",
+ "📊 MONITORING: Include meaningful console.log statements",
+ "🎯 FOCUS: Keep Worker code focused on business logic, not infrastructure concerns",
+ "🔍 GOOD PRACTICE: - In case of long task, try use properties to store the state of the task and use the properties to check where you are in the task",
+ "🔍 GOOD PRACTICE: - Avoid to load too much data in memory, use pagination when possible",
+ "🔍 GOOD PRACTICE: - Avoid too many HTTP requests, use AQL queries when possible",
+ "🔍 GOOD PRACTICE: - Avoid loading large file in memory, send it to a third party server to process it",
+ "🔍 GOOD PRACTICE: - Do not log too much, only log what is necessary",
+ ],
+ recommendations: [
+ "For FILTER_REPO actions: Use settings.repositories = ['repo1', 'repo2']",
+ "For non-FILTER_REPO actions: Implement filtering logic in code if needed",
+ "Always check action.filterType before deciding on filtering approach",
+ "Use includePatterns/excludePatterns for path-based filtering",
+ "Test Workers on development repositories first"
+ ],
+ specificAdvice: specificAdvice
+ };
+ }
+};
+
+export const WorkersTools = [
+ checkJfrogWorkersAvailabilityTool,
+ listWorkersActionsTool,
+ generateWorkerCodeTool,
+ createWorkerTool,
+ getWorkerBestPracticesTool,
+ listAllWorkersTool,
+];
+
+/* End of Tools creation Section */
\ No newline at end of file