apcore-mcp 0.2.0 → 0.4.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.
@@ -7,7 +7,7 @@
7
7
  * - Registering tools/list and tools/call request handlers
8
8
  */
9
9
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
10
- import { ListToolsRequestSchema, CallToolRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
10
+ import { ListToolsRequestSchema, CallToolRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
11
11
  import { SchemaConverter } from "../adapters/schema.js";
12
12
  import { AnnotationMapper } from "../adapters/annotations.js";
13
13
  export class MCPServerFactory {
@@ -25,7 +25,7 @@ export class MCPServerFactory {
25
25
  * @returns A configured Server instance with tools capability
26
26
  */
27
27
  createServer(name = "apcore-mcp", version = "0.1.0") {
28
- return new Server({ name, version }, { capabilities: { tools: {} } });
28
+ return new Server({ name, version }, { capabilities: { tools: {}, resources: {} } });
29
29
  }
30
30
  /**
31
31
  * Build an MCP Tool object from an apcore module descriptor.
@@ -40,9 +40,12 @@ export class MCPServerFactory {
40
40
  if (!descriptor.moduleId || typeof descriptor.moduleId !== "string") {
41
41
  throw new Error("ModuleDescriptor.moduleId is required and must be a string");
42
42
  }
43
- if (!descriptor.description || typeof descriptor.description !== "string") {
44
- throw new Error("ModuleDescriptor.description is required and must be a string");
43
+ if (descriptor.description !== undefined && descriptor.description !== null && typeof descriptor.description !== "string") {
44
+ throw new Error("ModuleDescriptor.description must be a string");
45
45
  }
46
+ // NOTE: TypeScript uses AnnotationMapper.toMcpAnnotations() directly,
47
+ // while Python uses SchemaExporter.export_mcp() for the same mapping.
48
+ // Both produce identical output. If annotation logic changes, update both paths.
46
49
  const mcpAnnotations = this._annotationMapper.toMcpAnnotations(descriptor.annotations);
47
50
  const convertedSchema = this._schemaConverter.convertInputSchema(descriptor);
48
51
  const hasApproval = this._annotationMapper.hasRequiresApproval(descriptor.annotations);
@@ -57,10 +60,16 @@ export class MCPServerFactory {
57
60
  openWorldHint: mcpAnnotations.openWorldHint,
58
61
  },
59
62
  };
60
- if (hasApproval) {
61
- tool._meta = {
62
- requiresApproval: true,
63
- };
63
+ const hasStreaming = descriptor.annotations?.streaming === true;
64
+ if (hasApproval || hasStreaming) {
65
+ const meta = {};
66
+ if (hasApproval) {
67
+ meta.requiresApproval = true;
68
+ }
69
+ if (hasStreaming) {
70
+ meta.streaming = true;
71
+ }
72
+ tool._meta = meta;
64
73
  }
65
74
  return tool;
66
75
  }
@@ -91,6 +100,68 @@ export class MCPServerFactory {
91
100
  }
92
101
  return tools;
93
102
  }
103
+ /**
104
+ * Register resources/list and resources/read handlers for modules with documentation.
105
+ *
106
+ * Iterates over registry.list(), gets each definition, and filters for
107
+ * descriptors that have a non-null `documentation` field. Registers:
108
+ * - resources/list: returns Resource objects with URI docs://{module_id}
109
+ * - resources/read: returns documentation text for the requested module
110
+ *
111
+ * @param server - The MCP Server to register handlers on
112
+ * @param registry - Registry to discover modules with documentation
113
+ */
114
+ registerResourceHandlers(server, registry) {
115
+ // Build a map of module_id -> documentation for modules with docs
116
+ const docsMap = new Map();
117
+ const moduleIds = registry.list();
118
+ for (const moduleId of moduleIds) {
119
+ try {
120
+ const descriptor = registry.getDefinition(moduleId);
121
+ if (descriptor?.documentation) {
122
+ docsMap.set(moduleId, descriptor.documentation);
123
+ }
124
+ }
125
+ catch {
126
+ // Skip modules that throw errors
127
+ }
128
+ }
129
+ // Handle resources/list requests
130
+ server.setRequestHandler(ListResourcesRequestSchema, async () => {
131
+ const resources = [];
132
+ for (const [moduleId, _doc] of docsMap) {
133
+ resources.push({
134
+ uri: `docs://${moduleId}`,
135
+ name: `${moduleId} documentation`,
136
+ mimeType: "text/plain",
137
+ });
138
+ }
139
+ return { resources };
140
+ });
141
+ // Handle resources/read requests
142
+ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
143
+ const uri = request.params.uri;
144
+ const prefix = "docs://";
145
+ if (!uri.startsWith(prefix)) {
146
+ throw new Error(`Unsupported URI scheme: ${uri}`);
147
+ }
148
+ const moduleId = uri.slice(prefix.length);
149
+ const documentation = docsMap.get(moduleId);
150
+ if (documentation === undefined) {
151
+ throw new Error(`Resource not found: ${uri}`);
152
+ }
153
+ const result = {
154
+ contents: [
155
+ {
156
+ uri,
157
+ text: documentation,
158
+ mimeType: "text/plain",
159
+ },
160
+ ],
161
+ };
162
+ return result;
163
+ });
164
+ }
94
165
  /**
95
166
  * Register tools/list and tools/call request handlers on a Server instance.
96
167
  *
@@ -104,16 +175,27 @@ export class MCPServerFactory {
104
175
  return { tools };
105
176
  });
106
177
  // Handle tools/call requests
107
- server.setRequestHandler(CallToolRequestSchema, async (request) => {
178
+ server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
108
179
  const { name, arguments: args } = request.params;
109
180
  const toolArgs = (args ?? {});
110
- const [content, isError] = await router.handleCall(name, toolArgs);
111
- if (isError) {
112
- throw new Error(content[0].text);
113
- }
181
+ // Build HandleCallExtra from MCP SDK extra
182
+ const handleCallExtra = {
183
+ sendNotification: extra?.sendNotification
184
+ ? (notification) => extra.sendNotification(notification)
185
+ : undefined,
186
+ sendRequest: extra?.sendRequest
187
+ ? (request, resultSchema) => extra.sendRequest(request, resultSchema)
188
+ : undefined,
189
+ _meta: extra?._meta
190
+ ? { progressToken: extra._meta.progressToken }
191
+ : undefined,
192
+ };
193
+ const [content, isError] = await router.handleCall(name, toolArgs, handleCallExtra);
194
+ // Return tool errors as MCP CallToolResult with isError flag
195
+ // rather than throwing protocol-level errors
114
196
  const result = {
115
- content: content,
116
- isError: false,
197
+ content: content.map(c => ({ type: "text", text: c.text })),
198
+ isError,
117
199
  };
118
200
  return result;
119
201
  });
@@ -1 +1 @@
1
- {"version":3,"file":"factory.js","sourceRoot":"","sources":["../../src/server/factory.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"factory.js","sourceRoot":"","sources":["../../src/server/factory.ts"],"names":[],"mappings":"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"}
@@ -3,28 +3,60 @@
3
3
  *
4
4
  * Bridges the MCP tools/call protocol with apcore's Executor.call() or callAsync(),
5
5
  * handling success/error formatting for MCP text content responses.
6
+ *
7
+ * When the executor supports stream() and the caller provides a progressToken,
8
+ * chunks are forwarded as MCP notifications/progress before returning the
9
+ * accumulated result.
6
10
  */
7
11
  import type { Executor, TextContentDict } from "../types.js";
8
12
  /** Tuple of [content array, isError flag] returned from handleCall. */
9
13
  export type CallResult = [TextContentDict[], boolean];
14
+ /**
15
+ * Extra context passed from the MCP SDK request handler.
16
+ *
17
+ * Mirrors the relevant subset of the SDK's `RequestHandlerExtra`:
18
+ * - `sendNotification` — sends an out-of-band notification on the current session
19
+ * - `sendRequest` — sends a request to the client (used for elicitation)
20
+ * - `_meta.progressToken` — opaque token the client attached to the request
21
+ */
22
+ export interface HandleCallExtra {
23
+ sendNotification?: (notification: Record<string, unknown>) => Promise<void>;
24
+ sendRequest?: (request: Record<string, unknown>, resultSchema: unknown) => Promise<unknown>;
25
+ _meta?: {
26
+ progressToken?: string | number;
27
+ };
28
+ }
29
+ /** Options for the ExecutionRouter constructor. */
30
+ export interface ExecutionRouterOptions {
31
+ validateInputs?: boolean;
32
+ }
10
33
  export declare class ExecutionRouter {
11
34
  private readonly _executor;
12
35
  private readonly _errorMapper;
36
+ private readonly _validateInputs;
13
37
  /**
14
38
  * Create an ExecutionRouter.
15
39
  *
16
40
  * @param executor - Duck-typed executor with call(moduleId, inputs) or callAsync(moduleId, inputs)
41
+ * @param options - Optional configuration including validateInputs
17
42
  */
18
- constructor(executor: Executor);
43
+ constructor(executor: Executor, options?: ExecutionRouterOptions);
19
44
  /**
20
45
  * Handle an MCP tools/call request by routing to the executor.
21
46
  *
22
- * Tries executor.call() first, then falls back to executor.callAsync().
47
+ * Streaming path: if the executor has stream(), a progressToken is present,
48
+ * and sendNotification is available, each chunk from stream() is forwarded as
49
+ * a `notifications/progress` notification. The chunks are shallow-merged into
50
+ * an accumulated result which is returned as the final response.
51
+ *
52
+ * Non-streaming path: tries executor.call() first, then falls back to
53
+ * executor.callAsync().
23
54
  *
24
55
  * @param toolName - The MCP tool name (maps to apcore moduleId)
25
56
  * @param args - The tool call arguments
57
+ * @param extra - Optional MCP SDK extra context with sendNotification and _meta
26
58
  * @returns Tuple of [content, isError] where content is an array of text content dicts
27
59
  */
28
- handleCall(toolName: string, args: Record<string, unknown>): Promise<CallResult>;
60
+ handleCall(toolName: string, args: Record<string, unknown>, extra?: HandleCallExtra): Promise<CallResult>;
29
61
  }
30
62
  //# sourceMappingURL=router.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../../src/server/router.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAE7D,uEAAuE;AACvE,MAAM,MAAM,UAAU,GAAG,CAAC,eAAe,EAAE,EAAE,OAAO,CAAC,CAAC;AAEtD,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAW;IACrC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAc;IAE3C;;;;OAIG;gBACS,QAAQ,EAAE,QAAQ;IAK9B;;;;;;;;OAQG;IACG,UAAU,CACd,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC5B,OAAO,CAAC,UAAU,CAAC;CA+BvB"}
1
+ {"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../../src/server/router.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,KAAK,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAK7D,uEAAuE;AACvE,MAAM,MAAM,UAAU,GAAG,CAAC,eAAe,EAAE,EAAE,OAAO,CAAC,CAAC;AAEtD;;;;;;;GAOG;AACH,MAAM,WAAW,eAAe;IAC9B,gBAAgB,CAAC,EAAE,CAAC,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5E,WAAW,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY,EAAE,OAAO,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC5F,KAAK,CAAC,EAAE;QAAE,aAAa,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;KAAE,CAAC;CAC7C;AAED,mDAAmD;AACnD,MAAM,WAAW,sBAAsB;IACrC,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAW;IACrC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAc;IAC3C,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAU;IAE1C;;;;;OAKG;gBACS,QAAQ,EAAE,QAAQ,EAAE,OAAO,CAAC,EAAE,sBAAsB;IAMhE;;;;;;;;;;;;;;;OAeG;IACG,UAAU,CACd,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,KAAK,CAAC,EAAE,eAAe,GACtB,OAAO,CAAC,UAAU,CAAC;CAkLvB"}
@@ -3,47 +3,187 @@
3
3
  *
4
4
  * Bridges the MCP tools/call protocol with apcore's Executor.call() or callAsync(),
5
5
  * handling success/error formatting for MCP text content responses.
6
+ *
7
+ * When the executor supports stream() and the caller provides a progressToken,
8
+ * chunks are forwarded as MCP notifications/progress before returning the
9
+ * accumulated result.
6
10
  */
7
11
  import { ErrorMapper } from "../adapters/errors.js";
12
+ import { createBridgeContext } from "./context.js";
13
+ import { MCP_PROGRESS_KEY, MCP_ELICIT_KEY } from "../helpers.js";
8
14
  export class ExecutionRouter {
9
15
  _executor;
10
16
  _errorMapper;
17
+ _validateInputs;
11
18
  /**
12
19
  * Create an ExecutionRouter.
13
20
  *
14
21
  * @param executor - Duck-typed executor with call(moduleId, inputs) or callAsync(moduleId, inputs)
22
+ * @param options - Optional configuration including validateInputs
15
23
  */
16
- constructor(executor) {
24
+ constructor(executor, options) {
17
25
  this._executor = executor;
18
26
  this._errorMapper = new ErrorMapper();
27
+ this._validateInputs = options?.validateInputs ?? false;
19
28
  }
20
29
  /**
21
30
  * Handle an MCP tools/call request by routing to the executor.
22
31
  *
23
- * Tries executor.call() first, then falls back to executor.callAsync().
32
+ * Streaming path: if the executor has stream(), a progressToken is present,
33
+ * and sendNotification is available, each chunk from stream() is forwarded as
34
+ * a `notifications/progress` notification. The chunks are shallow-merged into
35
+ * an accumulated result which is returned as the final response.
36
+ *
37
+ * Non-streaming path: tries executor.call() first, then falls back to
38
+ * executor.callAsync().
24
39
  *
25
40
  * @param toolName - The MCP tool name (maps to apcore moduleId)
26
41
  * @param args - The tool call arguments
42
+ * @param extra - Optional MCP SDK extra context with sendNotification and _meta
27
43
  * @returns Tuple of [content, isError] where content is an array of text content dicts
28
44
  */
29
- async handleCall(toolName, args) {
45
+ async handleCall(toolName, args, extra) {
30
46
  try {
31
- const callFn = this._executor.call
47
+ // ── Build context with MCP callbacks ──────────────────────────────
48
+ const progressToken = extra?._meta?.progressToken;
49
+ const sendNotification = extra?.sendNotification;
50
+ const sendRequest = extra?.sendRequest;
51
+ const contextData = {};
52
+ let hasCallbacks = false;
53
+ // Inject progress callback if progressToken + sendNotification available
54
+ if (progressToken !== undefined && sendNotification) {
55
+ hasCallbacks = true;
56
+ contextData[MCP_PROGRESS_KEY] = async (progress, total, message) => {
57
+ await sendNotification({
58
+ method: "notifications/progress",
59
+ params: {
60
+ progressToken,
61
+ progress,
62
+ total: total ?? 0,
63
+ ...(message !== undefined ? { message } : {}),
64
+ },
65
+ });
66
+ };
67
+ }
68
+ // Inject elicitation callback if sendRequest available
69
+ if (sendRequest) {
70
+ hasCallbacks = true;
71
+ contextData[MCP_ELICIT_KEY] = async (message, requestedSchema) => {
72
+ const result = await sendRequest({
73
+ method: "elicitation/create",
74
+ params: {
75
+ message,
76
+ ...(requestedSchema ? { requestedSchema } : {}),
77
+ },
78
+ }, {});
79
+ return result ?? null;
80
+ };
81
+ }
82
+ const context = hasCallbacks
83
+ ? createBridgeContext(contextData)
84
+ : undefined;
85
+ // ── Pre-execution validation ────────────────────────────────────
86
+ if (this._validateInputs && this._executor.validate) {
87
+ try {
88
+ const rawErrors = await this._executor.validate(toolName, args);
89
+ let errorMessages = [];
90
+ if (Array.isArray(rawErrors)) {
91
+ // Handle both string[] and ValidationResult.errors (array of objects)
92
+ errorMessages = rawErrors.map((e) => {
93
+ if (typeof e === 'string')
94
+ return e;
95
+ if (typeof e === 'object' && e !== null) {
96
+ const obj = e;
97
+ const field = obj.field ?? obj.path ?? '?';
98
+ const msg = obj.message ?? 'invalid';
99
+ return `${field}: ${msg}`;
100
+ }
101
+ return String(e);
102
+ });
103
+ }
104
+ else if (rawErrors && typeof rawErrors === 'object' && 'valid' in rawErrors) {
105
+ // Handle ValidationResult object
106
+ const vr = rawErrors;
107
+ if (!vr.valid) {
108
+ errorMessages = vr.errors.map(e => `${e.field ?? '?'}: ${e.message ?? 'invalid'}`);
109
+ }
110
+ }
111
+ if (errorMessages.length > 0) {
112
+ const detail = errorMessages.join("; ");
113
+ const content = [
114
+ { type: "text", text: `Validation failed: ${detail}` },
115
+ ];
116
+ return [content, true];
117
+ }
118
+ }
119
+ catch (valError) {
120
+ const errorInfo = this._errorMapper.toMcpError(valError);
121
+ const content = [
122
+ { type: "text", text: errorInfo.message },
123
+ ];
124
+ return [content, true];
125
+ }
126
+ }
127
+ // ── Streaming path ────────────────────────────────────────────────
128
+ if (this._executor.stream &&
129
+ progressToken !== undefined &&
130
+ sendNotification) {
131
+ let accumulated = {};
132
+ let chunkIndex = 0;
133
+ for await (const chunk of this._executor.stream(toolName, args, context)) {
134
+ // Shallow-merge each chunk into the accumulated result
135
+ accumulated = { ...accumulated, ...chunk };
136
+ // Send progress notification for each chunk
137
+ await sendNotification({
138
+ method: "notifications/progress",
139
+ params: {
140
+ progressToken,
141
+ progress: chunkIndex + 1,
142
+ message: JSON.stringify(chunk),
143
+ },
144
+ });
145
+ chunkIndex++;
146
+ }
147
+ const content = [
148
+ {
149
+ type: "text",
150
+ text: JSON.stringify(accumulated),
151
+ },
152
+ ];
153
+ if (context) {
154
+ content.push({
155
+ type: "text",
156
+ text: JSON.stringify({ _trace_id: context.traceId }),
157
+ });
158
+ }
159
+ return [content, false];
160
+ }
161
+ // ── Non-streaming path ────────────────────────────────────────────
162
+ const callFn = typeof this._executor.call === 'function'
32
163
  ? this._executor.call.bind(this._executor)
33
- : this._executor.callAsync?.bind(this._executor);
164
+ : typeof this._executor.callAsync === 'function'
165
+ ? this._executor.callAsync.bind(this._executor)
166
+ : null;
34
167
  if (!callFn) {
35
- throw new Error("Executor must implement call() or callAsync()");
168
+ throw new Error('Executor must implement call() or callAsync()');
36
169
  }
37
- const result = await callFn(toolName, args);
170
+ const result = await callFn(toolName, args, context);
38
171
  const content = [
39
172
  {
40
173
  type: "text",
41
174
  text: JSON.stringify(result),
42
175
  },
43
176
  ];
177
+ if (context) {
178
+ content.push({
179
+ type: "text",
180
+ text: JSON.stringify({ _trace_id: context.traceId }),
181
+ });
182
+ }
44
183
  return [content, false];
45
184
  }
46
185
  catch (error) {
186
+ console.error(`handleCall error for ${toolName}:`, error);
47
187
  const errorInfo = this._errorMapper.toMcpError(error);
48
188
  const content = [
49
189
  {
@@ -1 +1 @@
1
- {"version":3,"file":"router.js","sourceRoot":"","sources":["../../src/server/router.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAMpD,MAAM,OAAO,eAAe;IACT,SAAS,CAAW;IACpB,YAAY,CAAc;IAE3C;;;;OAIG;IACH,YAAY,QAAkB;QAC5B,IAAI,CAAC,SAAS,GAAG,QAAQ,CAAC;QAC1B,IAAI,CAAC,YAAY,GAAG,IAAI,WAAW,EAAE,CAAC;IACxC,CAAC;IAED;;;;;;;;OAQG;IACH,KAAK,CAAC,UAAU,CACd,QAAgB,EAChB,IAA6B;QAE7B,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI;gBAChC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC;gBAC1C,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,SAAS,EAAE,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YACnD,IAAI,CAAC,MAAM,EAAE,CAAC;gBACZ,MAAM,IAAI,KAAK,CAAC,+CAA+C,CAAC,CAAC;YACnE,CAAC;YACD,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;YAE5C,MAAM,OAAO,GAAsB;gBACjC;oBACE,IAAI,EAAE,MAAM;oBACZ,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC;iBAC7B;aACF,CAAC;YAEF,OAAO,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;QAC1B,CAAC;QAAC,OAAO,KAAc,EAAE,CAAC;YACxB,MAAM,SAAS,GAAG,IAAI,CAAC,YAAY,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;YAEtD,MAAM,OAAO,GAAsB;gBACjC;oBACE,IAAI,EAAE,MAAM;oBACZ,IAAI,EAAE,SAAS,CAAC,OAAO;iBACxB;aACF,CAAC;YAEF,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;QACzB,CAAC;IACH,CAAC;CACF"}
1
+ {"version":3,"file":"router.js","sourceRoot":"","sources":["../../src/server/router.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAEpD,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAyBjE,MAAM,OAAO,eAAe;IACT,SAAS,CAAW;IACpB,YAAY,CAAc;IAC1B,eAAe,CAAU;IAE1C;;;;;OAKG;IACH,YAAY,QAAkB,EAAE,OAAgC;QAC9D,IAAI,CAAC,SAAS,GAAG,QAAQ,CAAC;QAC1B,IAAI,CAAC,YAAY,GAAG,IAAI,WAAW,EAAE,CAAC;QACtC,IAAI,CAAC,eAAe,GAAG,OAAO,EAAE,cAAc,IAAI,KAAK,CAAC;IAC1D,CAAC;IAED;;;;;;;;;;;;;;;OAeG;IACH,KAAK,CAAC,UAAU,CACd,QAAgB,EAChB,IAA6B,EAC7B,KAAuB;QAEvB,IAAI,CAAC;YACH,qEAAqE;YACrE,MAAM,aAAa,GAAG,KAAK,EAAE,KAAK,EAAE,aAAa,CAAC;YAClD,MAAM,gBAAgB,GAAG,KAAK,EAAE,gBAAgB,CAAC;YACjD,MAAM,WAAW,GAAG,KAAK,EAAE,WAAW,CAAC;YAEvC,MAAM,WAAW,GAA4B,EAAE,CAAC;YAChD,IAAI,YAAY,GAAG,KAAK,CAAC;YAEzB,yEAAyE;YACzE,IAAI,aAAa,KAAK,SAAS,IAAI,gBAAgB,EAAE,CAAC;gBACpD,YAAY,GAAG,IAAI,CAAC;gBACpB,WAAW,CAAC,gBAAgB,CAAC,GAAG,KAAK,EACnC,QAAgB,EAChB,KAAc,EACd,OAAgB,EACD,EAAE;oBACjB,MAAM,gBAAgB,CAAC;wBACrB,MAAM,EAAE,wBAAwB;wBAChC,MAAM,EAAE;4BACN,aAAa;4BACb,QAAQ;4BACR,KAAK,EAAE,KAAK,IAAI,CAAC;4BACjB,GAAG,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;yBAC9C;qBACF,CAAC,CAAC;gBACL,CAAC,CAAC;YACJ,CAAC;YAED,uDAAuD;YACvD,IAAI,WAAW,EAAE,CAAC;gBAChB,YAAY,GAAG,IAAI,CAAC;gBACpB,WAAW,CAAC,cAAc,CAAC,GAAG,KAAK,EACjC,OAAe,EACf,eAAyC,EACX,EAAE;oBAChC,MAAM,MAAM,GAAG,MAAM,WAAW,CAC9B;wBACE,MAAM,EAAE,oBAAoB;wBAC5B,MAAM,EAAE;4BACN,OAAO;4BACP,GAAG,CAAC,eAAe,CAAC,CAAC,CAAC,EAAE,eAAe,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;yBAChD;qBACF,EACD,EAAE,CACH,CAAC;oBACF,OAAQ,MAAuB,IAAI,IAAI,CAAC;gBAC1C,CAAC,CAAC;YACJ,CAAC;YAED,MAAM,OAAO,GAAG,YAAY;gBAC1B,CAAC,CAAC,mBAAmB,CAAC,WAAW,CAAC;gBAClC,CAAC,CAAC,SAAS,CAAC;YAEd,mEAAmE;YACnE,IAAI,IAAI,CAAC,eAAe,IAAI,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,CAAC;gBACpD,IAAI,CAAC;oBACH,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;oBAChE,IAAI,aAAa,GAAa,EAAE,CAAC;oBACjC,IAAI,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,CAAC;wBAC7B,sEAAsE;wBACtE,aAAa,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC,CAAU,EAAE,EAAE;4BAC3C,IAAI,OAAO,CAAC,KAAK,QAAQ;gCAAE,OAAO,CAAC,CAAC;4BACpC,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC;gCACxC,MAAM,GAAG,GAAG,CAA4B,CAAC;gCACzC,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,IAAI,GAAG,CAAC,IAAI,IAAI,GAAG,CAAC;gCAC3C,MAAM,GAAG,GAAG,GAAG,CAAC,OAAO,IAAI,SAAS,CAAC;gCACrC,OAAO,GAAG,KAAK,KAAK,GAAG,EAAE,CAAC;4BAC5B,CAAC;4BACD,OAAO,MAAM,CAAC,CAAC,CAAC,CAAC;wBACnB,CAAC,CAAC,CAAC;oBACL,CAAC;yBAAM,IAAI,SAAS,IAAI,OAAO,SAAS,KAAK,QAAQ,IAAI,OAAO,IAAK,SAAoB,EAAE,CAAC;wBAC1F,iCAAiC;wBACjC,MAAM,EAAE,GAAG,SAAoF,CAAC;wBAChG,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC;4BACd,aAAa,GAAG,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,KAAK,IAAI,GAAG,KAAK,CAAC,CAAC,OAAO,IAAI,SAAS,EAAE,CAAC,CAAC;wBACrF,CAAC;oBACH,CAAC;oBACD,IAAI,aAAa,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;wBAC7B,MAAM,MAAM,GAAG,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;wBACxC,MAAM,OAAO,GAAsB;4BACjC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,sBAAsB,MAAM,EAAE,EAAE;yBACvD,CAAC;wBACF,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;oBACzB,CAAC;gBACH,CAAC;gBAAC,OAAO,QAAiB,EAAE,CAAC;oBAC3B,MAAM,SAAS,GAAG,IAAI,CAAC,YAAY,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;oBACzD,MAAM,OAAO,GAAsB;wBACjC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,CAAC,OAAO,EAAE;qBAC1C,CAAC;oBACF,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;gBACzB,CAAC;YACH,CAAC;YAED,qEAAqE;YACrE,IACE,IAAI,CAAC,SAAS,CAAC,MAAM;gBACrB,aAAa,KAAK,SAAS;gBAC3B,gBAAgB,EAChB,CAAC;gBACD,IAAI,WAAW,GAA4B,EAAE,CAAC;gBAC9C,IAAI,UAAU,GAAG,CAAC,CAAC;gBAEnB,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,QAAQ,EAAE,IAAI,EAAE,OAAO,CAAC,EAAE,CAAC;oBACzE,uDAAuD;oBACvD,WAAW,GAAG,EAAE,GAAG,WAAW,EAAE,GAAG,KAAK,EAAE,CAAC;oBAE3C,4CAA4C;oBAC5C,MAAM,gBAAgB,CAAC;wBACrB,MAAM,EAAE,wBAAwB;wBAChC,MAAM,EAAE;4BACN,aAAa;4BACb,QAAQ,EAAE,UAAU,GAAG,CAAC;4BACxB,OAAO,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC;yBAC/B;qBACF,CAAC,CAAC;oBAEH,UAAU,EAAE,CAAC;gBACf,CAAC;gBAED,MAAM,OAAO,GAAsB;oBACjC;wBACE,IAAI,EAAE,MAAM;wBACZ,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC;qBAClC;iBACF,CAAC;gBAEF,IAAI,OAAO,EAAE,CAAC;oBACZ,OAAO,CAAC,IAAI,CAAC;wBACX,IAAI,EAAE,MAAM;wBACZ,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,SAAS,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC;qBACrD,CAAC,CAAC;gBACL,CAAC;gBAED,OAAO,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;YAC1B,CAAC;YAED,qEAAqE;YACrE,MAAM,MAAM,GAAG,OAAO,IAAI,CAAC,SAAS,CAAC,IAAI,KAAK,UAAU;gBACtD,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC;gBAC1C,CAAC,CAAC,OAAO,IAAI,CAAC,SAAS,CAAC,SAAS,KAAK,UAAU;oBAChD,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC;oBAC/C,CAAC,CAAC,IAAI,CAAC;YACT,IAAI,CAAC,MAAM,EAAE,CAAC;gBACZ,MAAM,IAAI,KAAK,CAAC,+CAA+C,CAAC,CAAC;YACnE,CAAC;YACD,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,QAAQ,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC;YAErD,MAAM,OAAO,GAAsB;gBACjC;oBACE,IAAI,EAAE,MAAM;oBACZ,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC;iBAC7B;aACF,CAAC;YAEF,IAAI,OAAO,EAAE,CAAC;gBACZ,OAAO,CAAC,IAAI,CAAC;oBACX,IAAI,EAAE,MAAM;oBACZ,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,SAAS,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC;iBACrD,CAAC,CAAC;YACL,CAAC;YAED,OAAO,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;QAC1B,CAAC;QAAC,OAAO,KAAc,EAAE,CAAC;YACxB,OAAO,CAAC,KAAK,CAAC,wBAAwB,QAAQ,GAAG,EAAE,KAAK,CAAC,CAAC;YAC1D,MAAM,SAAS,GAAG,IAAI,CAAC,YAAY,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;YAEtD,MAAM,OAAO,GAAsB;gBACjC;oBACE,IAAI,EAAE,MAAM;oBACZ,IAAI,EAAE,SAAS,CAAC,OAAO;iBACxB;aACF,CAAC;YAEF,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;QACzB,CAAC;IACH,CAAC;CACF"}
@@ -14,9 +14,46 @@ export interface HttpTransportOptions {
14
14
  port: number;
15
15
  endpoint?: string;
16
16
  }
17
+ /** Duck-typed interface for a metrics collector that can export Prometheus text. */
18
+ export interface MetricsExporter {
19
+ exportPrometheus(): string;
20
+ }
17
21
  export declare class TransportManager {
18
22
  /** The underlying HTTP server, if an HTTP-based transport is active. */
19
23
  httpServer?: HttpServer;
24
+ /** Timestamp (ms) when this manager was created, used for uptime calculation. */
25
+ private _startTime;
26
+ /** Number of registered modules/tools. */
27
+ private _moduleCount;
28
+ /** Optional metrics collector for Prometheus /metrics endpoint. */
29
+ private _metricsCollector?;
30
+ /**
31
+ * Set the number of registered modules/tools.
32
+ *
33
+ * @param count - The number of modules
34
+ */
35
+ setModuleCount(count: number): void;
36
+ /**
37
+ * Set the metrics collector for Prometheus /metrics endpoint.
38
+ *
39
+ * @param collector - A MetricsExporter instance (e.g. MetricsCollector from apcore)
40
+ */
41
+ setMetricsCollector(collector: MetricsExporter): void;
42
+ /**
43
+ * Build the health check response payload.
44
+ *
45
+ * @returns Health status object with uptime and module count
46
+ */
47
+ private _buildHealthResponse;
48
+ /**
49
+ * Handle built-in routes (/health and /metrics).
50
+ *
51
+ * @param req - The incoming HTTP request
52
+ * @param res - The server response
53
+ * @param url - The parsed URL
54
+ * @returns true if the request was handled, false otherwise
55
+ */
56
+ private _handleBuiltinRoute;
20
57
  /**
21
58
  * Run the server using stdio transport.
22
59
  *
@@ -1 +1 @@
1
- {"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../../src/server/transport.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAsC,KAAK,MAAM,IAAI,UAAU,EAAuB,MAAM,WAAW,CAAC;AAC/G,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AAKxE,yCAAyC;AACzC,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AA+CD,qBAAa,gBAAgB;IAC3B,wEAAwE;IACxE,UAAU,CAAC,EAAE,UAAU,CAAC;IAExB;;;;;;;OAOG;IACG,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAK7C;;;;;;;;OAQG;IACG,iBAAiB,CACrB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,oBAAoB,GAC5B,OAAO,CAAC,IAAI,CAAC;IAuDhB;;;;;;;;;OASG;IACG,MAAM,CACV,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,oBAAoB,GAC5B,OAAO,CAAC,IAAI,CAAC;IAuEhB;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAQ5B;;;;;;OAMG;IACH,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;CAcpD"}
1
+ {"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../../src/server/transport.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAsC,KAAK,MAAM,IAAI,UAAU,EAAuB,MAAM,WAAW,CAAC;AAC/G,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AAKxE,yCAAyC;AACzC,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,oFAAoF;AACpF,MAAM,WAAW,eAAe;IAC9B,gBAAgB,IAAI,MAAM,CAAC;CAC5B;AAkDD,qBAAa,gBAAgB;IAC3B,wEAAwE;IACxE,UAAU,CAAC,EAAE,UAAU,CAAC;IAExB,iFAAiF;IACjF,OAAO,CAAC,UAAU,CAAsB;IAExC,0CAA0C;IAC1C,OAAO,CAAC,YAAY,CAAa;IAEjC,mEAAmE;IACnE,OAAO,CAAC,iBAAiB,CAAC,CAAkB;IAE5C;;;;OAIG;IACH,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAInC;;;;OAIG;IACH,mBAAmB,CAAC,SAAS,EAAE,eAAe,GAAG,IAAI;IAIrD;;;;OAIG;IACH,OAAO,CAAC,oBAAoB;IAQ5B;;;;;;;OAOG;IACH,OAAO,CAAC,mBAAmB;IAyB3B;;;;;;;OAOG;IACG,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAK7C;;;;;;;;OAQG;IACG,iBAAiB,CACrB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,oBAAoB,GAC5B,OAAO,CAAC,IAAI,CAAC;IAyDhB;;;;;;;;;OASG;IACG,MAAM,CACV,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,oBAAoB,GAC5B,OAAO,CAAC,IAAI,CAAC;IAyEhB;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAQ5B;;;;;;OAMG;IACH,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;CAcpD"}
@@ -12,6 +12,8 @@ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/
12
12
  import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
13
13
  /** Default maximum request body size in bytes (4MB). */
14
14
  const DEFAULT_MAX_BODY_BYTES = 4 * 1024 * 1024;
15
+ /** Prometheus exposition format content-type. */
16
+ const PROMETHEUS_CONTENT_TYPE = "text/plain; version=0.0.4; charset=utf-8";
15
17
  /** Maximum request body size in bytes. Configurable via APCORE_MAX_BODY_BYTES env var. */
16
18
  const MAX_BODY_BYTES = (() => {
17
19
  const env = process.env.APCORE_MAX_BODY_BYTES;
@@ -60,6 +62,73 @@ function readBody(req, maxBytes = MAX_BODY_BYTES) {
60
62
  export class TransportManager {
61
63
  /** The underlying HTTP server, if an HTTP-based transport is active. */
62
64
  httpServer;
65
+ /** Timestamp (ms) when this manager was created, used for uptime calculation. */
66
+ _startTime = Date.now();
67
+ /** Number of registered modules/tools. */
68
+ _moduleCount = 0;
69
+ /** Optional metrics collector for Prometheus /metrics endpoint. */
70
+ _metricsCollector;
71
+ /**
72
+ * Set the number of registered modules/tools.
73
+ *
74
+ * @param count - The number of modules
75
+ */
76
+ setModuleCount(count) {
77
+ this._moduleCount = count;
78
+ }
79
+ /**
80
+ * Set the metrics collector for Prometheus /metrics endpoint.
81
+ *
82
+ * @param collector - A MetricsExporter instance (e.g. MetricsCollector from apcore)
83
+ */
84
+ setMetricsCollector(collector) {
85
+ this._metricsCollector = collector;
86
+ }
87
+ /**
88
+ * Build the health check response payload.
89
+ *
90
+ * @returns Health status object with uptime and module count
91
+ */
92
+ _buildHealthResponse() {
93
+ return {
94
+ status: "ok",
95
+ uptime_seconds: Math.round((Date.now() - this._startTime) / 100) / 10,
96
+ module_count: this._moduleCount,
97
+ };
98
+ }
99
+ /**
100
+ * Handle built-in routes (/health and /metrics).
101
+ *
102
+ * @param req - The incoming HTTP request
103
+ * @param res - The server response
104
+ * @param url - The parsed URL
105
+ * @returns true if the request was handled, false otherwise
106
+ */
107
+ _handleBuiltinRoute(req, res, url) {
108
+ if (url.pathname === "/health" && req.method === "GET") {
109
+ res.writeHead(200, { "Content-Type": "application/json" });
110
+ res.end(JSON.stringify(this._buildHealthResponse()));
111
+ return true;
112
+ }
113
+ if (url.pathname === "/metrics" && req.method === "GET") {
114
+ if (!this._metricsCollector) {
115
+ res.writeHead(404);
116
+ res.end();
117
+ return true;
118
+ }
119
+ try {
120
+ const body = this._metricsCollector.exportPrometheus();
121
+ res.writeHead(200, { "Content-Type": PROMETHEUS_CONTENT_TYPE });
122
+ res.end(body);
123
+ }
124
+ catch {
125
+ res.writeHead(500);
126
+ res.end();
127
+ }
128
+ return true;
129
+ }
130
+ return false;
131
+ }
63
132
  /**
64
133
  * Run the server using stdio transport.
65
134
  *
@@ -90,6 +159,8 @@ export class TransportManager {
90
159
  await server.connect(transport);
91
160
  const httpServer = createServer(async (req, res) => {
92
161
  const url = new URL(req.url ?? "/", `http://${options.host}:${options.port}`);
162
+ if (this._handleBuiltinRoute(req, res, url))
163
+ return;
93
164
  if (url.pathname !== endpoint) {
94
165
  res.writeHead(404).end("Not Found");
95
166
  return;
@@ -146,6 +217,8 @@ export class TransportManager {
146
217
  const transports = new Map();
147
218
  const httpServer = createServer(async (req, res) => {
148
219
  const url = new URL(req.url ?? "/", `http://${options.host}:${options.port}`);
220
+ if (this._handleBuiltinRoute(req, res, url))
221
+ return;
149
222
  if (url.pathname === endpoint && req.method === "GET") {
150
223
  // Establish SSE connection
151
224
  const transport = new SSEServerTransport(messagesEndpoint, res);