@ontrails/mcp 1.0.0-beta.1 → 1.0.0-beta.11

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.
@@ -1,3 +1,3 @@
1
1
  $ oxlint ./src
2
2
  Found 0 warnings and 0 errors.
3
- Finished in 54ms on 12 files with 93 rules using 24 threads.
3
+ Finished in 67ms on 12 files with 93 rules using 24 threads.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,151 @@
1
1
  # @ontrails/mcp
2
2
 
3
+ ## 1.0.0-beta.11
4
+
5
+ ### Patch Changes
6
+
7
+ - Add services as a first-class primitive.
8
+
9
+ Services make infrastructure dependencies declarative, injectable, and governable. Define a service with `service()`, declare it on a trail with `services: [db]`, and access it with `db.from(ctx)` or `ctx.service()`.
10
+
11
+ **Core:** `service()` factory, `ServiceSpec<T>`, `ServiceContext`, singleton resolution in `executeTrail`, in-flight creation dedup, `isService` guard, `findDuplicateServiceId`, topo service discovery and validation, `services` field on trail specs.
12
+
13
+ **Testing:** Auto-resolution of `mock` factories in `testAll`, `testExamples`, `testContracts`, and `testFollows`. Explicit `services` overrides with correct precedence (`explicit > ctx.extensions > auto-mock`). Service mock propagation through follow graphs.
14
+
15
+ **Warden:** `service-declarations` rule validates `db.from(ctx)` and `ctx.service()` usage matches declared `services: [...]`. `service-exists` rule validates declared service IDs resolve in project context. Scope-aware AST walking skips nested function boundaries.
16
+
17
+ **Surfaces:** Service overrides thread through `dispatch` and `blaze` on CLI, MCP, and HTTP.
18
+
19
+ **Introspection:** Survey and surface map outputs include service graph. Topo exposes `.services`, `.getService()`, `.hasService()`, `.listServices()`, `.serviceIds()`, `.serviceCount`.
20
+
21
+ **Docs:** ADR-009 accepted. Unified services guide, updated vocabulary, getting-started, architecture, and package READMEs.
22
+
23
+ - Updated dependencies
24
+ - @ontrails/core@1.0.0-beta.11
25
+
26
+ ## 1.0.0-beta.10
27
+
28
+ ### Patch Changes
29
+
30
+ - Cleanup and hardening pass across all packages.
31
+
32
+ **core**: Deduplicate `DispatchOptions` as type alias of `ExecuteTrailOptions`. Replace `TrailContext` index signature with typed `extensions` field for type safety. Deep-merge `extensions` in `executeTrail` context resolution. Remove unused `Surface` type, `adapters.ts`, `health.ts`, and `job.ts` proof-of-concept from published package.
33
+
34
+ **cli**: Remove vestigial `kind` checks from build. Run `validateTopo()` automatically in `blaze()` with opt-out via `validate: false`.
35
+
36
+ **http**: Remove vestigial `kind` checks from build. Run `validateTopo()` automatically in `blaze()` with opt-out.
37
+
38
+ **mcp**: Remove vestigial `kind` checks from build. Run `validateTopo()` automatically in `blaze()` with opt-out.
39
+
40
+ **warden**: Project-aware rule context preserved in trail wrappers.
41
+
42
+ - Updated dependencies
43
+ - @ontrails/core@1.0.0-beta.10
44
+
45
+ ## 1.0.0-beta.9
46
+
47
+ ### Minor Changes
48
+
49
+ - Consolidated improvements across all surface packages.
50
+
51
+ **core**: Add `TrailResult<T>` utility type, `topo.ids()` and `topo.count` accessors, `dispatch()` for headless trail execution, and extract shared `executeTrail` pipeline used by CLI/MCP/HTTP.
52
+
53
+ **http**: Detect route path collisions and return `Result` from `buildHttpRoutes()`, wire request `AbortSignal` through to trail context, and make write → POST mapping explicit in intent-to-method lookup.
54
+
55
+ **mcp**: Return `Result` from `buildMcpTools()` on collision instead of throwing.
56
+
57
+ **cli**: Verify exception catching via centralized `executeTrail`.
58
+
59
+ **testing**: Follow context awareness improvements.
60
+
61
+ **warden**: Refactor rules as composable trails with examples.
62
+
63
+ **schema**: Error code and empty body fixes.
64
+
65
+ ### Patch Changes
66
+
67
+ - Updated dependencies
68
+ - @ontrails/core@1.0.0-beta.9
69
+
70
+ ## 1.0.0-beta.8
71
+
72
+ ### Patch Changes
73
+
74
+ - @ontrails/core@1.0.0-beta.8
75
+
76
+ ## 1.0.0-beta.7
77
+
78
+ ### Patch Changes
79
+
80
+ - @ontrails/core@1.0.0-beta.7
81
+
82
+ ## 1.0.0-beta.6
83
+
84
+ ### Patch Changes
85
+
86
+ - Updated dependencies
87
+ - @ontrails/core@1.0.0-beta.6
88
+
89
+ ## 1.0.0-beta.5
90
+
91
+ ### Patch Changes
92
+
93
+ - Updated dependencies
94
+ - @ontrails/core@1.0.0-beta.5
95
+
96
+ ## 1.0.0-beta.4
97
+
98
+ ### Major Changes
99
+
100
+ - API simplification: unified trail model, intent enum, run, metadata.
101
+
102
+ **BREAKING CHANGES:**
103
+
104
+ - `hike()` removed — use `trail()` with optional `follow: [...]` field
105
+ - `follows` renamed to `follow` (singular, matching `ctx.follow()`)
106
+ - `topo.hikes` removed — single `topo.trails` map
107
+ - `kind: 'hike'` removed — everything is `kind: 'trail'`
108
+ - `readOnly`/`destructive` booleans replaced by `intent: 'read' | 'write' | 'destroy'`
109
+ - `implementation` field renamed to `run`
110
+ - `markers` field renamed to `metadata`
111
+ - `testHike` renamed to `testFollows`, `HikeScenario` to `FollowScenario`
112
+ - `blaze()` now returns the surface handle (`Command` for CLI, `Server` for MCP)
113
+
114
+ ### Patch Changes
115
+
116
+ - Updated dependencies
117
+ - @ontrails/core@1.0.0-beta.4
118
+
119
+ ## 1.0.0-beta.3
120
+
121
+ ### Minor Changes
122
+
123
+ - Bug fixes across all surface packages found via parallel Codex review.
124
+
125
+ **core**: Fix Result.toJson false circular detection on DAGs, deserializeError subclass round-trip, topo cross-kind ID collisions, validateTopo multi-node cycle detection, error example input validation bypass, and deriveFields array type collapse.
126
+
127
+ **cli**: Switch blaze to parseAsync for proper async error handling, add boolean flag negation (--no-flag), and strict number parsing that rejects partial input.
128
+
129
+ **mcp**: Align BlobRef with core (including ReadableStream support) and detect tool-name collisions after normalization.
130
+
131
+ **testing**: Include hikes in testContracts validation, with follow-context awareness.
132
+
133
+ **warden**: Collect hike detour targets, validate detour refs in hike specs, and stop implementation-returns-result from walking into nested function bodies.
134
+
135
+ ### Patch Changes
136
+
137
+ - Updated dependencies
138
+ - @ontrails/core@1.0.0-beta.3
139
+
140
+ ## 1.0.0-beta.2
141
+
142
+ ### Patch Changes
143
+
144
+ - Fix workspace dependency resolution in published packages. Now using bun publish
145
+ which correctly replaces workspace:^ with actual version numbers.
146
+ - Updated dependencies
147
+ - @ontrails/core@1.0.0-beta.2
148
+
3
149
  ## 1.0.0-beta.1
