@ai-ecoverse/slicc-agent 3.27.1 → 3.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +45 -3
- package/dist/codemode/index.js +10 -4
- package/dist/codemode/index.js.map +2 -2
- package/dist/host.js +1 -0
- package/dist/host.js.map +2 -2
- package/dist/main.js +21 -2
- package/dist/main.js.map +2 -2
- package/dist/mcp/allow.js +39 -0
- package/dist/mcp/allow.js.map +7 -0
- package/dist/mcp/catalog.js +182 -0
- package/dist/mcp/catalog.js.map +7 -0
- package/dist/mcp/config.js +201 -0
- package/dist/mcp/config.js.map +7 -0
- package/dist/mcp/connection.js +296 -0
- package/dist/mcp/connection.js.map +7 -0
- package/dist/mcp/content.js +139 -0
- package/dist/mcp/content.js.map +7 -0
- package/dist/mcp/credentials.js +45 -0
- package/dist/mcp/credentials.js.map +7 -0
- package/dist/mcp/index.js +54 -0
- package/dist/mcp/index.js.map +7 -0
- package/dist/mcp/runtime.js +209 -0
- package/dist/mcp/runtime.js.map +7 -0
- package/dist/mcp/section.js +35 -0
- package/dist/mcp/section.js.map +7 -0
- package/dist/mcp/sse.js +112 -0
- package/dist/mcp/sse.js.map +7 -0
- package/dist/mcp/tools.js +413 -0
- package/dist/mcp/tools.js.map +7 -0
- package/dist/patches.js +2 -0
- package/dist/patches.js.map +2 -2
- package/dist/roles/roles.js +10 -1
- package/dist/roles/roles.js.map +2 -2
- package/dist/scoops/index.js +2 -1
- package/dist/scoops/index.js.map +2 -2
- package/dist/scoops/service.js +21 -6
- package/dist/scoops/service.js.map +2 -2
- package/dist/types/codemode/index.d.ts +8 -3
- package/dist/types/host.d.ts +2 -0
- package/dist/types/mcp/allow.d.ts +12 -0
- package/dist/types/mcp/catalog.d.ts +29 -0
- package/dist/types/mcp/config.d.ts +35 -0
- package/dist/types/mcp/connection.d.ts +57 -0
- package/dist/types/mcp/content.d.ts +27 -0
- package/dist/types/mcp/credentials.d.ts +15 -0
- package/dist/types/mcp/index.d.ts +16 -0
- package/dist/types/mcp/runtime.d.ts +42 -0
- package/dist/types/mcp/section.d.ts +7 -0
- package/dist/types/mcp/sse.d.ts +16 -0
- package/dist/types/mcp/tools.d.ts +44 -0
- package/dist/types/patches.d.ts +1 -0
- package/dist/types/scoops/index.d.ts +1 -0
- package/dist/types/scoops/service.d.ts +1 -0
- package/package.json +5 -4
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import {
|
|
2
|
+
defineExtension,
|
|
3
|
+
hook,
|
|
4
|
+
section,
|
|
5
|
+
ToolTask
|
|
6
|
+
} from "@earendil-works/pi-durable";
|
|
7
|
+
import { allows, McpAllowDoc, RESOURCE_NAMES, serverAllowed } from "./allow.js";
|
|
8
|
+
import {
|
|
9
|
+
mcpNamespace,
|
|
10
|
+
mcpToolName,
|
|
11
|
+
nameMatcher,
|
|
12
|
+
toolExposure
|
|
13
|
+
} from "./config.js";
|
|
14
|
+
import { renderSection } from "./section.js";
|
|
15
|
+
import {
|
|
16
|
+
apiSaver,
|
|
17
|
+
directTool,
|
|
18
|
+
discoveryGlobals,
|
|
19
|
+
resourceScriptTools,
|
|
20
|
+
resourceTools,
|
|
21
|
+
scriptTool
|
|
22
|
+
} from "./tools.js";
|
|
23
|
+
const MCP_EXTENSION = "slicc-mcp";
|
|
24
|
+
const MCP_SECTION = "mcp_servers";
|
|
25
|
+
const DIRECT_WAIT_MS = 1e4;
|
|
26
|
+
function effective(exposure, codemode) {
|
|
27
|
+
const indirect = exposure === "deferred" ? "codemode" : exposure;
|
|
28
|
+
return indirect === "codemode" && !codemode ? "direct" : indirect;
|
|
29
|
+
}
|
|
30
|
+
async function allowOf(read, conversationId, context) {
|
|
31
|
+
const doc = await read.snapshot(McpAllowDoc, conversationId, context).catch(() => void 0);
|
|
32
|
+
return doc?.allow ?? null;
|
|
33
|
+
}
|
|
34
|
+
class McpCatalog {
|
|
35
|
+
connections = /* @__PURE__ */ new Map();
|
|
36
|
+
entries = [];
|
|
37
|
+
codemodeOn = true;
|
|
38
|
+
current;
|
|
39
|
+
#registry;
|
|
40
|
+
#section = section(
|
|
41
|
+
MCP_SECTION,
|
|
42
|
+
async (input, context) => renderSection(this.listings(await allowOf(input.read, input.conversationId, context)))
|
|
43
|
+
);
|
|
44
|
+
#guard = hook(ToolTask, {
|
|
45
|
+
beforeTool: async (call, api, context) => {
|
|
46
|
+
const name = call.name;
|
|
47
|
+
if (!name.startsWith("mcp__") && !RESOURCE_NAMES.has(name)) return void 0;
|
|
48
|
+
const allow = await allowOf(api, api.conversationId, context);
|
|
49
|
+
if (allows(allow, name)) return void 0;
|
|
50
|
+
return { block: `${name} isn't among the MCP tools this scoop may use` };
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
constructor(registry) {
|
|
54
|
+
this.#registry = registry;
|
|
55
|
+
this.current = defineExtension({ name: MCP_EXTENSION });
|
|
56
|
+
}
|
|
57
|
+
connected() {
|
|
58
|
+
return [...this.connections.values()].filter((connection) => connection.live);
|
|
59
|
+
}
|
|
60
|
+
#resourceful() {
|
|
61
|
+
return this.connected().filter(
|
|
62
|
+
(connection) => connection.hasResources && effective(connection.entry.config.exposure ?? "codemode", this.codemodeOn) !== "hidden"
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
resourceServers(allow) {
|
|
66
|
+
return this.#resourceful().filter((connection) => serverAllowed(allow, connection.name)).map((connection) => ({ name: connection.name, connection }));
|
|
67
|
+
}
|
|
68
|
+
resourceExposure() {
|
|
69
|
+
const exposures = this.#resourceful().map(
|
|
70
|
+
(connection) => effective(connection.entry.config.exposure ?? "codemode", this.codemodeOn)
|
|
71
|
+
);
|
|
72
|
+
if (!exposures.length) return void 0;
|
|
73
|
+
return exposures.includes("direct") ? "direct" : "codemode";
|
|
74
|
+
}
|
|
75
|
+
listings(allow) {
|
|
76
|
+
return [...this.connections.values()].filter((connection) => {
|
|
77
|
+
if (!serverAllowed(allow, connection.name)) return false;
|
|
78
|
+
const { config } = connection.entry;
|
|
79
|
+
const exposures = [
|
|
80
|
+
config.exposure ?? "codemode",
|
|
81
|
+
...Object.values(config.toolExposure ?? {})
|
|
82
|
+
];
|
|
83
|
+
return exposures.some((exposure) => effective(exposure, this.codemodeOn) === "codemode");
|
|
84
|
+
}).map((connection) => ({
|
|
85
|
+
name: connection.name,
|
|
86
|
+
...connection.entry.config.description ? { description: connection.entry.config.description } : {},
|
|
87
|
+
...connection.instructions ? { instructions: connection.instructions } : {}
|
|
88
|
+
}));
|
|
89
|
+
}
|
|
90
|
+
install() {
|
|
91
|
+
const readable = this.resourceExposure() !== void 0;
|
|
92
|
+
const direct = this.entries.filter((entry) => entry.exposure === "direct").map((entry) => directTool(entry, () => readable && entry.connection.hasResources));
|
|
93
|
+
const resources = this.resourceExposure() === "direct" ? resourceTools(
|
|
94
|
+
async (api, context) => this.resourceServers(await allowOf(api, api.conversationId, context))
|
|
95
|
+
) : [];
|
|
96
|
+
this.current = defineExtension({
|
|
97
|
+
name: MCP_EXTENSION,
|
|
98
|
+
tools: [...direct, ...resources],
|
|
99
|
+
sections: [this.#section],
|
|
100
|
+
hooks: [this.#guard]
|
|
101
|
+
});
|
|
102
|
+
this.#registry.install(this.current);
|
|
103
|
+
}
|
|
104
|
+
rebuild() {
|
|
105
|
+
const taken = /* @__PURE__ */ new Set();
|
|
106
|
+
const next = [];
|
|
107
|
+
for (const connection of this.connected()) {
|
|
108
|
+
for (const tool of connection.tools) {
|
|
109
|
+
const exposure = effective(
|
|
110
|
+
toolExposure(connection.entry.config, tool.name),
|
|
111
|
+
this.codemodeOn
|
|
112
|
+
);
|
|
113
|
+
if (exposure === "hidden") continue;
|
|
114
|
+
const name = mcpToolName(connection.name, tool.name, (candidate) => taken.has(candidate));
|
|
115
|
+
taken.add(name);
|
|
116
|
+
next.push({ server: connection.name, name, exposure, tool, connection });
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
this.entries = next;
|
|
120
|
+
this.install();
|
|
121
|
+
}
|
|
122
|
+
direct(names) {
|
|
123
|
+
const match = nameMatcher([...names]);
|
|
124
|
+
return (this.current.tools ?? []).filter((tool) => match(tool.name));
|
|
125
|
+
}
|
|
126
|
+
async waitFor(allow) {
|
|
127
|
+
const pending = [...this.connections.values()].filter(
|
|
128
|
+
(connection) => connection.state === "connecting" && serverAllowed(allow, connection.name)
|
|
129
|
+
);
|
|
130
|
+
if (!pending.length) return;
|
|
131
|
+
let timer;
|
|
132
|
+
await Promise.race([
|
|
133
|
+
Promise.allSettled(pending.map((connection) => connection.getClient())),
|
|
134
|
+
new Promise((resolve) => {
|
|
135
|
+
timer = setTimeout(resolve, DIRECT_WAIT_MS);
|
|
136
|
+
})
|
|
137
|
+
]);
|
|
138
|
+
clearTimeout(timer);
|
|
139
|
+
}
|
|
140
|
+
#namespaces() {
|
|
141
|
+
const spaces = /* @__PURE__ */ new Map();
|
|
142
|
+
for (const entry of this.entries) {
|
|
143
|
+
const { config } = entry.connection.entry;
|
|
144
|
+
spaces.set(entry.name, {
|
|
145
|
+
name: mcpNamespace(entry.server),
|
|
146
|
+
...config.description ? { description: config.description } : {},
|
|
147
|
+
...entry.connection.instructions ? { instructions: entry.connection.instructions } : {}
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
return spaces;
|
|
151
|
+
}
|
|
152
|
+
async codemode(api, agent, context) {
|
|
153
|
+
if (!agent.extensions.some((extension) => extension.name === MCP_EXTENSION))
|
|
154
|
+
return { tools: [], globals: [] };
|
|
155
|
+
const allow = await allowOf(api, api.conversationId, context);
|
|
156
|
+
await this.waitFor(allow);
|
|
157
|
+
const signal = context.abortSignal;
|
|
158
|
+
const offered = new Set(agent.tools.map((tool) => tool.name));
|
|
159
|
+
const tools = this.entries.filter(
|
|
160
|
+
(entry) => allows(allow, entry.name) && (entry.exposure !== "direct" || offered.has(entry.name))
|
|
161
|
+
).map((entry) => scriptTool(entry, signal));
|
|
162
|
+
const save = apiSaver(api, "r", context);
|
|
163
|
+
const resources = this.resourceExposure() === "codemode" && this.resourceServers(allow).length ? resourceScriptTools(() => this.resourceServers(allow), signal, save).filter(
|
|
164
|
+
(tool) => allows(allow, tool.name)
|
|
165
|
+
) : [];
|
|
166
|
+
const all = [...tools, ...resources];
|
|
167
|
+
const spaces = this.#namespaces();
|
|
168
|
+
return {
|
|
169
|
+
tools: all,
|
|
170
|
+
globals: all.length ? discoveryGlobals(all, (name) => spaces.get(name)) : []
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
export {
|
|
175
|
+
DIRECT_WAIT_MS,
|
|
176
|
+
MCP_EXTENSION,
|
|
177
|
+
MCP_SECTION,
|
|
178
|
+
McpCatalog,
|
|
179
|
+
allowOf,
|
|
180
|
+
effective
|
|
181
|
+
};
|
|
182
|
+
//# sourceMappingURL=catalog.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/mcp/catalog.ts"],
|
|
4
|
+
"sourcesContent": ["import type { Context } from '@earendil-works/chord';\nimport {\n type Agent,\n type ConversationId,\n type DocumentReader,\n defineExtension,\n type Extension,\n hook,\n type Registry,\n section,\n type ToolExecutionApi,\n type ToolRegistration,\n ToolTask,\n} from '@earendil-works/pi-durable';\nimport { allows, type CodemodeExtra, McpAllowDoc, RESOURCE_NAMES, serverAllowed } from './allow.ts';\nimport {\n type McpExposure,\n mcpNamespace,\n mcpToolName,\n nameMatcher,\n toolExposure,\n} from './config.ts';\nimport type { McpServerConnection } from './connection.ts';\nimport { type Listing, renderSection } from './section.ts';\nimport {\n apiSaver,\n directTool,\n discoveryGlobals,\n type McpToolEntry,\n type Namespace,\n type ResourceServer,\n resourceScriptTools,\n resourceTools,\n scriptTool,\n} from './tools.ts';\n\nexport const MCP_EXTENSION = 'slicc-mcp';\nexport const MCP_SECTION = 'mcp_servers';\nexport const DIRECT_WAIT_MS = 10_000;\n\nexport function effective(exposure: McpExposure, codemode: boolean): McpExposure {\n const indirect = exposure === 'deferred' ? 'codemode' : exposure;\n return indirect === 'codemode' && !codemode ? 'direct' : indirect;\n}\n\nexport async function allowOf(\n read: DocumentReader,\n conversationId: ConversationId,\n context: Context\n): Promise<string[] | null> {\n const doc = await read.snapshot(McpAllowDoc, conversationId, context).catch(() => undefined);\n return doc?.allow ?? null;\n}\n\nexport class McpCatalog {\n readonly connections = new Map<string, McpServerConnection>();\n entries: McpToolEntry[] = [];\n codemodeOn = true;\n current: Extension;\n readonly #registry: Registry;\n readonly #section = section(MCP_SECTION, async (input, context) =>\n renderSection(this.listings(await allowOf(input.read, input.conversationId, context)))\n );\n\n readonly #guard = hook(ToolTask, {\n beforeTool: async (call, api, context) => {\n const name = call.name;\n if (!name.startsWith('mcp__') && !RESOURCE_NAMES.has(name)) return undefined;\n const allow = await allowOf(api, api.conversationId, context);\n if (allows(allow, name)) return undefined;\n return { block: `${name} isn't among the MCP tools this scoop may use` };\n },\n });\n\n constructor(registry: Registry) {\n this.#registry = registry;\n this.current = defineExtension({ name: MCP_EXTENSION });\n }\n\n connected(): McpServerConnection[] {\n return [...this.connections.values()].filter((connection) => connection.live);\n }\n\n #resourceful(): McpServerConnection[] {\n return this.connected().filter(\n (connection) =>\n connection.hasResources &&\n effective(connection.entry.config.exposure ?? 'codemode', this.codemodeOn) !== 'hidden'\n );\n }\n\n resourceServers(allow: readonly string[] | null): ResourceServer[] {\n return this.#resourceful()\n .filter((connection) => serverAllowed(allow, connection.name))\n .map((connection) => ({ name: connection.name, connection }));\n }\n\n resourceExposure(): McpExposure | undefined {\n const exposures = this.#resourceful().map((connection) =>\n effective(connection.entry.config.exposure ?? 'codemode', this.codemodeOn)\n );\n if (!exposures.length) return undefined;\n return exposures.includes('direct') ? 'direct' : 'codemode';\n }\n\n listings(allow: readonly string[] | null): Listing[] {\n return [...this.connections.values()]\n .filter((connection) => {\n if (!serverAllowed(allow, connection.name)) return false;\n const { config } = connection.entry;\n const exposures = [\n config.exposure ?? 'codemode',\n ...Object.values(config.toolExposure ?? {}),\n ];\n return exposures.some((exposure) => effective(exposure, this.codemodeOn) === 'codemode');\n })\n .map((connection) => ({\n name: connection.name,\n ...(connection.entry.config.description\n ? { description: connection.entry.config.description }\n : {}),\n ...(connection.instructions ? { instructions: connection.instructions } : {}),\n }));\n }\n\n install(): void {\n const readable = this.resourceExposure() !== undefined;\n const direct = this.entries\n .filter((entry) => entry.exposure === 'direct')\n .map((entry) => directTool(entry, () => readable && entry.connection.hasResources));\n const resources =\n this.resourceExposure() === 'direct'\n ? resourceTools(async (api, context) =>\n this.resourceServers(await allowOf(api, api.conversationId, context))\n )\n : [];\n this.current = defineExtension({\n name: MCP_EXTENSION,\n tools: [...direct, ...resources],\n sections: [this.#section],\n hooks: [this.#guard],\n });\n this.#registry.install(this.current);\n }\n\n rebuild(): void {\n const taken = new Set<string>();\n const next: McpToolEntry[] = [];\n for (const connection of this.connected()) {\n for (const tool of connection.tools) {\n const exposure = effective(\n toolExposure(connection.entry.config, tool.name),\n this.codemodeOn\n );\n if (exposure === 'hidden') continue;\n const name = mcpToolName(connection.name, tool.name, (candidate) => taken.has(candidate));\n taken.add(name);\n next.push({ server: connection.name, name, exposure, tool, connection });\n }\n }\n this.entries = next;\n this.install();\n }\n\n direct(names: readonly string[]): ToolRegistration[] {\n const match = nameMatcher([...names]);\n return ((this.current.tools ?? []) as ToolRegistration[]).filter((tool) => match(tool.name));\n }\n\n async waitFor(allow: readonly string[] | null): Promise<void> {\n const pending = [...this.connections.values()].filter(\n (connection) => connection.state === 'connecting' && serverAllowed(allow, connection.name)\n );\n if (!pending.length) return;\n let timer: ReturnType<typeof setTimeout> | undefined;\n await Promise.race([\n Promise.allSettled(pending.map((connection) => connection.getClient())),\n new Promise((resolve) => {\n timer = setTimeout(resolve, DIRECT_WAIT_MS);\n }),\n ]);\n clearTimeout(timer);\n }\n\n #namespaces(): Map<string, Namespace> {\n const spaces = new Map<string, Namespace>();\n for (const entry of this.entries) {\n const { config } = entry.connection.entry;\n spaces.set(entry.name, {\n name: mcpNamespace(entry.server),\n ...(config.description ? { description: config.description } : {}),\n ...(entry.connection.instructions ? { instructions: entry.connection.instructions } : {}),\n });\n }\n return spaces;\n }\n\n async codemode(api: ToolExecutionApi, agent: Agent, context: Context): Promise<CodemodeExtra> {\n if (!agent.extensions.some((extension) => extension.name === MCP_EXTENSION))\n return { tools: [], globals: [] };\n const allow = await allowOf(api, api.conversationId, context);\n await this.waitFor(allow);\n const signal = context.abortSignal as AbortSignal | undefined;\n const offered = new Set(agent.tools.map((tool) => tool.name));\n const tools = this.entries\n .filter(\n (entry) =>\n allows(allow, entry.name) && (entry.exposure !== 'direct' || offered.has(entry.name))\n )\n .map((entry) => scriptTool(entry, signal));\n const save = apiSaver(api, 'r', context);\n const resources =\n this.resourceExposure() === 'codemode' && this.resourceServers(allow).length\n ? resourceScriptTools(() => this.resourceServers(allow), signal, save).filter((tool) =>\n allows(allow, tool.name)\n )\n : [];\n const all = [...tools, ...resources];\n const spaces = this.#namespaces();\n return {\n tools: all,\n globals: all.length ? discoveryGlobals(all, (name) => spaces.get(name)) : [],\n };\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { hash } from "../licks/sources.js";
|
|
2
|
+
const SERVER_NAME = /^[A-Za-z0-9_-]+$/;
|
|
3
|
+
const EXPOSURES = ["codemode", "deferred", "direct", "hidden"];
|
|
4
|
+
const ALIASES = { "codemode-deferred": "codemode" };
|
|
5
|
+
const ACCEPTED = /* @__PURE__ */ new Set([
|
|
6
|
+
"type",
|
|
7
|
+
"url",
|
|
8
|
+
"headers",
|
|
9
|
+
"exposure",
|
|
10
|
+
"toolExposure",
|
|
11
|
+
"enabled",
|
|
12
|
+
"timeout",
|
|
13
|
+
"description"
|
|
14
|
+
]);
|
|
15
|
+
const SECRET_HEADER = /authorization|cookie|token|key|secret|password|session/i;
|
|
16
|
+
const SECRET_PARAM = /token|key|secret|password|signature|sig|auth|code/i;
|
|
17
|
+
const PLACEHOLDER = /\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g;
|
|
18
|
+
const SCHEME = /^\s*(?:bearer|basic|token|apikey|api-key)\s+/i;
|
|
19
|
+
const MAX_TOOL_NAME = 64;
|
|
20
|
+
const DEFAULT_TIMEOUT_SECONDS = 60;
|
|
21
|
+
function isRecord(value) {
|
|
22
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
23
|
+
}
|
|
24
|
+
function alias(value) {
|
|
25
|
+
return typeof value === "string" ? ALIASES[value] ?? value : value;
|
|
26
|
+
}
|
|
27
|
+
function isExposure(value) {
|
|
28
|
+
return typeof value === "string" && EXPOSURES.includes(value);
|
|
29
|
+
}
|
|
30
|
+
function placeholders(value) {
|
|
31
|
+
return [...value.matchAll(PLACEHOLDER)].map((match) => match[1]);
|
|
32
|
+
}
|
|
33
|
+
function literalSecret(header, value) {
|
|
34
|
+
if (!SECRET_HEADER.test(header)) return false;
|
|
35
|
+
return value.replace(PLACEHOLDER, "").replace(SCHEME, "").trim() !== "";
|
|
36
|
+
}
|
|
37
|
+
function urlSecret(url) {
|
|
38
|
+
if (url.username || url.password) return "url";
|
|
39
|
+
for (const name of url.searchParams.keys()) if (SECRET_PARAM.test(name)) return `url ?${name}`;
|
|
40
|
+
return void 0;
|
|
41
|
+
}
|
|
42
|
+
function fail(server, message, field) {
|
|
43
|
+
return { server, ...field ? { field } : {}, message };
|
|
44
|
+
}
|
|
45
|
+
function checkHeaders(name, headers) {
|
|
46
|
+
if (headers === void 0) return void 0;
|
|
47
|
+
if (!isRecord(headers) || !Object.values(headers).every((value) => typeof value === "string"))
|
|
48
|
+
return fail(name, "headers must map names to strings", "headers");
|
|
49
|
+
for (const [header, value] of Object.entries(headers)) {
|
|
50
|
+
if (value.trim().startsWith("!"))
|
|
51
|
+
return fail(
|
|
52
|
+
name,
|
|
53
|
+
`headers.${header} runs a command, which SLICC can't do`,
|
|
54
|
+
`headers.${header}`
|
|
55
|
+
);
|
|
56
|
+
if (literalSecret(header, value))
|
|
57
|
+
return fail(
|
|
58
|
+
name,
|
|
59
|
+
`headers.${header} holds a literal secret. mcp.json is readable by every agent: write \${NAME} instead, and add the value in Settings \u203A Accounts`,
|
|
60
|
+
`headers.${header}`
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
return void 0;
|
|
64
|
+
}
|
|
65
|
+
function checkExposures(name, value) {
|
|
66
|
+
const allowed = EXPOSURES.map((item) => `"${item}"`).join(", ");
|
|
67
|
+
if (value.exposure !== void 0 && !isExposure(value.exposure))
|
|
68
|
+
return fail(name, `exposure must be one of ${allowed}`, "exposure");
|
|
69
|
+
if (value.toolExposure === void 0) return void 0;
|
|
70
|
+
if (!isRecord(value.toolExposure))
|
|
71
|
+
return fail(name, "toolExposure must map tool names to exposures", "toolExposure");
|
|
72
|
+
for (const [tool, exposure] of Object.entries(value.toolExposure))
|
|
73
|
+
if (!isExposure(exposure))
|
|
74
|
+
return fail(name, `toolExposure "${tool}" must be one of ${allowed}`, `toolExposure.${tool}`);
|
|
75
|
+
return void 0;
|
|
76
|
+
}
|
|
77
|
+
function checkFields(name, value) {
|
|
78
|
+
if (value.enabled !== void 0 && typeof value.enabled !== "boolean")
|
|
79
|
+
return fail(name, "enabled must be a boolean", "enabled");
|
|
80
|
+
if (value.description !== void 0 && typeof value.description !== "string")
|
|
81
|
+
return fail(name, "description must be a string", "description");
|
|
82
|
+
if (value.timeout !== void 0 && (typeof value.timeout !== "number" || !(value.timeout > 0)))
|
|
83
|
+
return fail(name, "timeout must be a positive number of seconds", "timeout");
|
|
84
|
+
return void 0;
|
|
85
|
+
}
|
|
86
|
+
function checkTransport(name, value) {
|
|
87
|
+
if (value.command !== void 0 || value.type === "stdio")
|
|
88
|
+
return fail(name, "stdio servers need slicc-kernel#68, so this one is skipped", "command");
|
|
89
|
+
if (value.type === "sse")
|
|
90
|
+
return fail(
|
|
91
|
+
name,
|
|
92
|
+
"the legacy SSE transport is not supported; use the streamable HTTP URL",
|
|
93
|
+
"type"
|
|
94
|
+
);
|
|
95
|
+
if (value.type !== void 0 && value.type !== "http" && value.type !== "streamable-http")
|
|
96
|
+
return fail(name, 'type must be "http" or "streamable-http"', "type");
|
|
97
|
+
if (typeof value.url !== "string") return fail(name, 'needs a "url" (streamable HTTP)', "url");
|
|
98
|
+
if (!URL.canParse(value.url) || !/^https?:$/.test(new URL(value.url).protocol))
|
|
99
|
+
return fail(name, "url must be an http or https URL", "url");
|
|
100
|
+
const secret = urlSecret(new URL(value.url));
|
|
101
|
+
if (secret)
|
|
102
|
+
return fail(
|
|
103
|
+
name,
|
|
104
|
+
`${secret} holds a literal secret. Put it in a header as \${NAME} and add the value in Settings \u203A Accounts`,
|
|
105
|
+
"url"
|
|
106
|
+
);
|
|
107
|
+
return void 0;
|
|
108
|
+
}
|
|
109
|
+
function validateServer(name, raw) {
|
|
110
|
+
if (!SERVER_NAME.test(name))
|
|
111
|
+
return fail(name, `invalid server name "${name}" (use letters, digits, "_" and "-")`);
|
|
112
|
+
if (!isRecord(raw)) return fail(name, "must be an object");
|
|
113
|
+
const value = { ...raw, exposure: alias(raw.exposure) };
|
|
114
|
+
if (raw.exposure === void 0) delete value.exposure;
|
|
115
|
+
if (isRecord(raw.toolExposure))
|
|
116
|
+
value.toolExposure = Object.fromEntries(
|
|
117
|
+
Object.entries(raw.toolExposure).map(([tool, exposure]) => [tool, alias(exposure)])
|
|
118
|
+
);
|
|
119
|
+
for (const key of Object.keys(value))
|
|
120
|
+
if (key === "oauth" || key === "auth")
|
|
121
|
+
return fail(name, `${key} is not supported in SLICC yet`, key);
|
|
122
|
+
const problem = checkTransport(name, value) ?? checkHeaders(name, value.headers) ?? checkExposures(name, value) ?? checkFields(name, value);
|
|
123
|
+
if (problem) return problem;
|
|
124
|
+
const config = {};
|
|
125
|
+
for (const [key, entry] of Object.entries(value))
|
|
126
|
+
if (ACCEPTED.has(key) && key !== "type") config[key] = entry;
|
|
127
|
+
return config;
|
|
128
|
+
}
|
|
129
|
+
function canonical(name) {
|
|
130
|
+
return name.replace(/-/g, "_");
|
|
131
|
+
}
|
|
132
|
+
function parseMcpConfig(text) {
|
|
133
|
+
if (text === void 0 || text.trim() === "") return { servers: [], problems: [] };
|
|
134
|
+
let parsed;
|
|
135
|
+
try {
|
|
136
|
+
parsed = JSON.parse(text);
|
|
137
|
+
} catch (error) {
|
|
138
|
+
return { servers: [], problems: [{ message: `not valid JSON: ${error.message}` }] };
|
|
139
|
+
}
|
|
140
|
+
if (!isRecord(parsed) || parsed.mcpServers !== void 0 && !isRecord(parsed.mcpServers))
|
|
141
|
+
return { servers: [], problems: [{ message: "mcpServers must be an object" }] };
|
|
142
|
+
const servers = [];
|
|
143
|
+
const problems = [];
|
|
144
|
+
const seen = /* @__PURE__ */ new Set();
|
|
145
|
+
for (const [name, raw] of Object.entries(parsed.mcpServers ?? {})) {
|
|
146
|
+
const checked = validateServer(name, raw);
|
|
147
|
+
if ("message" in checked) {
|
|
148
|
+
problems.push(checked);
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
if (seen.has(canonical(name))) {
|
|
152
|
+
problems.push(fail(name, 'names that differ only in "-" and "_" count as the same server'));
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
seen.add(canonical(name));
|
|
156
|
+
servers.push({ name, config: checked });
|
|
157
|
+
}
|
|
158
|
+
return { servers, problems };
|
|
159
|
+
}
|
|
160
|
+
function patternRegExp(pattern) {
|
|
161
|
+
const source = pattern.split("*").map((part) => part.replace(/[.+?^${}()|[\]\\]/g, "\\$&")).join(".*");
|
|
162
|
+
return new RegExp(`^${source}$`);
|
|
163
|
+
}
|
|
164
|
+
function nameMatcher(entries) {
|
|
165
|
+
const names = new Set(entries.filter((entry) => !entry.includes("*")));
|
|
166
|
+
const patterns = entries.filter((entry) => entry.includes("*")).map(patternRegExp);
|
|
167
|
+
return (name) => names.has(name) || patterns.some((pattern) => pattern.test(name));
|
|
168
|
+
}
|
|
169
|
+
function toolExposure(config, tool) {
|
|
170
|
+
const overrides = config.toolExposure ?? {};
|
|
171
|
+
const exact = overrides[tool];
|
|
172
|
+
if (exact !== void 0) return exact;
|
|
173
|
+
for (const [pattern, exposure] of Object.entries(overrides))
|
|
174
|
+
if (pattern.includes("*") && patternRegExp(pattern).test(tool)) return exposure;
|
|
175
|
+
return config.exposure ?? "codemode";
|
|
176
|
+
}
|
|
177
|
+
function mcpNamespace(server) {
|
|
178
|
+
return `mcp__${canonical(server)}`;
|
|
179
|
+
}
|
|
180
|
+
function mcpToolName(server, tool, taken = () => false) {
|
|
181
|
+
const name = `mcp__${server}__${tool}`.replace(/[^A-Za-z0-9_]/g, "_");
|
|
182
|
+
if (name.length <= MAX_TOOL_NAME && !taken(name)) return name;
|
|
183
|
+
const suffix = hash(`${server}\0${tool}`).padStart(7, "0").slice(0, 7);
|
|
184
|
+
return `${name.slice(0, MAX_TOOL_NAME - suffix.length - 1)}_${suffix}`;
|
|
185
|
+
}
|
|
186
|
+
function sameConfig(a, b) {
|
|
187
|
+
return JSON.stringify(a) === JSON.stringify(b);
|
|
188
|
+
}
|
|
189
|
+
export {
|
|
190
|
+
DEFAULT_TIMEOUT_SECONDS,
|
|
191
|
+
literalSecret,
|
|
192
|
+
mcpNamespace,
|
|
193
|
+
mcpToolName,
|
|
194
|
+
nameMatcher,
|
|
195
|
+
parseMcpConfig,
|
|
196
|
+
placeholders,
|
|
197
|
+
sameConfig,
|
|
198
|
+
toolExposure,
|
|
199
|
+
validateServer
|
|
200
|
+
};
|
|
201
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/mcp/config.ts"],
|
|
4
|
+
"sourcesContent": ["import { hash } from '../licks/sources.ts';\n\nexport type McpExposure = 'codemode' | 'deferred' | 'direct' | 'hidden';\n\nexport interface McpServerConfig {\n url: string;\n headers?: Record<string, string>;\n exposure?: McpExposure;\n toolExposure?: Record<string, McpExposure>;\n enabled?: boolean;\n timeout?: number;\n description?: string;\n}\n\nexport interface McpServerEntry {\n name: string;\n config: McpServerConfig;\n}\n\nexport interface McpConfigProblem {\n server?: string;\n field?: string;\n message: string;\n}\n\nexport interface McpConfig {\n servers: McpServerEntry[];\n problems: McpConfigProblem[];\n}\n\nconst SERVER_NAME = /^[A-Za-z0-9_-]+$/;\nconst EXPOSURES: readonly string[] = ['codemode', 'deferred', 'direct', 'hidden'];\nconst ALIASES: Record<string, McpExposure> = { 'codemode-deferred': 'codemode' };\nconst ACCEPTED = new Set([\n 'type',\n 'url',\n 'headers',\n 'exposure',\n 'toolExposure',\n 'enabled',\n 'timeout',\n 'description',\n]);\nconst SECRET_HEADER = /authorization|cookie|token|key|secret|password|session/i;\nconst SECRET_PARAM = /token|key|secret|password|signature|sig|auth|code/i;\nconst PLACEHOLDER = /\\$\\{([A-Za-z_][A-Za-z0-9_]*)\\}/g;\nconst SCHEME = /^\\s*(?:bearer|basic|token|apikey|api-key)\\s+/i;\nconst MAX_TOOL_NAME = 64;\nexport const DEFAULT_TIMEOUT_SECONDS = 60;\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction alias(value: unknown): unknown {\n return typeof value === 'string' ? (ALIASES[value] ?? value) : value;\n}\n\nfunction isExposure(value: unknown): value is McpExposure {\n return typeof value === 'string' && EXPOSURES.includes(value);\n}\n\nexport function placeholders(value: string): string[] {\n return [...value.matchAll(PLACEHOLDER)].map((match) => match[1] as string);\n}\n\nexport function literalSecret(header: string, value: string): boolean {\n if (!SECRET_HEADER.test(header)) return false;\n return value.replace(PLACEHOLDER, '').replace(SCHEME, '').trim() !== '';\n}\n\nfunction urlSecret(url: URL): string | undefined {\n if (url.username || url.password) return 'url';\n for (const name of url.searchParams.keys()) if (SECRET_PARAM.test(name)) return `url ?${name}`;\n return undefined;\n}\n\ntype Checked = McpServerConfig | McpConfigProblem;\n\nfunction fail(server: string, message: string, field?: string): McpConfigProblem {\n return { server, ...(field ? { field } : {}), message };\n}\n\nfunction checkHeaders(name: string, headers: unknown): McpConfigProblem | undefined {\n if (headers === undefined) return undefined;\n if (!isRecord(headers) || !Object.values(headers).every((value) => typeof value === 'string'))\n return fail(name, 'headers must map names to strings', 'headers');\n for (const [header, value] of Object.entries(headers as Record<string, string>)) {\n if (value.trim().startsWith('!'))\n return fail(\n name,\n `headers.${header} runs a command, which SLICC can't do`,\n `headers.${header}`\n );\n if (literalSecret(header, value))\n return fail(\n name,\n `headers.${header} holds a literal secret. mcp.json is readable by every agent: write \\${NAME} instead, and add the value in Settings \u203A Accounts`,\n `headers.${header}`\n );\n }\n return undefined;\n}\n\nfunction checkExposures(\n name: string,\n value: Record<string, unknown>\n): McpConfigProblem | undefined {\n const allowed = EXPOSURES.map((item) => `\"${item}\"`).join(', ');\n if (value.exposure !== undefined && !isExposure(value.exposure))\n return fail(name, `exposure must be one of ${allowed}`, 'exposure');\n if (value.toolExposure === undefined) return undefined;\n if (!isRecord(value.toolExposure))\n return fail(name, 'toolExposure must map tool names to exposures', 'toolExposure');\n for (const [tool, exposure] of Object.entries(value.toolExposure))\n if (!isExposure(exposure))\n return fail(name, `toolExposure \"${tool}\" must be one of ${allowed}`, `toolExposure.${tool}`);\n return undefined;\n}\n\nfunction checkFields(name: string, value: Record<string, unknown>): McpConfigProblem | undefined {\n if (value.enabled !== undefined && typeof value.enabled !== 'boolean')\n return fail(name, 'enabled must be a boolean', 'enabled');\n if (value.description !== undefined && typeof value.description !== 'string')\n return fail(name, 'description must be a string', 'description');\n if (value.timeout !== undefined && (typeof value.timeout !== 'number' || !(value.timeout > 0)))\n return fail(name, 'timeout must be a positive number of seconds', 'timeout');\n return undefined;\n}\n\nfunction checkTransport(\n name: string,\n value: Record<string, unknown>\n): McpConfigProblem | undefined {\n if (value.command !== undefined || value.type === 'stdio')\n return fail(name, 'stdio servers need slicc-kernel#68, so this one is skipped', 'command');\n if (value.type === 'sse')\n return fail(\n name,\n 'the legacy SSE transport is not supported; use the streamable HTTP URL',\n 'type'\n );\n if (value.type !== undefined && value.type !== 'http' && value.type !== 'streamable-http')\n return fail(name, 'type must be \"http\" or \"streamable-http\"', 'type');\n if (typeof value.url !== 'string') return fail(name, 'needs a \"url\" (streamable HTTP)', 'url');\n if (!URL.canParse(value.url) || !/^https?:$/.test(new URL(value.url).protocol))\n return fail(name, 'url must be an http or https URL', 'url');\n const secret = urlSecret(new URL(value.url));\n if (secret)\n return fail(\n name,\n `${secret} holds a literal secret. Put it in a header as \\${NAME} and add the value in Settings \u203A Accounts`,\n 'url'\n );\n return undefined;\n}\n\nexport function validateServer(name: string, raw: unknown): Checked {\n if (!SERVER_NAME.test(name))\n return fail(name, `invalid server name \"${name}\" (use letters, digits, \"_\" and \"-\")`);\n if (!isRecord(raw)) return fail(name, 'must be an object');\n const value: Record<string, unknown> = { ...raw, exposure: alias(raw.exposure) };\n if (raw.exposure === undefined) delete value.exposure;\n if (isRecord(raw.toolExposure))\n value.toolExposure = Object.fromEntries(\n Object.entries(raw.toolExposure).map(([tool, exposure]) => [tool, alias(exposure)])\n );\n for (const key of Object.keys(value))\n if (key === 'oauth' || key === 'auth')\n return fail(name, `${key} is not supported in SLICC yet`, key);\n const problem =\n checkTransport(name, value) ??\n checkHeaders(name, value.headers) ??\n checkExposures(name, value) ??\n checkFields(name, value);\n if (problem) return problem;\n const config: Record<string, unknown> = {};\n for (const [key, entry] of Object.entries(value))\n if (ACCEPTED.has(key) && key !== 'type') config[key] = entry;\n return config as unknown as McpServerConfig;\n}\n\nfunction canonical(name: string): string {\n return name.replace(/-/g, '_');\n}\n\nexport function parseMcpConfig(text: string | undefined): McpConfig {\n if (text === undefined || text.trim() === '') return { servers: [], problems: [] };\n let parsed: unknown;\n try {\n parsed = JSON.parse(text);\n } catch (error) {\n return { servers: [], problems: [{ message: `not valid JSON: ${(error as Error).message}` }] };\n }\n if (!isRecord(parsed) || (parsed.mcpServers !== undefined && !isRecord(parsed.mcpServers)))\n return { servers: [], problems: [{ message: 'mcpServers must be an object' }] };\n const servers: McpServerEntry[] = [];\n const problems: McpConfigProblem[] = [];\n const seen = new Set<string>();\n for (const [name, raw] of Object.entries((parsed.mcpServers ?? {}) as Record<string, unknown>)) {\n const checked = validateServer(name, raw);\n if ('message' in checked) {\n problems.push(checked);\n continue;\n }\n if (seen.has(canonical(name))) {\n problems.push(fail(name, 'names that differ only in \"-\" and \"_\" count as the same server'));\n continue;\n }\n seen.add(canonical(name));\n servers.push({ name, config: checked });\n }\n return { servers, problems };\n}\n\nfunction patternRegExp(pattern: string): RegExp {\n const source = pattern\n .split('*')\n .map((part) => part.replace(/[.+?^${}()|[\\]\\\\]/g, '\\\\$&'))\n .join('.*');\n return new RegExp(`^${source}$`);\n}\n\nexport function nameMatcher(entries: readonly string[]): (name: string) => boolean {\n const names = new Set(entries.filter((entry) => !entry.includes('*')));\n const patterns = entries.filter((entry) => entry.includes('*')).map(patternRegExp);\n return (name) => names.has(name) || patterns.some((pattern) => pattern.test(name));\n}\n\nexport function toolExposure(config: McpServerConfig, tool: string): McpExposure {\n const overrides = config.toolExposure ?? {};\n const exact = overrides[tool];\n if (exact !== undefined) return exact;\n for (const [pattern, exposure] of Object.entries(overrides))\n if (pattern.includes('*') && patternRegExp(pattern).test(tool)) return exposure;\n return config.exposure ?? 'codemode';\n}\n\nexport function mcpNamespace(server: string): string {\n return `mcp__${canonical(server)}`;\n}\n\nexport function mcpToolName(\n server: string,\n tool: string,\n taken: (name: string) => boolean = () => false\n): string {\n const name = `mcp__${server}__${tool}`.replace(/[^A-Za-z0-9_]/g, '_');\n if (name.length <= MAX_TOOL_NAME && !taken(name)) return name;\n const suffix = hash(`${server}\\0${tool}`).padStart(7, '0').slice(0, 7);\n return `${name.slice(0, MAX_TOOL_NAME - suffix.length - 1)}_${suffix}`;\n}\n\nexport function sameConfig(a: McpServerConfig, b: McpServerConfig): boolean {\n return JSON.stringify(a) === JSON.stringify(b);\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|