@ontrails/mcp 1.0.0-beta.3 → 1.0.0-beta.32

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.
Files changed (49) hide show
  1. package/CHANGELOG.md +371 -8
  2. package/README.md +73 -21
  3. package/package.json +11 -3
  4. package/src/annotations.ts +32 -9
  5. package/src/build.ts +1189 -137
  6. package/src/index.ts +32 -4
  7. package/src/progress.ts +22 -0
  8. package/src/resources.ts +336 -0
  9. package/src/stdio.ts +9 -1
  10. package/src/surface.ts +281 -0
  11. package/.turbo/turbo-build.log +0 -1
  12. package/.turbo/turbo-lint.log +0 -3
  13. package/.turbo/turbo-typecheck.log +0 -1
  14. package/dist/annotations.d.ts +0 -19
  15. package/dist/annotations.d.ts.map +0 -1
  16. package/dist/annotations.js +0 -29
  17. package/dist/annotations.js.map +0 -1
  18. package/dist/blaze.d.ts +0 -36
  19. package/dist/blaze.d.ts.map +0 -1
  20. package/dist/blaze.js +0 -96
  21. package/dist/blaze.js.map +0 -1
  22. package/dist/build.d.ts +0 -40
  23. package/dist/build.d.ts.map +0 -1
  24. package/dist/build.js +0 -227
  25. package/dist/build.js.map +0 -1
  26. package/dist/index.d.ts +0 -7
  27. package/dist/index.d.ts.map +0 -1
  28. package/dist/index.js +0 -13
  29. package/dist/index.js.map +0 -1
  30. package/dist/progress.d.ts +0 -13
  31. package/dist/progress.d.ts.map +0 -1
  32. package/dist/progress.js +0 -51
  33. package/dist/progress.js.map +0 -1
  34. package/dist/stdio.d.ts +0 -12
  35. package/dist/stdio.d.ts.map +0 -1
  36. package/dist/stdio.js +0 -15
  37. package/dist/stdio.js.map +0 -1
  38. package/dist/tool-name.d.ts +0 -15
  39. package/dist/tool-name.d.ts.map +0 -1
  40. package/dist/tool-name.js +0 -19
  41. package/dist/tool-name.js.map +0 -1
  42. package/src/__tests__/annotations.test.ts +0 -70
  43. package/src/__tests__/blaze.test.ts +0 -105
  44. package/src/__tests__/build.test.ts +0 -454
  45. package/src/__tests__/progress.test.ts +0 -136
  46. package/src/__tests__/tool-name.test.ts +0 -46
  47. package/src/blaze.ts +0 -146
  48. package/tsconfig.json +0 -9
  49. package/tsconfig.tsbuildinfo +0 -1
package/src/index.ts CHANGED
@@ -1,13 +1,36 @@
1
1
  // Build
2
2
  export {
3
- buildMcpTools,
4
- type BuildMcpToolsOptions,
3
+ MCP_TOOL_ERROR_META_KEY,
4
+ MCP_TOOL_EXAMPLES_META_KEY,
5
+ MCP_TOOL_DEFERRED_META_KEY,
6
+ MCP_TOOL_FACET_META_KEY,
7
+ deriveMcpTools,
8
+ type DeriveMcpToolsOptions,
9
+ type McpSurfaceFacetDefinition,
10
+ type McpSurfaceFacetMap,
11
+ type McpSurfaceFacetTrailSelector,
5
12
  type McpToolDefinition,
6
13
  type McpToolResult,
14
+ type McpToolErrorMeta,
7
15
  type McpContent,
8
16
  type McpExtra,
17
+ type ResolveMcpPermit,
18
+ type ResolveMcpPermitInput,
9
19
  } from './build.js';
10
20
 
21
+ // MCP resources
22
+ export {
23
+ MCP_EXAMPLES_RESOURCE_PREFIX,
24
+ MCP_SURFACE_MAP_RESOURCE_URI,
25
+ MCP_TRAIL_RESOURCE_PREFIX,
26
+ buildMcpResources,
27
+ isMcpFacetTool,
28
+ type BuiltMcpResources,
29
+ type McpResourceContent,
30
+ type McpResourceDefinition,
31
+ type McpResourcesConfig,
32
+ } from './resources.js';
33
+
11
34
  // Tool naming
