@voltro/mcp 0.32.0 → 0.34.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.
@@ -0,0 +1,104 @@
1
+ import { D as e, E as t, a as n } from "./mcp-CZJe2Frv.js";
2
+ import { randomUUID as r } from "node:crypto";
3
+ //#region src/http.ts
4
+ var i = "mcp-session-id", a = "application/json", o = "text/event-stream", s = (e) => e.some((e) => e.method === "initialize"), c = async (e) => {
5
+ let t = e.newSessionId ?? r;
6
+ if (e.method === "DELETE") return {
7
+ kind: "empty",
8
+ status: 204
9
+ };
10
+ if (e.method === "GET") return e.acceptsSse ? {
11
+ kind: "sse",
12
+ events: [],
13
+ keepOpen: !0
14
+ } : {
15
+ kind: "json",
16
+ status: 405,
17
+ body: u(null, "GET requires Accept: text/event-stream")
18
+ };
19
+ if (e.method !== "POST") return {
20
+ kind: "json",
21
+ status: 405,
22
+ body: u(null, `method ${e.method} not allowed`)
23
+ };
24
+ if (e.body === void 0 || e.body === null) return {
25
+ kind: "json",
26
+ status: 400,
27
+ body: l("empty POST body")
28
+ };
29
+ let a = Array.isArray(e.body) ? e.body : [e.body], o = s(a), c = e.headers[i];
30
+ if (!o && c === void 0) return {
31
+ kind: "json",
32
+ status: 400,
33
+ body: l("missing Mcp-Session-Id header")
34
+ };
35
+ let d = o ? c ?? t() : c, f = (await Promise.all(a.map((t) => n(e.snapshot, t, e.serverInfo, e.live)))).filter((e) => e !== null);
36
+ return f.length === 0 ? {
37
+ kind: "empty",
38
+ status: 202
39
+ } : e.acceptsSse ? {
40
+ kind: "sse",
41
+ events: f,
42
+ keepOpen: !1,
43
+ ...d === void 0 ? {} : { sessionId: d }
44
+ } : {
45
+ kind: "json",
46
+ status: 200,
47
+ body: f.length === 1 ? f[0] : f,
48
+ ...d === void 0 ? {} : { sessionId: d }
49
+ };
50
+ }, l = (e) => ({
51
+ jsonrpc: "2.0",
52
+ id: null,
53
+ error: {
54
+ code: -32700,
55
+ message: e
56
+ }
57
+ }), u = (e, t) => ({
58
+ jsonrpc: "2.0",
59
+ id: e,
60
+ error: {
61
+ code: -32601,
62
+ message: t
63
+ }
64
+ }), d = (e) => `event: message\ndata: ${JSON.stringify(e)}\n\n`, f = (e) => e !== void 0 && e.split(",").some((e) => e.trim().startsWith(o)), p = async (e) => {
65
+ let t = "";
66
+ for await (let n of e) t += String(n);
67
+ if (t.trim() !== "") try {
68
+ return JSON.parse(t);
69
+ } catch {
70
+ return;
71
+ }
72
+ }, m = (n) => async (r, s) => {
73
+ let l = r.method ?? "GET", u = l === "POST" ? await p(r) : void 0, m = await n.source.get(), h = {};
74
+ for (let [e, t] of Object.entries(r.headers)) h[e.toLowerCase()] = Array.isArray(t) ? t[0] : t;
75
+ let g = await c({
76
+ method: l,
77
+ headers: h,
78
+ body: u,
79
+ snapshot: m,
80
+ serverInfo: n.serverInfo,
81
+ acceptsSse: f(h.accept),
82
+ live: n.live ?? e(t(process.env)),
83
+ ...n.newSessionId === void 0 ? {} : { newSessionId: n.newSessionId }
84
+ });
85
+ if (g.kind === "empty") {
86
+ s.writeHead(g.status), s.end();
87
+ return;
88
+ }
89
+ if (g.kind === "json") {
90
+ let e = { "content-type": a };
91
+ g.sessionId !== void 0 && (e[i] = g.sessionId), s.writeHead(g.status, e), s.end(JSON.stringify(g.body));
92
+ return;
93
+ }
94
+ let _ = {
95
+ "content-type": o,
96
+ "cache-control": "no-cache",
97
+ connection: "keep-alive"
98
+ };
99
+ g.sessionId !== void 0 && (_[i] = g.sessionId), s.writeHead(200, _);
100
+ for (let e of g.events) s.write(d(e));
101
+ g.keepOpen || s.end();
102
+ };
103
+ //#endregion
104
+ export { c as n, d as r, m as t };
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
- import { v as e } from "./mcp-DrI72Mwz.js";
3
- import { t } from "./http-CtkNxu9A.js";
4
- import { t as n } from "./serve-CRW_Na4H.js";
2
+ import { j as e } from "./mcp-CZJe2Frv.js";
3
+ import { t } from "./http-BGeW8WkP.js";
4
+ import { t as n } from "./serve-ZSmqUcRM.js";
5
5
  import { createServer as r } from "node:http";
