diff --git a/.vscode/mcp.json b/.vscode/mcp.json index 9f969be..ec92075 100644 --- a/.vscode/mcp.json +++ b/.vscode/mcp.json @@ -2,7 +2,7 @@ "servers": { "salesforce": { "command": "node", - "args": ["${workspaceFolder}/bin/run.js", "--toolsets", "all", "--orgs", "DEFAULT_TARGET_ORG", "--no-telemetry"] + "args": ["./bin/run.js", "--toolsets", "all", "--orgs", "ALLOW_ALL_ORGS", "--no-telemetry"] } } } diff --git a/src/shared/params.ts b/src/shared/params.ts index e470a5d..5bd83f8 100644 --- a/src/shared/params.ts +++ b/src/shared/params.ts @@ -36,7 +36,11 @@ USAGE: ...for my 'test@example.com' user ...for the 'test@example.com' org`); -export const useToolingApiParam = z.boolean().optional().describe('Use Tooling API for the operation'); +export const useToolingApiParam = z + .boolean() + .optional() + .default(false) + .describe('Use Tooling API for the operation (default is false).'); export const baseAbsolutePathParam = z .string() diff --git a/src/tools/EXTERNAL/external-tool-query-org.ts b/src/tools/EXTERNAL/external-tool-query-org.ts new file mode 100644 index 0000000..dfc376a --- /dev/null +++ b/src/tools/EXTERNAL/external-tool-query-org.ts @@ -0,0 +1,82 @@ +/* + * Copyright 2025, Salesforce, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * External tool logic for querying Salesforce orgs + * This file contains only the pure business logic that can be exported to external repos + */ + +import { type Connection } from '@salesforce/core'; +import { type CallToolResult } from '@modelcontextprotocol/sdk/types.js'; +import { z } from 'zod'; + +// Define the parameter schema for this external tool +export const queryOrgParamsSchema = z.object({ + query: z.string().describe('SOQL query to run'), + useToolingApi: z + .boolean() + .optional() + .default(false) + .describe('Use Tooling API for the operation (default is false).'), +}); + +export type ExternalQueryOrgParams = z.infer; + +// Description of the external tool +export const queryOrgDescription = 'Run a SOQL query against a Salesforce org.'; + +// Annotations for the external tool +// https://modelcontextprotocol.io/specification/2025-06-18/schema#toolannotations +export const queryOrgAnnotations = { + title: 'Query Org', + openWorldHint: false, + readOnlyHint: true, +}; + +// Logic-only function that can be imported in external MCP Servers +export const queryOrgExecutable = async ( + params: ExternalQueryOrgParams, + config: { + connection: Connection; + } +): Promise => { + const { query, useToolingApi } = params; + const { connection } = config; + + try { + const result = useToolingApi ? await connection.tooling.query(query) : await connection.query(query); + + return { + isError: false, + content: [ + { + type: 'text', + text: `SOQL query results:\n\n${JSON.stringify(result, null, 2)}`, + }, + ], + }; + } catch (error) { + return { + isError: true, + content: [ + { + type: 'text', + text: `Failed to query org: ${error instanceof Error ? error.message : 'Unknown error'}`, + }, + ], + }; + } +}; diff --git a/src/tools/data/index.ts b/src/tools/data/index.ts index 0181b3b..380e1a3 100644 --- a/src/tools/data/index.ts +++ b/src/tools/data/index.ts @@ -14,4 +14,4 @@ * limitations under the License. */ -export * from './sf-query-org.js'; +export * from './sf-query-org-using-external.js'; diff --git a/src/tools/data/sf-query-org-using-external.ts b/src/tools/data/sf-query-org-using-external.ts new file mode 100644 index 0000000..bdc28a0 --- /dev/null +++ b/src/tools/data/sf-query-org-using-external.ts @@ -0,0 +1,67 @@ +/* + * Copyright 2025, Salesforce, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { z } from 'zod'; + +import { getConnection } from '../../shared/auth.js'; +import { directoryParam, usernameOrAliasParam, useToolingApiParam } from '../../shared/params.js'; +import { SfMcpServer } from '../../sf-mcp-server.js'; +import { + queryOrgDescription, + queryOrgAnnotations, + queryOrgExecutable, + queryOrgParamsSchema as queryOrgParamsSchemaExternal, +} from '../EXTERNAL/external-tool-query-org.js'; + +export const queryOrgParamsSchema = z.object({ + // Shared parameters (used only in tool setup) + usernameOrAlias: usernameOrAliasParam, + directory: directoryParam, + // Shared parameters (passed through to external tool) + useToolingApi: useToolingApiParam, + // External tool parameters (passed through to external tool) + query: queryOrgParamsSchemaExternal.shape.query, +}); + +export type QueryOrgOptions = z.infer; + +export const registerToolQueryOrg = (server: SfMcpServer): void => { + server.tool( + 'sf-query-org', + `${queryOrgDescription} + + EXAMPLES: + ...query Contacts that have a Phone listed + ...find the 3 newest Property__c records`, + queryOrgParamsSchema.shape, + queryOrgAnnotations, + async ({ directory, usernameOrAlias, query, useToolingApi }) => { + process.chdir(directory); + const connection = await getConnection(usernameOrAlias); + + const passThroughParams = { + query, + useToolingApi, + }; + + const passThroughConfig = { + connection, + }; + + return queryOrgExecutable(passThroughParams, passThroughConfig); + } + ); +};