@tigerdata/mcp-boilerplate 0.4.3 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/http/api.js +2 -4
- package/dist/http/mcp.d.ts +2 -2
- package/dist/http/mcp.js +26 -2
- package/dist/httpServer.d.ts +3 -2
- package/dist/httpServer.js +4 -2
- package/dist/index.d.ts +2 -2
- package/dist/mcpServer.d.ts +8 -3
- package/dist/mcpServer.js +195 -84
- package/dist/stdio.d.ts +4 -3
- package/dist/stdio.js +2 -1
- package/dist/types.d.ts +41 -2
- package/package.json +2 -2
package/dist/http/api.js
CHANGED
|
@@ -8,7 +8,7 @@ export const apiRouterFactory = (context, apiFactories) => {
|
|
|
8
8
|
const router = Router();
|
|
9
9
|
router.use(bodyParser.json());
|
|
10
10
|
for (const factory of apiFactories) {
|
|
11
|
-
const tool = factory(context);
|
|
11
|
+
const tool = factory(context, {});
|
|
12
12
|
if (!tool.method || !tool.route)
|
|
13
13
|
continue;
|
|
14
14
|
router[tool.method](tool.route, async (req, res) => {
|
|
@@ -23,9 +23,7 @@ export const apiRouterFactory = (context, apiFactories) => {
|
|
|
23
23
|
parsedInput = Input.parse(input);
|
|
24
24
|
}
|
|
25
25
|
catch (error) {
|
|
26
|
-
res
|
|
27
|
-
.status(400)
|
|
28
|
-
.json({
|
|
26
|
+
res.status(400).json({
|
|
29
27
|
error: 'zod validation failure',
|
|
30
28
|
issues: error.issues,
|
|
31
29
|
});
|
package/dist/http/mcp.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
-
import { RouterFactoryResult } from '../types.js';
|
|
3
|
-
export declare const mcpRouterFactory: <Context extends Record<string, unknown>>(context: Context, createServer: (context: Context) => {
|
|
2
|
+
import { RouterFactoryResult, McpFeatureFlags } from '../types.js';
|
|
3
|
+
export declare const mcpRouterFactory: <Context extends Record<string, unknown>>(context: Context, createServer: (context: Context, featureFlags: McpFeatureFlags) => {
|
|
4
4
|
server: McpServer;
|
|
5
5
|
}, { name, stateful, }?: {
|
|
6
6
|
name?: string;
|
package/dist/http/mcp.js
CHANGED
|
@@ -11,8 +11,29 @@ const tracer = trace.getTracer(name ? `${name}.router.mcp` : 'router.mcp');
|
|
|
11
11
|
export const mcpRouterFactory = (context, createServer, { name, stateful = true, } = {}) => {
|
|
12
12
|
const router = Router();
|
|
13
13
|
const transports = new Map();
|
|
14
|
+
const sessionFeatureFlags = new Map();
|
|
15
|
+
const toSet = (flag) => flag
|
|
16
|
+
? Array.isArray(flag)
|
|
17
|
+
? new Set(flag)
|
|
18
|
+
: typeof flag === 'string'
|
|
19
|
+
? new Set(flag.split(',').map((s) => s.trim()))
|
|
20
|
+
: null
|
|
21
|
+
: null;
|
|
22
|
+
const parseFeatureFlags = (req) => ({
|
|
23
|
+
prompts: req.query.prompts !== 'false' && req.query.prompts !== '0',
|
|
24
|
+
enabledPrompts: toSet(req.query.enabled_prompts),
|
|
25
|
+
disabledPrompts: toSet(req.query.disabled_prompts),
|
|
26
|
+
resources: req.query.resources !== 'false' && req.query.resources !== '0',
|
|
27
|
+
enabledResources: toSet(req.query.enabled_resources),
|
|
28
|
+
disabledResources: toSet(req.query.disabled_resources),
|
|
29
|
+
tools: req.query.tools !== 'false' && req.query.tools !== '0',
|
|
30
|
+
enabledTools: toSet(req.query.enabled_tools),
|
|
31
|
+
disabledTools: toSet(req.query.disabled_tools),
|
|
32
|
+
query: req.query,
|
|
33
|
+
});
|
|
14
34
|
const handleStatelessRequest = async (req, res) => {
|
|
15
|
-
const
|
|
35
|
+
const featureFlags = parseFeatureFlags(req);
|
|
36
|
+
const { server } = createServer(context, featureFlags);
|
|
16
37
|
const transport = new StreamableHTTPServerTransport({
|
|
17
38
|
sessionIdGenerator: undefined,
|
|
18
39
|
});
|
|
@@ -60,20 +81,23 @@ export const mcpRouterFactory = (context, createServer, { name, stateful = true,
|
|
|
60
81
|
});
|
|
61
82
|
return;
|
|
62
83
|
}
|
|
84
|
+
const featureFlags = parseFeatureFlags(req);
|
|
63
85
|
transport = new StreamableHTTPServerTransport({
|
|
64
86
|
sessionIdGenerator: () => randomUUID(),
|
|
65
87
|
onsessioninitialized: (sessionId) => {
|
|
66
88
|
log.info(`Session initialized with ID: ${sessionId}`);
|
|
67
89
|
transports.set(sessionId, transport);
|
|
90
|
+
sessionFeatureFlags.set(sessionId, featureFlags);
|
|
68
91
|
},
|
|
69
92
|
onsessionclosed: (sessionId) => {
|
|
70
93
|
if (sessionId && transports.has(sessionId)) {
|
|
71
94
|
log.info(`Transport closed for session ${sessionId}, removing from transports map`);
|
|
72
95
|
transports.delete(sessionId);
|
|
96
|
+
sessionFeatureFlags.delete(sessionId);
|
|
73
97
|
}
|
|
74
98
|
},
|
|
75
99
|
});
|
|
76
|
-
const { server } = createServer(context);
|
|
100
|
+
const { server } = createServer(context, featureFlags);
|
|
77
101
|
await server.connect(transport);
|
|
78
102
|
}
|
|
79
103
|
await transport.handleRequest(req, res, body);
|
package/dist/httpServer.d.ts
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import express from 'express';
|
|
3
|
-
import { ApiFactory, PromptFactory } from './types.js';
|
|
3
|
+
import { ApiFactory, PromptFactory, ResourceFactory } from './types.js';
|
|
4
4
|
import { AdditionalSetupArgs } from './mcpServer.js';
|
|
5
5
|
import { Server } from 'node:http';
|
|
6
|
-
export declare const httpServerFactory: <Context extends Record<string, unknown>>({ name, version, context, apiFactories, promptFactories, additionalSetup, cleanupFn, stateful, }: {
|
|
6
|
+
export declare const httpServerFactory: <Context extends Record<string, unknown>>({ name, version, context, apiFactories, promptFactories, resourceFactories, additionalSetup, cleanupFn, stateful, }: {
|
|
7
7
|
name: string;
|
|
8
8
|
version?: string;
|
|
9
9
|
context: Context;
|
|
10
10
|
apiFactories?: readonly ApiFactory<Context, any, any>[];
|
|
11
11
|
promptFactories?: readonly PromptFactory<Context, any>[];
|
|
12
|
+
resourceFactories?: readonly ResourceFactory<Context>[];
|
|
12
13
|
additionalSetup?: (args: AdditionalSetupArgs<Context>) => void;
|
|
13
14
|
cleanupFn?: () => void | Promise<void>;
|
|
14
15
|
stateful?: boolean;
|
package/dist/httpServer.js
CHANGED
|
@@ -6,7 +6,7 @@ import { registerExitHandlers } from './registerExitHandlers.js';
|
|
|
6
6
|
import { mcpServerFactory } from './mcpServer.js';
|
|
7
7
|
import { log } from './logger.js';
|
|
8
8
|
import { StatusError } from './StatusError.js';
|
|
9
|
-
export const httpServerFactory = ({ name, version, context, apiFactories = [], promptFactories
|
|
9
|
+
export const httpServerFactory = ({ name, version, context, apiFactories = [], promptFactories, resourceFactories, additionalSetup, cleanupFn, stateful = true, }) => {
|
|
10
10
|
const cleanupFns = cleanupFn
|
|
11
11
|
? [cleanupFn]
|
|
12
12
|
: [];
|
|
@@ -14,13 +14,15 @@ export const httpServerFactory = ({ name, version, context, apiFactories = [], p
|
|
|
14
14
|
log.info('Starting HTTP server...');
|
|
15
15
|
const app = express();
|
|
16
16
|
app.enable('trust proxy');
|
|
17
|
-
const [mcpRouter, mcpCleanup] = mcpRouterFactory(context, () => mcpServerFactory({
|
|
17
|
+
const [mcpRouter, mcpCleanup] = mcpRouterFactory(context, (context, featureFlags) => mcpServerFactory({
|
|
18
18
|
name,
|
|
19
19
|
version,
|
|
20
20
|
context,
|
|
21
21
|
apiFactories,
|
|
22
22
|
promptFactories,
|
|
23
|
+
resourceFactories,
|
|
23
24
|
additionalSetup,
|
|
25
|
+
featureFlags,
|
|
24
26
|
}), { name, stateful });
|
|
25
27
|
cleanupFns.push(mcpCleanup);
|
|
26
28
|
app.use('/mcp', mcpRouter);
|
package/dist/index.d.ts
CHANGED
|
@@ -2,8 +2,8 @@ export { cliEntrypoint } from './cliEntrypoint.js';
|
|
|
2
2
|
export { httpServerFactory } from './httpServer.js';
|
|
3
3
|
export { log } from './logger.js';
|
|
4
4
|
export { stdioServerFactory } from './stdio.js';
|
|
5
|
-
export {
|
|
5
|
+
export type { ApiFactory, McpFeatureFlags, PromptFactory, ResourceFactory, ParsedQs, } from './types.js';
|
|
6
6
|
export { StatusError } from './StatusError.js';
|
|
7
|
-
export {
|
|
7
|
+
export type { AdditionalSetupArgs } from './mcpServer.js';
|
|
8
8
|
export { withSpan, addAiResultToSpan } from './tracing.js';
|
|
9
9
|
export { registerExitHandlers } from './registerExitHandlers.js';
|
package/dist/mcpServer.d.ts
CHANGED
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
-
import { ApiFactory, PromptFactory } from './types.js';
|
|
2
|
+
import { ApiFactory, PromptFactory, McpFeatureFlags, ResourceFactory } from './types.js';
|
|
3
|
+
import { ServerCapabilities } from '@modelcontextprotocol/sdk/types.js';
|
|
3
4
|
export interface AdditionalSetupArgs<Context extends Record<string, unknown>> {
|
|
4
5
|
context: Context;
|
|
5
6
|
server: McpServer;
|
|
7
|
+
featureFlags: McpFeatureFlags;
|
|
6
8
|
}
|
|
7
|
-
export declare const mcpServerFactory: <Context extends Record<string, unknown>>({ name, version, context, apiFactories, promptFactories, additionalSetup, }: {
|
|
9
|
+
export declare const mcpServerFactory: <Context extends Record<string, unknown>>({ name, version, context, apiFactories, promptFactories, resourceFactories, additionalSetup, additionalCapabilities, featureFlags, }: {
|
|
8
10
|
name: string;
|
|
9
11
|
version?: string;
|
|
10
12
|
context: Context;
|
|
11
|
-
apiFactories
|
|
13
|
+
apiFactories?: readonly ApiFactory<Context, any, any>[];
|
|
12
14
|
promptFactories?: readonly PromptFactory<Context, any>[];
|
|
15
|
+
resourceFactories?: readonly ResourceFactory<Context>[];
|
|
13
16
|
additionalSetup?: (args: AdditionalSetupArgs<Context>) => void;
|
|
17
|
+
additionalCapabilities?: ServerCapabilities;
|
|
18
|
+
featureFlags?: McpFeatureFlags;
|
|
14
19
|
}) => {
|
|
15
20
|
server: McpServer;
|
|
16
21
|
};
|
package/dist/mcpServer.js
CHANGED
|
@@ -1,116 +1,227 @@
|
|
|
1
|
-
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
1
|
+
import { McpServer, ResourceTemplate, } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
2
|
import { SpanStatusCode, trace, context as otelContext, propagation, SpanKind, } from '@opentelemetry/api';
|
|
3
3
|
import { log } from './logger.js';
|
|
4
4
|
const name = process.env.OTEL_SERVICE_NAME;
|
|
5
5
|
const tracer = trace.getTracer(name ? `${name}.mcpServer` : 'mcpServer');
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
6
|
+
const toSet = (str) => str ? new Set(str.split(',').map((s) => s.trim())) : null;
|
|
7
|
+
const enabledTools = toSet(process.env.MCP_ENABLED_TOOLS);
|
|
8
|
+
const disabledTools = toSet(process.env.MCP_DISABLED_TOOLS);
|
|
9
|
+
const enabledPrompts = toSet(process.env.MCP_ENABLED_PROMPTS);
|
|
10
|
+
const disabledPrompts = toSet(process.env.MCP_DISABLED_PROMPTS);
|
|
11
|
+
const enabledResources = toSet(process.env.MCP_ENABLED_RESOURCES);
|
|
12
|
+
const disabledResources = toSet(process.env.MCP_DISABLED_RESOURCES);
|
|
13
|
+
const shouldSkip = (item, enabledSets, disabledSets) => {
|
|
14
|
+
if (item.disabled)
|
|
15
|
+
return true;
|
|
16
|
+
for (const enabledSet of enabledSets) {
|
|
17
|
+
if (enabledSet && !enabledSet.has(item.name)) {
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
for (const disabledSet of disabledSets) {
|
|
22
|
+
if (disabledSet && disabledSet.has(item.name)) {
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return false;
|
|
27
|
+
};
|
|
28
|
+
export const mcpServerFactory = ({ name, version = '1.0.0', context, apiFactories = [], promptFactories = [], resourceFactories = [], additionalSetup, additionalCapabilities = {}, featureFlags = {}, }) => {
|
|
29
|
+
const enablePrompts = featureFlags.prompts !== false;
|
|
30
|
+
const enableResources = featureFlags.resources !== false;
|
|
31
|
+
const enableTools = featureFlags.tools !== false;
|
|
13
32
|
const server = new McpServer({
|
|
14
33
|
name,
|
|
15
34
|
version,
|
|
16
35
|
}, {
|
|
17
36
|
capabilities: {
|
|
18
|
-
tools: {},
|
|
19
|
-
...(promptFactories.length ? { prompts: {} } : null),
|
|
37
|
+
...(enableTools && apiFactories.length ? { tools: {} } : null),
|
|
38
|
+
...(enablePrompts && promptFactories.length ? { prompts: {} } : null),
|
|
39
|
+
...(enableResources && resourceFactories.length
|
|
40
|
+
? { resources: {} }
|
|
41
|
+
: null),
|
|
42
|
+
...additionalCapabilities,
|
|
20
43
|
},
|
|
21
44
|
});
|
|
22
|
-
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
45
|
+
if (enableTools) {
|
|
46
|
+
for (const factory of apiFactories) {
|
|
47
|
+
const tool = factory(context, featureFlags);
|
|
48
|
+
if (shouldSkip(tool, [enabledTools, featureFlags.enabledTools], [disabledTools, featureFlags.disabledTools])) {
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
server.registerTool(tool.name, {
|
|
52
|
+
...tool.config,
|
|
53
|
+
annotations: {
|
|
54
|
+
...tool.config.annotations,
|
|
55
|
+
// Some clients (e.g. claude code) do not yet support the title field
|
|
56
|
+
// at the top level and instead expect it in annotations. We also
|
|
57
|
+
// don't allow setting different titles in two places as that doesn't
|
|
58
|
+
// make sense.
|
|
59
|
+
title: tool.config.title,
|
|
60
|
+
},
|
|
61
|
+
}, async (args, extra) => {
|
|
62
|
+
let traceContext = otelContext.active();
|
|
63
|
+
if (extra?._meta?.traceparent) {
|
|
64
|
+
// Some MCP clients (e.g. pydantic) pass the parent trace context
|
|
65
|
+
traceContext = propagation.extract(traceContext, {
|
|
66
|
+
traceparent: extra._meta.traceparent,
|
|
67
|
+
tracestate: extra._meta.tracestate,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
return tracer.startActiveSpan(`mcp.tool.${tool.name}`, { kind: SpanKind.SERVER }, traceContext, async (span) => {
|
|
71
|
+
span.setAttribute('mcp.tool.args', JSON.stringify(args));
|
|
72
|
+
try {
|
|
73
|
+
const result = await tool.fn(args);
|
|
74
|
+
const text = JSON.stringify(result);
|
|
75
|
+
span.setAttribute('mcp.tool.responseBytes', text.length);
|
|
76
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
77
|
+
return {
|
|
78
|
+
content: [
|
|
79
|
+
{
|
|
80
|
+
type: 'text',
|
|
81
|
+
text,
|
|
82
|
+
},
|
|
83
|
+
],
|
|
84
|
+
structuredContent: result,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
log.error('Error invoking tool:', error);
|
|
89
|
+
span.recordException(error);
|
|
90
|
+
span.setStatus({
|
|
91
|
+
code: SpanStatusCode.ERROR,
|
|
92
|
+
message: error.message,
|
|
93
|
+
});
|
|
94
|
+
return {
|
|
95
|
+
content: [
|
|
96
|
+
{
|
|
97
|
+
type: 'text',
|
|
98
|
+
text: `Error: ${error.message || 'Unknown error'}`,
|
|
99
|
+
},
|
|
100
|
+
],
|
|
101
|
+
isError: true,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
finally {
|
|
105
|
+
span.end();
|
|
106
|
+
}
|
|
49
107
|
});
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
if (enablePrompts) {
|
|
112
|
+
for (const factory of promptFactories) {
|
|
113
|
+
const prompt = factory(context, featureFlags);
|
|
114
|
+
if (shouldSkip(prompt, [enabledPrompts, featureFlags.enabledPrompts], [disabledPrompts, featureFlags.disabledPrompts])) {
|
|
115
|
+
continue;
|
|
50
116
|
}
|
|
51
|
-
|
|
52
|
-
span.setAttribute('mcp.
|
|
117
|
+
server.registerPrompt(prompt.name, prompt.config, async (args) => tracer.startActiveSpan(`mcp.prompt.${prompt.name}`, async (span) => {
|
|
118
|
+
span.setAttribute('mcp.prompt.args', JSON.stringify(args));
|
|
53
119
|
try {
|
|
54
|
-
const result = await
|
|
55
|
-
const text = JSON.stringify(result);
|
|
56
|
-
span.setAttribute('mcp.tool.responseBytes', text.length);
|
|
120
|
+
const result = await prompt.fn(args);
|
|
57
121
|
span.setStatus({ code: SpanStatusCode.OK });
|
|
58
|
-
return
|
|
59
|
-
content: [
|
|
60
|
-
{
|
|
61
|
-
type: 'text',
|
|
62
|
-
text,
|
|
63
|
-
},
|
|
64
|
-
],
|
|
65
|
-
structuredContent: result,
|
|
66
|
-
};
|
|
122
|
+
return result;
|
|
67
123
|
}
|
|
68
124
|
catch (error) {
|
|
69
|
-
log.error('Error invoking
|
|
125
|
+
log.error('Error invoking prompt:', error);
|
|
70
126
|
span.recordException(error);
|
|
71
127
|
span.setStatus({
|
|
72
128
|
code: SpanStatusCode.ERROR,
|
|
73
129
|
message: error.message,
|
|
74
130
|
});
|
|
75
|
-
|
|
76
|
-
content: [
|
|
77
|
-
{
|
|
78
|
-
type: 'text',
|
|
79
|
-
text: `Error: ${error.message || 'Unknown error'}`,
|
|
80
|
-
},
|
|
81
|
-
],
|
|
82
|
-
isError: true,
|
|
83
|
-
};
|
|
131
|
+
throw error;
|
|
84
132
|
}
|
|
85
133
|
finally {
|
|
86
134
|
span.end();
|
|
87
135
|
}
|
|
88
|
-
});
|
|
89
|
-
}
|
|
136
|
+
}));
|
|
137
|
+
}
|
|
90
138
|
}
|
|
91
|
-
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
const result = await prompt.fn(args);
|
|
97
|
-
span.setStatus({ code: SpanStatusCode.OK });
|
|
98
|
-
return result;
|
|
99
|
-
}
|
|
100
|
-
catch (error) {
|
|
101
|
-
log.error('Error invoking prompt:', error);
|
|
102
|
-
span.recordException(error);
|
|
103
|
-
span.setStatus({
|
|
104
|
-
code: SpanStatusCode.ERROR,
|
|
105
|
-
message: error.message,
|
|
106
|
-
});
|
|
107
|
-
throw error;
|
|
139
|
+
if (enableResources) {
|
|
140
|
+
for (const factory of resourceFactories) {
|
|
141
|
+
const resource = factory(context, featureFlags);
|
|
142
|
+
if (shouldSkip(resource, [enabledResources, featureFlags.enabledResources], [disabledResources, featureFlags.disabledResources])) {
|
|
143
|
+
continue;
|
|
108
144
|
}
|
|
109
|
-
|
|
110
|
-
|
|
145
|
+
switch (resource.type) {
|
|
146
|
+
case 'static': {
|
|
147
|
+
server.registerResource(resource.name, resource.uri, resource.config, async (uri, extra) => tracer.startActiveSpan(`mcp.resource.static.${resource.name}`, async (span) => {
|
|
148
|
+
span.setAttribute('mcp.resource.uri', uri.toString());
|
|
149
|
+
span.setAttribute('mcp.resource.extra', JSON.stringify(extra));
|
|
150
|
+
try {
|
|
151
|
+
const result = await resource.read(uri, extra);
|
|
152
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
153
|
+
return result;
|
|
154
|
+
}
|
|
155
|
+
catch (error) {
|
|
156
|
+
log.error('Error invoking resource:', error);
|
|
157
|
+
span.recordException(error);
|
|
158
|
+
span.setStatus({
|
|
159
|
+
code: SpanStatusCode.ERROR,
|
|
160
|
+
message: error.message,
|
|
161
|
+
});
|
|
162
|
+
throw error;
|
|
163
|
+
}
|
|
164
|
+
finally {
|
|
165
|
+
span.end();
|
|
166
|
+
}
|
|
167
|
+
}));
|
|
168
|
+
break;
|
|
169
|
+
}
|
|
170
|
+
case 'templated': {
|
|
171
|
+
server.registerResource(resource.name, new ResourceTemplate(resource.uriTemplate, {
|
|
172
|
+
list: resource.list &&
|
|
173
|
+
((extra) => tracer.startActiveSpan(`mcp.resource.templated.${resource.name}.list`, async (span) => {
|
|
174
|
+
try {
|
|
175
|
+
const result = await resource.list(extra);
|
|
176
|
+
span.setAttribute('mcp.resource.list.uris', result.resources.map((r) => r.uri).join(', '));
|
|
177
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
178
|
+
return result;
|
|
179
|
+
}
|
|
180
|
+
catch (error) {
|
|
181
|
+
log.error('Error invoking resource list:', error);
|
|
182
|
+
span.recordException(error);
|
|
183
|
+
span.setStatus({
|
|
184
|
+
code: SpanStatusCode.ERROR,
|
|
185
|
+
message: error.message,
|
|
186
|
+
});
|
|
187
|
+
throw error;
|
|
188
|
+
}
|
|
189
|
+
finally {
|
|
190
|
+
span.end();
|
|
191
|
+
}
|
|
192
|
+
})),
|
|
193
|
+
complete: resource.complete,
|
|
194
|
+
}), resource.config, async (uri, variables, extra) => tracer.startActiveSpan(`mcp.resource.templated.${resource.name}`, async (span) => {
|
|
195
|
+
span.setAttribute('mcp.resource.uri', uri.toString());
|
|
196
|
+
span.setAttribute('mcp.resource.variables', JSON.stringify(variables));
|
|
197
|
+
span.setAttribute('mcp.resource.extra', JSON.stringify(extra));
|
|
198
|
+
try {
|
|
199
|
+
const result = await resource.read(uri, variables, extra);
|
|
200
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
201
|
+
return result;
|
|
202
|
+
}
|
|
203
|
+
catch (error) {
|
|
204
|
+
log.error('Error invoking resource:', error);
|
|
205
|
+
span.recordException(error);
|
|
206
|
+
span.setStatus({
|
|
207
|
+
code: SpanStatusCode.ERROR,
|
|
208
|
+
message: error.message,
|
|
209
|
+
});
|
|
210
|
+
throw error;
|
|
211
|
+
}
|
|
212
|
+
finally {
|
|
213
|
+
span.end();
|
|
214
|
+
}
|
|
215
|
+
}));
|
|
216
|
+
break;
|
|
217
|
+
}
|
|
218
|
+
default: {
|
|
219
|
+
// @ts-expect-error exhaustive check
|
|
220
|
+
throw new Error(`Unknown resource type: ${resource.type}`);
|
|
221
|
+
}
|
|
111
222
|
}
|
|
112
|
-
}
|
|
223
|
+
}
|
|
113
224
|
}
|
|
114
|
-
additionalSetup?.({ context, server });
|
|
225
|
+
additionalSetup?.({ context, server, featureFlags });
|
|
115
226
|
return { server };
|
|
116
227
|
};
|
package/dist/stdio.d.ts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { ApiFactory, PromptFactory } from './types.js';
|
|
2
|
+
import { ApiFactory, PromptFactory, ResourceFactory } from './types.js';
|
|
3
3
|
import { AdditionalSetupArgs } from './mcpServer.js';
|
|
4
|
-
export declare const stdioServerFactory: <Context extends Record<string, unknown>>({ name, version, context, apiFactories, promptFactories, additionalSetup, cleanupFn, }: {
|
|
4
|
+
export declare const stdioServerFactory: <Context extends Record<string, unknown>>({ name, version, context, apiFactories, promptFactories, resourceFactories, additionalSetup, cleanupFn, }: {
|
|
5
5
|
name: string;
|
|
6
6
|
version?: string;
|
|
7
7
|
context: Context;
|
|
8
|
-
apiFactories
|
|
8
|
+
apiFactories?: readonly ApiFactory<Context, any, any>[];
|
|
9
9
|
promptFactories?: readonly PromptFactory<Context, any>[];
|
|
10
|
+
resourceFactories?: readonly ResourceFactory<Context>[];
|
|
10
11
|
additionalSetup?: (args: AdditionalSetupArgs<Context>) => void;
|
|
11
12
|
cleanupFn?: () => Promise<void>;
|
|
12
13
|
}) => Promise<void>;
|
package/dist/stdio.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
3
|
import { mcpServerFactory } from './mcpServer.js';
|
|
4
4
|
import { registerExitHandlers } from './registerExitHandlers.js';
|
|
5
|
-
export const stdioServerFactory = async ({ name, version, context, apiFactories, promptFactories
|
|
5
|
+
export const stdioServerFactory = async ({ name, version, context, apiFactories, promptFactories, resourceFactories, additionalSetup, cleanupFn, }) => {
|
|
6
6
|
try {
|
|
7
7
|
console.error('Starting default (STDIO) server...');
|
|
8
8
|
const transport = new StdioServerTransport();
|
|
@@ -12,6 +12,7 @@ export const stdioServerFactory = async ({ name, version, context, apiFactories,
|
|
|
12
12
|
context,
|
|
13
13
|
apiFactories,
|
|
14
14
|
promptFactories,
|
|
15
|
+
resourceFactories,
|
|
15
16
|
additionalSetup,
|
|
16
17
|
});
|
|
17
18
|
await server.connect(transport);
|
package/dist/types.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { z } from 'zod';
|
|
|
2
2
|
import type { ZodRawShape, ZodTypeAny } from 'zod';
|
|
3
3
|
import type { ToolAnnotations, GetPromptResult } from '@modelcontextprotocol/sdk/types.js';
|
|
4
4
|
import { Router } from 'express';
|
|
5
|
+
import { CompleteResourceTemplateCallback, ListResourcesCallback, ReadResourceCallback, ReadResourceTemplateCallback, ResourceMetadata } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
5
6
|
export type ToolConfig<InputArgs extends ZodRawShape, OutputArgs extends ZodRawShape> = {
|
|
6
7
|
title?: string;
|
|
7
8
|
description?: string;
|
|
@@ -18,7 +19,7 @@ export interface ApiDefinition<InputArgs extends ZodRawShape, OutputArgs extends
|
|
|
18
19
|
fn: (args: z.objectOutputType<InputArgs, ZodTypeAny>) => Promise<z.objectOutputType<OutputArgs, ZodTypeAny>>;
|
|
19
20
|
pickResult?: (result: z.objectOutputType<OutputArgs, ZodTypeAny>) => SimplifiedOutputArgs;
|
|
20
21
|
}
|
|
21
|
-
export type ApiFactory<Context extends Record<string, unknown>, Input extends ZodRawShape, Output extends ZodRawShape, RestOutput = Output> = (ctx: Context) => ApiDefinition<Input, Output, RestOutput>;
|
|
22
|
+
export type ApiFactory<Context extends Record<string, unknown>, Input extends ZodRawShape, Output extends ZodRawShape, RestOutput = Output> = (ctx: Context, featureFlags: McpFeatureFlags) => ApiDefinition<Input, Output, RestOutput>;
|
|
22
23
|
export type RouterFactoryResult = [Router, () => void | Promise<void>];
|
|
23
24
|
export type PromptConfig<InputArgs extends ZodRawShape> = {
|
|
24
25
|
title?: string;
|
|
@@ -28,6 +29,44 @@ export type PromptConfig<InputArgs extends ZodRawShape> = {
|
|
|
28
29
|
export interface PromptDefinition<InputArgs extends ZodRawShape> {
|
|
29
30
|
name: string;
|
|
30
31
|
config: PromptConfig<InputArgs>;
|
|
32
|
+
disabled?: boolean;
|
|
31
33
|
fn: (args: z.objectOutputType<InputArgs, ZodTypeAny>) => Promise<GetPromptResult>;
|
|
32
34
|
}
|
|
33
|
-
export type PromptFactory<Context extends Record<string, unknown>, Input extends ZodRawShape> = (ctx: Context) => PromptDefinition<Input>;
|
|
35
|
+
export type PromptFactory<Context extends Record<string, unknown>, Input extends ZodRawShape> = (ctx: Context, featureFlags: McpFeatureFlags) => PromptDefinition<Input>;
|
|
36
|
+
export interface TemplatedResourceDefinition {
|
|
37
|
+
type: 'templated';
|
|
38
|
+
name: string;
|
|
39
|
+
uriTemplate: string;
|
|
40
|
+
list?: ListResourcesCallback;
|
|
41
|
+
complete?: {
|
|
42
|
+
[variable: string]: CompleteResourceTemplateCallback;
|
|
43
|
+
};
|
|
44
|
+
config: ResourceMetadata;
|
|
45
|
+
disabled?: boolean;
|
|
46
|
+
read: ReadResourceTemplateCallback;
|
|
47
|
+
}
|
|
48
|
+
export interface StaticResourceDefinition {
|
|
49
|
+
type: 'static';
|
|
50
|
+
name: string;
|
|
51
|
+
uri: string;
|
|
52
|
+
config: ResourceMetadata;
|
|
53
|
+
disabled?: boolean;
|
|
54
|
+
read: ReadResourceCallback;
|
|
55
|
+
}
|
|
56
|
+
export type ResourceDefinition = TemplatedResourceDefinition | StaticResourceDefinition;
|
|
57
|
+
export type ResourceFactory<Context extends Record<string, unknown>> = (ctx: Context, featureFlags: McpFeatureFlags) => ResourceDefinition;
|
|
58
|
+
export interface ParsedQs {
|
|
59
|
+
[key: string]: undefined | string | ParsedQs | (string | ParsedQs)[];
|
|
60
|
+
}
|
|
61
|
+
export interface McpFeatureFlags {
|
|
62
|
+
prompts?: boolean;
|
|
63
|
+
enabledPrompts?: Set<string> | null;
|
|
64
|
+
disabledPrompts?: Set<string> | null;
|
|
65
|
+
resources?: boolean;
|
|
66
|
+
enabledResources?: Set<string> | null;
|
|
67
|
+
disabledResources?: Set<string> | null;
|
|
68
|
+
tools?: boolean;
|
|
69
|
+
enabledTools?: Set<string> | null;
|
|
70
|
+
disabledTools?: Set<string> | null;
|
|
71
|
+
query?: ParsedQs;
|
|
72
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tigerdata/mcp-boilerplate",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "MCP boilerplate code for Node.js",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "TigerData",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"lint:fix": "eslint --fix"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
36
|
+
"@modelcontextprotocol/sdk": "^1.21.1",
|
|
37
37
|
"@opentelemetry/api": "^1.9.0",
|
|
38
38
|
"@opentelemetry/auto-instrumentations-node": "^0.66.0",
|
|
39
39
|
"@opentelemetry/exporter-trace-otlp-grpc": "^0.207.0",
|