@ontrails/mcp 1.0.0-beta.14 → 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 +7 -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 +11 -10
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +29 -38
- 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 +47 -20
- package/dist/trailhead.js.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/build.test.ts +148 -29
- package/src/__tests__/{trailhead.test.ts → surface.test.ts} +59 -26
- package/src/annotations.ts +4 -1
- package/src/build.ts +58 -68
- package/src/index.ts +9 -4
- package/src/stdio.ts +1 -1
- package/src/{trailhead.ts → surface.ts} +73 -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,14 +11,16 @@ import {
|
|
|
11
11
|
TRAILHEAD_KEY,
|
|
12
12
|
ValidationError,
|
|
13
13
|
executeTrail,
|
|
14
|
+
filterSurfaceTrails,
|
|
14
15
|
isBlobRef,
|
|
15
16
|
validateEstablishedTopo,
|
|
16
17
|
zodToJsonSchema,
|
|
17
18
|
} from '@ontrails/core';
|
|
18
19
|
import type {
|
|
19
20
|
BlobRef,
|
|
20
|
-
|
|
21
|
-
|
|
21
|
+
Intent,
|
|
22
|
+
Layer,
|
|
23
|
+
ResourceOverrideMap,
|
|
22
24
|
Topo,
|
|
23
25
|
Trail,
|
|
24
26
|
TrailContextInit,
|
|
@@ -33,18 +35,19 @@ import { deriveToolName } from './tool-name.js';
|
|
|
33
35
|
// Public types
|
|
34
36
|
// ---------------------------------------------------------------------------
|
|
35
37
|
|
|
36
|
-
export interface
|
|
37
|
-
/** Config values for
|
|
38
|
+
export interface DeriveMcpToolsOptions {
|
|
39
|
+
/** Config values for resources that declare a `config` schema, keyed by resource ID. */
|
|
38
40
|
readonly configValues?:
|
|
39
41
|
| Readonly<Record<string, Record<string, unknown>>>
|
|
40
42
|
| undefined;
|
|
41
43
|
readonly createContext?:
|
|
42
44
|
| (() => TrailContextInit | Promise<TrailContextInit>)
|
|
43
45
|
| undefined;
|
|
44
|
-
readonly
|
|
45
|
-
readonly
|
|
46
|
-
readonly
|
|
47
|
-
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;
|
|
48
51
|
/** Set to `false` to skip topo validation while building tools. */
|
|
49
52
|
readonly validate?: boolean | undefined;
|
|
50
53
|
}
|
|
@@ -227,9 +230,10 @@ const withMcpTrailhead = (
|
|
|
227
230
|
|
|
228
231
|
const createHandler =
|
|
229
232
|
(
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
+
graph: Topo,
|
|
234
|
+
t: Trail<unknown, unknown, unknown>,
|
|
235
|
+
layers: readonly Layer[],
|
|
236
|
+
options: DeriveMcpToolsOptions
|
|
233
237
|
): ((
|
|
234
238
|
args: Record<string, unknown>,
|
|
235
239
|
extra: McpExtra
|
|
@@ -241,8 +245,9 @@ const createHandler =
|
|
|
241
245
|
configValues: options.configValues,
|
|
242
246
|
createContext: options.createContext,
|
|
243
247
|
ctx: withMcpTrailhead(progressCb),
|
|
244
|
-
|
|
245
|
-
|
|
248
|
+
layers,
|
|
249
|
+
resources: options.resources,
|
|
250
|
+
topo: graph,
|
|
246
251
|
});
|
|
247
252
|
if (result.isOk()) {
|
|
248
253
|
return { content: await serializeOutput(result.value) };
|
|
@@ -255,37 +260,18 @@ const createHandler =
|
|
|
255
260
|
// ---------------------------------------------------------------------------
|
|
256
261
|
|
|
257
262
|
/**
|
|
258
|
-
* Build MCP tool definitions from
|
|
263
|
+
* Build MCP tool definitions from a graph's topology.
|
|
259
264
|
*
|
|
260
265
|
* Each trail in the topo becomes an McpToolDefinition with:
|
|
261
|
-
* - A derived tool name (
|
|
266
|
+
* - A derived tool name (topo-name-prefixed, underscore-delimited)
|
|
262
267
|
* - JSON Schema input from zodToJsonSchema
|
|
263
268
|
* - MCP annotations from trail meta
|
|
264
|
-
* - A handler that validates, composes
|
|
269
|
+
* - A handler that validates, composes layers, executes, and maps results
|
|
265
270
|
*/
|
|
266
|
-
/** Check if a trail should be included based on meta and filters. */
|
|
267
|
-
const shouldInclude = (
|
|
268
|
-
trail: Trail<unknown, unknown>,
|
|
269
|
-
options: BuildMcpToolsOptions
|
|
270
|
-
): boolean => {
|
|
271
|
-
if (trail.meta?.['internal'] === true) {
|
|
272
|
-
return false;
|
|
273
|
-
}
|
|
274
|
-
if (options.includeTrails !== undefined && options.includeTrails.length > 0) {
|
|
275
|
-
return options.includeTrails.includes(trail.id);
|
|
276
|
-
}
|
|
277
|
-
if (
|
|
278
|
-
options.excludeTrails !== undefined &&
|
|
279
|
-
options.excludeTrails.includes(trail.id)
|
|
280
|
-
) {
|
|
281
|
-
return false;
|
|
282
|
-
}
|
|
283
|
-
return true;
|
|
284
|
-
};
|
|
285
271
|
|
|
286
272
|
/** Build a description with optional example input appended. */
|
|
287
273
|
const buildDescription = (
|
|
288
|
-
trail: Trail<unknown, unknown>
|
|
274
|
+
trail: Trail<unknown, unknown, unknown>
|
|
289
275
|
): string | undefined => {
|
|
290
276
|
let { description } = trail;
|
|
291
277
|
if (
|
|
@@ -303,10 +289,10 @@ const buildDescription = (
|
|
|
303
289
|
|
|
304
290
|
/** Build a single MCP tool definition from a trail. */
|
|
305
291
|
const buildToolDefinition = (
|
|
306
|
-
|
|
307
|
-
trail: Trail<unknown, unknown>,
|
|
308
|
-
|
|
309
|
-
options:
|
|
292
|
+
graph: Topo,
|
|
293
|
+
trail: Trail<unknown, unknown, unknown>,
|
|
294
|
+
layers: readonly Layer[],
|
|
295
|
+
options: DeriveMcpToolsOptions
|
|
310
296
|
): McpToolDefinition => {
|
|
311
297
|
const rawAnnotations = deriveAnnotations(trail);
|
|
312
298
|
const annotations =
|
|
@@ -314,23 +300,23 @@ const buildToolDefinition = (
|
|
|
314
300
|
return {
|
|
315
301
|
annotations,
|
|
316
302
|
description: buildDescription(trail),
|
|
317
|
-
handler: createHandler(trail,
|
|
303
|
+
handler: createHandler(graph, trail, layers, options),
|
|
318
304
|
inputSchema: zodToJsonSchema(trail.input),
|
|
319
|
-
name: deriveToolName(
|
|
305
|
+
name: deriveToolName(graph.name, trail.id),
|
|
320
306
|
trailId: trail.id,
|
|
321
307
|
};
|
|
322
308
|
};
|
|
323
309
|
|
|
324
310
|
/** Register a trail as an MCP tool, checking for name collisions. */
|
|
325
311
|
const registerTool = (
|
|
326
|
-
|
|
327
|
-
trailItem: Trail<unknown, unknown>,
|
|
328
|
-
|
|
329
|
-
options:
|
|
312
|
+
graph: Topo,
|
|
313
|
+
trailItem: Trail<unknown, unknown, unknown>,
|
|
314
|
+
layers: readonly Layer[],
|
|
315
|
+
options: DeriveMcpToolsOptions,
|
|
330
316
|
nameToTrailId: Map<string, string>,
|
|
331
317
|
tools: McpToolDefinition[]
|
|
332
318
|
): Result<void, Error> => {
|
|
333
|
-
const toolName = deriveToolName(
|
|
319
|
+
const toolName = deriveToolName(graph.name, trailItem.id);
|
|
334
320
|
const existingId = nameToTrailId.get(toolName);
|
|
335
321
|
if (existingId !== undefined) {
|
|
336
322
|
return Result.err(
|
|
@@ -340,42 +326,46 @@ const registerTool = (
|
|
|
340
326
|
);
|
|
341
327
|
}
|
|
342
328
|
nameToTrailId.set(toolName, trailItem.id);
|
|
343
|
-
tools.push(buildToolDefinition(
|
|
329
|
+
tools.push(buildToolDefinition(graph, trailItem, layers, options));
|
|
344
330
|
return Result.ok();
|
|
345
331
|
};
|
|
346
332
|
|
|
347
333
|
/** Filter topo items to eligible trails. */
|
|
348
334
|
const eligibleTrails = (
|
|
349
|
-
|
|
350
|
-
options:
|
|
351
|
-
): Trail<unknown, unknown>[] =>
|
|
352
|
-
|
|
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
|
+
});
|
|
353
343
|
|
|
354
344
|
const validateToolBuild = (
|
|
355
|
-
|
|
356
|
-
options:
|
|
345
|
+
graph: Topo,
|
|
346
|
+
options: DeriveMcpToolsOptions
|
|
357
347
|
): Result<void, Error> => {
|
|
358
348
|
if (options.validate === false) {
|
|
359
349
|
return Result.ok();
|
|
360
350
|
}
|
|
361
351
|
|
|
362
|
-
const validated = validateEstablishedTopo(
|
|
352
|
+
const validated = validateEstablishedTopo(graph);
|
|
363
353
|
return validated.isErr() ? Result.err(validated.error) : Result.ok();
|
|
364
354
|
};
|
|
365
355
|
|
|
366
356
|
const registerTools = (
|
|
367
|
-
|
|
368
|
-
options:
|
|
369
|
-
|
|
357
|
+
graph: Topo,
|
|
358
|
+
options: DeriveMcpToolsOptions,
|
|
359
|
+
layers: readonly Layer[]
|
|
370
360
|
): Result<McpToolDefinition[], Error> => {
|
|
371
361
|
const tools: McpToolDefinition[] = [];
|
|
372
362
|
const nameToTrailId = new Map<string, string>();
|
|
373
363
|
|
|
374
|
-
for (const trailItem of eligibleTrails(
|
|
364
|
+
for (const trailItem of eligibleTrails(graph, options)) {
|
|
375
365
|
const registered = registerTool(
|
|
376
|
-
|
|
366
|
+
graph,
|
|
377
367
|
trailItem,
|
|
378
|
-
|
|
368
|
+
layers,
|
|
379
369
|
options,
|
|
380
370
|
nameToTrailId,
|
|
381
371
|
tools
|
|
@@ -388,14 +378,14 @@ const registerTools = (
|
|
|
388
378
|
return Result.ok(tools);
|
|
389
379
|
};
|
|
390
380
|
|
|
391
|
-
export const
|
|
392
|
-
|
|
393
|
-
options:
|
|
381
|
+
export const deriveMcpTools = (
|
|
382
|
+
graph: Topo,
|
|
383
|
+
options: DeriveMcpToolsOptions = {}
|
|
394
384
|
): Result<McpToolDefinition[], Error> => {
|
|
395
|
-
const validation = validateToolBuild(
|
|
385
|
+
const validation = validateToolBuild(graph, options);
|
|
396
386
|
if (validation.isErr()) {
|
|
397
387
|
return validation;
|
|
398
388
|
}
|
|
399
389
|
|
|
400
|
-
return registerTools(
|
|
390
|
+
return registerTools(graph, options, options.layers ?? []);
|
|
401
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 { validateEstablishedTopo } 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,30 +140,24 @@ 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 = validateEstablishedTopo(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,
|
|
160
161
|
validate: options.validate,
|
|
161
162
|
});
|
|
162
163
|
|
|
@@ -164,10 +165,33 @@ export const trailhead = async (
|
|
|
164
165
|
throw toolsResult.error;
|
|
165
166
|
}
|
|
166
167
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
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',
|
|
170
172
|
});
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
// ---------------------------------------------------------------------------
|
|
176
|
+
// surface
|
|
177
|
+
// ---------------------------------------------------------------------------
|
|
171
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);
|
|
172
190
|
await connectStdio(server);
|
|
191
|
+
|
|
192
|
+
return {
|
|
193
|
+
close: async () => {
|
|
194
|
+
await server.close();
|
|
195
|
+
},
|
|
196
|
+
};
|
|
173
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"}
|