TODO Next: MCP without the schema tax (phi meta-tools)
- mcpExpose=phi|direct|auto on RawConfig + MCPConfig; phi default keeps direct as a measured option for small servers (2-tool cheaper direct) - src/mcp.ts: buildMcpMetaTools + phi branch in connectMcp: lazy list/inspect/call behind 3 fixed schemas instead of N per-tool schemas; direct branch kept; bindMcpGuard wires permission+PluginHost guard for MCP calls through the same gate as built-ins - prompt mcpServers names-only under phi; under direct schemas travel as before — 20-tool server ~2750 tok -> ~phi (names) until mcp_call - session: isDirect mcpServerNames non-enumerable marker, activeTools hides mcp_call from pre-wired rules when inside mcp_call, /tools shows mcp_call and Enabled/Withheld reflects the 3 meta-names under phi - permission: mcp_* as read (free), mcp_call mutating keyed by server.tool, same top-level suppression + ask-to-approve as other mutating tools - cli: bindMcpGuard + /mcp list shows exposure + mcp_* listed; headless denies line mentions mcp_list - commit.ts: git_commit_message kept in git set via toolSetOf/ disabledToolNames overlay Tests: 798 pass, 0 fail; tsc exit 0; mcp.test.ts covers phi stays empty until mcp_call and direct still namespaced.
This commit is contained in:
1 parent
626450eb06
commit
2587e03beb
9 files changed
+236
-42
No files matched your search
+19
-1
@@ -337,7 +337,8 @@ export class Session {
|
||||
*/
|
||||
activeTools(): string[] {
|
||||
const withheld = new Set(disabledToolNames(this.opts.toolSets));
|
||||
const all = Object.keys(this.tools).filter((name) => !withheld.has(name));
|
||||
// __mcpServerNames is bookkeeping, not a tool
|
||||
const all = Object.keys(this.tools).filter((name) => name !== '__mcpServerNames' && !withheld.has(name));
|
||||
if (!this.variant.allowTools) return all;
|
||||
return all.filter((name) => this.variant.allowTools!.includes(name));
|
||||
}
|
||||
@@ -399,6 +400,22 @@ export class Session {
|
||||
return { usd, ceiling, overWarn: usd >= ceiling * 0.8, overLimit: usd >= ceiling };
|
||||
}
|
||||
|
||||
private mcpServerNamesForPrompt(): string[] | undefined {
|
||||
const hasMeta = this.tools['mcp_list'] !== undefined;
|
||||
if (!hasMeta) return undefined;
|
||||
const marker = (this.tools as Record<string, unknown>)['__mcpServerNames'];
|
||||
if (Array.isArray(marker) && marker.length > 0) return [...marker].sort() as string[];
|
||||
// fallback: derive from direct if marker missing (tests that inject tools manually)
|
||||
const direct = Object.keys(this.tools).filter((n) => n.startsWith('mcp__'));
|
||||
if (direct.length === 0) return undefined;
|
||||
const names = new Set<string>();
|
||||
for (const n of direct) {
|
||||
const m = /^mcp__([^_]+(?:_[^_]+)*)__/.exec(n);
|
||||
if (m) names.add(m[1]!);
|
||||
}
|
||||
return names.size > 0 ? [...names].sort() : undefined;
|
||||
}
|
||||
|
||||
private systemFor(): string {
|
||||
const mem = this.opts.memory;
|
||||
const memoryBlock = mem ? mem.render() : '';
|
||||
@@ -412,6 +429,7 @@ export class Session {
|
||||
plugins: this.pluginHost?.appendix ?? '',
|
||||
availableTools: this.activeTools(),
|
||||
canAsk: this.opts.ask !== undefined && this.activeTools().includes('ask'),
|
||||
...(this.mcpServerNamesForPrompt() ? { mcpServers: this.mcpServerNamesForPrompt() } : {}),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user