@ontrails/mcp 1.0.0-beta.8 → 1.0.0-beta.9
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/.turbo/turbo-lint.log +1 -1
- package/CHANGELOG.md +25 -0
- package/README.md +5 -2
- package/dist/blaze.d.ts.map +1 -1
- package/dist/blaze.js +5 -2
- package/dist/blaze.js.map +1 -1
- package/dist/build.d.ts +4 -1
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +19 -36
- package/dist/build.js.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/blaze.test.ts +18 -4
- package/src/__tests__/build.test.ts +58 -35
- package/src/blaze.ts +6 -2
- package/src/build.ts +36 -54
package/.turbo/turbo-lint.log
CHANGED
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,30 @@
|
|
|
1
1
|
# @ontrails/mcp
|
|
2
2
|
|
|
3
|
+
## 1.0.0-beta.9
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- Consolidated improvements across all surface packages.
|
|
8
|
+
|
|
9
|
+
**core**: Add `TrailResult<T>` utility type, `topo.ids()` and `topo.count` accessors, `dispatch()` for headless trail execution, and extract shared `executeTrail` pipeline used by CLI/MCP/HTTP.
|
|
10
|
+
|
|
11
|
+
**http**: Detect route path collisions and return `Result` from `buildHttpRoutes()`, wire request `AbortSignal` through to trail context, and make write → POST mapping explicit in intent-to-method lookup.
|
|
12
|
+
|
|
13
|
+
**mcp**: Return `Result` from `buildMcpTools()` on collision instead of throwing.
|
|
14
|
+
|
|
15
|
+
**cli**: Verify exception catching via centralized `executeTrail`.
|
|
16
|
+
|
|
17
|
+
**testing**: Follow context awareness improvements.
|
|
18
|
+
|
|
19
|
+
**warden**: Refactor rules as composable trails with examples.
|
|
20
|
+
|
|
21
|
+
**schema**: Error code and empty body fixes.
|
|
22
|
+
|
|
23
|
+
### Patch Changes
|
|
24
|
+
|
|
25
|
+
- Updated dependencies
|
|
26
|
+
- @ontrails/core@1.0.0-beta.9
|
|
27
|
+
|
|
3
28
|
## 1.0.0-beta.8
|
|
4
29
|
|
|
5
30
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -26,8 +26,9 @@ For more control, build the tools yourself:
|
|
|
26
26
|
```typescript
|
|
27
27
|
import { buildMcpTools } from '@ontrails/mcp';
|
|
28
28
|
|
|
29
|
-
const
|
|
30
|
-
|
|
29
|
+
const result = buildMcpTools(app);
|
|
30
|
+
if (result.isErr()) throw result.error; // ValidationError on tool-name collision
|
|
31
|
+
for (const tool of result.value) {
|
|
31
32
|
server.registerTool(tool.name, tool.handler, {
|
|
32
33
|
inputSchema: tool.inputSchema,
|
|
33
34
|
annotations: tool.annotations,
|
|
@@ -35,6 +36,8 @@ for (const tool of tools) {
|
|
|
35
36
|
}
|
|
36
37
|
```
|
|
37
38
|
|
|
39
|
+
`buildMcpTools` returns `Result<McpToolDefinition[], Error>` rather than a bare array. It returns `Result.err(ValidationError)` if two trails derive the same MCP tool name. Each `McpToolDefinition` includes a `trailId` field that records which trail the tool was derived from.
|
|
40
|
+
|
|
38
41
|
## API
|
|
39
42
|
|
|
40
43
|
| Export | What it does |
|
package/dist/blaze.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"blaze.d.ts","sourceRoot":"","sources":["../src/blaze.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AAKnE,OAAO,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAEhE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAQpD,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,aAAa,CAAC,EACnB,CAAC,MAAM,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC,GAC5C,SAAS,CAAC;IACd,QAAQ,CAAC,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAAC;IACvD,QAAQ,CAAC,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAAC;IACvD,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,KAAK,EAAE,GAAG,SAAS,CAAC;IAC/C,QAAQ,CAAC,UAAU,CAAC,EAChB;QACE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;QACnC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;KACvC,GACD,SAAS,CAAC;IACd,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CAC1C;AAMD;;GAEG;AACH,eAAO,MAAM,eAAe,GAC1B,OAAO,iBAAiB,EAAE,EAC1B,MAAM;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CAAE,KACxD,MAmEF,CAAC;AAMF;;GAEG;AACH,eAAO,MAAM,KAAK,GAChB,KAAK,IAAI,EACT,UAAS,eAAoB,KAC5B,OAAO,CAAC,IAAI,
|
|
1
|
+
{"version":3,"file":"blaze.d.ts","sourceRoot":"","sources":["../src/blaze.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AAKnE,OAAO,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAEhE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAQpD,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,aAAa,CAAC,EACnB,CAAC,MAAM,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC,GAC5C,SAAS,CAAC;IACd,QAAQ,CAAC,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAAC;IACvD,QAAQ,CAAC,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAAC;IACvD,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,KAAK,EAAE,GAAG,SAAS,CAAC;IAC/C,QAAQ,CAAC,UAAU,CAAC,EAChB;QACE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;QACnC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;KACvC,GACD,SAAS,CAAC;IACd,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CAC1C;AAMD;;GAEG;AACH,eAAO,MAAM,eAAe,GAC1B,OAAO,iBAAiB,EAAE,EAC1B,MAAM;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CAAE,KACxD,MAmEF,CAAC;AAMF;;GAEG;AACH,eAAO,MAAM,KAAK,GAChB,KAAK,IAAI,EACT,UAAS,eAAoB,KAC5B,OAAO,CAAC,IAAI,CAkBd,CAAC"}
|
package/dist/blaze.js
CHANGED
|
@@ -81,13 +81,16 @@ export const createMcpServer = (tools, info) => {
|
|
|
81
81
|
* Build MCP tools from an App, create a server, and connect via stdio.
|
|
82
82
|
*/
|
|
83
83
|
export const blaze = async (app, options = {}) => {
|
|
84
|
-
const
|
|
84
|
+
const toolsResult = buildMcpTools(app, {
|
|
85
85
|
createContext: options.createContext,
|
|
86
86
|
excludeTrails: options.excludeTrails,
|
|
87
87
|
includeTrails: options.includeTrails,
|
|
88
88
|
layers: options.layers,
|
|
89
89
|
});
|
|
90
|
-
|
|
90
|
+
if (toolsResult.isErr()) {
|
|
91
|
+
throw toolsResult.error;
|
|
92
|
+
}
|
|
93
|
+
const server = createMcpServer(toolsResult.value, {
|
|
91
94
|
name: options.serverInfo?.name ?? app.name,
|
|
92
95
|
version: options.serverInfo?.version ?? '0.1.0',
|
|
93
96
|
});
|
package/dist/blaze.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"blaze.js","sourceRoot":"","sources":["../src/blaze.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AACnE,OAAO,EACL,qBAAqB,EACrB,sBAAsB,GACvB,MAAM,oCAAoC,CAAC;AAI5C,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAsB1C,8EAA8E;AAC9E,iDAAiD;AACjD,8EAA8E;AAE9E;;GAEG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,CAC7B,KAA0B,EAC1B,IAAyD,EACjD,EAAE;IACV,MAAM,MAAM,GAAG,IAAI,MAAM,CACvB,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,EAC1C,EAAE,YAAY,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,CAChC,CAAC;IAEF,uCAAuC;IACvC,MAAM,OAAO,GAAG,IAAI,GAAG,EAA6B,CAAC;IACrD,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IAC/B,CAAC;IAED,8BAA8B;IAC9B,2EAA2E;IAC3E,MAAM,CAAC,iBAAiB,CAAC,sBAAsB,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC;QAC5D,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YACvB,WAAW,EAAE,CAAC,CAAC,WAAW;YAC1B,WAAW,EAAE,CAAC,CAAC,WAAW;YAC1B,WAAW,EAAE,CAAC,CAAC,WAAW;YAC1B,IAAI,EAAE,CAAC,CAAC,IAAI;SACb,CAAC,CAAC;KACJ,CAAC,CAAC,CAAC;IAEJ,8BAA8B;IAC9B,MAAM,CAAC,iBAAiB,CAAC,qBAAqB,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE;QAChE,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QAC9C,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;YACvB,OAAO;gBACL,OAAO,EAAE;oBACP;wBACE,IAAI,EAAE,iBAAiB,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE;wBAC5C,IAAI,EAAE,MAAe;qBACtB;iBACF;gBACD,OAAO,EAAE,IAAI;aACa,CAAC;QAC/B,CAAC;QAED,MAAM,IAAI,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,SAAS,IAAI,EAAE,CAA4B,CAAC;QACzE,MAAM,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC,KAAK,EAAE,aAAa,CAAC;QAE1D,MAAM,YAAY,GAChB,aAAa,KAAK,SAAS;YACzB,CAAC,CAAC,SAAS;YACX,CAAC,CAAC,KAAK,EAAE,OAAe,EAAE,KAAa,EAAE,EAAE;gBACvC,MAAM,MAAM,CAAC,YAAY,CAAC;oBACxB,MAAM,EAAE,wBAAwB;oBAChC,MAAM,EAAE;wBACN,QAAQ,EAAE,OAAO;wBACjB,aAAa,EAAE,aAAa;wBAC5B,KAAK;qBACN;iBACF,CAAC,CAAC;YACL,CAAC,CAAC;QAER,MAAM,KAAK,GAAG;YACZ,aAAa;YACb,YAAY;YACZ,MAAM,EAAE,SAAoC;SAC7C,CAAC;QAEF,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;QAC/C,0DAA0D;QAC1D,OAAO,EAAE,GAAG,MAAM,EAA6B,CAAC;IAClD,CAAC,CAAC,CAAC;IAEH,OAAO,MAAM,CAAC;AAChB,CAAC,CAAC;AAEF,8EAA8E;AAC9E,QAAQ;AACR,8EAA8E;AAE9E;;GAEG;AACH,MAAM,CAAC,MAAM,KAAK,GAAG,KAAK,EACxB,GAAS,EACT,UAA2B,EAAE,EACd,EAAE;IACjB,MAAM,
|
|
1
|
+
{"version":3,"file":"blaze.js","sourceRoot":"","sources":["../src/blaze.ts"],"names":[],"mappings":"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"}
|
package/dist/build.d.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* validate input, compose layers, execute the implementation, and map
|
|
6
6
|
* Results to MCP responses.
|
|
7
7
|
*/
|
|
8
|
+
import { Result } from '@ontrails/core';
|
|
8
9
|
import type { Layer, Topo, TrailContext } from '@ontrails/core';
|
|
9
10
|
import type { McpAnnotations } from './annotations.js';
|
|
10
11
|
export interface BuildMcpToolsOptions {
|
|
@@ -19,6 +20,8 @@ export interface McpToolDefinition {
|
|
|
19
20
|
readonly handler: (args: Record<string, unknown>, extra: McpExtra) => Promise<McpToolResult>;
|
|
20
21
|
readonly inputSchema: Record<string, unknown>;
|
|
21
22
|
readonly name: string;
|
|
23
|
+
/** The trail ID this tool was derived from. */
|
|
24
|
+
readonly trailId: string;
|
|
22
25
|
}
|
|
23
26
|
export interface McpExtra {
|
|
24
27
|
readonly progressToken?: string | number | undefined;
|
|
@@ -36,5 +39,5 @@ export interface McpContent {
|
|
|
36
39
|
readonly type: 'text' | 'image' | 'resource';
|
|
37
40
|
readonly uri?: string | undefined;
|
|
38
41
|
}
|
|
39
|
-
export declare const buildMcpTools: (app: Topo, options?: BuildMcpToolsOptions) => McpToolDefinition[]
|
|
42
|
+
export declare const buildMcpTools: (app: Topo, options?: BuildMcpToolsOptions) => Result<McpToolDefinition[], Error>;
|
|
40
43
|
//# sourceMappingURL=build.d.ts.map
|
package/dist/build.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../src/build.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;
|
|
1
|
+
{"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../src/build.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EACL,MAAM,EAKP,MAAM,gBAAgB,CAAC;AACxB,OAAO,KAAK,EAAW,KAAK,EAAE,IAAI,EAAS,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAEhF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AASvD,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,aAAa,CAAC,EACnB,CAAC,MAAM,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC,GAC5C,SAAS,CAAC;IACd,QAAQ,CAAC,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAAC;IACvD,QAAQ,CAAC,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAAC;IACvD,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,KAAK,EAAE,GAAG,SAAS,CAAC;CAChD;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,WAAW,EAAE,cAAc,GAAG,SAAS,CAAC;IACjD,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;IACzC,QAAQ,CAAC,OAAO,EAAE,CAChB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,KAAK,EAAE,QAAQ,KACZ,OAAO,CAAC,aAAa,CAAC,CAAC;IAC5B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,+CAA+C;IAC/C,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;IACrD,QAAQ,CAAC,YAAY,CAAC,EAClB,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC,GACnD,SAAS,CAAC;IACd,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,SAAS,CAAC;CAC3C;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,OAAO,EAAE,SAAS,UAAU,EAAE,CAAC;IACxC,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CACxC;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACvC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,GAAG,UAAU,CAAC;IAC7C,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACnC;AAwQD,eAAO,MAAM,aAAa,GACxB,KAAK,IAAI,EACT,UAAS,oBAAyB,KACjC,MAAM,CAAC,iBAAiB,EAAE,EAAE,KAAK,CAoBnC,CAAC"}
|
package/dist/build.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* validate input, compose layers, execute the implementation, and map
|
|
6
6
|
* Results to MCP responses.
|
|
7
7
|
*/
|
|
8
|
-
import {
|
|
8
|
+
import { Result, ValidationError, executeTrail, isBlobRef, zodToJsonSchema, } from '@ontrails/core';
|
|
9
9
|
import { deriveAnnotations } from './annotations.js';
|
|
10
10
|
import { createMcpProgressCallback } from './progress.js';
|
|
11
11
|
import { deriveToolName } from './tool-name.js';
|
|
@@ -114,40 +114,18 @@ const mcpError = (message) => ({
|
|
|
114
114
|
content: [{ text: message, type: 'text' }],
|
|
115
115
|
isError: true,
|
|
116
116
|
});
|
|
117
|
-
|
|
118
|
-
const buildTrailContext = async (options, extra) => {
|
|
119
|
-
const baseContext = options.createContext !== undefined && options.createContext !== null
|
|
120
|
-
? await options.createContext()
|
|
121
|
-
: createTrailContext();
|
|
122
|
-
const signal = extra.signal ?? baseContext.signal;
|
|
117
|
+
const createHandler = (t, layers, options) => async (args, extra) => {
|
|
123
118
|
const progressCb = createMcpProgressCallback(extra);
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
};
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
const impl = composeLayers([...layers], trail, trail.run);
|
|
133
|
-
try {
|
|
134
|
-
const result = await impl(validatedInput, ctx);
|
|
135
|
-
if (result.isOk()) {
|
|
136
|
-
return { content: await serializeOutput(result.value) };
|
|
137
|
-
}
|
|
138
|
-
return mcpError(result.error.message);
|
|
119
|
+
const result = await executeTrail(t, args, {
|
|
120
|
+
createContext: options.createContext,
|
|
121
|
+
ctx: progressCb === undefined ? undefined : { progress: progressCb },
|
|
122
|
+
layers,
|
|
123
|
+
signal: extra.signal,
|
|
124
|
+
});
|
|
125
|
+
if (result.isOk()) {
|
|
126
|
+
return { content: await serializeOutput(result.value) };
|
|
139
127
|
}
|
|
140
|
-
|
|
141
|
-
return mcpError(error instanceof Error ? error.message : String(error));
|
|
142
|
-
}
|
|
143
|
-
};
|
|
144
|
-
const createHandler = (trail, layers, options) => async (args, extra) => {
|
|
145
|
-
const validated = validateInput(trail.input, args);
|
|
146
|
-
if (validated.isErr()) {
|
|
147
|
-
return mcpError(validated.error.message);
|
|
148
|
-
}
|
|
149
|
-
const ctx = await buildTrailContext(options, extra);
|
|
150
|
-
return executeAndMap(trail, validated.value, ctx, layers);
|
|
128
|
+
return mcpError(result.error.message);
|
|
151
129
|
};
|
|
152
130
|
// ---------------------------------------------------------------------------
|
|
153
131
|
// Builder
|
|
@@ -198,6 +176,7 @@ const buildToolDefinition = (app, trail, layers, options) => {
|
|
|
198
176
|
handler: createHandler(trail, layers, options),
|
|
199
177
|
inputSchema: zodToJsonSchema(trail.input),
|
|
200
178
|
name: deriveToolName(app.name, trail.id),
|
|
179
|
+
trailId: trail.id,
|
|
201
180
|
};
|
|
202
181
|
};
|
|
203
182
|
/** Register a trail as an MCP tool, checking for name collisions. */
|
|
@@ -205,10 +184,11 @@ const registerTool = (app, trailItem, layers, options, nameToTrailId, tools) =>
|
|
|
205
184
|
const toolName = deriveToolName(app.name, trailItem.id);
|
|
206
185
|
const existingId = nameToTrailId.get(toolName);
|
|
207
186
|
if (existingId !== undefined) {
|
|
208
|
-
|
|
187
|
+
return Result.err(new ValidationError(`MCP tool-name collision: trails "${existingId}" and "${trailItem.id}" both derive the tool name "${toolName}"`));
|
|
209
188
|
}
|
|
210
189
|
nameToTrailId.set(toolName, trailItem.id);
|
|
211
190
|
tools.push(buildToolDefinition(app, trailItem, layers, options));
|
|
191
|
+
return Result.ok();
|
|
212
192
|
};
|
|
213
193
|
/** Filter topo items to eligible trails. */
|
|
214
194
|
const eligibleTrails = (app, options) => app
|
|
@@ -220,8 +200,11 @@ export const buildMcpTools = (app, options = {}) => {
|
|
|
220
200
|
const tools = [];
|
|
221
201
|
const nameToTrailId = new Map();
|
|
222
202
|
for (const trailItem of eligibleTrails(app, options)) {
|
|
223
|
-
registerTool(app, trailItem, layers, options, nameToTrailId, tools);
|
|
203
|
+
const registered = registerTool(app, trailItem, layers, options, nameToTrailId, tools);
|
|
204
|
+
if (registered.isErr()) {
|
|
205
|
+
return registered;
|
|
206
|
+
}
|
|
224
207
|
}
|
|
225
|
-
return tools;
|
|
208
|
+
return Result.ok(tools);
|
|
226
209
|
};
|
|
227
210
|
//# sourceMappingURL=build.js.map
|
package/dist/build.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"build.js","sourceRoot":"","sources":["../src/build.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EACL,
|
|
1
|
+
{"version":3,"file":"build.js","sourceRoot":"","sources":["../src/build.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ontrails/mcp",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.9",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": "./src/index.ts",
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"clean": "rm -rf dist *.tsbuildinfo"
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@ontrails/core": "^1.0.0-beta.
|
|
17
|
+
"@ontrails/core": "^1.0.0-beta.8"
|
|
18
18
|
},
|
|
19
19
|
"peerDependencies": {
|
|
20
20
|
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
@@ -5,12 +5,13 @@ import { z } from 'zod';
|
|
|
5
5
|
|
|
6
6
|
import { createMcpServer } from '../blaze.js';
|
|
7
7
|
import { buildMcpTools } from '../build.js';
|
|
8
|
+
import type { McpToolDefinition } from '../build.js';
|
|
8
9
|
|
|
9
10
|
// ---------------------------------------------------------------------------
|
|
10
11
|
// Tests
|
|
11
12
|
// ---------------------------------------------------------------------------
|
|
12
13
|
|
|
13
|
-
const requireTool = (tools:
|
|
14
|
+
const requireTool = (tools: McpToolDefinition[], name: string) => {
|
|
14
15
|
const tool = tools.find((entry) => entry.name === name);
|
|
15
16
|
expect(tool).toBeDefined();
|
|
16
17
|
if (!tool) {
|
|
@@ -19,6 +20,19 @@ const requireTool = (tools: ReturnType<typeof buildMcpTools>, name: string) => {
|
|
|
19
20
|
return tool;
|
|
20
21
|
};
|
|
21
22
|
|
|
23
|
+
/**
|
|
24
|
+
* Unwrap buildMcpTools result, throwing on error so test failures surface clearly.
|
|
25
|
+
*/
|
|
26
|
+
const buildTools = (
|
|
27
|
+
...args: Parameters<typeof buildMcpTools>
|
|
28
|
+
): McpToolDefinition[] => {
|
|
29
|
+
const result = buildMcpTools(...args);
|
|
30
|
+
if (result.isErr()) {
|
|
31
|
+
throw result.error;
|
|
32
|
+
}
|
|
33
|
+
return result.value;
|
|
34
|
+
};
|
|
35
|
+
|
|
22
36
|
const createIntegrationTools = () => {
|
|
23
37
|
const greetTrail = trail('greet', {
|
|
24
38
|
description: 'Greet someone',
|
|
@@ -34,7 +48,7 @@ const createIntegrationTools = () => {
|
|
|
34
48
|
run: (_input) => Result.ok({ deleted: true }),
|
|
35
49
|
});
|
|
36
50
|
|
|
37
|
-
return
|
|
51
|
+
return buildTools(topo('myapp', { deleteTrail, greetTrail }));
|
|
38
52
|
};
|
|
39
53
|
|
|
40
54
|
describe('blaze', () => {
|
|
@@ -47,7 +61,7 @@ describe('blaze', () => {
|
|
|
47
61
|
});
|
|
48
62
|
|
|
49
63
|
const app = topo('testapp', { echoTrail });
|
|
50
|
-
const tools =
|
|
64
|
+
const tools = buildTools(app);
|
|
51
65
|
const server = createMcpServer(tools, {
|
|
52
66
|
name: 'testapp',
|
|
53
67
|
version: '0.1.0',
|
|
@@ -72,7 +86,7 @@ describe('blaze', () => {
|
|
|
72
86
|
});
|
|
73
87
|
|
|
74
88
|
const app = topo('testapp', { echoTrail, searchTrail });
|
|
75
|
-
const tools =
|
|
89
|
+
const tools = buildTools(app);
|
|
76
90
|
|
|
77
91
|
expect(tools).toHaveLength(2);
|
|
78
92
|
|
|
@@ -5,7 +5,7 @@ import type { Layer } from '@ontrails/core';
|
|
|
5
5
|
import { z } from 'zod';
|
|
6
6
|
|
|
7
7
|
import { buildMcpTools } from '../build.js';
|
|
8
|
-
import type { McpExtra } from '../build.js';
|
|
8
|
+
import type { McpExtra, McpToolDefinition } from '../build.js';
|
|
9
9
|
|
|
10
10
|
// ---------------------------------------------------------------------------
|
|
11
11
|
// Helpers
|
|
@@ -47,7 +47,7 @@ const exampleTrail = trail('with.examples', {
|
|
|
47
47
|
|
|
48
48
|
const noExtra: McpExtra = {};
|
|
49
49
|
|
|
50
|
-
const requireTool = (tools:
|
|
50
|
+
const requireTool = (tools: McpToolDefinition[], name: string) => {
|
|
51
51
|
const tool = tools.find((entry) => entry.name === name);
|
|
52
52
|
expect(tool).toBeDefined();
|
|
53
53
|
if (!tool) {
|
|
@@ -56,7 +56,7 @@ const requireTool = (tools: ReturnType<typeof buildMcpTools>, name: string) => {
|
|
|
56
56
|
return tool;
|
|
57
57
|
};
|
|
58
58
|
|
|
59
|
-
const requireOnlyTool = (tools:
|
|
59
|
+
const requireOnlyTool = (tools: McpToolDefinition[]) => {
|
|
60
60
|
expect(tools).toHaveLength(1);
|
|
61
61
|
const [tool] = tools;
|
|
62
62
|
expect(tool).toBeDefined();
|
|
@@ -66,6 +66,20 @@ const requireOnlyTool = (tools: ReturnType<typeof buildMcpTools>) => {
|
|
|
66
66
|
return tool;
|
|
67
67
|
};
|
|
68
68
|
|
|
69
|
+
/**
|
|
70
|
+
* Unwrap buildMcpTools result for success-path tests.
|
|
71
|
+
* Throws if the result is an error so test failures surface clearly.
|
|
72
|
+
*/
|
|
73
|
+
const buildTools = (
|
|
74
|
+
...args: Parameters<typeof buildMcpTools>
|
|
75
|
+
): McpToolDefinition[] => {
|
|
76
|
+
const result = buildMcpTools(...args);
|
|
77
|
+
if (result.isErr()) {
|
|
78
|
+
throw result.error;
|
|
79
|
+
}
|
|
80
|
+
return result.value;
|
|
81
|
+
};
|
|
82
|
+
|
|
69
83
|
const parseJsonContent = (
|
|
70
84
|
content: { readonly text?: string | undefined } | undefined
|
|
71
85
|
): unknown => {
|
|
@@ -81,7 +95,7 @@ describe('buildMcpTools', () => {
|
|
|
81
95
|
describe('discovery', () => {
|
|
82
96
|
test('builds tools from a single-trail app', () => {
|
|
83
97
|
const app = topo('myapp', { echoTrail });
|
|
84
|
-
const tools =
|
|
98
|
+
const tools = buildTools(app);
|
|
85
99
|
|
|
86
100
|
expect(tools).toHaveLength(1);
|
|
87
101
|
expect(requireOnlyTool(tools).name).toBe('myapp_echo');
|
|
@@ -89,7 +103,7 @@ describe('buildMcpTools', () => {
|
|
|
89
103
|
|
|
90
104
|
test('builds tools from a multi-trail app', () => {
|
|
91
105
|
const app = topo('myapp', { deleteTrail, echoTrail, failTrail });
|
|
92
|
-
const tools =
|
|
106
|
+
const tools = buildTools(app);
|
|
93
107
|
|
|
94
108
|
expect(tools).toHaveLength(3);
|
|
95
109
|
const names = tools.map((t) => t.name);
|
|
@@ -100,14 +114,14 @@ describe('buildMcpTools', () => {
|
|
|
100
114
|
|
|
101
115
|
test('tool names follow derivation rules', () => {
|
|
102
116
|
const app = topo('myapp', { deleteTrail });
|
|
103
|
-
const tools =
|
|
117
|
+
const tools = buildTools(app);
|
|
104
118
|
|
|
105
119
|
expect(requireOnlyTool(tools).name).toBe('myapp_item_delete');
|
|
106
120
|
});
|
|
107
121
|
|
|
108
122
|
test('input schema is valid JSON Schema', () => {
|
|
109
123
|
const app = topo('myapp', { echoTrail });
|
|
110
|
-
const schema = requireOnlyTool(
|
|
124
|
+
const schema = requireOnlyTool(buildTools(app)).inputSchema;
|
|
111
125
|
|
|
112
126
|
expect(schema['type']).toBe('object');
|
|
113
127
|
expect(schema['properties']).toBeDefined();
|
|
@@ -117,7 +131,7 @@ describe('buildMcpTools', () => {
|
|
|
117
131
|
|
|
118
132
|
test('annotations are correctly derived', () => {
|
|
119
133
|
const app = topo('myapp', { deleteTrail, echoTrail });
|
|
120
|
-
const tools =
|
|
134
|
+
const tools = buildTools(app);
|
|
121
135
|
|
|
122
136
|
expect(requireTool(tools, 'myapp_echo').annotations?.readOnlyHint).toBe(
|
|
123
137
|
true
|
|
@@ -129,12 +143,22 @@ describe('buildMcpTools', () => {
|
|
|
129
143
|
requireTool(tools, 'myapp_item_delete').annotations?.destructiveHint
|
|
130
144
|
).toBe(true);
|
|
131
145
|
});
|
|
146
|
+
|
|
147
|
+
test('trailId identifies the source trail', () => {
|
|
148
|
+
const app = topo('myapp', { deleteTrail, echoTrail });
|
|
149
|
+
const tools = buildTools(app);
|
|
150
|
+
|
|
151
|
+
expect(requireTool(tools, 'myapp_echo').trailId).toBe('echo');
|
|
152
|
+
expect(requireTool(tools, 'myapp_item_delete').trailId).toBe(
|
|
153
|
+
'item.delete'
|
|
154
|
+
);
|
|
155
|
+
});
|
|
132
156
|
});
|
|
133
157
|
|
|
134
158
|
describe('handler execution', () => {
|
|
135
159
|
test('handler validates input and returns isError on invalid', async () => {
|
|
136
160
|
const app = topo('myapp', { echoTrail });
|
|
137
|
-
const tool = requireOnlyTool(
|
|
161
|
+
const tool = requireOnlyTool(buildTools(app));
|
|
138
162
|
|
|
139
163
|
const result = await tool.handler({ notMessage: 123 }, noExtra);
|
|
140
164
|
expect(result?.isError).toBe(true);
|
|
@@ -144,7 +168,7 @@ describe('buildMcpTools', () => {
|
|
|
144
168
|
|
|
145
169
|
test('handler calls implementation and returns result as text content', async () => {
|
|
146
170
|
const app = topo('myapp', { echoTrail });
|
|
147
|
-
const tool = requireOnlyTool(
|
|
171
|
+
const tool = requireOnlyTool(buildTools(app));
|
|
148
172
|
|
|
149
173
|
const result = await tool.handler({ message: 'hello' }, noExtra);
|
|
150
174
|
expect(result?.isError).toBeUndefined();
|
|
@@ -156,7 +180,7 @@ describe('buildMcpTools', () => {
|
|
|
156
180
|
|
|
157
181
|
test('handler maps errors to isError content', async () => {
|
|
158
182
|
const app = topo('myapp', { failTrail });
|
|
159
|
-
const tool = requireOnlyTool(
|
|
183
|
+
const tool = requireOnlyTool(buildTools(app));
|
|
160
184
|
|
|
161
185
|
const result = await tool.handler({ reason: 'broken' }, noExtra);
|
|
162
186
|
expect(result?.isError).toBe(true);
|
|
@@ -171,9 +195,7 @@ describe('buildMcpTools', () => {
|
|
|
171
195
|
},
|
|
172
196
|
});
|
|
173
197
|
|
|
174
|
-
const tool = requireOnlyTool(
|
|
175
|
-
buildMcpTools(topo('myapp', { throwTrail }))
|
|
176
|
-
);
|
|
198
|
+
const tool = requireOnlyTool(buildTools(topo('myapp', { throwTrail })));
|
|
177
199
|
const result = await tool.handler({}, noExtra);
|
|
178
200
|
|
|
179
201
|
expect(result?.isError).toBe(true);
|
|
@@ -184,7 +206,7 @@ describe('buildMcpTools', () => {
|
|
|
184
206
|
describe('filters', () => {
|
|
185
207
|
test('include filter limits which trails become tools', () => {
|
|
186
208
|
const app = topo('myapp', { deleteTrail, echoTrail, failTrail });
|
|
187
|
-
const tools =
|
|
209
|
+
const tools = buildTools(app, {
|
|
188
210
|
includeTrails: ['echo'],
|
|
189
211
|
});
|
|
190
212
|
|
|
@@ -194,7 +216,7 @@ describe('buildMcpTools', () => {
|
|
|
194
216
|
|
|
195
217
|
test('exclude filter removes specific trails', () => {
|
|
196
218
|
const app = topo('myapp', { deleteTrail, echoTrail, failTrail });
|
|
197
|
-
const tools =
|
|
219
|
+
const tools = buildTools(app, {
|
|
198
220
|
excludeTrails: ['fail'],
|
|
199
221
|
});
|
|
200
222
|
|
|
@@ -205,7 +227,7 @@ describe('buildMcpTools', () => {
|
|
|
205
227
|
|
|
206
228
|
test('include takes precedence over exclude', () => {
|
|
207
229
|
const app = topo('myapp', { deleteTrail, echoTrail, failTrail });
|
|
208
|
-
const tools =
|
|
230
|
+
const tools = buildTools(app, {
|
|
209
231
|
excludeTrails: ['fail'],
|
|
210
232
|
includeTrails: ['echo', 'fail'],
|
|
211
233
|
});
|
|
@@ -234,7 +256,7 @@ describe('buildMcpTools', () => {
|
|
|
234
256
|
};
|
|
235
257
|
|
|
236
258
|
const app = topo('myapp', { echoTrail });
|
|
237
|
-
const tool = requireOnlyTool(
|
|
259
|
+
const tool = requireOnlyTool(buildTools(app, { layers: [testLayer] }));
|
|
238
260
|
|
|
239
261
|
await tool.handler({ message: 'hi' }, noExtra);
|
|
240
262
|
expect(calls).toEqual(['before', 'after']);
|
|
@@ -252,9 +274,7 @@ describe('buildMcpTools', () => {
|
|
|
252
274
|
});
|
|
253
275
|
|
|
254
276
|
const controller = new AbortController();
|
|
255
|
-
const tool = requireOnlyTool(
|
|
256
|
-
buildMcpTools(topo('myapp', { signalTrail }))
|
|
257
|
-
);
|
|
277
|
+
const tool = requireOnlyTool(buildTools(topo('myapp', { signalTrail })));
|
|
258
278
|
|
|
259
279
|
await tool.handler({}, { signal: controller.signal });
|
|
260
280
|
expect(capturedSignal).toBe(controller.signal);
|
|
@@ -262,7 +282,7 @@ describe('buildMcpTools', () => {
|
|
|
262
282
|
|
|
263
283
|
test('description includes first example input when present', () => {
|
|
264
284
|
const app = topo('myapp', { exampleTrail });
|
|
265
|
-
const tool = requireOnlyTool(
|
|
285
|
+
const tool = requireOnlyTool(buildTools(app));
|
|
266
286
|
|
|
267
287
|
expect(tool.description).toContain('A trail with examples');
|
|
268
288
|
expect(tool.description).toContain('"name":"world"');
|
|
@@ -282,7 +302,7 @@ describe('buildMcpTools', () => {
|
|
|
282
302
|
|
|
283
303
|
const app = topo('myapp', { ctxTrail });
|
|
284
304
|
const tool = requireOnlyTool(
|
|
285
|
-
|
|
305
|
+
buildTools(app, {
|
|
286
306
|
createContext: () => ({
|
|
287
307
|
custom: true,
|
|
288
308
|
requestId: 'test-id',
|
|
@@ -311,7 +331,7 @@ describe('buildMcpTools', () => {
|
|
|
311
331
|
),
|
|
312
332
|
});
|
|
313
333
|
|
|
314
|
-
const tool = requireOnlyTool(
|
|
334
|
+
const tool = requireOnlyTool(buildTools(topo('myapp', { blobTrail })));
|
|
315
335
|
const result = await tool.handler({}, noExtra);
|
|
316
336
|
|
|
317
337
|
expect(result?.content[0]?.type).toBe('image');
|
|
@@ -333,7 +353,7 @@ describe('buildMcpTools', () => {
|
|
|
333
353
|
),
|
|
334
354
|
});
|
|
335
355
|
|
|
336
|
-
const tool = requireOnlyTool(
|
|
356
|
+
const tool = requireOnlyTool(buildTools(topo('myapp', { blobTrail })));
|
|
337
357
|
const result = await tool.handler({}, noExtra);
|
|
338
358
|
|
|
339
359
|
expect(result?.content[0]?.type).toBe('resource');
|
|
@@ -362,7 +382,7 @@ describe('buildMcpTools', () => {
|
|
|
362
382
|
),
|
|
363
383
|
});
|
|
364
384
|
|
|
365
|
-
const tool = requireOnlyTool(
|
|
385
|
+
const tool = requireOnlyTool(buildTools(topo('myapp', { blobTrail })));
|
|
366
386
|
const result = await tool.handler({}, noExtra);
|
|
367
387
|
|
|
368
388
|
expect(result?.content[0]?.type).toBe('image');
|
|
@@ -372,7 +392,7 @@ describe('buildMcpTools', () => {
|
|
|
372
392
|
});
|
|
373
393
|
|
|
374
394
|
describe('tool-name collision detection', () => {
|
|
375
|
-
test('
|
|
395
|
+
test('returns Err on trails that produce the same derived tool name', () => {
|
|
376
396
|
const dotTrail = trail('foo.bar', {
|
|
377
397
|
input: z.object({}),
|
|
378
398
|
run: () => Result.ok({ ok: true }),
|
|
@@ -383,10 +403,12 @@ describe('buildMcpTools', () => {
|
|
|
383
403
|
});
|
|
384
404
|
|
|
385
405
|
const app = topo('myapp', { dotTrail, underscoreTrail });
|
|
386
|
-
|
|
406
|
+
const result = buildMcpTools(app);
|
|
407
|
+
expect(result.isErr()).toBe(true);
|
|
408
|
+
expect(result.error?.message).toMatch(/tool-name collision/i);
|
|
387
409
|
});
|
|
388
410
|
|
|
389
|
-
test('
|
|
411
|
+
test('returns Err on trails where hyphen and underscore collide', () => {
|
|
390
412
|
const hyphenTrail = trail('foo-bar', {
|
|
391
413
|
input: z.object({}),
|
|
392
414
|
run: () => Result.ok({ ok: true }),
|
|
@@ -397,10 +419,12 @@ describe('buildMcpTools', () => {
|
|
|
397
419
|
});
|
|
398
420
|
|
|
399
421
|
const app = topo('myapp', { hyphenTrail, underscoreTrail });
|
|
400
|
-
|
|
422
|
+
const result = buildMcpTools(app);
|
|
423
|
+
expect(result.isErr()).toBe(true);
|
|
424
|
+
expect(result.error?.message).toMatch(/tool-name collision/i);
|
|
401
425
|
});
|
|
402
426
|
|
|
403
|
-
test('
|
|
427
|
+
test('returns Ok when trail names are distinct after normalization', () => {
|
|
404
428
|
const fooTrail = trail('foo', {
|
|
405
429
|
input: z.object({}),
|
|
406
430
|
run: () => Result.ok({ ok: true }),
|
|
@@ -411,7 +435,8 @@ describe('buildMcpTools', () => {
|
|
|
411
435
|
});
|
|
412
436
|
|
|
413
437
|
const app = topo('myapp', { barTrail, fooTrail });
|
|
414
|
-
|
|
438
|
+
const result = buildMcpTools(app);
|
|
439
|
+
expect(result.isOk()).toBe(true);
|
|
415
440
|
});
|
|
416
441
|
});
|
|
417
442
|
|
|
@@ -426,9 +451,7 @@ describe('buildMcpTools', () => {
|
|
|
426
451
|
run: (input) => Result.ok({ greeting: `Hello, ${input.name}!` }),
|
|
427
452
|
});
|
|
428
453
|
|
|
429
|
-
const tool = requireOnlyTool(
|
|
430
|
-
buildMcpTools(topo('testapp', { greetTrail }))
|
|
431
|
-
);
|
|
454
|
+
const tool = requireOnlyTool(buildTools(topo('testapp', { greetTrail })));
|
|
432
455
|
|
|
433
456
|
expect(tool).toMatchObject({
|
|
434
457
|
annotations: {
|
package/src/blaze.ts
CHANGED
|
@@ -130,14 +130,18 @@ export const blaze = async (
|
|
|
130
130
|
app: Topo,
|
|
131
131
|
options: BlazeMcpOptions = {}
|
|
132
132
|
): Promise<void> => {
|
|
133
|
-
const
|
|
133
|
+
const toolsResult = buildMcpTools(app, {
|
|
134
134
|
createContext: options.createContext,
|
|
135
135
|
excludeTrails: options.excludeTrails,
|
|
136
136
|
includeTrails: options.includeTrails,
|
|
137
137
|
layers: options.layers,
|
|
138
138
|
});
|
|
139
139
|
|
|
140
|
-
|
|
140
|
+
if (toolsResult.isErr()) {
|
|
141
|
+
throw toolsResult.error;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const server = createMcpServer(toolsResult.value, {
|
|
141
145
|
name: options.serverInfo?.name ?? app.name,
|
|
142
146
|
version: options.serverInfo?.version ?? '0.1.0',
|
|
143
147
|
});
|
package/src/build.ts
CHANGED
|
@@ -7,10 +7,10 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import {
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
Result,
|
|
11
|
+
ValidationError,
|
|
12
|
+
executeTrail,
|
|
12
13
|
isBlobRef,
|
|
13
|
-
validateInput,
|
|
14
14
|
zodToJsonSchema,
|
|
15
15
|
} from '@ontrails/core';
|
|
16
16
|
import type { BlobRef, Layer, Topo, Trail, TrailContext } from '@ontrails/core';
|
|
@@ -42,6 +42,8 @@ export interface McpToolDefinition {
|
|
|
42
42
|
) => Promise<McpToolResult>;
|
|
43
43
|
readonly inputSchema: Record<string, unknown>;
|
|
44
44
|
readonly name: string;
|
|
45
|
+
/** The trail ID this tool was derived from. */
|
|
46
|
+
readonly trailId: string;
|
|
45
47
|
}
|
|
46
48
|
|
|
47
49
|
export interface McpExtra {
|
|
@@ -197,48 +199,9 @@ const mcpError = (message: string): McpToolResult => ({
|
|
|
197
199
|
isError: true,
|
|
198
200
|
});
|
|
199
201
|
|
|
200
|
-
/** Build a TrailContext from options and MCP extra. */
|
|
201
|
-
const buildTrailContext = async (
|
|
202
|
-
options: BuildMcpToolsOptions,
|
|
203
|
-
extra: McpExtra
|
|
204
|
-
): Promise<TrailContext> => {
|
|
205
|
-
const baseContext =
|
|
206
|
-
options.createContext !== undefined && options.createContext !== null
|
|
207
|
-
? await options.createContext()
|
|
208
|
-
: createTrailContext();
|
|
209
|
-
|
|
210
|
-
const signal = extra.signal ?? baseContext.signal;
|
|
211
|
-
const progressCb = createMcpProgressCallback(extra);
|
|
212
|
-
|
|
213
|
-
return {
|
|
214
|
-
...baseContext,
|
|
215
|
-
signal,
|
|
216
|
-
...(progressCb === undefined ? {} : { progress: progressCb }),
|
|
217
|
-
};
|
|
218
|
-
};
|
|
219
|
-
|
|
220
|
-
/** Execute a trail and map the result to an MCP response. */
|
|
221
|
-
const executeAndMap = async (
|
|
222
|
-
trail: Trail<unknown, unknown>,
|
|
223
|
-
validatedInput: unknown,
|
|
224
|
-
ctx: TrailContext,
|
|
225
|
-
layers: readonly Layer[]
|
|
226
|
-
): Promise<McpToolResult> => {
|
|
227
|
-
const impl = composeLayers([...layers], trail, trail.run);
|
|
228
|
-
try {
|
|
229
|
-
const result = await impl(validatedInput, ctx);
|
|
230
|
-
if (result.isOk()) {
|
|
231
|
-
return { content: await serializeOutput(result.value) };
|
|
232
|
-
}
|
|
233
|
-
return mcpError(result.error.message);
|
|
234
|
-
} catch (error: unknown) {
|
|
235
|
-
return mcpError(error instanceof Error ? error.message : String(error));
|
|
236
|
-
}
|
|
237
|
-
};
|
|
238
|
-
|
|
239
202
|
const createHandler =
|
|
240
203
|
(
|
|
241
|
-
|
|
204
|
+
t: Trail<unknown, unknown>,
|
|
242
205
|
layers: readonly Layer[],
|
|
243
206
|
options: BuildMcpToolsOptions
|
|
244
207
|
): ((
|
|
@@ -246,12 +209,17 @@ const createHandler =
|
|
|
246
209
|
extra: McpExtra
|
|
247
210
|
) => Promise<McpToolResult>) =>
|
|
248
211
|
async (args, extra): Promise<McpToolResult> => {
|
|
249
|
-
const
|
|
250
|
-
|
|
251
|
-
|
|
212
|
+
const progressCb = createMcpProgressCallback(extra);
|
|
213
|
+
const result = await executeTrail(t, args, {
|
|
214
|
+
createContext: options.createContext,
|
|
215
|
+
ctx: progressCb === undefined ? undefined : { progress: progressCb },
|
|
216
|
+
layers,
|
|
217
|
+
signal: extra.signal,
|
|
218
|
+
});
|
|
219
|
+
if (result.isOk()) {
|
|
220
|
+
return { content: await serializeOutput(result.value) };
|
|
252
221
|
}
|
|
253
|
-
|
|
254
|
-
return executeAndMap(trail, validated.value, ctx, layers);
|
|
222
|
+
return mcpError(result.error.message);
|
|
255
223
|
};
|
|
256
224
|
|
|
257
225
|
// ---------------------------------------------------------------------------
|
|
@@ -321,6 +289,7 @@ const buildToolDefinition = (
|
|
|
321
289
|
handler: createHandler(trail, layers, options),
|
|
322
290
|
inputSchema: zodToJsonSchema(trail.input),
|
|
323
291
|
name: deriveToolName(app.name, trail.id),
|
|
292
|
+
trailId: trail.id,
|
|
324
293
|
};
|
|
325
294
|
};
|
|
326
295
|
|
|
@@ -332,16 +301,19 @@ const registerTool = (
|
|
|
332
301
|
options: BuildMcpToolsOptions,
|
|
333
302
|
nameToTrailId: Map<string, string>,
|
|
334
303
|
tools: McpToolDefinition[]
|
|
335
|
-
): void => {
|
|
304
|
+
): Result<void, Error> => {
|
|
336
305
|
const toolName = deriveToolName(app.name, trailItem.id);
|
|
337
306
|
const existingId = nameToTrailId.get(toolName);
|
|
338
307
|
if (existingId !== undefined) {
|
|
339
|
-
|
|
340
|
-
|
|
308
|
+
return Result.err(
|
|
309
|
+
new ValidationError(
|
|
310
|
+
`MCP tool-name collision: trails "${existingId}" and "${trailItem.id}" both derive the tool name "${toolName}"`
|
|
311
|
+
)
|
|
341
312
|
);
|
|
342
313
|
}
|
|
343
314
|
nameToTrailId.set(toolName, trailItem.id);
|
|
344
315
|
tools.push(buildToolDefinition(app, trailItem, layers, options));
|
|
316
|
+
return Result.ok();
|
|
345
317
|
};
|
|
346
318
|
|
|
347
319
|
/** Filter topo items to eligible trails. */
|
|
@@ -360,14 +332,24 @@ const eligibleTrails = (
|
|
|
360
332
|
export const buildMcpTools = (
|
|
361
333
|
app: Topo,
|
|
362
334
|
options: BuildMcpToolsOptions = {}
|
|
363
|
-
): McpToolDefinition[] => {
|
|
335
|
+
): Result<McpToolDefinition[], Error> => {
|
|
364
336
|
const layers = options.layers ?? [];
|
|
365
337
|
const tools: McpToolDefinition[] = [];
|
|
366
338
|
const nameToTrailId = new Map<string, string>();
|
|
367
339
|
|
|
368
340
|
for (const trailItem of eligibleTrails(app, options)) {
|
|
369
|
-
|
|
341
|
+
const registered = registerTool(
|
|
342
|
+
app,
|
|
343
|
+
trailItem,
|
|
344
|
+
layers,
|
|
345
|
+
options,
|
|
346
|
+
nameToTrailId,
|
|
347
|
+
tools
|
|
348
|
+
);
|
|
349
|
+
if (registered.isErr()) {
|
|
350
|
+
return registered;
|
|
351
|
+
}
|
|
370
352
|
}
|
|
371
353
|
|
|
372
|
-
return tools;
|
|
354
|
+
return Result.ok(tools);
|
|
373
355
|
};
|