feat(agent): unify MCP tools and adopt Responses delivery
Move project and host queries into native MCP with host-owned OAuth/context, while retaining memory, skills, actions and publication plugins. Add nearby-model queries and explicit chat/task/learning model defaults. Adopt Responses HTTP/SSE and native steer/queue inbox delivery, preserving scoped session ownership, pending input edits and investigation communication. Disable local-provider discovery: Server /health allowed native vLLM to probe /v1/models on port 8000. A native before/after regression prevents recurrence. Validation: 170 focused Bun tests; service/plugin typechecks; native discovery, provider-discovery and input-inbox smoke checks; no paid model inference.
This commit is contained in:
+15
-1
@@ -4,10 +4,14 @@ import {
|
||||
normalizeMcpServer, type McpRuntimeReport, type McpServerConfig, type McpSessionPolicy,
|
||||
} from "../src/runtime/mcp-config.js";
|
||||
import { getInternalBridgeConfig } from "./internalBridge.js";
|
||||
import { TASK_READER_AGENT, TASK_READER_TOOLS } from "../src/runtime/task-agent-policy.js";
|
||||
import { QUERY_MCP_SERVER, QUERY_MCP_TOOL_IDS } from "../src/runtime/product-mcp-tools.js";
|
||||
import { queryMcpConnection } from "./query-mcp.js";
|
||||
|
||||
type Context = Parameters<Plugin.Plugin["setup"]>[0];
|
||||
export const mcpToolAllowed = (policy: McpSessionPolicy, name: string) =>
|
||||
policy.mode !== "disabled" && (policy.mode === "interactive" || policy.readonly_tools.includes(name));
|
||||
policy.mode !== "disabled" && ((QUERY_MCP_TOOL_IDS as readonly string[]).includes(name)
|
||||
? policy.query_tools.includes(name) : policy.mode === "interactive" || policy.readonly_tools.includes(name));
|
||||
|
||||
async function sessionPolicy(sessionID: string, signal?: AbortSignal) {
|
||||
const { baseUrl, internalToken } = getInternalBridgeConfig();
|
||||
@@ -25,8 +29,14 @@ export async function registerMcpPolicy(ctx: Context) {
|
||||
const errors = new Set<string>();
|
||||
await ctx.mcp.transform((editor) => {
|
||||
servers.clear(); errors.clear();
|
||||
const connection = queryMcpConnection();
|
||||
// Reserved first-party connection: its endpoint and credential belong to
|
||||
// the Agent host, not administrator extensions or model arguments.
|
||||
if (connection) editor.set(QUERY_MCP_SERVER, connection);
|
||||
else if (editor.get?.(QUERY_MCP_SERVER)) throw new Error("Host query MCP connection unavailable");
|
||||
for (const [name, value] of editor.list()) {
|
||||
try {
|
||||
if (name.startsWith(`${QUERY_MCP_SERVER}_`)) throw new Error("Reserved query MCP namespace");
|
||||
const config = normalizeMcpServer(name, value);
|
||||
if (config.type === "local") config.environment = mcpLocalEnvironment(process.env, config.environment);
|
||||
editor.set(name, config);
|
||||
@@ -62,6 +72,10 @@ export async function registerMcpPolicy(ctx: Context) {
|
||||
} catch { event.effect = "deny"; }
|
||||
});
|
||||
await ctx.session.hook("context", async (event) => {
|
||||
if (event.agent === TASK_READER_AGENT) {
|
||||
for (const name of Object.keys(event.tools)) if (isMcpTool(name) && !(TASK_READER_TOOLS as readonly string[]).includes(name)) delete event.tools[name];
|
||||
return;
|
||||
}
|
||||
if (!servers.size && !Object.keys(event.tools).some(isMcpTool)) return;
|
||||
const policy = await sessionPolicy(event.sessionID);
|
||||
for (const name of Object.keys(event.tools)) if (isMcpTool(name) && !mcpToolAllowed(policy, name)) delete event.tools[name];
|
||||
|
||||
Reference in New Issue
Block a user