6
6
  //#endregion
7
7
  //#region src/httpServer.ts
package/dist/index.d.ts CHANGED
@@ -1,6 +1,56 @@
1
1
  import { IncomingMessage } from 'node:http';
2
2
  import { ServerResponse } from 'node:http';
3
3
 
4
+ /** The MCP tool-name prefix for an app procedure. Reserved: a static
5
+ * `voltro_*` tool can never collide with it. */
6
+ export declare const APP_TOOL_PREFIX = "app_";
7
+
8
+ export declare interface AppToolInventory {
9
+ readonly enabled: boolean;
10
+ readonly reason?: string;
11
+ readonly tools: ReadonlyArray<AppToolListing>;
12
+ readonly dropped: ReadonlyArray<{
13
+ readonly name: string;
14
+ readonly reason: string;
15
+ }>;
16
+ }
17
+
18
+ /** One admitted app tool, as `GET /_voltro/inspect/agent/tools` reports it. */
19
+ export declare interface AppToolListing {
20
+ readonly name: string;
21
+ readonly tag: string;
22
+ readonly kind: string;
23
+ readonly description: string;
24
+ readonly write: boolean;
25
+ readonly confirm: boolean;
26
+ readonly maxPerRun?: number;
27
+ readonly input?: Record<string, unknown>;
28
+ }
29
+
30
+ /**
31
+ * `todos.create` → `app_todos_create`.
32
+ *
33
+ * MCP tool names are `[A-Za-z0-9_-]`; an rpc tag is dotted. The mapping is NOT
34
+ * reversed by string surgery at call time — a tag containing a `-` and one
35
+ * containing a `.` can produce the same name — so the call path looks the
36
+ * original up in the SAME listing it rendered from. A collision drops the later
37
+ * tag (sorted first, so the drop is deterministic) rather than silently
38
+ * shadowing one procedure with another.
39
+ */
40
+ export declare const appToolName: (tag: string) => string;
41
+
42
+ export declare const boundText: (raw: unknown, maxChars: number) => string;
43
+
44
+ /**
45
+ * `POST /_voltro/inspect/agent/call`. Returns the text the model sees.
46
+ *
47
+ * A refusal — missing credential, policy denial, the app's own guard — comes
48
+ * back as `Error: …` text rather than a thrown failure, exactly as an app tool's
49
+ * rejection does in `@voltro/ai`: a refusal IS information the model should act
50
+ * on, and hiding it behind a transport error makes the agent retry blindly.
51
+ */
52
+ export declare const callAppTool: (options: LiveAppOptions, tag: string, input: unknown) => Promise<string>;
53
+
4
54
  /** Pure tool dispatch → the text payload for an MCP `tools/call` result. */
5
55
  export declare const callTool: (snapshot: ManifestSnapshot, name: string, args: Record<string, unknown>) => string;
