@promptev/context-engine 0.0.0 → 0.0.2

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.
Files changed (60) hide show
  1. package/README.md +116 -5
  2. package/dist/cli.js +1896 -552
  3. package/dist/cli.js.map +1 -1
  4. package/dist/{config-Bl9U789m.d.cts → config-BODDdXJ7.d.ts} +75 -16
  5. package/dist/{config-Bt9bUQqU.d.ts → config-C5RZ00W6.d.cts} +75 -16
  6. package/dist/express.cjs +925 -151
  7. package/dist/express.cjs.map +1 -1
  8. package/dist/express.d.cts +11 -4
  9. package/dist/express.d.ts +11 -4
  10. package/dist/express.js +926 -152
  11. package/dist/express.js.map +1 -1
  12. package/dist/fastify.cjs +923 -151
  13. package/dist/fastify.cjs.map +1 -1
  14. package/dist/fastify.d.cts +8 -4
  15. package/dist/fastify.d.ts +8 -4
  16. package/dist/fastify.js +924 -152
  17. package/dist/fastify.js.map +1 -1
  18. package/dist/{governance-XIScatRO.d.ts → governance-BLPK7NMe.d.ts} +8 -2
  19. package/dist/{governance-BDkcv4qZ.d.cts → governance-P9pRb4Ol.d.cts} +8 -2
  20. package/dist/graph/index.cjs +171 -59
  21. package/dist/graph/index.cjs.map +1 -1
  22. package/dist/graph/index.d.cts +5 -3
  23. package/dist/graph/index.d.ts +5 -3
  24. package/dist/graph/index.js +171 -59
  25. package/dist/graph/index.js.map +1 -1
  26. package/dist/hono.cjs +923 -151
  27. package/dist/hono.cjs.map +1 -1
  28. package/dist/hono.d.cts +8 -4
  29. package/dist/hono.d.ts +8 -4
  30. package/dist/hono.js +924 -152
  31. package/dist/hono.js.map +1 -1
  32. package/dist/index.cjs +2413 -1052
  33. package/dist/index.cjs.map +1 -1
  34. package/dist/index.d.cts +125 -107
  35. package/dist/index.d.ts +125 -107
  36. package/dist/index.js +2412 -1050
  37. package/dist/index.js.map +1 -1
  38. package/dist/mcp.cjs +100 -14
  39. package/dist/mcp.cjs.map +1 -1
  40. package/dist/mcp.d.cts +5 -0
  41. package/dist/mcp.d.ts +5 -0
  42. package/dist/mcp.js +100 -14
  43. package/dist/mcp.js.map +1 -1
  44. package/dist/migrations/sql/0003_tools.sql +2 -0
  45. package/dist/migrations/sql/0004_acl_indexes.sql +23 -2
  46. package/dist/{redaction-BmDSWJ7h.d.cts → redaction-BqD_DEUQ.d.cts} +22 -1
  47. package/dist/{redaction-BmDSWJ7h.d.ts → redaction-BqD_DEUQ.d.ts} +22 -1
  48. package/dist/redaction-presidio.d.cts +1 -1
  49. package/dist/redaction-presidio.d.ts +1 -1
  50. package/dist/{router-CrxZ2y_Z.d.ts → router-CiFwC-EN.d.cts} +17 -2
  51. package/dist/{router-OPgSoYAB.d.cts → router-D8gBzwLd.d.ts} +17 -2
  52. package/dist/skills/context-engine/SKILL.md +5 -1
  53. package/dist/storage-CJrKgJeJ.d.ts +167 -0
  54. package/dist/storage-Dvpq2xAC.d.cts +167 -0
  55. package/package.json +61 -23
  56. package/src/migrations/sql/0003_tools.sql +2 -0
  57. package/src/migrations/sql/0004_acl_indexes.sql +23 -2
  58. package/src/skills/context-engine/SKILL.md +5 -1
  59. package/dist/embeddings-B-jZ42mk.d.cts +0 -67
  60. package/dist/embeddings-DaSdAZN3.d.ts +0 -67
package/dist/mcp.cjs CHANGED
@@ -1,5 +1,9 @@
1
1
  'use strict';
2
2
 
3
+ var zod = require('zod');
4
+
5
+ // src/mcp.ts
6
+
3
7
  // src/errors.ts