12
35
  export { deriveToolName } from './tool-name.js';
13
36
 
@@ -17,8 +40,13 @@ export { deriveAnnotations, type McpAnnotations } from './annotations.js';
17
40
  // Progress
18
41
  export { createMcpProgressCallback } from './progress.js';
19
42
 
20
- // Blaze
21
- export { blaze, type BlazeMcpOptions } from './blaze.js';
43
+ // Surface
44
+ export {
45
+ createServer,
46
+ surface,
47
+ type CreateServerOptions,
48
+ type SurfaceMcpResult,
49
+ } from './surface.js';
22
50
 
23
51
  // Transport
24
52
  export { connectStdio } from './stdio.js';
package/src/progress.ts CHANGED
@@ -54,6 +54,28 @@ const progressHandlers: Record<
54
54
  *
55
55
  * Returns `undefined` if the MCP client did not provide a progressToken
56
56
  * (meaning no progress reporting was requested).
57
+ *
58
+ * @example
59
+ * ```ts
60
+ * import { createMcpProgressCallback, type McpExtra } from '@ontrails/mcp';
61
+ *
62
+ * const extra: McpExtra = {
63
+ * progressToken: 'token-123',
64
+ * sendProgress: async (current, total) => {
65
+ * console.log(`progress ${current}/${total}`);
66
+ * },
67
+ * };
68
+ *
69
+ * const progress = createMcpProgressCallback(extra);
70
+ * if (progress !== undefined) {
71
+ * progress({
72
+ * current: 0,
73
+ * ts: new Date().toISOString(),
74
+ * total: 1,
75
+ * type: 'start',
76
+ * });
77
+ * }
78
+ * ```
57
79
  */
