Skip to content

Commit 67ae503

Browse files
chore(mcp): provides high-level initMcpServer function and exports known clients
1 parent 13a317a commit 67ae503

8 files changed

Lines changed: 99 additions & 83 deletions

File tree

packages/mcp-server/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,10 @@
2828
},
2929
"dependencies": {
3030
"scan-documents": "file:../../dist/",
31-
"@modelcontextprotocol/sdk": "^1.6.1",
31+
"@modelcontextprotocol/sdk": "^1.11.5",
3232
"yargs": "^17.7.2",
3333
"@cloudflare/cabidela": "^0.2.4",
34-
"zod": "^3.24.4",
34+
"zod": "^3.25.20",
3535
"zod-to-json-schema": "^3.24.5"
3636
},
3737
"bin": {

packages/mcp-server/src/compat.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,46 @@ export const defaultClientCapabilities: ClientCapabilities = {
1919
toolNameLength: undefined,
2020
};
2121

22+
export type ClientType = 'openai-agents' | 'claude' | 'claude-code' | 'cursor';
23+
24+
// Client presets for compatibility
25+
// Note that these could change over time as models get better, so this is
26+
// a best effort.
27+
export const knownClients: Record<ClientType, ClientCapabilities> = {
28+
'openai-agents': {
29+
topLevelUnions: false,
30+
validJson: true,
31+
refs: true,
32+
unions: true,
33+
formats: true,
34+
toolNameLength: undefined,
35+
},
36+
claude: {
37+
topLevelUnions: true,
38+
validJson: false,
39+
refs: true,
40+
unions: true,
41+
formats: true,
42+
toolNameLength: undefined,
43+
},
44+
'claude-code': {
45+
topLevelUnions: false,
46+
validJson: true,
47+
refs: true,
48+
unions: true,
49+
formats: true,
50+
toolNameLength: undefined,
51+
},
52+
cursor: {
53+
topLevelUnions: false,
54+
validJson: true,
55+
refs: false,
56+
unions: false,
57+
formats: false,
58+
toolNameLength: 50,
59+
},
60+
};
61+
2262
/**
2363
* Attempts to parse strings into JSON objects
2464
*/

packages/mcp-server/src/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
22
import { init, selectTools, server } from './server';
33
import { Endpoint, endpoints } from './tools';
4-
import { ParsedOptions, parseOptions } from './options';
4+
import { McpOptions, parseOptions } from './options';
55

66
async function main() {
77
const options = parseOptionsOrError();
@@ -41,7 +41,7 @@ function parseOptionsOrError() {
4141
}
4242
}
4343

44-
function selectToolsOrError(endpoints: Endpoint[], options: ParsedOptions) {
44+
function selectToolsOrError(endpoints: Endpoint[], options: McpOptions) {
4545
try {
4646
const includedTools = selectTools(endpoints, options);
4747
if (includedTools.length === 0) {

packages/mcp-server/src/options.ts

Lines changed: 12 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,19 @@
11
import yargs from 'yargs';
22
import { hideBin } from 'yargs/helpers';
33
import { endpoints, Filter } from './tools';
4-
import { ClientCapabilities } from './compat';
4+
import { ClientCapabilities, knownClients, ClientType } from './compat';
55

6-
type ClientType = 'openai-agents' | 'claude' | 'claude-code' | 'cursor';
7-
8-
// Client presets for compatibility
9-
// Note that these could change over time as models get better, so this is
10-
// a best effort.
11-
const CLIENT_PRESETS: Record<ClientType, ClientCapabilities> = {
12-
'openai-agents': {
13-
topLevelUnions: false,
14-
validJson: true,
15-
refs: true,
16-
unions: true,
17-
formats: true,
18-
toolNameLength: undefined,
19-
},
20-
claude: {
21-
topLevelUnions: true,
22-
validJson: false,
23-
refs: true,
24-
unions: true,
25-
formats: true,
26-
toolNameLength: undefined,
27-
},
28-
'claude-code': {
29-
topLevelUnions: false,
30-
validJson: true,
31-
refs: true,
32-
unions: true,
33-
formats: true,
34-
toolNameLength: undefined,
35-
},
36-
cursor: {
37-
topLevelUnions: false,
38-
validJson: true,
39-
refs: false,
40-
unions: false,
41-
formats: false,
42-
toolNameLength: 50,
43-
},
6+
export type CLIOptions = McpOptions & {
7+
list: boolean;
448
};
459

46-
export interface ParsedOptions {
10+
export type McpOptions = {
11+
client: ClientType | undefined;
4712
includeDynamicTools: boolean | undefined;
4813
includeAllTools: boolean | undefined;
4914
filters: Filter[];
50-
capabilities: ClientCapabilities;
51-
list: boolean;
52-
}
15+
capabilities?: Partial<ClientCapabilities>;
16+
};
5317

5418
const CAPABILITY_CHOICES = [
5519
'top-level-unions',
@@ -80,7 +44,7 @@ function parseCapabilityValue(cap: string): { name: Capability; value?: number }
8044
return { name: cap as Capability };
8145
}
8246

83-
export function parseOptions(): ParsedOptions {
47+
export function parseOptions(): CLIOptions {
8448
const opts = yargs(hideBin(process.argv))
8549
.option('tools', {
8650
type: 'string',
@@ -141,7 +105,7 @@ export function parseOptions(): ParsedOptions {
141105
})
142106
.option('client', {
143107
type: 'string',
144-
choices: Object.keys(CLIENT_PRESETS),
108+
choices: Object.keys(knownClients),
145109
description: 'Specify the MCP client being used',
146110
})
147111
.option('capability', {
@@ -229,14 +193,6 @@ export function parseOptions(): ParsedOptions {
229193
toolNameLength: undefined,
230194
};
231195

232-
// Apply client preset if specified
233-
if (typeof argv.client === 'string') {
234-
const clientKey = argv.client as ClientType;
235-
if (CLIENT_PRESETS[clientKey]) {
236-
Object.assign(clientCapabilities, CLIENT_PRESETS[clientKey]);
237-
}
238-
}
239-
240196
// Apply individual capability overrides
241197
if (Array.isArray(argv.capability)) {
242198
for (const cap of argv.capability) {
@@ -282,7 +238,9 @@ export function parseOptions(): ParsedOptions {
282238
const includeAllTools =
283239
explicitTools ? argv.tools?.includes('all') && !argv.noTools?.includes('all') : undefined;
284240

241+
const client = argv.client as ClientType;
285242
return {
243+
client: client && knownClients[client] ? client : undefined,
286244
includeDynamicTools,
287245
includeAllTools,
288246
filters,
@@ -323,7 +281,7 @@ Client Presets (--client):
323281
Presets like '--client=openai-agents' or '--client=cursor' automatically configure these capabilities based on current known limitations of those clients, simplifying setup.
324282
325283
Current presets:
326-
${JSON.stringify(CLIENT_PRESETS, null, 2)}
284+
${JSON.stringify(knownClients, null, 2)}
327285
`;
328286
}
329287

packages/mcp-server/src/server.ts

Lines changed: 39 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,22 @@ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
44
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
55
import { Endpoint, endpoints, HandlerFunction, query } from './tools';
66
import { CallToolRequestSchema, ListToolsRequestSchema, Tool } from '@modelcontextprotocol/sdk/types.js';
7+
import { ClientOptions } from 'scan-documents';
78
import ScanDocuments from 'scan-documents';
89
import {
910
applyCompatibilityTransformations,
1011
ClientCapabilities,
1112
defaultClientCapabilities,
13+
knownClients,
1214
parseEmbeddedJSON,
1315
} from './compat';
1416
import { dynamicTools } from './dynamic-tools';
15-
import { ParsedOptions } from './options';
17+
import { McpOptions } from './options';
18+
19+
export { McpOptions } from './options';
20+
export { ClientType } from './compat';
21+
export { Filter } from './tools';
22+
export { ClientOptions } from 'scan-documents';
1623
export { endpoints } from './tools';
1724

1825
// Create server instance
@@ -32,6 +39,21 @@ export const server = new McpServer(
3239
* Initializes the provided MCP Server with the given tools and handlers.
3340
* If not provided, the default client, tools and handlers will be used.
3441
*/
42+
export function initMcpServer(params: {
43+
server: Server | McpServer;
44+
clientOptions: ClientOptions;
45+
mcpOptions: McpOptions;
46+
endpoints?: { tool: Tool; handler: HandlerFunction }[];
47+
}) {
48+
const transformedEndpoints = selectTools(endpoints, params.mcpOptions);
49+
const client = new ScanDocuments(params.clientOptions);
50+
const capabilities = {
51+
...defaultClientCapabilities,
52+
...(params.mcpOptions.client ? knownClients[params.mcpOptions.client] : params.mcpOptions.capabilities),
53+
};
54+
init({ server: params.server, client, endpoints: transformedEndpoints, capabilities });
55+
}
56+
3557
export function init(params: {
3658
server: Server | McpServer;
3759
client?: ScanDocuments;
@@ -65,24 +87,27 @@ export function init(params: {
6587
/**
6688
* Selects the tools to include in the MCP Server based on the provided options.
6789
*/
68-
export function selectTools(endpoints: Endpoint[], options: ParsedOptions) {
90+
export function selectTools(endpoints: Endpoint[], options: McpOptions) {
6991
const filteredEndpoints = query(options.filters, endpoints);
7092

71-
const includedTools = filteredEndpoints;
93+
let includedTools = filteredEndpoints;
7294

73-
if (options.includeAllTools && includedTools.length === 0) {
74-
includedTools.push(...endpoints);
75-
}
76-
77-
if (options.includeDynamicTools) {
78-
includedTools.push(...dynamicTools(endpoints));
79-
}
80-
81-
if (includedTools.length === 0) {
82-
includedTools.push(...endpoints);
95+
if (includedTools.length > 0) {
96+
if (options.includeDynamicTools) {
97+
includedTools = dynamicTools(includedTools);
98+
}
99+
} else {
100+
if (options.includeAllTools) {
101+
includedTools = endpoints;
102+
} else if (options.includeDynamicTools) {
103+
includedTools = dynamicTools(endpoints);
104+
} else {
105+
includedTools = endpoints;
106+
}
83107
}
84108

85-
return applyCompatibilityTransformations(includedTools, options.capabilities);
109+
const capabilities = { ...defaultClientCapabilities, ...options.capabilities };
110+
return applyCompatibilityTransformations(includedTools, capabilities);
86111
}
87112

88113
/**

packages/mcp-server/src/tools/index.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,10 @@ export function query(filters: Filter[], endpoints: Endpoint[]): Endpoint[] {
7070
});
7171

7272
// Check if any filters didn't match
73-
if (unmatchedFilters.size > 0) {
73+
const unmatched = Array.from(unmatchedFilters).filter((f) => f.type === 'tool' || f.type === 'resource');
74+
if (unmatched.length > 0) {
7475
throw new Error(
75-
`The following filters did not match any endpoints: ${[...unmatchedFilters]
76+
`The following filters did not match any endpoints: ${unmatched
7677
.map((f) => `${f.type}=${f.value}`)
7778
.join(', ')}`,
7879
);

packages/mcp-server/src/tools/types.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,6 @@ export type Metadata = {
9191
resource: string;
9292
operation: 'read' | 'write';
9393
tags: string[];
94-
9594
httpMethod?: string;
9695
httpPath?: string;
9796
operationId?: string;

packages/mcp-server/tests/options.test.ts

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -77,14 +77,7 @@ describe('parseOptions', () => {
7777

7878
const result = parseOptions();
7979

80-
expect(result.capabilities).toEqual({
81-
topLevelUnions: false,
82-
validJson: true,
83-
refs: true,
84-
unions: true,
85-
formats: true,
86-
toolNameLength: undefined,
87-
});
80+
expect(result.client).toEqual('openai-agents');
8881

8982
cleanup();
9083
});

0 commit comments

Comments
 (0)