4
8
  var ExtraMissingError = class extends Error {
5
9
  constructor(extra, pkg, what) {
@@ -17,17 +21,87 @@ async function requireExtra(specifier, extra, what) {
17
21
  }
18
22
  }
19
23
 
20
- // src/tools/mcp-tools.ts
24
+ // src/sentinels.ts
25
+ var TRUSTED = /* @__PURE__ */ Symbol.for("context_engine.TRUSTED");
26
+
27
+ // src/routing-core.ts
28
+ var HandlerError = class extends Error {
29
+ status;
30
+ detail;
31
+ constructor(status, detail) {
32
+ super(typeof detail === "string" ? detail : JSON.stringify(detail));
33
+ this.name = "HandlerError";
34
+ this.status = status;
35
+ this.detail = detail;
36
+ }
37
+ };
38
+ zod.z.object({
39
+ text: zod.z.string(),
40
+ name: zod.z.string(),
41
+ source_id: zod.z.string().nullable().optional(),
42
+ external_id: zod.z.string().nullable().optional(),
43
+ description: zod.z.string().nullable().optional(),
44
+ meta_data: zod.z.record(zod.z.unknown()).nullable().optional(),
45
+ acl: zod.z.array(zod.z.string()).nullable().optional(),
46
+ mode: zod.z.enum(["hybrid", "graph"]).nullable().optional(),
47
+ extract_structured: zod.z.boolean().optional().default(false),
48
+ batch: zod.z.boolean().optional().default(false)
49
+ }).strip();
50
+ zod.z.object({
51
+ acl: zod.z.array(zod.z.string()).nullable().optional(),
52
+ name: zod.z.string().nullable().optional(),
53
+ description: zod.z.string().nullable().optional(),
54
+ meta_data: zod.z.record(zod.z.unknown()).nullable().optional()
55
+ }).strict();
56
+ zod.z.object({
57
+ query: zod.z.string(),
58
+ source_ids: zod.z.array(zod.z.string()).nullable().optional(),
59
+ // Narrows WITHIN a source and INTERSECTS with source_ids — it can only
60
+ // shrink the result set (the ACL predicate still applies in the same SQL
61
+ // conjunction), so exposing it needs no authorizeAcl-style grant check.
62
+ document_ids: zod.z.array(zod.z.string()).nullable().optional(),
63
+ top_k: zod.z.number().int().optional().default(10),
64
+ mode: zod.z.enum(["hybrid", "graph"]).optional().default("hybrid"),
65
+ compress_to_tokens: zod.z.number().int().nullable().optional()
66
+ }).strip();
67
+ var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
68
+ function requireValidUuid(documentId) {
69
+ if (!UUID_RE.test(String(documentId))) {
70
+ throw new HandlerError(400, `invalid document id: ${JSON.stringify(documentId)}`);
71
+ }
72
+ }
73
+ function resolveRequestPrincipals(value, opts) {
74
+ if (value === TRUSTED) return TRUSTED;
75
+ if (value == null) {
76
+ throw new HandlerError(
77
+ 500,
78
+ `${opts.surface}: the \`principals\` dependency returned null, which means TRUSTED CALLER \u2014 it disables ACL filtering and skips the check that stops a caller filing documents under groups it does not hold. This mount is misconfigured: return [] for an unauthenticated caller, or pass TRUSTED explicitly (from @promptev/context-engine) if the surface really is trusted.`
79
+ );
80
+ }
81
+ if (!Array.isArray(value) || value.some((p) => typeof p !== "string")) {
82
+ throw new HandlerError(
83
+ 500,
84
+ `${opts.surface}: the \`principals\` dependency must return a list of principal strings, [] for an anonymous caller, or TRUSTED \u2014 got ${typeof value}.`
85
+ );
86
+ }
87
+ return value;
88
+ }
21
89
  var SCOPE_NOTE = " Results are scoped to the caller's permissions (resolved by the server's injected principals dependency \u2014 never a caller-supplied argument) and to the given source_ids, if any.";
22
90
  async function resolvePrincipalsFn(principals) {
23
91
  const result = await Promise.resolve(principals());
24
- return result;
92
+ try {
93
+ return resolveRequestPrincipals(result, { surface: "the MCP `principals` dependency" });
94
+ } catch (exc) {
95
+ if (exc instanceof HandlerError) throw new Error(exc.message, { cause: exc });
96
+ throw exc;
97
+ }
25
98
  }
26
- function registerToolGateway(mcp, engine, principals) {
99
+ function registerToolGateway(mcp, engine, principals, approvalScope = null) {
27
100
  mcp.tool(
28
101
  "search_tools",
29
102
  "Keyword search over the tools this deployment has registered (http/db/mcp/function) \u2014 the ACL-visible name, kind, description, and JSON Schema params of each match, so a caller can discover what it can then invoke with `execute_tool`." + SCOPE_NOTE,
30
- { query: { type: "string" }, limit: { type: "number" } },
103
+ // Zod raw shape see the note on the search tool in mcp.ts.
104
+ { query: zod.z.string(), limit: zod.z.number().int().optional() },
31
105
  async (...raw) => {
32
106
  const args = raw[0] ?? {};
33
107
  const callerPrincipals = await resolvePrincipalsFn(principals);
@@ -41,13 +115,15 @@ function registerToolGateway(mcp, engine, principals) {
41
115
  mcp.tool(
42
116
  "execute_tool",
43
117
  "Governed execution of a tool previously discovered via `search_tools` (ACL check, approval gate, audit). Returns `{result, usage}` on success, or an `approval_required` payload when the call needs a human approval that isn't already granted \u2014 the caller must not retry until that approval resolves." + SCOPE_NOTE,
44
- { name: { type: "string" }, args: { type: "object" } },
118
+ { name: zod.z.string(), args: zod.z.record(zod.z.unknown()).optional() },
45
119
  async (...raw) => {
46
120
  const body = raw[0] ?? {};
47
121
  const callerPrincipals = await resolvePrincipalsFn(principals);
122
+ const scope = approvalScope ? await Promise.resolve(approvalScope()) : null;
48
123
  return engine.executeTool(String(body.name), body.args ?? null, {
49
124
  principals: callerPrincipals,
50
- source: "mcp"
125
+ source: "mcp",
126
+ approvalScope: scope
51
127
  });
52
128
  }
53
129
  );
@@ -92,15 +168,24 @@ async function createMcpApp(engine, opts) {
92
168
  "search_knowledge_base",
93
169
  `Hybrid search (full-text + trigram + vector, RRF-fused) over the ingested corpus. Returns the top-matching chunks with their source document.${SCOPE_NOTE}`,
94
170
  {
95
- query: { type: "string" },
96
- source_ids: { type: "array", items: { type: "string" } },
97
- top_k: { type: "number" },
98
- mode: { type: "string", enum: ["hybrid", "graph"] }
171
+ // Zod RAW SHAPES, not JSON schema: the SDK's isZodRawShape test
172
+ // rejects a plain schema object on current SDK versions that made
173
+ // registration THROW at startup, and on 1.12.0 the object was consumed
174
+ // as annotations and every handler ran with NO arguments.
175
+ query: zod.z.string(),
176
+ source_ids: zod.z.array(zod.z.string()).optional(),
177
+ document_ids: zod.z.array(zod.z.string()).optional(),
178
+ top_k: zod.z.number().int().optional(),
179
+ mode: zod.z.enum(["hybrid", "graph"]).optional()
99
180
  },
100
181
  async (args) => {
182
+ for (const did of args.document_ids ?? []) {
183
+ requireValidUuid(did);
184
+ }
101
185
  const callerPrincipals = await resolvePrincipalsFn(opts.principals);
102
186
  const result = await engine.search(String(args.query ?? ""), {
103
187
  sourceIds: args.source_ids,
188
+ documentIds: args.document_ids,
104
189
  principals: callerPrincipals,
105
190
  topK: args.top_k ?? 10,
106
191
  mode: args.mode ?? "hybrid"
@@ -122,7 +207,7 @@ async function createMcpApp(engine, opts) {
122
207
  tool(
123
208
  "get_document",
124
209
  `Fetch one document's full text and metadata by id.${SCOPE_NOTE}`,
125
- { document_id: { type: "string" } },
210
+ { document_id: zod.z.string() },
126
211
  async (args) => {
127
212
  const callerPrincipals = await resolvePrincipalsFn(opts.principals);
128
213
  return engine.getDocument(String(args.document_id), { principals: callerPrincipals });
@@ -132,7 +217,7 @@ async function createMcpApp(engine, opts) {
132
217
  tool(
133
218
  "query_structured",
134
219
  `Answer a question against documents' extracted structured data (only documents with structured data are candidates).${SCOPE_NOTE}`,
135
- { question: { type: "string" }, source_ids: { type: "array", items: { type: "string" } } },
220
+ { question: zod.z.string(), source_ids: zod.z.array(zod.z.string()).optional() },
136
221
  async (args) => {
137
222
  const callerPrincipals = await resolvePrincipalsFn(opts.principals);
138
223
  if (!engine.queryStructured) throw new Error("queryStructured is not available on this engine");
@@ -146,7 +231,7 @@ async function createMcpApp(engine, opts) {
146
231
  tool(
147
232
  "compute",
148
233
  `Run LLM-authored JavaScript over in-scope spreadsheet documents and return the computed result.${SCOPE_NOTE}`,
149
- { instruction: { type: "string" }, source_ids: { type: "array", items: { type: "string" } } },
234
+ { instruction: zod.z.string(), source_ids: zod.z.array(zod.z.string()).optional() },
150
235
  async (args) => {
151
236
  const callerPrincipals = await resolvePrincipalsFn(opts.principals);
152
237
  if (!engine.compute) throw new Error("compute is not available on this engine");
@@ -165,7 +250,8 @@ async function createMcpApp(engine, opts) {
165
250
  }
166
251
  },
167
252
  engine,
168
- opts.principals
253
+ opts.principals,
254
+ opts.approvalScope ?? null
169
255
  );
170
256
  const handler = (async (req, res) => {
171
257
  const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: void 0 });
package/dist/mcp.cjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/errors.ts","../src/extras.ts","../src/tools/mcp-tools.ts","../src/version.ts","../src/mcp.ts"],"names":["handler"],"mappings":"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","file":"mcp.cjs","sourcesContent":["/** Action-surface errors — user-facing / data-dependent failures (not found, ACL, bad input).\n * Config/programming errors stay as TypeError / RangeError / Error.\n */\nexport class EngineActionError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"EngineActionError\";\n }\n}\n\n/** Missing vs forbidden document: identical message so callers cannot distinguish. */\nexport class DocumentNotFoundError extends Error {\n constructor(documentId: string) {\n super(`document not found: ${documentId}`);\n this.name = \"DocumentNotFoundError\";\n }\n}\n\nexport class GraphLegUnavailable extends Error {\n constructor(message = \"graph ranked list was not supplied\") {\n super(message);\n this.name = \"GraphLegUnavailable\";\n }\n}\n\nexport class ApprovalNotPending extends Error {\n constructor(message = \"approval is not pending\") {\n super(message);\n this.name = \"ApprovalNotPending\";\n }\n}\n\nexport class ApprovalExpired extends Error {\n constructor(message = \"approval has expired\") {\n super(message);\n this.name = \"ApprovalExpired\";\n }\n}\n\nexport class CodeExecutionError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"CodeExecutionError\";\n }\n}\n\nexport class CodeExecutionTimeout extends Error {\n constructor(message = \"code execution timed out\") {\n super(message);\n this.name = \"CodeExecutionTimeout\";\n }\n}\n\nexport class ExtraMissingError extends Error {\n constructor(extra: string, pkg: string, what: string) {\n super(`${what} requires the '${pkg}' package (optional extra: ${extra}): npm install ${pkg}`);\n this.name = \"ExtraMissingError\";\n }\n}\n","import { ExtraMissingError } from \"./errors.js\";\n\nexport async function tryImport<T = unknown>(specifier: string): Promise<T | null> {\n try {\n return (await import(specifier)) as T;\n } catch {\n return null;\n }\n}\n\nexport async function requireExtra<T = unknown>(specifier: string, extra: string, what: string): Promise<T> {\n try {\n return (await import(specifier)) as T;\n } catch (_err) {\n throw new ExtraMissingError(extra, specifier, what);\n }\n}\n","export type PrincipalsFn = () => unknown | Promise<unknown>;\n\nexport const SCOPE_NOTE =\n \" Results are scoped to the caller's permissions (resolved by the server's \" +\n \"injected principals dependency — never a caller-supplied argument) and to \" +\n \"the given source_ids, if any.\";\n\nexport async function resolvePrincipalsFn(principals: PrincipalsFn): Promise<string[] | null> {\n const result = await Promise.resolve(principals());\n return result as string[] | null;\n}\n\nexport type McpToolHost = {\n tool(\n name: string,\n description: string,\n schema: Record<string, unknown>,\n handler: (...args: never[]) => Promise<unknown>,\n ): unknown;\n};\n\nexport function registerToolGateway(\n mcp: McpToolHost,\n engine: {\n searchTools: (query: string, opts?: Record<string, unknown>) => Promise<unknown[]>;\n executeTool: (\n name: string,\n args: Record<string, unknown> | null,\n opts?: Record<string, unknown>,\n ) => Promise<Record<string, unknown>>;\n },\n principals: PrincipalsFn,\n): void {\n mcp.tool(\n \"search_tools\",\n \"Keyword search over the tools this deployment has registered \" +\n \"(http/db/mcp/function) — the ACL-visible name, kind, description, \" +\n \"and JSON Schema params of each match, so a caller can discover what \" +\n \"it can then invoke with `execute_tool`.\" +\n SCOPE_NOTE,\n { query: { type: \"string\" }, limit: { type: \"number\" } },\n async (...raw: never[]) => {\n const args = (raw[0] ?? {}) as { query?: string; limit?: number };\n const callerPrincipals = await resolvePrincipalsFn(principals);\n const tools = await engine.searchTools(String(args.query ?? \"\"), {\n principals: callerPrincipals,\n limit: args.limit ?? 10,\n });\n return { tools };\n },\n );\n\n mcp.tool(\n \"execute_tool\",\n \"Governed execution of a tool previously discovered via \" +\n \"`search_tools` (ACL check, approval gate, audit). Returns \" +\n \"`{result, usage}` on success, or an `approval_required` payload \" +\n \"when the call needs a human approval that isn't already granted — \" +\n \"the caller must not retry until that approval resolves.\" +\n SCOPE_NOTE,\n { name: { type: \"string\" }, args: { type: \"object\" } },\n async (...raw: never[]) => {\n const body = (raw[0] ?? {}) as { name?: string; args?: Record<string, unknown> };\n const callerPrincipals = await resolvePrincipalsFn(principals);\n return engine.executeTool(String(body.name), body.args ?? null, {\n principals: callerPrincipals,\n source: \"mcp\",\n });\n },\n );\n}\n","/** Bumped by CI on every main merge; 0.0.0 = pre-first-release. */\nexport const __version__ = \"0.0.0\";\n","import { ExtraMissingError } from \"./errors.js\";\nimport { requireExtra } from \"./extras.js\";\nimport {\n type PrincipalsFn,\n registerToolGateway,\n resolvePrincipalsFn,\n SCOPE_NOTE,\n} from \"./tools/mcp-tools.js\";\nimport { __version__ } from \"./version.js\";\n\ntype Engine = {\n config: { llm?: unknown; enableCodeExecution?: boolean };\n search: (query: string, opts?: Record<string, unknown>) => Promise<{ hits: unknown[]; usage?: unknown }>;\n getDocument: (id: string, opts?: Record<string, unknown>) => Promise<unknown>;\n queryStructured?: (question: string, opts?: Record<string, unknown>) => Promise<unknown>;\n compute?: (instruction: string, opts?: Record<string, unknown>) => Promise<unknown>;\n searchTools: (query: string, opts?: Record<string, unknown>) => Promise<unknown[]>;\n executeTool: (\n name: string,\n args: Record<string, unknown> | null,\n opts?: Record<string, unknown>,\n ) => Promise<Record<string, unknown>>;\n};\n\nfunction asMcpResult(data: unknown): { content: Array<{ type: \"text\"; text: string }> } {\n return { content: [{ type: \"text\", text: JSON.stringify(data) }] };\n}\n\nexport async function createMcpApp(\n engine: Engine,\n opts: { principals: PrincipalsFn },\n): Promise<\n ((req: import(\"node:http\").IncomingMessage, res: import(\"node:http\").ServerResponse) => Promise<void>) & {\n mcp: unknown;\n }\n> {\n if (!opts?.principals) {\n throw new TypeError(\"createMcpApp requires principals\");\n }\n\n let McpServer: new (info: {\n name: string;\n version: string;\n }) => {\n tool: (...args: unknown[]) => unknown;\n connect: (t: unknown) => Promise<void>;\n };\n let StreamableHTTPServerTransport: new (opts: {\n sessionIdGenerator?: undefined;\n }) => {\n handleRequest: (req: unknown, res: unknown, body?: unknown) => Promise<void>;\n };\n try {\n const serverMod = await requireExtra<{ McpServer: typeof McpServer }>(\n \"@modelcontextprotocol/sdk/server/mcp.js\",\n \"mcp\",\n \"MCP server\",\n );\n const httpMod = await requireExtra<{\n StreamableHTTPServerTransport: typeof StreamableHTTPServerTransport;\n }>(\"@modelcontextprotocol/sdk/server/streamableHttp.js\", \"mcp\", \"MCP server\");\n McpServer = serverMod.McpServer;\n StreamableHTTPServerTransport = httpMod.StreamableHTTPServerTransport;\n } catch (exc) {\n if (exc instanceof ExtraMissingError) throw exc;\n throw new ExtraMissingError(\"mcp\", \"@modelcontextprotocol/sdk\", \"MCP server\");\n }\n\n const mcp = new McpServer({ name: \"context-engine\", version: __version__ });\n\n const tool = (\n name: string,\n description: string,\n schema: Record<string, unknown>,\n handler: (args: Record<string, unknown>) => Promise<unknown>,\n ) => {\n mcp.tool(name, description, schema, async (args: Record<string, unknown>) =>\n asMcpResult(await handler(args)),\n );\n };\n\n tool(\n \"search_knowledge_base\",\n `Hybrid search (full-text + trigram + vector, RRF-fused) over the ingested corpus. Returns the top-matching chunks with their source document.${SCOPE_NOTE}`,\n {\n query: { type: \"string\" },\n source_ids: { type: \"array\", items: { type: \"string\" } },\n top_k: { type: \"number\" },\n mode: { type: \"string\", enum: [\"hybrid\", \"graph\"] },\n },\n async (args) => {\n const callerPrincipals = await resolvePrincipalsFn(opts.principals);\n const result = await engine.search(String(args.query ?? \"\"), {\n sourceIds: args.source_ids,\n principals: callerPrincipals,\n topK: args.top_k ?? 10,\n mode: args.mode ?? \"hybrid\",\n });\n return {\n hits: (result.hits ?? []).map((hit) => {\n const h = hit as Record<string, unknown>;\n return {\n document_name: h.document_name ?? h.documentName,\n chunk_text: h.chunk_text ?? h.chunkText,\n score: h.score,\n source_id: h.source_id ?? h.sourceId,\n };\n }),\n usage: result.usage,\n };\n },\n );\n\n tool(\n \"get_document\",\n `Fetch one document's full text and metadata by id.${SCOPE_NOTE}`,\n { document_id: { type: \"string\" } },\n async (args) => {\n const callerPrincipals = await resolvePrincipalsFn(opts.principals);\n return engine.getDocument(String(args.document_id), { principals: callerPrincipals });\n },\n );\n\n if (engine.config.llm != null) {\n tool(\n \"query_structured\",\n `Answer a question against documents' extracted structured data (only documents with structured data are candidates).${SCOPE_NOTE}`,\n { question: { type: \"string\" }, source_ids: { type: \"array\", items: { type: \"string\" } } },\n async (args) => {\n const callerPrincipals = await resolvePrincipalsFn(opts.principals);\n if (!engine.queryStructured) throw new Error(\"queryStructured is not available on this engine\");\n return engine.queryStructured(String(args.question), {\n sourceIds: args.source_ids,\n principals: callerPrincipals,\n });\n },\n );\n if (engine.config.enableCodeExecution) {\n tool(\n \"compute\",\n `Run LLM-authored JavaScript over in-scope spreadsheet documents and return the computed result.${SCOPE_NOTE}`,\n { instruction: { type: \"string\" }, source_ids: { type: \"array\", items: { type: \"string\" } } },\n async (args) => {\n const callerPrincipals = await resolvePrincipalsFn(opts.principals);\n if (!engine.compute) throw new Error(\"compute is not available on this engine\");\n return engine.compute(String(args.instruction), {\n sourceIds: args.source_ids,\n principals: callerPrincipals,\n });\n },\n );\n }\n }\n\n registerToolGateway(\n {\n tool: (name, description, schema, handler) => {\n tool(name, description, schema, async (args) => handler(args as never));\n },\n },\n engine,\n opts.principals,\n );\n\n const handler = (async (\n req: import(\"node:http\").IncomingMessage,\n res: import(\"node:http\").ServerResponse,\n ) => {\n const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined });\n await mcp.connect(transport);\n await transport.handleRequest(req, res);\n }) as ((\n req: import(\"node:http\").IncomingMessage,\n res: import(\"node:http\").ServerResponse,\n ) => Promise<void>) & { mcp: unknown };\n handler.mcp = mcp;\n return handler;\n}\n"]}
1
+ {"version":3,"sources":["../src/errors.ts","../src/extras.ts","../src/sentinels.ts","../src/routing-core.ts","../src/tools/mcp-tools.ts","../src/version.ts","../src/mcp.ts"],"names":["z","handler"],"mappings":"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","file":"mcp.cjs","sourcesContent":["/** Action-surface errors — user-facing / data-dependent failures (not found, ACL, bad input).\n * Config/programming errors stay as TypeError / RangeError / Error.\n */\nexport class EngineActionError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"EngineActionError\";\n }\n}\n\n/** Missing vs forbidden document: identical message so callers cannot distinguish. */\nexport class DocumentNotFoundError extends Error {\n constructor(documentId: string) {\n super(`document not found: ${documentId}`);\n this.name = \"DocumentNotFoundError\";\n }\n}\n\nexport class GraphLegUnavailable extends Error {\n constructor(message = \"graph ranked list was not supplied\") {\n super(message);\n this.name = \"GraphLegUnavailable\";\n }\n}\n\nexport class ApprovalNotPending extends Error {\n constructor(message = \"approval is not pending\") {\n super(message);\n this.name = \"ApprovalNotPending\";\n }\n}\n\nexport class ApprovalExpired extends Error {\n constructor(message = \"approval has expired\") {\n super(message);\n this.name = \"ApprovalExpired\";\n }\n}\n\nexport class CodeExecutionError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"CodeExecutionError\";\n }\n}\n\nexport class CodeExecutionTimeout extends Error {\n constructor(message = \"code execution timed out\") {\n super(message);\n this.name = \"CodeExecutionTimeout\";\n }\n}\n\nexport class ExtraMissingError extends Error {\n constructor(extra: string, pkg: string, what: string) {\n super(`${what} requires the '${pkg}' package (optional extra: ${extra}): npm install ${pkg}`);\n this.name = \"ExtraMissingError\";\n }\n}\n","import { ExtraMissingError } from \"./errors.js\";\n\nexport async function tryImport<T = unknown>(specifier: string): Promise<T | null> {\n try {\n return (await import(specifier)) as T;\n } catch {\n return null;\n }\n}\n\nexport async function requireExtra<T = unknown>(specifier: string, extra: string, what: string): Promise<T> {\n try {\n return (await import(specifier)) as T;\n } catch (_err) {\n throw new ExtraMissingError(extra, specifier, what);\n }\n}\n","/**\n * Sentinels distinguishing omitted arguments from real values, including null.\n *\n * UNSET: \"argument omitted\" vs null (e.g. updateDocument acl=null means unrestricted).\n * TRUSTED: trusted caller, ACL filtering disabled. Truthy on purpose so\n * `if (principals)` does not treat a trusted caller as anonymous.\n */\n\nexport const UNSET: unique symbol = Symbol.for(\"context_engine.UNSET\");\nexport type Unset = typeof UNSET;\n\n// Symbol.for (the process-global registry), NEVER a class instance: the\n// package ships per-entry bundles (tsup splitting:false), so dist/index.js\n// and dist/mcp.js each carry their own copy of this module. A class instance\n// is a different object per copy, and `result === TRUSTED` then fails for an\n// app importing TRUSTED from one entry while mounting another — every tool\n// call on a correctly configured trusted mount errors. UNSET above survives\n// bundle duplication for exactly this reason; TRUSTED must too.\nexport const TRUSTED: unique symbol = Symbol.for(\"context_engine.TRUSTED\");\nexport type Trusted = typeof TRUSTED;\n\nexport type Principals = string[] | null | typeof TRUSTED | undefined;\n\n// Once per method, matching Python's default warnings filter — before the\n// dedup, a legacy caller re-triggered the warning on EVERY request.\nconst warnedMethods = new Set<string>();\n\nexport function resolvePrincipals(value: Principals, method: string): string[] | null {\n if (value === TRUSTED) return null;\n if (value === undefined || value === null) {\n // `undefined` (omitted) is the same legacy trusted spelling as `null` —\n // Python's omitted `principals` defaults to None and warns identically.\n if (!warnedMethods.has(method)) {\n warnedMethods.add(method);\n process.emitWarning(\n `${method}(principals=${value === null ? \"null\" : \"undefined\"}) means TRUSTED CALLER — ` +\n `access control is disabled and every document is returned. If that is what ` +\n `you want, pass principals=TRUSTED (from @promptev/context-engine) to say so ` +\n `explicitly. If you meant 'no authenticated user', pass principals=[] instead. ` +\n `Passing null/omitting will raise in 1.0.`,\n { type: \"DeprecationWarning\", code: \"CE_PRINCIPALS_NULL\" },\n );\n }\n return null;\n }\n if (!Array.isArray(value)) {\n // NEVER coerce: a bare string (or any other object) coercing to null\n // used to mean full-corpus TRUSTED — an ACL bypass a caller could hit\n // with one wrong type. Fail loudly instead.\n throw new TypeError(\n `${method}(principals=...) must be an array of principal strings, [] for an ` +\n `anonymous caller, or TRUSTED — got ${typeof value}. A non-array value must ` +\n `never silently disable ACL filtering.`,\n );\n }\n return value;\n}\n\nexport function isTrusted(principals: string[] | null | undefined): boolean {\n return principals === null || principals === undefined;\n}\n","import { z } from \"zod\";\nimport { TRUSTED, type Trusted } from \"./sentinels.js\";\n\nexport class HandlerError extends Error {\n status: number;\n detail: unknown;\n constructor(status: number, detail: unknown) {\n super(typeof detail === \"string\" ? detail : JSON.stringify(detail));\n this.name = \"HandlerError\";\n this.status = status;\n this.detail = detail;\n }\n}\n\nexport const ingestJsonRequestSchema = z\n .object({\n text: z.string(),\n name: z.string(),\n source_id: z.string().nullable().optional(),\n external_id: z.string().nullable().optional(),\n description: z.string().nullable().optional(),\n meta_data: z.record(z.unknown()).nullable().optional(),\n acl: z.array(z.string()).nullable().optional(),\n mode: z.enum([\"hybrid\", \"graph\"]).nullable().optional(),\n extract_structured: z.boolean().optional().default(false),\n batch: z.boolean().optional().default(false),\n })\n .strip();\n\nexport const documentPatchSchema = z\n .object({\n acl: z.array(z.string()).nullable().optional(),\n name: z.string().nullable().optional(),\n description: z.string().nullable().optional(),\n meta_data: z.record(z.unknown()).nullable().optional(),\n })\n .strict();\n\nexport const searchRequestSchema = z\n .object({\n query: z.string(),\n source_ids: z.array(z.string()).nullable().optional(),\n // Narrows WITHIN a source and INTERSECTS with source_ids — it can only\n // shrink the result set (the ACL predicate still applies in the same SQL\n // conjunction), so exposing it needs no authorizeAcl-style grant check.\n document_ids: z.array(z.string()).nullable().optional(),\n top_k: z.number().int().optional().default(10),\n mode: z.enum([\"hybrid\", \"graph\"]).optional().default(\"hybrid\"),\n compress_to_tokens: z.number().int().nullable().optional(),\n })\n .strip();\n\nexport type IngestJSONRequest = z.infer<typeof ingestJsonRequestSchema>;\nexport type DocumentPatch = z.infer<typeof documentPatchSchema>;\nexport type SearchRequest = z.infer<typeof searchRequestSchema>;\n\nexport type ContextEngineLike = {\n ingest: (args: Record<string, unknown>) => Promise<unknown>;\n search: (query: string, opts?: Record<string, unknown>) => Promise<{ hits?: unknown[]; usage?: unknown }>;\n listDocuments: (opts: Record<string, unknown>) => Promise<unknown>;\n getDocument: (id: string, opts?: Record<string, unknown>) => Promise<unknown>;\n deleteDocument: (id: string, opts?: Record<string, unknown>) => Promise<unknown>;\n updateDocument: (id: string, opts?: Record<string, unknown>) => Promise<unknown>;\n stats: (sourceId?: string | null) => Promise<unknown>;\n};\n\nconst UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n\nexport function requireValidUuid(documentId: string): void {\n if (!UUID_RE.test(String(documentId))) {\n throw new HandlerError(400, `invalid document id: ${JSON.stringify(documentId)}`);\n }\n}\n\nexport function formBool(value: unknown): boolean {\n if (typeof value === \"boolean\") return value;\n if (value == null) return false;\n return [\"1\", \"true\", \"yes\", \"on\"].includes(String(value).trim().toLowerCase());\n}\n\nexport function formJson(value: unknown): unknown {\n if (value === undefined || value === null || value === \"\") return null;\n if (typeof value === \"object\") return value;\n try {\n return JSON.parse(String(value));\n } catch {\n throw new HandlerError(400, `invalid JSON in field: ${JSON.stringify(value)}`);\n }\n}\n\nexport function formStr(value: unknown): string | null {\n return value === undefined || value === null || value === \"\" ? null : String(value);\n}\n\nexport function resolveRequestPrincipals(value: unknown, opts: { surface: string }): string[] | Trusted {\n // Hand the SENTINEL through, not null: every engine method resolves TRUSTED\n // silently, while `principals=null` triggers the deprecation warning aimed\n // at legacy in-process callers — collapsing here made a correctly\n // configured trusted mount re-trigger that warning on every request.\n if (value === TRUSTED) return TRUSTED;\n if (value == null) {\n throw new HandlerError(\n 500,\n `${opts.surface}: the \\`principals\\` dependency returned null, which means ` +\n `TRUSTED CALLER — it disables ACL filtering and skips the check that ` +\n `stops a caller filing documents under groups it does not hold. This ` +\n `mount is misconfigured: return [] for an unauthenticated caller, or ` +\n `pass TRUSTED explicitly (from @promptev/context-engine) if the surface really ` +\n `is trusted.`,\n );\n }\n // A runtime check, not a cast: `as string[]` let a plain-JS dependency\n // return a bare string that flowed into resolvePrincipals and (before its\n // own guard) coerced to full-corpus TRUSTED.\n if (!Array.isArray(value) || value.some((p) => typeof p !== \"string\")) {\n throw new HandlerError(\n 500,\n `${opts.surface}: the \\`principals\\` dependency must return a list of ` +\n `principal strings, [] for an anonymous caller, or TRUSTED — got ${typeof value}.`,\n );\n }\n return value;\n}\n\nexport function authorizeAcl(requested: unknown, principals: string[] | null | Trusted): void {\n if (requested == null || principals == null || principals === TRUSTED) return;\n if (!Array.isArray(requested)) {\n throw new HandlerError(422, \"acl must be a list of principal strings\");\n }\n // TRUSTED was excluded above, so what remains is the plain list.\n const held = new Set(principals as string[]);\n const ungranted = [...new Set(requested.map(String))].filter((p) => !held.has(p)).sort();\n if (ungranted.length) {\n throw new HandlerError(403, `cannot grant access to principals you do not hold: ${ungranted}`);\n }\n}\n\nfunction asDict(obj: unknown): unknown {\n if (obj == null) return obj;\n if (typeof obj === \"object\" && typeof (obj as { toJSON?: () => unknown }).toJSON === \"function\") {\n return (obj as { toJSON: () => unknown }).toJSON();\n }\n return obj;\n}\n\nfunction zodError(err: z.ZodError): HandlerError {\n return new HandlerError(\n 422,\n err.issues.map((i) => ({ loc: i.path, msg: i.message, type: i.code })),\n );\n}\n\nasync function runIngest(engine: ContextEngineLike, kwargs: Record<string, unknown>): Promise<unknown> {\n try {\n return asDict(await engine.ingest(kwargs));\n } catch (exc) {\n if (exc instanceof TypeError || (exc instanceof Error && exc.name === \"TypeError\")) {\n throw new HandlerError(400, String(exc));\n }\n if (exc instanceof Error && exc.name === \"Error\" && /invalid|required/i.test(exc.message)) {\n throw new HandlerError(400, String(exc));\n }\n throw exc;\n }\n}\n\nexport async function handleIngestJson(\n engine: ContextEngineLike,\n payload: unknown,\n principals: unknown,\n): Promise<unknown> {\n const resolved = resolveRequestPrincipals(principals, { surface: \"handle_ingest_json\" });\n const parsed = ingestJsonRequestSchema.safeParse(payload);\n if (!parsed.success) throw zodError(parsed.error);\n const body = parsed.data;\n authorizeAcl(body.acl, resolved);\n return runIngest(engine, {\n text: body.text,\n name: body.name,\n description: body.description,\n sourceId: body.source_id,\n externalId: body.external_id,\n metaData: body.meta_data,\n acl: body.acl,\n mode: body.mode,\n extractStructured: body.extract_structured,\n batch: body.batch,\n });\n}\n\nexport async function handleIngestFile(\n engine: ContextEngineLike,\n opts: {\n content: Buffer | Uint8Array;\n filename: string | null | undefined;\n form: Record<string, unknown> | Map<string, unknown>;\n principals: unknown;\n },\n): Promise<unknown> {\n const resolved = resolveRequestPrincipals(opts.principals, { surface: \"handle_ingest_file\" });\n const form = opts.form instanceof Map ? Object.fromEntries(opts.form) : opts.form;\n const acl = formJson(form.acl);\n authorizeAcl(acl, resolved);\n return runIngest(engine, {\n content: opts.content,\n filename: opts.filename,\n name: formStr(form.name) || opts.filename,\n description: formStr(form.description),\n sourceId: formStr(form.source_id),\n externalId: formStr(form.external_id),\n metaData: formJson(form.meta_data),\n acl,\n mode: formStr(form.mode),\n extractStructured: formBool(form.extract_structured),\n batch: formBool(form.batch),\n });\n}\n\nexport async function handleListDocuments(\n engine: ContextEngineLike,\n opts: {\n sourceId: string | null | undefined;\n limit: number;\n cursor: string | null | undefined;\n principals: unknown;\n },\n): Promise<unknown> {\n const resolved = resolveRequestPrincipals(opts.principals, { surface: \"handle_list_documents\" });\n let parsedCursor: unknown = null;\n if (opts.cursor) {\n try {\n parsedCursor = JSON.parse(opts.cursor);\n } catch {\n throw new HandlerError(400, `invalid cursor: ${JSON.stringify(opts.cursor)}`);\n }\n }\n return engine.listDocuments({\n sourceId: opts.sourceId ?? null,\n principals: resolved,\n cursor: parsedCursor,\n limit: opts.limit,\n });\n}\n\nexport async function handleGetDocument(\n engine: ContextEngineLike,\n documentId: string,\n principals: unknown,\n): Promise<unknown> {\n const resolved = resolveRequestPrincipals(principals, { surface: \"handle_get_document\" });\n requireValidUuid(documentId);\n try {\n return await engine.getDocument(documentId, { principals: resolved });\n } catch (exc) {\n if (\n exc instanceof Error &&\n (exc.name === \"DocumentNotFoundError\" || exc instanceof TypeError === false)\n ) {\n const msg = String(exc);\n if (/not found/i.test(msg) || exc.name === \"DocumentNotFoundError\") {\n throw new HandlerError(404, msg.replace(/^Error:\\s*/, \"\"));\n }\n }\n throw exc;\n }\n}\n\nexport async function handleDeleteDocument(\n engine: ContextEngineLike,\n documentId: string,\n principals: unknown,\n): Promise<unknown> {\n const resolved = resolveRequestPrincipals(principals, { surface: \"handle_delete_document\" });\n requireValidUuid(documentId);\n try {\n await engine.deleteDocument(documentId, { principals: resolved });\n } catch (exc) {\n const msg = String(exc);\n if (/not found/i.test(msg) || (exc instanceof Error && exc.name === \"DocumentNotFoundError\")) {\n throw new HandlerError(404, msg.replace(/^Error:\\s*/, \"\"));\n }\n throw exc;\n }\n return { deleted: documentId };\n}\n\nexport async function handleUpdateDocument(\n engine: ContextEngineLike,\n documentId: string,\n payload: unknown,\n principals: unknown,\n): Promise<unknown> {\n const resolved = resolveRequestPrincipals(principals, { surface: \"handle_update_document\" });\n requireValidUuid(documentId);\n const parsed = documentPatchSchema.safeParse(payload);\n if (!parsed.success) throw zodError(parsed.error);\n const provided: Record<string, unknown> = {};\n const raw = payload as Record<string, unknown>;\n for (const key of [\"acl\", \"name\", \"description\", \"meta_data\"] as const) {\n if (Object.hasOwn(raw, key)) {\n provided[key === \"meta_data\" ? \"metaData\" : key] = raw[key];\n }\n }\n if (\"acl\" in provided) authorizeAcl(provided.acl, resolved);\n try {\n const changed = await engine.updateDocument(documentId, { principals: resolved, ...provided });\n return { changed };\n } catch (exc) {\n const msg = String(exc);\n if (/not found/i.test(msg) || (exc instanceof Error && exc.name === \"DocumentNotFoundError\")) {\n throw new HandlerError(404, msg.replace(/^Error:\\s*/, \"\"));\n }\n throw exc;\n }\n}\n\nexport async function handleSearch(\n engine: ContextEngineLike,\n payload: unknown,\n principals: unknown,\n): Promise<unknown> {\n const resolved = resolveRequestPrincipals(principals, { surface: \"handle_search\" });\n const parsed = searchRequestSchema.safeParse(payload);\n if (!parsed.success) throw zodError(parsed.error);\n const body = parsed.data;\n // Early, like every sibling document endpoint: a malformed id must be a\n // clean 400 here, not a Postgres 22P02 raised inside every search leg —\n // whose 400-vs-500 mapping would otherwise hang on the ENGLISH pg error\n // text, with the raw DatabaseError echoed to the caller.\n for (const did of body.document_ids ?? []) requireValidUuid(did);\n try {\n const result = await engine.search(body.query, {\n sourceIds: body.source_ids,\n documentIds: body.document_ids,\n principals: resolved,\n topK: body.top_k,\n mode: body.mode,\n compressToTokens: body.compress_to_tokens,\n });\n const hits = (result.hits ?? []).map((hit) => asDict(hit));\n return { hits, usage: result.usage };\n } catch (exc) {\n if (exc instanceof Error && /invalid|required|graph/i.test(exc.message)) {\n throw new HandlerError(400, String(exc));\n }\n throw exc;\n }\n}\n\nexport async function handleStats(\n engine: ContextEngineLike,\n sourceId: string | null | undefined,\n): Promise<unknown> {\n return engine.stats(sourceId ?? null);\n}\n","import { z } from \"zod\";\nimport { HandlerError, resolveRequestPrincipals } from \"../routing-core.js\";\nimport type { Trusted } from \"../sentinels.js\";\n\nexport type PrincipalsFn = () => unknown | Promise<unknown>;\n/** Zero-argument, sync or async, resolved fresh per execution — the same\n * contract as `PrincipalsFn`. Returns the opaque approval scope (a run id is\n * the expected shape) or `null` for the unscoped legacy path. */\nexport type ApprovalScopeFn = () => unknown | Promise<unknown>;\n\nexport const SCOPE_NOTE =\n \" Results are scoped to the caller's permissions (resolved by the server's \" +\n \"injected principals dependency — never a caller-supplied argument) and to \" +\n \"the given source_ids, if any.\";\n\nexport async function resolvePrincipalsFn(principals: PrincipalsFn): Promise<string[] | Trusted> {\n const result = await Promise.resolve(principals());\n // Validation DELEGATES to routing-core's resolveRequestPrincipals — the\n // single canonical remote-surface validator (TRUSTED passes through, null\n // and non-arrays are rejected) — rather than hand-copying its rules: a\n // rule added to the canonical validator must not silently miss the MCP\n // surface. Its HandlerError is an HTTP shape, so it is re-raised as a\n // plain Error with the same detail — the MCP host converts that to the\n // isError=true result this surface's contract promises.\n try {\n return resolveRequestPrincipals(result, { surface: \"the MCP `principals` dependency\" });\n } catch (exc) {\n if (exc instanceof HandlerError) throw new Error(exc.message, { cause: exc });\n throw exc;\n }\n}\n\nexport type McpToolHost = {\n tool(\n name: string,\n description: string,\n schema: Record<string, unknown>,\n handler: (...args: never[]) => Promise<unknown>,\n ): unknown;\n};\n\nexport function registerToolGateway(\n mcp: McpToolHost,\n engine: {\n searchTools: (query: string, opts?: Record<string, unknown>) => Promise<unknown[]>;\n executeTool: (\n name: string,\n args: Record<string, unknown> | null,\n opts?: Record<string, unknown>,\n ) => Promise<Record<string, unknown>>;\n },\n principals: PrincipalsFn,\n /** Optional server-side resolver for the approval claim scope (see\n * governance `executeTool`). Like `principals`, NEVER a tool argument: a\n * model that could name its own scope could claim another run's\n * approvals. Validation is the library's, so a wrong type surfaces as the\n * same isError=true result any other host-side wiring bug does. */\n approvalScope: ApprovalScopeFn | null = null,\n): void {\n mcp.tool(\n \"search_tools\",\n \"Keyword search over the tools this deployment has registered \" +\n \"(http/db/mcp/function) — the ACL-visible name, kind, description, \" +\n \"and JSON Schema params of each match, so a caller can discover what \" +\n \"it can then invoke with `execute_tool`.\" +\n SCOPE_NOTE,\n // Zod raw shape — see the note on the search tool in mcp.ts.\n { query: z.string(), limit: z.number().int().optional() },\n async (...raw: never[]) => {\n const args = (raw[0] ?? {}) as { query?: string; limit?: number };\n const callerPrincipals = await resolvePrincipalsFn(principals);\n const tools = await engine.searchTools(String(args.query ?? \"\"), {\n principals: callerPrincipals,\n limit: args.limit ?? 10,\n });\n return { tools };\n },\n );\n\n mcp.tool(\n \"execute_tool\",\n \"Governed execution of a tool previously discovered via \" +\n \"`search_tools` (ACL check, approval gate, audit). Returns \" +\n \"`{result, usage}` on success, or an `approval_required` payload \" +\n \"when the call needs a human approval that isn't already granted — \" +\n \"the caller must not retry until that approval resolves.\" +\n SCOPE_NOTE,\n { name: z.string(), args: z.record(z.unknown()).optional() },\n async (...raw: never[]) => {\n const body = (raw[0] ?? {}) as { name?: string; args?: Record<string, unknown> };\n const callerPrincipals = await resolvePrincipalsFn(principals);\n const scope = approvalScope ? await Promise.resolve(approvalScope()) : null;\n return engine.executeTool(String(body.name), body.args ?? null, {\n principals: callerPrincipals,\n source: \"mcp\",\n approvalScope: scope,\n });\n },\n );\n}\n","/** Bumped by CI on every main merge; 0.0.0 = pre-first-release. */\nexport const __version__ = \"0.0.0\";\n","import { z } from \"zod\";\nimport { ExtraMissingError } from \"./errors.js\";\nimport { requireExtra } from \"./extras.js\";\nimport { requireValidUuid } from \"./routing-core.js\";\nimport {\n type ApprovalScopeFn,\n type PrincipalsFn,\n registerToolGateway,\n resolvePrincipalsFn,\n SCOPE_NOTE,\n} from \"./tools/mcp-tools.js\";\nimport { __version__ } from \"./version.js\";\n\ntype Engine = {\n config: { llm?: unknown; enableCodeExecution?: boolean };\n search: (query: string, opts?: Record<string, unknown>) => Promise<{ hits: unknown[]; usage?: unknown }>;\n getDocument: (id: string, opts?: Record<string, unknown>) => Promise<unknown>;\n queryStructured?: (question: string, opts?: Record<string, unknown>) => Promise<unknown>;\n compute?: (instruction: string, opts?: Record<string, unknown>) => Promise<unknown>;\n searchTools: (query: string, opts?: Record<string, unknown>) => Promise<unknown[]>;\n executeTool: (\n name: string,\n args: Record<string, unknown> | null,\n opts?: Record<string, unknown>,\n ) => Promise<Record<string, unknown>>;\n};\n\nfunction asMcpResult(data: unknown): { content: Array<{ type: \"text\"; text: string }> } {\n return { content: [{ type: \"text\", text: JSON.stringify(data) }] };\n}\n\nexport async function createMcpApp(\n engine: Engine,\n opts: { principals: PrincipalsFn; approvalScope?: ApprovalScopeFn | null },\n): Promise<\n ((req: import(\"node:http\").IncomingMessage, res: import(\"node:http\").ServerResponse) => Promise<void>) & {\n mcp: unknown;\n }\n> {\n if (!opts?.principals) {\n throw new TypeError(\"createMcpApp requires principals\");\n }\n\n let McpServer: new (info: {\n name: string;\n version: string;\n }) => {\n tool: (...args: unknown[]) => unknown;\n connect: (t: unknown) => Promise<void>;\n };\n let StreamableHTTPServerTransport: new (opts: {\n sessionIdGenerator?: undefined;\n }) => {\n handleRequest: (req: unknown, res: unknown, body?: unknown) => Promise<void>;\n };\n try {\n const serverMod = await requireExtra<{ McpServer: typeof McpServer }>(\n \"@modelcontextprotocol/sdk/server/mcp.js\",\n \"mcp\",\n \"MCP server\",\n );\n const httpMod = await requireExtra<{\n StreamableHTTPServerTransport: typeof StreamableHTTPServerTransport;\n }>(\"@modelcontextprotocol/sdk/server/streamableHttp.js\", \"mcp\", \"MCP server\");\n McpServer = serverMod.McpServer;\n StreamableHTTPServerTransport = httpMod.StreamableHTTPServerTransport;\n } catch (exc) {\n if (exc instanceof ExtraMissingError) throw exc;\n throw new ExtraMissingError(\"mcp\", \"@modelcontextprotocol/sdk\", \"MCP server\");\n }\n\n const mcp = new McpServer({ name: \"context-engine\", version: __version__ });\n\n const tool = (\n name: string,\n description: string,\n schema: Record<string, unknown>,\n handler: (args: Record<string, unknown>) => Promise<unknown>,\n ) => {\n mcp.tool(name, description, schema, async (args: Record<string, unknown>) =>\n asMcpResult(await handler(args)),\n );\n };\n\n tool(\n \"search_knowledge_base\",\n `Hybrid search (full-text + trigram + vector, RRF-fused) over the ingested corpus. Returns the top-matching chunks with their source document.${SCOPE_NOTE}`,\n {\n // Zod RAW SHAPES, not JSON schema: the SDK's isZodRawShape test\n // rejects a plain schema object — on current SDK versions that made\n // registration THROW at startup, and on 1.12.0 the object was consumed\n // as annotations and every handler ran with NO arguments.\n query: z.string(),\n source_ids: z.array(z.string()).optional(),\n document_ids: z.array(z.string()).optional(),\n top_k: z.number().int().optional(),\n mode: z.enum([\"hybrid\", \"graph\"]).optional(),\n },\n async (args) => {\n // Early, mirroring the HTTP handler: a malformed id must surface as a\n // clean tool error, not a Postgres 22P02 from inside every leg.\n for (const did of (args.document_ids as string[] | undefined) ?? []) {\n requireValidUuid(did);\n }\n const callerPrincipals = await resolvePrincipalsFn(opts.principals);\n const result = await engine.search(String(args.query ?? \"\"), {\n sourceIds: args.source_ids,\n documentIds: args.document_ids,\n principals: callerPrincipals,\n topK: args.top_k ?? 10,\n mode: args.mode ?? \"hybrid\",\n });\n return {\n hits: (result.hits ?? []).map((hit) => {\n const h = hit as Record<string, unknown>;\n return {\n document_name: h.document_name ?? h.documentName,\n chunk_text: h.chunk_text ?? h.chunkText,\n score: h.score,\n source_id: h.source_id ?? h.sourceId,\n };\n }),\n usage: result.usage,\n };\n },\n );\n\n tool(\n \"get_document\",\n `Fetch one document's full text and metadata by id.${SCOPE_NOTE}`,\n { document_id: z.string() },\n async (args) => {\n const callerPrincipals = await resolvePrincipalsFn(opts.principals);\n return engine.getDocument(String(args.document_id), { principals: callerPrincipals });\n },\n );\n\n if (engine.config.llm != null) {\n tool(\n \"query_structured\",\n `Answer a question against documents' extracted structured data (only documents with structured data are candidates).${SCOPE_NOTE}`,\n { question: z.string(), source_ids: z.array(z.string()).optional() },\n async (args) => {\n const callerPrincipals = await resolvePrincipalsFn(opts.principals);\n if (!engine.queryStructured) throw new Error(\"queryStructured is not available on this engine\");\n return engine.queryStructured(String(args.question), {\n sourceIds: args.source_ids,\n principals: callerPrincipals,\n });\n },\n );\n if (engine.config.enableCodeExecution) {\n tool(\n \"compute\",\n `Run LLM-authored JavaScript over in-scope spreadsheet documents and return the computed result.${SCOPE_NOTE}`,\n { instruction: z.string(), source_ids: z.array(z.string()).optional() },\n async (args) => {\n const callerPrincipals = await resolvePrincipalsFn(opts.principals);\n if (!engine.compute) throw new Error(\"compute is not available on this engine\");\n return engine.compute(String(args.instruction), {\n sourceIds: args.source_ids,\n principals: callerPrincipals,\n });\n },\n );\n }\n }\n\n registerToolGateway(\n {\n tool: (name, description, schema, handler) => {\n tool(name, description, schema, async (args) => handler(args as never));\n },\n },\n engine,\n opts.principals,\n opts.approvalScope ?? null,\n );\n\n const handler = (async (\n req: import(\"node:http\").IncomingMessage,\n res: import(\"node:http\").ServerResponse,\n ) => {\n const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined });\n await mcp.connect(transport);\n await transport.handleRequest(req, res);\n }) as ((\n req: import(\"node:http\").IncomingMessage,\n res: import(\"node:http\").ServerResponse,\n ) => Promise<void>) & { mcp: unknown };\n handler.mcp = mcp;\n return handler;\n}\n"]}
package/dist/mcp.d.cts CHANGED
@@ -1,6 +1,10 @@
1
1
  import * as node_http from 'node:http';
2
2
 
3
3
  type PrincipalsFn = () => unknown | Promise<unknown>;
4
+ /** Zero-argument, sync or async, resolved fresh per execution — the same
5
+ * contract as `PrincipalsFn`. Returns the opaque approval scope (a run id is
6
+ * the expected shape) or `null` for the unscoped legacy path. */
7
+ type ApprovalScopeFn = () => unknown | Promise<unknown>;
4
8
 
5
9
  type Engine = {
6
10
  config: {
@@ -19,6 +23,7 @@ type Engine = {
19
23
  };
20
24
  declare function createMcpApp(engine: Engine, opts: {
21
25
  principals: PrincipalsFn;
26
+ approvalScope?: ApprovalScopeFn | null;
22
27
  }): Promise<((req: node_http.IncomingMessage, res: node_http.ServerResponse) => Promise<void>) & {
23
28
  mcp: unknown;
24
29
  }>;
package/dist/mcp.d.ts CHANGED
@@ -1,6 +1,10 @@
1
1
  import * as node_http from 'node:http';
2
2
 
3
3
  type PrincipalsFn = () => unknown | Promise<unknown>;
4
+ /** Zero-argument, sync or async, resolved fresh per execution — the same
5
+ * contract as `PrincipalsFn`. Returns the opaque approval scope (a run id is
6
+ * the expected shape) or `null` for the unscoped legacy path. */
7
+ type ApprovalScopeFn = () => unknown | Promise<unknown>;
4
8
 
5
9
  type Engine = {
6
10
  config: {
@@ -19,6 +23,7 @@ type Engine = {
19
23
  };
20
24
  declare function createMcpApp(engine: Engine, opts: {
21
25
  principals: PrincipalsFn;
26
+ approvalScope?: ApprovalScopeFn | null;
22
27
  }): Promise<((req: node_http.IncomingMessage, res: node_http.ServerResponse) => Promise<void>) & {
23
28
  mcp: unknown;
24
29
  }>;
package/dist/mcp.js CHANGED
@@ -1,3 +1,7 @@
1
+ import { z } from 'zod';
2
+
3
+ // src/mcp.ts
4
+
1
5
  // src/errors.ts
2
6
  var ExtraMissingError = class extends Error {
3
7
  constructor(extra, pkg, what) {
@@ -15,17 +19,87 @@ async function requireExtra(specifier, extra, what) {
15
19
  }
16
20
  }
17
21
 
18
- // src/tools/mcp-tools.ts
22
+ // src/sentinels.ts
23
+ var TRUSTED = /* @__PURE__ */ Symbol.for("context_engine.TRUSTED");
24
+
25
+ // src/routing-core.ts
26
+ var HandlerError = class extends Error {
27
+ status;
28
+ detail;
29
+ constructor(status, detail) {
30
+ super(typeof detail === "string" ? detail : JSON.stringify(detail));
31
+ this.name = "HandlerError";
32
+ this.status = status;
33
+ this.detail = detail;
34
+ }
35
+ };
36
+ z.object({
37
+ text: z.string(),
38
+ name: z.string(),
39
+ source_id: z.string().nullable().optional(),
40
+ external_id: z.string().nullable().optional(),
41
+ description: z.string().nullable().optional(),
42
+ meta_data: z.record(z.unknown()).nullable().optional(),
43
+ acl: z.array(z.string()).nullable().optional(),
44
+ mode: z.enum(["hybrid", "graph"]).nullable().optional(),
45
+ extract_structured: z.boolean().optional().default(false),
46
+ batch: z.boolean().optional().default(false)
47
+ }).strip();
48
+ z.object({
49
+ acl: z.array(z.string()).nullable().optional(),
50
+ name: z.string().nullable().optional(),
51
+ description: z.string().nullable().optional(),
52
+ meta_data: z.record(z.unknown()).nullable().optional()
53
+ }).strict();
54
+ z.object({
55
+ query: z.string(),
56
+ source_ids: z.array(z.string()).nullable().optional(),
57
+ // Narrows WITHIN a source and INTERSECTS with source_ids — it can only
58
+ // shrink the result set (the ACL predicate still applies in the same SQL
59
+ // conjunction), so exposing it needs no authorizeAcl-style grant check.
60
+ document_ids: z.array(z.string()).nullable().optional(),
61
+ top_k: z.number().int().optional().default(10),
62
+ mode: z.enum(["hybrid", "graph"]).optional().default("hybrid"),
63
+ compress_to_tokens: z.number().int().nullable().optional()
64
+ }).strip();
65
+ var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
66
+ function requireValidUuid(documentId) {
67
+ if (!UUID_RE.test(String(documentId))) {
68
+ throw new HandlerError(400, `invalid document id: ${JSON.stringify(documentId)}`);
69
+ }
70
+ }
71
+ function resolveRequestPrincipals(value, opts) {
72
+ if (value === TRUSTED) return TRUSTED;
73
+ if (value == null) {
74
+ throw new HandlerError(
75
+ 500,
76
+ `${opts.surface}: the \`principals\` dependency returned null, which means TRUSTED CALLER \u2014 it disables ACL filtering and skips the check that stops a caller filing documents under groups it does not hold. This mount is misconfigured: return [] for an unauthenticated caller, or pass TRUSTED explicitly (from @promptev/context-engine) if the surface really is trusted.`
77
+ );
78
+ }
79
+ if (!Array.isArray(value) || value.some((p) => typeof p !== "string")) {
80
+ throw new HandlerError(
81
+ 500,
82
+ `${opts.surface}: the \`principals\` dependency must return a list of principal strings, [] for an anonymous caller, or TRUSTED \u2014 got ${typeof value}.`
83
+ );
84
+ }
85
+ return value;
86
+ }
19
87
  var SCOPE_NOTE = " Results are scoped to the caller's permissions (resolved by the server's injected principals dependency \u2014 never a caller-supplied argument) and to the given source_ids, if any.";
20
88
  async function resolvePrincipalsFn(principals) {
21
89
  const result = await Promise.resolve(principals());
22
- return result;
90
+ try {
91
+ return resolveRequestPrincipals(result, { surface: "the MCP `principals` dependency" });
92
+ } catch (exc) {
93
+ if (exc instanceof HandlerError) throw new Error(exc.message, { cause: exc });
94
+ throw exc;
95
+ }
23
96
  }
24
- function registerToolGateway(mcp, engine, principals) {
97
+ function registerToolGateway(mcp, engine, principals, approvalScope = null) {
25
98
  mcp.tool(
26
99
  "search_tools",
27
100
  "Keyword search over the tools this deployment has registered (http/db/mcp/function) \u2014 the ACL-visible name, kind, description, and JSON Schema params of each match, so a caller can discover what it can then invoke with `execute_tool`." + SCOPE_NOTE,
28
- { query: { type: "string" }, limit: { type: "number" } },
101
+ // Zod raw shape see the note on the search tool in mcp.ts.
102
+ { query: z.string(), limit: z.number().int().optional() },
29
103
  async (...raw) => {
30
104
  const args = raw[0] ?? {};
31
105
  const callerPrincipals = await resolvePrincipalsFn(principals);
@@ -39,13 +113,15 @@ function registerToolGateway(mcp, engine, principals) {
39
113
  mcp.tool(
40
114
  "execute_tool",
41
115
  "Governed execution of a tool previously discovered via `search_tools` (ACL check, approval gate, audit). Returns `{result, usage}` on success, or an `approval_required` payload when the call needs a human approval that isn't already granted \u2014 the caller must not retry until that approval resolves." + SCOPE_NOTE,
42
- { name: { type: "string" }, args: { type: "object" } },
116
+ { name: z.string(), args: z.record(z.unknown()).optional() },
43
117
  async (...raw) => {
44
118
  const body = raw[0] ?? {};
45
119
  const callerPrincipals = await resolvePrincipalsFn(principals);
120
+ const scope = approvalScope ? await Promise.resolve(approvalScope()) : null;
46
121
  return engine.executeTool(String(body.name), body.args ?? null, {
47
122
  principals: callerPrincipals,
48
- source: "mcp"
123
+ source: "mcp",
124
+ approvalScope: scope
49
125
  });
50
126
  }
51
127
  );
@@ -90,15 +166,24 @@ async function createMcpApp(engine, opts) {
90
166
  "search_knowledge_base",
91
167
  `Hybrid search (full-text + trigram + vector, RRF-fused) over the ingested corpus. Returns the top-matching chunks with their source document.${SCOPE_NOTE}`,
92
168
  {
93
- query: { type: "string" },
94
- source_ids: { type: "array", items: { type: "string" } },
95
- top_k: { type: "number" },
96
- mode: { type: "string", enum: ["hybrid", "graph"] }
169
+ // Zod RAW SHAPES, not JSON schema: the SDK's isZodRawShape test
170
+ // rejects a plain schema object on current SDK versions that made
171
+ // registration THROW at startup, and on 1.12.0 the object was consumed
172
+ // as annotations and every handler ran with NO arguments.
173
+ query: z.string(),
174
+ source_ids: z.array(z.string()).optional(),
175
+ document_ids: z.array(z.string()).optional(),
176
+ top_k: z.number().int().optional(),
177
+ mode: z.enum(["hybrid", "graph"]).optional()
97
178
  },
98
179
  async (args) => {
180
+ for (const did of args.document_ids ?? []) {
181
+ requireValidUuid(did);
182
+ }
99
183
  const callerPrincipals = await resolvePrincipalsFn(opts.principals);
100
184
  const result = await engine.search(String(args.query ?? ""), {
101
185
  sourceIds: args.source_ids,
186
+ documentIds: args.document_ids,
102
187
  principals: callerPrincipals,
103
188
  topK: args.top_k ?? 10,
104
189
  mode: args.mode ?? "hybrid"
@@ -120,7 +205,7 @@ async function createMcpApp(engine, opts) {
120
205
  tool(
121
206
  "get_document",
122
207
  `Fetch one document's full text and metadata by id.${SCOPE_NOTE}`,
123
- { document_id: { type: "string" } },
208
+ { document_id: z.string() },
124
209
  async (args) => {
125
210
  const callerPrincipals = await resolvePrincipalsFn(opts.principals);
126
211
  return engine.getDocument(String(args.document_id), { principals: callerPrincipals });
@@ -130,7 +215,7 @@ async function createMcpApp(engine, opts) {
130
215
  tool(
131
216
  "query_structured",
132
217
  `Answer a question against documents' extracted structured data (only documents with structured data are candidates).${SCOPE_NOTE}`,
133
- { question: { type: "string" }, source_ids: { type: "array", items: { type: "string" } } },
218
+ { question: z.string(), source_ids: z.array(z.string()).optional() },
134
219
  async (args) => {
135
220
  const callerPrincipals = await resolvePrincipalsFn(opts.principals);
136
221
  if (!engine.queryStructured) throw new Error("queryStructured is not available on this engine");
@@ -144,7 +229,7 @@ async function createMcpApp(engine, opts) {
144
229
  tool(
145
230
  "compute",
146
231
  `Run LLM-authored JavaScript over in-scope spreadsheet documents and return the computed result.${SCOPE_NOTE}`,
147
- { instruction: { type: "string" }, source_ids: { type: "array", items: { type: "string" } } },
232
+ { instruction: z.string(), source_ids: z.array(z.string()).optional() },
148
233
  async (args) => {
149
234
  const callerPrincipals = await resolvePrincipalsFn(opts.principals);
150
235
  if (!engine.compute) throw new Error("compute is not available on this engine");
@@ -163,7 +248,8 @@ async function createMcpApp(engine, opts) {
163
248
  }
164
249
  },
165
250
  engine,
166
- opts.principals
251
+ opts.principals,
252
+ opts.approvalScope ?? null
167
253
  );
168
254
  const handler = (async (req, res) => {
169
255
  const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: void 0 });