@agimon-ai/po-mcp 0.0.0-alpha.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/LICENSE +21 -0
- package/README.md +223 -0
- package/dist/apiContracts.cjs +269 -0
- package/dist/apiContracts.cjs.map +1 -0
- package/dist/apiContracts.d.mts +137 -0
- package/dist/apiContracts.d.mts.map +1 -0
- package/dist/apiContracts.mjs +258 -0
- package/dist/apiContracts.mjs.map +1 -0
- package/dist/extensions/mcp-ui.mjs +31 -0
- package/dist/extensions/mcp-ui.mjs.map +1 -0
- package/dist/extensions/mcp.cjs +104 -0
- package/dist/extensions/mcp.cjs.map +1 -0
- package/dist/extensions/mcp.d.mts +5 -0
- package/dist/extensions/mcp.d.mts.map +1 -0
- package/dist/extensions/mcp.mjs +99 -0
- package/dist/extensions/mcp.mjs.map +1 -0
- package/dist/extensions/server.cjs +2852 -0
- package/dist/extensions/server.cjs.map +1 -0
- package/dist/extensions/server.d.mts +5 -0
- package/dist/extensions/server.d.mts.map +1 -0
- package/dist/extensions/server.mjs +2839 -0
- package/dist/extensions/server.mjs.map +1 -0
- package/dist/extensions/web.mjs +2200 -0
- package/dist/extensions/web.mjs.map +1 -0
- package/dist/index.cjs +18 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.mts +89 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +9 -0
- package/dist/index.mjs.map +1 -0
- package/dist/mcpCatalog-6Tpt6Gqn.cjs +900 -0
- package/dist/mcpCatalog-6Tpt6Gqn.cjs.map +1 -0
- package/dist/mcpCatalog-SlqWS-sb.mjs +806 -0
- package/dist/mcpCatalog-SlqWS-sb.mjs.map +1 -0
- package/dist/mcpConfig-BYESy-zP.d.mts +57 -0
- package/dist/mcpConfig-BYESy-zP.d.mts.map +1 -0
- package/dist/projection-gyAA2jRB.mjs +43 -0
- package/dist/projection-gyAA2jRB.mjs.map +1 -0
- package/dist/projection-w3XxXmVi.cjs +51 -0
- package/dist/projection-w3XxXmVi.cjs.map +1 -0
- package/dist/projection.cjs +17 -0
- package/dist/projection.d.mts +12 -0
- package/dist/projection.d.mts.map +1 -0
- package/dist/projection.mjs +4 -0
- package/dist/rolldown-runtime-C0BPl7ul.cjs +43 -0
- package/llms.txt +9 -0
- package/package.json +157 -0
- package/src/prompts/po-use-mcp/SKILL.md +79 -0
|
@@ -0,0 +1,900 @@
|
|
|
1
|
+
const require_rolldown_runtime = require("./rolldown-runtime-C0BPl7ul.cjs");
|
|
2
|
+
let node_crypto = require("node:crypto");
|
|
3
|
+
let node_fs = require("node:fs");
|
|
4
|
+
node_fs = require_rolldown_runtime.__toESM(node_fs, 1);
|
|
5
|
+
let node_path = require("node:path");
|
|
6
|
+
node_path = require_rolldown_runtime.__toESM(node_path, 1);
|
|
7
|
+
let _agimon_ai_po_config_agentPluginMcp = require("@agimon-ai/po-config/agentPluginMcp");
|
|
8
|
+
let yaml = require("yaml");
|
|
9
|
+
let node_os = require("node:os");
|
|
10
|
+
node_os = require_rolldown_runtime.__toESM(node_os, 1);
|
|
11
|
+
let _agimon_ai_mcp_proxy_package_json = require("@agimon-ai/mcp-proxy/package.json");
|
|
12
|
+
_agimon_ai_mcp_proxy_package_json = require_rolldown_runtime.__toESM(_agimon_ai_mcp_proxy_package_json, 1);
|
|
13
|
+
let _agimon_ai_po_core_mcpSession = require("@agimon-ai/po-core/mcpSession");
|
|
14
|
+
//#region src/services/configSources/index.ts
|
|
15
|
+
/** Wrapper entry used by non-Doom clients to expose `mcp-config.yaml` as one MCP. */
|
|
16
|
+
const PROXY_SERVER_NAME = "mcp-proxy";
|
|
17
|
+
const PROXY_SERVER_ALIASES = /* @__PURE__ */ new Set([PROXY_SERVER_NAME, "agiflow-proxy"]);
|
|
18
|
+
const PRIVATE_FILE_MODE = 384;
|
|
19
|
+
const PRIVATE_DIRECTORY_MODE = 448;
|
|
20
|
+
const DEFAULT_UPSTREAM_CONFIG = "mcp-config.yaml";
|
|
21
|
+
const CONFIG_ARGUMENT = "--config";
|
|
22
|
+
function isObject(value) {
|
|
23
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
24
|
+
}
|
|
25
|
+
function isYamlPath(filePath) {
|
|
26
|
+
const extension = node_path.default.extname(filePath).toLowerCase();
|
|
27
|
+
return extension === ".yaml" || extension === ".yml";
|
|
28
|
+
}
|
|
29
|
+
function readConfigDocument(filePath) {
|
|
30
|
+
try {
|
|
31
|
+
const contents = node_fs.default.readFileSync(filePath, "utf8");
|
|
32
|
+
const parsed = isYamlPath(filePath) ? (0, yaml.parse)(contents) : JSON.parse(contents);
|
|
33
|
+
return isObject(parsed) ? {
|
|
34
|
+
config: parsed,
|
|
35
|
+
contents
|
|
36
|
+
} : void 0;
|
|
37
|
+
} catch {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function readConfigObject(filePath) {
|
|
42
|
+
return readConfigDocument(filePath)?.config;
|
|
43
|
+
}
|
|
44
|
+
function serverDefinitions(config) {
|
|
45
|
+
return isObject(config?.mcpServers) ? config.mcpServers : {};
|
|
46
|
+
}
|
|
47
|
+
function isEnabledDefinition(definition) {
|
|
48
|
+
return !isObject(definition) || definition.disabled !== true;
|
|
49
|
+
}
|
|
50
|
+
function isProxyWrapper(name) {
|
|
51
|
+
return PROXY_SERVER_ALIASES.has(name);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Resolves the upstream config referenced by the repository proxy wrapper.
|
|
55
|
+
*
|
|
56
|
+
* The wrapper remains useful to Claude, Codex, and other external clients. Doom
|
|
57
|
+
* embeds the same client/runtime code, so it reads this path and connects each
|
|
58
|
+
* declared server itself instead of starting the wrapper as a nested MCP.
|
|
59
|
+
*/
|
|
60
|
+
function resolveSharedConfigPathFrom(repoRoot, repositoryConfigPath) {
|
|
61
|
+
const servers = serverDefinitions(readConfigObject(repositoryConfigPath));
|
|
62
|
+
const rawArgs = [...PROXY_SERVER_ALIASES].map((name) => servers[name]).find((definition) => isObject(definition))?.args;
|
|
63
|
+
const args = Array.isArray(rawArgs) ? rawArgs : [];
|
|
64
|
+
const configArgumentIndex = args.indexOf(CONFIG_ARGUMENT);
|
|
65
|
+
const declared = args[configArgumentIndex + 1];
|
|
66
|
+
const candidate = configArgumentIndex >= 0 && typeof declared === "string" ? node_path.default.resolve(repoRoot, declared) : node_path.default.join(repoRoot, DEFAULT_UPSTREAM_CONFIG);
|
|
67
|
+
return node_fs.default.existsSync(candidate) ? candidate : void 0;
|
|
68
|
+
}
|
|
69
|
+
function resolveSharedConfigPath(repoRoot) {
|
|
70
|
+
return resolveSharedConfigPathFrom(repoRoot, node_path.default.join(repoRoot, ".mcp.json"));
|
|
71
|
+
}
|
|
72
|
+
/** Empty and absent allowlists both mean "keep everything". */
|
|
73
|
+
function isAllowed(name, allowed) {
|
|
74
|
+
return !allowed || allowed.length === 0 || allowed.includes(name);
|
|
75
|
+
}
|
|
76
|
+
function stagedPath(filePath, contents, stagingDirectory) {
|
|
77
|
+
const digest = (0, node_crypto.createHash)("sha256").update(filePath).update("\0").update(contents).digest("hex").slice(0, 16);
|
|
78
|
+
const extension = isYamlPath(filePath) ? ".yaml" : ".json";
|
|
79
|
+
return node_path.default.join(stagingDirectory, `mcp-${digest}${extension}`);
|
|
80
|
+
}
|
|
81
|
+
function serializeConfig(filePath, config) {
|
|
82
|
+
return isYamlPath(filePath) ? (0, yaml.stringify)(config) : `${JSON.stringify(config, null, 2)}\n`;
|
|
83
|
+
}
|
|
84
|
+
function layerCacheKey(filePath, format, originalContents, effectiveContents, sourceIdentity) {
|
|
85
|
+
return (0, node_crypto.createHash)("sha256").update(format).update("\0").update(node_fs.default.realpathSync(filePath)).update("\0").update(sourceIdentity ?? "unprojected").update("\0").update(originalContents).update("\0").update(effectiveContents).digest("hex");
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Rewrites one config layer with disallowed servers removed.
|
|
89
|
+
*
|
|
90
|
+
* Proxy wrapper entries are stripped from `.mcp.json` layers because Doom reaches
|
|
91
|
+
* their upstream config directly. Entries are removed rather than marked disabled,
|
|
92
|
+
* so a filtered stdio server is never spawned at all.
|
|
93
|
+
*/
|
|
94
|
+
function filterLayer(filePath, allowed, stagingDirectory, options) {
|
|
95
|
+
const document = readConfigDocument(filePath);
|
|
96
|
+
if (!document) return void 0;
|
|
97
|
+
const { config, contents: originalContents } = document;
|
|
98
|
+
const servers = serverDefinitions(config);
|
|
99
|
+
const kept = {};
|
|
100
|
+
const dropped = [];
|
|
101
|
+
for (const [name, definition] of Object.entries(servers)) if (options.stripProxyWrappers && isProxyWrapper(name) || !isAllowed(name, allowed)) dropped.push(name);
|
|
102
|
+
else kept[name] = definition;
|
|
103
|
+
const keptNames = Object.entries(kept).filter(([, definition]) => isEnabledDefinition(definition)).map(([name]) => name);
|
|
104
|
+
if (dropped.length === 0) return {
|
|
105
|
+
source: {
|
|
106
|
+
path: filePath,
|
|
107
|
+
format: "claude",
|
|
108
|
+
cacheKey: layerCacheKey(filePath, "claude", originalContents, originalContents, options.sourceIdentity)
|
|
109
|
+
},
|
|
110
|
+
kept: keptNames,
|
|
111
|
+
dropped
|
|
112
|
+
};
|
|
113
|
+
node_fs.default.mkdirSync(stagingDirectory, {
|
|
114
|
+
mode: PRIVATE_DIRECTORY_MODE,
|
|
115
|
+
recursive: true
|
|
116
|
+
});
|
|
117
|
+
const contents = serializeConfig(filePath, {
|
|
118
|
+
...config,
|
|
119
|
+
mcpServers: kept
|
|
120
|
+
});
|
|
121
|
+
const target = stagedPath(filePath, contents, stagingDirectory);
|
|
122
|
+
node_fs.default.writeFileSync(target, contents, { mode: PRIVATE_FILE_MODE });
|
|
123
|
+
return {
|
|
124
|
+
source: {
|
|
125
|
+
path: target,
|
|
126
|
+
format: "claude",
|
|
127
|
+
cacheKey: layerCacheKey(filePath, "claude", originalContents, contents, options.sourceIdentity)
|
|
128
|
+
},
|
|
129
|
+
kept: keptNames,
|
|
130
|
+
dropped
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
function digestMatches(source) {
|
|
134
|
+
try {
|
|
135
|
+
return (0, node_crypto.createHash)("sha256").update(node_fs.default.readFileSync(source.configPath)).digest("hex") === source.contentDigest;
|
|
136
|
+
} catch {
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
function emptyGroups(diagnostics = []) {
|
|
141
|
+
return {
|
|
142
|
+
shared: {
|
|
143
|
+
configSources: [],
|
|
144
|
+
configPaths: [],
|
|
145
|
+
serverNames: []
|
|
146
|
+
},
|
|
147
|
+
sessionLocal: {
|
|
148
|
+
configSources: [],
|
|
149
|
+
configPaths: [],
|
|
150
|
+
serverNames: []
|
|
151
|
+
},
|
|
152
|
+
droppedServers: [],
|
|
153
|
+
diagnostics
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Resolves every config layer for this session and applies its domain allowlist.
|
|
158
|
+
*
|
|
159
|
+
* All returned layers go into one embedded `@agimon-ai/mcp-proxy` client container.
|
|
160
|
+
* The package is used as a library only: its wrapper MCP never becomes a Doom MCP
|
|
161
|
+
* server, so status, enable/disable, resources, and OAuth stay attached to each
|
|
162
|
+
* actual downstream server.
|
|
163
|
+
*/
|
|
164
|
+
function buildMcpConfigGroups(input) {
|
|
165
|
+
if (input.enabled === false) return emptyGroups();
|
|
166
|
+
const projectedSources = input.sources;
|
|
167
|
+
const legacyLayers = projectedSources ? [] : [node_path.default.join(input.repoRoot, ".mcp.json"), ...(input.pluginConfigPaths ?? []).map((candidate) => node_path.default.resolve(input.repoRoot, candidate))].filter((candidate) => node_fs.default.existsSync(candidate));
|
|
168
|
+
const configSources = [];
|
|
169
|
+
const serverNames = [];
|
|
170
|
+
const droppedServers = [];
|
|
171
|
+
const diagnostics = [];
|
|
172
|
+
for (const layer of legacyLayers) {
|
|
173
|
+
const filtered = filterLayer(layer, input.allowlist?.servers, input.stagingDirectory, { stripProxyWrappers: true });
|
|
174
|
+
if (!filtered) continue;
|
|
175
|
+
configSources.push(filtered.source);
|
|
176
|
+
serverNames.push(...filtered.kept);
|
|
177
|
+
droppedServers.push(...filtered.dropped);
|
|
178
|
+
}
|
|
179
|
+
for (const source of projectedSources ?? []) {
|
|
180
|
+
if (source.format === "agent-plugin-v1") {
|
|
181
|
+
const normalized = (0, _agimon_ai_po_config_agentPluginMcp.normalizeAgentPluginMcpSource)(source, {
|
|
182
|
+
stagingDirectory: input.stagingDirectory,
|
|
183
|
+
allowedServers: input.allowlist?.servers
|
|
184
|
+
});
|
|
185
|
+
if (normalized.configSource) configSources.push(normalized.configSource);
|
|
186
|
+
serverNames.push(...normalized.serverNames);
|
|
187
|
+
droppedServers.push(...normalized.droppedServers);
|
|
188
|
+
diagnostics.push(...normalized.diagnostics);
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
if (!digestMatches(source)) {
|
|
192
|
+
diagnostics.push(`Native MCP source "${source.sourceId}" was disabled: the file is missing or its content digest changed.`);
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
const filtered = filterLayer(source.configPath, input.allowlist?.servers, input.stagingDirectory, {
|
|
196
|
+
stripProxyWrappers: true,
|
|
197
|
+
sourceIdentity: `${source.sourceId}:${source.contentDigest}`
|
|
198
|
+
});
|
|
199
|
+
if (!filtered) {
|
|
200
|
+
diagnostics.push(`Native MCP source "${source.sourceId}" was disabled: the config could not be parsed.`);
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
configSources.push(filtered.source);
|
|
204
|
+
serverNames.push(...filtered.kept);
|
|
205
|
+
droppedServers.push(...filtered.dropped);
|
|
206
|
+
}
|
|
207
|
+
const repositorySource = projectedSources?.find((source) => source.format === "native" && source.owner === "repository");
|
|
208
|
+
const upstreamPath = projectedSources ? repositorySource && digestMatches(repositorySource) ? resolveSharedConfigPathFrom(input.repoRoot, repositorySource.configPath) : void 0 : resolveSharedConfigPath(input.repoRoot);
|
|
209
|
+
const upstream = upstreamPath ? filterLayer(upstreamPath, input.allowlist?.proxy, input.stagingDirectory, { stripProxyWrappers: true }) : void 0;
|
|
210
|
+
if (upstream) droppedServers.push(...upstream.dropped);
|
|
211
|
+
return {
|
|
212
|
+
shared: {
|
|
213
|
+
configSources: upstream ? [upstream.source] : [],
|
|
214
|
+
configPaths: upstream ? [upstream.source.path] : [],
|
|
215
|
+
serverNames: upstream ? upstream.kept : []
|
|
216
|
+
},
|
|
217
|
+
sessionLocal: {
|
|
218
|
+
configSources,
|
|
219
|
+
configPaths: configSources.map((source) => source.path),
|
|
220
|
+
serverNames: [...new Set(serverNames)]
|
|
221
|
+
},
|
|
222
|
+
droppedServers: [...new Set(droppedServers)],
|
|
223
|
+
diagnostics
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
//#endregion
|
|
227
|
+
//#region src/services/mcpRuntime/index.ts
|
|
228
|
+
var mcpRuntime_exports = /* @__PURE__ */ require_rolldown_runtime.__exportAll({
|
|
229
|
+
McpRuntimeOwner: () => McpRuntimeOwner,
|
|
230
|
+
definitionsCachePath: () => definitionsCachePath,
|
|
231
|
+
hubCallbackSink: () => hubCallbackSink,
|
|
232
|
+
readCachedCatalog: () => readCachedCatalog
|
|
233
|
+
});
|
|
234
|
+
/**
|
|
235
|
+
* Presents the hub's redirect surface as the proxy's callback collaborator.
|
|
236
|
+
*
|
|
237
|
+
* `listen` binds nothing: the hub already serves the address this advertises,
|
|
238
|
+
* so opening a loopback port would only create one nobody redirects to.
|
|
239
|
+
*/
|
|
240
|
+
function hubCallbackSink(redirect) {
|
|
241
|
+
return {
|
|
242
|
+
redirectUri: () => redirect.redirectUri,
|
|
243
|
+
getPort: () => void 0,
|
|
244
|
+
listen: (preferredPort) => Promise.resolve(preferredPort),
|
|
245
|
+
reserve: (state, timeoutMs) => redirect.reserve(state, timeoutMs),
|
|
246
|
+
waitForCallback: (state, timeoutMs) => redirect.wait(state, timeoutMs),
|
|
247
|
+
cancel: (state) => redirect.cancel(state)
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
const MCP_APP_CLIENT_CAPABILITIES = { extensions: { "io.modelcontextprotocol/ui": { mimeTypes: ["text/html;profile=mcp-app", "text/html+skybridge"] } } };
|
|
251
|
+
/**
|
|
252
|
+
* Definitions cache path shared by Doom's synchronous warm reader and mcp-proxy.
|
|
253
|
+
*
|
|
254
|
+
* Generated staging paths are deliberately absent: projected source content must
|
|
255
|
+
* invalidate a reused path, while identical portable content must survive a new
|
|
256
|
+
* private staging directory. Stable source origins are carried inside each cache
|
|
257
|
+
* key; format and merge behavior remain part of the ordered identity.
|
|
258
|
+
*/
|
|
259
|
+
function definitionsCachePath(configSources) {
|
|
260
|
+
if (configSources.length === 0) throw new Error("definitionsCachePath requires at least one config source");
|
|
261
|
+
const digest = (0, node_crypto.createHash)("sha256").update(JSON.stringify(configSources.map((source) => ({
|
|
262
|
+
cacheKey: source.cacheKey,
|
|
263
|
+
format: source.format,
|
|
264
|
+
optional: source.optional ?? false,
|
|
265
|
+
mergeStrategy: source.mergeStrategy ?? "remote-priority"
|
|
266
|
+
})))).update("\0").update(_agimon_ai_mcp_proxy_package_json.default.version).update("\0").update(JSON.stringify(MCP_APP_CLIENT_CAPABILITIES)).digest("hex");
|
|
267
|
+
return (0, node_path.join)((0, node_os.homedir)(), ".mcp-proxy", `po-${digest}.definitions-cache.json`);
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Reads the catalog the previous run left behind.
|
|
271
|
+
*
|
|
272
|
+
* Synchronous on purpose: Pi has to be able to register these tools during
|
|
273
|
+
* extension install, before it takes its first turn, and awaiting a file read
|
|
274
|
+
* would let that turn start with an empty tool set. A miss is normal on a first
|
|
275
|
+
* run and resolves to an empty catalog rather than an error.
|
|
276
|
+
*
|
|
277
|
+
* The file path is content-addressed from the current ordered layers, and its proxy
|
|
278
|
+
* version is checked before any schema is registered. Servers filtered out of the
|
|
279
|
+
* current configuration are also excluded later by `McpCatalog.seed`.
|
|
280
|
+
*/
|
|
281
|
+
function readCachedCatalog(configSources, cachePathFor = definitionsCachePath) {
|
|
282
|
+
if (configSources.length === 0) return { servers: [] };
|
|
283
|
+
try {
|
|
284
|
+
const cachePath = cachePathFor(configSources);
|
|
285
|
+
const cache = JSON.parse((0, node_fs.readFileSync)(cachePath, "utf8"));
|
|
286
|
+
if (cache.oneMcpVersion !== _agimon_ai_mcp_proxy_package_json.default.version) return { servers: [] };
|
|
287
|
+
return { servers: Object.values(cache.servers ?? {}).map((server) => ({
|
|
288
|
+
name: server.serverName,
|
|
289
|
+
tools: server.tools ?? []
|
|
290
|
+
})) };
|
|
291
|
+
} catch {
|
|
292
|
+
return { servers: [] };
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
function toProxyConfigSource(source) {
|
|
296
|
+
return {
|
|
297
|
+
path: source.path,
|
|
298
|
+
format: source.format,
|
|
299
|
+
...source.optional === void 0 ? {} : { optional: source.optional },
|
|
300
|
+
...source.mergeStrategy === void 0 ? {} : { mergeStrategy: source.mergeStrategy }
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Owns the one live mcp-proxy container for this extension.
|
|
305
|
+
*
|
|
306
|
+
* Pi reloads extensions in process, so a connect that lands after a session restart
|
|
307
|
+
* or a `/domains` switch would otherwise fold tools into a dead context. Every start
|
|
308
|
+
* takes the next generation; anything an older generation produces is dropped and its
|
|
309
|
+
* container disposed.
|
|
310
|
+
*/
|
|
311
|
+
var McpRuntimeOwner = class {
|
|
312
|
+
generation = 0;
|
|
313
|
+
active;
|
|
314
|
+
unsubscribe;
|
|
315
|
+
get currentGeneration() {
|
|
316
|
+
return this.generation;
|
|
317
|
+
}
|
|
318
|
+
/** The live container, or undefined before the first start and after dispose. */
|
|
319
|
+
getServices() {
|
|
320
|
+
return this.active;
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Whether a container a caller captured earlier is still the live one.
|
|
324
|
+
*
|
|
325
|
+
* An async continuation that started under one container has to check this
|
|
326
|
+
* before writing its result, or a `/domains` switch would fold stale tools into
|
|
327
|
+
* the new session.
|
|
328
|
+
*/
|
|
329
|
+
isCurrent(services) {
|
|
330
|
+
return services !== void 0 && services === this.active;
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Starts a container for the current configuration, replacing any predecessor.
|
|
334
|
+
*
|
|
335
|
+
* Returns undefined when a newer generation started while this one was building,
|
|
336
|
+
* which is the signal for the caller to abandon its half-finished work.
|
|
337
|
+
*/
|
|
338
|
+
async start(options) {
|
|
339
|
+
const generation = ++this.generation;
|
|
340
|
+
await this.retire();
|
|
341
|
+
if (options.configSources.length === 0) return void 0;
|
|
342
|
+
const { createProxyContainer } = await import("@agimon-ai/mcp-proxy");
|
|
343
|
+
const cachePath = definitionsCachePath(options.configSources);
|
|
344
|
+
const services = await createProxyContainer({
|
|
345
|
+
configSources: options.configSources.map(toProxyConfigSource),
|
|
346
|
+
workspaceRoot: options.workspaceRoot,
|
|
347
|
+
executionCwd: options.executionCwd,
|
|
348
|
+
environment: options.environment,
|
|
349
|
+
definitionsCachePath: cachePath,
|
|
350
|
+
startupMode: "background",
|
|
351
|
+
clientCapabilities: MCP_APP_CLIENT_CAPABILITIES,
|
|
352
|
+
outputSchemaValidation: {
|
|
353
|
+
mode: "warn",
|
|
354
|
+
onWarning: (warning) => {
|
|
355
|
+
if (generation === this.generation) options.onOutputSchemaWarning?.(warning);
|
|
356
|
+
}
|
|
357
|
+
},
|
|
358
|
+
auth: {
|
|
359
|
+
tokenStore: options.tokenStore,
|
|
360
|
+
onAuthorizationUrl: options.onAuthorizationUrl,
|
|
361
|
+
...options.callbackServer ? { callbackServer: options.callbackServer } : {}
|
|
362
|
+
}
|
|
363
|
+
});
|
|
364
|
+
if (generation !== this.generation) {
|
|
365
|
+
await services.dispose().catch(() => void 0);
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
this.active = services;
|
|
369
|
+
const forward = options.onServerStateChange;
|
|
370
|
+
if (forward) this.unsubscribe = services.clientManager.onServerStateChange((change) => {
|
|
371
|
+
if (generation === this.generation) forward(change);
|
|
372
|
+
});
|
|
373
|
+
return {
|
|
374
|
+
generation,
|
|
375
|
+
services,
|
|
376
|
+
connectionsSettled: services.connectionsSettled
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
/** Tears the container down and invalidates every generation still in flight. */
|
|
380
|
+
async dispose() {
|
|
381
|
+
++this.generation;
|
|
382
|
+
await this.retire();
|
|
383
|
+
}
|
|
384
|
+
async retire() {
|
|
385
|
+
this.unsubscribe?.();
|
|
386
|
+
this.unsubscribe = void 0;
|
|
387
|
+
const services = this.active;
|
|
388
|
+
this.active = void 0;
|
|
389
|
+
if (services) await services.dispose().catch(() => void 0);
|
|
390
|
+
}
|
|
391
|
+
};
|
|
392
|
+
//#endregion
|
|
393
|
+
//#region src/services/sessionConfig/index.ts
|
|
394
|
+
function asStringArray(value) {
|
|
395
|
+
if (!Array.isArray(value)) return void 0;
|
|
396
|
+
const strings = value.filter((entry) => typeof entry === "string" && entry.length > 0);
|
|
397
|
+
return strings.length > 0 ? strings : void 0;
|
|
398
|
+
}
|
|
399
|
+
function readAllowlist(value) {
|
|
400
|
+
if (typeof value !== "object" || value === null) return void 0;
|
|
401
|
+
const { servers, proxy } = value;
|
|
402
|
+
const allowlist = {};
|
|
403
|
+
const allowedServers = asStringArray(servers);
|
|
404
|
+
const allowedProxy = asStringArray(proxy);
|
|
405
|
+
if (allowedServers) allowlist.servers = allowedServers;
|
|
406
|
+
if (allowedProxy) allowlist.proxy = allowedProxy;
|
|
407
|
+
return allowlist.servers || allowlist.proxy ? allowlist : void 0;
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* Resolves what this session should connect to.
|
|
411
|
+
*
|
|
412
|
+
* Every field falls back to a working default so the extension is usable outside
|
|
413
|
+
* po: a bare Pi session in a repository still gets that repository's
|
|
414
|
+
* `.mcp.json` with no allowlist. A malformed value is treated as absent rather
|
|
415
|
+
* than fatal, because losing MCP is better than losing the session.
|
|
416
|
+
*/
|
|
417
|
+
function readSessionConfig(env = process.env, defaultRepoRoot = process.cwd()) {
|
|
418
|
+
const fallback = {
|
|
419
|
+
repoRoot: defaultRepoRoot,
|
|
420
|
+
stagingDirectory: node_path.default.join(node_os.default.tmpdir(), "doom-mcp")
|
|
421
|
+
};
|
|
422
|
+
const strict = (0, _agimon_ai_po_core_mcpSession.readDoomMcpSessionConfig)(env);
|
|
423
|
+
if (strict) {
|
|
424
|
+
const { allowlist, pluginConfigPaths, ...required } = strict;
|
|
425
|
+
const strictAllowlist = readAllowlist(allowlist);
|
|
426
|
+
return {
|
|
427
|
+
...required,
|
|
428
|
+
...pluginConfigPaths?.length ? { pluginConfigPaths: [...pluginConfigPaths] } : {},
|
|
429
|
+
...strictAllowlist ? { allowlist: strictAllowlist } : {}
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
const raw = env[_agimon_ai_po_core_mcpSession.DOOM_MCP_SESSION_ENV_VAR];
|
|
433
|
+
if (!raw) return fallback;
|
|
434
|
+
let parsed;
|
|
435
|
+
try {
|
|
436
|
+
parsed = JSON.parse(raw);
|
|
437
|
+
} catch {
|
|
438
|
+
return fallback;
|
|
439
|
+
}
|
|
440
|
+
if (typeof parsed !== "object" || parsed === null) return fallback;
|
|
441
|
+
const { repoRoot, generatedConfigPath, pluginConfigPaths, allowlist, stagingDirectory } = parsed;
|
|
442
|
+
const resolvedRoot = typeof repoRoot === "string" && repoRoot ? repoRoot : fallback.repoRoot;
|
|
443
|
+
const generatedConfig = typeof generatedConfigPath === "string" && generatedConfigPath ? generatedConfigPath : void 0;
|
|
444
|
+
const plugins = asStringArray(pluginConfigPaths);
|
|
445
|
+
const resolvedAllowlist = readAllowlist(allowlist);
|
|
446
|
+
return {
|
|
447
|
+
repoRoot: resolvedRoot,
|
|
448
|
+
stagingDirectory: typeof stagingDirectory === "string" && stagingDirectory ? stagingDirectory : fallback.stagingDirectory,
|
|
449
|
+
...generatedConfig ? { generatedConfigPath: generatedConfig } : {},
|
|
450
|
+
...plugins ? { pluginConfigPaths: plugins } : {},
|
|
451
|
+
...resolvedAllowlist ? { allowlist: resolvedAllowlist } : {}
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
/** Serializes the configuration for the Pi child process po launches. */
|
|
455
|
+
function sessionConfigEnvironment(config) {
|
|
456
|
+
return (0, _agimon_ai_po_core_mcpSession.doomMcpSessionEnvironment)(config);
|
|
457
|
+
}
|
|
458
|
+
//#endregion
|
|
459
|
+
//#region src/schemas/directTools.ts
|
|
460
|
+
/** Environment variable carrying a child agent's MCP tool selection. */
|
|
461
|
+
const DIRECT_TOOLS_ENV = "MCP_DIRECT_TOOLS";
|
|
462
|
+
/** Explicit "no MCP tools", distinct from an unset variable meaning "no limit". */
|
|
463
|
+
const NO_DIRECT_TOOLS = "__none__";
|
|
464
|
+
//#endregion
|
|
465
|
+
//#region src/services/mcpCatalog/index.ts
|
|
466
|
+
function metadataRecord(value) {
|
|
467
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
468
|
+
}
|
|
469
|
+
/** Standard fields take precedence, including explicitly invalid fields (fail closed). */
|
|
470
|
+
function normalizeMcpAppMetadata(meta) {
|
|
471
|
+
const ui = metadataRecord(meta?.ui);
|
|
472
|
+
const standardUri = ui && "resourceUri" in ui ? ui.resourceUri : meta?.["ui/resourceUri"];
|
|
473
|
+
const legacyUri = meta?.["openai/outputTemplate"];
|
|
474
|
+
const standard = standardUri !== void 0;
|
|
475
|
+
const candidate = standard ? standardUri : legacyUri;
|
|
476
|
+
const resourceUri = typeof candidate === "string" && candidate.startsWith("ui://") && candidate.length <= 8192 && !Array.from(candidate).some((character) => character.charCodeAt(0) < 32 || character.charCodeAt(0) === 127) ? candidate : void 0;
|
|
477
|
+
const visibility = ui?.visibility;
|
|
478
|
+
const validVisibility = Array.isArray(visibility) && visibility.every((value) => value === "model" || value === "app");
|
|
479
|
+
const legacy = legacyUri !== void 0 || meta?.["openai/widgetAccessible"] !== void 0 || meta?.["openai/visibility"] !== void 0;
|
|
480
|
+
const modelVisible = visibility === void 0 ? standard || meta?.["openai/visibility"] !== "private" : validVisibility && visibility.includes("model");
|
|
481
|
+
const appVisible = visibility === void 0 ? standard || !legacy ? true : meta?.["openai/widgetAccessible"] === true : validVisibility && visibility.includes("app");
|
|
482
|
+
return {
|
|
483
|
+
...resourceUri === void 0 ? {} : {
|
|
484
|
+
resourceUri,
|
|
485
|
+
protocol: standard ? "mcp" : "openai"
|
|
486
|
+
},
|
|
487
|
+
modelVisible,
|
|
488
|
+
appVisible
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
function isModelVisibleMcpTool(tool) {
|
|
492
|
+
return normalizeMcpAppMetadata(tool._meta).modelVisible;
|
|
493
|
+
}
|
|
494
|
+
/** Execution projections carry normalized visibility; discovery retains the untouched metadata. */
|
|
495
|
+
function mcpToolExecutionMetadata(tool) {
|
|
496
|
+
if (tool._meta === void 0) return void 0;
|
|
497
|
+
const normalized = normalizeMcpAppMetadata(tool._meta);
|
|
498
|
+
return {
|
|
499
|
+
...tool._meta,
|
|
500
|
+
ui: {
|
|
501
|
+
...metadataRecord(tool._meta.ui),
|
|
502
|
+
visibility: [...normalized.modelVisible ? ["model"] : [], ...normalized.appVisible ? ["app"] : []]
|
|
503
|
+
}
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
function isAppVisibleMcpTool(tool) {
|
|
507
|
+
return normalizeMcpAppMetadata(tool._meta).appVisible;
|
|
508
|
+
}
|
|
509
|
+
/**
|
|
510
|
+
* States where calling a tool cannot succeed, so its tools are withheld.
|
|
511
|
+
*
|
|
512
|
+
* `closed` is absent deliberately: a server closed after an idle timeout is
|
|
513
|
+
* reconnected by the next call rather than gone. `disabled` is absent because it is
|
|
514
|
+
* no longer a reachable transition; `entry.disabled` carries that instead.
|
|
515
|
+
*/
|
|
516
|
+
const UNAVAILABLE_STATES = /* @__PURE__ */ new Set(["failed", "needs-auth"]);
|
|
517
|
+
/** Where a server starts: known to the session, not yet dialled. */
|
|
518
|
+
const NOT_CONNECTED = "not-connected";
|
|
519
|
+
const DISABLED = "disabled";
|
|
520
|
+
const ALL_SERVERS_SELECTOR = "*";
|
|
521
|
+
const SELECTOR_SEPARATOR = "/";
|
|
522
|
+
/**
|
|
523
|
+
* Pi's tool name for a downstream tool.
|
|
524
|
+
*
|
|
525
|
+
* Always prefixed, matching what `pi-mcp-adapter` produced by default. Tool names
|
|
526
|
+
* are a contract with agent frontmatter selectors, transcripts, and user habit, so
|
|
527
|
+
* they stay stable across the swap rather than following mcp-proxy's internal
|
|
528
|
+
* scheme, which prefixes only on a clash.
|
|
529
|
+
*/
|
|
530
|
+
function toPiToolName(serverName, toolName) {
|
|
531
|
+
return `${serverName.replace(/[^a-zA-Z0-9]/g, "_")}_${toolName}`;
|
|
532
|
+
}
|
|
533
|
+
function toCatalogTool(serverName, tool) {
|
|
534
|
+
return {
|
|
535
|
+
piName: toPiToolName(serverName, tool.name),
|
|
536
|
+
toolName: tool.name,
|
|
537
|
+
serverName,
|
|
538
|
+
...tool.description ? { description: tool.description } : {},
|
|
539
|
+
inputSchema: tool.inputSchema,
|
|
540
|
+
...tool.annotations === void 0 ? {} : { annotations: tool.annotations },
|
|
541
|
+
...tool.outputSchema === void 0 ? {} : { outputSchema: tool.outputSchema },
|
|
542
|
+
...tool._meta === void 0 ? {} : { _meta: tool._meta }
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
/**
|
|
546
|
+
* The session's MCP servers and the tools registered for each.
|
|
547
|
+
*
|
|
548
|
+
* Seeded from the previous run's catalog so tools exist before any socket opens,
|
|
549
|
+
* then folded forward as servers report in. Tools are never removed: Pi 0.84 has no
|
|
550
|
+
* way to unregister one, so a tool whose server later fails stays registered and is
|
|
551
|
+
* simply left out of the active set.
|
|
552
|
+
*/
|
|
553
|
+
var McpCatalog = class {
|
|
554
|
+
directTools;
|
|
555
|
+
entries = /* @__PURE__ */ new Map();
|
|
556
|
+
diagnostics = [];
|
|
557
|
+
/** Pi names already claimed, so two servers cannot register the same one. */
|
|
558
|
+
claimed = /* @__PURE__ */ new Map();
|
|
559
|
+
/**
|
|
560
|
+
* @param directTools - A child agent's tool selection. Undefined in a normal
|
|
561
|
+
* session, where every configured tool is available.
|
|
562
|
+
*/
|
|
563
|
+
constructor(directTools) {
|
|
564
|
+
this.directTools = directTools;
|
|
565
|
+
}
|
|
566
|
+
/**
|
|
567
|
+
* Seeds the servers this session will connect to, all still `not-connected`.
|
|
568
|
+
*
|
|
569
|
+
* Configured names come first so a run with no cache still reports what it is
|
|
570
|
+
* about to reach rather than looking empty; the cache then fills in their tools.
|
|
571
|
+
* A cached server that configuration has since dropped is not seeded at all.
|
|
572
|
+
*/
|
|
573
|
+
seed(catalog, configuredServers = []) {
|
|
574
|
+
for (const name of configuredServers) this.entries.set(name, {
|
|
575
|
+
name,
|
|
576
|
+
state: NOT_CONNECTED,
|
|
577
|
+
tools: [],
|
|
578
|
+
resourceCount: 0
|
|
579
|
+
});
|
|
580
|
+
for (const server of catalog.servers) {
|
|
581
|
+
if (configuredServers.length > 0 && !this.entries.has(server.name)) continue;
|
|
582
|
+
this.entries.set(server.name, {
|
|
583
|
+
name: server.name,
|
|
584
|
+
state: NOT_CONNECTED,
|
|
585
|
+
tools: this.claimTools(server.name, server.tools),
|
|
586
|
+
resourceCount: 0
|
|
587
|
+
});
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
/**
|
|
591
|
+
* Hides or restores a server's tools for this session.
|
|
592
|
+
*
|
|
593
|
+
* Only the flag moves: `state` keeps tracking what the server reports, so
|
|
594
|
+
* re-enabling lands on the current truth rather than on a remembered value that
|
|
595
|
+
* may have gone stale while the server was hidden.
|
|
596
|
+
*/
|
|
597
|
+
setDisabled(serverName, disabled) {
|
|
598
|
+
const entry = this.entries.get(serverName);
|
|
599
|
+
if (entry) entry.disabled = disabled;
|
|
600
|
+
else this.entries.set(serverName, {
|
|
601
|
+
name: serverName,
|
|
602
|
+
state: NOT_CONNECTED,
|
|
603
|
+
disabled,
|
|
604
|
+
tools: [],
|
|
605
|
+
resourceCount: 0
|
|
606
|
+
});
|
|
607
|
+
}
|
|
608
|
+
/** Records how many resources a server offered the last time they were listed. */
|
|
609
|
+
setResourceCount(serverName, resourceCount) {
|
|
610
|
+
const entry = this.entries.get(serverName);
|
|
611
|
+
if (entry) entry.resourceCount = resourceCount;
|
|
612
|
+
}
|
|
613
|
+
/**
|
|
614
|
+
* Folds a live server transition into the catalog.
|
|
615
|
+
*
|
|
616
|
+
* Returns the tools that are new to this catalog, which is what the caller has to
|
|
617
|
+
* register with Pi. A reconnect returns nothing, since its tools already exist.
|
|
618
|
+
*/
|
|
619
|
+
applyStateChange(change, tools, resourceCount) {
|
|
620
|
+
const entry = this.entries.get(change.serverName) ?? {
|
|
621
|
+
name: change.serverName,
|
|
622
|
+
state: change.state,
|
|
623
|
+
tools: [],
|
|
624
|
+
resourceCount: 0
|
|
625
|
+
};
|
|
626
|
+
entry.state = change.state;
|
|
627
|
+
if (change.error) entry.error = change.error;
|
|
628
|
+
else delete entry.error;
|
|
629
|
+
if (resourceCount !== void 0) entry.resourceCount = resourceCount;
|
|
630
|
+
if (change.state === "connected" && tools !== void 0) {
|
|
631
|
+
const known = new Map(entry.tools.map((tool) => [tool.toolName, tool]));
|
|
632
|
+
const next = [];
|
|
633
|
+
const added = [];
|
|
634
|
+
for (const tool of tools) {
|
|
635
|
+
if (known.get(tool.name) !== void 0) {
|
|
636
|
+
next.push(toCatalogTool(change.serverName, tool));
|
|
637
|
+
continue;
|
|
638
|
+
}
|
|
639
|
+
const [claimed] = this.claimTools(change.serverName, [tool]);
|
|
640
|
+
if (claimed !== void 0) {
|
|
641
|
+
next.push(claimed);
|
|
642
|
+
added.push(claimed);
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
entry.tools = next;
|
|
646
|
+
this.entries.set(change.serverName, entry);
|
|
647
|
+
return added;
|
|
648
|
+
}
|
|
649
|
+
this.entries.set(change.serverName, entry);
|
|
650
|
+
return [];
|
|
651
|
+
}
|
|
652
|
+
/**
|
|
653
|
+
* Tool names Pi should currently expose.
|
|
654
|
+
*
|
|
655
|
+
* Optimistic: a server that has not reported yet still contributes its cached
|
|
656
|
+
* tools, because calling one connects it on demand. The live background pass can
|
|
657
|
+
* take longer than Pi startup, so requiring `connected` first would make a warm
|
|
658
|
+
* session needlessly start with no tools.
|
|
659
|
+
*
|
|
660
|
+
* Only a server that is known to be unusable, or one the user disabled, is held
|
|
661
|
+
* back.
|
|
662
|
+
*/
|
|
663
|
+
activeToolNames() {
|
|
664
|
+
const names = [];
|
|
665
|
+
for (const entry of this.entries.values()) {
|
|
666
|
+
if (entry.disabled || UNAVAILABLE_STATES.has(entry.state)) continue;
|
|
667
|
+
for (const tool of entry.tools) {
|
|
668
|
+
if (this.directTools && !this.directTools.allows(entry.name, tool.toolName)) continue;
|
|
669
|
+
names.push(tool.piName);
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
return names;
|
|
673
|
+
}
|
|
674
|
+
/** Every tool ever registered, whether or not its server is reachable. */
|
|
675
|
+
allTools() {
|
|
676
|
+
return [...this.entries.values()].flatMap((entry) => entry.tools);
|
|
677
|
+
}
|
|
678
|
+
/** Resolve configured selectors only to tool names this authoritative catalog confirms. */
|
|
679
|
+
resolveToolSelectors(selectors) {
|
|
680
|
+
let allServers = false;
|
|
681
|
+
const selectedServers = /* @__PURE__ */ new Set();
|
|
682
|
+
const selectedTools = /* @__PURE__ */ new Map();
|
|
683
|
+
for (const selection of selectors) {
|
|
684
|
+
const item = selection.trim().replace(/\/+$/u, "");
|
|
685
|
+
if (!item) continue;
|
|
686
|
+
if (item === ALL_SERVERS_SELECTOR) {
|
|
687
|
+
allServers = true;
|
|
688
|
+
continue;
|
|
689
|
+
}
|
|
690
|
+
const separator = item.indexOf(SELECTOR_SEPARATOR);
|
|
691
|
+
if (separator === -1) {
|
|
692
|
+
selectedServers.add(item);
|
|
693
|
+
continue;
|
|
694
|
+
}
|
|
695
|
+
const server = item.slice(0, separator);
|
|
696
|
+
const tool = item.slice(separator + 1);
|
|
697
|
+
if (!server) continue;
|
|
698
|
+
if (!tool) {
|
|
699
|
+
selectedServers.add(server);
|
|
700
|
+
continue;
|
|
701
|
+
}
|
|
702
|
+
const names = selectedTools.get(server) ?? /* @__PURE__ */ new Set();
|
|
703
|
+
names.add(tool);
|
|
704
|
+
selectedTools.set(server, names);
|
|
705
|
+
}
|
|
706
|
+
const resolved = [];
|
|
707
|
+
const seen = /* @__PURE__ */ new Set();
|
|
708
|
+
for (const tool of this.allTools()) {
|
|
709
|
+
if (!isModelVisibleMcpTool(tool)) continue;
|
|
710
|
+
if (!(allServers || selectedServers.has(tool.serverName)) && !selectedTools.get(tool.serverName)?.has(tool.toolName)) continue;
|
|
711
|
+
if (seen.has(tool.piName)) continue;
|
|
712
|
+
seen.add(tool.piName);
|
|
713
|
+
resolved.push({
|
|
714
|
+
name: tool.piName,
|
|
715
|
+
selector: `${tool.serverName}${SELECTOR_SEPARATOR}${tool.toolName}`
|
|
716
|
+
});
|
|
717
|
+
}
|
|
718
|
+
return resolved;
|
|
719
|
+
}
|
|
720
|
+
findTool(piName) {
|
|
721
|
+
return this.allTools().find((tool) => tool.piName === piName);
|
|
722
|
+
}
|
|
723
|
+
/**
|
|
724
|
+
* The cross-extension status shape.
|
|
725
|
+
*
|
|
726
|
+
* A disabled server reports `disabled` as its state, because that is the one word
|
|
727
|
+
* the wire contract has for "configured but contributing nothing", and consumers
|
|
728
|
+
* outside this process have no use for the transition underneath it.
|
|
729
|
+
*/
|
|
730
|
+
toSnapshot() {
|
|
731
|
+
return { servers: [...this.entries.values()].map((entry) => ({
|
|
732
|
+
name: entry.name,
|
|
733
|
+
state: entry.disabled ? DISABLED : entry.state,
|
|
734
|
+
tools: entry.tools.filter(isModelVisibleMcpTool).map((tool) => tool.piName),
|
|
735
|
+
resourceCount: entry.resourceCount,
|
|
736
|
+
...entry.error ? { error: entry.error } : {}
|
|
737
|
+
})) };
|
|
738
|
+
}
|
|
739
|
+
/**
|
|
740
|
+
* The fuller picture the overlay renders.
|
|
741
|
+
*
|
|
742
|
+
* Carries what the snapshot cannot: each tool's downstream name and description,
|
|
743
|
+
* whether a child agent's selection withholds it, and `enabled` as its own field
|
|
744
|
+
* so the overlay can offer disable and enable against the real server state.
|
|
745
|
+
*/
|
|
746
|
+
toView() {
|
|
747
|
+
return [...this.entries.values()].map((entry) => ({
|
|
748
|
+
name: entry.name,
|
|
749
|
+
state: entry.state,
|
|
750
|
+
tools: entry.tools.map((tool) => ({
|
|
751
|
+
piName: tool.piName,
|
|
752
|
+
toolName: tool.toolName,
|
|
753
|
+
...tool.description ? { description: tool.description } : {},
|
|
754
|
+
active: isModelVisibleMcpTool(tool) && (!this.directTools || this.directTools.allows(entry.name, tool.toolName))
|
|
755
|
+
})),
|
|
756
|
+
resourceCount: entry.resourceCount,
|
|
757
|
+
enabled: !entry.disabled,
|
|
758
|
+
...entry.error ? { error: entry.error } : {}
|
|
759
|
+
}));
|
|
760
|
+
}
|
|
761
|
+
/** Problems worth surfacing to the user. Reported, never thrown. */
|
|
762
|
+
getDiagnostics() {
|
|
763
|
+
return this.diagnostics;
|
|
764
|
+
}
|
|
765
|
+
/**
|
|
766
|
+
* Records a problem for the user without interrupting whatever hit it.
|
|
767
|
+
*
|
|
768
|
+
* Deduplicated: a fault that recurs on every repaint would otherwise grow this
|
|
769
|
+
* list for as long as the session runs, and the second copy tells nobody
|
|
770
|
+
* anything the first did not.
|
|
771
|
+
*/
|
|
772
|
+
addDiagnostic(diagnostic) {
|
|
773
|
+
if (!this.diagnostics.includes(diagnostic)) this.diagnostics.push(diagnostic);
|
|
774
|
+
}
|
|
775
|
+
/**
|
|
776
|
+
* Assigns Pi names, dropping any that another server already owns.
|
|
777
|
+
*
|
|
778
|
+
* Two server names that normalize to the same prefix (`code-intel` and
|
|
779
|
+
* `code_intel`) would otherwise have one silently shadow the other's tool.
|
|
780
|
+
*/
|
|
781
|
+
claimTools(serverName, tools) {
|
|
782
|
+
const claimed = [];
|
|
783
|
+
for (const tool of tools) {
|
|
784
|
+
const candidate = toCatalogTool(serverName, tool);
|
|
785
|
+
const owner = this.claimed.get(candidate.piName);
|
|
786
|
+
if (owner !== void 0 && owner !== serverName) {
|
|
787
|
+
this.diagnostics.push(`MCP tool "${candidate.piName}" from "${serverName}" was dropped: "${owner}" already registered that name.`);
|
|
788
|
+
continue;
|
|
789
|
+
}
|
|
790
|
+
this.claimed.set(candidate.piName, serverName);
|
|
791
|
+
claimed.push(candidate);
|
|
792
|
+
}
|
|
793
|
+
return claimed;
|
|
794
|
+
}
|
|
795
|
+
};
|
|
796
|
+
//#endregion
|
|
797
|
+
Object.defineProperty(exports, "DIRECT_TOOLS_ENV", {
|
|
798
|
+
enumerable: true,
|
|
799
|
+
get: function() {
|
|
800
|
+
return DIRECT_TOOLS_ENV;
|
|
801
|
+
}
|
|
802
|
+
});
|
|
803
|
+
Object.defineProperty(exports, "McpCatalog", {
|
|
804
|
+
enumerable: true,
|
|
805
|
+
get: function() {
|
|
806
|
+
return McpCatalog;
|
|
807
|
+
}
|
|
808
|
+
});
|
|
809
|
+
Object.defineProperty(exports, "McpRuntimeOwner", {
|
|
810
|
+
enumerable: true,
|
|
811
|
+
get: function() {
|
|
812
|
+
return McpRuntimeOwner;
|
|
813
|
+
}
|
|
814
|
+
});
|
|
815
|
+
Object.defineProperty(exports, "NO_DIRECT_TOOLS", {
|
|
816
|
+
enumerable: true,
|
|
817
|
+
get: function() {
|
|
818
|
+
return NO_DIRECT_TOOLS;
|
|
819
|
+
}
|
|
820
|
+
});
|
|
821
|
+
Object.defineProperty(exports, "PROXY_SERVER_NAME", {
|
|
822
|
+
enumerable: true,
|
|
823
|
+
get: function() {
|
|
824
|
+
return PROXY_SERVER_NAME;
|
|
825
|
+
}
|
|
826
|
+
});
|
|
827
|
+
Object.defineProperty(exports, "buildMcpConfigGroups", {
|
|
828
|
+
enumerable: true,
|
|
829
|
+
get: function() {
|
|
830
|
+
return buildMcpConfigGroups;
|
|
831
|
+
}
|
|
832
|
+
});
|
|
833
|
+
Object.defineProperty(exports, "definitionsCachePath", {
|
|
834
|
+
enumerable: true,
|
|
835
|
+
get: function() {
|
|
836
|
+
return definitionsCachePath;
|
|
837
|
+
}
|
|
838
|
+
});
|
|
839
|
+
Object.defineProperty(exports, "hubCallbackSink", {
|
|
840
|
+
enumerable: true,
|
|
841
|
+
get: function() {
|
|
842
|
+
return hubCallbackSink;
|
|
843
|
+
}
|
|
844
|
+
});
|
|
845
|
+
Object.defineProperty(exports, "isAppVisibleMcpTool", {
|
|
846
|
+
enumerable: true,
|
|
847
|
+
get: function() {
|
|
848
|
+
return isAppVisibleMcpTool;
|
|
849
|
+
}
|
|
850
|
+
});
|
|
851
|
+
Object.defineProperty(exports, "isModelVisibleMcpTool", {
|
|
852
|
+
enumerable: true,
|
|
853
|
+
get: function() {
|
|
854
|
+
return isModelVisibleMcpTool;
|
|
855
|
+
}
|
|
856
|
+
});
|
|
857
|
+
Object.defineProperty(exports, "mcpRuntime_exports", {
|
|
858
|
+
enumerable: true,
|
|
859
|
+
get: function() {
|
|
860
|
+
return mcpRuntime_exports;
|
|
861
|
+
}
|
|
862
|
+
});
|
|
863
|
+
Object.defineProperty(exports, "mcpToolExecutionMetadata", {
|
|
864
|
+
enumerable: true,
|
|
865
|
+
get: function() {
|
|
866
|
+
return mcpToolExecutionMetadata;
|
|
867
|
+
}
|
|
868
|
+
});
|
|
869
|
+
Object.defineProperty(exports, "normalizeMcpAppMetadata", {
|
|
870
|
+
enumerable: true,
|
|
871
|
+
get: function() {
|
|
872
|
+
return normalizeMcpAppMetadata;
|
|
873
|
+
}
|
|
874
|
+
});
|
|
875
|
+
Object.defineProperty(exports, "readCachedCatalog", {
|
|
876
|
+
enumerable: true,
|
|
877
|
+
get: function() {
|
|
878
|
+
return readCachedCatalog;
|
|
879
|
+
}
|
|
880
|
+
});
|
|
881
|
+
Object.defineProperty(exports, "readSessionConfig", {
|
|
882
|
+
enumerable: true,
|
|
883
|
+
get: function() {
|
|
884
|
+
return readSessionConfig;
|
|
885
|
+
}
|
|
886
|
+
});
|
|
887
|
+
Object.defineProperty(exports, "sessionConfigEnvironment", {
|
|
888
|
+
enumerable: true,
|
|
889
|
+
get: function() {
|
|
890
|
+
return sessionConfigEnvironment;
|
|
891
|
+
}
|
|
892
|
+
});
|
|
893
|
+
Object.defineProperty(exports, "toPiToolName", {
|
|
894
|
+
enumerable: true,
|
|
895
|
+
get: function() {
|
|
896
|
+
return toPiToolName;
|
|
897
|
+
}
|
|
898
|
+
});
|
|
899
|
+
|
|
900
|
+
//# sourceMappingURL=mcpCatalog-6Tpt6Gqn.cjs.map
|