@ontrails/mcp 1.0.0-beta.2 → 1.0.0-beta.21
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/CHANGELOG.md +300 -5
- package/README.md +70 -21
- package/package.json +11 -3
- package/src/annotations.ts +32 -9
- package/src/build.ts +1236 -137
- package/src/index.ts +31 -4
- package/src/progress.ts +22 -0
- package/src/resources.ts +228 -0
- package/src/stdio.ts +9 -1
- package/src/surface.ts +279 -0
- package/.turbo/turbo-build.log +0 -1
- package/.turbo/turbo-lint.log +0 -3
- package/.turbo/turbo-typecheck.log +0 -1
- package/dist/annotations.d.ts +0 -19
- package/dist/annotations.d.ts.map +0 -1
- package/dist/annotations.js +0 -29
- package/dist/annotations.js.map +0 -1
- package/dist/blaze.d.ts +0 -36
- package/dist/blaze.d.ts.map +0 -1
- package/dist/blaze.js +0 -96
- package/dist/blaze.js.map +0 -1
- package/dist/build.d.ts +0 -40
- package/dist/build.d.ts.map +0 -1
- package/dist/build.js +0 -190
- package/dist/build.js.map +0 -1
- package/dist/index.d.ts +0 -7
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -13
- package/dist/index.js.map +0 -1
- package/dist/progress.d.ts +0 -13
- package/dist/progress.d.ts.map +0 -1
- package/dist/progress.js +0 -51
- package/dist/progress.js.map +0 -1
- package/dist/stdio.d.ts +0 -12
- package/dist/stdio.d.ts.map +0 -1
- package/dist/stdio.js +0 -15
- package/dist/stdio.js.map +0 -1
- package/dist/tool-name.d.ts +0 -15
- package/dist/tool-name.d.ts.map +0 -1
- package/dist/tool-name.js +0 -19
- package/dist/tool-name.js.map +0 -1
- package/src/__tests__/annotations.test.ts +0 -70
- package/src/__tests__/blaze.test.ts +0 -105
- package/src/__tests__/build.test.ts +0 -377
- package/src/__tests__/progress.test.ts +0 -136
- package/src/__tests__/tool-name.test.ts +0 -46
- package/src/blaze.ts +0 -146
- package/tsconfig.json +0 -9
- package/tsconfig.tsbuildinfo +0 -1
package/src/index.ts
CHANGED
|
@@ -1,13 +1,35 @@
|
|
|
1
1
|
// Build
|
|
2
2
|
export {
|
|
3
|
-
|
|
4
|
-
|
|
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
|
+
buildMcpResources,
|
|
26
|
+
isMcpFacetTool,
|
|
27
|
+
type BuiltMcpResources,
|
|
28
|
+
type McpResourceContent,
|
|
29
|
+
type McpResourceDefinition,
|
|
30
|
+
type McpResourcesConfig,
|
|
31
|
+
} from './resources.js';
|
|
32
|
+
|
|
11
33
|
// Tool naming
|
|
12
34
|
export { deriveToolName } from './tool-name.js';
|
|
13
35
|
|
|
@@ -17,8 +39,13 @@ export { deriveAnnotations, type McpAnnotations } from './annotations.js';
|
|
|
17
39
|
// Progress
|
|
18
40
|
export { createMcpProgressCallback } from './progress.js';
|
|
19
41
|
|
|
20
|
-
//
|
|
21
|
-
export {
|
|
42
|
+
// Surface
|
|
43
|
+
export {
|
|
44
|
+
createServer,
|
|
45
|
+
surface,
|
|
46
|
+
type CreateServerOptions,
|
|
47
|
+
type SurfaceMcpResult,
|
|
48
|
+
} from './surface.js';
|
|
22
49
|
|
|
23
50
|
// Transport
|
|
24
51
|
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
|
package/src/resources.ts
ADDED
|
@@ -0,0 +1,228 @@
|
|
|
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
|
+
export interface McpResourceDefinition {
|
|
39
|
+
readonly uri: string;
|
|
40
|
+
readonly mimeType: string;
|
|
41
|
+
readonly name: string;
|
|
42
|
+
readonly description?: string | undefined;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface McpResourceContent {
|
|
46
|
+
readonly uri: string;
|
|
47
|
+
readonly mimeType: string;
|
|
48
|
+
readonly text: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface McpResourcesConfig {
|
|
52
|
+
readonly surfaceMap?: boolean | undefined;
|
|
53
|
+
readonly examples?: boolean | undefined;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface BuiltMcpResources {
|
|
57
|
+
readonly list: readonly McpResourceDefinition[];
|
|
58
|
+
readonly read: (uri: string) => McpResourceContent | undefined;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
interface McpSurfaceMapTool {
|
|
62
|
+
readonly annotations: McpToolDefinition['annotations'];
|
|
63
|
+
readonly description: string | undefined;
|
|
64
|
+
readonly facetId?: string | undefined;
|
|
65
|
+
readonly inputSchema: Record<string, unknown>;
|
|
66
|
+
readonly memberTrailIds?: readonly string[] | undefined;
|
|
67
|
+
readonly name: string;
|
|
68
|
+
readonly outputSchema?: Record<string, unknown> | undefined;
|
|
69
|
+
readonly trailId?: string | undefined;
|
|
70
|
+
readonly versions?: McpToolDefinition['versions'];
|
|
71
|
+
readonly deferred?: true | undefined;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
interface McpSurfaceMap {
|
|
75
|
+
readonly surface: 'mcp';
|
|
76
|
+
readonly tools: readonly McpSurfaceMapTool[];
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const asJson = (value: unknown): string =>
|
|
80
|
+
`${JSON.stringify(value, null, 2)}\n`;
|
|
81
|
+
|
|
82
|
+
const projectSurfaceMapTool = (tool: McpToolDefinition): McpSurfaceMapTool => ({
|
|
83
|
+
annotations: tool.annotations,
|
|
84
|
+
description: tool.description,
|
|
85
|
+
inputSchema: tool.inputSchema,
|
|
86
|
+
name: tool.name,
|
|
87
|
+
...(tool.facetId === undefined ? {} : { facetId: tool.facetId }),
|
|
88
|
+
...(tool.memberTrailIds === undefined
|
|
89
|
+
? {}
|
|
90
|
+
: { memberTrailIds: tool.memberTrailIds }),
|
|
91
|
+
...(tool.outputSchema === undefined
|
|
92
|
+
? {}
|
|
93
|
+
: { outputSchema: tool.outputSchema }),
|
|
94
|
+
...(tool.trailId === undefined ? {} : { trailId: tool.trailId }),
|
|
95
|
+
...(tool.versions === undefined ? {} : { versions: tool.versions }),
|
|
96
|
+
...(tool._meta?.[MCP_TOOL_DEFERRED_META_KEY] === true
|
|
97
|
+
? { deferred: true }
|
|
98
|
+
: {}),
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
const buildSurfaceMap = (
|
|
102
|
+
tools: readonly McpToolDefinition[]
|
|
103
|
+
): McpSurfaceMap => ({
|
|
104
|
+
surface: 'mcp',
|
|
105
|
+
tools: tools.map(projectSurfaceMapTool),
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
const exposedTrailIds = (
|
|
109
|
+
tools: readonly McpToolDefinition[]
|
|
110
|
+
): ReadonlySet<string> =>
|
|
111
|
+
new Set(
|
|
112
|
+
tools.flatMap((tool) => [
|
|
113
|
+
...(tool.trailId === undefined ? [] : [tool.trailId]),
|
|
114
|
+
...(tool.memberTrailIds ?? []),
|
|
115
|
+
])
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
const examplesUriForTrail = (trailId: string): string =>
|
|
119
|
+
`${MCP_EXAMPLES_RESOURCE_PREFIX}${encodeURIComponent(trailId)}`;
|
|
120
|
+
|
|
121
|
+
const buildExampleResource = (
|
|
122
|
+
trailItem: Trail<unknown, unknown, unknown>
|
|
123
|
+
):
|
|
124
|
+
| {
|
|
125
|
+
readonly content: McpResourceContent;
|
|
126
|
+
readonly listing: McpResourceDefinition;
|
|
127
|
+
}
|
|
128
|
+
| undefined => {
|
|
129
|
+
const examples = deriveStructuredTrailExamples(trailItem.examples);
|
|
130
|
+
if (examples === undefined) {
|
|
131
|
+
return undefined;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const uri = examplesUriForTrail(trailItem.id);
|
|
135
|
+
return {
|
|
136
|
+
content: {
|
|
137
|
+
mimeType: 'application/json',
|
|
138
|
+
text: asJson({
|
|
139
|
+
examples,
|
|
140
|
+
trailId: trailItem.id,
|
|
141
|
+
}),
|
|
142
|
+
uri,
|
|
143
|
+
},
|
|
144
|
+
listing: {
|
|
145
|
+
description: `Structured examples for trail "${trailItem.id}".`,
|
|
146
|
+
mimeType: 'application/json',
|
|
147
|
+
name: `Trail examples: ${trailItem.id}`,
|
|
148
|
+
uri,
|
|
149
|
+
},
|
|
150
|
+
};
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
const buildExampleResources = (
|
|
154
|
+
graph: Topo,
|
|
155
|
+
tools: readonly McpToolDefinition[]
|
|
156
|
+
): readonly {
|
|
157
|
+
readonly content: McpResourceContent;
|
|
158
|
+
readonly listing: McpResourceDefinition;
|
|
159
|
+
}[] => {
|
|
160
|
+
const visibleTrailIds = exposedTrailIds(tools);
|
|
161
|
+
return graph
|
|
162
|
+
.list()
|
|
163
|
+
.filter((trailItem) => visibleTrailIds.has(trailItem.id))
|
|
164
|
+
.map((trailItem) =>
|
|
165
|
+
buildExampleResource(trailItem as Trail<unknown, unknown, unknown>)
|
|
166
|
+
)
|
|
167
|
+
.filter((resource) => resource !== undefined);
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Build the cold-context MCP resources for a Trails graph and tool set.
|
|
172
|
+
*
|
|
173
|
+
* @example
|
|
174
|
+
* ```ts
|
|
175
|
+
* import { buildMcpResources, deriveMcpTools } from '@ontrails/mcp';
|
|
176
|
+
*
|
|
177
|
+
* const tools = deriveMcpTools(app).value;
|
|
178
|
+
* const resources = buildMcpResources(app, tools);
|
|
179
|
+
* ```
|
|
180
|
+
*/
|
|
181
|
+
export const buildMcpResources = (
|
|
182
|
+
graph: Topo,
|
|
183
|
+
tools: readonly McpToolDefinition[],
|
|
184
|
+
config: McpResourcesConfig = {}
|
|
185
|
+
): BuiltMcpResources => {
|
|
186
|
+
const listings: McpResourceDefinition[] = [];
|
|
187
|
+
const contents = new Map<string, McpResourceContent>();
|
|
188
|
+
|
|
189
|
+
if (config.surfaceMap !== false) {
|
|
190
|
+
const surfaceMapListing = {
|
|
191
|
+
description: 'Resolved MCP surface projection for this Trails app.',
|
|
192
|
+
mimeType: 'application/json',
|
|
193
|
+
name: 'Trails MCP surface map',
|
|
194
|
+
uri: MCP_SURFACE_MAP_RESOURCE_URI,
|
|
195
|
+
};
|
|
196
|
+
listings.push(surfaceMapListing);
|
|
197
|
+
contents.set(MCP_SURFACE_MAP_RESOURCE_URI, {
|
|
198
|
+
mimeType: 'application/json',
|
|
199
|
+
text: asJson(buildSurfaceMap(tools)),
|
|
200
|
+
uri: MCP_SURFACE_MAP_RESOURCE_URI,
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (config.examples !== false) {
|
|
205
|
+
for (const resource of buildExampleResources(graph, tools)) {
|
|
206
|
+
listings.push(resource.listing);
|
|
207
|
+
contents.set(resource.content.uri, resource.content);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return {
|
|
212
|
+
list: listings,
|
|
213
|
+
read: (uri) => contents.get(uri),
|
|
214
|
+
};
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Return whether an MCP tool was projected from a surface facet.
|
|
219
|
+
*
|
|
220
|
+
* @example
|
|
221
|
+
* ```ts
|
|
222
|
+
* import { isMcpFacetTool } from '@ontrails/mcp';
|
|
223
|
+
*
|
|
224
|
+
* const facetTools = tools.filter(isMcpFacetTool);
|
|
225
|
+
* ```
|
|
226
|
+
*/
|
|
227
|
+
export const isMcpFacetTool = (tool: McpToolDefinition): boolean =>
|
|
228
|
+
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
|
|
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();
|
package/src/surface.ts
ADDED
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Surface helpers for exposing a topo over MCP.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
6
|
+
import {
|
|
7
|
+
CallToolRequestSchema,
|
|
8
|
+
ListResourcesRequestSchema,
|
|
9
|
+
ListToolsRequestSchema,
|
|
10
|
+
ReadResourceRequestSchema,
|
|
11
|
+
} from '@modelcontextprotocol/sdk/types.js';
|
|
12
|
+
import type {
|
|
13
|
+
BaseSurfaceOptions,
|
|
14
|
+
Layer,
|
|
15
|
+
ResourceOverrideMap,
|
|
16
|
+
Topo,
|
|
17
|
+
TrailContextInit,
|
|
18
|
+
} from '@ontrails/core';
|
|
19
|
+
|
|
20
|
+
import type {
|
|
21
|
+
McpSurfaceFacetMap,
|
|
22
|
+
McpToolDefinition,
|
|
23
|
+
ResolveMcpPermit,
|
|
24
|
+
} from './build.js';
|
|
25
|
+
import { deriveMcpTools } from './build.js';
|
|
26
|
+
import { buildMcpResources } from './resources.js';
|
|
27
|
+
import type { BuiltMcpResources, McpResourcesConfig } from './resources.js';
|
|
28
|
+
import { connectStdio } from './stdio.js';
|
|
29
|
+
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
// Options
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
|
|
34
|
+
export interface CreateServerOptions extends BaseSurfaceOptions {
|
|
35
|
+
readonly createContext?:
|
|
36
|
+
| (() => TrailContextInit | Promise<TrailContextInit>)
|
|
37
|
+
| undefined;
|
|
38
|
+
readonly description?: string | undefined;
|
|
39
|
+
readonly facets?: McpSurfaceFacetMap | undefined;
|
|
40
|
+
readonly layers?: readonly Layer[] | undefined;
|
|
41
|
+
readonly mcpResources?: McpResourcesConfig | false | undefined;
|
|
42
|
+
readonly name?: string | undefined;
|
|
43
|
+
readonly resources?: ResourceOverrideMap | undefined;
|
|
44
|
+
readonly resolvePermit?: ResolveMcpPermit | undefined;
|
|
45
|
+
readonly version?: string | undefined;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface SurfaceMcpResult {
|
|
49
|
+
readonly close: () => Promise<void>;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
// Internal: create MCP server with tool handlers
|
|
54
|
+
// ---------------------------------------------------------------------------
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Create an MCP Server instance and register all tools.
|
|
58
|
+
*
|
|
59
|
+
* When provided, `info.description` is forwarded to the MCP SDK as the
|
|
60
|
+
* server's `instructions` field — the SDK's documented channel for
|
|
61
|
+
* "optional instructions describing how to use the server and its features."
|
|
62
|
+
*/
|
|
63
|
+
const createMcpServer = (
|
|
64
|
+
tools: McpToolDefinition[],
|
|
65
|
+
info: {
|
|
66
|
+
readonly name: string;
|
|
67
|
+
readonly version: string;
|
|
68
|
+
readonly description?: string | undefined;
|
|
69
|
+
},
|
|
70
|
+
mcpResources?: BuiltMcpResources | undefined
|
|
71
|
+
): Server => {
|
|
72
|
+
const server = new Server(
|
|
73
|
+
{ name: info.name, version: info.version },
|
|
74
|
+
{
|
|
75
|
+
capabilities: {
|
|
76
|
+
...(mcpResources === undefined ? {} : { resources: {} }),
|
|
77
|
+
tools: {},
|
|
78
|
+
},
|
|
79
|
+
...(info.description === undefined
|
|
80
|
+
? {}
|
|
81
|
+
: { instructions: info.description }),
|
|
82
|
+
}
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
// Build a lookup map for tool dispatch
|
|
86
|
+
const toolMap = new Map<string, McpToolDefinition>();
|
|
87
|
+
for (const tool of tools) {
|
|
88
|
+
toolMap.set(tool.name, tool);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Register tools/list handler
|
|
92
|
+
// oxlint-disable-next-line require-await -- MCP SDK requires async handler
|
|
93
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
94
|
+
tools: tools.map((t) => ({
|
|
95
|
+
_meta: t._meta,
|
|
96
|
+
annotations: t.annotations,
|
|
97
|
+
description: t.description,
|
|
98
|
+
inputSchema: t.inputSchema,
|
|
99
|
+
name: t.name,
|
|
100
|
+
outputSchema: t.outputSchema,
|
|
101
|
+
})),
|
|
102
|
+
}));
|
|
103
|
+
|
|
104
|
+
// Register tools/call handler
|
|
105
|
+
server.setRequestHandler(
|
|
106
|
+
CallToolRequestSchema,
|
|
107
|
+
async (request, requestExtra) => {
|
|
108
|
+
const tool = toolMap.get(request.params.name);
|
|
109
|
+
if (tool === undefined) {
|
|
110
|
+
return {
|
|
111
|
+
content: [
|
|
112
|
+
{
|
|
113
|
+
text: `Unknown tool: ${request.params.name}`,
|
|
114
|
+
type: 'text' as const,
|
|
115
|
+
},
|
|
116
|
+
],
|
|
117
|
+
isError: true,
|
|
118
|
+
} as Record<string, unknown>;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const args = (request.params.arguments ?? {}) as Record<string, unknown>;
|
|
122
|
+
const progressToken = request.params._meta?.progressToken;
|
|
123
|
+
const { authInfo } = requestExtra as {
|
|
124
|
+
readonly authInfo?:
|
|
125
|
+
| {
|
|
126
|
+
readonly accessToken?: string | undefined;
|
|
127
|
+
readonly sessionId?: string | undefined;
|
|
128
|
+
readonly token?: string | undefined;
|
|
129
|
+
}
|
|
130
|
+
| undefined;
|
|
131
|
+
};
|
|
132
|
+
const authorizationToken = authInfo?.accessToken ?? authInfo?.token;
|
|
133
|
+
|
|
134
|
+
const sendProgress =
|
|
135
|
+
progressToken === undefined
|
|
136
|
+
? undefined
|
|
137
|
+
: async (current: number, total: number) => {
|
|
138
|
+
await server.notification({
|
|
139
|
+
method: 'notifications/progress',
|
|
140
|
+
params: {
|
|
141
|
+
progress: current,
|
|
142
|
+
progressToken,
|
|
143
|
+
total,
|
|
144
|
+
},
|
|
145
|
+
});
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
const extra = {
|
|
149
|
+
abortSignal: requestExtra.signal,
|
|
150
|
+
...(authorizationToken === undefined
|
|
151
|
+
? {}
|
|
152
|
+
: { authorization: `Bearer ${authorizationToken}` }),
|
|
153
|
+
progressToken,
|
|
154
|
+
sendProgress,
|
|
155
|
+
...(authInfo?.sessionId === undefined
|
|
156
|
+
? {}
|
|
157
|
+
: { sessionId: authInfo.sessionId }),
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
const result = await tool.handler(args, extra);
|
|
161
|
+
// Spread to satisfy MCP SDK's index-signature requirement
|
|
162
|
+
return { ...result } as Record<string, unknown>;
|
|
163
|
+
}
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
if (mcpResources !== undefined) {
|
|
167
|
+
// oxlint-disable-next-line require-await -- MCP SDK requires async handler
|
|
168
|
+
server.setRequestHandler(ListResourcesRequestSchema, async () => ({
|
|
169
|
+
resources: mcpResources.list.map((resource) => ({
|
|
170
|
+
description: resource.description,
|
|
171
|
+
mimeType: resource.mimeType,
|
|
172
|
+
name: resource.name,
|
|
173
|
+
uri: resource.uri,
|
|
174
|
+
})),
|
|
175
|
+
}));
|
|
176
|
+
|
|
177
|
+
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
178
|
+
const content = mcpResources.read(request.params.uri);
|
|
179
|
+
return {
|
|
180
|
+
contents: [
|
|
181
|
+
content ?? {
|
|
182
|
+
mimeType: 'text/plain',
|
|
183
|
+
text: `Unknown MCP resource: ${request.params.uri}`,
|
|
184
|
+
uri: request.params.uri,
|
|
185
|
+
},
|
|
186
|
+
],
|
|
187
|
+
};
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return server;
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
// ---------------------------------------------------------------------------
|
|
195
|
+
// createServer
|
|
196
|
+
// ---------------------------------------------------------------------------
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Build MCP tools from a topo and create an MCP server.
|
|
200
|
+
*
|
|
201
|
+
* @remarks This is a host materialization boundary. Derivation failures are
|
|
202
|
+
* thrown for server bootstrap code after `deriveMcpTools` has already
|
|
203
|
+
* represented the framework error as a Result.
|
|
204
|
+
*
|
|
205
|
+
* @example
|
|
206
|
+
* ```ts
|
|
207
|
+
* import { connectStdio, createServer } from '@ontrails/mcp';
|
|
208
|
+
*
|
|
209
|
+
* const server = createServer(graph, { name: 'demo' });
|
|
210
|
+
* await connectStdio(server);
|
|
211
|
+
* ```
|
|
212
|
+
*/
|
|
213
|
+
export const createServer = (
|
|
214
|
+
graph: Topo,
|
|
215
|
+
options: CreateServerOptions = {}
|
|
216
|
+
): Server => {
|
|
217
|
+
const toolsResult = deriveMcpTools(graph, {
|
|
218
|
+
configValues: options.configValues,
|
|
219
|
+
createContext: options.createContext,
|
|
220
|
+
exclude: options.exclude,
|
|
221
|
+
facets: options.facets,
|
|
222
|
+
include: options.include,
|
|
223
|
+
intent: options.intent,
|
|
224
|
+
layers: options.layers,
|
|
225
|
+
resolvePermit: options.resolvePermit,
|
|
226
|
+
resources: options.resources,
|
|
227
|
+
validate: options.validate,
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
if (toolsResult.isErr()) {
|
|
231
|
+
throw toolsResult.error;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const mcpResources =
|
|
235
|
+
options.mcpResources === false
|
|
236
|
+
? undefined
|
|
237
|
+
: buildMcpResources(graph, toolsResult.value, options.mcpResources);
|
|
238
|
+
|
|
239
|
+
return createMcpServer(
|
|
240
|
+
toolsResult.value,
|
|
241
|
+
{
|
|
242
|
+
description: options.description ?? graph.description,
|
|
243
|
+
name: options.name ?? graph.name,
|
|
244
|
+
version: options.version ?? graph.version ?? '0.1.0',
|
|
245
|
+
},
|
|
246
|
+
mcpResources
|
|
247
|
+
);
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
// ---------------------------------------------------------------------------
|
|
251
|
+
// surface
|
|
252
|
+
// ---------------------------------------------------------------------------
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Build MCP tools from a topo, create a server, and connect via stdio.
|
|
256
|
+
*
|
|
257
|
+
* @remarks Opens the MCP server on stdio. For custom transports, use
|
|
258
|
+
* `createServer(graph)` with `connectStdio` or your own adapter.
|
|
259
|
+
*
|
|
260
|
+
* @example
|
|
261
|
+
* ```ts
|
|
262
|
+
* import { surface } from '@ontrails/mcp';
|
|
263
|
+
*
|
|
264
|
+
* await surface(graph, { name: 'demo' });
|
|
265
|
+
* ```
|
|
266
|
+
*/
|
|
267
|
+
export const surface = async (
|
|
268
|
+
graph: Topo,
|
|
269
|
+
options: CreateServerOptions = {}
|
|
270
|
+
): Promise<SurfaceMcpResult> => {
|
|
271
|
+
const server = createServer(graph, options);
|
|
272
|
+
await connectStdio(server);
|
|
273
|
+
|
|
274
|
+
return {
|
|
275
|
+
close: async () => {
|
|
276
|
+
await server.close();
|
|
277
|
+
},
|
|
278
|
+
};
|
|
279
|
+
};
|
package/.turbo/turbo-build.log
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
$ tsc -b
|
package/.turbo/turbo-lint.log
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
$ tsc --noEmit
|
package/dist/annotations.d.ts
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Derive MCP tool annotations from trail spec markers.
|
|
3
|
-
*/
|
|
4
|
-
import type { Trail } from '@ontrails/core';
|
|
5
|
-
export interface McpAnnotations {
|
|
6
|
-
readonly readOnlyHint?: boolean | undefined;
|
|
7
|
-
readonly destructiveHint?: boolean | undefined;
|
|
8
|
-
readonly idempotentHint?: boolean | undefined;
|
|
9
|
-
readonly openWorldHint?: boolean | undefined;
|
|
10
|
-
readonly title?: string | undefined;
|
|
11
|
-
}
|
|
12
|
-
/**
|
|
13
|
-
* Map trail spec fields to MCP tool annotations.
|
|
14
|
-
*
|
|
15
|
-
* Only sets hints that are explicitly declared on the trail.
|
|
16
|
-
* Omitted hints let the MCP SDK use its defaults.
|
|
17
|
-
*/
|
|
18
|
-
export declare const deriveAnnotations: (trail: Pick<Trail<unknown, unknown>, "readOnly" | "destructive" | "idempotent" | "description">) => McpAnnotations;
|
|
19
|
-
//# sourceMappingURL=annotations.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"annotations.d.ts","sourceRoot":"","sources":["../src/annotations.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAM5C,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,YAAY,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC5C,QAAQ,CAAC,eAAe,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC/C,QAAQ,CAAC,cAAc,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC9C,QAAQ,CAAC,aAAa,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC7C,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACrC;AAMD;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,GAC5B,OAAO,IAAI,CACT,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,EACvB,UAAU,GAAG,aAAa,GAAG,YAAY,GAAG,aAAa,CAC1D,KACA,cAiBF,CAAC"}
|
package/dist/annotations.js
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Derive MCP tool annotations from trail spec markers.
|
|
3
|
-
*/
|
|
4
|
-
// ---------------------------------------------------------------------------
|
|
5
|
-
// Derivation
|
|
6
|
-
// ---------------------------------------------------------------------------
|
|
7
|
-
/**
|
|
8
|
-
* Map trail spec fields to MCP tool annotations.
|
|
9
|
-
*
|
|
10
|
-
* Only sets hints that are explicitly declared on the trail.
|
|
11
|
-
* Omitted hints let the MCP SDK use its defaults.
|
|
12
|
-
*/
|
|
13
|
-
export const deriveAnnotations = (trail) => {
|
|
14
|
-
const annotations = {};
|
|
15
|
-
if (trail.readOnly === true) {
|
|
16
|
-
annotations['readOnlyHint'] = true;
|
|
17
|
-
}
|
|
18
|
-
if (trail.destructive === true) {
|
|
19
|
-
annotations['destructiveHint'] = true;
|
|
20
|
-
}
|
|
21
|
-
if (trail.idempotent === true) {
|
|
22
|
-
annotations['idempotentHint'] = true;
|
|
23
|
-
}
|
|
24
|
-
if (trail.description !== undefined) {
|
|
25
|
-
annotations['title'] = trail.description;
|
|
26
|
-
}
|
|
27
|
-
return annotations;
|
|
28
|
-
};
|
|
29
|
-
//# sourceMappingURL=annotations.js.map
|
package/dist/annotations.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"annotations.js","sourceRoot":"","sources":["../src/annotations.ts"],"names":[],"mappings":"AAAA;;GAEG;AAgBH,8EAA8E;AAC9E,aAAa;AACb,8EAA8E;AAE9E;;;;;GAKG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAC/B,KAGC,EACe,EAAE;IAClB,MAAM,WAAW,GAA4B,EAAE,CAAC;IAEhD,IAAI,KAAK,CAAC,QAAQ,KAAK,IAAI,EAAE,CAAC;QAC5B,WAAW,CAAC,cAAc,CAAC,GAAG,IAAI,CAAC;IACrC,CAAC;IACD,IAAI,KAAK,CAAC,WAAW,KAAK,IAAI,EAAE,CAAC;QAC/B,WAAW,CAAC,iBAAiB,CAAC,GAAG,IAAI,CAAC;IACxC,CAAC;IACD,IAAI,KAAK,CAAC,UAAU,KAAK,IAAI,EAAE,CAAC;QAC9B,WAAW,CAAC,gBAAgB,CAAC,GAAG,IAAI,CAAC;IACvC,CAAC;IACD,IAAI,KAAK,CAAC,WAAW,KAAK,SAAS,EAAE,CAAC;QACpC,WAAW,CAAC,OAAO,CAAC,GAAG,KAAK,CAAC,WAAW,CAAC;IAC3C,CAAC;IAED,OAAO,WAA6B,CAAC;AACvC,CAAC,CAAC"}
|