@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 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
  });
@@ -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 { server } = createServer(context);
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);
@@ -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;
@@ -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 = [], additionalSetup, cleanupFn, stateful = true, }) => {
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 { type ApiFactory } from './types.js';
5
+ export type { ApiFactory, McpFeatureFlags, PromptFactory, ResourceFactory, ParsedQs, } from './types.js';
6
6
  export { StatusError } from './StatusError.js';
7
- export { type AdditionalSetupArgs } from './mcpServer.js';
7
+ export type { AdditionalSetupArgs } from './mcpServer.js';
8
8
  export { withSpan, addAiResultToSpan } from './tracing.js';
9
9
  export { registerExitHandlers } from './registerExitHandlers.js';
@@ -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: readonly ApiFactory<Context, any, any>[];
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 enabledTools = process.env.MCP_ENABLED_TOOLS
7
- ? new Set(process.env.MCP_ENABLED_TOOLS.split(',').map((s) => s.trim()))
8
- : null;
9
- const disabledTools = process.env.MCP_DISABLED_TOOLS
10
- ? new Set(process.env.MCP_DISABLED_TOOLS.split(',').map((s) => s.trim()))
11
- : null;
12
- export const mcpServerFactory = ({ name, version = '1.0.0', context, apiFactories, promptFactories = [], additionalSetup, }) => {
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
- for (const factory of apiFactories) {
23
- const tool = factory(context);
24
- if (tool.disabled)
25
- continue;
26
- if (enabledTools && !enabledTools.has(tool.name)) {
27
- continue;
28
- }
29
- if (disabledTools && disabledTools.has(tool.name)) {
30
- continue;
31
- }
32
- server.registerTool(tool.name, {
33
- ...tool.config,
34
- annotations: {
35
- ...tool.config.annotations,
36
- // Some clients (e.g. claude code) do not yet support the title field
37
- // at the top level and instead expect it in annotations. We also
38
- // don't allow setting different titles in two places as that doesn't
39
- // make sense.
40
- title: tool.config.title,
41
- },
42
- }, async (args, extra) => {
43
- let traceContext = otelContext.active();
44
- if (extra?._meta?.traceparent) {
45
- // Some MCP clients (e.g. pydantic) pass the parent trace context
46
- traceContext = propagation.extract(traceContext, {
47
- traceparent: extra._meta.traceparent,
48
- tracestate: extra._meta.tracestate,
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
- return tracer.startActiveSpan(`mcp.tool.${tool.name}`, { kind: SpanKind.SERVER }, traceContext, async (span) => {
52
- span.setAttribute('mcp.tool.args', JSON.stringify(args));
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 tool.fn(args);
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 tool:', error);
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
- return {
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
- for (const factory of promptFactories) {
92
- const prompt = factory(context);
93
- server.registerPrompt(prompt.name, prompt.config, async (args) => tracer.startActiveSpan(`mcp.prompt.${prompt.name}`, async (span) => {
94
- span.setAttribute('mcp.prompt.args', JSON.stringify(args));
95
- try {
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
- finally {
110
- span.end();
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: readonly ApiFactory<Context, any, any>[];
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 = [], additionalSetup, cleanupFn, }) => {
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.4.3",
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.20.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",