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.
- package/dist/adapters/annotations.d.ts +6 -2
- package/dist/adapters/annotations.d.ts.map +1 -1
- package/dist/adapters/annotations.js +27 -9
- package/dist/adapters/annotations.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +11 -5
- package/dist/cli.js.map +1 -1
- package/dist/helpers.d.ts +44 -0
- package/dist/helpers.d.ts.map +1 -0
- package/dist/helpers.js +45 -0
- package/dist/helpers.js.map +1 -0
- package/dist/index.d.ts +37 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +91 -19
- package/dist/index.js.map +1 -1
- package/dist/server/context.d.ts +31 -0
- package/dist/server/context.d.ts.map +1 -0
- package/dist/server/context.js +39 -0
- package/dist/server/context.js.map +1 -0
- package/dist/server/factory.d.ts +12 -0
- package/dist/server/factory.d.ts.map +1 -1
- package/dist/server/factory.js +97 -15
- package/dist/server/factory.js.map +1 -1
- package/dist/server/router.d.ts +35 -3
- package/dist/server/router.d.ts.map +1 -1
- package/dist/server/router.js +147 -7
- package/dist/server/router.js.map +1 -1
- package/dist/server/transport.d.ts +37 -0
- package/dist/server/transport.d.ts.map +1 -1
- package/dist/server/transport.js +73 -0
- package/dist/server/transport.js.map +1 -1
- package/dist/types.d.ts +8 -3
- package/dist/types.d.ts.map +1 -1
- package/package.json +19 -2
package/dist/server/factory.js
CHANGED
|
@@ -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 (
|
|
44
|
-
throw new Error("ModuleDescriptor.description
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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
|
|
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":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AACnE,OAAO,EACL,sBAAsB,EACtB,qBAAqB,
|
|
1
|
+
{"version":3,"file":"factory.js","sourceRoot":"","sources":["../../src/server/factory.ts"],"names":[],"mappings":"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"}
|
package/dist/server/router.d.ts
CHANGED
|
@@ -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
|
-
*
|
|
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
|
|
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
|
|
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"}
|
package/dist/server/router.js
CHANGED
|
@@ -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
|
-
*
|
|
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
|
-
|
|
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
|
|
164
|
+
: typeof this._executor.callAsync === 'function'
|
|
165
|
+
? this._executor.callAsync.bind(this._executor)
|
|
166
|
+
: null;
|
|
34
167
|
if (!callFn) {
|
|
35
|
-
throw new Error(
|
|
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
|
|
1
|
+
{"version":3,"file":"router.js","sourceRoot":"","sources":["../../src/server/router.ts"],"names":[],"mappings":"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"}
|
|
@@ -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;
|
|
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"}
|
package/dist/server/transport.js
CHANGED
|
@@ -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);
|