4
150
 
5
151
  ### Patch 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
- readOnly: true,
15
- implementation: (input) => Result.ok(`Hello, ${input.name}!`),
14
+ intent: 'read',
15
+ run: (input) => Result.ok(`Hello, ${input.name}!`),
16
16
  });
17
17
 
18
18
  const app = topo('myapp', { greet });
@@ -26,8 +26,9 @@ For more control, build the tools yourself:
26
26
  ```typescript
27
27
  import { buildMcpTools } from '@ontrails/mcp';
28
28
 
29
- const tools = buildMcpTools(app);
30
- for (const tool of tools) {
29
+ const result = buildMcpTools(app);
30
+ if (result.isErr()) throw result.error; // ValidationError on tool-name collision
31
+ for (const tool of result.value) {
31
32
  server.registerTool(tool.name, tool.handler, {
32
33
  inputSchema: tool.inputSchema,
33
34
  annotations: tool.annotations,
@@ -35,6 +36,8 @@ for (const tool of tools) {
35
36
  }
36
37
  ```
37
38
 
39
+ `buildMcpTools` returns `Result<McpToolDefinition[], Error>` rather than a bare array. It returns `Result.err(ValidationError)` if two trails derive the same MCP tool name. Each `McpToolDefinition` includes a `trailId` field that records which trail the tool was derived from.
40
+
38
41
  ## API