6
56
 
@@ -30,6 +80,14 @@ export declare const createManifestSource: (options: ManifestSourceOptions) => M
30
80
 
31
81
  export declare const EMPTY_MANIFEST: CapabilityManifest;
32
82
 
83
+ /** `GET /_voltro/inspect/agent/tools`. Never throws — a failure becomes an
84
+ * `enabled: false` inventory carrying the reason, because a model handed an
85
+ * empty tool list with no explanation concludes the app has no tools. */
86
+ export declare const fetchAppTools: (options: LiveAppOptions) => Promise<AppToolInventory>;
87
+
88
+ /** `GET /_voltro/inspect/checks` — the framework's invariant checks. */
89
+ export declare const fetchInvariantChecks: (options: LiveAppOptions) => Promise<string>;
90
+
33
91
  /** Fetch the live capability manifest from a running api. */
34
92
  export declare const fetchManifest: (baseUrl: string, token?: string,
35
93
  /** Abort the request after this long — a hung api must not hang the agent. */
@@ -49,6 +107,27 @@ export declare const getPrompt: (snapshot: ManifestSnapshot, name: string, args?
49
107
  */
50
108
  export declare const handleMcpRequest: (snapshot: ManifestSnapshot, request: JsonRpcRequest, serverInfo?: ServerInfo) => JsonRpcResponse | null;
51
109
 
110
+ /**
111
+ * The same protocol, for the methods that need a ROUND TRIP to the app.
112
+ *
113
+ * Two of them do: `tools/list` has to fold in the app's executable agent tools
114
+ * (a set the app's policy decides, not us), and `tools/call` has to reach the
115
+ * app for those tools and for `voltro_check_invariants`. Everything else is
116
+ * unchanged and delegates to the pure `handleMcpRequest` above.
117
+ *
118
+ * A SEPARATE async entry point rather than a widened return type on the pure
119
+ * one — the same split `@voltro/cli` made between `handleInspectRequest` and
120
+ * `handleInspectAsyncRequest`, for the same reason: most of the surface does no
121
+ * I/O and should not become awaitable, and the pure core stays testable without
122
+ * a transport.
123
+ *
124
+ * `live === undefined` degrades to exactly the previous behaviour: manifest
125
+ * introspection only, no app tools, and `voltro_check_invariants` says why it
126
+ * cannot answer rather than throwing an "unknown tool" a reader would misread as
127
+ * a version mismatch.
128
+ */
129
+ export declare const handleMcpRequestAsync: (snapshot: ManifestSnapshot, request: JsonRpcRequest, serverInfo?: ServerInfo, live?: LiveSurface) => Promise<JsonRpcResponse | null>;
130
+
52
131
  /** What `routeHttp` decided the transport should do. */
53
132
  export declare type HttpOutcome =
54
133
  /** Reply once with a JSON body + status (single JSON-RPC response, or an error). */
@@ -71,6 +150,8 @@ export declare type HttpOutcome =
71
150
  readonly status: number;
72
151
  };
73
152
 
153
+ export declare const isAppToolName: (name: string) => boolean;
154
+
74
155
  export declare interface JsonRpcRequest {
75
156
  readonly jsonrpc: '2.0';
76
157
  readonly id?: string | number | null;
@@ -97,6 +178,73 @@ export declare const listPrompts: () => ReadonlyArray<McpPrompt>;
97
178
  */
98
179
  export declare const listResources: (snapshot: ManifestSnapshot) => ReadonlyArray<McpResource>;
99
180
 
181
+ export declare const LIVE_BOUNDS_DEFAULTS: LiveBounds;
182
+
183
+ /** Where the app is and what we may present to it. */
184
+ export declare interface LiveAppOptions {
185
+ readonly baseUrl: string;
186
+ readonly inspectToken?: string;
187
+ /** `VOLTRO_INSPECT_WRITE_TOKEN` — required for any non-GET inspect call. */
188
+ readonly inspectWriteToken?: string;
189
+ /** `VOLTRO_AGENT_TOKEN` — the APP credential a tool call executes as. */
190
+ readonly agentToken?: string;
191
+ readonly bounds: LiveBounds;
192
+ /** Injectable for tests; defaults to the global `fetch`. */
193
+ readonly fetchImpl?: typeof fetch;
194
+ }
195
+
196
+ export declare const liveAppOptionsFromEnv: (env: Readonly<Record<string, string | undefined>>, overrides?: Partial<LiveBounds>) => LiveAppOptions;
197
+
198
+ /** Ceilings on what the app may send back into a model's context. Every one an
199
+ * env-overridable number rather than a literal buried in the code. */
200
+ export declare interface LiveBounds {
201
+ /** How many app tools may mount. Env `VOLTRO_MCP_MAX_APP_TOOLS`. */
202
+ readonly maxTools: number;
203
+ /** Ceiling on one tool description. Env `VOLTRO_MCP_MAX_DESCRIPTION_CHARS`. */
204
+ readonly maxDescriptionChars: number;
205
+ /** Ceiling on one tool-call result, in bytes. Env `VOLTRO_MCP_MAX_RESULT_BYTES`. */
206
+ readonly maxResultBytes: number;
207
+ /** Per-request deadline. Env `VOLTRO_MCP_TIMEOUT_MS`. */
208
+ readonly requestTimeoutMs: number;
209
+ }
210
+
211
+ /** The MCP tool shape, mirrored from `tools.ts` to avoid an import cycle. */
212
+ export declare interface LiveMcpTool {
213
+ readonly name: string;
214
+ readonly description: string;
215
+ readonly inputSchema: Record<string, unknown>;
216
+ }
217
+
218
+ /** What `handleMcpRequestAsync` needs. One interface so a test supplies a fake
219
+ * and never opens a socket — the same seam `McpTransport` is on the client. */
220
+ export declare interface LiveSurface {
221
+ /** The app tools mountable right now, plus everything that did not mount. */
222
+ readonly appTools: () => Promise<{
223
+ readonly mounted: ReadonlyArray<MountedAppTool>;
224
+ readonly dropped: ReadonlyArray<{
225
+ readonly name: string;
226
+ readonly reason: string;
227
+ }>;
228
+ }>;
229
+ /** Execute one, by its rpc TAG (never by the MCP name the model produced). */
230
+ readonly callAppTool: (tag: string, input: unknown) => Promise<string>;
231
+ /** The framework's invariant checks, as JSON text. */
232
+ readonly invariantChecks: () => Promise<string>;
233
+ }
234
+
235
+ /**
236
+ * A `LiveSurface` over a running app, with the tool list TTL-cached.
237
+ *
238
+ * Cached for the same reason `createManifestSource` caches: during `voltro dev`
239
+ * the set changes as files are saved, so a process-lifetime snapshot goes stale
240
+ * within a minute — and re-fetching on every `tools/call` would put a round trip
241
+ * in front of every action. The CALL is never cached.
242
+ */
243
+ export declare const makeLiveSurface: (options: LiveAppOptions, cache?: {
244
+ readonly ttlMs?: number;
245
+ readonly now?: () => number;
246
+ }) => LiveSurface;
247
+
100
248
  /** The stable URI for the whole-manifest resource. */
101
249
  export declare const MANIFEST_URI = "voltro://manifest";
102
250
 
@@ -123,6 +271,13 @@ export declare interface ManifestProcedure {
123
271
  readonly kind: 'policy';
124
272
  readonly action: string;
125
273
  readonly resourceType: string;
274
+ } | {
275
+ /** The erased `openAccess: '<why>'` decision. A procedure is guarded,
276
+ * open-on-purpose, or undecided; without this variant the middle state
277
+ * is indistinguishable from the last, and an agent reading the manifest
278
+ * cannot tell a deliberately public tool from an unreviewed one. */
279
+ readonly kind: 'open';
280
+ readonly reason: string;
126
281
  }>;
127
282
  /** Present when the descriptor is projected to a public REST endpoint. */
128
283
  readonly publicApi?: Record<string, unknown>;
@@ -168,9 +323,21 @@ export declare interface ManifestTable {
168
323
  readonly nullable: boolean;
169
324
  readonly refersTo?: string;
170
325
  readonly enum?: ReadonlyArray<string>;
326
+ /** `.serverOnly()` — never crosses any wire, and refused as mutation input.
327
+ * The only one of the three exposure axes that is a claim ABOUT the wire,
328
+ * so the only one a tool may act on by omitting a field. */
329
+ readonly serverOnly?: boolean;
330
+ /** `.encrypted()` — ciphertext at rest. NOT a wire-exposure signal. */
331
+ readonly encrypted?: boolean;
332
+ /** `.sensitive(class)` — the declared class. The export-masking axis. */
333
+ readonly sensitive?: string;
171
334
  }>;
172
335
  readonly reactive: boolean;
173
336
  readonly framework: boolean;
337
+ /** The primary-key column a row-keyed action targets, when there is one. */
338
+ readonly pkColumn?: string;
339
+ /** False when no single primary key exists — no row can be addressed. */
340
+ readonly editable?: boolean;
174
341
  }
175
342
 
176
343
  /** A `prompts/get` result. */
@@ -231,6 +398,27 @@ export declare class MissingPromptArgumentError extends Error {
231
398
  constructor(promptName: string, argument: string);
232
399
  }
233
400
 
401
+ /**
402
+ * Project the app's inventory onto MCP tools, bounded and deduplicated.
403
+ *
404
+ * The description carries the WRITE marker explicitly. A model choosing between
405
+ * `app_todos_list` and `app_todos_delete` has no other signal that one of them
406
+ * destroys data — the kind is in our type system, not in its context.
407
+ */
408
+ export declare const mountAppTools: (inventory: AppToolInventory, bounds: LiveBounds) => {
409
+ readonly mounted: ReadonlyArray<MountedAppTool>;
410
+ readonly dropped: ReadonlyArray<{
411
+ name: string;
412
+ reason: string;
413
+ }>;
414
+ };
415
+
416
+ /** An app tool as a mountable MCP tool, plus the tag to call it by. */
417
+ export declare interface MountedAppTool {
418
+ readonly tool: LiveMcpTool;
419
+ readonly tag: string;
420
+ }
421
+
234
422
  /**
235
423
  * MCP version negotiation: echo the client's requested revision when we
236
424
  * support it, otherwise answer with our latest (the client then decides
@@ -249,6 +437,9 @@ export declare interface NodeHttpHandlerOptions {
249
437
  readonly source: ManifestSource;
250
438
  readonly serverInfo: ServerInfo;
251
439
  readonly newSessionId?: () => string;
440
+ /** Defaults to a surface built from `process.env` — the same variables the
441
+ * stdio bin reads, so the two transports expose the same tools. */
442
+ readonly live?: LiveSurface;
252
443
  }
