|
|
@@ -1,82 +1,30 @@
|
|
|
-import { afterAll, expect } from "bun:test"
|
|
|
-import { LATEST_PROTOCOL_VERSION } from "@modelcontextprotocol/sdk/types.js"
|
|
|
-import { Effect } from "effect"
|
|
|
-import { MCP } from "../../src/mcp/index"
|
|
|
-import { testEffect } from "../lib/effect"
|
|
|
+import path from "node:path"
|
|
|
+import { expect, test } from "bun:test"
|
|
|
|
|
|
-const server = Bun.serve({
|
|
|
- port: 0,
|
|
|
- async fetch(request) {
|
|
|
- if (request.method !== "POST") return new Response(null, { status: 405 })
|
|
|
+test("explicit auth fails when anonymous initialize and catalog emit no OAuth challenge", async () => {
|
|
|
+ const child = Bun.spawn([process.execPath, path.join(import.meta.dir, "../fixture/mcp-oauth-anonymous.ts")], {
|
|
|
+ cwd: path.join(import.meta.dir, "../.."),
|
|
|
+ stdout: "pipe",
|
|
|
+ stderr: "pipe",
|
|
|
+ })
|
|
|
+ const [code, stdout, stderr] = await Promise.all([
|
|
|
+ child.exited,
|
|
|
+ Bun.readableStreamToText(child.stdout),
|
|
|
+ Bun.readableStreamToText(child.stderr),
|
|
|
+ ])
|
|
|
|
|
|
- const message = (await request.json()) as { id?: number; method: string }
|
|
|
- if (message.method === "initialize") {
|
|
|
- return Response.json({
|
|
|
- jsonrpc: "2.0",
|
|
|
- id: message.id,
|
|
|
- result: {
|
|
|
- protocolVersion: LATEST_PROTOCOL_VERSION,
|
|
|
- capabilities: { tools: {} },
|
|
|
- serverInfo: { name: "anonymous-oauth-test", version: "1" },
|
|
|
- },
|
|
|
- })
|
|
|
- }
|
|
|
- if (message.method === "notifications/initialized") return new Response(null, { status: 202 })
|
|
|
- if (message.method === "tools/list") {
|
|
|
- return Response.json({
|
|
|
- jsonrpc: "2.0",
|
|
|
- id: message.id,
|
|
|
- result: {
|
|
|
- tools: [{ name: "protected", inputSchema: { type: "object", properties: {} } }],
|
|
|
- },
|
|
|
- })
|
|
|
- }
|
|
|
- if (message.method === "tools/call") {
|
|
|
- return new Response("Authentication required", {
|
|
|
- status: 401,
|
|
|
- headers: { "WWW-Authenticate": `Bearer resource_metadata="${server.url}.well-known/oauth-protected-resource"` },
|
|
|
- })
|
|
|
- }
|
|
|
- return Response.json({ jsonrpc: "2.0", id: message.id, error: { code: -32601, message: "Method not found" } })
|
|
|
- },
|
|
|
-})
|
|
|
-
|
|
|
-afterAll(() => server.stop(true))
|
|
|
-
|
|
|
-const it = testEffect(MCP.defaultLayer)
|
|
|
-
|
|
|
-it.instance(
|
|
|
- "explicit auth fails when anonymous initialize and catalog emit no OAuth challenge",
|
|
|
- () =>
|
|
|
- MCP.Service.use((mcp) =>
|
|
|
- Effect.gen(function* () {
|
|
|
- const added = yield* mcp.add("anonymous-oauth", { type: "remote", url: server.url.toString() })
|
|
|
- expect(added.status).toEqual({ "anonymous-oauth": { status: "connected" } })
|
|
|
- expect(Object.keys(yield* mcp.tools())).toEqual(["anonymous-oauth_protected"])
|
|
|
-
|
|
|
- const protectedResponse = yield* Effect.promise(() =>
|
|
|
- fetch(server.url, {
|
|
|
- method: "POST",
|
|
|
- headers: { "content-type": "application/json" },
|
|
|
- body: JSON.stringify({
|
|
|
- jsonrpc: "2.0",
|
|
|
- id: 1,
|
|
|
- method: "tools/call",
|
|
|
- params: { name: "protected", arguments: {} },
|
|
|
- }),
|
|
|
- }),
|
|
|
- )
|
|
|
- expect(protectedResponse.status).toBe(401)
|
|
|
-
|
|
|
- const result = yield* mcp.authenticate("anonymous-oauth")
|
|
|
- expect(result).toEqual({
|
|
|
- status: "failed",
|
|
|
- error:
|
|
|
- "The server did not issue a standard OAuth challenge. Anonymous MCP access remains available, but authentication was not completed. Verify the server's OAuth configuration or use credentials supported by the server.",
|
|
|
- })
|
|
|
- expect(yield* mcp.hasStoredTokens("anonymous-oauth")).toBe(false)
|
|
|
- expect(yield* mcp.status()).toEqual({ "anonymous-oauth": { status: "connected" } })
|
|
|
- }),
|
|
|
- ),
|
|
|
- { config: { mcp: { "anonymous-oauth": { type: "remote", url: server.url.toString() } } } },
|
|
|
-)
|
|
|
+ expect(code, stderr).toBe(0)
|
|
|
+ const marker = "MCP_OAUTH_RESULT="
|
|
|
+ expect(stdout).toContain(marker)
|
|
|
+ expect(JSON.parse(stdout.slice(stdout.lastIndexOf(marker) + marker.length))).toEqual({
|
|
|
+ initialStatus: "connected",
|
|
|
+ initialTools: ["anonymous-oauth_protected"],
|
|
|
+ protectedToolFailed: true,
|
|
|
+ authStatus: "failed",
|
|
|
+ authError:
|
|
|
+ "The server did not issue a standard OAuth challenge. Anonymous MCP access remains available, but authentication was not completed. Verify the server's OAuth configuration or use credentials supported by the server.",
|
|
|
+ hasStoredTokens: false,
|
|
|
+ finalStatus: "connected",
|
|
|
+ finalTools: ["anonymous-oauth_protected"],
|
|
|
+ })
|
|
|
+}, 30_000)
|