@ontrails/mcp 1.0.0-beta.13 → 1.0.0-beta.15
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 +18 -0
- package/README.md +18 -15
- package/dist/annotations.d.ts +1 -1
- package/dist/annotations.d.ts.map +1 -1
- package/dist/annotations.js.map +1 -1
- package/dist/build.d.ts +13 -10
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +38 -34
- package/dist/build.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/stdio.d.ts +1 -1
- package/dist/stdio.js +1 -1
- package/dist/surface.d.ts +35 -0
- package/dist/surface.d.ts.map +1 -0
- package/dist/{blaze.js → surface.js} +44 -29
- package/dist/surface.js.map +1 -0
- package/dist/trailhead.d.ts +28 -12
- package/dist/trailhead.d.ts.map +1 -1
- package/dist/trailhead.js +48 -20
- package/dist/trailhead.js.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/build.test.ts +162 -28
- package/src/__tests__/{trailhead.test.ts → surface.test.ts} +59 -26
- package/src/annotations.ts +4 -1
- package/src/build.ts +79 -62
- package/src/index.ts +9 -4
- package/src/stdio.ts +1 -1
- package/src/{trailhead.ts → surface.ts} +74 -49
- package/tsconfig.tests.json +10 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/blaze.d.ts +0 -41
- package/dist/blaze.d.ts.map +0 -1
- package/dist/blaze.js.map +0 -1
package/dist/trailhead.d.ts
CHANGED
|
@@ -9,33 +9,49 @@
|
|
|
9
9
|
* ```
|
|
10
10
|
*/
|
|
11
11
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
12
|
-
import type {
|
|
12
|
+
import type { Intent, Layer, ResourceOverrideMap, Topo, TrailContextInit } from '@ontrails/core';
|
|
13
13
|
import type { McpToolDefinition } from './build.js';
|
|
14
14
|
export interface TrailheadMcpOptions {
|
|
15
|
-
/** Config values for
|
|
15
|
+
/** Config values for resources that declare a `config` schema, keyed by resource ID. */
|
|
16
16
|
readonly configValues?: Readonly<Record<string, Record<string, unknown>>> | undefined;
|
|
17
17
|
readonly createContext?: (() => TrailContextInit | Promise<TrailContextInit>) | undefined;
|
|
18
|
-
readonly
|
|
19
|
-
readonly
|
|
20
|
-
readonly
|
|
21
|
-
readonly
|
|
22
|
-
readonly
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
} | undefined;
|
|
26
|
-
readonly transport?: 'stdio' | undefined;
|
|
18
|
+
readonly description?: string | undefined;
|
|
19
|
+
readonly exclude?: readonly string[] | undefined;
|
|
20
|
+
readonly include?: readonly string[] | undefined;
|
|
21
|
+
readonly intent?: readonly Intent[] | undefined;
|
|
22
|
+
readonly layers?: readonly Layer[] | undefined;
|
|
23
|
+
readonly name?: string | undefined;
|
|
24
|
+
readonly resources?: ResourceOverrideMap | undefined;
|
|
27
25
|
/** Set to `false` to skip topo validation at startup. Defaults to `true`. */
|
|
28
26
|
readonly validate?: boolean | undefined;
|
|
27
|
+
readonly version?: string | undefined;
|
|
28
|
+
}
|
|
29
|
+
export type CreateServerOptions = TrailheadMcpOptions;
|
|
30
|
+
export interface SurfaceMcpResult {
|
|
31
|
+
readonly close: () => Promise<void>;
|
|
29
32
|
}
|
|
30
33
|
/**
|
|
31
34
|
* Create an MCP Server instance and register all tools.
|
|
35
|
+
*
|
|
36
|
+
* When provided, `info.description` is forwarded to the MCP SDK as the
|
|
37
|
+
* server's `instructions` field — the SDK's documented channel for
|
|
38
|
+
* "optional instructions describing how to use the server and its features."
|
|
32
39
|
*/
|
|
33
40
|
export declare const createMcpServer: (tools: McpToolDefinition[], info: {
|
|
34
41
|
readonly name: string;
|
|
35
42
|
readonly version: string;
|
|
43
|
+
readonly description?: string | undefined;
|
|
36
44
|
}) => Server;
|
|
37
45
|
/**
|
|
38
|
-
* Build MCP tools from
|
|
46
|
+
* Build MCP tools from a topo and create an MCP server.
|
|
47
|
+
*/
|
|
48
|
+
export declare const createServer: (app: Topo, options?: CreateServerOptions) => Server;
|
|
49
|
+
/**
|
|
50
|
+
* Build MCP tools from a topo, create a server, and connect via stdio.
|
|
51
|
+
*/
|
|
52
|
+
export declare const surface: (app: Topo, options?: TrailheadMcpOptions) => Promise<SurfaceMcpResult>;
|
|
53
|
+
/**
|
|
54
|
+
* Build MCP tools from a topo, create a server, and connect via stdio.
|
|
39
55
|
*/
|
|
40
56
|
export declare const trailhead: (app: Topo, options?: TrailheadMcpOptions) => Promise<void>;
|
|
41
57
|
//# sourceMappingURL=trailhead.d.ts.map
|
package/dist/trailhead.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"trailhead.d.ts","sourceRoot":"","sources":["../src/trailhead.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AAKnE,OAAO,KAAK,EACV,
|
|
1
|
+
{"version":3,"file":"trailhead.d.ts","sourceRoot":"","sources":["../src/trailhead.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AAKnE,OAAO,KAAK,EACV,MAAM,EACN,KAAK,EACL,mBAAmB,EACnB,IAAI,EACJ,gBAAgB,EACjB,MAAM,gBAAgB,CAAC;AAExB,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAQpD,MAAM,WAAW,mBAAmB;IAClC,wFAAwF;IACxF,QAAQ,CAAC,YAAY,CAAC,EAClB,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,GACjD,SAAS,CAAC;IACd,QAAQ,CAAC,aAAa,CAAC,EACnB,CAAC,MAAM,gBAAgB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAC,GACpD,SAAS,CAAC;IACd,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1C,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAAC;IACjD,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAAC;IACjD,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAAC;IAChD,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,KAAK,EAAE,GAAG,SAAS,CAAC;IAC/C,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,QAAQ,CAAC,SAAS,CAAC,EAAE,mBAAmB,GAAG,SAAS,CAAC;IACrD,6EAA6E;IAC7E,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACxC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACvC;AAED,MAAM,MAAM,mBAAmB,GAAG,mBAAmB,CAAC;AAEtD,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CACrC;AAMD;;;;;;GAMG;AACH,eAAO,MAAM,eAAe,GAC1B,OAAO,iBAAiB,EAAE,EAC1B,MAAM;IACJ,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC3C,KACA,MAwEF,CAAC;AAMF;;GAEG;AACH,eAAO,MAAM,YAAY,GACvB,KAAK,IAAI,EACT,UAAS,mBAAwB,KAChC,MAqBF,CAAC;AAMF;;GAEG;AACH,eAAO,MAAM,OAAO,GAClB,KAAK,IAAI,EACT,UAAS,mBAAwB,KAChC,OAAO,CAAC,gBAAgB,CAS1B,CAAC;AAMF;;GAEG;AACH,eAAO,MAAM,SAAS,GACpB,KAAK,IAAI,EACT,UAAS,mBAAwB,KAChC,OAAO,CAAC,IAAI,CAEd,CAAC"}
|
package/dist/trailhead.js
CHANGED
|
@@ -10,17 +10,25 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
12
12
|
import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
13
|
-
import {
|
|
14
|
-
import { buildMcpTools } from './build.js';
|
|
13
|
+
import { deriveMcpTools } from './build.js';
|
|
15
14
|
import { connectStdio } from './stdio.js';
|
|
16
15
|
// ---------------------------------------------------------------------------
|
|
17
16
|
// Internal: create MCP server with tool handlers
|
|
18
17
|
// ---------------------------------------------------------------------------
|
|
19
18
|
/**
|
|
20
19
|
* Create an MCP Server instance and register all tools.
|
|
20
|
+
*
|
|
21
|
+
* When provided, `info.description` is forwarded to the MCP SDK as the
|
|
22
|
+
* server's `instructions` field — the SDK's documented channel for
|
|
23
|
+
* "optional instructions describing how to use the server and its features."
|
|
21
24
|
*/
|
|
22
25
|
export const createMcpServer = (tools, info) => {
|
|
23
|
-
const server = new Server({ name: info.name, version: info.version }, {
|
|
26
|
+
const server = new Server({ name: info.name, version: info.version }, {
|
|
27
|
+
capabilities: { tools: {} },
|
|
28
|
+
...(info.description === undefined
|
|
29
|
+
? {}
|
|
30
|
+
: { instructions: info.description }),
|
|
31
|
+
});
|
|
24
32
|
// Build a lookup map for tool dispatch
|
|
25
33
|
const toolMap = new Map();
|
|
26
34
|
for (const tool of tools) {
|
|
@@ -76,33 +84,53 @@ export const createMcpServer = (tools, info) => {
|
|
|
76
84
|
return server;
|
|
77
85
|
};
|
|
78
86
|
// ---------------------------------------------------------------------------
|
|
79
|
-
//
|
|
87
|
+
// createServer
|
|
80
88
|
// ---------------------------------------------------------------------------
|
|
81
89
|
/**
|
|
82
|
-
* Build MCP tools from
|
|
90
|
+
* Build MCP tools from a topo and create an MCP server.
|
|
83
91
|
*/
|
|
84
|
-
export const
|
|
85
|
-
|
|
86
|
-
const validated = validateTopo(app);
|
|
87
|
-
if (validated.isErr()) {
|
|
88
|
-
throw validated.error;
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
const toolsResult = buildMcpTools(app, {
|
|
92
|
+
export const createServer = (app, options = {}) => {
|
|
93
|
+
const toolsResult = deriveMcpTools(app, {
|
|
92
94
|
configValues: options.configValues,
|
|
93
95
|
createContext: options.createContext,
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
96
|
+
exclude: options.exclude,
|
|
97
|
+
include: options.include,
|
|
98
|
+
intent: options.intent,
|
|
99
|
+
layers: options.layers,
|
|
100
|
+
resources: options.resources,
|
|
101
|
+
validate: options.validate,
|
|
98
102
|
});
|
|
99
103
|
if (toolsResult.isErr()) {
|
|
100
104
|
throw toolsResult.error;
|
|
101
105
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
106
|
+
return createMcpServer(toolsResult.value, {
|
|
107
|
+
description: options.description ?? app.description,
|
|
108
|
+
name: options.name ?? app.name,
|
|
109
|
+
version: options.version ?? app.version ?? '0.1.0',
|
|
105
110
|
});
|
|
111
|
+
};
|
|
112
|
+
// ---------------------------------------------------------------------------
|
|
113
|
+
// surface
|
|
114
|
+
// ---------------------------------------------------------------------------
|
|
115
|
+
/**
|
|
116
|
+
* Build MCP tools from a topo, create a server, and connect via stdio.
|
|
117
|
+
*/
|
|
118
|
+
export const surface = async (app, options = {}) => {
|
|
119
|
+
const server = createServer(app, options);
|
|
106
120
|
await connectStdio(server);
|
|
121
|
+
return {
|
|
122
|
+
close: async () => {
|
|
123
|
+
await server.close();
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
};
|
|
127
|
+
// ---------------------------------------------------------------------------
|
|
128
|
+
// trailhead
|
|
129
|
+
// ---------------------------------------------------------------------------
|
|
130
|
+
/**
|
|
131
|
+
* Build MCP tools from a topo, create a server, and connect via stdio.
|
|
132
|
+
*/
|
|
133
|
+
export const trailhead = async (app, options = {}) => {
|
|
134
|
+
await surface(app, options);
|
|
107
135
|
};
|
|
108
136
|
//# sourceMappingURL=trailhead.js.map
|
package/dist/trailhead.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"trailhead.js","sourceRoot":"","sources":["../src/trailhead.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AACnE,OAAO,EACL,qBAAqB,EACrB,sBAAsB,GACvB,MAAM,oCAAoC,CAAC;
|
|
1
|
+
{"version":3,"file":"trailhead.js","sourceRoot":"","sources":["../src/trailhead.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AACnE,OAAO,EACL,qBAAqB,EACrB,sBAAsB,GACvB,MAAM,oCAAoC,CAAC;AAU5C,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAgC1C,8EAA8E;AAC9E,iDAAiD;AACjD,8EAA8E;AAE9E;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,CAC7B,KAA0B,EAC1B,IAIC,EACO,EAAE;IACV,MAAM,MAAM,GAAG,IAAI,MAAM,CACvB,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,EAC1C;QACE,YAAY,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE;QAC3B,GAAG,CAAC,IAAI,CAAC,WAAW,KAAK,SAAS;YAChC,CAAC,CAAC,EAAE;YACJ,CAAC,CAAC,EAAE,YAAY,EAAE,IAAI,CAAC,WAAW,EAAE,CAAC;KACxC,CACF,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,WAAW,EAAE,SAAoC;YACjD,aAAa;YACb,YAAY;SACb,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,eAAe;AACf,8EAA8E;AAE9E;;GAEG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,CAC1B,GAAS,EACT,UAA+B,EAAE,EACzB,EAAE;IACV,MAAM,WAAW,GAAG,cAAc,CAAC,GAAG,EAAE;QACtC,YAAY,EAAE,OAAO,CAAC,YAAY;QAClC,aAAa,EAAE,OAAO,CAAC,aAAa;QACpC,OAAO,EAAE,OAAO,CAAC,OAAO;QACxB,OAAO,EAAE,OAAO,CAAC,OAAO;QACxB,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,QAAQ,EAAE,OAAO,CAAC,QAAQ;KAC3B,CAAC,CAAC;IAEH,IAAI,WAAW,CAAC,KAAK,EAAE,EAAE,CAAC;QACxB,MAAM,WAAW,CAAC,KAAK,CAAC;IAC1B,CAAC;IAED,OAAO,eAAe,CAAC,WAAW,CAAC,KAAK,EAAE;QACxC,WAAW,EAAE,OAAO,CAAC,WAAW,IAAI,GAAG,CAAC,WAAW;QACnD,IAAI,EAAE,OAAO,CAAC,IAAI,IAAI,GAAG,CAAC,IAAI;QAC9B,OAAO,EAAE,OAAO,CAAC,OAAO,IAAI,GAAG,CAAC,OAAO,IAAI,OAAO;KACnD,CAAC,CAAC;AACL,CAAC,CAAC;AAEF,8EAA8E;AAC9E,UAAU;AACV,8EAA8E;AAE9E;;GAEG;AACH,MAAM,CAAC,MAAM,OAAO,GAAG,KAAK,EAC1B,GAAS,EACT,UAA+B,EAAE,EACN,EAAE;IAC7B,MAAM,MAAM,GAAG,YAAY,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;IAC1C,MAAM,YAAY,CAAC,MAAM,CAAC,CAAC;IAE3B,OAAO;QACL,KAAK,EAAE,KAAK,IAAI,EAAE;YAChB,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;QACvB,CAAC;KACF,CAAC;AACJ,CAAC,CAAC;AAEF,8EAA8E;AAC9E,YAAY;AACZ,8EAA8E;AAE9E;;GAEG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG,KAAK,EAC5B,GAAS,EACT,UAA+B,EAAE,EAClB,EAAE;IACjB,MAAM,OAAO,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;AAC9B,CAAC,CAAC"}
|
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.15",
|
|
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.14"
|
|
18
18
|
},
|
|
19
19
|
"peerDependencies": {
|
|
20
20
|
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
@@ -4,14 +4,15 @@ import {
|
|
|
4
4
|
Result,
|
|
5
5
|
TRAILHEAD_KEY,
|
|
6
6
|
createBlobRef,
|
|
7
|
-
|
|
7
|
+
resource,
|
|
8
|
+
signal,
|
|
8
9
|
trail,
|
|
9
10
|
topo,
|
|
10
11
|
} from '@ontrails/core';
|
|
11
|
-
import type {
|
|
12
|
+
import type { Layer, TrailContext } from '@ontrails/core';
|
|
12
13
|
import { z } from 'zod';
|
|
13
14
|
|
|
14
|
-
import {
|
|
15
|
+
import { deriveMcpTools } from '../build.js';
|
|
15
16
|
import type { McpExtra, McpToolDefinition } from '../build.js';
|
|
16
17
|
|
|
17
18
|
// ---------------------------------------------------------------------------
|
|
@@ -52,13 +53,30 @@ const exampleTrail = trail('with.examples', {
|
|
|
52
53
|
input: z.object({ name: z.string() }),
|
|
53
54
|
});
|
|
54
55
|
|
|
55
|
-
const
|
|
56
|
+
const internalTrail = trail('internal.secret', {
|
|
57
|
+
blaze: () => Result.ok({ ok: true }),
|
|
58
|
+
input: z.object({}),
|
|
59
|
+
visibility: 'internal',
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
const dbResource = resource('db.main', {
|
|
56
63
|
create: () =>
|
|
57
64
|
Result.ok({
|
|
58
65
|
source: 'factory',
|
|
59
66
|
}),
|
|
60
67
|
});
|
|
61
68
|
|
|
69
|
+
const orderPlaced = signal('order.placed', {
|
|
70
|
+
payload: z.object({ orderId: z.string() }),
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
const requireFire = (fire: TrailContext['fire']) => {
|
|
74
|
+
if (!fire) {
|
|
75
|
+
throw new Error('Expected ctx.fire to be bound');
|
|
76
|
+
}
|
|
77
|
+
return fire;
|
|
78
|
+
};
|
|
79
|
+
|
|
62
80
|
const noExtra: McpExtra = {};
|
|
63
81
|
|
|
64
82
|
const requireTool = (tools: McpToolDefinition[], name: string) => {
|
|
@@ -81,13 +99,13 @@ const requireOnlyTool = (tools: McpToolDefinition[]) => {
|
|
|
81
99
|
};
|
|
82
100
|
|
|
83
101
|
/**
|
|
84
|
-
* Unwrap
|
|
102
|
+
* Unwrap deriveMcpTools result for success-path tests.
|
|
85
103
|
* Throws if the result is an error so test failures show up clearly.
|
|
86
104
|
*/
|
|
87
105
|
const buildTools = (
|
|
88
|
-
...args: Parameters<typeof
|
|
106
|
+
...args: Parameters<typeof deriveMcpTools>
|
|
89
107
|
): McpToolDefinition[] => {
|
|
90
|
-
const result =
|
|
108
|
+
const result = deriveMcpTools(...args);
|
|
91
109
|
if (result.isErr()) {
|
|
92
110
|
throw result.error;
|
|
93
111
|
}
|
|
@@ -105,7 +123,7 @@ const parseJsonContent = (
|
|
|
105
123
|
// Tests
|
|
106
124
|
// ---------------------------------------------------------------------------
|
|
107
125
|
|
|
108
|
-
describe('
|
|
126
|
+
describe('deriveMcpTools', () => {
|
|
109
127
|
describe('discovery', () => {
|
|
110
128
|
test('builds tools from a single-trail app', () => {
|
|
111
129
|
const app = topo('myapp', { echoTrail });
|
|
@@ -192,6 +210,60 @@ describe('buildMcpTools', () => {
|
|
|
192
210
|
});
|
|
193
211
|
});
|
|
194
212
|
|
|
213
|
+
test('passes topo to executeTrail so MCP-invoked producers can fan out', async () => {
|
|
214
|
+
const captured: string[] = [];
|
|
215
|
+
const consumer = trail('notify.email', {
|
|
216
|
+
blaze: (input: { orderId: string }) => {
|
|
217
|
+
captured.push(input.orderId);
|
|
218
|
+
return Result.ok({ delivered: true });
|
|
219
|
+
},
|
|
220
|
+
input: z.object({ orderId: z.string() }),
|
|
221
|
+
on: ['order.placed'],
|
|
222
|
+
});
|
|
223
|
+
const producer = trail('order.create', {
|
|
224
|
+
blaze: async (input: { orderId: string }, ctx) => {
|
|
225
|
+
const fired = await requireFire(ctx.fire)('order.placed', {
|
|
226
|
+
orderId: input.orderId,
|
|
227
|
+
});
|
|
228
|
+
return fired.match({
|
|
229
|
+
err: (error) => Result.err(error),
|
|
230
|
+
ok: () => Result.ok({ ok: true }),
|
|
231
|
+
});
|
|
232
|
+
},
|
|
233
|
+
fires: ['order.placed'],
|
|
234
|
+
input: z.object({ orderId: z.string() }),
|
|
235
|
+
});
|
|
236
|
+
const tool = requireTool(
|
|
237
|
+
buildTools(topo('signal-mcp', { consumer, orderPlaced, producer })),
|
|
238
|
+
'signal_mcp_order_create'
|
|
239
|
+
);
|
|
240
|
+
|
|
241
|
+
const result = await tool.handler({ orderId: 'o-mcp' }, noExtra);
|
|
242
|
+
|
|
243
|
+
expect(result.isError).toBeUndefined();
|
|
244
|
+
expect(captured).toEqual(['o-mcp']);
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
test('consumer trails with on: are not exposed as MCP tools', () => {
|
|
248
|
+
const consumer = trail('notify.email', {
|
|
249
|
+
blaze: () => Result.ok({ delivered: true }),
|
|
250
|
+
input: z.object({ orderId: z.string() }),
|
|
251
|
+
on: ['order.placed'],
|
|
252
|
+
});
|
|
253
|
+
const producer = trail('order.create', {
|
|
254
|
+
blaze: () => Result.ok({ ok: true }),
|
|
255
|
+
fires: ['order.placed'],
|
|
256
|
+
input: z.object({ orderId: z.string() }),
|
|
257
|
+
});
|
|
258
|
+
const tools = buildTools(
|
|
259
|
+
topo('signal-mcp', { consumer, orderPlaced, producer })
|
|
260
|
+
);
|
|
261
|
+
const toolNames = tools.map((t) => t.name);
|
|
262
|
+
|
|
263
|
+
expect(toolNames).toContain('signal_mcp_order_create');
|
|
264
|
+
expect(toolNames).not.toContain('signal_mcp_notify_email');
|
|
265
|
+
});
|
|
266
|
+
|
|
195
267
|
test('handler maps errors to isError content', async () => {
|
|
196
268
|
const app = topo('myapp', { failTrail });
|
|
197
269
|
const tool = requireOnlyTool(buildTools(app));
|
|
@@ -221,7 +293,7 @@ describe('buildMcpTools', () => {
|
|
|
221
293
|
test('include filter limits which trails become tools', () => {
|
|
222
294
|
const app = topo('myapp', { deleteTrail, echoTrail, failTrail });
|
|
223
295
|
const tools = buildTools(app, {
|
|
224
|
-
|
|
296
|
+
include: ['echo'],
|
|
225
297
|
});
|
|
226
298
|
|
|
227
299
|
expect(tools).toHaveLength(1);
|
|
@@ -231,7 +303,7 @@ describe('buildMcpTools', () => {
|
|
|
231
303
|
test('exclude filter removes specific trails', () => {
|
|
232
304
|
const app = topo('myapp', { deleteTrail, echoTrail, failTrail });
|
|
233
305
|
const tools = buildTools(app, {
|
|
234
|
-
|
|
306
|
+
exclude: ['fail'],
|
|
235
307
|
});
|
|
236
308
|
|
|
237
309
|
expect(tools).toHaveLength(2);
|
|
@@ -239,26 +311,73 @@ describe('buildMcpTools', () => {
|
|
|
239
311
|
expect(names).not.toContain('myapp_fail');
|
|
240
312
|
});
|
|
241
313
|
|
|
242
|
-
test('
|
|
314
|
+
test('exclude patterns apply before include narrowing', () => {
|
|
243
315
|
const app = topo('myapp', { deleteTrail, echoTrail, failTrail });
|
|
244
316
|
const tools = buildTools(app, {
|
|
245
|
-
|
|
246
|
-
|
|
317
|
+
exclude: ['fail'],
|
|
318
|
+
include: ['echo', 'fail'],
|
|
247
319
|
});
|
|
248
320
|
|
|
249
|
-
expect(tools).toHaveLength(
|
|
321
|
+
expect(tools).toHaveLength(1);
|
|
250
322
|
const names = tools.map((t) => t.name);
|
|
251
323
|
expect(names).toContain('myapp_echo');
|
|
252
|
-
expect(names).toContain('myapp_fail');
|
|
324
|
+
expect(names).not.toContain('myapp_fail');
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
test('exact include can expose an internal trail', () => {
|
|
328
|
+
const app = topo('myapp', { echoTrail, internalTrail });
|
|
329
|
+
const tools = buildTools(app, {
|
|
330
|
+
include: ['internal.secret'],
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
expect(tools.map((tool) => tool.trailId)).toEqual(['internal.secret']);
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
test('wildcard include does not expose internal trails', () => {
|
|
337
|
+
const app = topo('myapp', { echoTrail, internalTrail });
|
|
338
|
+
const tools = buildTools(app, {
|
|
339
|
+
include: ['**'],
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
expect(tools.map((tool) => tool.trailId)).toEqual(['echo']);
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
test('include narrows to a single trail and exclude drops another', () => {
|
|
346
|
+
const app = topo('myapp', { deleteTrail, echoTrail, failTrail });
|
|
347
|
+
const tools = buildTools(app, {
|
|
348
|
+
exclude: ['fail'],
|
|
349
|
+
include: ['echo'],
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
expect(tools.map((tool) => tool.trailId)).toEqual(['echo']);
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
test('intent filters narrow the tool list', () => {
|
|
356
|
+
const app = topo('myapp', { deleteTrail, echoTrail, failTrail });
|
|
357
|
+
const tools = buildTools(app, {
|
|
358
|
+
intent: ['read'],
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
expect(tools.map((tool) => tool.trailId)).toEqual(['echo']);
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
test('intent filters compose with include patterns using AND logic', () => {
|
|
365
|
+
const app = topo('myapp', { deleteTrail, echoTrail });
|
|
366
|
+
const tools = buildTools(app, {
|
|
367
|
+
include: ['item.*', 'echo'],
|
|
368
|
+
intent: ['destroy'],
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
expect(tools.map((tool) => tool.trailId)).toEqual(['item.delete']);
|
|
253
372
|
});
|
|
254
373
|
});
|
|
255
374
|
|
|
256
375
|
describe('composition', () => {
|
|
257
|
-
test('
|
|
376
|
+
test('layers compose and execute around the implementation', async () => {
|
|
258
377
|
const calls: string[] = [];
|
|
259
378
|
|
|
260
|
-
const testGate:
|
|
261
|
-
name: 'test-
|
|
379
|
+
const testGate: Layer = {
|
|
380
|
+
name: 'test-layer',
|
|
262
381
|
wrap(_trail, impl) {
|
|
263
382
|
return async (input, ctx) => {
|
|
264
383
|
calls.push('before');
|
|
@@ -270,7 +389,7 @@ describe('buildMcpTools', () => {
|
|
|
270
389
|
};
|
|
271
390
|
|
|
272
391
|
const app = topo('myapp', { echoTrail });
|
|
273
|
-
const tool = requireOnlyTool(buildTools(app, {
|
|
392
|
+
const tool = requireOnlyTool(buildTools(app, { layers: [testGate] }));
|
|
274
393
|
|
|
275
394
|
await tool.handler({ message: 'hi' }, noExtra);
|
|
276
395
|
expect(calls).toEqual(['before', 'after']);
|
|
@@ -331,18 +450,18 @@ describe('buildMcpTools', () => {
|
|
|
331
450
|
expect(trailheadMarkerUsed).toBe(true);
|
|
332
451
|
});
|
|
333
452
|
|
|
334
|
-
test('
|
|
335
|
-
const
|
|
453
|
+
test('resource overrides are forwarded to executeTrail', async () => {
|
|
454
|
+
const resourceTrail = trail('resource.check', {
|
|
336
455
|
blaze: (_input, ctx) =>
|
|
337
|
-
Result.ok({ source:
|
|
456
|
+
Result.ok({ source: dbResource.from(ctx).source as string }),
|
|
338
457
|
input: z.object({}),
|
|
339
458
|
output: z.object({ source: z.string() }),
|
|
340
|
-
|
|
459
|
+
resources: [dbResource],
|
|
341
460
|
});
|
|
342
461
|
|
|
343
462
|
const tool = requireOnlyTool(
|
|
344
|
-
buildTools(topo('myapp', {
|
|
345
|
-
|
|
463
|
+
buildTools(topo('myapp', { dbResource, resourceTrail }), {
|
|
464
|
+
resources: { 'db.main': { source: 'override' } },
|
|
346
465
|
})
|
|
347
466
|
);
|
|
348
467
|
|
|
@@ -441,7 +560,7 @@ describe('buildMcpTools', () => {
|
|
|
441
560
|
});
|
|
442
561
|
|
|
443
562
|
const app = topo('myapp', { dotTrail, underscoreTrail });
|
|
444
|
-
const result =
|
|
563
|
+
const result = deriveMcpTools(app);
|
|
445
564
|
expect(result.isErr()).toBe(true);
|
|
446
565
|
expect(result.error?.message).toMatch(/tool-name collision/i);
|
|
447
566
|
});
|
|
@@ -457,7 +576,7 @@ describe('buildMcpTools', () => {
|
|
|
457
576
|
});
|
|
458
577
|
|
|
459
578
|
const app = topo('myapp', { hyphenTrail, underscoreTrail });
|
|
460
|
-
const result =
|
|
579
|
+
const result = deriveMcpTools(app);
|
|
461
580
|
expect(result.isErr()).toBe(true);
|
|
462
581
|
expect(result.error?.message).toMatch(/tool-name collision/i);
|
|
463
582
|
});
|
|
@@ -473,7 +592,7 @@ describe('buildMcpTools', () => {
|
|
|
473
592
|
});
|
|
474
593
|
|
|
475
594
|
const app = topo('myapp', { barTrail, fooTrail });
|
|
476
|
-
const result =
|
|
595
|
+
const result = deriveMcpTools(app);
|
|
477
596
|
expect(result.isOk()).toBe(true);
|
|
478
597
|
});
|
|
479
598
|
});
|
|
@@ -511,4 +630,19 @@ describe('buildMcpTools', () => {
|
|
|
511
630
|
expect(errorResult?.isError).toBe(true);
|
|
512
631
|
});
|
|
513
632
|
});
|
|
633
|
+
|
|
634
|
+
describe('established graph enforcement', () => {
|
|
635
|
+
test('returns Err when draft contamination remains', () => {
|
|
636
|
+
const draftTrail = trail('entity.export', {
|
|
637
|
+
blaze: () => Result.ok({ ok: true }),
|
|
638
|
+
crosses: ['_draft.entity.prepare'],
|
|
639
|
+
input: z.object({}),
|
|
640
|
+
});
|
|
641
|
+
|
|
642
|
+
const result = deriveMcpTools(topo('myapp', { draftTrail }));
|
|
643
|
+
|
|
644
|
+
expect(result.isErr()).toBe(true);
|
|
645
|
+
expect(result.error?.message).toMatch(/draft/i);
|
|
646
|
+
});
|
|
647
|
+
});
|
|
514
648
|
});
|