253
444
 
254
445
  export declare const PROMPTS: ReadonlyArray<McpPrompt>;
@@ -263,13 +454,21 @@ export declare const PROTOCOL_VERSION = "2025-06-18";
263
454
  */
264
455
  export declare const readResource: (snapshot: ManifestSnapshot, uri: string) => ReadonlyArray<McpResourceContents>;
265
456
 
457
+ export declare const resolveLiveBounds: (env: Readonly<Record<string, string | undefined>>, overrides?: Partial<LiveBounds>) => LiveBounds;
458
+
266
459
  /**
267
- * The pure protocol core of the Streamable HTTP transport. Given the HTTP
268
- * method, headers and parsed body, decide the response shape. Reuses the same
269
- * pure `handleMcpRequest` the stdio transport uses — the two transports share
270
- * one protocol implementation.
460
+ * The protocol core of the Streamable HTTP transport. Given the HTTP method,
461
+ * headers and parsed body, decide the response shape. Reuses the SAME
462
+ * `handleMcpRequestAsync` the stdio transport uses — the two transports share one
463
+ * protocol implementation, and that is the property that stopped an executable
464
+ * tool surface from existing on one of them only.
465
+ *
466
+ * Async because two methods now reach the app (`tools/list` folds in its agent
467
+ * tools, `tools/call` executes one). Everything else still resolves without
468
+ * touching the network. The DECISION is still pure in the sense that matters —
469
+ * no socket is bound here, and a test supplies a `live` double.
271
470
  */
