@ontrails/mcp 1.0.0-beta.3 → 1.0.0-beta.5
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 +30 -0
- package/README.md +7 -7
- package/dist/annotations.d.ts +2 -2
- package/dist/annotations.d.ts.map +1 -1
- package/dist/annotations.js +8 -6
- package/dist/annotations.js.map +1 -1
- package/dist/build.js +6 -6
- package/dist/build.js.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/annotations.test.ts +15 -22
- package/src/__tests__/blaze.test.ts +9 -9
- package/src/__tests__/build.test.ts +26 -27
- package/src/annotations.ts +11 -11
- package/src/build.ts +6 -6
package/.turbo/turbo-lint.log
CHANGED
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,35 @@
|
|
|
1
1
|
# @ontrails/mcp
|
|
2
2
|
|
|
3
|
+
## 1.0.0-beta.5
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies
|
|
8
|
+
- @ontrails/core@1.0.0-beta.5
|
|
9
|
+
|
|
10
|
+
## 1.0.0-beta.4
|
|
11
|
+
|
|
12
|
+
### Major Changes
|
|
13
|
+
|
|
14
|
+
- API simplification: unified trail model, intent enum, run, metadata.
|
|
15
|
+
|
|
16
|
+
**BREAKING CHANGES:**
|
|
17
|
+
|
|
18
|
+
- `hike()` removed — use `trail()` with optional `follow: [...]` field
|
|
19
|
+
- `follows` renamed to `follow` (singular, matching `ctx.follow()`)
|
|
20
|
+
- `topo.hikes` removed — single `topo.trails` map
|
|
21
|
+
- `kind: 'hike'` removed — everything is `kind: 'trail'`
|
|
22
|
+
- `readOnly`/`destructive` booleans replaced by `intent: 'read' | 'write' | 'destroy'`
|
|
23
|
+
- `implementation` field renamed to `run`
|
|
24
|
+
- `markers` field renamed to `metadata`
|
|
25
|
+
- `testHike` renamed to `testFollows`, `HikeScenario` to `FollowScenario`
|
|
26
|
+
- `blaze()` now returns the surface handle (`Command` for CLI, `Server` for MCP)
|
|
27
|
+
|
|
28
|
+
### Patch Changes
|
|
29
|
+
|
|
30
|
+
- Updated dependencies
|
|
31
|
+
- @ontrails/core@1.0.0-beta.4
|
|
32
|
+
|
|
3
33
|
## 1.0.0-beta.3
|
|
4
34
|
|
|
5
35
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -11,8 +11,8 @@ import { z } from 'zod';
|
|
|
11
11
|
|
|
12
12
|
const greet = trail('greet', {
|
|
13
13
|
input: z.object({ name: z.string().describe('Who to greet') }),
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
intent: 'read',
|
|
15
|
+
run: (input) => Result.ok(`Hello, ${input.name}!`),
|
|
16
16
|
});
|
|
17
17
|
|
|
18
18
|
const app = topo('myapp', { greet });
|
|
@@ -42,19 +42,19 @@ for (const tool of tools) {
|
|
|
42
42
|
| `blaze(app, options?)` | Start an MCP server with all trails as tools |
|
|
43
43
|
| `buildMcpTools(app, options?)` | Build tool definitions without starting a server |
|
|
44
44
|
| `deriveToolName(appName, trailId)` | Compute the MCP tool name from app and trail IDs |
|
|
45
|
-
| `deriveAnnotations(trail)` | Extract MCP annotations from trail
|
|
45
|
+
| `deriveAnnotations(trail)` | Extract MCP annotations from trail intent and metadata |
|
|
46
46
|
| `createMcpProgressCallback(server)` | Bridge `ctx.progress` to MCP `notifications/progress` |
|
|
47
47
|
|
|
48
48
|
See the [API Reference](../../docs/api-reference.md) for the full list.
|
|
49
49
|
|
|
50
50
|
## Annotations
|
|
51
51
|
|
|
52
|
-
Trail
|
|
52
|
+
Trail intent and metadata map directly to MCP annotations:
|
|
53
53
|
|
|
54
54
|
| Trail field | MCP annotation |
|
|
55
55
|
| --- | --- |
|
|
56
|
-
| `
|
|
57
|
-
| `
|
|
56
|
+
| `intent: 'read'` | `readOnlyHint: true` |
|
|
57
|
+
| `intent: 'destroy'` | `destructiveHint: true` |
|
|
58
58
|
| `idempotent: true` | `idempotentHint: true` |
|
|
59
59
|
| `description` | `title` |
|
|
60
60
|
|
|
@@ -70,7 +70,7 @@ Implementations report progress through `ctx.progress`. On MCP, these bridge to
|
|
|
70
70
|
|
|
71
71
|
```typescript
|
|
72
72
|
const importTrail = trail('data.import', {
|
|
73
|
-
|
|
73
|
+
run: async (input, ctx) => {
|
|
74
74
|
for (let i = 0; i < items.length; i++) {
|
|
75
75
|
await processItem(items[i]);
|
|
76
76
|
ctx.progress?.({ type: 'progress', current: i + 1, total: items.length });
|
package/dist/annotations.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Derive MCP tool annotations from trail spec
|
|
2
|
+
* Derive MCP tool annotations from trail spec fields.
|
|
3
3
|
*/
|
|
4
4
|
import type { Trail } from '@ontrails/core';
|
|
5
5
|
export interface McpAnnotations {
|
|
@@ -15,5 +15,5 @@ export interface McpAnnotations {
|
|
|
15
15
|
* Only sets hints that are explicitly declared on the trail.
|
|
16
16
|
* Omitted hints let the MCP SDK use its defaults.
|
|
17
17
|
*/
|
|
18
|
-
export declare const deriveAnnotations: (trail: Pick<Trail<unknown, unknown>, "
|
|
18
|
+
export declare const deriveAnnotations: (trail: Pick<Trail<unknown, unknown>, "intent" | "idempotent" | "description">) => McpAnnotations;
|
|
19
19
|
//# sourceMappingURL=annotations.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"annotations.d.ts","sourceRoot":"","sources":["../src/annotations.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"annotations.d.ts","sourceRoot":"","sources":["../src/annotations.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAU,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAMpD,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,CAAC,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,EAAE,QAAQ,GAAG,YAAY,GAAG,aAAa,CAAC,KAC5E,cAoBF,CAAC"}
|
package/dist/annotations.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Derive MCP tool annotations from trail spec
|
|
2
|
+
* Derive MCP tool annotations from trail spec fields.
|
|
3
3
|
*/
|
|
4
4
|
// ---------------------------------------------------------------------------
|
|
5
5
|
// Derivation
|
|
@@ -12,11 +12,13 @@
|
|
|
12
12
|
*/
|
|
13
13
|
export const deriveAnnotations = (trail) => {
|
|
14
14
|
const annotations = {};
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
15
|
+
const intentToHint = {
|
|
16
|
+
destroy: 'destructiveHint',
|
|
17
|
+
read: 'readOnlyHint',
|
|
18
|
+
};
|
|
19
|
+
const hint = intentToHint[trail.intent];
|
|
20
|
+
if (hint) {
|
|
21
|
+
annotations[hint] = true;
|
|
20
22
|
}
|
|
21
23
|
if (trail.idempotent === true) {
|
|
22
24
|
annotations['idempotentHint'] = true;
|
package/dist/annotations.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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,
|
|
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,KAA6E,EAC7D,EAAE;IAClB,MAAM,WAAW,GAA4B,EAAE,CAAC;IAEhD,MAAM,YAAY,GAAoC;QACpD,OAAO,EAAE,iBAAiB;QAC1B,IAAI,EAAE,cAAc;KACrB,CAAC;IAEF,MAAM,IAAI,GAAG,YAAY,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IACxC,IAAI,IAAI,EAAE,CAAC;QACT,WAAW,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAC3B,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"}
|
package/dist/build.js
CHANGED
|
@@ -129,7 +129,7 @@ const buildTrailContext = async (options, extra) => {
|
|
|
129
129
|
};
|
|
130
130
|
/** Execute a trail and map the result to an MCP response. */
|
|
131
131
|
const executeAndMap = async (trail, validatedInput, ctx, layers) => {
|
|
132
|
-
const impl = composeLayers([...layers], trail, trail.
|
|
132
|
+
const impl = composeLayers([...layers], trail, trail.run);
|
|
133
133
|
try {
|
|
134
134
|
const result = await impl(validatedInput, ctx);
|
|
135
135
|
if (result.isOk()) {
|
|
@@ -158,12 +158,12 @@ const createHandler = (trail, layers, options) => async (args, extra) => {
|
|
|
158
158
|
* Each trail in the topo becomes an McpToolDefinition with:
|
|
159
159
|
* - A derived tool name (app-prefixed, underscore-delimited)
|
|
160
160
|
* - JSON Schema input from zodToJsonSchema
|
|
161
|
-
* - MCP annotations from trail
|
|
161
|
+
* - MCP annotations from trail metadata
|
|
162
162
|
* - A handler that validates, composes layers, executes, and maps results
|
|
163
163
|
*/
|
|
164
|
-
/** Check if a trail should be included based on
|
|
164
|
+
/** Check if a trail should be included based on metadata and filters. */
|
|
165
165
|
const shouldInclude = (trail, options) => {
|
|
166
|
-
if (trail.
|
|
166
|
+
if (trail.metadata?.['internal'] === true) {
|
|
167
167
|
return false;
|
|
168
168
|
}
|
|
169
169
|
if (options.includeTrails !== undefined && options.includeTrails.length > 0) {
|
|
@@ -210,10 +210,10 @@ const registerTool = (app, trailItem, layers, options, nameToTrailId, tools) =>
|
|
|
210
210
|
nameToTrailId.set(toolName, trailItem.id);
|
|
211
211
|
tools.push(buildToolDefinition(app, trailItem, layers, options));
|
|
212
212
|
};
|
|
213
|
-
/** Filter topo items to eligible trails
|
|
213
|
+
/** Filter topo items to eligible trails. */
|
|
214
214
|
const eligibleTrails = (app, options) => app
|
|
215
215
|
.list()
|
|
216
|
-
.filter((item) =>
|
|
216
|
+
.filter((item) => item.kind === 'trail' &&
|
|
217
217
|
shouldInclude(item, options));
|
|
218
218
|
export const buildMcpTools = (app, options = {}) => {
|
|
219
219
|
const layers = options.layers ?? [];
|
package/dist/build.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"build.js","sourceRoot":"","sources":["../src/build.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"build.js","sourceRoot":"","sources":["../src/build.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -3,36 +3,39 @@ import { describe, expect, test } from 'bun:test';
|
|
|
3
3
|
import { deriveAnnotations } from '../annotations.js';
|
|
4
4
|
|
|
5
5
|
describe('deriveAnnotations', () => {
|
|
6
|
-
test('
|
|
7
|
-
const annotations = deriveAnnotations({
|
|
6
|
+
test('read intent produces readOnlyHint', () => {
|
|
7
|
+
const annotations = deriveAnnotations({ intent: 'read' });
|
|
8
8
|
expect(annotations.readOnlyHint).toBe(true);
|
|
9
9
|
expect(annotations.destructiveHint).toBeUndefined();
|
|
10
10
|
expect(annotations.idempotentHint).toBeUndefined();
|
|
11
11
|
});
|
|
12
12
|
|
|
13
|
-
test('
|
|
14
|
-
const annotations = deriveAnnotations({
|
|
13
|
+
test('destroy intent produces destructiveHint', () => {
|
|
14
|
+
const annotations = deriveAnnotations({ intent: 'destroy' });
|
|
15
15
|
expect(annotations.destructiveHint).toBe(true);
|
|
16
16
|
expect(annotations.readOnlyHint).toBeUndefined();
|
|
17
17
|
});
|
|
18
18
|
|
|
19
19
|
test('idempotent trail produces idempotentHint', () => {
|
|
20
|
-
const annotations = deriveAnnotations({
|
|
20
|
+
const annotations = deriveAnnotations({
|
|
21
|
+
idempotent: true,
|
|
22
|
+
intent: 'write',
|
|
23
|
+
});
|
|
21
24
|
expect(annotations.idempotentHint).toBe(true);
|
|
22
25
|
});
|
|
23
26
|
|
|
24
|
-
test('
|
|
27
|
+
test('read intent with idempotent combines correctly', () => {
|
|
25
28
|
const annotations = deriveAnnotations({
|
|
26
29
|
idempotent: true,
|
|
27
|
-
|
|
30
|
+
intent: 'read',
|
|
28
31
|
});
|
|
29
32
|
expect(annotations.readOnlyHint).toBe(true);
|
|
30
33
|
expect(annotations.idempotentHint).toBe(true);
|
|
31
34
|
expect(annotations.destructiveHint).toBeUndefined();
|
|
32
35
|
});
|
|
33
36
|
|
|
34
|
-
test('
|
|
35
|
-
const annotations = deriveAnnotations({});
|
|
37
|
+
test('write intent produces empty annotations', () => {
|
|
38
|
+
const annotations = deriveAnnotations({ intent: 'write' });
|
|
36
39
|
expect(annotations.readOnlyHint).toBeUndefined();
|
|
37
40
|
expect(annotations.destructiveHint).toBeUndefined();
|
|
38
41
|
expect(annotations.idempotentHint).toBeUndefined();
|
|
@@ -42,29 +45,19 @@ describe('deriveAnnotations', () => {
|
|
|
42
45
|
test('description maps to title', () => {
|
|
43
46
|
const annotations = deriveAnnotations({
|
|
44
47
|
description: 'Show entity details',
|
|
48
|
+
intent: 'write',
|
|
45
49
|
});
|
|
46
50
|
expect(annotations.title).toBe('Show entity details');
|
|
47
51
|
});
|
|
48
52
|
|
|
49
|
-
test('all
|
|
53
|
+
test('all hints plus description', () => {
|
|
50
54
|
const annotations = deriveAnnotations({
|
|
51
55
|
description: 'A trail',
|
|
52
|
-
destructive: true,
|
|
53
56
|
idempotent: true,
|
|
54
|
-
|
|
57
|
+
intent: 'destroy',
|
|
55
58
|
});
|
|
56
|
-
expect(annotations.readOnlyHint).toBe(true);
|
|
57
59
|
expect(annotations.destructiveHint).toBe(true);
|
|
58
60
|
expect(annotations.idempotentHint).toBe(true);
|
|
59
61
|
expect(annotations.title).toBe('A trail');
|
|
60
62
|
});
|
|
61
|
-
|
|
62
|
-
test('false values are not included', () => {
|
|
63
|
-
const annotations = deriveAnnotations({
|
|
64
|
-
destructive: false,
|
|
65
|
-
readOnly: false,
|
|
66
|
-
});
|
|
67
|
-
expect(annotations.readOnlyHint).toBeUndefined();
|
|
68
|
-
expect(annotations.destructiveHint).toBeUndefined();
|
|
69
|
-
});
|
|
70
63
|
});
|
|
@@ -22,16 +22,16 @@ const requireTool = (tools: ReturnType<typeof buildMcpTools>, name: string) => {
|
|
|
22
22
|
const createIntegrationTools = () => {
|
|
23
23
|
const greetTrail = trail('greet', {
|
|
24
24
|
description: 'Greet someone',
|
|
25
|
-
implementation: (input) => Result.ok({ greeting: `Hello, ${input.name}!` }),
|
|
26
25
|
input: z.object({ name: z.string() }),
|
|
27
|
-
|
|
26
|
+
intent: 'read',
|
|
27
|
+
run: (input) => Result.ok({ greeting: `Hello, ${input.name}!` }),
|
|
28
28
|
});
|
|
29
29
|
|
|
30
30
|
const deleteTrail = trail('item.delete', {
|
|
31
31
|
description: 'Delete an item',
|
|
32
|
-
destructive: true,
|
|
33
|
-
implementation: (_input) => Result.ok({ deleted: true }),
|
|
34
32
|
input: z.object({ id: z.string() }),
|
|
33
|
+
intent: 'destroy',
|
|
34
|
+
run: (_input) => Result.ok({ deleted: true }),
|
|
35
35
|
});
|
|
36
36
|
|
|
37
37
|
return buildMcpTools(topo('myapp', { deleteTrail, greetTrail }));
|
|
@@ -41,9 +41,9 @@ describe('blaze', () => {
|
|
|
41
41
|
test('createMcpServer registers tools that can be listed', () => {
|
|
42
42
|
const echoTrail = trail('echo', {
|
|
43
43
|
description: 'Echo',
|
|
44
|
-
implementation: (input) => Result.ok({ reply: input.message }),
|
|
45
44
|
input: z.object({ message: z.string() }),
|
|
46
|
-
|
|
45
|
+
intent: 'read',
|
|
46
|
+
run: (input) => Result.ok({ reply: input.message }),
|
|
47
47
|
});
|
|
48
48
|
|
|
49
49
|
const app = topo('testapp', { echoTrail });
|
|
@@ -60,15 +60,15 @@ describe('blaze', () => {
|
|
|
60
60
|
test('createMcpServer handles multiple tools', () => {
|
|
61
61
|
const echoTrail = trail('echo', {
|
|
62
62
|
description: 'Echo',
|
|
63
|
-
implementation: (input) => Result.ok({ reply: input.message }),
|
|
64
63
|
input: z.object({ message: z.string() }),
|
|
64
|
+
run: (input) => Result.ok({ reply: input.message }),
|
|
65
65
|
});
|
|
66
66
|
|
|
67
67
|
const searchTrail = trail('search', {
|
|
68
68
|
description: 'Search',
|
|
69
|
-
implementation: (input) => Result.ok({ results: [input.query] }),
|
|
70
69
|
input: z.object({ query: z.string() }),
|
|
71
|
-
|
|
70
|
+
intent: 'read',
|
|
71
|
+
run: (input) => Result.ok({ results: [input.query] }),
|
|
72
72
|
});
|
|
73
73
|
|
|
74
74
|
const app = topo('testapp', { echoTrail, searchTrail });
|
|
@@ -13,23 +13,23 @@ import type { McpExtra } from '../build.js';
|
|
|
13
13
|
|
|
14
14
|
const echoTrail = trail('echo', {
|
|
15
15
|
description: 'Echo a message back',
|
|
16
|
-
implementation: (input) => Result.ok({ reply: input.message }),
|
|
17
16
|
input: z.object({ message: z.string() }),
|
|
17
|
+
intent: 'read',
|
|
18
18
|
output: z.object({ reply: z.string() }),
|
|
19
|
-
|
|
19
|
+
run: (input) => Result.ok({ reply: input.message }),
|
|
20
20
|
});
|
|
21
21
|
|
|
22
22
|
const deleteTrail = trail('item.delete', {
|
|
23
23
|
description: 'Delete an item',
|
|
24
|
-
destructive: true,
|
|
25
|
-
implementation: (_input) => Result.ok({ deleted: true }),
|
|
26
24
|
input: z.object({ id: z.string() }),
|
|
25
|
+
intent: 'destroy',
|
|
26
|
+
run: (_input) => Result.ok({ deleted: true }),
|
|
27
27
|
});
|
|
28
28
|
|
|
29
29
|
const failTrail = trail('fail', {
|
|
30
30
|
description: 'Always fails',
|
|
31
|
-
implementation: (input) => Result.err(new Error(input.reason)),
|
|
32
31
|
input: z.object({ reason: z.string() }),
|
|
32
|
+
run: (input) => Result.err(new Error(input.reason)),
|
|
33
33
|
});
|
|
34
34
|
|
|
35
35
|
const exampleTrail = trail('with.examples', {
|
|
@@ -41,8 +41,8 @@ const exampleTrail = trail('with.examples', {
|
|
|
41
41
|
name: 'basic',
|
|
42
42
|
},
|
|
43
43
|
],
|
|
44
|
-
implementation: (input) => Result.ok({ greeting: `hello ${input.name}` }),
|
|
45
44
|
input: z.object({ name: z.string() }),
|
|
45
|
+
run: (input) => Result.ok({ greeting: `hello ${input.name}` }),
|
|
46
46
|
});
|
|
47
47
|
|
|
48
48
|
const noExtra: McpExtra = {};
|
|
@@ -165,10 +165,10 @@ describe('buildMcpTools', () => {
|
|
|
165
165
|
|
|
166
166
|
test('handler catches thrown exceptions', async () => {
|
|
167
167
|
const throwTrail = trail('throw', {
|
|
168
|
-
|
|
168
|
+
input: z.object({}),
|
|
169
|
+
run: () => {
|
|
169
170
|
throw new Error('unexpected crash');
|
|
170
171
|
},
|
|
171
|
-
input: z.object({}),
|
|
172
172
|
});
|
|
173
173
|
|
|
174
174
|
const tool = requireOnlyTool(
|
|
@@ -244,11 +244,11 @@ describe('buildMcpTools', () => {
|
|
|
244
244
|
let capturedSignal: AbortSignal | undefined;
|
|
245
245
|
|
|
246
246
|
const signalTrail = trail('signal.check', {
|
|
247
|
-
|
|
247
|
+
input: z.object({}),
|
|
248
|
+
run: (_input, ctx) => {
|
|
248
249
|
capturedSignal = ctx.signal;
|
|
249
250
|
return Result.ok({ ok: true });
|
|
250
251
|
},
|
|
251
|
-
input: z.object({}),
|
|
252
252
|
});
|
|
253
253
|
|
|
254
254
|
const controller = new AbortController();
|
|
@@ -272,12 +272,12 @@ describe('buildMcpTools', () => {
|
|
|
272
272
|
let contextUsed = false;
|
|
273
273
|
|
|
274
274
|
const ctxTrail = trail('ctx.check', {
|
|
275
|
-
|
|
275
|
+
input: z.object({}),
|
|
276
|
+
run: (_input, ctx) => {
|
|
276
277
|
const ctxRecord = ctx as Record<string, unknown>;
|
|
277
278
|
contextUsed = ctxRecord['custom'] === true;
|
|
278
279
|
return Result.ok({ ok: true });
|
|
279
280
|
},
|
|
280
|
-
input: z.object({}),
|
|
281
281
|
});
|
|
282
282
|
|
|
283
283
|
const app = topo('myapp', { ctxTrail });
|
|
@@ -299,7 +299,8 @@ describe('buildMcpTools', () => {
|
|
|
299
299
|
describe('blob outputs', () => {
|
|
300
300
|
test('BlobRef output converts to image content', async () => {
|
|
301
301
|
const blobTrail = trail('blob.image', {
|
|
302
|
-
|
|
302
|
+
input: z.object({}),
|
|
303
|
+
run: () =>
|
|
303
304
|
Result.ok(
|
|
304
305
|
createBlobRef({
|
|
305
306
|
data: new Uint8Array([1, 2, 3]),
|
|
@@ -308,7 +309,6 @@ describe('buildMcpTools', () => {
|
|
|
308
309
|
size: 3,
|
|
309
310
|
})
|
|
310
311
|
),
|
|
311
|
-
input: z.object({}),
|
|
312
312
|
});
|
|
313
313
|
|
|
314
314
|
const tool = requireOnlyTool(buildMcpTools(topo('myapp', { blobTrail })));
|
|
@@ -321,7 +321,8 @@ describe('buildMcpTools', () => {
|
|
|
321
321
|
|
|
322
322
|
test('BlobRef output converts to resource content for non-images', async () => {
|
|
323
323
|
const blobTrail = trail('blob.file', {
|
|
324
|
-
|
|
324
|
+
input: z.object({}),
|
|
325
|
+
run: () =>
|
|
325
326
|
Result.ok(
|
|
326
327
|
createBlobRef({
|
|
327
328
|
data: new Uint8Array([1, 2, 3]),
|
|
@@ -330,7 +331,6 @@ describe('buildMcpTools', () => {
|
|
|
330
331
|
size: 3,
|
|
331
332
|
})
|
|
332
333
|
),
|
|
333
|
-
input: z.object({}),
|
|
334
334
|
});
|
|
335
335
|
|
|
336
336
|
const tool = requireOnlyTool(buildMcpTools(topo('myapp', { blobTrail })));
|
|
@@ -350,7 +350,8 @@ describe('buildMcpTools', () => {
|
|
|
350
350
|
},
|
|
351
351
|
});
|
|
352
352
|
const blobTrail = trail('blob.stream', {
|
|
353
|
-
|
|
353
|
+
input: z.object({}),
|
|
354
|
+
run: () =>
|
|
354
355
|
Result.ok(
|
|
355
356
|
createBlobRef({
|
|
356
357
|
data: stream,
|
|
@@ -359,7 +360,6 @@ describe('buildMcpTools', () => {
|
|
|
359
360
|
size: 3,
|
|
360
361
|
})
|
|
361
362
|
),
|
|
362
|
-
input: z.object({}),
|
|
363
363
|
});
|
|
364
364
|
|
|
365
365
|
const tool = requireOnlyTool(buildMcpTools(topo('myapp', { blobTrail })));
|
|
@@ -374,12 +374,12 @@ describe('buildMcpTools', () => {
|
|
|
374
374
|
describe('tool-name collision detection', () => {
|
|
375
375
|
test('throws on trails that produce the same derived tool name', () => {
|
|
376
376
|
const dotTrail = trail('foo.bar', {
|
|
377
|
-
implementation: () => Result.ok({ ok: true }),
|
|
378
377
|
input: z.object({}),
|
|
378
|
+
run: () => Result.ok({ ok: true }),
|
|
379
379
|
});
|
|
380
380
|
const underscoreTrail = trail('foo_bar', {
|
|
381
|
-
implementation: () => Result.ok({ ok: true }),
|
|
382
381
|
input: z.object({}),
|
|
382
|
+
run: () => Result.ok({ ok: true }),
|
|
383
383
|
});
|
|
384
384
|
|
|
385
385
|
const app = topo('myapp', { dotTrail, underscoreTrail });
|
|
@@ -388,12 +388,12 @@ describe('buildMcpTools', () => {
|
|
|
388
388
|
|
|
389
389
|
test('throws on trails where hyphen and underscore collide', () => {
|
|
390
390
|
const hyphenTrail = trail('foo-bar', {
|
|
391
|
-
implementation: () => Result.ok({ ok: true }),
|
|
392
391
|
input: z.object({}),
|
|
392
|
+
run: () => Result.ok({ ok: true }),
|
|
393
393
|
});
|
|
394
394
|
const underscoreTrail = trail('foo_bar', {
|
|
395
|
-
implementation: () => Result.ok({ ok: true }),
|
|
396
395
|
input: z.object({}),
|
|
396
|
+
run: () => Result.ok({ ok: true }),
|
|
397
397
|
});
|
|
398
398
|
|
|
399
399
|
const app = topo('myapp', { hyphenTrail, underscoreTrail });
|
|
@@ -402,12 +402,12 @@ describe('buildMcpTools', () => {
|
|
|
402
402
|
|
|
403
403
|
test('does not throw when trail names are distinct after normalization', () => {
|
|
404
404
|
const fooTrail = trail('foo', {
|
|
405
|
-
implementation: () => Result.ok({ ok: true }),
|
|
406
405
|
input: z.object({}),
|
|
406
|
+
run: () => Result.ok({ ok: true }),
|
|
407
407
|
});
|
|
408
408
|
const barTrail = trail('bar', {
|
|
409
|
-
implementation: () => Result.ok({ ok: true }),
|
|
410
409
|
input: z.object({}),
|
|
410
|
+
run: () => Result.ok({ ok: true }),
|
|
411
411
|
});
|
|
412
412
|
|
|
413
413
|
const app = topo('myapp', { barTrail, fooTrail });
|
|
@@ -420,11 +420,10 @@ describe('buildMcpTools', () => {
|
|
|
420
420
|
const greetTrail = trail('greet', {
|
|
421
421
|
description: 'Greet someone',
|
|
422
422
|
idempotent: true,
|
|
423
|
-
implementation: (input) =>
|
|
424
|
-
Result.ok({ greeting: `Hello, ${input.name}!` }),
|
|
425
423
|
input: z.object({ name: z.string() }),
|
|
424
|
+
intent: 'read',
|
|
426
425
|
output: z.object({ greeting: z.string() }),
|
|
427
|
-
|
|
426
|
+
run: (input) => Result.ok({ greeting: `Hello, ${input.name}!` }),
|
|
428
427
|
});
|
|
429
428
|
|
|
430
429
|
const tool = requireOnlyTool(
|
package/src/annotations.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Derive MCP tool annotations from trail spec
|
|
2
|
+
* Derive MCP tool annotations from trail spec fields.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import type { Trail } from '@ontrails/core';
|
|
5
|
+
import type { Intent, Trail } from '@ontrails/core';
|
|
6
6
|
|
|
7
7
|
// ---------------------------------------------------------------------------
|
|
8
8
|
// Types
|
|
@@ -27,18 +27,18 @@ export interface McpAnnotations {
|
|
|
27
27
|
* Omitted hints let the MCP SDK use its defaults.
|
|
28
28
|
*/
|
|
29
29
|
export const deriveAnnotations = (
|
|
30
|
-
trail: Pick<
|
|
31
|
-
Trail<unknown, unknown>,
|
|
32
|
-
'readOnly' | 'destructive' | 'idempotent' | 'description'
|
|
33
|
-
>
|
|
30
|
+
trail: Pick<Trail<unknown, unknown>, 'intent' | 'idempotent' | 'description'>
|
|
34
31
|
): McpAnnotations => {
|
|
35
32
|
const annotations: Record<string, unknown> = {};
|
|
36
33
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
34
|
+
const intentToHint: Partial<Record<Intent, string>> = {
|
|
35
|
+
destroy: 'destructiveHint',
|
|
36
|
+
read: 'readOnlyHint',
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const hint = intentToHint[trail.intent];
|
|
40
|
+
if (hint) {
|
|
41
|
+
annotations[hint] = true;
|
|
42
42
|
}
|
|
43
43
|
if (trail.idempotent === true) {
|
|
44
44
|
annotations['idempotentHint'] = true;
|
package/src/build.ts
CHANGED
|
@@ -224,7 +224,7 @@ const executeAndMap = async (
|
|
|
224
224
|
ctx: TrailContext,
|
|
225
225
|
layers: readonly Layer[]
|
|
226
226
|
): Promise<McpToolResult> => {
|
|
227
|
-
const impl = composeLayers([...layers], trail, trail.
|
|
227
|
+
const impl = composeLayers([...layers], trail, trail.run);
|
|
228
228
|
try {
|
|
229
229
|
const result = await impl(validatedInput, ctx);
|
|
230
230
|
if (result.isOk()) {
|
|
@@ -264,15 +264,15 @@ const createHandler =
|
|
|
264
264
|
* Each trail in the topo becomes an McpToolDefinition with:
|
|
265
265
|
* - A derived tool name (app-prefixed, underscore-delimited)
|
|
266
266
|
* - JSON Schema input from zodToJsonSchema
|
|
267
|
-
* - MCP annotations from trail
|
|
267
|
+
* - MCP annotations from trail metadata
|
|
268
268
|
* - A handler that validates, composes layers, executes, and maps results
|
|
269
269
|
*/
|
|
270
|
-
/** Check if a trail should be included based on
|
|
270
|
+
/** Check if a trail should be included based on metadata and filters. */
|
|
271
271
|
const shouldInclude = (
|
|
272
272
|
trail: Trail<unknown, unknown>,
|
|
273
273
|
options: BuildMcpToolsOptions
|
|
274
274
|
): boolean => {
|
|
275
|
-
if (trail.
|
|
275
|
+
if (trail.metadata?.['internal'] === true) {
|
|
276
276
|
return false;
|
|
277
277
|
}
|
|
278
278
|
if (options.includeTrails !== undefined && options.includeTrails.length > 0) {
|
|
@@ -344,7 +344,7 @@ const registerTool = (
|
|
|
344
344
|
tools.push(buildToolDefinition(app, trailItem, layers, options));
|
|
345
345
|
};
|
|
346
346
|
|
|
347
|
-
/** Filter topo items to eligible trails
|
|
347
|
+
/** Filter topo items to eligible trails. */
|
|
348
348
|
const eligibleTrails = (
|
|
349
349
|
app: Topo,
|
|
350
350
|
options: BuildMcpToolsOptions
|
|
@@ -353,7 +353,7 @@ const eligibleTrails = (
|
|
|
353
353
|
.list()
|
|
354
354
|
.filter(
|
|
355
355
|
(item): item is Trail<unknown, unknown> =>
|
|
356
|
-
|
|
356
|
+
item.kind === 'trail' &&
|
|
357
357
|
shouldInclude(item as Trail<unknown, unknown>, options)
|
|
358
358
|
);
|
|
359
359
|
|