Fix MCP send_http_request ignoring environmentId (#654)

This commit is contained in:
Ricardo Lopes Temoteo
2026-09-15 14:04:38 -07:00
committed by GitHub
parent 4859aba142
commit ab616fd915
11 changed files with 619 additions and 8 deletions
+23
View File
@@ -33,3 +33,26 @@ Restart Claude Desktop and make sure Yaak is running.
- `get_environment_id` - Get the current environment ID
- `copy_to_clipboard` - Copy text to the system clipboard
- `show_toast` - Show a toast notification in Yaak
## Environment Selection
`send_http_request` accepts an optional `environmentId` for that execution. It overrides the
active environment without changing the selection in Yaak. Omitting it uses the host's
active environment, as before.
The ID must identify a base or sub-environment in the request's workspace. An empty,
unknown, foreign-workspace, or folder environment ID returns an error before sending.
Use the base environment ID to run with global variables only. Folder variables still
apply according to the request's folder hierarchy.
```json
{
"id": "rq_example",
"workspaceId": "wk_example",
"environmentId": "ev_staging"
}
```
This requires a Yaak host with support for the `environmentId` plugin API field;
older hosts ignore it. `get_environment_id` reports the active selection, not the
override used by a previous send.
@@ -0,0 +1,98 @@
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import type { Context } from "@yaakapp/api";
import { afterEach, expect, test, vi } from "vite-plus/test";
import { registerHttpRequestTools } from "./httpRequest";
const cleanups: Array<() => Promise<void>> = [];
afterEach(async () => {
for (const cleanup of cleanups.splice(0)) await cleanup();
});
async function fixture({ missing = false, sendError = false } = {}) {
const httpRequest = { id: "rq_test", workspaceId: "wk_test", url: "http://localhost/echo" };
const send = vi.fn(async (_args: { httpRequest: unknown; environmentId?: string }) => {
if (sendError) throw new Error("Environment not found");
return { httpResponse: { id: "rs_test", status: 200 } };
});
// Only the host is mocked; calls go through the real MCP client, schema and transport.
const yaak = {
workspace: {
list: async () => [{ id: "wk_test", name: "Test Workspace" }],
withContext: () => yaak,
},
httpRequest: { getById: async () => (missing ? null : httpRequest), send },
};
const server = new McpServer({ name: "yaak-test", version: "0.0.0" });
registerHttpRequestTools(server, { yaak: yaak as unknown as Context });
const client = new Client({ name: "test", version: "0.0.0" });
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
await server.connect(serverTransport);
await client.connect(clientTransport);
cleanups.push(async () => {
await client.close();
await server.close();
});
return { client, send, httpRequest };
}
test("forwards the advertised environmentId to the host", async () => {
const { client, send, httpRequest } = await fixture();
const { tools } = await client.listTools();
expect(
tools.find((tool) => tool.name === "send_http_request")?.inputSchema.properties,
).toHaveProperty("environmentId");
const result = await client.callTool({
name: "send_http_request",
arguments: { id: httpRequest.id, workspaceId: "wk_test", environmentId: "ev_staging" },
});
expect(result.isError).toBeFalsy();
expect(send).toHaveBeenCalledExactlyOnceWith({ httpRequest, environmentId: "ev_staging" });
});
test("leaves the environment unspecified when omitted", async () => {
const { client, send, httpRequest } = await fixture();
const result = await client.callTool({
name: "send_http_request",
arguments: { id: httpRequest.id },
});
expect(result.isError).toBeFalsy();
expect(send).toHaveBeenCalledExactlyOnceWith(expect.objectContaining({ httpRequest }));
expect(send.mock.calls[0]?.[0]).not.toHaveProperty("environmentId", expect.any(String));
});
test("does not send a missing request", async () => {
const { client, send } = await fixture({ missing: true });
const result = await client.callTool({
name: "send_http_request",
arguments: { id: "rq_missing" },
});
expect(result.isError).toBe(true);
expect(send).not.toHaveBeenCalled();
});
test("returns host environment errors to the MCP client", async () => {
const { client } = await fixture({ sendError: true });
const result = await client.callTool({
name: "send_http_request",
arguments: { id: "rq_test", environmentId: "ev_missing" },
});
expect(result.isError).toBe(true);
expect(result.content).toEqual([{ type: "text", text: "Environment not found" }]);
});
test("keeps concurrent environment overrides separate", async () => {
const { client, send, httpRequest } = await fixture();
await Promise.all(
["ev_a", "ev_b"].map((environmentId) =>
client.callTool({
name: "send_http_request",
arguments: { id: httpRequest.id, environmentId },
}),
),
);
expect(send).toHaveBeenCalledTimes(2);
expect(send).toHaveBeenCalledWith({ httpRequest, environmentId: "ev_a" });
expect(send).toHaveBeenCalledWith({ httpRequest, environmentId: "ev_b" });
});
@@ -69,18 +69,24 @@ export function registerHttpRequestTools(server: McpServer, ctx: McpServerContex
description: "Send an HTTP request and get the response",
inputSchema: {
id: z.string().describe("The HTTP request ID to send"),
environmentId: z.string().optional().describe("Optional environment ID to use"),
environmentId: z
.string()
.optional()
.describe("Environment ID for this send only; defaults to the active environment"),
workspaceId: workspaceIdSchema,
},
},
async ({ id, workspaceId }) => {
async ({ id, workspaceId, environmentId }) => {
const workspaceCtx = await getWorkspaceContext(ctx, workspaceId);
const httpRequest = await workspaceCtx.yaak.httpRequest.getById({ id });
if (httpRequest == null) {
throw new Error(`HTTP request with ID ${id} not found`);
}
const { httpResponse: response } = await workspaceCtx.yaak.httpRequest.send({ httpRequest });
const { httpResponse: response } = await workspaceCtx.yaak.httpRequest.send({
httpRequest,
environmentId,
});
return {
content: [