272
- export declare const routeHttp: (options: RouteHttpOptions) => HttpOutcome;
471
+ export declare const routeHttp: (options: RouteHttpOptions) => Promise<HttpOutcome>;
273
472
 
274
473
  export declare interface RouteHttpOptions {
275
474
  readonly method: string;
@@ -283,6 +482,9 @@ export declare interface RouteHttpOptions {
283
482
  readonly acceptsSse: boolean;
284
483
  /** Mint a session id (override for deterministic tests). */
285
484
  readonly newSessionId?: () => string;
485
+ /** The live app surface — executable agent tools + invariant checks. Absent ⇒
486
+ * manifest introspection only, which is what this transport did before. */
487
+ readonly live?: LiveSurface;
286
488
  }
287
489
 
288
490
  /** Fallback identity. The bin overrides `version` with the value from its own
package/dist/index.js CHANGED
@@ -1,3 +1,3 @@
1
- import { _ as e, a as t, c as n, d as r, f as i, g as a, h as o, i as s, l as c, m as l, n as u, o as d, p as f, r as p, s as m, t as h, u as g, v as _, y as v } from "./mcp-DrI72Mwz.js";
2
- import { n as y, r as b, t as x } from "./http-CtkNxu9A.js";
3
- export { e as EMPTY_MANIFEST, r as MANIFEST_URI, d as MissingPromptArgumentError, m as PROMPTS, h as PROTOCOL_VERSION, u as SERVER_INFO, p as SUPPORTED_PROTOCOL_VERSIONS, o as TOOLS, n as UnknownPromptError, i as UnknownResourceError, a as callTool, _ as createManifestSource, v as fetchManifest, c as getPrompt, s as handleMcpRequest, g as listPrompts, f as listResources, t as negotiateProtocolVersion, x as nodeHttpHandler, l as readResource, y as routeHttp, b as sseEvent };
1
+ import { A as e, C as t, D as n, E as r, M as i, O as a, S as o, T as s, _ as c, a as l, b as u, c as d, d as f, f as p, g as m, h, i as g, j as _, k as v, l as y, m as b, n as x, o as S, p as C, r as w, s as T, t as E, u as D, v as O, w as k, x as A, y as j } from "./mcp-CZJe2Frv.js";
2
+ import { n as M, r as N, t as P } from "./http-BGeW8WkP.js";
3
+ export { O as APP_TOOL_PREFIX, e as EMPTY_MANIFEST, j as LIVE_BOUNDS_DEFAULTS, p as MANIFEST_URI, T as MissingPromptArgumentError, d as PROMPTS, E as PROTOCOL_VERSION, x as SERVER_INFO, w as SUPPORTED_PROTOCOL_VERSIONS, m as TOOLS, y as UnknownPromptError, C as UnknownResourceError, u as appToolName, A as boundText, o as callAppTool, c as callTool, _ as createManifestSource, t as fetchAppTools, k as fetchInvariantChecks, i as fetchManifest, D as getPrompt, g as handleMcpRequest, l as handleMcpRequestAsync, s as isAppToolName, f as listPrompts, b as listResources, r as liveAppOptionsFromEnv, n as makeLiveSurface, a as mountAppTools, S as negotiateProtocolVersion, P as nodeHttpHandler, h as readResource, v as resolveLiveBounds, M as routeHttp, N as sseEvent };