58
80
  export const createMcpProgressCallback = (
59
81
  extra: McpExtra
@@ -0,0 +1,336 @@
1
+ /**
2
+ * MCP resource projection for cold Trails context.
3
+ */
4
+
5
+ import { deriveStructuredTrailExamples } from '@ontrails/core';
6
+ import type { Topo, Trail } from '@ontrails/core';
7
+
8
+ import {
9
+ MCP_TOOL_DEFERRED_META_KEY,
10
+ MCP_TOOL_FACET_META_KEY,
11
+ } from './build.js';
12
+ import type { McpToolDefinition } from './build.js';
13
+
14
+ /**
15
+ * Resource URI used for the resolved MCP surface map.
16
+ *
17
+ * @example
18
+ * ```ts
19
+ * import { MCP_SURFACE_MAP_RESOURCE_URI } from '@ontrails/mcp';
20
+ *
21
+ * const surfaceMap = resources.read(MCP_SURFACE_MAP_RESOURCE_URI);
22
+ * ```
23
+ */
24
+ export const MCP_SURFACE_MAP_RESOURCE_URI = 'trails://surface-map';
25
+
26
+ /**
27
+ * Prefix used for trail example resources exposed through MCP.
28
+ *
29
+ * @example
30
+ * ```ts
31
+ * import { MCP_EXAMPLES_RESOURCE_PREFIX } from '@ontrails/mcp';
32
+ *
33
+ * const uri = `${MCP_EXAMPLES_RESOURCE_PREFIX}${encodeURIComponent('tasks.create')}`;
34
+ * ```
35
+ */
36
+ export const MCP_EXAMPLES_RESOURCE_PREFIX = 'trails://examples/';
37
+
38
+ /**
39
+ * Prefix used for trail graph fact resources exposed through MCP.
40
+ *
41
+ * @example
42
+ * ```ts
43
+ * import { MCP_TRAIL_RESOURCE_PREFIX } from '@ontrails/mcp';
44
+ *
45
+ * const uri = `${MCP_TRAIL_RESOURCE_PREFIX}${encodeURIComponent('tasks.create')}`;
46
+ * ```
47
+ */
48
+ export const MCP_TRAIL_RESOURCE_PREFIX = 'trails://trail/';
49
+
50
+ export interface McpResourceDefinition {
51
+ readonly uri: string;
52
+ readonly mimeType: string;
53
+ readonly name: string;
54
+ readonly description?: string | undefined;
55
+ }
56
+
57
+ export interface McpResourceContent {
58
+ readonly uri: string;
59
+ readonly mimeType: string;
60
+ readonly text: string;
61
+ }
62
+
63
+ export interface McpResourcesConfig {
64
+ readonly surfaceMap?: boolean | undefined;
65
+ readonly examples?: boolean | undefined;
66
+ readonly graph?: boolean | undefined;
67
+ }
68
+
69
+ export interface BuiltMcpResources {
70
+ readonly list: readonly McpResourceDefinition[];
71
+ readonly read: (uri: string) => McpResourceContent | undefined;
72
+ }
73
+
74
+ interface McpSurfaceMapTool {
75
+ readonly annotations: McpToolDefinition['annotations'];
76
+ readonly description: string | undefined;
77
+ readonly facetId?: string | undefined;
78
+ readonly inputSchema: Record<string, unknown>;
79
+ readonly memberTrailIds?: readonly string[] | undefined;
80
+ readonly name: string;
81
+ readonly outputSchema?: Record<string, unknown> | undefined;
82
+ readonly trailId?: string | undefined;
83
+ readonly versions?: McpToolDefinition['versions'];
84
+ readonly deferred?: true | undefined;
85
+ }
86
+
87
+ interface McpSurfaceMap {
88
+ readonly surface: 'mcp';
89
+ readonly tools: readonly McpSurfaceMapTool[];
90
+ }
91
+
92
+ interface McpTrailResource {
93
+ readonly trailId: string;
94
+ readonly description?: string | undefined;
95
+ readonly intent: Trail<unknown, unknown, unknown>['intent'];
96
+ readonly visibility: Trail<unknown, unknown, unknown>['visibility'];
97
+ readonly composes: readonly string[];
98
+ readonly resources: readonly string[];
99
+ readonly signals: {
100
+ readonly fires: readonly string[];
101
+ readonly on: readonly string[];
102
+ };
103
+ readonly surface: 'mcp';
104
+ readonly tools: readonly McpSurfaceMapTool[];
105
+ }
106
+
107
+ const asJson = (value: unknown): string =>
108
+ `${JSON.stringify(value, null, 2)}\n`;
109
+
110
+ const projectSurfaceMapTool = (tool: McpToolDefinition): McpSurfaceMapTool => ({
111
+ annotations: tool.annotations,
112
+ description: tool.description,
113
+ inputSchema: tool.inputSchema,
114
+ name: tool.name,
115
+ ...(tool.facetId === undefined ? {} : { facetId: tool.facetId }),
116
+ ...(tool.memberTrailIds === undefined
117
+ ? {}
118
+ : { memberTrailIds: tool.memberTrailIds }),
119
+ ...(tool.outputSchema === undefined
120
+ ? {}
121
+ : { outputSchema: tool.outputSchema }),
122
+ ...(tool.trailId === undefined ? {} : { trailId: tool.trailId }),
123
+ ...(tool.versions === undefined ? {} : { versions: tool.versions }),
124
+ ...(tool._meta?.[MCP_TOOL_DEFERRED_META_KEY] === true
125
+ ? { deferred: true }
126
+ : {}),
127
+ });
128
+
129
+ const buildSurfaceMap = (
130
+ tools: readonly McpToolDefinition[]
131
+ ): McpSurfaceMap => ({
132
+ surface: 'mcp',
133
+ tools: tools.map(projectSurfaceMapTool),
134
+ });
135
+
136
+ const exposedTrailIds = (
137
+ tools: readonly McpToolDefinition[]
138
+ ): ReadonlySet<string> =>
139
+ new Set(
140
+ tools.flatMap((tool) => [
141
+ ...(tool.trailId === undefined ? [] : [tool.trailId]),
142
+ ...(tool.memberTrailIds ?? []),
143
+ ])
144
+ );
145
+
146
+ const examplesUriForTrail = (trailId: string): string =>
147
+ `${MCP_EXAMPLES_RESOURCE_PREFIX}${encodeURIComponent(trailId)}`;
148
+
149
+ const trailUriForTrail = (trailId: string): string =>
150
+ `${MCP_TRAIL_RESOURCE_PREFIX}${encodeURIComponent(trailId)}`;
151
+
152
+ const buildExampleResource = (
153
+ trailItem: Trail<unknown, unknown, unknown>
154
+ ):
155
+ | {
156
+ readonly content: McpResourceContent;
157
+ readonly listing: McpResourceDefinition;
158
+ }
159
+ | undefined => {
160
+ const examples = deriveStructuredTrailExamples(trailItem.examples);
161
+ if (examples === undefined) {
162
+ return undefined;
163
+ }
164
+
165
+ const uri = examplesUriForTrail(trailItem.id);
166
+ return {
167
+ content: {
168
+ mimeType: 'application/json',
169
+ text: asJson({
170
+ examples,
171
+ trailId: trailItem.id,
172
+ }),
173
+ uri,
174
+ },
175
+ listing: {
176
+ description: `Structured examples for trail "${trailItem.id}".`,
177
+ mimeType: 'application/json',
178
+ name: `Trail examples: ${trailItem.id}`,
179
+ uri,
180
+ },
181
+ };
182
+ };
183
+
184
+ const buildExampleResources = (
185
+ graph: Topo,
186
+ tools: readonly McpToolDefinition[]
187
+ ): readonly {
188
+ readonly content: McpResourceContent;
189
+ readonly listing: McpResourceDefinition;
190
+ }[] => {
191
+ const visibleTrailIds = exposedTrailIds(tools);
192
+ return graph
193
+ .list()
194
+ .filter((trailItem) => visibleTrailIds.has(trailItem.id))
195
+ .map((trailItem) =>
196
+ buildExampleResource(trailItem as Trail<unknown, unknown, unknown>)
197
+ )
198
+ .filter((resource) => resource !== undefined);
199
+ };
200
+
201
+ const resourceId = (
202
+ resource: Trail<unknown, unknown, unknown>['resources'][number]
203
+ ) => resource.id;
204
+
205
+ const buildTrailGraphResource = (
206
+ trailItem: Trail<unknown, unknown, unknown>,
207
+ tools: readonly McpToolDefinition[]
208
+ ): {
209
+ readonly content: McpResourceContent;
210
+ readonly listing: McpResourceDefinition;
211
+ } => {
212
+ const uri = trailUriForTrail(trailItem.id);
213
+ const surfaceTools = tools
214
+ .filter(
215
+ (tool) =>
216
+ tool.trailId === trailItem.id ||
217
+ tool.memberTrailIds?.includes(trailItem.id) === true
218
+ )
219
+ .map(projectSurfaceMapTool);
220
+ const payload: McpTrailResource = {
221
+ composes: trailItem.composes,
222
+ ...(trailItem.description === undefined
223
+ ? {}
224
+ : { description: trailItem.description }),
225
+ intent: trailItem.intent,
226
+ resources: trailItem.resources.map(resourceId),
227
+ signals: {
228
+ fires: trailItem.fires,
229
+ on: trailItem.on,
230
+ },
231
+ surface: 'mcp',
232
+ tools: surfaceTools,
233
+ trailId: trailItem.id,
234
+ visibility: trailItem.visibility,
235
+ };
236
+
237
+ return {
238
+ content: {
239
+ mimeType: 'application/json',
240
+ text: asJson(payload),
241
+ uri,
242
+ },
243
+ listing: {
244
+ description: `MCP-visible graph facts for trail "${trailItem.id}".`,
245
+ mimeType: 'application/json',
246
+ name: `Trail graph fact: ${trailItem.id}`,
247
+ uri,
248
+ },
249
+ };
250
+ };
251
+
252
+ const buildTrailGraphResources = (
253
+ graph: Topo,
254
+ tools: readonly McpToolDefinition[]
255
+ ): readonly {
256
+ readonly content: McpResourceContent;
257
+ readonly listing: McpResourceDefinition;
258
+ }[] => {
259
+ const visibleTrailIds = exposedTrailIds(tools);
260
+ return graph
261
+ .list()
262
+ .filter((trailItem) => visibleTrailIds.has(trailItem.id))
263
+ .map((trailItem) =>
264
+ buildTrailGraphResource(
265
+ trailItem as Trail<unknown, unknown, unknown>,
266
+ tools
267
+ )
268
+ );
269
+ };
270
+
271
+ /**
272
+ * Build the cold-context MCP resources for a Trails graph and tool set.
273
+ *
274
+ * @example
275
+ * ```ts
276
+ * import { buildMcpResources, deriveMcpTools } from '@ontrails/mcp';
277
+ *
278
+ * const tools = deriveMcpTools(app).value;
279
+ * const resources = buildMcpResources(app, tools);
280
+ * ```
281
+ */
282
+ export const buildMcpResources = (
283
+ graph: Topo,
284
+ tools: readonly McpToolDefinition[],
285
+ config: McpResourcesConfig = {}
286
+ ): BuiltMcpResources => {
287
+ const listings: McpResourceDefinition[] = [];
288
+ const contents = new Map<string, McpResourceContent>();
289
+
290
+ if (config.surfaceMap !== false) {
291
+ const surfaceMapListing = {
292
+ description: 'Resolved MCP surface projection for this Trails app.',
293
+ mimeType: 'application/json',
294
+ name: 'Trails MCP surface map',
295
+ uri: MCP_SURFACE_MAP_RESOURCE_URI,
296
+ };
297
+ listings.push(surfaceMapListing);
298
+ contents.set(MCP_SURFACE_MAP_RESOURCE_URI, {
299
+ mimeType: 'application/json',
300
+ text: asJson(buildSurfaceMap(tools)),
301
+ uri: MCP_SURFACE_MAP_RESOURCE_URI,
302
+ });
303
+ }
304
+
305
+ if (config.examples !== false) {
306
+ for (const resource of buildExampleResources(graph, tools)) {
307
+ listings.push(resource.listing);
308
+ contents.set(resource.content.uri, resource.content);
309
+ }
310
+ }
311
+
312
+ if (config.graph === true) {
313
+ for (const resource of buildTrailGraphResources(graph, tools)) {
314
+ listings.push(resource.listing);
315
+ contents.set(resource.content.uri, resource.content);
316
+ }
317
+ }
318
+
319
+ return {
320
+ list: listings,
321
+ read: (uri) => contents.get(uri),
322
+ };
323
+ };
324
+
325
+ /**
326
+ * Return whether an MCP tool was projected from a surface facet.
327
+ *
328
+ * @example
329
+ * ```ts
330
+ * import { isMcpFacetTool } from '@ontrails/mcp';
331
+ *
332
+ * const facetTools = tools.filter(isMcpFacetTool);
333
+ * ```
334
+ */
335
+ export const isMcpFacetTool = (tool: McpToolDefinition): boolean =>
336
+ tool._meta?.[MCP_TOOL_FACET_META_KEY] !== undefined;
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 blaze().
5
+ * (SSE, streamable HTTP) without changing surface().
6
6
  */
7
7
 
8
8
  import type { Server } from '@modelcontextprotocol/sdk/server/index.js';
@@ -10,6 +10,14 @@ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
10
10
 
11
11
  /**
12
12
  * Connect an MCP server to stdio transport.
13
+ *
14
+ * @example
15
+ * ```ts
16
+ * import { connectStdio, createServer } from '@ontrails/mcp';
17
+ *
18
+ * const server = createServer(graph);
19
+ * await connectStdio(server);
20
+ * ```
13
21
  */
14
22
  export const connectStdio = async (server: Server): Promise<void> => {
15
23
  const transport = new StdioServerTransport();