@bevel-software/platform-mcp-core 0.8.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.
Files changed (43) hide show
  1. package/LICENSE +202 -0
  2. package/THIRD-PARTY-NOTICES.md +3065 -0
  3. package/dist/code-mode-names.d.ts +40 -0
  4. package/dist/code-mode-names.d.ts.map +1 -0
  5. package/dist/code-mode-names.js +93 -0
  6. package/dist/code-mode-names.js.map +1 -0
  7. package/dist/dispatch.d.ts +44 -0
  8. package/dist/dispatch.d.ts.map +1 -0
  9. package/dist/dispatch.js +72 -0
  10. package/dist/dispatch.js.map +1 -0
  11. package/dist/index.d.ts +33 -0
  12. package/dist/index.d.ts.map +1 -0
  13. package/dist/index.js +33 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/meta-tools.d.ts +27 -0
  16. package/dist/meta-tools.d.ts.map +1 -0
  17. package/dist/meta-tools.js +161 -0
  18. package/dist/meta-tools.js.map +1 -0
  19. package/dist/proxied-tool.d.ts +49 -0
  20. package/dist/proxied-tool.d.ts.map +1 -0
  21. package/dist/proxied-tool.js +183 -0
  22. package/dist/proxied-tool.js.map +1 -0
  23. package/dist/results.d.ts +19 -0
  24. package/dist/results.d.ts.map +1 -0
  25. package/dist/results.js +130 -0
  26. package/dist/results.js.map +1 -0
  27. package/dist/skills.d.ts +22 -0
  28. package/dist/skills.d.ts.map +1 -0
  29. package/dist/skills.js +20 -0
  30. package/dist/skills.js.map +1 -0
  31. package/dist/utcp-namespace.d.ts +30 -0
  32. package/dist/utcp-namespace.d.ts.map +1 -0
  33. package/dist/utcp-namespace.js +66 -0
  34. package/dist/utcp-namespace.js.map +1 -0
  35. package/package.json +50 -0
  36. package/src/code-mode-names.ts +107 -0
  37. package/src/dispatch.ts +88 -0
  38. package/src/index.ts +71 -0
  39. package/src/meta-tools.ts +186 -0
  40. package/src/proxied-tool.ts +200 -0
  41. package/src/results.ts +131 -0
  42. package/src/skills.ts +34 -0
  43. package/src/utcp-namespace.ts +70 -0