39
42
 
40
43
  | Export | What it does |
@@ -42,19 +45,19 @@ for (const tool of tools) {
42
45
  | `blaze(app, options?)` | Start an MCP server with all trails as tools |
43
46
  | `buildMcpTools(app, options?)` | Build tool definitions without starting a server |
44
47
  | `deriveToolName(appName, trailId)` | Compute the MCP tool name from app and trail IDs |
45
- | `deriveAnnotations(trail)` | Extract MCP annotations from trail markers |
48
+ | `deriveAnnotations(trail)` | Extract MCP annotations from trail intent and metadata |
46
49
  | `createMcpProgressCallback(server)` | Bridge `ctx.progress` to MCP `notifications/progress` |
47
50
 
48
51
  See the [API Reference](../../docs/api-reference.md) for the full list.
49
52
 
50
53
  ## Annotations
51
54
 
52
- Trail markers map directly to MCP annotations:
55
+ Trail intent and metadata map directly to MCP annotations:
53
56
 
54
57
  | Trail field | MCP annotation |
55
58
  | --- | --- |
56
- | `readOnly: true` | `readOnlyHint: true` |
57
- | `destructive: true` | `destructiveHint: true` |
59
+ | `intent: 'read'` | `readOnlyHint: true` |
60
+ | `intent: 'destroy'` | `destructiveHint: true` |
58
61
  | `idempotent: true` | `idempotentHint: true` |
59
62
  | `description` | `title` |
60
63
 
@@ -64,13 +67,17 @@ No manual annotation definitions. The contract is the source of truth.
64
67
 
65
68
  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.
66
69
 
70
+ ## Service resolution
71
+
72
+ Declared services on each trail are resolved into the context before the implementation runs.
73
+
67
74
  ## Progress bridge
68
75
 
69
76
  Implementations report progress through `ctx.progress`. On MCP, these bridge to `notifications/progress` when the client sends a `progressToken`:
70
77
 
71
78
  ```typescript
72
79
  const importTrail = trail('data.import', {
73
- implementation: async (input, ctx) => {
80
+ run: async (input, ctx) => {
74
81
  for (let i = 0; i < items.length; i++) {
75
82
  await processItem(items[i]);
76
83
  ctx.progress?.({ type: 'progress', current: i + 1, total: items.length });
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Derive MCP tool annotations from trail spec markers.
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>, "readOnly" | "destructive" | "idempotent" | "description">) => McpAnnotations;
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,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"}
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"}
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Derive MCP tool annotations from trail spec markers.
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
- if (trail.readOnly === true) {
16
- annotations['readOnlyHint'] = true;
17
- }
18
- if (trail.destructive === true) {
19
- annotations['destructiveHint'] = true;
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;
@@ -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,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"}
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/blaze.d.ts CHANGED
@@ -9,10 +9,10 @@
9
9
  * ```
10
10
  */
11
11
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
12
- import type { Layer, Topo, TrailContext } from '@ontrails/core';
12
+ import type { Layer, ServiceOverrideMap, Topo, TrailContextInit } from '@ontrails/core';
13
13
  import type { McpToolDefinition } from './build.js';
14
14
  export interface BlazeMcpOptions {
15
- readonly createContext?: (() => TrailContext | Promise<TrailContext>) | undefined;
15
+ readonly createContext?: (() => TrailContextInit | Promise<TrailContextInit>) | undefined;
16
16
  readonly excludeTrails?: readonly string[] | undefined;
17
17
  readonly includeTrails?: readonly string[] | undefined;
18
18
  readonly layers?: readonly Layer[] | undefined;
@@ -21,6 +21,9 @@ export interface BlazeMcpOptions {
21
21
  readonly version?: string | undefined;
22
22
  } | undefined;
23
23
  readonly transport?: 'stdio' | undefined;
24
+ readonly services?: ServiceOverrideMap | undefined;
25
+ /** Set to `false` to skip topo validation at startup. Defaults to `true`. */
26
+ readonly validate?: boolean | undefined;
24
27
  }
25
28
  /**
26
29
  * Create an MCP Server instance and register all tools.
@@ -1 +1 @@
1
- {"version":3,"file":"blaze.d.ts","sourceRoot":"","sources":["../src/blaze.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AAKnE,OAAO,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAEhE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAQpD,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,aAAa,CAAC,EACnB,CAAC,MAAM,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC,GAC5C,SAAS,CAAC;IACd,QAAQ,CAAC,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAAC;IACvD,QAAQ,CAAC,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAAC;IACvD,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,KAAK,EAAE,GAAG,SAAS,CAAC;IAC/C,QAAQ,CAAC,UAAU,CAAC,EAChB;QACE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;QACnC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;KACvC,GACD,SAAS,CAAC;IACd,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CAC1C;AAMD;;GAEG;AACH,eAAO,MAAM,eAAe,GAC1B,OAAO,iBAAiB,EAAE,EAC1B,MAAM;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CAAE,KACxD,MAmEF,CAAC;AAMF;;GAEG;AACH,eAAO,MAAM,KAAK,GAChB,KAAK,IAAI,EACT,UAAS,eAAoB,KAC5B,OAAO,CAAC,IAAI,CAcd,CAAC"}
1
+ {"version":3,"file":"blaze.d.ts","sourceRoot":"","sources":["../src/blaze.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AAKnE,OAAO,KAAK,EACV,KAAK,EACL,kBAAkB,EAClB,IAAI,EACJ,gBAAgB,EACjB,MAAM,gBAAgB,CAAC;AAGxB,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAQpD,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,aAAa,CAAC,EACnB,CAAC,MAAM,gBAAgB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAC,GACpD,SAAS,CAAC;IACd,QAAQ,CAAC,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAAC;IACvD,QAAQ,CAAC,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAAC;IACvD,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,KAAK,EAAE,GAAG,SAAS,CAAC;IAC/C,QAAQ,CAAC,UAAU,CAAC,EAChB;QACE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;QACnC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;KACvC,GACD,SAAS,CAAC;IACd,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACzC,QAAQ,CAAC,QAAQ,CAAC,EAAE,kBAAkB,GAAG,SAAS,CAAC;IACnD,6EAA6E;IAC7E,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CACzC;AAMD;;GAEG;AACH,eAAO,MAAM,eAAe,GAC1B,OAAO,iBAAiB,EAAE,EAC1B,MAAM;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CAAE,KACxD,MAmEF,CAAC;AAMF;;GAEG;AACH,eAAO,MAAM,KAAK,GAChB,KAAK,IAAI,EACT,UAAS,eAAoB,KAC5B,OAAO,CAAC,IAAI,CA0Bd,CAAC"}
package/dist/blaze.js CHANGED
@@ -10,6 +10,7 @@
10
10
  */
11
11
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
12
12
  import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
13
+ import { validateTopo } from '@ontrails/core';
13
14
  import { buildMcpTools } from './build.js';
14
15
  import { connectStdio } from './stdio.js';
15
16
  // ---------------------------------------------------------------------------
@@ -81,13 +82,23 @@ export const createMcpServer = (tools, info) => {
81
82
  * Build MCP tools from an App, create a server, and connect via stdio.
82
83
  */
83
84
  export const blaze = async (app, options = {}) => {
84
- const tools = buildMcpTools(app, {
85
+ if (options.validate !== false) {
86
+ const validated = validateTopo(app);
87
+ if (validated.isErr()) {
88
+ throw validated.error;
89
+ }
90
+ }
91
+ const toolsResult = buildMcpTools(app, {
85
92
  createContext: options.createContext,
86
93
  excludeTrails: options.excludeTrails,
87
94
  includeTrails: options.includeTrails,
88
95
  layers: options.layers,
96
+ services: options.services,
89
97
  });
90
- const server = createMcpServer(tools, {
98
+ if (toolsResult.isErr()) {
99
+ throw toolsResult.error;
100
+ }
101
+ const server = createMcpServer(toolsResult.value, {
91
102
  name: options.serverInfo?.name ?? app.name,
92
103
  version: options.serverInfo?.version ?? '0.1.0',
93
104
  });
package/dist/blaze.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"blaze.js","sourceRoot":"","sources":["../src/blaze.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"blaze.js","sourceRoot":"","sources":["../src/blaze.ts"],"names":[],"mappings":"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"}
package/dist/build.d.ts CHANGED
@@ -5,13 +5,15 @@
5
5
  * validate input, compose layers, execute the implementation, and map
6
6
  * Results to MCP responses.
7
7
  */
8
- import type { Layer, Topo, TrailContext } from '@ontrails/core';
8
+ import { Result } from '@ontrails/core';
9
+ import type { Layer, ServiceOverrideMap, Topo, TrailContextInit } from '@ontrails/core';
9
10
  import type { McpAnnotations } from './annotations.js';
10
11
  export interface BuildMcpToolsOptions {
11
- readonly createContext?: (() => TrailContext | Promise<TrailContext>) | undefined;
12
+ readonly createContext?: (() => TrailContextInit | Promise<TrailContextInit>) | undefined;
12
13
  readonly excludeTrails?: readonly string[] | undefined;
13
14
  readonly includeTrails?: readonly string[] | undefined;
14
15
  readonly layers?: readonly Layer[] | undefined;
16
+ readonly services?: ServiceOverrideMap | undefined;
15
17
  }
16
18
  export interface McpToolDefinition {
17
19
  readonly annotations: McpAnnotations | undefined;
@@ -19,6 +21,8 @@ export interface McpToolDefinition {
19
21
  readonly handler: (args: Record<string, unknown>, extra: McpExtra) => Promise<McpToolResult>;
20
22
  readonly inputSchema: Record<string, unknown>;
21
23
  readonly name: string;
24
+ /** The trail ID this tool was derived from. */
25
+ readonly trailId: string;
22
26
  }
23
27
  export interface McpExtra {
24
28
  readonly progressToken?: string | number | undefined;
@@ -36,5 +40,5 @@ export interface McpContent {
36
40
  readonly type: 'text' | 'image' | 'resource';
37
41
  readonly uri?: string | undefined;
38
42
  }
39
- export declare const buildMcpTools: (app: Topo, options?: BuildMcpToolsOptions) => McpToolDefinition[];
43
+ export declare const buildMcpTools: (app: Topo, options?: BuildMcpToolsOptions) => Result<McpToolDefinition[], Error>;
40
44
  //# sourceMappingURL=build.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../src/build.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAQH,OAAO,KAAK,EAAE,KAAK,EAAE,IAAI,EAAS,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAEvE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AASvD,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,aAAa,CAAC,EACnB,CAAC,MAAM,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC,GAC5C,SAAS,CAAC;IACd,QAAQ,CAAC,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAAC;IACvD,QAAQ,CAAC,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAAC;IACvD,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,KAAK,EAAE,GAAG,SAAS,CAAC;CAChD;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,WAAW,EAAE,cAAc,GAAG,SAAS,CAAC;IACjD,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;IACzC,QAAQ,CAAC,OAAO,EAAE,CAChB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,KAAK,EAAE,QAAQ,KACZ,OAAO,CAAC,aAAa,CAAC,CAAC;IAC5B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;IACrD,QAAQ,CAAC,YAAY,CAAC,EAClB,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC,GACnD,SAAS,CAAC;IACd,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,SAAS,CAAC;CAC3C;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,OAAO,EAAE,SAAS,UAAU,EAAE,CAAC;IACxC,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CACxC;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACvC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,GAAG,UAAU,CAAC;IAC7C,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACnC;AA4OD,eAAO,MAAM,aAAa,GACxB,KAAK,IAAI,EACT,UAAS,oBAAyB,KACjC,iBAAiB,EAiBnB,CAAC"}
1
+ {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../src/build.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EACL,MAAM,EAKP,MAAM,gBAAgB,CAAC;AACxB,OAAO,KAAK,EAEV,KAAK,EACL,kBAAkB,EAClB,IAAI,EAEJ,gBAAgB,EACjB,MAAM,gBAAgB,CAAC;AAExB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AASvD,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,aAAa,CAAC,EACnB,CAAC,MAAM,gBAAgB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAC,GACpD,SAAS,CAAC;IACd,QAAQ,CAAC,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAAC;IACvD,QAAQ,CAAC,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAAC;IACvD,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,KAAK,EAAE,GAAG,SAAS,CAAC;IAC/C,QAAQ,CAAC,QAAQ,CAAC,EAAE,kBAAkB,GAAG,SAAS,CAAC;CACpD;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,WAAW,EAAE,cAAc,GAAG,SAAS,CAAC;IACjD,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;IACzC,QAAQ,CAAC,OAAO,EAAE,CAChB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,KAAK,EAAE,QAAQ,KACZ,OAAO,CAAC,aAAa,CAAC,CAAC;IAC5B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,+CAA+C;IAC/C,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;IACrD,QAAQ,CAAC,YAAY,CAAC,EAClB,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC,GACnD,SAAS,CAAC;IACd,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,SAAS,CAAC;CAC3C;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,OAAO,EAAE,SAAS,UAAU,EAAE,CAAC;IACxC,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CACxC;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACvC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,GAAG,UAAU,CAAC;IAC7C,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACnC;AAmQD,eAAO,MAAM,aAAa,GACxB,KAAK,IAAI,EACT,UAAS,oBAAyB,KACjC,MAAM,CAAC,iBAAiB,EAAE,EAAE,KAAK,CAoBnC,CAAC"}
package/dist/build.js CHANGED
@@ -5,18 +5,44 @@
5
5
  * validate input, compose layers, execute the implementation, and map
6
6
  * Results to MCP responses.
7
7
  */
8
- import { composeLayers, createTrailContext, validateInput, zodToJsonSchema, } from '@ontrails/core';
8
+ import { Result, ValidationError, executeTrail, isBlobRef, zodToJsonSchema, } from '@ontrails/core';
9
9
  import { deriveAnnotations } from './annotations.js';
10
10
  import { createMcpProgressCallback } from './progress.js';
11
11
  import { deriveToolName } from './tool-name.js';
12
- const isBlobRef = (value) => {
13
- if (typeof value !== 'object' || value === null) {
14
- return false;
12
+ // ---------------------------------------------------------------------------
13
+ // Internal helpers (defined before use)
14
+ // ---------------------------------------------------------------------------
15
+ /** Concatenate an array of Uint8Array chunks into a single Uint8Array. */
16
+ const concatChunks = (chunks, totalLength) => {
17
+ const result = new Uint8Array(totalLength);
18
+ let offset = 0;
19
+ for (const chunk of chunks) {
20
+ result.set(chunk, offset);
21
+ offset += chunk.length;
22
+ }
23
+ return result;
24
+ };
25
+ /** Collect a ReadableStream into a single Uint8Array. */
26
+ const collectStream = async (stream) => {
27
+ const reader = stream.getReader();
28
+ const chunks = [];
29
+ let totalLength = 0;
30
+ for (;;) {
31
+ const { done, value } = await reader.read();
32
+ if (done) {
33
+ break;
34
+ }
35
+ chunks.push(value);
36
+ totalLength += value.length;
15
37
  }
16
- const obj = value;
17
- return (obj['kind'] === 'blob' &&
18
- obj['data'] instanceof Uint8Array &&
19
- typeof obj['mimeType'] === 'string');
38
+ return concatChunks(chunks, totalLength);
39
+ };
40
+ /** Resolve BlobRef data to Uint8Array (handles ReadableStream). */
41
+ const resolveBlobData = (blob) => {
42
+ if (blob.data instanceof ReadableStream) {
43
+ return collectStream(blob.data);
44
+ }
45
+ return blob.data;
20
46
  };
21
47
  const uint8ArrayToBase64 = (bytes) => {
22
48
  // Use btoa with manual conversion for runtime-agnostic base64
@@ -26,10 +52,11 @@ const uint8ArrayToBase64 = (bytes) => {
26
52
  }
27
53
  return btoa(binary);
28
54
  };
29
- const blobToContent = (blob) => {
55
+ const blobToContent = async (blob) => {
56
+ const bytes = await resolveBlobData(blob);
30
57
  if (blob.mimeType.startsWith('image/')) {
31
58
  return {
32
- data: uint8ArrayToBase64(blob.data),
59
+ data: uint8ArrayToBase64(bytes),
33
60
  mimeType: blob.mimeType,
34
61
  type: 'image',
35
62
  };
@@ -37,18 +64,18 @@ const blobToContent = (blob) => {
37
64
  return {
38
65
  mimeType: blob.mimeType,
39
66
  type: 'resource',
40
- uri: `blob://${blob.name ?? 'unnamed'}`,
67
+ uri: `blob://${blob.name}`,
41
68
  };
42
69
  };
43
70
  /** Separate blob fields from non-blob fields in an object. */
44
- const separateBlobFields = (obj) => {
71
+ const separateBlobFields = async (obj) => {
45
72
  const blobContents = [];
46
73
  const textFields = {};
47
74
  let hasBlobFields = false;
48
75
  for (const [key, val] of Object.entries(obj)) {
49
76
  if (isBlobRef(val)) {
50
77
  hasBlobFields = true;
51
- blobContents.push(blobToContent(val));
78
+ blobContents.push(await blobToContent(val));
52
79
  }
53
80
  else {
54
81
  textFields[key] = val;
@@ -57,8 +84,8 @@ const separateBlobFields = (obj) => {
57
84
  return { blobContents, hasBlobFields, textFields };
58
85
  };
59
86
  /** Serialize a mixed blob/text object to MCP content. */
60
- const serializeMixedObject = (obj) => {
61
- const { blobContents, hasBlobFields, textFields } = separateBlobFields(obj);
87
+ const serializeMixedObject = async (obj) => {
88
+ const { blobContents, hasBlobFields, textFields } = await separateBlobFields(obj);
62
89
  if (!hasBlobFields) {
63
90
  return undefined;
64
91
  }
@@ -67,12 +94,12 @@ const serializeMixedObject = (obj) => {
67
94
  }
68
95
  return blobContents;
69
96
  };
70
- const serializeOutput = (value) => {
97
+ const serializeOutput = async (value) => {
71
98
  if (isBlobRef(value)) {
72
- return [blobToContent(value)];
99
+ return [await blobToContent(value)];
73
100
  }
74
101
  if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
75
- const mixed = serializeMixedObject(value);
102
+ const mixed = await serializeMixedObject(value);
76
103
  if (mixed) {
77
104
  return mixed;
78
105
  }
@@ -87,40 +114,19 @@ const mcpError = (message) => ({
87
114
  content: [{ text: message, type: 'text' }],
88
115
  isError: true,
89
116
  });
90
- /** Build a TrailContext from options and MCP extra. */
91
- const buildTrailContext = async (options, extra) => {
92
- const baseContext = options.createContext !== undefined && options.createContext !== null
93
- ? await options.createContext()
94
- : createTrailContext();
95
- const signal = extra.signal ?? baseContext.signal;
117
+ const createHandler = (t, layers, options) => async (args, extra) => {
96
118
  const progressCb = createMcpProgressCallback(extra);
97
- return {
98
- ...baseContext,
99
- signal,
100
- ...(progressCb === undefined ? {} : { progress: progressCb }),
101
- };
102
- };
103
- /** Execute a trail and map the result to an MCP response. */
104
- const executeAndMap = async (trail, validatedInput, ctx, layers) => {
105
- const impl = composeLayers([...layers], trail, trail.implementation);
106
- try {
107
- const result = await impl(validatedInput, ctx);
108
- if (result.isOk()) {
109
- return { content: serializeOutput(result.value) };
110
- }
111
- return mcpError(result.error.message);
112
- }
113
- catch (error) {
114
- return mcpError(error instanceof Error ? error.message : String(error));
115
- }
116
- };
117
- const createHandler = (trail, layers, options) => async (args, extra) => {
118
- const validated = validateInput(trail.input, args);
119
- if (validated.isErr()) {
120
- return mcpError(validated.error.message);
121
- }
122
- const ctx = await buildTrailContext(options, extra);
123
- return executeAndMap(trail, validated.value, ctx, layers);
119
+ const result = await executeTrail(t, args, {
120
+ createContext: options.createContext,
121
+ ctx: progressCb === undefined ? undefined : { progress: progressCb },
122
+ layers,
123
+ services: options.services,
124
+ signal: extra.signal,
125
+ });
126
+ if (result.isOk()) {
127
+ return { content: await serializeOutput(result.value) };
128
+ }
129
+ return mcpError(result.error.message);
124
130
  };
125
131
  // ---------------------------------------------------------------------------
126
132
  // Builder
@@ -131,12 +137,12 @@ const createHandler = (trail, layers, options) => async (args, extra) => {
131
137
  * Each trail in the topo becomes an McpToolDefinition with:
132
138
  * - A derived tool name (app-prefixed, underscore-delimited)
133
139
  * - JSON Schema input from zodToJsonSchema
134
- * - MCP annotations from trail markers
140
+ * - MCP annotations from trail metadata
135
141
  * - A handler that validates, composes layers, executes, and maps results
136
142
  */
137
- /** Check if a trail should be included based on markers and filters. */
143
+ /** Check if a trail should be included based on metadata and filters. */
138
144
  const shouldInclude = (trail, options) => {
139
- if (trail.markers?.['internal'] === true) {
145
+ if (trail.metadata?.['internal'] === true) {
140
146
  return false;
141
147
  }
142
148
  if (options.includeTrails !== undefined && options.includeTrails.length > 0) {
@@ -171,20 +177,32 @@ const buildToolDefinition = (app, trail, layers, options) => {
171
177
  handler: createHandler(trail, layers, options),
172
178
  inputSchema: zodToJsonSchema(trail.input),
173
179
  name: deriveToolName(app.name, trail.id),
180
+ trailId: trail.id,
174
181
  };
175
182
  };
183
+ /** Register a trail as an MCP tool, checking for name collisions. */
184
+ const registerTool = (app, trailItem, layers, options, nameToTrailId, tools) => {
185
+ const toolName = deriveToolName(app.name, trailItem.id);
186
+ const existingId = nameToTrailId.get(toolName);
187
+ if (existingId !== undefined) {
188
+ return Result.err(new ValidationError(`MCP tool-name collision: trails "${existingId}" and "${trailItem.id}" both derive the tool name "${toolName}"`));
189
+ }
190
+ nameToTrailId.set(toolName, trailItem.id);
191
+ tools.push(buildToolDefinition(app, trailItem, layers, options));
192
+ return Result.ok();
193
+ };
194
+ /** Filter topo items to eligible trails. */
195
+ const eligibleTrails = (app, options) => app.list().filter((trail) => shouldInclude(trail, options));
176
196
  export const buildMcpTools = (app, options = {}) => {
177
197
  const layers = options.layers ?? [];
178
198
  const tools = [];
179
- for (const item of app.list()) {
180
- if (item.kind !== 'trail' && item.kind !== 'hike') {
181
- continue;
182
- }
183
- if (!shouldInclude(item, options)) {
184
- continue;
199
+ const nameToTrailId = new Map();
200
+ for (const trailItem of eligibleTrails(app, options)) {
201
+ const registered = registerTool(app, trailItem, layers, options, nameToTrailId, tools);
202
+ if (registered.isErr()) {
203
+ return registered;
185
204
  }
186
- tools.push(buildToolDefinition(app, item, layers, options));
187
205
  }
188
- return tools;
206
+ return Result.ok(tools);
189
207
  };
190
208
  //# sourceMappingURL=build.js.map