diff --git a/src/lib/server/mcp/server.ts b/src/lib/server/mcp/server.ts index 03830fb..27bda16 100644 --- a/src/lib/server/mcp/server.ts +++ b/src/lib/server/mcp/server.ts @@ -36,7 +36,7 @@ export function registerTools(server: McpServer, token: Token) { path: z.string().describe("Path appended to target's baseUrl"), headers: z.record(z.string(), z.string()).optional().describe("Additional request headers"), body: z.union([z.string(), z.record(z.string(), z.unknown())]).optional().describe("Request body"), - approved: z.boolean().optional().describe("Set to true after user approves a guarded request"), + approved: z.preprocess(val => val === "true" || val === true, z.boolean()).optional().describe("Set to true after user approves a guarded request"), }, async (args) => { const result = await apiRequest(token, args); @@ -51,7 +51,7 @@ export function registerTools(server: McpServer, token: Token) { target: z.string().describe("Target slug"), command: z.string().describe("Shell command to execute"), timeout: z.number().optional().describe("Timeout in seconds (default 30, max 60)"), - approved: z.boolean().optional().describe("Set to true after user approves a guarded request"), + approved: z.preprocess(val => val === "true" || val === true, z.boolean()).optional().describe("Set to true after user approves a guarded request"), }, async (args) => { const result = await sshExec(token, args); diff --git a/src/lib/server/mcp/tools/api-request.ts b/src/lib/server/mcp/tools/api-request.ts index 4d2c744..47cfa5f 100644 --- a/src/lib/server/mcp/tools/api-request.ts +++ b/src/lib/server/mcp/tools/api-request.ts @@ -69,9 +69,9 @@ export async function apiRequest(token: Token, args: ApiRequestArgs) { status: "approval_required", reason: guardResult.reason, matched: guardResult.matched, - request: { type: "api", method, path }, + request: { target: targetSlug, method, path, headers, body }, next_action: - "STOP. Do NOT re-send this request yet. Present the reason to the user, wait for their explicit approval, then re-call this tool with approved: true.", + "STOP. Do NOT re-send this request yet. Present the reason to the user and explain why it was flagged. Wait for the user to explicitly approve. Only then re-call this SAME tool with all the SAME parameters (target, method, path, headers, body) AND set approved: true.", }; } } diff --git a/src/lib/server/mcp/tools/ssh-exec.ts b/src/lib/server/mcp/tools/ssh-exec.ts index 36182ed..d2d4070 100644 --- a/src/lib/server/mcp/tools/ssh-exec.ts +++ b/src/lib/server/mcp/tools/ssh-exec.ts @@ -91,9 +91,9 @@ export async function sshExec(token: Token, args: SshExecArgs) { status: "approval_required", reason: guardResult.reason, matched: guardResult.matched, - request: { type: "ssh", command }, + request: { target: targetSlug, command, timeout }, next_action: - "STOP. Do NOT re-send this request yet. You MUST present the blocked command to the user, explain what it does and why it was flagged, then wait for the user to explicitly reply with approval. Only after the user responds confirming approval may you re-call this tool with approved: true. If the user denies, abort. Never auto-approve.", + "STOP. Do NOT re-send this request yet. Present the command to the user, explain what it does and why it was flagged. Wait for the user to explicitly approve. Only then re-call this SAME tool with all the SAME parameters (target, command, timeout) AND set approved: true. If the user denies, abort. Never auto-approve.", }; } } diff --git a/tests/integration/mcp.test.ts b/tests/integration/mcp.test.ts index d98fcfb..a38627b 100644 --- a/tests/integration/mcp.test.ts +++ b/tests/integration/mcp.test.ts @@ -106,7 +106,7 @@ describe("MCP tools", () => { expect(result.status).toBe("approval_required"); expect(result.reason).toContain("rm -r"); expect(result.matched).toBe("rm -r"); - expect(result.request).toEqual({ type: "ssh", command: "rm -rf /tmp/old" }); + expect(result.request).toEqual({ target: "deployserver", command: "rm -rf /tmp/old", timeout: undefined }); expect(result.next_action).toContain("approved: true"); }); });