@@ -0,0 +1,183 @@
1
+ /**
2
+ * MCP tool-name grammar (also the Anthropic API's), and a length bound. A
3
+ * remote MCP server can expose a tool whose flattened name breaks this — too
4
+ * long, or an illegal char the `<manual>_<name>` flattening didn't remove — and
5
+ * an MCP client (or the model API behind it) rejects the ENTIRE `tools/list`
6
+ * response when a single entry is non-conforming. That makes EVERY tool vanish
7
+ * the moment one bad tool from a newly-added server enters the catalog, with no
8
+ * server-side error (the rejection is the client's). `toListedTool` isolates it
9
+ * per tool: drop the offender (logged), normalize an odd schema, keep the rest.
10
+ */
11
+ const MCP_TOOL_NAME_RE = /^[a-zA-Z0-9_-]+$/;
12
+ // The Anthropic API caps a tool name at 128 chars — but the MCP CLIENT (Claude
13
+ // Code, claude.ai) prepends `mcp__<server>__` (≈20+ chars) before sending it,
14
+ // and that FULL name is what the 128 applies to. So budget for the prefix here,
15
+ // or a long `googlecalendar_…` name we pass gets the whole request 400'd. This
16
+ // is deliberately conservative; a dropped tool is logged so it's diagnosable.
17
+ const MCP_TOOL_NAME_MAX = 100;
18
+ /** A discovered tool as an MCP listing entry, or null if its name can't be listed. */
19
+ export function toListedTool(tool) {
20
+ if (!MCP_TOOL_NAME_RE.test(tool.mcpName) || tool.mcpName.length > MCP_TOOL_NAME_MAX) {
21
+ console.warn(`[mcp] dropping tool "${tool.mcpName}" from the listing — not a valid MCP tool name ` +
22
+ `(must match ${MCP_TOOL_NAME_RE} and be ≤${MCP_TOOL_NAME_MAX} chars). ` +
23
+ 'One non-conforming tool would otherwise make the whole toolset disappear on the client.');
24
+ return null;
25
+ }
26
+ // MCP requires an object inputSchema. A remote server's schema that isn't a
27
+ // plain object (or omits `type: 'object'`) can invalidate the whole response,
28
+ // so normalize it — keeping any declared properties — rather than pass it
29
+ // through verbatim.
30
+ const raw = tool.inputSchema;
31
+ let inputSchema = raw && typeof raw === 'object' && !Array.isArray(raw)
32
+ ? { type: 'object', ...raw }
33
+ : { type: 'object', properties: {} };
34
+ // Sanitize the schema into what the Anthropic tool validator accepts. A
35
+ // remote server that emits a construct the validator rejects — Google's
36
+ // gmail/calendar use `$ref`/`$defs` AND OpenAPI `format` values like
37
+ // `int32`/`byte` — makes the CLIENT reject the ENTIRE tools/list response,
38
+ // so all tools vanish and nothing registers. Sanitizing per-tool means one
39
+ // odd server can't blank the whole toolset.
40
+ inputSchema = sanitizeInputSchema(inputSchema);
41
+ // The MCP/Anthropic validator requires the top-level `type` to be exactly
42
+ // "object" and (for the Anthropic API) `properties` to be present. Force both
43
+ // so a remote schema that declared something else — or a union like
44
+ // `["object","null"]` — can't reject the whole tools/list.
45
+ inputSchema.type = 'object';
46
+ // An array passes `typeof === 'object'` but is not a property map, so it
47
+ // must coerce like any other non-object or it poisons the whole listing.
48
+ if (typeof inputSchema.properties !== 'object' ||
49
+ inputSchema.properties === null ||
50
+ Array.isArray(inputSchema.properties)) {
51
+ inputSchema.properties = {};
52
+ }
53
+ return {
54
+ name: tool.mcpName,
55
+ description: tool.description,
56
+ inputSchema: inputSchema,
57
+ };
58
+ }
59
+ /** JSON-Schema string `format` values the Anthropic tool validator accepts. */
60
+ const SUPPORTED_SCHEMA_FORMATS = new Set([
61
+ 'date-time',
62
+ 'time',
63
+ 'date',
64
+ 'duration',
65
+ 'email',
66
+ 'hostname',
67
+ 'uri',
68
+ 'ipv4',
69
+ 'ipv6',
70
+ 'uuid',
71
+ ]);
72
+ /**
73
+ * Make a remote server's JSON Schema safe for the Anthropic tool validator:
74
+ * - inline local `$ref` pointers (`#/$defs/...`, `#/definitions/...`) and drop
75
+ * the now-unreferenced `$defs`/`definitions` blocks (the API restricts `$ref`
76
+ * and MCP clients converting our schemas reject it outright);
77
+ * - drop non-standard `format` values (OpenAPI's `int32`/`byte`/… — only the
78
+ * JSON-Schema-standard formats above are accepted; `format` is advisory, so
79
+ * dropping it doesn't change tool behavior).
80
+ * Depth-bounded so a recursive schema degrades to a permissive `{}` node instead
81
+ * of hanging or emitting the unsupported recursion; non-local/external refs
82
+ * degrade the same way. Exported for direct testing.
83
+ */
84
+ export function sanitizeInputSchema(schema) {
85
+ const root = schema;
86
+ const resolvePointer = (pointer) => {
87
+ if (!pointer.startsWith('#/'))
88
+ return undefined;
89
+ let node = root;
90
+ for (const partRaw of pointer.slice(2).split('/')) {
91
+ const part = partRaw.replace(/~1/g, '/').replace(/~0/g, '~');
92
+ if (!node || typeof node !== 'object')
93
+ return undefined;
94
+ node = node[part];
95
+ }
96
+ return node;
97
+ };
98
+ // `isPropertyMap` marks the value of `properties`/`patternProperties`: its
99
+ // keys are the tool's OWN field names, not schema keywords, so a field
100
+ // literally named `format`, `$ref` or `definitions` must survive untouched
101
+ // (its VALUE is still a schema and is walked as one).
102
+ const walk = (node, depth, isPropertyMap = false) => {
103
+ if (depth > 20)
104
+ return {}; // recursion/cycle guard — permissive fallback
105
+ if (Array.isArray(node))
106
+ return node.map((item) => walk(item, depth + 1));
107
+ if (!node || typeof node !== 'object')
108
+ return node;
109
+ const obj = node;
110
+ if (!isPropertyMap && typeof obj.$ref === 'string') {
111
+ const target = resolvePointer(obj.$ref);
112
+ // JSON Schema allows siblings next to $ref; keep them, target wins ties.
113
+ const siblings = { ...obj };
114
+ delete siblings.$ref;
115
+ const resolved = walk(target ?? {}, depth + 1);
116
+ return resolved && typeof resolved === 'object' && !Array.isArray(resolved)
117
+ ? { ...siblings, ...resolved }
118
+ : Object.keys(siblings).length
119
+ ? siblings
120
+ : resolved ?? {};
121
+ }
122
+ const out = {};
123
+ for (const [key, value] of Object.entries(obj)) {
124
+ if (isPropertyMap) {
125
+ out[key] = walk(value, depth + 1);
126
+ continue;
127
+ }
128
+ if (key === '$defs' || key === 'definitions')
129
+ continue; // inlined above
130
+ // Drop a non-standard `format` (OpenAPI `int32`/`byte`/…) — the validator
131
+ // only allows the JSON-Schema-standard set; the annotation is non-load-bearing.
132
+ if (key === 'format' && (typeof value !== 'string' || !SUPPORTED_SCHEMA_FORMATS.has(value))) {
133
+ continue;
134
+ }
135
+ out[key] = walk(value, depth + 1, key === 'properties' || key === 'patternProperties');
136
+ }
137
+ return out;
138
+ };
139
+ return walk(schema, 0);
140
+ }
141
+ /**
142
+ * Flatten a discovered UTCP tool (`<manual>.<tool>`) into the advertised shape
143
+ * when MULTIPLE manuals are registered. Tools from `kbManualName` keep their
144
+ * bare name (so existing agents still call `read_file`); every other manual's
145
+ * tool is namespaced as `<manual>_<tool>` to guarantee a unique, dot-free MCP
146
+ * name.
147
+ *
148
+ * `kbManualName` is a parameter rather than a constant because the two surfaces
149
+ * that call this reach the KB through different manuals: the hosted proxy
150
+ * registers it over loopback, the local server registers the deployment's MCP
151
+ * endpoint. Whichever manual carries the core toolset is the one whose names
152
+ * must stay bare.
153
+ */
154
+ export function flattenManualTool(tool, kbManualName) {
155
+ const dot = tool.name.indexOf('.');
156
+ const manual = dot >= 0 ? tool.name.slice(0, dot) : '';
157
+ const bare = dot >= 0 ? tool.name.slice(dot + 1) : tool.name;
158
+ const mcpName = manual === kbManualName ? bare : tool.name.replace(/\./g, '_');
159
+ return {
160
+ utcpName: tool.name,
161
+ mcpName,
162
+ description: tool.description,
163
+ inputSchema: tool.inputs,
164
+ manualName: manual,
165
+ };
166
+ }
167
+ /**
168
+ * Flatten one discovered UTCP tool into the advertised shape: strip the
169
+ * `<manual>.` namespace prefix for the MCP name and keep the UTCP input schema
170
+ * verbatim (Bevel-hosted HTTP tools show their `{body}` envelope, exactly as in
171
+ * `call_tool_chain`).
172
+ */
173
+ export function flattenDiscoveredTool(prefix, tool) {
174
+ return {
175
+ utcpName: tool.name,
176
+ mcpName: tool.name.startsWith(prefix) ? tool.name.slice(prefix.length) : tool.name,
177
+ description: tool.description,
178
+ inputSchema: tool.inputs,
179
+ // `prefix` is `<manual>.`; the manual is that without the trailing dot.
180
+ manualName: prefix.endsWith('.') ? prefix.slice(0, -1) : prefix,
181
+ };
182
+ }
183
+ //# sourceMappingURL=proxied-tool.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"proxied-tool.js","sourceRoot":"","sources":["../src/proxied-tool.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
2
+ /**
3
+ * Extract a human-meaningful failure message from a tool-call error. UTCP's
4
+ * HTTP protocol surfaces a non-2xx as an axios-style error whose `.response.data`
5
+ * is the REST endpoint's JSON body (`{ error: "..." }`). Pull that out so the
6
+ * MCP caller sees the tool's real message instead of a bare "status code 500".
7
+ */
8
+ export declare function describeToolFailure(err: unknown): string;
9
+ export declare function toCallToolResult(value: unknown): CallToolResult;
10
+ export declare function renderProgress(chunk: unknown): string;
11
+ export declare function toolError(message: string): CallToolResult;
12
+ /**
13
+ * The result returned when the caller is missing personal credentials a tool
14
+ * needs. Marked `isError` so the external agent surfaces it to the person rather
15
+ * than treating it as tool output. Names the tool, lists the missing items, and
16
+ * gives the absolute setup-page URL so the person can provide them and retry.
17
+ */
18
+ export declare function needsAuthorizationResult(toolName: string, missing: string[], connectUrl: string): CallToolResult;
19
+ //# sourceMappingURL=results.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"results.d.ts","sourceRoot":"","sources":["../src/results.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AAEzE;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAgBxD;AAsED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,cAAc,CAS/D;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAGrD;AAED,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,cAAc,CAEzD;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,EAAE,EACjB,UAAU,EAAE,MAAM,GACjB,cAAc,CAMhB"}
@@ -0,0 +1,130 @@
1
+ /**
2
+ * Extract a human-meaningful failure message from a tool-call error. UTCP's
3
+ * HTTP protocol surfaces a non-2xx as an axios-style error whose `.response.data`
4
+ * is the REST endpoint's JSON body (`{ error: "..." }`). Pull that out so the
5
+ * MCP caller sees the tool's real message instead of a bare "status code 500".
6
+ */
7
+ export function describeToolFailure(err) {
8
+ const data = err?.response?.data;
9
+ if (data && typeof data === 'object') {
10
+ const inner = data.error;
11
+ if (typeof inner === 'string' && inner.length > 0)
12
+ return inner;
13
+ }
14
+ if (typeof data === 'string' && data.length > 0)
15
+ return data;
16
+ // Total, like `safeJsonText`: a thrown value whose own `toString` throws
17
+ // (e.g. a null-prototype object) must still come back as a description —
18
+ // this function runs inside catch paths, where a second throw would turn
19
+ // a tool failure into a handler failure.
20
+ try {
21
+ return err instanceof Error ? err.message : String(err);
22
+ }
23
+ catch {
24
+ return '(indescribable tool failure)';
25
+ }
26
+ }
27
+ /**
28
+ * Turn a tool's final value into an MCP result:
29
+ * - a tool that already returns the MCP agentic shape (`{ content: [...] }`,
30
+ * each entry a real content block) is passed through unchanged;
31
+ * - a bare string becomes the text content;
32
+ * - anything else is JSON-stringified into one text block.
33
+ *
34
+ * Note we do NOT collapse a structured object down to its `text` field: doing
35
+ * so silently dropped the other fields (e.g. `ask`'s `status` / `sessionId`,
36
+ * the id a caller must echo back to poll or continue a conversation).
37
+ * Stringifying the whole object keeps every field, so the caller always sees
38
+ * the id it is responsible for echoing.
39
+ *
40
+ * The passthrough guard checks the entries, not just that `content` is an array:
41
+ * a domain value that merely happens to carry a `content` array of non-blocks
42
+ * (e.g. `{ content: ['a', 'b'] }`) is data, not an MCP result, so it falls
43
+ * through to JSON-stringify and survives intact instead of being emitted as a
44
+ * malformed result the client can't parse.
45
+ */
46
+ function isMcpContentBlock(entry) {
47
+ return typeof entry === 'object' && entry !== null && typeof entry.type === 'string';
48
+ }
49
+ /**
50
+ * `JSON.stringify`, made total. A tool can legally hand back a value JSON
51
+ * refuses verbatim — a BigInt or circular object (throws), a bare
52
+ * function/symbol (stringifies to `undefined`) — and a COMPLETED call must not
53
+ * turn into a crash at the serialization step. The plain stringify runs first
54
+ * so well-formed values (shared non-circular references included) come out
55
+ * exactly as before; only a value it rejects degrades to the replacer pass
56
+ * (BigInt → decimal string, its own ancestor → '[Circular]'), and a value even
57
+ * that can't take (e.g. a throwing `toJSON`) falls back to `String(value)`.
58
+ */
59
+ function safeJsonText(value) {
60
+ try {
61
+ const text = JSON.stringify(value);
62
+ if (text !== undefined)
63
+ return text;
64
+ }
65
+ catch {
66
+ // BigInt / circular / throwing toJSON — degrade below.
67
+ }
68
+ try {
69
+ // Circularity is being one's own ANCESTOR, not being visited twice: a
70
+ // shared (diamond) reference is legal JSON and stringify visits it once
71
+ // per parent, so a grown-only "seen" set would mislabel every sibling
72
+ // share as circular. The stack tracks only the active descent — the
73
+ // holder (`this`) of the current key is necessarily the innermost live
74
+ // ancestor, so popping down to it discards branches already unwound.
75
+ const ancestors = [];
76
+ const text = JSON.stringify(value, function (_key, v) {
77
+ if (typeof v === 'bigint')
78
+ return v.toString();
79
+ if (v && typeof v === 'object') {
80
+ while (ancestors.length > 0 && ancestors[ancestors.length - 1] !== this)
81
+ ancestors.pop();
82
+ if (ancestors.includes(v))
83
+ return '[Circular]';
84
+ ancestors.push(v);
85
+ }
86
+ return v;
87
+ });
88
+ if (text !== undefined)
89
+ return text;
90
+ }
91
+ catch {
92
+ // fall through to the value's own toString
93
+ }
94
+ try {
95
+ return String(value);
96
+ }
97
+ catch {
98
+ return '(unserializable tool output)';
99
+ }
100
+ }
101
+ export function toCallToolResult(value) {
102
+ // Already in MCP agentic format — pass through untouched, but only when every
103
+ // `content` entry is a real content block (has a string `type`).
104
+ const content = value?.content;
105
+ if (value && typeof value === 'object' && Array.isArray(content) && content.every(isMcpContentBlock)) {
106
+ return value;
107
+ }
108
+ const text = typeof value === 'string' ? value : safeJsonText(value ?? null);
109
+ return { content: [{ type: 'text', text: text || '(tool produced no output)' }] };
110
+ }
111
+ export function renderProgress(chunk) {
112
+ const s = typeof chunk === 'string' ? chunk : safeJsonText(chunk);
113
+ return s.length > 500 ? s.slice(0, 497) + '...' : s;
114
+ }
115
+ export function toolError(message) {
116
+ return { isError: true, content: [{ type: 'text', text: message }] };
117
+ }
118
+ /**
119
+ * The result returned when the caller is missing personal credentials a tool
120
+ * needs. Marked `isError` so the external agent surfaces it to the person rather
121
+ * than treating it as tool output. Names the tool, lists the missing items, and
122
+ * gives the absolute setup-page URL so the person can provide them and retry.
123
+ */
124
+ export function needsAuthorizationResult(toolName, missing, connectUrl) {
125
+ const items = missing.join(', ');
126
+ const text = `The "${toolName}" tool needs credentials you haven't set up yet: ${items}. ` +
127
+ `Open ${connectUrl} to connect your accounts and enter your keys, then run the tool again.`;
128
+ return { isError: true, content: [{ type: 'text', text }] };
129
+ }
130
+ //# sourceMappingURL=results.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"results.js","sourceRoot":"","sources":["../src/results.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Skills exposed as MCP prompts (slash commands in an MCP client).
3
+ *
4
+ * Both surfaces do this the same way and must keep doing it the same way: a
5
+ * skill's instructions are the prompt body, and the bundled files are named at
6
+ * the end with the exact call that fetches them. A person running the same
7
+ * skill through the hosted endpoint and through the local server should get
8
+ * identical text, so the formatting lives here rather than in either server.
9
+ */
10
+ /** Minimal skill shapes the prompt bridge needs (no dependency on `modules/skills`). */
11
+ export interface SkillSummary {
12
+ name: string;
13
+ description: string;
14
+ }
15
+ export interface LoadedSkill extends SkillSummary {
16
+ body: string;
17
+ path: string;
18
+ files: string[];
19
+ }
20
+ /** The prompt message text for a loaded skill: its instructions body + a pointer to bundled files. */
21
+ export declare function skillPromptText(skill: LoadedSkill): string;
22
+ //# sourceMappingURL=skills.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skills.d.ts","sourceRoot":"","sources":["../src/skills.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,wFAAwF;AACxF,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,WAAY,SAAQ,YAAY;IAC/C,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED,sGAAsG;AACtG,wBAAgB,eAAe,CAAC,KAAK,EAAE,WAAW,GAAG,MAAM,CAU1D"}
package/dist/skills.js ADDED
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Skills exposed as MCP prompts (slash commands in an MCP client).
3
+ *
4
+ * Both surfaces do this the same way and must keep doing it the same way: a
5
+ * skill's instructions are the prompt body, and the bundled files are named at
6
+ * the end with the exact call that fetches them. A person running the same
7
+ * skill through the hosted endpoint and through the local server should get
8
+ * identical text, so the formatting lives here rather than in either server.
9
+ */
10
+ /** The prompt message text for a loaded skill: its instructions body + a pointer to bundled files. */
11
+ export function skillPromptText(skill) {
12
+ const rel = skill.files.map((f) => f.startsWith(`${skill.path}/`) ? f.slice(skill.path.length + 1) : f);
13
+ // Each name is quoted: a comma inside a file name must not read as a list
14
+ // separator, and the quoted form is exactly the `file` value get_skill takes.
15
+ const footer = rel.length
16
+ ? `\n\n---\nSkill folder: ${skill.path}\nBundled files (fetch each with the get_skill tool: { name: ${JSON.stringify(skill.name)}, file }): ${rel.map((f) => JSON.stringify(f)).join(', ')}`
17
+ : '';
18
+ return `${skill.body}${footer}`;
19
+ }
20
+ //# sourceMappingURL=skills.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skills.js","sourceRoot":"","sources":["../src/skills.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAcH,sGAAsG;AACtG,MAAM,UAAU,eAAe,CAAC,KAAkB;IAChD,MAAM,GAAG,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAChC,CAAC,CAAC,UAAU,CAAC,GAAG,KAAK,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CACpE,CAAC;IACF,0EAA0E;IAC1E,8EAA8E;IAC9E,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM;QACvB,CAAC,CAAC,0BAA0B,KAAK,CAAC,IAAI,gEAAgE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,cAAc,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;QAC5L,CAAC,CAAC,EAAE,CAAC;IACP,OAAO,GAAG,KAAK,CAAC,IAAI,GAAG,MAAM,EAAE,CAAC;AAClC,CAAC"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * The key UTCP uses to look up a manual's `${VAR}`. `@utcp/sdk` first SANITIZES
3
+ * the manual name — every non-word char (`-`, `.`, spaces, …) becomes `_`
4
+ * (`manualCallTemplate.name.replace(/[^\w]/g, "_")` at registration) — and then
5
+ * namespaces variables under `<namespace>_<VAR>` where every `_` in the
6
+ * sanitized namespace is DOUBLED (the variable substitutor), so `my_tool` + `KEY`
7
+ * → `my__tool_KEY` and `my-tool` + `KEY` → the very same `my__tool_KEY`. Our
8
+ * secret storage AND scope resolution must derive the exact same key. For an
9
+ * alphanumeric name (no `_`, no punctuation) this reduces to `<name>_<VAR>`,
10
+ * so plain tool names are unaffected.
11
+ */
12
+ export declare function utcpNamespacePrefix(manualName: string): string;
13
+ export declare function utcpNamespacedKey(manualName: string, varName: string): string;
14
+ /**
15
+ * Seed the loopback discovery vars (`<ns>_API_URL` + `<ns>_CONNECTION_KEY`) for
16
+ * every Bevel-HOSTED manual in `manuals` — one whose discovery `url` targets our
17
+ * own backend with `${API_URL}` as its ORIGIN (the KB manual and inline `.tool`
18
+ * sub-manuals). A third-party http/mcp `.tool` points at an arbitrary URL, so it
19
+ * is deliberately NOT seeded: handing it `connectionKey` would leak the bearer to
20
+ * that endpoint. Keying off the origin (not a bare `includes('${API_URL}')`) is
21
+ * deliberate — an `http` `.tool`'s URL is author-controlled, so a substring match
22
+ * would let `http://attacker/?x=${API_URL}` be seeded the bearer and exfiltrate
23
+ * it. This is the exfiltration-safe seeding rule shared by the external MCP proxy
24
+ * and the in-process agent factory, so the bearer-leak decision is written ONCE.
25
+ */
26
+ export declare function seedBevelHostedManualVars(manuals: readonly {
27
+ name?: unknown;
28
+ url?: unknown;
29
+ }[], loopbackBaseUrl: string, connectionKey: string): Record<string, string>;
30
+ //# sourceMappingURL=utcp-namespace.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utcp-namespace.d.ts","sourceRoot":"","sources":["../src/utcp-namespace.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAE9D;AAED,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAE7E;AAyBD;;;;;;;;;;;GAWG;AACH,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,SAAS;IAAE,IAAI,CAAC,EAAE,OAAO,CAAC;IAAC,GAAG,CAAC,EAAE,OAAO,CAAA;CAAE,EAAE,EACrD,eAAe,EAAE,MAAM,EACvB,aAAa,EAAE,MAAM,GACpB,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAWxB"}
@@ -0,0 +1,66 @@
1
+ /**
2
+ * The key UTCP uses to look up a manual's `${VAR}`. `@utcp/sdk` first SANITIZES
3
+ * the manual name — every non-word char (`-`, `.`, spaces, …) becomes `_`
4
+ * (`manualCallTemplate.name.replace(/[^\w]/g, "_")` at registration) — and then
5
+ * namespaces variables under `<namespace>_<VAR>` where every `_` in the
6
+ * sanitized namespace is DOUBLED (the variable substitutor), so `my_tool` + `KEY`
7
+ * → `my__tool_KEY` and `my-tool` + `KEY` → the very same `my__tool_KEY`. Our
8
+ * secret storage AND scope resolution must derive the exact same key. For an
9
+ * alphanumeric name (no `_`, no punctuation) this reduces to `<name>_<VAR>`,
10
+ * so plain tool names are unaffected.
11
+ */
12
+ export function utcpNamespacePrefix(manualName) {
13
+ return `${manualName.replace(/[^\w]/g, '_').replace(/_/g, '__')}_`;
14
+ }
15
+ export function utcpNamespacedKey(manualName, varName) {
16
+ return `${utcpNamespacePrefix(manualName)}${varName}`;
17
+ }
18
+ /**
19
+ * The origin template WE mint for every Bevel-hosted manual's discovery URL
20
+ * (`${API_URL}/api/…`). `${API_URL}` expands to the loopback origin, so a
21
+ * Bevel-hosted URL always has this exact string as its scheme+authority.
22
+ */
23
+ const LOOPBACK_ORIGIN_TEMPLATE = '${API_URL}';
24
+ /**
25
+ * True only when `${API_URL}` is the URL's ORIGIN (scheme+authority) — the shape
26
+ * we ourselves produce for a Bevel-hosted manual. A third-party `.tool` author
27
+ * can embed the literal `${API_URL}` anywhere in a path or query
28
+ * (`http://evil.example/?x=${API_URL}`) but can NOT make it the authority without
29
+ * pointing the request back at our own loopback, so anchoring the trust decision
30
+ * to the origin is what a user `.tool`'s URL text cannot forge.
31
+ */
32
+ function isBevelHostedUrl(url) {
33
+ if (!url.startsWith(LOOPBACK_ORIGIN_TEMPLATE))
34
+ return false;
35
+ // The char right after the origin must delimit the authority; anything else
36
+ // (`${API_URL}.evil.com`, `${API_URL}evil`) is a different, untrusted host.
37
+ const next = url.charAt(LOOPBACK_ORIGIN_TEMPLATE.length);
38
+ return next === '' || next === '/' || next === '?' || next === '#';
39
+ }
40
+ /**
41
+ * Seed the loopback discovery vars (`<ns>_API_URL` + `<ns>_CONNECTION_KEY`) for
42
+ * every Bevel-HOSTED manual in `manuals` — one whose discovery `url` targets our
43
+ * own backend with `${API_URL}` as its ORIGIN (the KB manual and inline `.tool`
44
+ * sub-manuals). A third-party http/mcp `.tool` points at an arbitrary URL, so it
45
+ * is deliberately NOT seeded: handing it `connectionKey` would leak the bearer to
46
+ * that endpoint. Keying off the origin (not a bare `includes('${API_URL}')`) is
47
+ * deliberate — an `http` `.tool`'s URL is author-controlled, so a substring match
48
+ * would let `http://attacker/?x=${API_URL}` be seeded the bearer and exfiltrate
49
+ * it. This is the exfiltration-safe seeding rule shared by the external MCP proxy
50
+ * and the in-process agent factory, so the bearer-leak decision is written ONCE.
51
+ */
52
+ export function seedBevelHostedManualVars(manuals, loopbackBaseUrl, connectionKey) {
53
+ const variables = {};
54
+ for (const m of manuals) {
55
+ const name = typeof m.name === 'string' ? m.name : '';
56
+ if (!name)
57
+ continue;
58
+ const url = m.url;
59
+ if (typeof url !== 'string' || !isBevelHostedUrl(url))
60
+ continue;
61
+ variables[utcpNamespacedKey(name, 'API_URL')] = loopbackBaseUrl;
62
+ variables[utcpNamespacedKey(name, 'CONNECTION_KEY')] = connectionKey;
63
+ }
64
+ return variables;
65
+ }
66
+ //# sourceMappingURL=utcp-namespace.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utcp-namespace.js","sourceRoot":"","sources":["../src/utcp-namespace.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,MAAM,UAAU,mBAAmB,CAAC,UAAkB;IACpD,OAAO,GAAG,UAAU,CAAC,OAAO,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC;AACrE,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,UAAkB,EAAE,OAAe;IACnE,OAAO,GAAG,mBAAmB,CAAC,UAAU,CAAC,GAAG,OAAO,EAAE,CAAC;AACxD,CAAC;AAED;;;;GAIG;AACH,MAAM,wBAAwB,GAAG,YAAY,CAAC;AAE9C;;;;;;;GAOG;AACH,SAAS,gBAAgB,CAAC,GAAW;IACnC,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,wBAAwB,CAAC;QAAE,OAAO,KAAK,CAAC;IAC5D,4EAA4E;IAC5E,4EAA4E;IAC5E,MAAM,IAAI,GAAG,GAAG,CAAC,MAAM,CAAC,wBAAwB,CAAC,MAAM,CAAC,CAAC;IACzD,OAAO,IAAI,KAAK,EAAE,IAAI,IAAI,KAAK,GAAG,IAAI,IAAI,KAAK,GAAG,IAAI,IAAI,KAAK,GAAG,CAAC;AACrE,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,yBAAyB,CACvC,OAAqD,EACrD,eAAuB,EACvB,aAAqB;IAErB,MAAM,SAAS,GAA2B,EAAE,CAAC;IAC7C,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;QACxB,MAAM,IAAI,GAAG,OAAO,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;QACtD,IAAI,CAAC,IAAI;YAAE,SAAS;QACpB,MAAM,GAAG,GAAI,CAAuB,CAAC,GAAG,CAAC;QACzC,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC;YAAE,SAAS;QAChE,SAAS,CAAC,iBAAiB,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC,GAAG,eAAe,CAAC;QAChE,SAAS,CAAC,iBAAiB,CAAC,IAAI,EAAE,gBAAgB,CAAC,CAAC,GAAG,aAAa,CAAC;IACvE,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC"}
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "@bevel-software/platform-mcp-core",
3
+ "version": "0.8.0",
4
+ "description": "The transport-agnostic half of Bevel's MCP surface: UTCP manual registration, tool discovery, MCP listing/dispatch and the code-mode meta-tools. Shared by the hosted proxy and the local stdio server.",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "default": "./dist/index.js"
12
+ },
13
+ "./package.json": "./package.json"
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "src",
18
+ "!src/__tests__",
19
+ "!dist/__tests__",
20
+ "THIRD-PARTY-NOTICES.md"
21
+ ],
22
+ "engines": {
23
+ "node": ">=22 <23"
24
+ },
25
+ "dependencies": {
26
+ "@modelcontextprotocol/sdk": "^1.29.0",
27
+ "@utcp/code-mode": "^1.2.12",
28
+ "@utcp/http": "^1.1.11",
29
+ "@utcp/mcp": "^1.1.3",
30
+ "@utcp/sdk": "^1.1.1"
31
+ },
32
+ "devDependencies": {
33
+ "typescript": "^5.8.0",
34
+ "vitest": "^3.2.6"
35
+ },
36
+ "license": "Apache-2.0",
37
+ "repository": {
38
+ "type": "git",
39
+ "url": "git+https://github.com/Bevel-Software/Hexis.git",
40
+ "directory": "packages/mcp-core"
41
+ },
42
+ "publishConfig": {
43
+ "access": "public"
44
+ },
45
+ "scripts": {
46
+ "build": "tsc -p tsconfig.json",
47
+ "typecheck": "tsc -p tsconfig.json --noEmit",
48
+ "test": "vitest run"
49
+ }
50
+ }