@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
|
@@ -3,14 +3,22 @@ import { describe, expect, test } from 'bun:test';
|
|
|
3
3
|
import { Result, trail, topo } from '@ontrails/core';
|
|
4
4
|
import { z } from 'zod';
|
|
5
5
|
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
6
|
+
import { createServer, surface } from '../surface.js';
|
|
7
|
+
import { deriveMcpTools } from '../build.js';
|
|
8
8
|
import type { McpToolDefinition } from '../build.js';
|
|
9
9
|
|
|
10
10
|
// ---------------------------------------------------------------------------
|
|
11
11
|
// Tests
|
|
12
12
|
// ---------------------------------------------------------------------------
|
|
13
13
|
|
|
14
|
+
const unwrapOk = <T>(result: Result<T, Error>): T =>
|
|
15
|
+
result.match({
|
|
16
|
+
err: (error) => {
|
|
17
|
+
throw error;
|
|
18
|
+
},
|
|
19
|
+
ok: (value) => value,
|
|
20
|
+
});
|
|
21
|
+
|
|
14
22
|
const requireTool = (tools: McpToolDefinition[], name: string) => {
|
|
15
23
|
const tool = tools.find((entry) => entry.name === name);
|
|
16
24
|
expect(tool).toBeDefined();
|
|
@@ -21,19 +29,19 @@ const requireTool = (tools: McpToolDefinition[], name: string) => {
|
|
|
21
29
|
};
|
|
22
30
|
|
|
23
31
|
/**
|
|
24
|
-
* Unwrap
|
|
32
|
+
* Unwrap deriveMcpTools result, throwing on error so test failures show up clearly.
|
|
25
33
|
*/
|
|
26
|
-
const
|
|
27
|
-
...args: Parameters<typeof
|
|
34
|
+
const deriveTools = (
|
|
35
|
+
...args: Parameters<typeof deriveMcpTools>
|
|
28
36
|
): McpToolDefinition[] => {
|
|
29
|
-
const result =
|
|
37
|
+
const result = deriveMcpTools(...args);
|
|
30
38
|
if (result.isErr()) {
|
|
31
39
|
throw result.error;
|
|
32
40
|
}
|
|
33
41
|
return result.value;
|
|
34
42
|
};
|
|
35
43
|
|
|
36
|
-
const
|
|
44
|
+
const createIntegrationFixtures = () => {
|
|
37
45
|
const greetTrail = trail('greet', {
|
|
38
46
|
blaze: (input) => Result.ok({ greeting: `Hello, ${input.name}!` }),
|
|
39
47
|
description: 'Greet someone',
|
|
@@ -48,11 +56,12 @@ const createIntegrationTools = () => {
|
|
|
48
56
|
intent: 'destroy',
|
|
49
57
|
});
|
|
50
58
|
|
|
51
|
-
|
|
59
|
+
const app = topo('myapp', { deleteTrail, greetTrail });
|
|
60
|
+
return { app, tools: deriveTools(app) };
|
|
52
61
|
};
|
|
53
62
|
|
|
54
|
-
describe('
|
|
55
|
-
test('
|
|
63
|
+
describe('surface', () => {
|
|
64
|
+
test('surface throws on invalid topo', async () => {
|
|
56
65
|
const t = trail('broken', {
|
|
57
66
|
blaze: () => Result.ok({}),
|
|
58
67
|
crosses: ['nonexistent.trail'],
|
|
@@ -60,10 +69,10 @@ describe('trailhead', () => {
|
|
|
60
69
|
output: z.object({}),
|
|
61
70
|
});
|
|
62
71
|
const app = topo('test', { t });
|
|
63
|
-
await expect(
|
|
72
|
+
await expect(surface(app)).rejects.toThrow(/validation/i);
|
|
64
73
|
});
|
|
65
74
|
|
|
66
|
-
test('
|
|
75
|
+
test('surface skips validation when validate: false', async () => {
|
|
67
76
|
const t = trail('broken', {
|
|
68
77
|
blaze: () => Result.ok({}),
|
|
69
78
|
crosses: ['nonexistent.trail'],
|
|
@@ -72,7 +81,7 @@ describe('trailhead', () => {
|
|
|
72
81
|
});
|
|
73
82
|
const app = topo('test', { t });
|
|
74
83
|
const result = await Promise.race([
|
|
75
|
-
|
|
84
|
+
surface(app, { validate: false }).then(() => 'resolved' as const),
|
|
76
85
|
// oxlint-disable-next-line avoid-new -- Promise constructor needed for setTimeout-based timeout
|
|
77
86
|
new Promise<'timeout'>((resolve) => {
|
|
78
87
|
setTimeout(() => {
|
|
@@ -83,15 +92,22 @@ describe('trailhead', () => {
|
|
|
83
92
|
expect(['resolved', 'timeout']).toContain(result);
|
|
84
93
|
});
|
|
85
94
|
|
|
86
|
-
test('
|
|
87
|
-
const opts: Parameters<typeof
|
|
88
|
-
|
|
95
|
+
test('CreateServerOptions accepts flattened identity and resource fields', () => {
|
|
96
|
+
const opts: Parameters<typeof surface>[1] = {
|
|
97
|
+
description: 'Test MCP server',
|
|
98
|
+
name: 'testapp',
|
|
99
|
+
resources: {},
|
|
89
100
|
validate: false,
|
|
101
|
+
version: '1.2.3',
|
|
90
102
|
};
|
|
91
|
-
expect(opts.
|
|
103
|
+
expect(opts.description).toBe('Test MCP server');
|
|
104
|
+
expect(opts.name).toBe('testapp');
|
|
105
|
+
expect(opts.resources).toEqual({});
|
|
106
|
+
expect(opts.validate).toBe(false);
|
|
107
|
+
expect(opts.version).toBe('1.2.3');
|
|
92
108
|
});
|
|
93
109
|
|
|
94
|
-
test('
|
|
110
|
+
test('createServer registers tools that can be listed', () => {
|
|
95
111
|
const echoTrail = trail('echo', {
|
|
96
112
|
blaze: (input) => Result.ok({ reply: input.message }),
|
|
97
113
|
description: 'Echo',
|
|
@@ -100,8 +116,7 @@ describe('trailhead', () => {
|
|
|
100
116
|
});
|
|
101
117
|
|
|
102
118
|
const app = topo('testapp', { echoTrail });
|
|
103
|
-
const
|
|
104
|
-
const server = createMcpServer(tools, {
|
|
119
|
+
const server = createServer(app, {
|
|
105
120
|
name: 'testapp',
|
|
106
121
|
version: '0.1.0',
|
|
107
122
|
});
|
|
@@ -110,7 +125,7 @@ describe('trailhead', () => {
|
|
|
110
125
|
expect(server).toBeDefined();
|
|
111
126
|
});
|
|
112
127
|
|
|
113
|
-
test('
|
|
128
|
+
test('createServer handles multiple tools', () => {
|
|
114
129
|
const echoTrail = trail('echo', {
|
|
115
130
|
blaze: (input) => Result.ok({ reply: input.message }),
|
|
116
131
|
description: 'Echo',
|
|
@@ -125,19 +140,37 @@ describe('trailhead', () => {
|
|
|
125
140
|
});
|
|
126
141
|
|
|
127
142
|
const app = topo('testapp', { echoTrail, searchTrail });
|
|
128
|
-
const tools =
|
|
143
|
+
const tools = deriveTools(app);
|
|
129
144
|
|
|
130
145
|
expect(tools).toHaveLength(2);
|
|
131
146
|
|
|
132
|
-
const server =
|
|
147
|
+
const server = createServer(app, {
|
|
133
148
|
name: 'testapp',
|
|
134
149
|
version: '0.1.0',
|
|
135
150
|
});
|
|
136
151
|
expect(server).toBeDefined();
|
|
137
152
|
});
|
|
138
153
|
|
|
139
|
-
test('
|
|
140
|
-
const
|
|
154
|
+
test('deriveMcpTools returns tools and createServer materializes the server', () => {
|
|
155
|
+
const echoTrail = trail('echo', {
|
|
156
|
+
blaze: (input) => Result.ok({ reply: input.message }),
|
|
157
|
+
description: 'Echo',
|
|
158
|
+
input: z.object({ message: z.string() }),
|
|
159
|
+
});
|
|
160
|
+
const app = topo('surface-api', { echoTrail });
|
|
161
|
+
|
|
162
|
+
const tools = unwrapOk(deriveMcpTools(app));
|
|
163
|
+
expect(tools).toHaveLength(1);
|
|
164
|
+
|
|
165
|
+
const server = createServer(app, {
|
|
166
|
+
description: 'Surface API smoke',
|
|
167
|
+
version: '2.0.0',
|
|
168
|
+
});
|
|
169
|
+
expect(server).toBeDefined();
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
test('deriveMcpTools + createServer integration', () => {
|
|
173
|
+
const { app, tools } = createIntegrationFixtures();
|
|
141
174
|
const names = tools.map((t) => t.name);
|
|
142
175
|
expect(names).toContain('myapp_greet');
|
|
143
176
|
expect(names).toContain('myapp_item_delete');
|
|
@@ -149,7 +182,7 @@ describe('trailhead', () => {
|
|
|
149
182
|
requireTool(tools, 'myapp_item_delete').annotations?.destructiveHint
|
|
150
183
|
).toBe(true);
|
|
151
184
|
|
|
152
|
-
const server =
|
|
185
|
+
const server = createServer(app, {
|
|
153
186
|
name: 'myapp',
|
|
154
187
|
version: '1.0.0',
|
|
155
188
|
});
|
package/src/annotations.ts
CHANGED
|
@@ -27,7 +27,10 @@ export interface McpAnnotations {
|
|
|
27
27
|
* Omitted hints let the MCP SDK use its defaults.
|
|
28
28
|
*/
|
|
29
29
|
export const deriveAnnotations = (
|
|
30
|
-
trail: Pick<
|
|
30
|
+
trail: Pick<
|
|
31
|
+
Trail<unknown, unknown, unknown>,
|
|
32
|
+
'intent' | 'idempotent' | 'description'
|
|
33
|
+
>
|
|
31
34
|
): McpAnnotations => {
|
|
32
35
|
const annotations: Record<string, unknown> = {};
|
|
33
36
|
|
package/src/build.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Build MCP tool definitions from a Trails
|
|
2
|
+
* Build MCP tool definitions from a Trails graph.
|
|
3
3
|
*
|
|
4
4
|
* Iterates the topo, generates McpToolDefinition[] with handlers that
|
|
5
|
-
* validate input, compose
|
|
5
|
+
* validate input, compose layers, execute the implementation, and map
|
|
6
6
|
* Results to MCP responses.
|
|
7
7
|
*/
|
|
8
8
|
|
|
@@ -11,13 +11,16 @@ import {
|
|
|
11
11
|
TRAILHEAD_KEY,
|
|
12
12
|
ValidationError,
|
|
13
13
|
executeTrail,
|
|
14
|
+
filterSurfaceTrails,
|
|
14
15
|
isBlobRef,
|
|
16
|
+
validateEstablishedTopo,
|
|
15
17
|
zodToJsonSchema,
|
|
16
18
|
} from '@ontrails/core';
|
|
17
19
|
import type {
|
|
18
20
|
BlobRef,
|
|
19
|
-
|
|
20
|
-
|
|
21
|
+
Intent,
|
|
22
|
+
Layer,
|
|
23
|
+
ResourceOverrideMap,
|
|
21
24
|
Topo,
|
|
22
25
|
Trail,
|
|
23
26
|
TrailContextInit,
|
|
@@ -32,18 +35,21 @@ import { deriveToolName } from './tool-name.js';
|
|
|
32
35
|
// Public types
|
|
33
36
|
// ---------------------------------------------------------------------------
|
|
34
37
|
|
|
35
|
-
export interface
|
|
36
|
-
/** Config values for
|
|
38
|
+
export interface DeriveMcpToolsOptions {
|
|
39
|
+
/** Config values for resources that declare a `config` schema, keyed by resource ID. */
|
|
37
40
|
readonly configValues?:
|
|
38
41
|
| Readonly<Record<string, Record<string, unknown>>>
|
|
39
42
|
| undefined;
|
|
40
43
|
readonly createContext?:
|
|
41
44
|
| (() => TrailContextInit | Promise<TrailContextInit>)
|
|
42
45
|
| undefined;
|
|
43
|
-
readonly
|
|
44
|
-
readonly
|
|
45
|
-
readonly
|
|
46
|
-
readonly
|
|
46
|
+
readonly exclude?: readonly string[] | undefined;
|
|
47
|
+
readonly include?: readonly string[] | undefined;
|
|
48
|
+
readonly intent?: readonly Intent[] | undefined;
|
|
49
|
+
readonly layers?: readonly Layer[] | undefined;
|
|
50
|
+
readonly resources?: ResourceOverrideMap | undefined;
|
|
51
|
+
/** Set to `false` to skip topo validation while building tools. */
|
|
52
|
+
readonly validate?: boolean | undefined;
|
|
47
53
|
}
|
|
48
54
|
|
|
49
55
|
export interface McpToolDefinition {
|
|
@@ -224,9 +230,10 @@ const withMcpTrailhead = (
|
|
|
224
230
|
|
|
225
231
|
const createHandler =
|
|
226
232
|
(
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
233
|
+
graph: Topo,
|
|
234
|
+
t: Trail<unknown, unknown, unknown>,
|
|
235
|
+
layers: readonly Layer[],
|
|
236
|
+
options: DeriveMcpToolsOptions
|
|
230
237
|
): ((
|
|
231
238
|
args: Record<string, unknown>,
|
|
232
239
|
extra: McpExtra
|
|
@@ -238,8 +245,9 @@ const createHandler =
|
|
|
238
245
|
configValues: options.configValues,
|
|
239
246
|
createContext: options.createContext,
|
|
240
247
|
ctx: withMcpTrailhead(progressCb),
|
|
241
|
-
|
|
242
|
-
|
|
248
|
+
layers,
|
|
249
|
+
resources: options.resources,
|
|
250
|
+
topo: graph,
|
|
243
251
|
});
|
|
244
252
|
if (result.isOk()) {
|
|
245
253
|
return { content: await serializeOutput(result.value) };
|
|
@@ -252,37 +260,18 @@ const createHandler =
|
|
|
252
260
|
// ---------------------------------------------------------------------------
|
|
253
261
|
|
|
254
262
|
/**
|
|
255
|
-
* Build MCP tool definitions from
|
|
263
|
+
* Build MCP tool definitions from a graph's topology.
|
|
256
264
|
*
|
|
257
265
|
* Each trail in the topo becomes an McpToolDefinition with:
|
|
258
|
-
* - A derived tool name (
|
|
266
|
+
* - A derived tool name (topo-name-prefixed, underscore-delimited)
|
|
259
267
|
* - JSON Schema input from zodToJsonSchema
|
|
260
268
|
* - MCP annotations from trail meta
|
|
261
|
-
* - A handler that validates, composes
|
|
269
|
+
* - A handler that validates, composes layers, executes, and maps results
|
|
262
270
|
*/
|
|
263
|
-
/** Check if a trail should be included based on meta and filters. */
|
|
264
|
-
const shouldInclude = (
|
|
265
|
-
trail: Trail<unknown, unknown>,
|
|
266
|
-
options: BuildMcpToolsOptions
|
|
267
|
-
): boolean => {
|
|
268
|
-
if (trail.meta?.['internal'] === true) {
|
|
269
|
-
return false;
|
|
270
|
-
}
|
|
271
|
-
if (options.includeTrails !== undefined && options.includeTrails.length > 0) {
|
|
272
|
-
return options.includeTrails.includes(trail.id);
|
|
273
|
-
}
|
|
274
|
-
if (
|
|
275
|
-
options.excludeTrails !== undefined &&
|
|
276
|
-
options.excludeTrails.includes(trail.id)
|
|
277
|
-
) {
|
|
278
|
-
return false;
|
|
279
|
-
}
|
|
280
|
-
return true;
|
|
281
|
-
};
|
|
282
271
|
|
|
283
272
|
/** Build a description with optional example input appended. */
|
|
284
273
|
const buildDescription = (
|
|
285
|
-
trail: Trail<unknown, unknown>
|
|
274
|
+
trail: Trail<unknown, unknown, unknown>
|
|
286
275
|
): string | undefined => {
|
|
287
276
|
let { description } = trail;
|
|
288
277
|
if (
|
|
@@ -300,10 +289,10 @@ const buildDescription = (
|
|
|
300
289
|
|
|
301
290
|
/** Build a single MCP tool definition from a trail. */
|
|
302
291
|
const buildToolDefinition = (
|
|
303
|
-
|
|
304
|
-
trail: Trail<unknown, unknown>,
|
|
305
|
-
|
|
306
|
-
options:
|
|
292
|
+
graph: Topo,
|
|
293
|
+
trail: Trail<unknown, unknown, unknown>,
|
|
294
|
+
layers: readonly Layer[],
|
|
295
|
+
options: DeriveMcpToolsOptions
|
|
307
296
|
): McpToolDefinition => {
|
|
308
297
|
const rawAnnotations = deriveAnnotations(trail);
|
|
309
298
|
const annotations =
|
|
@@ -311,23 +300,23 @@ const buildToolDefinition = (
|
|
|
311
300
|
return {
|
|
312
301
|
annotations,
|
|
313
302
|
description: buildDescription(trail),
|
|
314
|
-
handler: createHandler(trail,
|
|
303
|
+
handler: createHandler(graph, trail, layers, options),
|
|
315
304
|
inputSchema: zodToJsonSchema(trail.input),
|
|
316
|
-
name: deriveToolName(
|
|
305
|
+
name: deriveToolName(graph.name, trail.id),
|
|
317
306
|
trailId: trail.id,
|
|
318
307
|
};
|
|
319
308
|
};
|
|
320
309
|
|
|
321
310
|
/** Register a trail as an MCP tool, checking for name collisions. */
|
|
322
311
|
const registerTool = (
|
|
323
|
-
|
|
324
|
-
trailItem: Trail<unknown, unknown>,
|
|
325
|
-
|
|
326
|
-
options:
|
|
312
|
+
graph: Topo,
|
|
313
|
+
trailItem: Trail<unknown, unknown, unknown>,
|
|
314
|
+
layers: readonly Layer[],
|
|
315
|
+
options: DeriveMcpToolsOptions,
|
|
327
316
|
nameToTrailId: Map<string, string>,
|
|
328
317
|
tools: McpToolDefinition[]
|
|
329
318
|
): Result<void, Error> => {
|
|
330
|
-
const toolName = deriveToolName(
|
|
319
|
+
const toolName = deriveToolName(graph.name, trailItem.id);
|
|
331
320
|
const existingId = nameToTrailId.get(toolName);
|
|
332
321
|
if (existingId !== undefined) {
|
|
333
322
|
return Result.err(
|
|
@@ -337,30 +326,46 @@ const registerTool = (
|
|
|
337
326
|
);
|
|
338
327
|
}
|
|
339
328
|
nameToTrailId.set(toolName, trailItem.id);
|
|
340
|
-
tools.push(buildToolDefinition(
|
|
329
|
+
tools.push(buildToolDefinition(graph, trailItem, layers, options));
|
|
341
330
|
return Result.ok();
|
|
342
331
|
};
|
|
343
332
|
|
|
344
333
|
/** Filter topo items to eligible trails. */
|
|
345
334
|
const eligibleTrails = (
|
|
346
|
-
|
|
347
|
-
options:
|
|
348
|
-
): Trail<unknown, unknown>[] =>
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
335
|
+
graph: Topo,
|
|
336
|
+
options: DeriveMcpToolsOptions
|
|
337
|
+
): Trail<unknown, unknown, unknown>[] =>
|
|
338
|
+
filterSurfaceTrails(graph.list(), {
|
|
339
|
+
exclude: options.exclude,
|
|
340
|
+
include: options.include,
|
|
341
|
+
intent: options.intent,
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
const validateToolBuild = (
|
|
345
|
+
graph: Topo,
|
|
346
|
+
options: DeriveMcpToolsOptions
|
|
347
|
+
): Result<void, Error> => {
|
|
348
|
+
if (options.validate === false) {
|
|
349
|
+
return Result.ok();
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
const validated = validateEstablishedTopo(graph);
|
|
353
|
+
return validated.isErr() ? Result.err(validated.error) : Result.ok();
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
const registerTools = (
|
|
357
|
+
graph: Topo,
|
|
358
|
+
options: DeriveMcpToolsOptions,
|
|
359
|
+
layers: readonly Layer[]
|
|
354
360
|
): Result<McpToolDefinition[], Error> => {
|
|
355
|
-
const gates = options.gates ?? [];
|
|
356
361
|
const tools: McpToolDefinition[] = [];
|
|
357
362
|
const nameToTrailId = new Map<string, string>();
|
|
358
363
|
|
|
359
|
-
for (const trailItem of eligibleTrails(
|
|
364
|
+
for (const trailItem of eligibleTrails(graph, options)) {
|
|
360
365
|
const registered = registerTool(
|
|
361
|
-
|
|
366
|
+
graph,
|
|
362
367
|
trailItem,
|
|
363
|
-
|
|
368
|
+
layers,
|
|
364
369
|
options,
|
|
365
370
|
nameToTrailId,
|
|
366
371
|
tools
|
|
@@ -372,3 +377,15 @@ export const buildMcpTools = (
|
|
|
372
377
|
|
|
373
378
|
return Result.ok(tools);
|
|
374
379
|
};
|
|
380
|
+
|
|
381
|
+
export const deriveMcpTools = (
|
|
382
|
+
graph: Topo,
|
|
383
|
+
options: DeriveMcpToolsOptions = {}
|
|
384
|
+
): Result<McpToolDefinition[], Error> => {
|
|
385
|
+
const validation = validateToolBuild(graph, options);
|
|
386
|
+
if (validation.isErr()) {
|
|
387
|
+
return validation;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
return registerTools(graph, options, options.layers ?? []);
|
|
391
|
+
};
|
package/src/index.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Build
|
|
2
2
|
export {
|
|
3
|
-
|
|
4
|
-
type
|
|
3
|
+
deriveMcpTools,
|
|
4
|
+
type DeriveMcpToolsOptions,
|
|
5
5
|
type McpToolDefinition,
|
|
6
6
|
type McpToolResult,
|
|
7
7
|
type McpContent,
|
|
@@ -17,8 +17,13 @@ export { deriveAnnotations, type McpAnnotations } from './annotations.js';
|
|
|
17
17
|
// Progress
|
|
18
18
|
export { createMcpProgressCallback } from './progress.js';
|
|
19
19
|
|
|
20
|
-
//
|
|
21
|
-
export {
|
|
20
|
+
// Surface
|
|
21
|
+
export {
|
|
22
|
+
createServer,
|
|
23
|
+
surface,
|
|
24
|
+
type CreateServerOptions,
|
|
25
|
+
type SurfaceMcpResult,
|
|
26
|
+
} from './surface.js';
|
|
22
27
|
|
|
23
28
|
// Transport
|
|
24
29
|
export { connectStdio } from './stdio.js';
|
package/src/stdio.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Thin wrapper around MCP SDK's StdioServerTransport.
|
|
3
3
|
*
|
|
4
4
|
* Exists as a separate function so it can be swapped for other transports
|
|
5
|
-
* (SSE, streamable HTTP) without changing
|
|
5
|
+
* (SSE, streamable HTTP) without changing surface().
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import type { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
@@ -1,12 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
* Three lines to expose trails as MCP tools:
|
|
5
|
-
*
|
|
6
|
-
* ```ts
|
|
7
|
-
* const app = topo("myapp", entity);
|
|
8
|
-
* await trailhead(app);
|
|
9
|
-
* ```
|
|
2
|
+
* Surface helpers for exposing a topo over MCP.
|
|
10
3
|
*/
|
|
11
4
|
|
|
12
5
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
@@ -15,42 +8,43 @@ import {
|
|
|
15
8
|
ListToolsRequestSchema,
|
|
16
9
|
} from '@modelcontextprotocol/sdk/types.js';
|
|
17
10
|
import type {
|
|
18
|
-
|
|
19
|
-
|
|
11
|
+
Intent,
|
|
12
|
+
Layer,
|
|
13
|
+
ResourceOverrideMap,
|
|
20
14
|
Topo,
|
|
21
15
|
TrailContextInit,
|
|
22
16
|
} from '@ontrails/core';
|
|
23
|
-
import { validateTopo } from '@ontrails/core';
|
|
24
17
|
|
|
25
18
|
import type { McpToolDefinition } from './build.js';
|
|
26
|
-
import {
|
|
19
|
+
import { deriveMcpTools } from './build.js';
|
|
27
20
|
import { connectStdio } from './stdio.js';
|
|
28
21
|
|
|
29
22
|
// ---------------------------------------------------------------------------
|
|
30
23
|
// Options
|
|
31
24
|
// ---------------------------------------------------------------------------
|
|
32
25
|
|
|
33
|
-
export interface
|
|
34
|
-
/** Config values for
|
|
26
|
+
export interface CreateServerOptions {
|
|
27
|
+
/** Config values for resources that declare a `config` schema, keyed by resource ID. */
|
|
35
28
|
readonly configValues?:
|
|
36
29
|
| Readonly<Record<string, Record<string, unknown>>>
|
|
37
30
|
| undefined;
|
|
38
31
|
readonly createContext?:
|
|
39
32
|
| (() => TrailContextInit | Promise<TrailContextInit>)
|
|
40
33
|
| undefined;
|
|
41
|
-
readonly
|
|
42
|
-
readonly
|
|
43
|
-
readonly
|
|
44
|
-
readonly
|
|
45
|
-
readonly
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
readonly version?: string | undefined;
|
|
49
|
-
}
|
|
50
|
-
| undefined;
|
|
51
|
-
readonly transport?: 'stdio' | undefined;
|
|
34
|
+
readonly description?: string | undefined;
|
|
35
|
+
readonly exclude?: readonly string[] | undefined;
|
|
36
|
+
readonly include?: readonly string[] | undefined;
|
|
37
|
+
readonly intent?: readonly Intent[] | undefined;
|
|
38
|
+
readonly layers?: readonly Layer[] | undefined;
|
|
39
|
+
readonly name?: string | undefined;
|
|
40
|
+
readonly resources?: ResourceOverrideMap | undefined;
|
|
52
41
|
/** Set to `false` to skip topo validation at startup. Defaults to `true`. */
|
|
53
42
|
readonly validate?: boolean | undefined;
|
|
43
|
+
readonly version?: string | undefined;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface SurfaceMcpResult {
|
|
47
|
+
readonly close: () => Promise<void>;
|
|
54
48
|
}
|
|
55
49
|
|
|
56
50
|
// ---------------------------------------------------------------------------
|
|
@@ -59,14 +53,27 @@ export interface TrailheadMcpOptions {
|
|
|
59
53
|
|
|
60
54
|
/**
|
|
61
55
|
* Create an MCP Server instance and register all tools.
|
|
56
|
+
*
|
|
57
|
+
* When provided, `info.description` is forwarded to the MCP SDK as the
|
|
58
|
+
* server's `instructions` field — the SDK's documented channel for
|
|
59
|
+
* "optional instructions describing how to use the server and its features."
|
|
62
60
|
*/
|
|
63
|
-
|
|
61
|
+
const createMcpServer = (
|
|
64
62
|
tools: McpToolDefinition[],
|
|
65
|
-
info: {
|
|
63
|
+
info: {
|
|
64
|
+
readonly name: string;
|
|
65
|
+
readonly version: string;
|
|
66
|
+
readonly description?: string | undefined;
|
|
67
|
+
}
|
|
66
68
|
): Server => {
|
|
67
69
|
const server = new Server(
|
|
68
70
|
{ name: info.name, version: info.version },
|
|
69
|
-
{
|
|
71
|
+
{
|
|
72
|
+
capabilities: { tools: {} },
|
|
73
|
+
...(info.description === undefined
|
|
74
|
+
? {}
|
|
75
|
+
: { instructions: info.description }),
|
|
76
|
+
}
|
|
70
77
|
);
|
|
71
78
|
|
|
72
79
|
// Build a lookup map for tool dispatch
|
|
@@ -133,40 +140,58 @@ export const createMcpServer = (
|
|
|
133
140
|
};
|
|
134
141
|
|
|
135
142
|
// ---------------------------------------------------------------------------
|
|
136
|
-
//
|
|
143
|
+
// createServer
|
|
137
144
|
// ---------------------------------------------------------------------------
|
|
138
145
|
|
|
139
146
|
/**
|
|
140
|
-
* Build MCP tools from
|
|
147
|
+
* Build MCP tools from a topo and create an MCP server.
|
|
141
148
|
*/
|
|
142
|
-
export const
|
|
143
|
-
|
|
144
|
-
options:
|
|
145
|
-
):
|
|
146
|
-
|
|
147
|
-
const validated = validateTopo(app);
|
|
148
|
-
if (validated.isErr()) {
|
|
149
|
-
throw validated.error;
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
const toolsResult = buildMcpTools(app, {
|
|
149
|
+
export const createServer = (
|
|
150
|
+
graph: Topo,
|
|
151
|
+
options: CreateServerOptions = {}
|
|
152
|
+
): Server => {
|
|
153
|
+
const toolsResult = deriveMcpTools(graph, {
|
|
154
154
|
configValues: options.configValues,
|
|
155
155
|
createContext: options.createContext,
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
156
|
+
exclude: options.exclude,
|
|
157
|
+
include: options.include,
|
|
158
|
+
intent: options.intent,
|
|
159
|
+
layers: options.layers,
|
|
160
|
+
resources: options.resources,
|
|
161
|
+
validate: options.validate,
|
|
160
162
|
});
|
|
161
163
|
|
|
162
164
|
if (toolsResult.isErr()) {
|
|
163
165
|
throw toolsResult.error;
|
|
164
166
|
}
|
|
165
167
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
168
|
+
return createMcpServer(toolsResult.value, {
|
|
169
|
+
description: options.description ?? graph.description,
|
|
170
|
+
name: options.name ?? graph.name,
|
|
171
|
+
version: options.version ?? graph.version ?? '0.1.0',
|
|
169
172
|
});
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
// ---------------------------------------------------------------------------
|
|
176
|
+
// surface
|
|
177
|
+
// ---------------------------------------------------------------------------
|
|
170
178
|
|
|
179
|
+
/**
|
|
180
|
+
* Build MCP tools from a topo, create a server, and connect via stdio.
|
|
181
|
+
*
|
|
182
|
+
* @remarks Opens the MCP server on stdio. For custom transports, use
|
|
183
|
+
* `createServer(graph)` with `connectStdio` or your own connector.
|
|
184
|
+
*/
|
|
185
|
+
export const surface = async (
|
|
186
|
+
graph: Topo,
|
|
187
|
+
options: CreateServerOptions = {}
|
|
188
|
+
): Promise<SurfaceMcpResult> => {
|
|
189
|
+
const server = createServer(graph, options);
|
|
171
190
|
await connectStdio(server);
|
|
191
|
+
|
|
192
|
+
return {
|
|
193
|
+
close: async () => {
|
|
194
|
+
await server.close();
|
|
195
|
+
},
|
|
196
|
+
};
|
|
172
197
|
};
|
package/tsconfig.tsbuildinfo
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"root":["./src/annotations.ts","./src/build.ts","./src/index.ts","./src/progress.ts","./src/stdio.ts","./src/
|
|
1
|
+
{"root":["./src/annotations.ts","./src/build.ts","./src/index.ts","./src/progress.ts","./src/stdio.ts","./src/surface.ts","./src/tool-name.ts"],"version":"5.9.3"}
|