@ontrails/mcp 1.0.0-beta.19 → 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 +19 -0
- package/README.md +24 -0
- package/package.json +2 -2
- package/src/build.ts +374 -8
- package/src/index.ts +17 -0
- package/src/resources.ts +228 -0
- package/src/surface.ts +57 -8
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# @ontrails/mcp
|
|
2
2
|
|
|
3
|
+
## 1.0.0-beta.21
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies [99523f2]
|
|
8
|
+
- @ontrails/core@1.0.0-beta.21
|
|
9
|
+
|
|
10
|
+
## 1.0.0-beta.20
|
|
11
|
+
|
|
12
|
+
### Minor Changes
|
|
13
|
+
|
|
14
|
+
- accb9ec: Add MCP surface facets, MCP resource projection for cold context, and deferred-loading metadata hints.
|
|
15
|
+
|
|
16
|
+
### Patch Changes
|
|
17
|
+
|
|
18
|
+
- 9bec01c: Document MCP resource projection and deferred-loading options for cold surface context.
|
|
19
|
+
- Updated dependencies [851a2a3]
|
|
20
|
+
- @ontrails/core@1.0.0-beta.20
|
|
21
|
+
|
|
3
22
|
## 1.0.0-beta.19
|
|
4
23
|
|
|
5
24
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -54,6 +54,7 @@ for (const tool of result.value) {
|
|
|
54
54
|
| --- | --- |
|
|
55
55
|
| `surface(graph, options?)` | Start an MCP server with all trails as tools |
|
|
56
56
|
| `deriveMcpTools(graph, options?)` | Build tool definitions without starting a server |
|
|
57
|
+
| `buildMcpResources(graph, tools, config?)` | Build MCP resource listings and read handlers for cold context |
|
|
57
58
|
| `deriveToolName(appName, trailId)` | Compute the MCP tool name from app and trail IDs |
|
|
58
59
|
| `deriveAnnotations(trail)` | Extract MCP annotations from trail intent, idempotency, and description |
|
|
59
60
|
| `createMcpProgressCallback(server)` | Bridge `ctx.progress` to MCP `notifications/progress` |
|
|
@@ -79,6 +80,29 @@ MCP tool definitions include the trail's input schema, and trails with an `outpu
|
|
|
79
80
|
|
|
80
81
|
Trail examples are projected as structured metadata under `_meta["ontrails/examples"]`. Each projected example preserves its input, expected output or error, a success/error kind, and provenance pointing back to the authored `trail.examples` field.
|
|
81
82
|
|
|
83
|
+
## MCP resources and deferred loading
|
|
84
|
+
|
|
85
|
+
Cold context is projected through MCP resources, not extra Trails resources. `surface(graph)` and `createServer(graph)` expose MCP resources by default:
|
|
86
|
+
|
|
87
|
+
- `trails://surface-map` lists the resolved MCP tool projection, including ordinary tools, facet tools, schemas, versions, deferred hints, and member trail IDs.
|
|
88
|
+
- `trails://examples/<trailId>` exposes structured examples for exposed trails that define examples.
|
|
89
|
+
|
|
90
|
+
Disable resource projection only when the host needs a minimal MCP capability surface:
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
await surface(graph, { mcpResources: false });
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Or choose a narrower resource set:
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
await surface(graph, {
|
|
100
|
+
mcpResources: { examples: false, surfaceMap: true },
|
|
101
|
+
});
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Facet definitions may set `mcp: { loading: 'deferred' }`. In this release, deferred loading is a compatibility hint under `_meta["ontrails/deferred"]`; the MCP tool schema remains present so clients that do not understand deferred loading continue to work.
|
|
105
|
+
|
|
82
106
|
## Tool naming
|
|
83
107
|
|
|
84
108
|
Trail IDs become MCP tool names with the app prefix: `entity.show` in app `myapp` becomes `myapp_entity_show`. Dots and hyphens become underscores, everything lowercase.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ontrails/mcp",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.21",
|
|
4
4
|
"files": [
|
|
5
5
|
"src/**/*.ts",
|
|
6
6
|
"!src/**/__tests__/**",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"clean": "rm -rf dist *.tsbuildinfo"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@ontrails/core": "^1.0.0-beta.
|
|
25
|
+
"@ontrails/core": "^1.0.0-beta.21"
|
|
26
26
|
},
|
|
27
27
|
"peerDependencies": {
|
|
28
28
|
"@modelcontextprotocol/sdk": "^1.28.0",
|
package/src/build.ts
CHANGED
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
isBlobRef,
|
|
19
19
|
isTrailsError,
|
|
20
20
|
LAYER_FIELD_RESERVED_NAMES,
|
|
21
|
+
matchesTrailPattern,
|
|
21
22
|
projectLayerFieldName,
|
|
22
23
|
projectPublicSurfaceError,
|
|
23
24
|
toBlobRefDescriptor,
|
|
@@ -69,6 +70,31 @@ export const MCP_TOOL_EXAMPLES_META_KEY = 'ontrails/examples';
|
|
|
69
70
|
*/
|
|
70
71
|
export const MCP_TOOL_ERROR_META_KEY = 'ontrails/error';
|
|
71
72
|
|
|
73
|
+
/**
|
|
74
|
+
* Metadata key used to identify MCP tools derived from surface facets.
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* ```ts
|
|
78
|
+
* import { MCP_TOOL_FACET_META_KEY } from '@ontrails/mcp';
|
|
79
|
+
*
|
|
80
|
+
* const facet = tool._meta?.[MCP_TOOL_FACET_META_KEY];
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
83
|
+
export const MCP_TOOL_FACET_META_KEY = 'ontrails/facet';
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Metadata key used as a compatibility hint for clients that support
|
|
87
|
+
* deferred MCP tool loading.
|
|
88
|
+
*
|
|
89
|
+
* @example
|
|
90
|
+
* ```ts
|
|
91
|
+
* import { MCP_TOOL_DEFERRED_META_KEY } from '@ontrails/mcp';
|
|
92
|
+
*
|
|
93
|
+
* const isDeferred = tool._meta?.[MCP_TOOL_DEFERRED_META_KEY] === true;
|
|
94
|
+
* ```
|
|
95
|
+
*/
|
|
96
|
+
export const MCP_TOOL_DEFERRED_META_KEY = 'ontrails/deferred';
|
|
97
|
+
|
|
72
98
|
// ---------------------------------------------------------------------------
|
|
73
99
|
// Public types
|
|
74
100
|
// ---------------------------------------------------------------------------
|
|
@@ -77,11 +103,31 @@ export interface DeriveMcpToolsOptions extends BaseSurfaceOptions {
|
|
|
77
103
|
readonly createContext?:
|
|
78
104
|
| (() => TrailContextInit | Promise<TrailContextInit>)
|
|
79
105
|
| undefined;
|
|
106
|
+
readonly facets?: McpSurfaceFacetMap | undefined;
|
|
80
107
|
readonly layers?: readonly Layer[] | undefined;
|
|
81
108
|
readonly resources?: ResourceOverrideMap | undefined;
|
|
82
109
|
readonly resolvePermit?: ResolveMcpPermit | undefined;
|
|
83
110
|
}
|
|
84
111
|
|
|
112
|
+
export type McpSurfaceFacetTrailSelector = string | readonly string[];
|
|
113
|
+
|
|
114
|
+
export interface McpSurfaceFacetDefinition {
|
|
115
|
+
readonly trails: McpSurfaceFacetTrailSelector;
|
|
116
|
+
readonly description: string;
|
|
117
|
+
readonly visibility?: 'public' | 'internal' | undefined;
|
|
118
|
+
readonly descriptionStableThrough?: string | undefined;
|
|
119
|
+
readonly visibilityWideningAccepted?: true | undefined;
|
|
120
|
+
readonly mcp?:
|
|
121
|
+
| {
|
|
122
|
+
readonly loading?: 'deferred' | undefined;
|
|
123
|
+
}
|
|
124
|
+
| undefined;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export type McpSurfaceFacetMap = Readonly<
|
|
128
|
+
Record<string, McpSurfaceFacetDefinition>
|
|
129
|
+
>;
|
|
130
|
+
|
|
85
131
|
export interface ResolveMcpPermitInput {
|
|
86
132
|
readonly authorization?: string | undefined;
|
|
87
133
|
readonly bearerToken?: string | undefined;
|
|
@@ -98,15 +144,17 @@ export interface McpToolDefinition {
|
|
|
98
144
|
readonly _meta?: Record<string, unknown> | undefined;
|
|
99
145
|
readonly annotations: McpAnnotations | undefined;
|
|
100
146
|
readonly description: string | undefined;
|
|
147
|
+
readonly facetId?: string | undefined;
|
|
101
148
|
readonly handler: (
|
|
102
149
|
args: Record<string, unknown>,
|
|
103
150
|
extra: McpExtra
|
|
104
151
|
) => Promise<McpToolResult>;
|
|
105
152
|
readonly inputSchema: Record<string, unknown>;
|
|
153
|
+
readonly memberTrailIds?: readonly string[] | undefined;
|
|
106
154
|
readonly name: string;
|
|
107
155
|
readonly outputSchema?: Record<string, unknown> | undefined;
|
|
108
156
|
/** The trail ID this tool was derived from. */
|
|
109
|
-
readonly trailId
|
|
157
|
+
readonly trailId?: string | undefined;
|
|
110
158
|
readonly versions?: readonly SurfaceTrailVersionProjection[] | undefined;
|
|
111
159
|
}
|
|
112
160
|
|
|
@@ -923,6 +971,16 @@ const buildMeta = (
|
|
|
923
971
|
return { [MCP_TOOL_EXAMPLES_META_KEY]: examples };
|
|
924
972
|
};
|
|
925
973
|
|
|
974
|
+
const mergeMeta = (
|
|
975
|
+
...entries: readonly (Record<string, unknown> | undefined)[]
|
|
976
|
+
): Record<string, unknown> | undefined => {
|
|
977
|
+
const merged = Object.assign(
|
|
978
|
+
{},
|
|
979
|
+
...(entries.filter(Boolean) as Record<string, unknown>[])
|
|
980
|
+
);
|
|
981
|
+
return Object.keys(merged).length > 0 ? merged : undefined;
|
|
982
|
+
};
|
|
983
|
+
|
|
926
984
|
/** Build a single MCP tool definition from a trail. */
|
|
927
985
|
const buildToolDefinition = (
|
|
928
986
|
graph: Topo,
|
|
@@ -962,25 +1020,217 @@ const buildToolDefinition = (
|
|
|
962
1020
|
};
|
|
963
1021
|
};
|
|
964
1022
|
|
|
1023
|
+
const facetSelectors = (
|
|
1024
|
+
selector: McpSurfaceFacetTrailSelector
|
|
1025
|
+
): readonly string[] => (typeof selector === 'string' ? [selector] : selector);
|
|
1026
|
+
|
|
1027
|
+
const matchesFacetSelector = (
|
|
1028
|
+
trailId: string,
|
|
1029
|
+
selector: McpSurfaceFacetTrailSelector
|
|
1030
|
+
): boolean =>
|
|
1031
|
+
facetSelectors(selector).some((pattern) =>
|
|
1032
|
+
matchesTrailPattern(trailId, pattern)
|
|
1033
|
+
);
|
|
1034
|
+
|
|
1035
|
+
interface FacetMemberTool {
|
|
1036
|
+
readonly tool: McpToolDefinition;
|
|
1037
|
+
readonly trail: Trail<unknown, unknown, unknown>;
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
|
1041
|
+
value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
1042
|
+
|
|
1043
|
+
const buildFacetInputSchema = (
|
|
1044
|
+
members: readonly FacetMemberTool[]
|
|
1045
|
+
): Record<string, unknown> => ({
|
|
1046
|
+
anyOf: members.map(({ tool, trail }) => ({
|
|
1047
|
+
properties: {
|
|
1048
|
+
input: tool.inputSchema,
|
|
1049
|
+
trail: { const: trail.id },
|
|
1050
|
+
},
|
|
1051
|
+
required: ['trail', 'input'],
|
|
1052
|
+
type: 'object',
|
|
1053
|
+
})),
|
|
1054
|
+
properties: {
|
|
1055
|
+
input: { type: 'object' },
|
|
1056
|
+
trail: {
|
|
1057
|
+
enum: members.map(({ trail }) => trail.id),
|
|
1058
|
+
type: 'string',
|
|
1059
|
+
},
|
|
1060
|
+
},
|
|
1061
|
+
required: ['trail', 'input'],
|
|
1062
|
+
type: 'object',
|
|
1063
|
+
});
|
|
1064
|
+
|
|
1065
|
+
const buildFacetOutputSchema = (
|
|
1066
|
+
members: readonly FacetMemberTool[]
|
|
1067
|
+
): Record<string, unknown> => {
|
|
1068
|
+
const outputSchemas = members.map(({ tool }) => tool.outputSchema ?? {});
|
|
1069
|
+
return {
|
|
1070
|
+
properties: {
|
|
1071
|
+
output:
|
|
1072
|
+
outputSchemas.length === 1
|
|
1073
|
+
? (outputSchemas[0] ?? {})
|
|
1074
|
+
: { anyOf: outputSchemas },
|
|
1075
|
+
trail: {
|
|
1076
|
+
enum: members.map(({ trail }) => trail.id),
|
|
1077
|
+
type: 'string',
|
|
1078
|
+
},
|
|
1079
|
+
},
|
|
1080
|
+
required: ['trail', 'output'],
|
|
1081
|
+
type: 'object',
|
|
1082
|
+
};
|
|
1083
|
+
};
|
|
1084
|
+
|
|
1085
|
+
const parseJsonTextContent = (
|
|
1086
|
+
content: readonly McpContent[]
|
|
1087
|
+
): unknown | undefined => {
|
|
1088
|
+
const text = content.find((item) => item.type === 'text')?.text;
|
|
1089
|
+
if (text === undefined) {
|
|
1090
|
+
return undefined;
|
|
1091
|
+
}
|
|
1092
|
+
try {
|
|
1093
|
+
return JSON.parse(text) as unknown;
|
|
1094
|
+
} catch {
|
|
1095
|
+
return undefined;
|
|
1096
|
+
}
|
|
1097
|
+
};
|
|
1098
|
+
|
|
1099
|
+
const wrapFacetResult = (
|
|
1100
|
+
trailId: string,
|
|
1101
|
+
result: McpToolResult
|
|
1102
|
+
): McpToolResult => {
|
|
1103
|
+
if (result.isError === true) {
|
|
1104
|
+
return result;
|
|
1105
|
+
}
|
|
1106
|
+
const output =
|
|
1107
|
+
result.structuredContent ?? parseJsonTextContent(result.content) ?? null;
|
|
1108
|
+
const envelope = { output, trail: trailId };
|
|
1109
|
+
return {
|
|
1110
|
+
...(result._meta === undefined ? {} : { _meta: result._meta }),
|
|
1111
|
+
content: [
|
|
1112
|
+
{ text: JSON.stringify(envelope), type: 'text' },
|
|
1113
|
+
...result.content.filter((item) => item.type !== 'text'),
|
|
1114
|
+
],
|
|
1115
|
+
structuredContent: envelope,
|
|
1116
|
+
};
|
|
1117
|
+
};
|
|
1118
|
+
|
|
1119
|
+
const createFacetHandler = (
|
|
1120
|
+
facetId: string,
|
|
1121
|
+
members: readonly FacetMemberTool[]
|
|
1122
|
+
): McpToolDefinition['handler'] => {
|
|
1123
|
+
const byTrailId = new Map(
|
|
1124
|
+
members.map((member) => [member.trail.id, member.tool])
|
|
1125
|
+
);
|
|
1126
|
+
|
|
1127
|
+
return async (args, extra): Promise<McpToolResult> => {
|
|
1128
|
+
const trailId = typeof args['trail'] === 'string' ? args['trail'] : '';
|
|
1129
|
+
const tool = byTrailId.get(trailId);
|
|
1130
|
+
if (tool === undefined) {
|
|
1131
|
+
return mcpError(
|
|
1132
|
+
new ValidationError(
|
|
1133
|
+
`MCP facet "${facetId}" received unknown trail selector "${trailId || '(missing)'}"`
|
|
1134
|
+
)
|
|
1135
|
+
);
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
const { input } = args;
|
|
1139
|
+
if (!isRecord(input)) {
|
|
1140
|
+
return mcpError(
|
|
1141
|
+
new ValidationError(
|
|
1142
|
+
`MCP facet "${facetId}" expects an object input for trail "${trailId}"`
|
|
1143
|
+
)
|
|
1144
|
+
);
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
return wrapFacetResult(trailId, await tool.handler(input, extra));
|
|
1148
|
+
};
|
|
1149
|
+
};
|
|
1150
|
+
|
|
1151
|
+
const deriveFacetIntent = (
|
|
1152
|
+
members: readonly FacetMemberTool[]
|
|
1153
|
+
): Pick<Trail<unknown, unknown, unknown>, 'intent'>['intent'] => {
|
|
1154
|
+
if (members.every(({ trail }) => trail.intent === 'read')) {
|
|
1155
|
+
return 'read';
|
|
1156
|
+
}
|
|
1157
|
+
if (members.some(({ trail }) => trail.intent === 'destroy')) {
|
|
1158
|
+
return 'destroy';
|
|
1159
|
+
}
|
|
1160
|
+
return 'write';
|
|
1161
|
+
};
|
|
1162
|
+
|
|
1163
|
+
const deriveFacetAnnotations = (
|
|
1164
|
+
definition: McpSurfaceFacetDefinition,
|
|
1165
|
+
members: readonly FacetMemberTool[]
|
|
1166
|
+
): McpAnnotations | undefined => {
|
|
1167
|
+
const annotations = deriveAnnotations({
|
|
1168
|
+
description: definition.description,
|
|
1169
|
+
idempotent: false,
|
|
1170
|
+
intent: deriveFacetIntent(members),
|
|
1171
|
+
} as Pick<
|
|
1172
|
+
Trail<unknown, unknown, unknown>,
|
|
1173
|
+
'description' | 'idempotent' | 'intent'
|
|
1174
|
+
>);
|
|
1175
|
+
return Object.keys(annotations).length > 0 ? annotations : undefined;
|
|
1176
|
+
};
|
|
1177
|
+
|
|
1178
|
+
const buildFacetMeta = (
|
|
1179
|
+
facetId: string,
|
|
1180
|
+
definition: McpSurfaceFacetDefinition,
|
|
1181
|
+
memberTrailIds: readonly string[]
|
|
1182
|
+
): Record<string, unknown> | undefined =>
|
|
1183
|
+
mergeMeta(
|
|
1184
|
+
{
|
|
1185
|
+
[MCP_TOOL_FACET_META_KEY]: {
|
|
1186
|
+
id: facetId,
|
|
1187
|
+
memberTrailIds,
|
|
1188
|
+
},
|
|
1189
|
+
},
|
|
1190
|
+
definition.mcp?.loading === 'deferred'
|
|
1191
|
+
? { [MCP_TOOL_DEFERRED_META_KEY]: true }
|
|
1192
|
+
: undefined
|
|
1193
|
+
);
|
|
1194
|
+
|
|
1195
|
+
const buildFacetToolDefinition = (
|
|
1196
|
+
graph: Topo,
|
|
1197
|
+
facetId: string,
|
|
1198
|
+
definition: McpSurfaceFacetDefinition,
|
|
1199
|
+
members: readonly FacetMemberTool[]
|
|
1200
|
+
): McpToolDefinition => {
|
|
1201
|
+
const memberTrailIds = members.map(({ trail }) => trail.id);
|
|
1202
|
+
return {
|
|
1203
|
+
_meta: buildFacetMeta(facetId, definition, memberTrailIds),
|
|
1204
|
+
annotations: deriveFacetAnnotations(definition, members),
|
|
1205
|
+
description: definition.description,
|
|
1206
|
+
facetId,
|
|
1207
|
+
handler: createFacetHandler(facetId, members),
|
|
1208
|
+
inputSchema: buildFacetInputSchema(members),
|
|
1209
|
+
memberTrailIds,
|
|
1210
|
+
name: deriveToolName(graph.name, facetId),
|
|
1211
|
+
outputSchema: buildFacetOutputSchema(members),
|
|
1212
|
+
};
|
|
1213
|
+
};
|
|
1214
|
+
|
|
965
1215
|
/** Register a trail as an MCP tool, checking for name collisions. */
|
|
966
1216
|
const registerTool = (
|
|
967
1217
|
graph: Topo,
|
|
968
1218
|
trailItem: Trail<unknown, unknown, unknown>,
|
|
969
1219
|
layers: readonly Layer[],
|
|
970
1220
|
options: DeriveMcpToolsOptions,
|
|
971
|
-
|
|
1221
|
+
nameToSourceId: Map<string, string>,
|
|
972
1222
|
tools: McpToolDefinition[]
|
|
973
1223
|
): Result<void, Error> => {
|
|
974
1224
|
const toolName = deriveToolName(graph.name, trailItem.id);
|
|
975
|
-
const existingId =
|
|
1225
|
+
const existingId = nameToSourceId.get(toolName);
|
|
976
1226
|
if (existingId !== undefined) {
|
|
977
1227
|
return Result.err(
|
|
978
1228
|
new ValidationError(
|
|
979
|
-
`MCP tool-name collision:
|
|
1229
|
+
`MCP tool-name collision: "${existingId}" and "trail:${trailItem.id}" both derive the tool name "${toolName}"`
|
|
980
1230
|
)
|
|
981
1231
|
);
|
|
982
1232
|
}
|
|
983
|
-
|
|
1233
|
+
nameToSourceId.set(toolName, `trail:${trailItem.id}`);
|
|
984
1234
|
tools.push(buildToolDefinition(graph, trailItem, layers, options));
|
|
985
1235
|
return Result.ok();
|
|
986
1236
|
};
|
|
@@ -1001,21 +1251,137 @@ const validateToolBuild = (
|
|
|
1001
1251
|
options: DeriveMcpToolsOptions
|
|
1002
1252
|
): Result<void, Error> => validateSurfaceTopo(graph, options);
|
|
1003
1253
|
|
|
1254
|
+
const collectFacetMembers = (
|
|
1255
|
+
graph: Topo,
|
|
1256
|
+
definition: McpSurfaceFacetDefinition,
|
|
1257
|
+
availableTrails: readonly Trail<unknown, unknown, unknown>[],
|
|
1258
|
+
layers: readonly Layer[],
|
|
1259
|
+
options: DeriveMcpToolsOptions
|
|
1260
|
+
): readonly FacetMemberTool[] =>
|
|
1261
|
+
availableTrails
|
|
1262
|
+
.filter((trailItem) =>
|
|
1263
|
+
matchesFacetSelector(trailItem.id, definition.trails)
|
|
1264
|
+
)
|
|
1265
|
+
.map((trailItem) => ({
|
|
1266
|
+
tool: buildToolDefinition(graph, trailItem, layers, options),
|
|
1267
|
+
trail: trailItem,
|
|
1268
|
+
}));
|
|
1269
|
+
|
|
1270
|
+
const registerFacet = (
|
|
1271
|
+
graph: Topo,
|
|
1272
|
+
facetId: string,
|
|
1273
|
+
definition: McpSurfaceFacetDefinition,
|
|
1274
|
+
members: readonly FacetMemberTool[],
|
|
1275
|
+
nameToSourceId: Map<string, string>,
|
|
1276
|
+
tools: McpToolDefinition[]
|
|
1277
|
+
): Result<void, Error> => {
|
|
1278
|
+
if (members.length === 0) {
|
|
1279
|
+
return Result.err(
|
|
1280
|
+
new ValidationError(
|
|
1281
|
+
`MCP facet "${facetId}" did not match any surface-eligible trails`
|
|
1282
|
+
)
|
|
1283
|
+
);
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1286
|
+
const toolName = deriveToolName(graph.name, facetId);
|
|
1287
|
+
const existingId = nameToSourceId.get(toolName);
|
|
1288
|
+
if (existingId !== undefined) {
|
|
1289
|
+
return Result.err(
|
|
1290
|
+
new ValidationError(
|
|
1291
|
+
`MCP tool-name collision: "${existingId}" and "facet:${facetId}" both derive the tool name "${toolName}"`
|
|
1292
|
+
)
|
|
1293
|
+
);
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
nameToSourceId.set(toolName, `facet:${facetId}`);
|
|
1297
|
+
tools.push(buildFacetToolDefinition(graph, facetId, definition, members));
|
|
1298
|
+
return Result.ok();
|
|
1299
|
+
};
|
|
1300
|
+
|
|
1301
|
+
const registerFacets = (
|
|
1302
|
+
graph: Topo,
|
|
1303
|
+
options: DeriveMcpToolsOptions,
|
|
1304
|
+
layers: readonly Layer[],
|
|
1305
|
+
availableTrails: readonly Trail<unknown, unknown, unknown>[],
|
|
1306
|
+
nameToSourceId: Map<string, string>,
|
|
1307
|
+
tools: McpToolDefinition[]
|
|
1308
|
+
): Result<ReadonlySet<string>, Error> => {
|
|
1309
|
+
const { facets } = options;
|
|
1310
|
+
const consumedTrailIds = new Set<string>();
|
|
1311
|
+
const ownerByTrailId = new Map<string, string>();
|
|
1312
|
+
|
|
1313
|
+
if (facets === undefined || Object.keys(facets).length === 0) {
|
|
1314
|
+
return Result.ok(consumedTrailIds);
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1317
|
+
for (const [facetId, definition] of Object.entries(facets).toSorted()) {
|
|
1318
|
+
const members = collectFacetMembers(
|
|
1319
|
+
graph,
|
|
1320
|
+
definition,
|
|
1321
|
+
availableTrails,
|
|
1322
|
+
layers,
|
|
1323
|
+
options
|
|
1324
|
+
);
|
|
1325
|
+
for (const { trail: memberTrail } of members) {
|
|
1326
|
+
const previous = ownerByTrailId.get(memberTrail.id);
|
|
1327
|
+
if (previous !== undefined) {
|
|
1328
|
+
return Result.err(
|
|
1329
|
+
new ValidationError(
|
|
1330
|
+
`MCP facet overlap: trail "${memberTrail.id}" is selected by facets "${previous}" and "${facetId}"`
|
|
1331
|
+
)
|
|
1332
|
+
);
|
|
1333
|
+
}
|
|
1334
|
+
ownerByTrailId.set(memberTrail.id, facetId);
|
|
1335
|
+
consumedTrailIds.add(memberTrail.id);
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1338
|
+
const registered = registerFacet(
|
|
1339
|
+
graph,
|
|
1340
|
+
facetId,
|
|
1341
|
+
definition,
|
|
1342
|
+
members,
|
|
1343
|
+
nameToSourceId,
|
|
1344
|
+
tools
|
|
1345
|
+
);
|
|
1346
|
+
if (registered.isErr()) {
|
|
1347
|
+
return registered;
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
return Result.ok(consumedTrailIds);
|
|
1352
|
+
};
|
|
1353
|
+
|
|
1004
1354
|
const registerTools = (
|
|
1005
1355
|
graph: Topo,
|
|
1006
1356
|
options: DeriveMcpToolsOptions,
|
|
1007
1357
|
layers: readonly Layer[]
|
|
1008
1358
|
): Result<McpToolDefinition[], Error> => {
|
|
1009
1359
|
const tools: McpToolDefinition[] = [];
|
|
1010
|
-
const
|
|
1360
|
+
const nameToSourceId = new Map<string, string>();
|
|
1361
|
+
const availableTrails = eligibleTrails(graph, options);
|
|
1362
|
+
const registeredFacets = registerFacets(
|
|
1363
|
+
graph,
|
|
1364
|
+
options,
|
|
1365
|
+
layers,
|
|
1366
|
+
availableTrails,
|
|
1367
|
+
nameToSourceId,
|
|
1368
|
+
tools
|
|
1369
|
+
);
|
|
1370
|
+
if (registeredFacets.isErr()) {
|
|
1371
|
+
return registeredFacets;
|
|
1372
|
+
}
|
|
1373
|
+
const consumedTrailIds = registeredFacets.value;
|
|
1011
1374
|
|
|
1012
|
-
for (const trailItem of
|
|
1375
|
+
for (const trailItem of availableTrails) {
|
|
1376
|
+
if (consumedTrailIds.has(trailItem.id)) {
|
|
1377
|
+
continue;
|
|
1378
|
+
}
|
|
1013
1379
|
const registered = registerTool(
|
|
1014
1380
|
graph,
|
|
1015
1381
|
trailItem,
|
|
1016
1382
|
layers,
|
|
1017
1383
|
options,
|
|
1018
|
-
|
|
1384
|
+
nameToSourceId,
|
|
1019
1385
|
tools
|
|
1020
1386
|
);
|
|
1021
1387
|
if (registered.isErr()) {
|
package/src/index.ts
CHANGED
|
@@ -2,8 +2,13 @@
|
|
|
2
2
|
export {
|
|
3
3
|
MCP_TOOL_ERROR_META_KEY,
|
|
4
4
|
MCP_TOOL_EXAMPLES_META_KEY,
|
|
5
|
+
MCP_TOOL_DEFERRED_META_KEY,
|
|
6
|
+
MCP_TOOL_FACET_META_KEY,
|
|
5
7
|
deriveMcpTools,
|
|
6
8
|
type DeriveMcpToolsOptions,
|
|
9
|
+
type McpSurfaceFacetDefinition,
|
|
10
|
+
type McpSurfaceFacetMap,
|
|
11
|
+
type McpSurfaceFacetTrailSelector,
|
|
7
12
|
type McpToolDefinition,
|
|
8
13
|
type McpToolResult,
|
|
9
14
|
type McpToolErrorMeta,
|
|
@@ -13,6 +18,18 @@ export {
|
|
|
13
18
|
type ResolveMcpPermitInput,
|
|
14
19
|
} from './build.js';
|
|
15
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
|
+
|
|
16
33
|
// Tool naming
|
|
17
34
|
export { deriveToolName } from './tool-name.js';
|
|
18
35
|
|
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/surface.ts
CHANGED
|
@@ -5,7 +5,9 @@
|
|
|
5
5
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
6
6
|
import {
|
|
7
7
|
CallToolRequestSchema,
|
|
8
|
+
ListResourcesRequestSchema,
|
|
8
9
|
ListToolsRequestSchema,
|
|
10
|
+
ReadResourceRequestSchema,
|
|
9
11
|
} from '@modelcontextprotocol/sdk/types.js';
|
|
10
12
|
import type {
|
|
11
13
|
BaseSurfaceOptions,
|
|
@@ -15,8 +17,14 @@ import type {
|
|
|
15
17
|
TrailContextInit,
|
|
16
18
|
} from '@ontrails/core';
|
|
17
19
|
|
|
18
|
-
import type {
|
|
20
|
+
import type {
|
|
21
|
+
McpSurfaceFacetMap,
|
|
22
|
+
McpToolDefinition,
|
|
23
|
+
ResolveMcpPermit,
|
|
24
|
+
} from './build.js';
|
|
19
25
|
import { deriveMcpTools } from './build.js';
|
|
26
|
+
import { buildMcpResources } from './resources.js';
|
|
27
|
+
import type { BuiltMcpResources, McpResourcesConfig } from './resources.js';
|
|
20
28
|
import { connectStdio } from './stdio.js';
|
|
21
29
|
|
|
22
30
|
// ---------------------------------------------------------------------------
|
|
@@ -28,7 +36,9 @@ export interface CreateServerOptions extends BaseSurfaceOptions {
|
|
|
28
36
|
| (() => TrailContextInit | Promise<TrailContextInit>)
|
|
29
37
|
| undefined;
|
|
30
38
|
readonly description?: string | undefined;
|
|
39
|
+
readonly facets?: McpSurfaceFacetMap | undefined;
|
|
31
40
|
readonly layers?: readonly Layer[] | undefined;
|
|
41
|
+
readonly mcpResources?: McpResourcesConfig | false | undefined;
|
|
32
42
|
readonly name?: string | undefined;
|
|
33
43
|
readonly resources?: ResourceOverrideMap | undefined;
|
|
34
44
|
readonly resolvePermit?: ResolveMcpPermit | undefined;
|
|
@@ -56,12 +66,16 @@ const createMcpServer = (
|
|
|
56
66
|
readonly name: string;
|
|
57
67
|
readonly version: string;
|
|
58
68
|
readonly description?: string | undefined;
|
|
59
|
-
}
|
|
69
|
+
},
|
|
70
|
+
mcpResources?: BuiltMcpResources | undefined
|
|
60
71
|
): Server => {
|
|
61
72
|
const server = new Server(
|
|
62
73
|
{ name: info.name, version: info.version },
|
|
63
74
|
{
|
|
64
|
-
capabilities: {
|
|
75
|
+
capabilities: {
|
|
76
|
+
...(mcpResources === undefined ? {} : { resources: {} }),
|
|
77
|
+
tools: {},
|
|
78
|
+
},
|
|
65
79
|
...(info.description === undefined
|
|
66
80
|
? {}
|
|
67
81
|
: { instructions: info.description }),
|
|
@@ -149,6 +163,31 @@ const createMcpServer = (
|
|
|
149
163
|
}
|
|
150
164
|
);
|
|
151
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
|
+
|
|
152
191
|
return server;
|
|
153
192
|
};
|
|
154
193
|
|
|
@@ -179,6 +218,7 @@ export const createServer = (
|
|
|
179
218
|
configValues: options.configValues,
|
|
180
219
|
createContext: options.createContext,
|
|
181
220
|
exclude: options.exclude,
|
|
221
|
+
facets: options.facets,
|
|
182
222
|
include: options.include,
|
|
183
223
|
intent: options.intent,
|
|
184
224
|
layers: options.layers,
|
|
@@ -191,11 +231,20 @@ export const createServer = (
|
|
|
191
231
|
throw toolsResult.error;
|
|
192
232
|
}
|
|
193
233
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
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
|
+
);
|
|
199
248
|
};
|
|
200
249
|
|
|
201
250
|
// ---------------------------------------------------------------------------
|