Allow SurfMind Chrome extension MCP origin (#513)
This commit is contained in:
parent
47883b5d5a
commit
6470b875ad
@ -9,13 +9,18 @@ const ctx: ExecutionContext = {
|
|||||||
props: {},
|
props: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
function request(method: string, body?: unknown) {
|
function request(
|
||||||
|
method: string,
|
||||||
|
body?: unknown,
|
||||||
|
headers?: Record<string, string>,
|
||||||
|
) {
|
||||||
return new Request("https://open-seo.test/mcp", {
|
return new Request("https://open-seo.test/mcp", {
|
||||||
method,
|
method,
|
||||||
headers: {
|
headers: {
|
||||||
Host: "open-seo.test",
|
Host: "open-seo.test",
|
||||||
Accept: "application/json, text/event-stream",
|
Accept: "application/json, text/event-stream",
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
|
...headers,
|
||||||
},
|
},
|
||||||
body: body === undefined ? undefined : JSON.stringify(body),
|
body: body === undefined ? undefined : JSON.stringify(body),
|
||||||
});
|
});
|
||||||
@ -97,4 +102,38 @@ describe("Agents SDK v2 MCP transport", () => {
|
|||||||
expect(responseText).toContain('\\"scopes\\":[\\"mcp\\"]');
|
expect(responseText).toContain('\\"scopes\\":[\\"mcp\\"]');
|
||||||
expect(responseText).toContain('\\"organizationId\\":\\"org-1\\"');
|
expect(responseText).toContain('\\"organizationId\\":\\"org-1\\"');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("accepts the SurfMind extension origin and rejects other browser origins", async () => {
|
||||||
|
const handler = createMcpHandler(
|
||||||
|
() => new McpServer({ name: "test", version: "1.0.0" }),
|
||||||
|
{
|
||||||
|
route: "/mcp",
|
||||||
|
allowedOriginHostnames: [
|
||||||
|
"open-seo.test",
|
||||||
|
"pghallcbnfabbgfijhbcldaapmgidnaa",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
const body = {
|
||||||
|
jsonrpc: "2.0",
|
||||||
|
id: 1,
|
||||||
|
method: "tools/list",
|
||||||
|
};
|
||||||
|
|
||||||
|
const surfMindResponse = await handler(
|
||||||
|
request("POST", body, {
|
||||||
|
Origin: "chrome-extension://pghallcbnfabbgfijhbcldaapmgidnaa",
|
||||||
|
}),
|
||||||
|
{},
|
||||||
|
ctx,
|
||||||
|
);
|
||||||
|
const unrelatedOriginResponse = await handler(
|
||||||
|
request("POST", body, { Origin: "https://evil.com" }),
|
||||||
|
{},
|
||||||
|
ctx,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(surfMindResponse.status).toBe(200);
|
||||||
|
expect(unrelatedOriginResponse.status).toBe(403);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -215,7 +215,10 @@ describe("handleAuthenticatedOpenSeoMcpRequest", () => {
|
|||||||
expect(response.headers.get("connection")).not.toBe("keep-alive");
|
expect(response.headers.get("connection")).not.toBe("keep-alive");
|
||||||
expect(selfHostedAuthMocks.createMcpHandler).toHaveBeenCalledWith(
|
expect(selfHostedAuthMocks.createMcpHandler).toHaveBeenCalledWith(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
allowedOriginHostnames: ["open-seo.test"],
|
allowedOriginHostnames: [
|
||||||
|
"open-seo.test",
|
||||||
|
"pghallcbnfabbgfijhbcldaapmgidnaa",
|
||||||
|
],
|
||||||
legacy: "reject",
|
legacy: "reject",
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@ -255,6 +258,24 @@ describe("handleAuthenticatedOpenSeoMcpRequest", () => {
|
|||||||
expect(selfHostedAuthMocks.createOpenSeoMcpServer).not.toHaveBeenCalled();
|
expect(selfHostedAuthMocks.createOpenSeoMcpServer).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("accepts a legacy request from the SurfMind Chrome extension", async () => {
|
||||||
|
const props = hostedProps();
|
||||||
|
|
||||||
|
const response = await handleAuthenticatedOpenSeoMcpRequest(
|
||||||
|
createMcpRequest({
|
||||||
|
Origin: "chrome-extension://pghallcbnfabbgfijhbcldaapmgidnaa",
|
||||||
|
}),
|
||||||
|
props,
|
||||||
|
{},
|
||||||
|
{ ...ctx, props },
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(response.status).toBe(200);
|
||||||
|
expect(selfHostedAuthMocks.createOpenSeoMcpServer).toHaveBeenCalledWith(
|
||||||
|
props,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it("rejects provider props missing the OAuth client identity", async () => {
|
it("rejects provider props missing the OAuth client identity", async () => {
|
||||||
// Hosted tokens always carry clientId/scopes; a token without them must
|
// Hosted tokens always carry clientId/scopes; a token without them must
|
||||||
// fail closed rather than skip scope enforcement.
|
// fail closed rather than skip scope enforcement.
|
||||||
|
|||||||
@ -32,6 +32,8 @@ const MCP_CORS_HEADERS = {
|
|||||||
"Access-Control-Max-Age": "86400",
|
"Access-Control-Max-Age": "86400",
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
const SURFMIND_CHROME_EXTENSION_HOSTNAME = "pghallcbnfabbgfijhbcldaapmgidnaa";
|
||||||
|
|
||||||
function withMcpCors(response: Response) {
|
function withMcpCors(response: Response) {
|
||||||
const headers = new Headers(response.headers);
|
const headers = new Headers(response.headers);
|
||||||
for (const [name, value] of Object.entries(MCP_CORS_HEADERS)) {
|
for (const [name, value] of Object.entries(MCP_CORS_HEADERS)) {
|
||||||
@ -158,6 +160,7 @@ export async function handleAuthenticatedOpenSeoMcpRequest(
|
|||||||
|
|
||||||
return createRequestHandler(result.data, [
|
return createRequestHandler(result.data, [
|
||||||
new URL(getHostedBaseUrl()).hostname,
|
new URL(getHostedBaseUrl()).hostname,
|
||||||
|
SURFMIND_CHROME_EXTENSION_HOSTNAME,
|
||||||
])(request, env, ctx);
|
])(request, env, ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user