@tigerdata/mcp-boilerplate 1.0.2 → 1.2.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.
@@ -16,7 +16,7 @@ export async function cliEntrypoint(stdioEntrypoint, httpEntrypoint, instrumenta
16
16
  case 'http': {
17
17
  let cleanup;
18
18
  if (args.includes('--instrument') ||
19
- process.env.INSTRUMENT === 'true') {
19
+ process.env.INSTRUMENT?.toLowerCase().trim() === 'true') {
20
20
  const { instrument } = await import(instrumentation);
21
21
  ({ cleanup } = instrument());
22
22
  }
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import type { Server } from 'node:http';
3
- import express from 'express';
3
+ import express, { type Router } from 'express';
4
4
  import { type AdditionalSetupArgs } from './mcpServer.js';
5
5
  import type { BaseApiFactory, BasePromptFactory, ResourceFactory } from './types.js';
6
- export declare const httpServerFactory: <Context extends Record<string, unknown>>({ name, version, context, apiFactories, promptFactories, resourceFactories, additionalSetup, cleanupFn, stateful, instructions, }: {
6
+ interface HttpServerOptions<Context extends Record<string, unknown>> {
7
7
  name: string;
8
8
  version?: string;
9
9
  context: Context;
@@ -14,10 +14,28 @@ export declare const httpServerFactory: <Context extends Record<string, unknown>
14
14
  cleanupFn?: () => void | Promise<void>;
15
15
  stateful?: boolean;
16
16
  instructions?: string;
17
- }) => Promise<{
18
- app: express.Express;
19
- server: Server;
17
+ /**
18
+ * When provided, mount routers on this app/router instead of creating a new
19
+ * one. The caller owns the server lifecycle — `httpServerFactory` will not
20
+ * call `app.listen()`. The returned `server` will be `null`.
21
+ */
22
+ app?: Router;
23
+ /**
24
+ * Path to mount the MCP router at. Defaults to `"/mcp"`.
25
+ */
26
+ mcpPath?: string;
27
+ /**
28
+ * Path to mount the API router at. Defaults to `"/api"`.
29
+ */
30
+ apiPath?: string;
31
+ }
32
+ interface HttpServerResult {
33
+ app: Router;
34
+ /** `null` when an external `app` was provided (caller owns the server). */
35
+ server: Server | null;
20
36
  apiRouter: express.Router;
21
37
  mcpRouter: express.Router;
22
38
  registerCleanupFn: (fn: () => Promise<void>) => void;
23
- }>;
39
+ }
40
+ export declare const httpServerFactory: <Context extends Record<string, unknown>>({ name, version, context, apiFactories, promptFactories, resourceFactories, additionalSetup, cleanupFn, stateful, instructions, app: externalApp, mcpPath, apiPath, }: HttpServerOptions<Context>) => Promise<HttpServerResult>;
41
+ export {};
@@ -7,14 +7,21 @@ import { log } from './logger.js';
7
7
  import { mcpServerFactory } from './mcpServer.js';
8
8
  import { registerExitHandlers } from './registerExitHandlers.js';
9
9
  import { StatusError } from './StatusError.js';
10
- export const httpServerFactory = async ({ name, version, context, apiFactories = [], promptFactories, resourceFactories, additionalSetup, cleanupFn, stateful = true, instructions, }) => {
10
+ export const httpServerFactory = async ({ name, version, context, apiFactories = [], promptFactories, resourceFactories, additionalSetup, cleanupFn, stateful = true, instructions, app: externalApp, mcpPath = '/mcp', apiPath = '/api', }) => {
11
11
  const cleanupFns = cleanupFn
12
12
  ? [cleanupFn]
13
13
  : [];
14
14
  const exitHandler = registerExitHandlers(cleanupFns);
15
- log.info('Starting HTTP server...');
16
- const app = express();
17
- app.enable('trust proxy');
15
+ let app;
16
+ let ownApp;
17
+ if (externalApp) {
18
+ app = externalApp;
19
+ }
20
+ else {
21
+ ownApp = express();
22
+ ownApp.enable('trust proxy');
23
+ app = ownApp;
24
+ }
18
25
  const PORT = process.env.PORT || 3001;
19
26
  const inspector = process.env.NODE_ENV !== 'production' ||
20
27
  ['1', 'true'].includes(process.env.ENABLE_INSPECTOR ?? '0');
@@ -30,10 +37,10 @@ export const httpServerFactory = async ({ name, version, context, apiFactories =
30
37
  instructions,
31
38
  }), { name, stateful, inspector });
32
39
  cleanupFns.push(mcpCleanup);
33
- app.use('/mcp', mcpRouter);
40
+ app.use(mcpPath, mcpRouter);
34
41
  const [apiRouter, apiCleanup] = await apiRouterFactory(context, apiFactories);
35
42
  cleanupFns.push(apiCleanup);
36
- app.use('/api', apiRouter);
43
+ app.use(apiPath, apiRouter);
37
44
  // Error handler
38
45
  app.use((err, _req, res, _next) => {
39
46
  if (err instanceof StatusError && err.status < 500) {
@@ -49,19 +56,34 @@ export const httpServerFactory = async ({ name, version, context, apiFactories =
49
56
  .status(err instanceof StatusError ? err.status : 500)
50
57
  .json({ error: err.message });
51
58
  });
52
- if (inspector) {
59
+ if (inspector && 'listen' in app) {
60
+ const expressApp = app;
53
61
  process.env.MCP_USE_ANONYMIZED_TELEMETRY = 'false';
54
62
  import('@mcp-use/inspector')
55
63
  .then(({ mountInspector }) => {
56
- app.use(bodyParser.json());
57
- mountInspector(app, {
64
+ expressApp.use(bodyParser.json());
65
+ mountInspector(expressApp, {
58
66
  autoConnectUrl: process.env.MCP_PUBLIC_URL ?? `http://localhost:${PORT}/mcp`,
59
67
  });
60
68
  })
61
69
  .catch(log.error);
62
70
  }
63
- // Start the server
64
- const server = app.listen(PORT, async (error) => {
71
+ // When an external app is provided, the caller owns the server lifecycle.
72
+ if (externalApp) {
73
+ return {
74
+ app,
75
+ server: null,
76
+ apiRouter,
77
+ mcpRouter,
78
+ registerCleanupFn: (fn) => {
79
+ cleanupFns.push(fn);
80
+ },
81
+ };
82
+ }
83
+ // Start the server (ownApp is guaranteed to exist here — we returned early for external apps)
84
+ if (!ownApp)
85
+ throw new Error('Expected own Express app');
86
+ const server = ownApp.listen(PORT, async (error) => {
65
87
  if (error) {
66
88
  log.error('Error starting HTTP server:', error);
67
89
  exitHandler(1);
@@ -34,7 +34,7 @@ export const zSkillCfg = z.discriminatedUnion('type', [
34
34
  zGitHubSkillCfg,
35
35
  zGitHubCollectionSkillCfg,
36
36
  ]);
37
- export const zSkillCfgMap = z.record(zSkillCfg);
37
+ export const zSkillCfgMap = z.record(z.string(), zSkillCfg);
38
38
  export const zSkillMatter = z.object({
39
39
  name: z.string().trim().min(1),
40
40
  description: z.string(),
@@ -42,7 +42,7 @@ export const zSkillMatter = z.object({
42
42
  export const zLocalSkill = zSkillMatter.extend(zLocalSkillCfg.shape);
43
43
  export const zGitHubSkill = zSkillMatter.extend(zGitHubSkillCfg.shape);
44
44
  export const zSkill = z.discriminatedUnion('type', [zLocalSkill, zGitHubSkill]);
45
- export const zSkillMap = z.record(zSkill);
45
+ export const zSkillMap = z.record(z.string(), zSkill);
46
46
  export const zViewSkillInputSchema = {
47
47
  skill_name: z
48
48
  .string()
package/dist/types.d.ts CHANGED
@@ -1,7 +1,8 @@
1
1
  import type { CompleteResourceTemplateCallback, ListResourcesCallback, ReadResourceCallback, ReadResourceTemplateCallback, ResourceMetadata } from '@modelcontextprotocol/sdk/server/mcp.js';
2
2
  import type { GetPromptResult, ToolAnnotations } from '@modelcontextprotocol/sdk/types.js';
3
3
  import type { Router } from 'express';
4
- import type { ZodRawShape, ZodTypeAny, z } from 'zod';
4
+ import type { ZodRawShape, z } from 'zod';
5
+ type ObjectOutput<T extends ZodRawShape> = z.infer<z.ZodObject<T>>;
5
6
  export type BaseToolConfig = {
6
7
  title?: string;
7
8
  description?: string;
@@ -40,8 +41,8 @@ export type ToolConfig<InputArgs extends ZodRawShape, OutputArgs extends ZodRawS
40
41
  };
41
42
  export interface ApiDefinition<InputArgs extends ZodRawShape, OutputArgs extends ZodRawShape, SimplifiedOutputArgs = OutputArgs> extends BaseApiDefinition {
42
43
  config: ToolConfig<InputArgs, OutputArgs>;
43
- fn(args: z.objectOutputType<InputArgs, ZodTypeAny>): Promise<z.objectOutputType<OutputArgs, ZodTypeAny>>;
44
- pickResult?(result: z.objectOutputType<OutputArgs, ZodTypeAny>): SimplifiedOutputArgs;
44
+ fn(args: ObjectOutput<InputArgs>): Promise<ObjectOutput<OutputArgs>>;
45
+ pickResult?(result: ObjectOutput<OutputArgs>): SimplifiedOutputArgs;
45
46
  }
46
47
  export type ApiFactory<Context extends Record<string, unknown>, Input extends ZodRawShape, Output extends ZodRawShape, RestOutput = Output> = (ctx: Context, featureFlags: McpFeatureFlags) => ApiDefinition<Input, Output, RestOutput> | Promise<ApiDefinition<Input, Output, RestOutput>>;
47
48
  export type RouterFactoryResult = [Router, () => void | Promise<void>];
@@ -52,7 +53,7 @@ export type PromptConfig<InputArgs extends ZodRawShape> = {
52
53
  };
53
54
  export interface PromptDefinition<InputArgs extends ZodRawShape> extends BasePromptDefinition {
54
55
  config: PromptConfig<InputArgs>;
55
- fn(args: z.objectOutputType<InputArgs, ZodTypeAny>): Promise<GetPromptResult>;
56
+ fn(args: ObjectOutput<InputArgs>): Promise<GetPromptResult>;
56
57
  }
57
58
  export type PromptFactory<Context extends Record<string, unknown>, Input extends ZodRawShape> = (ctx: Context, featureFlags: McpFeatureFlags) => PromptDefinition<Input>;
58
59
  export interface TemplatedResourceDefinition {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tigerdata/mcp-boilerplate",
3
- "version": "1.0.2",
3
+ "version": "1.2.0",
4
4
  "description": "MCP boilerplate code for Node.js",
5
5
  "license": "Apache-2.0",
6
6
  "author": "TigerData",
@@ -54,11 +54,14 @@
54
54
  "express": "^5.2.1",
55
55
  "gray-matter": "^4.0.3",
56
56
  "raw-body": "^3.0.2",
57
- "yaml": "^2.8.2",
58
- "zod": "^3.23.8"
57
+ "yaml": "^2.8.2"
58
+ },
59
+ "peerDependencies": {
60
+ "zod": "^3.25 || ^4.0"
59
61
  },
60
62
  "devDependencies": {
61
63
  "@biomejs/biome": "^2.3.11",
64
+ "zod": "^3.25.0",
62
65
  "@octokit/rest": "^22.0.1",
63
66
  "@types/bun": "^1.3.5",
64
67
  "@types/express": "^5.0.6",