diff --git a/packages/mcp-server/README.md b/packages/mcp-server/README.md index d3f1a0e9..f27c9213 100644 --- a/packages/mcp-server/README.md +++ b/packages/mcp-server/README.md @@ -8,9 +8,7 @@ It is generated with [Stainless](https://www.stainless.com/). See [the user guide](https://modelcontextprotocol.io/quickstart/user) for setup. -Once it's set up, add your MCP server to your `claude_desktop_config.json` file to enable it. - -The configuration file should be at: +Once it's set up, find your `claude_desktop_config.json` file: - macOS: `~/Library/Application Support/Claude/claude_desktop_config.json` - Windows: `%APPDATA%\Claude\claude_desktop_config.json` @@ -30,3 +28,92 @@ Add the following value to your `mcpServers` section. Make sure to provide any n } } ``` + +## Filtering tools + +You can run the package on the command line to discover and filter the set of tools that are exposed by the +MCP Server. This can be helpful for large APIs where including all endpoints at once is too much for your AI's +context window. + +You can filter by multiple aspects: + +- `--tool` includes a specific tool by name +- `--resource` includes all tools under a specific resource, and can have wildcards, e.g. `my.resource*` +- `--operation` includes just read (get/list) or just write operations + +See more information with `--help`: + +```sh +$ npx -y writer-sdk-mcp --help +``` + +All of these command-line options can be repeated, combined together, and have corresponding exclusion versions (e.g. `--no-tool`). + +Use `--list` to see the list of available tools, or see below. + +## Available Tools + +The following tools are available in this MCP server. + +### Resource `applications`: + +- `retrieve_applications` (`read`): Retrieves detailed information for a specific no-code application, including its configuration and current status. +- `list_applications` (`read`): Retrieves a paginated list of no-code applications with optional filtering and sorting capabilities. +- `generate_content_applications` (`write`): Generate content from an existing no-code application with inputs. + +### Resource `applications.jobs`: + +- `create_applications_jobs` (`write`): Generate content asynchronously from an existing application with inputs. +- `retrieve_applications_jobs` (`read`): Retrieves a single job created via the Async API. +- `list_applications_jobs` (`read`): Retrieve all jobs created via the async API, linked to the provided application ID (or alias). +- `retry_applications_jobs` (`write`): Re-triggers the async execution of a single job previously created via the Async api and terminated in error. + +### Resource `applications.graphs`: + +- `update_applications_graphs` (`write`): Updates the Knowledge Graphs listed and associates them with the no-code chat app to be used. +- `list_applications_graphs` (`read`): Retrieve Knowledge Graphs associated with a no-code chat application. + +### Resource `chat`: + +- `chat_chat` (`write`): Generate a chat completion based on the provided messages. The response shown below is for non-streaming. To learn about streaming responses, see the [chat completion guide](/api-guides/chat-completion). + +### Resource `completions`: + +- `create_completions` (`write`): Text generation + +### Resource `models`: + +- `list_models` (`read`): List models + +### Resource `graphs`: + +- `create_graphs` (`write`): Create a new Knowledge Graph. +- `retrieve_graphs` (`read`): Retrieve a Knowledge Graph. +- `update_graphs` (`write`): Update the name and description of a Knowledge Graph. +- `list_graphs` (`read`): Retrieve a list of Knowledge Graphs. +- `delete_graphs` (`write`): Delete a Knowledge Graph. +- `add_file_to_graph_graphs` (`write`): Add a file to a Knowledge Graph. +- `question_graphs` (`write`): Ask a question to specified Knowledge Graphs. +- `remove_file_from_graph_graphs` (`write`): Remove a file from a Knowledge Graph. + +### Resource `files`: + +- `retrieve_files` (`read`): Retrieve file +- `list_files` (`read`): List files +- `delete_files` (`write`): Delete file +- `download_files` (`read`): Download file +- `retry_files` (`write`): Retry failed files +- `upload_files` (`write`): Upload file + +### Resource `tools`: + +- `context_aware_splitting_tools` (`write`): Splits a long block of text (maximum 4000 words) into smaller chunks while preserving the semantic meaning of the text and context between the chunks. +- `parse_pdf_tools` (`write`): Parse PDF to other formats. + +### Resource `tools.comprehend`: + +- `medical_tools_comprehend` (`write`): Analyze unstructured medical text to extract entities labeled with standardized medical codes and confidence scores. + +### Resource `vision`: + +- `analyze_vision` (`write`): Submit images and a prompt to generate an analysis of the images. diff --git a/packages/mcp-server/jest.config.ts b/packages/mcp-server/jest.config.ts new file mode 100644 index 00000000..3080925d --- /dev/null +++ b/packages/mcp-server/jest.config.ts @@ -0,0 +1,17 @@ +import type { JestConfigWithTsJest } from 'ts-jest'; + +const config: JestConfigWithTsJest = { + preset: 'ts-jest/presets/default-esm', + testEnvironment: 'node', + transform: { + '^.+\\.(t|j)sx?$': ['@swc/jest', { sourceMaps: 'inline' }], + }, + moduleNameMapper: { + '^writer-sdk-mcp$': '/src/index.ts', + '^writer-sdk-mcp/(.*)$': '/src/$1', + }, + modulePathIgnorePatterns: ['/dist/'], + testPathIgnorePatterns: ['scripts'], +}; + +export default config; diff --git a/packages/mcp-server/package.json b/packages/mcp-server/package.json index 3f805df4..3b2dcef4 100644 --- a/packages/mcp-server/package.json +++ b/packages/mcp-server/package.json @@ -11,7 +11,7 @@ "packageManager": "yarn@1.22.22", "private": false, "scripts": { - "test": "echo 'no tests defined yet' && exit 1", + "test": "jest", "build": "bash ./build", "prepack": "echo 'to pack, run yarn build && (cd dist; yarn pack)' && exit 1", "prepublishOnly": "echo 'to publish, run yarn build && (cd dist; yarn publish)' && exit 1", @@ -23,7 +23,8 @@ }, "dependencies": { "writer-sdk": "file:../../dist/", - "@modelcontextprotocol/sdk": "^1.6.1" + "@modelcontextprotocol/sdk": "^1.6.1", + "yargs": "^17.7.2" }, "bin": { "mcp-server": "dist/index.js" diff --git a/packages/mcp-server/src/index.ts b/packages/mcp-server/src/index.ts index 038f6a81..fd6d7f86 100644 --- a/packages/mcp-server/src/index.ts +++ b/packages/mcp-server/src/index.ts @@ -1,10 +1,116 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; -import { server, init } from './server'; +import yargs from 'yargs'; +import { hideBin } from 'yargs/helpers'; +import { init, server } from './server'; +import { endpoints, Filter, query } from './tools'; async function main() { - init({ server }); + const opts = yargs(hideBin(process.argv)) + .option('tool', { + type: 'string', + array: true, + description: 'Include tools matching the specified names', + }) + .option('resource', { + type: 'string', + array: true, + description: 'Include tools matching the specified resources', + }) + .option('operation', { + type: 'string', + array: true, + choices: ['read', 'write'], + description: 'Include tools matching the specified operations', + }) + .option('tag', { + type: 'string', + array: true, + description: 'Include tools with the specified tags', + }) + .option('no-tool', { + type: 'string', + array: true, + description: 'Exclude tools matching the specified names', + }) + .option('no-resource', { + type: 'string', + array: true, + description: 'Exclude tools matching the specified resources', + }) + .option('no-operation', { + type: 'string', + array: true, + description: 'Exclude tools matching the specified operations', + }) + .option('no-tag', { + type: 'string', + array: true, + description: 'Exclude tools with the specified tags', + }) + .option('list', { + type: 'boolean', + description: 'List all tools and exit', + }) + .help(); + + for (const [command, desc] of examples()) { + opts.example(command, desc); + } + + const argv = opts.parseSync(); + + if (argv.list) { + listAllTools(); + return; + } + const filters: Filter[] = []; + + for (const tag of argv.tag || []) { + filters.push({ type: 'tag', op: 'include', value: tag }); + } + + for (const tag of argv.noTag || []) { + filters.push({ type: 'tag', op: 'exclude', value: tag }); + } + + for (const resource of argv.resource || []) { + filters.push({ type: 'resource', op: 'include', value: resource }); + } + + for (const resource of argv.noResource || []) { + filters.push({ type: 'resource', op: 'exclude', value: resource }); + } + + for (const tool of argv.tool || []) { + filters.push({ type: 'tool', op: 'include', value: tool }); + } + + for (const tool of argv.noTool || []) { + filters.push({ type: 'tool', op: 'exclude', value: tool }); + } + + for (const operation of argv.operation || []) { + filters.push({ type: 'operation', op: 'include', value: operation }); + } + + for (const operation of argv.noOperation || []) { + filters.push({ type: 'operation', op: 'exclude', value: operation }); + } + + const filteredEndpoints = query(filters, endpoints); + + if (filteredEndpoints.length === 0) { + console.error('No tools match the provided filters.'); + process.exit(1); + } + + console.error( + `MCP Server starting with ${filteredEndpoints.length} tools:`, + filteredEndpoints.map((e) => e.tool.name), + ); + + init({ server, endpoints: filteredEndpoints }); + const transport = new StdioServerTransport(); await server.connect(transport); console.error('MCP Server running on stdio'); @@ -16,3 +122,69 @@ if (require.main === module) { process.exit(1); }); } + +function listAllTools() { + if (endpoints.length === 0) { + console.error('No tools available.'); + return; + } + console.error('Available tools:\n'); + + // Group endpoints by resource + const resourceGroups = new Map(); + + for (const endpoint of endpoints) { + const resource = endpoint.metadata.resource; + if (!resourceGroups.has(resource)) { + resourceGroups.set(resource, []); + } + resourceGroups.get(resource)!.push(endpoint); + } + + // Sort resources alphabetically + const sortedResources = Array.from(resourceGroups.keys()).sort(); + + // Display hierarchically by resource + for (const resource of sortedResources) { + console.error(`Resource: ${resource}`); + + const resourceEndpoints = resourceGroups.get(resource)!; + // Sort endpoints by tool name + resourceEndpoints.sort((a, b) => a.tool.name.localeCompare(b.tool.name)); + + for (const endpoint of resourceEndpoints) { + const { + tool, + metadata: { operation, tags }, + } = endpoint; + + console.error(` - ${tool.name} (${operation}) ${tags.length > 0 ? `tags: ${tags.join(', ')}` : ''}`); + console.error(` Description: ${tool.description}`); + } + console.error(''); + } +} + +function examples(): [string, string][] { + const firstEndpoint = endpoints[0]!; + const secondEndpoint = + endpoints.find((e) => e.metadata.resource !== firstEndpoint.metadata.resource) || endpoints[1]; + const tag = endpoints.find((e) => e.metadata.tags.length > 0)?.metadata.tags[0]; + const otherEndpoint = secondEndpoint || firstEndpoint; + + return [ + [ + `--tool="${firstEndpoint.tool.name}" ${secondEndpoint ? `--tool="${secondEndpoint.tool.name}"` : ''}`, + 'Include tools by name', + ], + [ + `--resource="${firstEndpoint.metadata.resource}" --operation="read"`, + 'Filter by resource and operation', + ], + [ + `--resource="${otherEndpoint.metadata.resource}*" --no-tool="${otherEndpoint.tool.name}"`, + 'Use resource wildcards and exclusions', + ], + ...(tag ? [[`--tag="${tag}"`, 'Filter based on tags'] as [string, string]] : []), + ]; +} diff --git a/packages/mcp-server/src/server.ts b/packages/mcp-server/src/server.ts index 06572eb8..db57eab1 100644 --- a/packages/mcp-server/src/server.ts +++ b/packages/mcp-server/src/server.ts @@ -2,10 +2,10 @@ import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; -import { tools, handlers, HandlerFunction } from './tools'; +import { endpoints, HandlerFunction } from './tools'; import { CallToolRequestSchema, ListToolsRequestSchema, Tool } from '@modelcontextprotocol/sdk/types.js'; import Writer from 'writer-sdk'; -export { tools, handlers } from './tools'; +export { endpoints } from './tools'; // Create server instance export const server = new McpServer( @@ -27,24 +27,27 @@ export const server = new McpServer( export function init(params: { server: Server | McpServer; client?: Writer; - tools?: Tool[]; - handlers?: Record; + endpoints?: { tool: Tool; handler: HandlerFunction }[]; }) { const server = params.server instanceof McpServer ? params.server.server : params.server; - const providedTools = params.tools || tools; - const providedHandlers = params.handlers || handlers; + const providedEndpoints = params.endpoints || endpoints; + const tools = providedEndpoints.map((endpoint) => endpoint.tool); + const handlers = Object.fromEntries( + providedEndpoints.map((endpoint) => [endpoint.tool.name, endpoint.handler]), + ); + const client = params.client || new Writer({}); server.setRequestHandler(ListToolsRequestSchema, async () => { return { - tools: providedTools, + tools, }; }); server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; - const handler = providedHandlers[name]; + const handler = handlers[name]; if (!handler) { throw new Error(`Unknown tool: ${name}`); } diff --git a/packages/mcp-server/src/tools/applications/generate-content-applications.ts b/packages/mcp-server/src/tools/applications/generate-content-applications.ts index a42d8851..16724238 100644 --- a/packages/mcp-server/src/tools/applications/generate-content-applications.ts +++ b/packages/mcp-server/src/tools/applications/generate-content-applications.ts @@ -1,8 +1,15 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import type { Metadata } from '../'; import Writer from 'writer-sdk'; +export const metadata: Metadata = { + resource: 'applications', + operation: 'write', + tags: [], +}; + export const tool: Tool = { name: 'generate_content_applications', description: 'Generate content from an existing no-code application with inputs.', @@ -92,4 +99,4 @@ export const handler = (client: Writer, args: any) => { return client.applications.generateContent(application_id, body); }; -export default { tool, handler }; +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/applications/graphs/list-applications-graphs.ts b/packages/mcp-server/src/tools/applications/graphs/list-applications-graphs.ts index b5544917..42ef6dcf 100644 --- a/packages/mcp-server/src/tools/applications/graphs/list-applications-graphs.ts +++ b/packages/mcp-server/src/tools/applications/graphs/list-applications-graphs.ts @@ -1,8 +1,15 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import type { Metadata } from '../../'; import Writer from 'writer-sdk'; +export const metadata: Metadata = { + resource: 'applications.graphs', + operation: 'read', + tags: [], +}; + export const tool: Tool = { name: 'list_applications_graphs', description: 'Retrieve Knowledge Graphs associated with a no-code chat application.', @@ -21,4 +28,4 @@ export const handler = (client: Writer, args: any) => { return client.applications.graphs.list(application_id); }; -export default { tool, handler }; +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/applications/graphs/update-applications-graphs.ts b/packages/mcp-server/src/tools/applications/graphs/update-applications-graphs.ts index 5cb49f2b..f0424710 100644 --- a/packages/mcp-server/src/tools/applications/graphs/update-applications-graphs.ts +++ b/packages/mcp-server/src/tools/applications/graphs/update-applications-graphs.ts @@ -1,8 +1,15 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import type { Metadata } from '../../'; import Writer from 'writer-sdk'; +export const metadata: Metadata = { + resource: 'applications.graphs', + operation: 'write', + tags: [], +}; + export const tool: Tool = { name: 'update_applications_graphs', description: @@ -31,4 +38,4 @@ export const handler = (client: Writer, args: any) => { return client.applications.graphs.update(application_id, body); }; -export default { tool, handler }; +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/applications/jobs/create-applications-jobs.ts b/packages/mcp-server/src/tools/applications/jobs/create-applications-jobs.ts index d7e8414e..57e4c136 100644 --- a/packages/mcp-server/src/tools/applications/jobs/create-applications-jobs.ts +++ b/packages/mcp-server/src/tools/applications/jobs/create-applications-jobs.ts @@ -1,8 +1,15 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import type { Metadata } from '../../'; import Writer from 'writer-sdk'; +export const metadata: Metadata = { + resource: 'applications.jobs', + operation: 'write', + tags: [], +}; + export const tool: Tool = { name: 'create_applications_jobs', description: 'Generate content asynchronously from an existing application with inputs.', @@ -45,4 +52,4 @@ export const handler = (client: Writer, args: any) => { return client.applications.jobs.create(application_id, body); }; -export default { tool, handler }; +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/applications/jobs/list-applications-jobs.ts b/packages/mcp-server/src/tools/applications/jobs/list-applications-jobs.ts index fc46c36f..1cf227d7 100644 --- a/packages/mcp-server/src/tools/applications/jobs/list-applications-jobs.ts +++ b/packages/mcp-server/src/tools/applications/jobs/list-applications-jobs.ts @@ -1,8 +1,15 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import type { Metadata } from '../../'; import Writer from 'writer-sdk'; +export const metadata: Metadata = { + resource: 'applications.jobs', + operation: 'read', + tags: [], +}; + export const tool: Tool = { name: 'list_applications_jobs', description: @@ -36,4 +43,4 @@ export const handler = (client: Writer, args: any) => { return client.applications.jobs.list(application_id, body); }; -export default { tool, handler }; +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/applications/jobs/retrieve-applications-jobs.ts b/packages/mcp-server/src/tools/applications/jobs/retrieve-applications-jobs.ts index fb642a61..fc742636 100644 --- a/packages/mcp-server/src/tools/applications/jobs/retrieve-applications-jobs.ts +++ b/packages/mcp-server/src/tools/applications/jobs/retrieve-applications-jobs.ts @@ -1,8 +1,15 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import type { Metadata } from '../../'; import Writer from 'writer-sdk'; +export const metadata: Metadata = { + resource: 'applications.jobs', + operation: 'read', + tags: [], +}; + export const tool: Tool = { name: 'retrieve_applications_jobs', description: 'Retrieves a single job created via the Async API.', @@ -21,4 +28,4 @@ export const handler = (client: Writer, args: any) => { return client.applications.jobs.retrieve(job_id); }; -export default { tool, handler }; +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/applications/jobs/retry-applications-jobs.ts b/packages/mcp-server/src/tools/applications/jobs/retry-applications-jobs.ts index b99fb9c3..7bd34357 100644 --- a/packages/mcp-server/src/tools/applications/jobs/retry-applications-jobs.ts +++ b/packages/mcp-server/src/tools/applications/jobs/retry-applications-jobs.ts @@ -1,8 +1,15 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import type { Metadata } from '../../'; import Writer from 'writer-sdk'; +export const metadata: Metadata = { + resource: 'applications.jobs', + operation: 'write', + tags: [], +}; + export const tool: Tool = { name: 'retry_applications_jobs', description: @@ -22,4 +29,4 @@ export const handler = (client: Writer, args: any) => { return client.applications.jobs.retry(job_id); }; -export default { tool, handler }; +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/applications/list-applications.ts b/packages/mcp-server/src/tools/applications/list-applications.ts index bd63b81e..76a9ef5a 100644 --- a/packages/mcp-server/src/tools/applications/list-applications.ts +++ b/packages/mcp-server/src/tools/applications/list-applications.ts @@ -1,8 +1,15 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import type { Metadata } from '../'; import Writer from 'writer-sdk'; +export const metadata: Metadata = { + resource: 'applications', + operation: 'read', + tags: [], +}; + export const tool: Tool = { name: 'list_applications', description: @@ -42,4 +49,4 @@ export const handler = (client: Writer, args: any) => { return client.applications.list(body); }; -export default { tool, handler }; +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/applications/retrieve-applications.ts b/packages/mcp-server/src/tools/applications/retrieve-applications.ts index 288cfbba..13dadf43 100644 --- a/packages/mcp-server/src/tools/applications/retrieve-applications.ts +++ b/packages/mcp-server/src/tools/applications/retrieve-applications.ts @@ -1,8 +1,15 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import type { Metadata } from '../'; import Writer from 'writer-sdk'; +export const metadata: Metadata = { + resource: 'applications', + operation: 'read', + tags: [], +}; + export const tool: Tool = { name: 'retrieve_applications', description: @@ -22,4 +29,4 @@ export const handler = (client: Writer, args: any) => { return client.applications.retrieve(application_id); }; -export default { tool, handler }; +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/chat/chat-chat.ts b/packages/mcp-server/src/tools/chat/chat-chat.ts index df15f763..c841af20 100644 --- a/packages/mcp-server/src/tools/chat/chat-chat.ts +++ b/packages/mcp-server/src/tools/chat/chat-chat.ts @@ -1,8 +1,15 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import type { Metadata } from '../'; import Writer from 'writer-sdk'; +export const metadata: Metadata = { + resource: 'chat', + operation: 'write', + tags: [], +}; + export const tool: Tool = { name: 'chat_chat', description: @@ -503,4 +510,4 @@ export const handler = (client: Writer, args: any) => { return client.chat.chat(body); }; -export default { tool, handler }; +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/completions/create-completions.ts b/packages/mcp-server/src/tools/completions/create-completions.ts index c9ae1e2b..728ee3e0 100644 --- a/packages/mcp-server/src/tools/completions/create-completions.ts +++ b/packages/mcp-server/src/tools/completions/create-completions.ts @@ -1,8 +1,15 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import type { Metadata } from '../'; import Writer from 'writer-sdk'; +export const metadata: Metadata = { + resource: 'completions', + operation: 'write', + tags: [], +}; + export const tool: Tool = { name: 'create_completions', description: 'Text generation', @@ -134,4 +141,4 @@ export const handler = (client: Writer, args: any) => { return client.completions.create(body); }; -export default { tool, handler }; +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/files/delete-files.ts b/packages/mcp-server/src/tools/files/delete-files.ts index ce8d5764..43db0856 100644 --- a/packages/mcp-server/src/tools/files/delete-files.ts +++ b/packages/mcp-server/src/tools/files/delete-files.ts @@ -1,8 +1,15 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import type { Metadata } from '../'; import Writer from 'writer-sdk'; +export const metadata: Metadata = { + resource: 'files', + operation: 'write', + tags: [], +}; + export const tool: Tool = { name: 'delete_files', description: 'Delete file', @@ -21,4 +28,4 @@ export const handler = (client: Writer, args: any) => { return client.files.delete(file_id); }; -export default { tool, handler }; +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/files/download-files.ts b/packages/mcp-server/src/tools/files/download-files.ts index 9179522b..eb6c2b98 100644 --- a/packages/mcp-server/src/tools/files/download-files.ts +++ b/packages/mcp-server/src/tools/files/download-files.ts @@ -1,8 +1,15 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import type { Metadata } from '../'; import Writer from 'writer-sdk'; +export const metadata: Metadata = { + resource: 'files', + operation: 'read', + tags: [], +}; + export const tool: Tool = { name: 'download_files', description: 'Download file', @@ -21,4 +28,4 @@ export const handler = (client: Writer, args: any) => { return client.files.download(file_id); }; -export default { tool, handler }; +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/files/list-files.ts b/packages/mcp-server/src/tools/files/list-files.ts index c1a8a1a3..233ca7ff 100644 --- a/packages/mcp-server/src/tools/files/list-files.ts +++ b/packages/mcp-server/src/tools/files/list-files.ts @@ -1,8 +1,15 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import type { Metadata } from '../'; import Writer from 'writer-sdk'; +export const metadata: Metadata = { + resource: 'files', + operation: 'read', + tags: [], +}; + export const tool: Tool = { name: 'list_files', description: 'List files', @@ -49,4 +56,4 @@ export const handler = (client: Writer, args: any) => { return client.files.list(body); }; -export default { tool, handler }; +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/files/retrieve-files.ts b/packages/mcp-server/src/tools/files/retrieve-files.ts index e741aa2a..279fed7d 100644 --- a/packages/mcp-server/src/tools/files/retrieve-files.ts +++ b/packages/mcp-server/src/tools/files/retrieve-files.ts @@ -1,8 +1,15 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import type { Metadata } from '../'; import Writer from 'writer-sdk'; +export const metadata: Metadata = { + resource: 'files', + operation: 'read', + tags: [], +}; + export const tool: Tool = { name: 'retrieve_files', description: 'Retrieve file', @@ -21,4 +28,4 @@ export const handler = (client: Writer, args: any) => { return client.files.retrieve(file_id); }; -export default { tool, handler }; +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/files/retry-files.ts b/packages/mcp-server/src/tools/files/retry-files.ts index 3ef13bd6..1805d966 100644 --- a/packages/mcp-server/src/tools/files/retry-files.ts +++ b/packages/mcp-server/src/tools/files/retry-files.ts @@ -1,8 +1,15 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import type { Metadata } from '../'; import Writer from 'writer-sdk'; +export const metadata: Metadata = { + resource: 'files', + operation: 'write', + tags: [], +}; + export const tool: Tool = { name: 'retry_files', description: 'Retry failed files', @@ -25,4 +32,4 @@ export const handler = (client: Writer, args: any) => { return client.files.retry(body); }; -export default { tool, handler }; +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/files/upload-files.ts b/packages/mcp-server/src/tools/files/upload-files.ts index faaf8420..e78a5e8e 100644 --- a/packages/mcp-server/src/tools/files/upload-files.ts +++ b/packages/mcp-server/src/tools/files/upload-files.ts @@ -1,8 +1,15 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import type { Metadata } from '../'; import Writer from 'writer-sdk'; +export const metadata: Metadata = { + resource: 'files', + operation: 'write', + tags: [], +}; + export const tool: Tool = { name: 'upload_files', description: 'Upload file', @@ -24,4 +31,4 @@ export const handler = (client: Writer, args: any) => { return client.files.upload(body); }; -export default { tool, handler }; +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/graphs/add-file-to-graph-graphs.ts b/packages/mcp-server/src/tools/graphs/add-file-to-graph-graphs.ts index 0d1dc2d9..d46a9f8f 100644 --- a/packages/mcp-server/src/tools/graphs/add-file-to-graph-graphs.ts +++ b/packages/mcp-server/src/tools/graphs/add-file-to-graph-graphs.ts @@ -1,8 +1,15 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import type { Metadata } from '../'; import Writer from 'writer-sdk'; +export const metadata: Metadata = { + resource: 'graphs', + operation: 'write', + tags: [], +}; + export const tool: Tool = { name: 'add_file_to_graph_graphs', description: 'Add a file to a Knowledge Graph.', @@ -25,4 +32,4 @@ export const handler = (client: Writer, args: any) => { return client.graphs.addFileToGraph(graph_id, body); }; -export default { tool, handler }; +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/graphs/create-graphs.ts b/packages/mcp-server/src/tools/graphs/create-graphs.ts index e39dc80a..16eaae0b 100644 --- a/packages/mcp-server/src/tools/graphs/create-graphs.ts +++ b/packages/mcp-server/src/tools/graphs/create-graphs.ts @@ -1,8 +1,15 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import type { Metadata } from '../'; import Writer from 'writer-sdk'; +export const metadata: Metadata = { + resource: 'graphs', + operation: 'write', + tags: [], +}; + export const tool: Tool = { name: 'create_graphs', description: 'Create a new Knowledge Graph.', @@ -28,4 +35,4 @@ export const handler = (client: Writer, args: any) => { return client.graphs.create(body); }; -export default { tool, handler }; +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/graphs/delete-graphs.ts b/packages/mcp-server/src/tools/graphs/delete-graphs.ts index bf0c7784..f6b80575 100644 --- a/packages/mcp-server/src/tools/graphs/delete-graphs.ts +++ b/packages/mcp-server/src/tools/graphs/delete-graphs.ts @@ -1,8 +1,15 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import type { Metadata } from '../'; import Writer from 'writer-sdk'; +export const metadata: Metadata = { + resource: 'graphs', + operation: 'write', + tags: [], +}; + export const tool: Tool = { name: 'delete_graphs', description: 'Delete a Knowledge Graph.', @@ -21,4 +28,4 @@ export const handler = (client: Writer, args: any) => { return client.graphs.delete(graph_id); }; -export default { tool, handler }; +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/graphs/list-graphs.ts b/packages/mcp-server/src/tools/graphs/list-graphs.ts index 0cbfa081..9cd8ce98 100644 --- a/packages/mcp-server/src/tools/graphs/list-graphs.ts +++ b/packages/mcp-server/src/tools/graphs/list-graphs.ts @@ -1,8 +1,15 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import type { Metadata } from '../'; import Writer from 'writer-sdk'; +export const metadata: Metadata = { + resource: 'graphs', + operation: 'read', + tags: [], +}; + export const tool: Tool = { name: 'list_graphs', description: 'Retrieve a list of Knowledge Graphs.', @@ -39,4 +46,4 @@ export const handler = (client: Writer, args: any) => { return client.graphs.list(body); }; -export default { tool, handler }; +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/graphs/question-graphs.ts b/packages/mcp-server/src/tools/graphs/question-graphs.ts index 415ce9cb..ae5d32c2 100644 --- a/packages/mcp-server/src/tools/graphs/question-graphs.ts +++ b/packages/mcp-server/src/tools/graphs/question-graphs.ts @@ -1,8 +1,15 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import type { Metadata } from '../'; import Writer from 'writer-sdk'; +export const metadata: Metadata = { + resource: 'graphs', + operation: 'write', + tags: [], +}; + export const tool: Tool = { name: 'question_graphs', description: 'Ask a question to specified Knowledge Graphs.', @@ -70,4 +77,4 @@ export const handler = (client: Writer, args: any) => { return client.graphs.question(body); }; -export default { tool, handler }; +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/graphs/remove-file-from-graph-graphs.ts b/packages/mcp-server/src/tools/graphs/remove-file-from-graph-graphs.ts index 702dd4c7..e52690dd 100644 --- a/packages/mcp-server/src/tools/graphs/remove-file-from-graph-graphs.ts +++ b/packages/mcp-server/src/tools/graphs/remove-file-from-graph-graphs.ts @@ -1,8 +1,15 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import type { Metadata } from '../'; import Writer from 'writer-sdk'; +export const metadata: Metadata = { + resource: 'graphs', + operation: 'write', + tags: [], +}; + export const tool: Tool = { name: 'remove_file_from_graph_graphs', description: 'Remove a file from a Knowledge Graph.', @@ -24,4 +31,4 @@ export const handler = (client: Writer, args: any) => { return client.graphs.removeFileFromGraph(file_id, body); }; -export default { tool, handler }; +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/graphs/retrieve-graphs.ts b/packages/mcp-server/src/tools/graphs/retrieve-graphs.ts index a4db022c..f5ad7058 100644 --- a/packages/mcp-server/src/tools/graphs/retrieve-graphs.ts +++ b/packages/mcp-server/src/tools/graphs/retrieve-graphs.ts @@ -1,8 +1,15 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import type { Metadata } from '../'; import Writer from 'writer-sdk'; +export const metadata: Metadata = { + resource: 'graphs', + operation: 'read', + tags: [], +}; + export const tool: Tool = { name: 'retrieve_graphs', description: 'Retrieve a Knowledge Graph.', @@ -21,4 +28,4 @@ export const handler = (client: Writer, args: any) => { return client.graphs.retrieve(graph_id); }; -export default { tool, handler }; +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/graphs/update-graphs.ts b/packages/mcp-server/src/tools/graphs/update-graphs.ts index 43862d8a..a9e12203 100644 --- a/packages/mcp-server/src/tools/graphs/update-graphs.ts +++ b/packages/mcp-server/src/tools/graphs/update-graphs.ts @@ -1,8 +1,15 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import type { Metadata } from '../'; import Writer from 'writer-sdk'; +export const metadata: Metadata = { + resource: 'graphs', + operation: 'write', + tags: [], +}; + export const tool: Tool = { name: 'update_graphs', description: 'Update the name and description of a Knowledge Graph.', @@ -31,4 +38,4 @@ export const handler = (client: Writer, args: any) => { return client.graphs.update(graph_id, body); }; -export default { tool, handler }; +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/index.ts b/packages/mcp-server/src/tools/index.ts index 6078d718..8f17a2ef 100644 --- a/packages/mcp-server/src/tools/index.ts +++ b/packages/mcp-server/src/tools/index.ts @@ -34,14 +34,24 @@ import parse_pdf_tools from './tools/parse-pdf-tools'; import medical_tools_comprehend from './tools/comprehend/medical-tools-comprehend'; import analyze_vision from './vision/analyze-vision'; -export const tools: Tool[] = []; - export type HandlerFunction = (client: Writer, args: any) => Promise; -export const handlers: Record = {}; -function addEndpoint(endpoint: { tool: Tool; handler: HandlerFunction }) { - tools.push(endpoint.tool); - handlers[endpoint.tool.name] = endpoint.handler; +export type Metadata = { + resource: string; + operation: 'read' | 'write'; + tags: string[]; +}; + +export type Endpoint = { + metadata: Metadata; + tool: Tool; + handler: HandlerFunction; +}; + +export const endpoints: Endpoint[] = []; + +function addEndpoint(endpoint: Endpoint) { + endpoints.push(endpoint); } addEndpoint(retrieve_applications); @@ -74,3 +84,49 @@ addEndpoint(context_aware_splitting_tools); addEndpoint(parse_pdf_tools); addEndpoint(medical_tools_comprehend); addEndpoint(analyze_vision); + +export type Filter = { + type: 'resource' | 'operation' | 'tag' | 'tool'; + op: 'include' | 'exclude'; + value: string; +}; + +export function query(filters: Filter[], endpoints: Endpoint[]): Endpoint[] { + if (filters.length === 0) { + return endpoints; + } + const allExcludes = filters.every((filter) => filter.op === 'exclude'); + + return endpoints.filter((endpoint: Endpoint) => { + let included = false || allExcludes; + + for (const filter of filters) { + if (match(filter, endpoint)) { + included = filter.op === 'include'; + } + } + + return included; + }); +} + +function match({ type, value }: Filter, endpoint: Endpoint): boolean { + switch (type) { + case 'resource': { + const regexStr = '^' + normalizeResource(value).replace(/\*/g, '.*') + '$'; + const regex = new RegExp(regexStr); + console.error('regex is', regexStr); + return regex.test(normalizeResource(endpoint.metadata.resource)); + } + case 'operation': + return endpoint.metadata.operation === value; + case 'tag': + return endpoint.metadata.tags.includes(value); + case 'tool': + return endpoint.tool.name === value; + } +} + +function normalizeResource(resource: string): string { + return resource.toLowerCase().replace(/[^a-z.*\-_]*/g, ''); +} diff --git a/packages/mcp-server/src/tools/models/list-models.ts b/packages/mcp-server/src/tools/models/list-models.ts index 65221814..11c4a8b5 100644 --- a/packages/mcp-server/src/tools/models/list-models.ts +++ b/packages/mcp-server/src/tools/models/list-models.ts @@ -1,8 +1,15 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import type { Metadata } from '../'; import Writer from 'writer-sdk'; +export const metadata: Metadata = { + resource: 'models', + operation: 'read', + tags: [], +}; + export const tool: Tool = { name: 'list_models', description: 'List models', @@ -17,4 +24,4 @@ export const handler = (client: Writer, args: any) => { return client.models.list(); }; -export default { tool, handler }; +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/tools/comprehend/medical-tools-comprehend.ts b/packages/mcp-server/src/tools/tools/comprehend/medical-tools-comprehend.ts index 887f300d..8727c1b7 100644 --- a/packages/mcp-server/src/tools/tools/comprehend/medical-tools-comprehend.ts +++ b/packages/mcp-server/src/tools/tools/comprehend/medical-tools-comprehend.ts @@ -1,8 +1,15 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import type { Metadata } from '../../'; import Writer from 'writer-sdk'; +export const metadata: Metadata = { + resource: 'tools.comprehend', + operation: 'write', + tags: [], +}; + export const tool: Tool = { name: 'medical_tools_comprehend', description: @@ -30,4 +37,4 @@ export const handler = (client: Writer, args: any) => { return client.tools.comprehend.medical(body); }; -export default { tool, handler }; +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/tools/context-aware-splitting-tools.ts b/packages/mcp-server/src/tools/tools/context-aware-splitting-tools.ts index 7c8a51be..d794ffe5 100644 --- a/packages/mcp-server/src/tools/tools/context-aware-splitting-tools.ts +++ b/packages/mcp-server/src/tools/tools/context-aware-splitting-tools.ts @@ -1,8 +1,15 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import type { Metadata } from '../'; import Writer from 'writer-sdk'; +export const metadata: Metadata = { + resource: 'tools', + operation: 'write', + tags: [], +}; + export const tool: Tool = { name: 'context_aware_splitting_tools', description: @@ -30,4 +37,4 @@ export const handler = (client: Writer, args: any) => { return client.tools.contextAwareSplitting(body); }; -export default { tool, handler }; +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/tools/parse-pdf-tools.ts b/packages/mcp-server/src/tools/tools/parse-pdf-tools.ts index 881e0855..e2dc6da8 100644 --- a/packages/mcp-server/src/tools/tools/parse-pdf-tools.ts +++ b/packages/mcp-server/src/tools/tools/parse-pdf-tools.ts @@ -1,8 +1,15 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import type { Metadata } from '../'; import Writer from 'writer-sdk'; +export const metadata: Metadata = { + resource: 'tools', + operation: 'write', + tags: [], +}; + export const tool: Tool = { name: 'parse_pdf_tools', description: 'Parse PDF to other formats.', @@ -27,4 +34,4 @@ export const handler = (client: Writer, args: any) => { return client.tools.parsePdf(file_id, body); }; -export default { tool, handler }; +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/vision/analyze-vision.ts b/packages/mcp-server/src/tools/vision/analyze-vision.ts index b1108bb6..d859c7c7 100644 --- a/packages/mcp-server/src/tools/vision/analyze-vision.ts +++ b/packages/mcp-server/src/tools/vision/analyze-vision.ts @@ -1,8 +1,15 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import type { Metadata } from '../'; import Writer from 'writer-sdk'; +export const metadata: Metadata = { + resource: 'vision', + operation: 'write', + tags: [], +}; + export const tool: Tool = { name: 'analyze_vision', description: 'Submit images and a prompt to generate an analysis of the images.', @@ -49,4 +56,4 @@ export const handler = (client: Writer, args: any) => { return client.vision.analyze(body); }; -export default { tool, handler }; +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/tests/tools.test.ts b/packages/mcp-server/tests/tools.test.ts new file mode 100644 index 00000000..79979e3c --- /dev/null +++ b/packages/mcp-server/tests/tools.test.ts @@ -0,0 +1,226 @@ +import { Endpoint, Filter, Metadata, query } from '../src/tools'; + +describe('Endpoint filtering', () => { + const endpoints: Endpoint[] = [ + endpoint({ + resource: 'user', + operation: 'read', + tags: ['admin'], + toolName: 'retrieve_user', + }), + endpoint({ + resource: 'user.profile', + operation: 'write', + tags: [], + toolName: 'create_user_profile', + }), + endpoint({ + resource: 'user.profile', + operation: 'read', + tags: [], + toolName: 'get_user_profile', + }), + endpoint({ + resource: 'user.roles.permissions', + operation: 'write', + tags: ['admin', 'security'], + toolName: 'update_user_role_permissions', + }), + endpoint({ + resource: 'documents.metadata.tags', + operation: 'write', + tags: ['taxonomy', 'metadata'], + toolName: 'create_document_metadata_tags', + }), + endpoint({ + resource: 'organization.settings', + operation: 'read', + tags: ['admin', 'configuration'], + toolName: 'get_organization_settings', + }), + ]; + + const tests: { name: string; filters: Filter[]; expected: string[] }[] = [ + { + name: 'match all', + filters: [], + expected: endpoints.map((e) => e.tool.name), + }, + + // Resource tests + { + name: 'simple resource', + filters: [{ type: 'resource', op: 'include', value: 'user' }], + expected: ['retrieve_user'], + }, + { + name: 'exclude resource', + filters: [{ type: 'resource', op: 'exclude', value: 'user' }], + expected: [ + 'create_user_profile', + 'get_user_profile', + 'update_user_role_permissions', + 'create_document_metadata_tags', + 'get_organization_settings', + ], + }, + { + name: 'resource and subresources', + filters: [{ type: 'resource', op: 'include', value: 'user*' }], + expected: ['retrieve_user', 'create_user_profile', 'get_user_profile', 'update_user_role_permissions'], + }, + { + name: 'just subresources', + filters: [{ type: 'resource', op: 'include', value: 'user.*' }], + expected: ['create_user_profile', 'get_user_profile', 'update_user_role_permissions'], + }, + { + name: 'specific subresource', + filters: [{ type: 'resource', op: 'include', value: 'user.roles.permissions' }], + expected: ['update_user_role_permissions'], + }, + { + name: 'deep wildcard match', + filters: [{ type: 'resource', op: 'include', value: '*.*.tags' }], + expected: ['create_document_metadata_tags'], + }, + + // Operation tests + { + name: 'read operation', + filters: [{ type: 'operation', op: 'include', value: 'read' }], + expected: ['retrieve_user', 'get_user_profile', 'get_organization_settings'], + }, + { + name: 'write operation', + filters: [{ type: 'operation', op: 'include', value: 'write' }], + expected: ['create_user_profile', 'update_user_role_permissions', 'create_document_metadata_tags'], + }, + { + name: 'resource and operation combined', + filters: [ + { type: 'resource', op: 'include', value: 'user.profile' }, + { type: 'operation', op: 'exclude', value: 'write' }, + ], + expected: ['get_user_profile'], + }, + + // Tag tests + { + name: 'admin tag', + filters: [{ type: 'tag', op: 'include', value: 'admin' }], + expected: ['retrieve_user', 'update_user_role_permissions', 'get_organization_settings'], + }, + { + name: 'taxonomy tag', + filters: [{ type: 'tag', op: 'include', value: 'taxonomy' }], + expected: ['create_document_metadata_tags'], + }, + { + name: 'multiple tags (OR logic)', + filters: [ + { type: 'tag', op: 'include', value: 'admin' }, + { type: 'tag', op: 'include', value: 'security' }, + ], + expected: ['retrieve_user', 'update_user_role_permissions', 'get_organization_settings'], + }, + { + name: 'excluding a tag', + filters: [ + { type: 'tag', op: 'include', value: 'admin' }, + { type: 'tag', op: 'exclude', value: 'security' }, + ], + expected: ['retrieve_user', 'get_organization_settings'], + }, + + // Tool name tests + { + name: 'tool name match', + filters: [{ type: 'tool', op: 'include', value: 'get_organization_settings' }], + expected: ['get_organization_settings'], + }, + { + name: 'two tools match', + filters: [ + { type: 'tool', op: 'include', value: 'get_organization_settings' }, + { type: 'tool', op: 'include', value: 'create_user_profile' }, + ], + expected: ['create_user_profile', 'get_organization_settings'], + }, + { + name: 'excluding tool by name', + filters: [ + { type: 'resource', op: 'include', value: 'user*' }, + { type: 'tool', op: 'exclude', value: 'retrieve_user' }, + ], + expected: ['create_user_profile', 'get_user_profile', 'update_user_role_permissions'], + }, + + // Complex combinations + { + name: 'complex filter: read operations with admin tag', + filters: [ + { type: 'operation', op: 'include', value: 'read' }, + { type: 'tag', op: 'include', value: 'admin' }, + ], + expected: [ + 'retrieve_user', + 'get_user_profile', + 'update_user_role_permissions', + 'get_organization_settings', + ], + }, + { + name: 'complex filter: user resources with no tags', + filters: [ + { type: 'resource', op: 'include', value: 'user.profile' }, + { type: 'tag', op: 'exclude', value: 'admin' }, + ], + expected: ['create_user_profile', 'get_user_profile'], + }, + { + name: 'complex filter: user resources and tags', + filters: [ + { type: 'resource', op: 'include', value: 'user.profile' }, + { type: 'tag', op: 'include', value: 'admin' }, + ], + expected: [ + 'retrieve_user', + 'create_user_profile', + 'get_user_profile', + 'update_user_role_permissions', + 'get_organization_settings', + ], + }, + ]; + + tests.forEach((test) => { + it(`filters by ${test.name}`, () => { + console.log(`Running test: ${test.name}`); + const filtered = query(test.filters, endpoints); + expect(filtered.map((e) => e.tool.name)).toEqual(test.expected); + }); + }); +}); + +function endpoint({ + resource, + operation, + tags, + toolName, +}: { + resource: string; + operation: Metadata['operation']; + tags: string[]; + toolName: string; +}): Endpoint { + return { + metadata: { + resource, + operation, + tags, + }, + tool: { name: toolName, inputSchema: { type: 'object', properties: {} } }, + handler: jest.fn(), + }; +}