@ultimat3/mcp 18.0.0 → 19.1.0

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/README.md CHANGED
@@ -114,10 +114,21 @@ export const mcp = defineAppMcp({
114
114
  resolveToken: (token) => sessions.resolveAgentToken(token),
115
115
  });
116
116
 
117
- // app.config.ts
118
- routes: [mcp.route] // POST /mcp, rate-limited per method class
119
117
  ```
120
118
 
119
+ **The web role mounts it, `As of 2026-09-05` — the contract is the file's location.** `app.config.ts`
120
+ has no `routes:` key and never had one; this page said `routes: [mcp.route]` while nothing read it,
121
+ and `POST /mcp` answered `X_ROUTE_NOT_FOUND` in every app ever scaffolded. Now: `apps/<app>/mcp.ts`
122
+ exports `mcp` (the value `defineAppMcp` returns), and both boots — `x dev` and `runRole` — mount
123
+ `POST config.ai.mcp.path` → `mcp.route.handle(request)` when `config.ai.mcp.expose` is `true`,
124
+ which is the **default**, and say `app mcp mounted` in the boot log (`x dev` prints `mcp POST /mcp`
125
+ in its summary). The http pipeline does not pre-judge the route (`auth: 'public'`,
126
+ `enforcedBy: 'handler'`): `mcp.route.handle` reads `Authorization: Bearer` through `resolveToken`
127
+ and decides per tool through the policy every other surface evaluates. `expose: true` with nothing
128
+ to mount — no file exports `mcp`, or the export was built without `resolveToken` and so carries no
129
+ `route` — logs `X_MCP_APP_UNMOUNTED` once, with the file to write; `expose: false` mounts nothing
130
+ and says nothing.
131
+
121
132
  `include: 'exposed'` reads the action and query registries instead of asking for
122
133
  `actions: [...]` / `queries: [...]` — the registries already know who opted in, and a
123
134
  second hand-maintained list is a thing that goes stale silently. The explicit arrays still
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ultimat3/mcp",
3
- "version": "18.0.0",
3
+ "version": "19.1.0",
4
4
  "description": "MCP server, dev tools, and the action-to-tool projection \u2014 one authz system, two surfaces",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -31,13 +31,13 @@
31
31
  "test": "bun test"
32
32
  },
33
33
  "dependencies": {
34
- "@ultimat3/action": "18.0.0",
35
- "@ultimat3/core": "18.0.0",
36
- "@ultimat3/entity": "18.0.0",
37
- "@ultimat3/http": "18.0.0",
38
- "@ultimat3/jobs": "18.0.0",
39
- "@ultimat3/policy": "18.0.0",
40
- "@ultimat3/query": "18.0.0",
41
- "@ultimat3/schema": "18.0.0"
34
+ "@ultimat3/action": "19.1.0",
35
+ "@ultimat3/core": "19.1.0",
36
+ "@ultimat3/entity": "19.1.0",
37
+ "@ultimat3/http": "19.1.0",
38
+ "@ultimat3/jobs": "19.1.0",
39
+ "@ultimat3/policy": "19.1.0",
40
+ "@ultimat3/query": "19.1.0",
41
+ "@ultimat3/schema": "19.1.0"
42
42
  }
43
43
  }
package/src/errors.ts CHANGED
@@ -17,6 +17,7 @@ export const MCP_ERROR_CODES = [
17
17
  'X_MCP_SCOPE_UNKNOWN',
18
18
  'X_MCP_SCOPE_CONFLICT',
19
19
  'X_MCP_RATE_LIMITED',
20
+ 'X_MCP_APP_UNMOUNTED',
20
21
  ] as const;
21
22
 
22
23
  export type McpErrorCode = (typeof MCP_ERROR_CODES)[number];
@@ -35,6 +36,7 @@ export const MCP_ERROR_TITLES: Readonly<Record<McpErrorCode, string>> = {
35
36
  X_MCP_SCOPE_UNKNOWN: 'defineAppMcp scopes a tool this server does not project',
36
37
  X_MCP_SCOPE_CONFLICT: 'two scopes claim one MCP tool',
37
38
  X_MCP_RATE_LIMITED: "the caller has spent its allowance for this request's class",
39
+ X_MCP_APP_UNMOUNTED: "the app's MCP endpoint is exposed in config and nothing can be mounted",
38
40
  };
39
41
 
40
42
  // Titles must be registered for `format()` to render the contract's first line. Every code above is
@@ -156,6 +158,33 @@ export class McpToolUndeclaredError extends UltimateError {
156
158
  * asking for the name reaches whichever copy won, which is the worst failure mode available —
157
159
  * a call that succeeds against the wrong handler and reports nothing.
158
160
  */
161
+ /**
162
+ * `config.ai.mcp.expose` is `true` — the DEFAULT — and the web role found nothing to mount at
163
+ * `config.ai.mcp.path`. Two causes, one code: no `apps/<app>/mcp.ts` exports an `mcp`, or the one
164
+ * that does was built without `resolveToken`, so `defineAppMcp` built no `route` (a token resolver
165
+ * is what turns a bearer header into an actor, and an endpoint with no way to name its caller is
166
+ * not one the framework will serve). Logged once per boot by the web role, never thrown: an app
167
+ * with no agent surface yet is a working app, and this line is the instruction for the day it
168
+ * wants one.
169
+ */
170
+ export class McpAppUnmountedError extends UltimateError {
171
+ readonly reason: 'missing' | 'no-route';
172
+ constructor(input: { reason: 'missing' | 'no-route'; path: string; file: string }) {
173
+ super({
174
+ code: 'X_MCP_APP_UNMOUNTED',
175
+ cause:
176
+ input.reason === 'missing'
177
+ ? `ai.mcp.expose is true and no ${input.file} exports mcp, so POST ${input.path} answers 404`
178
+ : `${input.file} exports mcp with no route — defineAppMcp was given no resolveToken — so POST ${input.path} answers 404`,
179
+ fix:
180
+ input.reason === 'missing'
181
+ ? `write ${input.file}: export const mcp = defineAppMcp({ include: 'exposed', resolveToken: (token) => resolveAgentToken(token) }) — or set ai: { mcp: { expose: false } } in app.config.ts`
182
+ : `add resolveToken: (token) => resolveAgentToken(token) to defineAppMcp({ ... }) in ${input.file} — the route is only built with one`,
183
+ });
184
+ this.reason = input.reason;
185
+ }
186
+ }
187
+
159
188
  export class McpToolDuplicateError extends UltimateError {
160
189
  /**
161
190
  * Which declaration each copy came from, when the projector knows — carried the way
package/src/index.ts CHANGED
@@ -33,6 +33,7 @@ export type { McpErrorCode } from './errors';
33
33
  export {
34
34
  MCP_ERROR_CODES,
35
35
  MCP_ERROR_TITLES,
36
+ McpAppUnmountedError,
36
37
  McpArgsInvalidError,
37
38
  McpNotBranchDbError,
38
39
  McpProtocolError,