Files
computer-playbook/roles/web-app-n8n/files/playwright/test-mcp-guest.js
kevinveenbirkenbachandClaude Opus 5 3db5820b6e fix(n8n): serve the MCP trigger over Streamable HTTP on 1.100.1 so Open WebUI can connect
Run 34421944328 n8n#1: Open WebUI 0.11.0 ships only a Streamable HTTP
MCP client. n8n 1.95.3's MCP Server Trigger speaks SSE only and answered
the client's POST to /webhook/infinito-mcp/sse with 404, so "every
registered MCP server answers Open WebUI's client" failed.

n8n moves to 1.100.1 and the managed trigger to typeVersion 2, which
serves Streamable HTTP at /mcp/<path>. meta/mcp.yml declares
transport: streamable_http at /mcp/infinito-mcp (the trigger path stays
infinito-mcp) and points source_url at the 1.100.1 tree. Open WebUI's
supported_transports drops sse, which its client never spoke; no
provider declares sse any more, so no pairing is lost.

n8n#0: the guest spec probed ${APP_BASE_URL}${MCP_ENDPOINT_PATH} and
expected a refusal of >= 300 without an event stream. The trigger is
exposure: internal, so the public vhost must answer 404; the spec now
asserts exactly that through mcpEndpointUrl(..., N8N_BASE_URL), and the
bearer guard itself is proven in-cluster by the CLI MCP contract.

Requirement 035 and the MCP audit table follow the version and
transport.

Upgrade path, read from upstream at both tags: the postgres migrations
are identical (45/45) and the only new common migration is
AddProjectDescriptionColumn, an additive nullable column; BREAKING-
CHANGES.md lists only 1.98.0 in range (route metric format, Form node
tag attributes, Node >= 20), none of which this deployment uses. A fresh
1.100.1 install ran all 89 migrations and passed the MCP bridge, server,
non-member and guest specs locally.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-11 12:20:46 +02:00

28 lines
1.1 KiB
JavaScript

const { test, expect } = require("@playwright/test");
const { skipUnlessServiceEnabled } = require("./service-gating");
const { mcpEndpointUrl, registerMcpDisabledState } = require("./mcp-endpoint");
const { decodeDotenvQuotedValue } = require("./personas");
const mcpEndpointPath = decodeDotenvQuotedValue(process.env.MCP_ENDPOINT_PATH || "");
const resolveEndpointUrl = () => mcpEndpointUrl(mcpEndpointPath, process.env.N8N_BASE_URL);
exports.register = function () {
test("guest: the public vhost refuses the internal MCP trigger to every unauthenticated caller", async ({ page }) => {
skipUnlessServiceEnabled("mcp");
expect(mcpEndpointPath, "MCP_ENDPOINT_PATH must be set").toBeTruthy();
const response = await page.request.get(resolveEndpointUrl(), {
failOnStatusCode: false,
maxRedirects: 0,
headers: { accept: "text/event-stream" },
});
expect(
response.status(),
"n8n's MCP trigger is exposure: internal, so the public vhost must answer 404; the bearer guard itself is proven in-cluster by the CLI MCP contract",
).toBe(404);
});
registerMcpDisabledState(resolveEndpointUrl);
};