Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 90 additions & 3 deletions packages/mcp-server/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand All @@ -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.
17 changes: 17 additions & 0 deletions packages/mcp-server/jest.config.ts
Original file line number Diff line number Diff line change
@@ -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$': '<rootDir>/src/index.ts',
'^writer-sdk-mcp/(.*)$': '<rootDir>/src/$1',
},
modulePathIgnorePatterns: ['<rootDir>/dist/'],
testPathIgnorePatterns: ['scripts'],
};

export default config;
5 changes: 3 additions & 2 deletions packages/mcp-server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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"
Expand Down
180 changes: 176 additions & 4 deletions packages/mcp-server/src/index.ts
Original file line number Diff line number Diff line change
@@ -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');
Expand All @@ -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<string, typeof endpoints>();

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]] : []),
];
}
19 changes: 11 additions & 8 deletions packages/mcp-server/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -27,24 +27,27 @@ export const server = new McpServer(
export function init(params: {
server: Server | McpServer;
client?: Writer;
tools?: Tool[];
handlers?: Record<string, HandlerFunction>;
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}`);
}
Expand Down
Loading