| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102 |
- import { test, expect, describe } from "bun:test"
- import { determineScope } from "@modelcontextprotocol/sdk/client/auth.js"
- import { McpOAuthProvider, OAUTH_CALLBACK_PORT, OAUTH_CALLBACK_PATH } from "../../src/mcp/oauth-provider"
- import type { McpAuth } from "../../src/mcp/auth"
- // Stub auth — only synchronous getters are exercised in these tests
- const stubAuth = {} as McpAuth.Interface
- const makeProvider = (config: ConstructorParameters<typeof McpOAuthProvider>[2]) =>
- new McpOAuthProvider("test-server", "https://mcp.example.com/mcp", config, { onRedirect: async () => {} }, stubAuth)
- describe("McpOAuthProvider.redirectUrl", () => {
- test("defaults to 127.0.0.1:19876/mcp/oauth/callback", () => {
- const provider = makeProvider({})
- expect(provider.redirectUrl).toBe(`http://127.0.0.1:${OAUTH_CALLBACK_PORT}${OAUTH_CALLBACK_PATH}`)
- })
- test("uses callbackPort when set", () => {
- const provider = makeProvider({ callbackPort: 6620 })
- expect(provider.redirectUrl).toBe(`http://127.0.0.1:6620${OAUTH_CALLBACK_PATH}`)
- })
- test("redirectUri takes precedence over callbackPort", () => {
- const provider = makeProvider({
- callbackPort: 6620,
- redirectUri: "http://127.0.0.1:9999/custom/callback",
- })
- expect(provider.redirectUrl).toBe("http://127.0.0.1:9999/custom/callback")
- })
- test("uses explicit redirectUri when set without callbackPort", () => {
- const provider = makeProvider({ redirectUri: "http://127.0.0.1:8080/oauth/callback" })
- expect(provider.redirectUrl).toBe("http://127.0.0.1:8080/oauth/callback")
- })
- })
- describe("McpOAuthProvider.clientMetadata", () => {
- test("includes redirect_uris from redirectUrl", () => {
- const provider = makeProvider({ callbackPort: 6620 })
- expect(provider.clientMetadata.redirect_uris).toEqual([`http://127.0.0.1:6620${OAUTH_CALLBACK_PATH}`])
- })
- test("includes scope when set in config", () => {
- const provider = makeProvider({ scope: "openid offline_access" })
- expect(provider.clientMetadata.scope).toBe("openid offline_access")
- })
- test("omits scope when not set in config", () => {
- const provider = makeProvider({})
- expect(provider.clientMetadata.scope).toBeUndefined()
- })
- test("sets token_endpoint_auth_method to client_secret_post when clientSecret provided", () => {
- const provider = makeProvider({ clientSecret: "secret" })
- expect(provider.clientMetadata.token_endpoint_auth_method).toBe("client_secret_post")
- })
- test("sets token_endpoint_auth_method to none when no clientSecret", () => {
- const provider = makeProvider({})
- expect(provider.clientMetadata.token_endpoint_auth_method).toBe("none")
- })
- })
- describe("MCP OAuth scope selection", () => {
- test("adds offline_access when the authorization server and client support refresh tokens", () => {
- expect(
- determineScope({
- resourceMetadata: {
- resource: "https://mcp.example.com/mcp",
- scopes_supported: ["resource.read"],
- },
- authServerMetadata: {
- issuer: "https://auth.example.com",
- authorization_endpoint: "https://auth.example.com/authorize",
- token_endpoint: "https://auth.example.com/token",
- response_types_supported: ["code"],
- scopes_supported: ["resource.read", "offline_access"],
- },
- clientMetadata: makeProvider({}).clientMetadata,
- }),
- ).toBe("resource.read offline_access")
- })
- test("does not add unsupported authorization server scopes", () => {
- expect(
- determineScope({
- resourceMetadata: {
- resource: "https://mcp.example.com/mcp",
- scopes_supported: ["resource.read"],
- },
- authServerMetadata: {
- issuer: "https://auth.example.com",
- authorization_endpoint: "https://auth.example.com/authorize",
- token_endpoint: "https://auth.example.com/token",
- response_types_supported: ["code"],
- scopes_supported: ["resource.read"],
- },
- clientMetadata: makeProvider({}).clientMetadata,
- }),
- ).toBe("resource.read")
- })
- })
|