Skip to content

Commit 5f83a8d

Browse files
committed
fix(core): use public origin for MCP discovery 401
1 parent 0ed9652 commit 5f83a8d

2 files changed

Lines changed: 23 additions & 4 deletions

File tree

packages/core/src/astro/middleware/auth.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,13 +67,17 @@ function csrfRejectedResponse(): Response {
6767
);
6868
}
6969

70-
function mcpUnauthorizedResponse(url: URL): Response {
70+
function mcpUnauthorizedResponse(
71+
url: URL,
72+
config?: Parameters<typeof getPublicOrigin>[1],
73+
): Response {
74+
const origin = getPublicOrigin(url, config);
7175
return Response.json(
7276
{ error: { code: "NOT_AUTHENTICATED", message: "Not authenticated" } },
7377
{
7478
status: 401,
7579
headers: {
76-
"WWW-Authenticate": `Bearer resource_metadata="${url.origin}/.well-known/oauth-protected-resource"`,
80+
"WWW-Authenticate": `Bearer resource_metadata="${origin}/.well-known/oauth-protected-resource"`,
7781
...MW_CACHE_HEADERS,
7882
},
7983
},
@@ -217,7 +221,7 @@ export const onRequest = defineMiddleware(async (context, next) => {
217221
const method = context.request.method.toUpperCase();
218222
const isMcpEndpoint = url.pathname === MCP_ENDPOINT_PATH;
219223
if (isMcpEndpoint && !isTokenAuth) {
220-
return mcpUnauthorizedResponse(url);
224+
return mcpUnauthorizedResponse(url, context.locals.emdash?.config);
221225
}
222226

223227
// CSRF protection: require X-EmDash-Request header on state-changing requests.

packages/core/tests/unit/auth/mcp-discovery-post.test.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ async function runAuthMiddleware(opts: {
4848
method?: string;
4949
headers?: HeadersInit;
5050
sessionUserId?: string | null;
51+
siteUrl?: string;
5152
}) {
5253
const url = new URL(opts.pathname, "https://example.com");
5354
const session = {
@@ -76,7 +77,7 @@ async function runAuthMiddleware(opts: {
7677
locals: {
7778
emdash: {
7879
db: {},
79-
config: {},
80+
config: opts.siteUrl ? { siteUrl: opts.siteUrl } : {},
8081
},
8182
},
8283
session,
@@ -120,6 +121,20 @@ describe("MCP discovery auth middleware", () => {
120121
expect(session.get).not.toHaveBeenCalled();
121122
});
122123

124+
it("uses the configured public origin for anonymous MCP POST discovery responses", async () => {
125+
const { response, next } = await runAuthMiddleware({
126+
pathname: "/_emdash/api/mcp",
127+
headers: { "Content-Type": "application/json" },
128+
siteUrl: "https://public.example.com",
129+
});
130+
131+
expect(next).not.toHaveBeenCalled();
132+
expect(response.status).toBe(401);
133+
expect(response.headers.get("WWW-Authenticate")).toBe(
134+
'Bearer resource_metadata="https://public.example.com/.well-known/oauth-protected-resource"',
135+
);
136+
});
137+
123138
it("returns 401 with discovery metadata for invalid bearer tokens on MCP POST", async () => {
124139
const { response, next } = await runAuthMiddleware({
125140
pathname: "/_emdash/api/mcp",

0 commit comments

Comments
 (0)