源码快照 @ 2026-09-02 (source-only, 剔除非源码素材)
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,46 @@
|
||||
import assert from "node:assert/strict"
|
||||
import { describe, it } from "node:test"
|
||||
import { SdkError, SdkErrorCode } from "@modelcontextprotocol/client"
|
||||
import {
|
||||
EnterpriseMcpClientError,
|
||||
EnterpriseMcpLifecycleDeadlineError,
|
||||
isEnterpriseMcpLifecycleDeadline,
|
||||
} from "../src/index.js"
|
||||
|
||||
describe("lifecycle deadline error", () => {
|
||||
it("aborts with an SdkError so the SDK rethrows it instead of stringifying the reason", () => {
|
||||
const error = new EnterpriseMcpLifecycleDeadlineError("tool-execution")
|
||||
|
||||
// The SDK only passes an abort reason through untouched when it is already
|
||||
// an SdkError; anything else becomes String(reason) on a new RequestTimeout.
|
||||
assert.ok(error instanceof SdkError)
|
||||
assert.equal(error.code, SdkErrorCode.RequestTimeout)
|
||||
assert.equal(error.operationPhase, "tool-execution")
|
||||
assert.match(error.message, /exceeded its lifecycle deadline/)
|
||||
})
|
||||
|
||||
it("carries a marker that survives an SDK round trip", () => {
|
||||
const reason = new EnterpriseMcpLifecycleDeadlineError("tool-execution")
|
||||
const rethrown = reason instanceof SdkError ? reason : new SdkError(SdkErrorCode.RequestTimeout, String(reason))
|
||||
|
||||
assert.ok(isEnterpriseMcpLifecycleDeadline(rethrown))
|
||||
})
|
||||
|
||||
it("detects the deadline through a wrapped cause chain", () => {
|
||||
const wrapped = new EnterpriseMcpClientError({
|
||||
operationPhase: "tool-execution",
|
||||
requestPhase: "mcp-tool-execution",
|
||||
cause: new EnterpriseMcpLifecycleDeadlineError("tool-execution"),
|
||||
})
|
||||
|
||||
assert.ok(isEnterpriseMcpLifecycleDeadline(wrapped))
|
||||
})
|
||||
|
||||
it("does not claim a provider's own RequestTimeout as ours", () => {
|
||||
const providerError = new SdkError(SdkErrorCode.RequestTimeout, "upstream is busy", { provider_detail: "busy" })
|
||||
|
||||
assert.equal(isEnterpriseMcpLifecycleDeadline(providerError), false)
|
||||
assert.equal(isEnterpriseMcpLifecycleDeadline(new Error("unrelated")), false)
|
||||
assert.equal(isEnterpriseMcpLifecycleDeadline(undefined), false)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,442 @@
|
||||
import assert from "node:assert/strict"
|
||||
import { describe, it } from "node:test"
|
||||
import { z } from "zod"
|
||||
import {
|
||||
discoverConnectionRequirements,
|
||||
selectRecoverableAuthorizationServerIssuer,
|
||||
type EnterpriseMcpFetch,
|
||||
} from "../src/index.js"
|
||||
|
||||
const rpcRequestSchema = z.object({
|
||||
id: z.union([z.string(), z.number()]).optional(),
|
||||
method: z.string(),
|
||||
}).passthrough()
|
||||
|
||||
function unauthenticatedMcpFetch(): EnterpriseMcpFetch {
|
||||
return async (_url, init) => {
|
||||
const body = typeof init?.body === "string" ? init.body : ""
|
||||
if (!body) return new Response(null, { status: 202 })
|
||||
const request = rpcRequestSchema.parse(JSON.parse(body))
|
||||
if (request.method === "notifications/initialized") return new Response(null, { status: 202 })
|
||||
if (request.method === "initialize") {
|
||||
return Response.json({
|
||||
jsonrpc: "2.0",
|
||||
id: request.id,
|
||||
result: {
|
||||
protocolVersion: "2025-06-18",
|
||||
capabilities: { tools: {} },
|
||||
serverInfo: { name: "requirements-test", version: "1.0.0" },
|
||||
},
|
||||
})
|
||||
}
|
||||
if (request.method === "tools/list") {
|
||||
return Response.json({
|
||||
jsonrpc: "2.0",
|
||||
id: request.id,
|
||||
result: {
|
||||
tools: [{
|
||||
name: "read-record",
|
||||
inputSchema: { type: "object" },
|
||||
annotations: { readOnlyHint: true, destructiveHint: false },
|
||||
}],
|
||||
},
|
||||
})
|
||||
}
|
||||
return new Response(null, { status: 404 })
|
||||
}
|
||||
}
|
||||
|
||||
describe("enterprise MCP requirements discovery", () => {
|
||||
it("discovers a 2026 stateless server without an initialize handshake", async () => {
|
||||
const methods: string[] = []
|
||||
const fetch: EnterpriseMcpFetch = async (_url, init) => {
|
||||
assert.equal(typeof init?.body, "string")
|
||||
const request = rpcRequestSchema.parse(JSON.parse(init?.body as string))
|
||||
methods.push(request.method)
|
||||
assert.equal(new Headers(init?.headers).get("mcp-protocol-version"), "2026-07-28")
|
||||
assert.equal(new Headers(init?.headers).has("mcp-session-id"), false)
|
||||
if (request.method === "server/discover") {
|
||||
return Response.json({
|
||||
jsonrpc: "2.0",
|
||||
id: request.id,
|
||||
result: {
|
||||
supportedVersions: ["2026-07-28"],
|
||||
capabilities: { tools: {} },
|
||||
resultType: "complete",
|
||||
ttlMs: 0,
|
||||
cacheScope: "private",
|
||||
_meta: {
|
||||
"io.modelcontextprotocol/serverInfo": { name: "requirements-modern-test", version: "1.0.0" },
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
if (request.method === "tools/list") {
|
||||
return Response.json({
|
||||
jsonrpc: "2.0",
|
||||
id: request.id,
|
||||
result: {
|
||||
resultType: "complete",
|
||||
ttlMs: 0,
|
||||
cacheScope: "private",
|
||||
tools: [{
|
||||
name: "read-modern-record",
|
||||
inputSchema: { type: "object" },
|
||||
annotations: { readOnlyHint: true, destructiveHint: false },
|
||||
}],
|
||||
_meta: {
|
||||
"io.modelcontextprotocol/serverInfo": { name: "requirements-modern-test", version: "1.0.0" },
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
return new Response(null, { status: 404 })
|
||||
}
|
||||
|
||||
const result = await discoverConnectionRequirements({
|
||||
serverUrl: "https://mcp.example.test/mcp",
|
||||
fetch,
|
||||
})
|
||||
|
||||
assert.deepEqual(methods, ["server/discover", "tools/list"])
|
||||
assert.equal(result.status, "ready")
|
||||
assert.equal(result.server.initialize, "succeeded")
|
||||
assert.equal(result.server.protocolEra, "modern")
|
||||
assert.equal(result.server.protocolVersion, "2026-07-28")
|
||||
assert.equal(result.tools.items?.[0]?.name, "read-modern-record")
|
||||
})
|
||||
|
||||
it("initializes and lists tools without creating registration state", async () => {
|
||||
const result = await discoverConnectionRequirements({
|
||||
serverUrl: "https://mcp.example.test/mcp",
|
||||
fetch: unauthenticatedMcpFetch(),
|
||||
})
|
||||
|
||||
assert.equal(result.status, "ready")
|
||||
assert.equal(result.server.initialize, "succeeded")
|
||||
assert.equal(result.authentication.kind, "none")
|
||||
assert.equal(result.tools.visibility, "available_without_auth")
|
||||
assert.equal(result.tools.count, 1)
|
||||
assert.equal(result.tools.items?.[0]?.readOnlyHint, true)
|
||||
})
|
||||
|
||||
it("discovers protected-resource and authorization-server requirements without performing DCR", async () => {
|
||||
let registrationRequests = 0
|
||||
const fetch: EnterpriseMcpFetch = async (url) => {
|
||||
const target = new URL(url)
|
||||
if (target.pathname === "/resource-metadata") {
|
||||
return Response.json({
|
||||
resource: "https://mcp.example.test/mcp",
|
||||
authorization_servers: ["https://identity.example.test/tenant"],
|
||||
scopes_supported: ["records.read", "records.write", "offline_access"],
|
||||
})
|
||||
}
|
||||
if (target.pathname === "/.well-known/oauth-authorization-server/tenant") {
|
||||
return Response.json({
|
||||
issuer: "https://identity.example.test/tenant",
|
||||
authorization_endpoint: "https://identity.example.test/tenant/authorize",
|
||||
token_endpoint: "https://identity.example.test/tenant/token",
|
||||
registration_endpoint: "https://identity.example.test/tenant/register",
|
||||
response_types_supported: ["code"],
|
||||
grant_types_supported: ["authorization_code", "refresh_token"],
|
||||
token_endpoint_auth_methods_supported: ["none"],
|
||||
code_challenge_methods_supported: ["S256"],
|
||||
client_id_metadata_document_supported: true,
|
||||
scopes_supported: ["records.read", "records.write", "offline_access"],
|
||||
})
|
||||
}
|
||||
if (target.pathname.endsWith("/register")) {
|
||||
registrationRequests += 1
|
||||
return new Response(null, { status: 500 })
|
||||
}
|
||||
return new Response(null, {
|
||||
status: 401,
|
||||
headers: {
|
||||
"www-authenticate": "Bearer resource_metadata=\"https://mcp.example.test/resource-metadata\", scope=\"records.read\"",
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const result = await discoverConnectionRequirements({
|
||||
serverUrl: "https://mcp.example.test/mcp",
|
||||
fetch,
|
||||
})
|
||||
|
||||
assert.equal(result.server.initialize, "authentication_required")
|
||||
assert.equal(result.authentication.kind, "oauth")
|
||||
assert.deepEqual(result.authentication.requiredScopes, ["records.read"])
|
||||
assert.deepEqual(result.authentication.recommendedScopes, ["records.read", "offline_access"])
|
||||
assert.equal(result.authentication.recommendedRegistrationMethod, "client_metadata")
|
||||
assert.deepEqual(result.authentication.availableRegistrationMethods, ["client_metadata", "pre_registered", "dynamic"])
|
||||
assert.equal(result.authentication.refreshSupport, "supported")
|
||||
assert.equal(result.tools.visibility, "requires_auth")
|
||||
assert.equal(registrationRequests, 0)
|
||||
})
|
||||
|
||||
it("requires explicit selection when protected-resource metadata advertises multiple issuers", async () => {
|
||||
const issuers = ["https://identity-a.example.test", "https://identity-b.example.test"]
|
||||
const fetch: EnterpriseMcpFetch = async (url) => {
|
||||
const target = new URL(url)
|
||||
if (target.pathname === "/resource-metadata") {
|
||||
return Response.json({ resource: "https://mcp.example.test/mcp", authorization_servers: issuers })
|
||||
}
|
||||
const issuer = issuers.find((candidate) => target.origin === candidate)
|
||||
if (issuer && target.pathname === "/.well-known/oauth-authorization-server") {
|
||||
return Response.json({
|
||||
issuer,
|
||||
authorization_endpoint: `${issuer}/authorize`,
|
||||
token_endpoint: `${issuer}/token`,
|
||||
response_types_supported: ["code"],
|
||||
client_id_metadata_document_supported: true,
|
||||
})
|
||||
}
|
||||
return new Response(null, {
|
||||
status: 401,
|
||||
headers: { "www-authenticate": "Bearer resource_metadata=\"https://mcp.example.test/resource-metadata\"" },
|
||||
})
|
||||
}
|
||||
|
||||
const result = await discoverConnectionRequirements({
|
||||
serverUrl: "https://mcp.example.test/mcp",
|
||||
fetch,
|
||||
})
|
||||
assert.equal(result.status, "manual_action_required")
|
||||
assert.equal(result.authentication.authorizationServers.length, 2)
|
||||
assert.equal(result.manualRequirements[0]?.code, "authorization_server_selection")
|
||||
assert.equal(result.manualRequirements[0]?.required, true)
|
||||
})
|
||||
|
||||
it("accepts a resource-scoped OAuth discovery alias while exposing the canonical issuer", async () => {
|
||||
const resource = "https://api.salesforce.example:443/platform/mcp/v1/platform/sobject-all"
|
||||
const fetch: EnterpriseMcpFetch = async (url) => {
|
||||
const target = new URL(url)
|
||||
if (target.pathname === "/.well-known/oauth-protected-resource/platform/mcp/v1/platform/sobject-all") {
|
||||
return Response.json({
|
||||
resource,
|
||||
authorization_servers: [resource],
|
||||
scopes_supported: ["mcp_api", "refresh_token"],
|
||||
})
|
||||
}
|
||||
if (target.pathname === "/.well-known/oauth-authorization-server/platform/mcp/v1/platform/sobject-all") {
|
||||
return Response.json({
|
||||
issuer: "https://login.salesforce.example",
|
||||
authorization_endpoint: "https://login.salesforce.example/services/oauth2/authorize",
|
||||
token_endpoint: "https://login.salesforce.example/services/oauth2/token",
|
||||
response_types_supported: ["code"],
|
||||
grant_types_supported: ["authorization_code", "refresh_token"],
|
||||
token_endpoint_auth_methods_supported: ["none", "client_secret_post"],
|
||||
code_challenge_methods_supported: ["S256"],
|
||||
scopes_supported: ["mcp_api", "refresh_token"],
|
||||
})
|
||||
}
|
||||
if (target.origin === "https://login.salesforce.example" && target.pathname === "/.well-known/oauth-authorization-server") {
|
||||
return Response.json({
|
||||
issuer: "https://login.salesforce.example",
|
||||
authorization_endpoint: "https://login.salesforce.example/services/oauth2/authorize",
|
||||
token_endpoint: "https://login.salesforce.example/services/oauth2/token",
|
||||
response_types_supported: ["code"],
|
||||
grant_types_supported: ["authorization_code", "refresh_token"],
|
||||
token_endpoint_auth_methods_supported: ["none", "client_secret_post"],
|
||||
code_challenge_methods_supported: ["S256"],
|
||||
scopes_supported: ["mcp_api", "refresh_token"],
|
||||
})
|
||||
}
|
||||
return new Response(null, { status: 401 })
|
||||
}
|
||||
|
||||
const result = await discoverConnectionRequirements({
|
||||
serverUrl: "https://api.salesforce.example/platform/mcp/v1/platform/sobject-all",
|
||||
fetch,
|
||||
})
|
||||
|
||||
assert.equal(result.authentication.authorizationServers[0]?.issuer, "https://login.salesforce.example")
|
||||
assert.deepEqual(result.authentication.authorizationServers[0]?.scopesSupported, ["mcp_api", "refresh_token"])
|
||||
assert.deepEqual(result.authentication.recommendedScopes, ["mcp_api", "refresh_token"])
|
||||
assert.equal(result.authentication.recommendedRegistrationMethod, "pre_registered")
|
||||
assert.equal(result.warnings.some((warning) => warning.code === "oauth_issuer_mismatch"), false)
|
||||
assert.equal(selectRecoverableAuthorizationServerIssuer({
|
||||
selectedIssuer: resource,
|
||||
requirements: result,
|
||||
}), "https://login.salesforce.example")
|
||||
})
|
||||
|
||||
it("accepts a root resource discovery alias with an equivalent trailing slash", async () => {
|
||||
const fetch: EnterpriseMcpFetch = async (url) => {
|
||||
const target = new URL(url)
|
||||
if (target.origin === "https://mcp.vercel.example" && target.pathname === "/.well-known/oauth-protected-resource") {
|
||||
return Response.json({
|
||||
resource: "https://mcp.vercel.example/",
|
||||
authorization_servers: ["https://mcp.vercel.example"],
|
||||
})
|
||||
}
|
||||
if (target.origin === "https://mcp.vercel.example" && target.pathname === "/.well-known/oauth-authorization-server") {
|
||||
return Response.json({
|
||||
issuer: "https://vercel.example",
|
||||
authorization_endpoint: "https://vercel.example/oauth/authorize",
|
||||
token_endpoint: "https://vercel.example/oauth/token",
|
||||
registration_endpoint: "https://vercel.example/oauth/register",
|
||||
response_types_supported: ["code"],
|
||||
grant_types_supported: ["authorization_code", "refresh_token"],
|
||||
token_endpoint_auth_methods_supported: ["none"],
|
||||
code_challenge_methods_supported: ["S256"],
|
||||
})
|
||||
}
|
||||
if (target.origin === "https://vercel.example" && target.pathname === "/.well-known/oauth-authorization-server") {
|
||||
return Response.json({
|
||||
issuer: "https://vercel.example",
|
||||
authorization_endpoint: "https://vercel.example/oauth/authorize",
|
||||
token_endpoint: "https://vercel.example/oauth/token",
|
||||
registration_endpoint: "https://vercel.example/oauth/register",
|
||||
response_types_supported: ["code"],
|
||||
grant_types_supported: ["authorization_code", "refresh_token"],
|
||||
token_endpoint_auth_methods_supported: ["none"],
|
||||
code_challenge_methods_supported: ["S256"],
|
||||
})
|
||||
}
|
||||
return new Response(null, {
|
||||
status: 401,
|
||||
headers: {
|
||||
"www-authenticate": "Bearer resource_metadata=\"https://mcp.vercel.example/.well-known/oauth-protected-resource\"",
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const result = await discoverConnectionRequirements({
|
||||
serverUrl: "https://mcp.vercel.example",
|
||||
fetch,
|
||||
})
|
||||
|
||||
assert.equal(result.authentication.authorizationServers[0]?.issuer, "https://vercel.example")
|
||||
assert.equal(result.authentication.recommendedRegistrationMethod, "dynamic")
|
||||
assert.equal(result.warnings.some((warning) => warning.code === "oauth_issuer_mismatch"), false)
|
||||
assert.equal(selectRecoverableAuthorizationServerIssuer({
|
||||
selectedIssuer: "https://mcp.vercel.example",
|
||||
requirements: result,
|
||||
}), "https://vercel.example")
|
||||
assert.equal(selectRecoverableAuthorizationServerIssuer({
|
||||
selectedIssuer: "https://unrelated.example",
|
||||
requirements: result,
|
||||
}), undefined)
|
||||
})
|
||||
|
||||
it("ignores endpoints from a resource alias and uses strictly bound canonical metadata", async () => {
|
||||
const resource = "https://mcp.example.test/mcp"
|
||||
const issuer = "https://identity.example.test"
|
||||
const fetch: EnterpriseMcpFetch = async (url) => {
|
||||
const target = new URL(url)
|
||||
if (target.origin === "https://mcp.example.test" && target.pathname === "/.well-known/oauth-protected-resource/mcp") {
|
||||
return Response.json({ resource, authorization_servers: [resource] })
|
||||
}
|
||||
if (target.origin === "https://mcp.example.test" && target.pathname === "/.well-known/oauth-authorization-server/mcp") {
|
||||
return Response.json({
|
||||
issuer,
|
||||
authorization_endpoint: "https://attacker.example.test/authorize",
|
||||
token_endpoint: "https://attacker.example.test/token",
|
||||
registration_endpoint: "https://attacker.example.test/register",
|
||||
response_types_supported: ["code"],
|
||||
})
|
||||
}
|
||||
if (target.origin === "https://identity.example.test" && target.pathname === "/.well-known/oauth-authorization-server") {
|
||||
return Response.json({
|
||||
issuer,
|
||||
authorization_endpoint: `${issuer}/authorize`,
|
||||
token_endpoint: `${issuer}/token`,
|
||||
response_types_supported: ["code"],
|
||||
})
|
||||
}
|
||||
return new Response(null, {
|
||||
status: 401,
|
||||
headers: { "www-authenticate": "Bearer resource_metadata=\"https://mcp.example.test/.well-known/oauth-protected-resource/mcp\"" },
|
||||
})
|
||||
}
|
||||
|
||||
const result = await discoverConnectionRequirements({ serverUrl: resource, fetch })
|
||||
|
||||
assert.equal(result.authentication.authorizationServers[0]?.issuer, issuer)
|
||||
assert.equal(result.authentication.authorizationServers[0]?.authorizationEndpoint, `${issuer}/authorize`)
|
||||
assert.equal(result.authentication.authorizationServers[0]?.tokenEndpoint, `${issuer}/token`)
|
||||
assert.equal(result.authentication.authorizationServers[0]?.registrationEndpoint, undefined)
|
||||
})
|
||||
|
||||
it("rejects a resource alias when the claimed canonical issuer does not verify", async () => {
|
||||
const resource = "https://mcp.example.test/mcp"
|
||||
const fetch: EnterpriseMcpFetch = async (url) => {
|
||||
const target = new URL(url)
|
||||
if (target.origin === "https://mcp.example.test" && target.pathname === "/.well-known/oauth-protected-resource/mcp") {
|
||||
return Response.json({ resource, authorization_servers: [resource] })
|
||||
}
|
||||
if (target.origin === "https://mcp.example.test" && target.pathname === "/.well-known/oauth-authorization-server/mcp") {
|
||||
return Response.json({
|
||||
issuer: "https://identity.example.test",
|
||||
authorization_endpoint: "https://attacker.example.test/authorize",
|
||||
token_endpoint: "https://attacker.example.test/token",
|
||||
response_types_supported: ["code"],
|
||||
})
|
||||
}
|
||||
if (target.origin === "https://identity.example.test" && target.pathname === "/.well-known/oauth-authorization-server") {
|
||||
return Response.json({
|
||||
issuer: "https://different.example.test",
|
||||
authorization_endpoint: "https://attacker.example.test/authorize",
|
||||
token_endpoint: "https://attacker.example.test/token",
|
||||
response_types_supported: ["code"],
|
||||
})
|
||||
}
|
||||
return new Response(null, {
|
||||
status: 401,
|
||||
headers: { "www-authenticate": "Bearer resource_metadata=\"https://mcp.example.test/.well-known/oauth-protected-resource/mcp\"" },
|
||||
})
|
||||
}
|
||||
|
||||
const result = await discoverConnectionRequirements({ serverUrl: resource, fetch })
|
||||
|
||||
assert.equal(result.authentication.authorizationServers.length, 0)
|
||||
assert.equal(result.authentication.authorizationServers.some((server) => server.tokenEndpoint?.includes("attacker.example.test")), false)
|
||||
assert.equal(result.warnings.some((warning) => warning.code === "oauth_issuer_mismatch"), true)
|
||||
assert.equal(result.status, "manual_action_required")
|
||||
})
|
||||
|
||||
it("accepts an authorization-server root issuer with an equivalent trailing slash", async () => {
|
||||
const fetch: EnterpriseMcpFetch = async (url) => {
|
||||
const target = new URL(url)
|
||||
if (target.origin === "https://mcp.close.example" && target.pathname === "/.well-known/oauth-protected-resource") {
|
||||
return Response.json({
|
||||
resource: "https://mcp.close.example/",
|
||||
authorization_servers: ["https://api.close.example/"],
|
||||
scopes_supported: ["mcp.read", "offline_access"],
|
||||
})
|
||||
}
|
||||
if (target.origin === "https://api.close.example" && target.pathname === "/.well-known/oauth-authorization-server") {
|
||||
return Response.json({
|
||||
issuer: "https://api.close.example",
|
||||
authorization_endpoint: "https://app.close.example/oauth2/authorize/",
|
||||
token_endpoint: "https://api.close.example/oauth2/token/",
|
||||
registration_endpoint: "https://api.close.example/oauth2/register/",
|
||||
response_types_supported: ["code"],
|
||||
grant_types_supported: ["authorization_code", "refresh_token"],
|
||||
token_endpoint_auth_methods_supported: ["none", "client_secret_basic", "client_secret_post"],
|
||||
code_challenge_methods_supported: ["S256"],
|
||||
scopes_supported: ["mcp.read", "offline_access"],
|
||||
})
|
||||
}
|
||||
return new Response(null, {
|
||||
status: 401,
|
||||
headers: {
|
||||
"www-authenticate": "Bearer resource_metadata=\"https://mcp.close.example/.well-known/oauth-protected-resource\"",
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const result = await discoverConnectionRequirements({
|
||||
serverUrl: "https://mcp.close.example/mcp",
|
||||
fetch,
|
||||
})
|
||||
|
||||
assert.equal(result.authentication.authorizationServers[0]?.issuer, "https://api.close.example")
|
||||
assert.equal(result.authentication.recommendedRegistrationMethod, "dynamic")
|
||||
assert.equal(result.warnings.some((warning) => warning.code === "oauth_issuer_mismatch"), false)
|
||||
assert.equal(selectRecoverableAuthorizationServerIssuer({
|
||||
selectedIssuer: "https://api.close.example/",
|
||||
requirements: result,
|
||||
}), "https://api.close.example")
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,261 @@
|
||||
import assert from "node:assert/strict"
|
||||
import { describe, it } from "node:test"
|
||||
import { exchangeAuthorization, OAuthError, OAuthErrorCode } from "@modelcontextprotocol/client"
|
||||
import { z } from "zod"
|
||||
import {
|
||||
createEnterpriseMcpClient,
|
||||
createEnterpriseMcpTokenResponseCompat,
|
||||
SLACK_STYLE_OAUTH_ERROR_MAPPING,
|
||||
type EnterpriseMcpConnection,
|
||||
type EnterpriseMcpDiagnosticEvent,
|
||||
} from "../src/index.js"
|
||||
import { createEnterpriseMcpRequestObserver } from "../src/request-observer.js"
|
||||
import { redactedResponseBodyExcerpt } from "../src/response-body-excerpt.js"
|
||||
|
||||
const SAFE_SLACK_ERROR = "Slack-style provider error: invalid_refresh_token"
|
||||
const AUTHORIZATION_CODE = "SECRETVALUE123"
|
||||
const JWT = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.signatureABCD"
|
||||
const SLACK_REFRESH_TOKEN = "xoxe-1-1234567890-abcdef"
|
||||
const BEARER_TOKEN = "Bearer abcdefghijklmnopqrstuvwx"
|
||||
const GITHUB_TOKEN = "ghp_ABCDEFGHIJKLMNOPQRSTUVWXYZ123456"
|
||||
const OPAQUE_TOKEN = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmn"
|
||||
|
||||
const tokenResponseSchema = z.object({
|
||||
ok: z.boolean().optional(),
|
||||
access_token: z.string(),
|
||||
token_type: z.string(),
|
||||
}).passthrough()
|
||||
|
||||
const discoverRequestSchema = z.object({
|
||||
id: z.union([z.string(), z.number()]),
|
||||
method: z.literal("server/discover"),
|
||||
params: z.object({ _meta: z.record(z.string(), z.unknown()) }).passthrough(),
|
||||
}).passthrough()
|
||||
|
||||
function tokenRequestInit(): RequestInit {
|
||||
return {
|
||||
method: "POST",
|
||||
headers: { "content-type": "application/x-www-form-urlencoded" },
|
||||
body: new URLSearchParams({ grant_type: "authorization_code", code: "approved-code" }),
|
||||
}
|
||||
}
|
||||
|
||||
function noAuthConnection(): EnterpriseMcpConnection {
|
||||
return {
|
||||
id: "slack-compat-connection",
|
||||
serverUrl: "https://mcp.example.test/mcp",
|
||||
authorization: { type: "none" },
|
||||
}
|
||||
}
|
||||
|
||||
describe("Slack-style MCP compatibility", () => {
|
||||
it("maps the documented provider errors from one exported table", () => {
|
||||
assert.equal(SLACK_STYLE_OAUTH_ERROR_MAPPING.invalid_code, "invalid_grant")
|
||||
assert.equal(SLACK_STYLE_OAUTH_ERROR_MAPPING.bad_client_secret, "invalid_client")
|
||||
assert.equal(SLACK_STYLE_OAUTH_ERROR_MAPPING.no_user_scopes, "invalid_scope")
|
||||
assert.equal(SLACK_STYLE_OAUTH_ERROR_MAPPING.invalid_auth, "access_denied")
|
||||
})
|
||||
|
||||
it("turns an HTTP-200 ok:false token exchange into a typed OAuth error", async () => {
|
||||
const translations: EnterpriseMcpDiagnosticEvent[] = []
|
||||
const compatibleFetch = createEnterpriseMcpTokenResponseCompat({
|
||||
fetch: async () => Response.json({
|
||||
ok: false,
|
||||
error: "invalid_code",
|
||||
access_token: "must-not-be-diagnosed",
|
||||
weird_field: "xoxp-arbitrary",
|
||||
}),
|
||||
onTranslation: (translation) => translations.push({
|
||||
kind: "request",
|
||||
connectionId: "slack-compat-connection",
|
||||
operationPhase: "authorization-callback",
|
||||
...translation,
|
||||
}),
|
||||
})
|
||||
const sdkFetch: typeof fetch = async (url, init) => compatibleFetch(
|
||||
typeof url === "string" || url instanceof URL ? url : url.url,
|
||||
init,
|
||||
)
|
||||
|
||||
await assert.rejects(
|
||||
exchangeAuthorization("https://provider.example.test", {
|
||||
metadata: {
|
||||
issuer: "https://provider.example.test",
|
||||
authorization_endpoint: "https://provider.example.test/authorize",
|
||||
token_endpoint: "https://provider.example.test/token",
|
||||
response_types_supported: ["code"],
|
||||
},
|
||||
clientInformation: { client_id: "enterprise-client" },
|
||||
authorizationCode: "approved-code",
|
||||
codeVerifier: "pkce-verifier",
|
||||
redirectUri: "https://den.example.test/callback",
|
||||
fetchFn: sdkFetch,
|
||||
}),
|
||||
(error: unknown) => {
|
||||
assert.ok(error instanceof OAuthError)
|
||||
assert.equal(error.code, OAuthErrorCode.InvalidGrant)
|
||||
assert.match(error.message, /Slack-style provider error: invalid_code/)
|
||||
return true
|
||||
},
|
||||
)
|
||||
assert.equal(translations.length, 1)
|
||||
const translation = translations[0]
|
||||
assert.ok(translation && translation.kind !== "credential-invalidation")
|
||||
assert.equal(translation.httpStatus, 400)
|
||||
assert.match(translation.responseBodyExcerpt ?? "", /invalid_code/)
|
||||
assert.doesNotMatch(translation.responseBodyExcerpt ?? "", /must-not-be-diagnosed/)
|
||||
assert.doesNotMatch(translation.responseBodyExcerpt ?? "", /weird_field|xoxp-arbitrary/)
|
||||
})
|
||||
|
||||
it("normalizes a non-Bearer token_type while preserving other token fields", async () => {
|
||||
const compatibleFetch = createEnterpriseMcpTokenResponseCompat({
|
||||
fetch: async () => Response.json({
|
||||
ok: true,
|
||||
access_token: "xoxp-secret-token",
|
||||
token_type: "user",
|
||||
scope: "search:read",
|
||||
}),
|
||||
})
|
||||
|
||||
const response = await compatibleFetch("https://provider.example.test/token", tokenRequestInit())
|
||||
const body = tokenResponseSchema.parse(await response.json())
|
||||
assert.equal(body.token_type, "Bearer")
|
||||
assert.equal(body.access_token, "xoxp-secret-token")
|
||||
assert.equal(body.scope, "search:read")
|
||||
})
|
||||
|
||||
it("passes standard token responses through as the original Response", async () => {
|
||||
const original = Response.json({ access_token: "standard-token", token_type: "Bearer" })
|
||||
const compatibleFetch = createEnterpriseMcpTokenResponseCompat({ fetch: async () => original })
|
||||
|
||||
assert.strictEqual(
|
||||
await compatibleFetch("https://provider.example.test/token", tokenRequestInit()),
|
||||
original,
|
||||
)
|
||||
})
|
||||
|
||||
it("negotiates the stateless protocol without initialize or a session id", async () => {
|
||||
const methods: string[] = []
|
||||
const headers: Headers[] = []
|
||||
const events: EnterpriseMcpDiagnosticEvent[] = []
|
||||
const client = createEnterpriseMcpClient({
|
||||
diagnosticSink: (event) => events.push(event),
|
||||
fetch: async (_url, init) => {
|
||||
headers.push(new Headers(init?.headers))
|
||||
const body = init?.body
|
||||
assert.equal(typeof body, "string")
|
||||
const parsed: unknown = JSON.parse(body as string)
|
||||
const request = discoverRequestSchema.parse(parsed)
|
||||
methods.push(request.method)
|
||||
return Response.json({
|
||||
jsonrpc: "2.0" as const,
|
||||
id: request.id,
|
||||
result: {
|
||||
supportedVersions: ["2026-07-28"],
|
||||
capabilities: {},
|
||||
_meta: {
|
||||
"io.modelcontextprotocol/serverInfo": { name: "stateless-test", version: "1.0.0" },
|
||||
},
|
||||
},
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
assert.deepEqual(await client.connect({
|
||||
connection: noAuthConnection(),
|
||||
redirectUri: "https://den.example.test/callback",
|
||||
}), { status: "connected" })
|
||||
assert.deepEqual(methods, ["server/discover"])
|
||||
assert.equal(headers[0]?.get("mcp-protocol-version"), "2026-07-28")
|
||||
assert.equal(headers[0]?.get("mcp-method"), "server/discover")
|
||||
assert.equal(headers[0]?.has("mcp-session-id"), false)
|
||||
assert.equal(events.filter((event) => event.kind !== "credential-invalidation"
|
||||
&& event.protocolEra === "modern"
|
||||
&& event.protocolVersion === "2026-07-28").length, 1)
|
||||
})
|
||||
|
||||
for (const status of [401, 500]) {
|
||||
it(`does not downgrade the discovery probe after HTTP ${status}`, async () => {
|
||||
let protocolRequests = 0
|
||||
const client = createEnterpriseMcpClient({
|
||||
fetch: async (_url, init) => {
|
||||
if (typeof init?.body === "string") protocolRequests += 1
|
||||
return Response.json({ error: "provider_rejected" }, { status })
|
||||
},
|
||||
})
|
||||
|
||||
await assert.rejects(client.connect({
|
||||
connection: noAuthConnection(),
|
||||
redirectUri: "https://den.example.test/callback",
|
||||
}))
|
||||
assert.equal(protocolRequests, 1)
|
||||
})
|
||||
}
|
||||
|
||||
it("captures a bounded MCP error body excerpt with sensitive values redacted", async () => {
|
||||
const events: EnterpriseMcpDiagnosticEvent[] = []
|
||||
const originalBody = {
|
||||
error: "provider_rejected",
|
||||
access_token: "must-not-appear",
|
||||
authorization_code: "also-secret",
|
||||
}
|
||||
const observer = createEnterpriseMcpRequestObserver({
|
||||
connectionId: "slack-compat-connection",
|
||||
operationPhase: "connection-handshake",
|
||||
fetch: async () => Response.json(originalBody, { status: 400 }),
|
||||
diagnosticSink: (event) => events.push(event),
|
||||
signal: new AbortController().signal,
|
||||
clock: { now: () => Date.now() },
|
||||
})
|
||||
|
||||
const response = await observer.fetch("https://mcp.example.test/mcp", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "initialize", params: {} }),
|
||||
})
|
||||
assert.deepEqual(await response.json(), originalBody)
|
||||
const failed = events.find((event) => event.kind === "request" && event.outcome === "failed")
|
||||
assert.ok(failed && failed.kind !== "credential-invalidation")
|
||||
assert.match(failed.responseBodyExcerpt ?? "", /\[redacted\]/)
|
||||
assert.doesNotMatch(failed.responseBodyExcerpt ?? "", /must-not-appear|also-secret/)
|
||||
assert.ok((failed.responseBodyExcerpt?.length ?? 0) <= 2_000)
|
||||
})
|
||||
|
||||
it("redacts credential content inside non-sensitive fields and raw text", () => {
|
||||
const errorDescription = [
|
||||
SAFE_SLACK_ERROR,
|
||||
`authorization_code=${AUTHORIZATION_CODE}`,
|
||||
JWT,
|
||||
SLACK_REFRESH_TOKEN,
|
||||
BEARER_TOKEN,
|
||||
GITHUB_TOKEN,
|
||||
OPAQUE_TOKEN,
|
||||
].join("; ")
|
||||
const excerpt = redactedResponseBodyExcerpt(JSON.stringify({
|
||||
error_description: errorDescription,
|
||||
access_token: "key-based-secret",
|
||||
}))
|
||||
|
||||
assert.match(excerpt, new RegExp(SAFE_SLACK_ERROR))
|
||||
assert.match(excerpt, /authorization_code=\[redacted\]/)
|
||||
assert.match(excerpt, /"access_token":"\[redacted\]"/)
|
||||
for (const secret of [
|
||||
AUTHORIZATION_CODE,
|
||||
JWT,
|
||||
SLACK_REFRESH_TOKEN,
|
||||
"abcdefghijklmnopqrstuvwx",
|
||||
GITHUB_TOKEN,
|
||||
OPAQUE_TOKEN,
|
||||
"key-based-secret",
|
||||
]) {
|
||||
assert.doesNotMatch(excerpt, new RegExp(secret))
|
||||
}
|
||||
assert.ok(excerpt.length <= 2_000)
|
||||
|
||||
const rawExcerpt = redactedResponseBodyExcerpt(
|
||||
`not-json ${SAFE_SLACK_ERROR}; authorization_code=${AUTHORIZATION_CODE}; ${JWT}; ${SLACK_REFRESH_TOKEN}`,
|
||||
)
|
||||
assert.match(rawExcerpt, new RegExp(SAFE_SLACK_ERROR))
|
||||
assert.match(rawExcerpt, /authorization_code=\[redacted\]/)
|
||||
assert.doesNotMatch(rawExcerpt, new RegExp(`${AUTHORIZATION_CODE}|${JWT}|${SLACK_REFRESH_TOKEN}`))
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,239 @@
|
||||
/**
|
||||
* Runnable demo for the terminal-401 credential guard (legacy flow
|
||||
* enterprise-mcp-terminal-401-guard). Drives the real createEnterpriseMcpClient
|
||||
* through three provider scenarios and prints machine-readable evidence:
|
||||
*
|
||||
* act 1 tool execution gets a transient 401 (no WWW-Authenticate proof)
|
||||
* -> the stored credential must survive
|
||||
* act 2 a background tool-discovery probe gets 401 + invalid_token
|
||||
* -> the stored credential must survive (probes never invalidate)
|
||||
* act 3 tool execution gets 401 + invalid_token proof
|
||||
* -> the credential is invalidated once and a credential-invalidation
|
||||
* diagnostic is emitted
|
||||
*
|
||||
* Output: human-readable act lines plus a final DEMO_RESULT_JSON=<json> line.
|
||||
*/
|
||||
import { z } from "zod"
|
||||
import {
|
||||
createEnterpriseMcpClient,
|
||||
type EnterpriseMcpConnection,
|
||||
type EnterpriseMcpDiagnosticEvent,
|
||||
type EnterpriseMcpFetch,
|
||||
type EnterpriseMcpOAuthAuthorizationHandle,
|
||||
type EnterpriseMcpOAuthClientRegistration,
|
||||
type EnterpriseMcpOAuthCredential,
|
||||
type EnterpriseMcpOAuthPersistence,
|
||||
} from "../src/index.js"
|
||||
import type { OAuthClientInformationMixed, OAuthDiscoveryState, OAuthTokens } from "@modelcontextprotocol/client"
|
||||
|
||||
const rpcRequestSchema = z.object({
|
||||
id: z.union([z.string(), z.number()]).nullish(),
|
||||
method: z.string(),
|
||||
})
|
||||
|
||||
class DemoOAuthPersistence implements EnterpriseMcpOAuthPersistence {
|
||||
registration: EnterpriseMcpOAuthClientRegistration | undefined
|
||||
credential: EnterpriseMcpOAuthCredential | undefined
|
||||
invalidationCount = 0
|
||||
private revision = 0
|
||||
private discoveryState: OAuthDiscoveryState | undefined
|
||||
private readonly authorizationRecords = new Map<
|
||||
string,
|
||||
{ handle: EnterpriseMcpOAuthAuthorizationHandle; codeVerifier: string }
|
||||
>()
|
||||
|
||||
private nextRevision(): string {
|
||||
this.revision += 1
|
||||
return `revision-${this.revision}`
|
||||
}
|
||||
|
||||
readonly clientRegistrations = {
|
||||
load: async () => this.registration,
|
||||
save: async (input: {
|
||||
clientInformation: OAuthClientInformationMixed
|
||||
expiresAt?: number
|
||||
source: "client-metadata" | "dynamic"
|
||||
}) => {
|
||||
if (!this.registration) {
|
||||
this.registration = {
|
||||
clientInformation: input.clientInformation,
|
||||
revision: this.nextRevision(),
|
||||
expiresAt: input.expiresAt,
|
||||
source: input.source,
|
||||
}
|
||||
}
|
||||
return this.registration
|
||||
},
|
||||
invalidate: async () => {
|
||||
this.registration = undefined
|
||||
},
|
||||
}
|
||||
|
||||
readonly authorizations = {
|
||||
begin: async (input: {
|
||||
id: string
|
||||
codeVerifier: string
|
||||
expiresAt: number
|
||||
clientRegistrationRevision?: string
|
||||
}) => {
|
||||
this.authorizationRecords.set(input.id, {
|
||||
handle: {
|
||||
id: input.id,
|
||||
revision: this.nextRevision(),
|
||||
expiresAt: input.expiresAt,
|
||||
clientRegistrationRevision: input.clientRegistrationRevision,
|
||||
},
|
||||
codeVerifier: input.codeVerifier,
|
||||
})
|
||||
},
|
||||
load: async (input: { id: string }) => this.authorizationRecords.get(input.id),
|
||||
invalidate: async (input: { id: string }) => {
|
||||
this.authorizationRecords.delete(input.id)
|
||||
},
|
||||
}
|
||||
|
||||
readonly discovery = {
|
||||
load: async () => this.discoveryState,
|
||||
save: async (input: { state: OAuthDiscoveryState }) => {
|
||||
this.discoveryState = input.state
|
||||
},
|
||||
invalidate: async () => {
|
||||
this.discoveryState = undefined
|
||||
},
|
||||
}
|
||||
|
||||
readonly credentials = {
|
||||
load: async () => this.credential,
|
||||
save: async (input: { tokens: OAuthTokens; expiresAt?: number }) => {
|
||||
this.credential = {
|
||||
tokens: input.tokens,
|
||||
expiresAt: input.expiresAt,
|
||||
revision: this.nextRevision(),
|
||||
}
|
||||
},
|
||||
invalidate: async () => {
|
||||
this.credential = undefined
|
||||
this.invalidationCount += 1
|
||||
},
|
||||
}
|
||||
|
||||
seed(): void {
|
||||
this.registration = {
|
||||
clientInformation: { client_id: "demo-client" },
|
||||
revision: this.nextRevision(),
|
||||
source: "pre-registered",
|
||||
}
|
||||
this.credential = {
|
||||
tokens: { access_token: "demo-access-token", token_type: "Bearer" },
|
||||
revision: this.nextRevision(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function scriptedProviderFetch(input: {
|
||||
rejectMethod: "tools/call" | "tools/list"
|
||||
status: number
|
||||
wwwAuthenticate?: string
|
||||
}): EnterpriseMcpFetch {
|
||||
return async (_url, init) => {
|
||||
const body = typeof init?.body === "string" ? init.body : undefined
|
||||
if (!body) return new Response(null, { status: 202 })
|
||||
const request = rpcRequestSchema.parse(JSON.parse(body))
|
||||
if (request.method === "notifications/initialized") return new Response(null, { status: 202 })
|
||||
if (request.method === "initialize") {
|
||||
return Response.json({
|
||||
jsonrpc: "2.0",
|
||||
id: request.id,
|
||||
result: {
|
||||
protocolVersion: "2025-06-18",
|
||||
capabilities: { tools: {} },
|
||||
serverInfo: { name: "terminal-401-guard-demo", version: "1.0.0" },
|
||||
},
|
||||
})
|
||||
}
|
||||
if (request.method === input.rejectMethod) {
|
||||
const headers = new Headers()
|
||||
if (input.wwwAuthenticate) headers.set("www-authenticate", input.wwwAuthenticate)
|
||||
return new Response(null, { status: input.status, headers })
|
||||
}
|
||||
if (request.method === "tools/list") {
|
||||
return Response.json({
|
||||
jsonrpc: "2.0",
|
||||
id: request.id,
|
||||
result: { tools: [{ name: "demo-tool", inputSchema: { type: "object", properties: {} } }] },
|
||||
})
|
||||
}
|
||||
return new Response(null, { status: 404 })
|
||||
}
|
||||
}
|
||||
|
||||
type ActResult = {
|
||||
act: string
|
||||
operation: "tools/call" | "tools/list"
|
||||
providerResponse: string
|
||||
credentialIntact: boolean
|
||||
invalidationCount: number
|
||||
invalidationDiagnostics: EnterpriseMcpDiagnosticEvent[]
|
||||
}
|
||||
|
||||
async function runAct(input: {
|
||||
act: string
|
||||
operation: "tools/call" | "tools/list"
|
||||
status: number
|
||||
wwwAuthenticate?: string
|
||||
}): Promise<ActResult> {
|
||||
const persistence = new DemoOAuthPersistence()
|
||||
persistence.seed()
|
||||
const events: EnterpriseMcpDiagnosticEvent[] = []
|
||||
const client = createEnterpriseMcpClient({
|
||||
fetch: scriptedProviderFetch({
|
||||
rejectMethod: input.operation,
|
||||
status: input.status,
|
||||
wwwAuthenticate: input.wwwAuthenticate,
|
||||
}),
|
||||
diagnosticSink: (event) => events.push(event),
|
||||
})
|
||||
const connection: EnterpriseMcpConnection = {
|
||||
id: `demo-${input.act}`,
|
||||
serverUrl: "https://mcp.example.test/mcp",
|
||||
authorization: { type: "oauth", persistence },
|
||||
}
|
||||
const operation = input.operation === "tools/call"
|
||||
? client.callTool({ connection, redirectUri: "https://den.example.test/oauth/callback", toolName: "demo-tool", arguments: {} })
|
||||
: client.listTools({ connection, redirectUri: "https://den.example.test/oauth/callback" })
|
||||
const rejected = await operation.then(() => false, () => true)
|
||||
if (!rejected) throw new Error(`act "${input.act}" expected the provider rejection to surface`)
|
||||
return {
|
||||
act: input.act,
|
||||
operation: input.operation,
|
||||
providerResponse: `${input.status}${input.wwwAuthenticate ? ` with WWW-Authenticate: ${input.wwwAuthenticate}` : " without a WWW-Authenticate challenge"}`,
|
||||
credentialIntact: persistence.credential?.tokens.access_token === "demo-access-token",
|
||||
invalidationCount: persistence.invalidationCount,
|
||||
invalidationDiagnostics: events.filter((event) => event.kind === "credential-invalidation"),
|
||||
}
|
||||
}
|
||||
|
||||
const acts: ActResult[] = [
|
||||
await runAct({ act: "transient-401-on-execute", operation: "tools/call", status: 401 }),
|
||||
await runAct({
|
||||
act: "invalid-token-on-discovery-probe",
|
||||
operation: "tools/list",
|
||||
status: 401,
|
||||
wwwAuthenticate: 'Bearer error="invalid_token"',
|
||||
}),
|
||||
await runAct({
|
||||
act: "invalid-token-on-execute",
|
||||
operation: "tools/call",
|
||||
status: 401,
|
||||
wwwAuthenticate: 'Bearer error="invalid_token"',
|
||||
}),
|
||||
]
|
||||
|
||||
for (const act of acts) {
|
||||
console.log(
|
||||
`act=${act.act} operation=${act.operation} provider=${act.providerResponse} `
|
||||
+ `credentialIntact=${act.credentialIntact} invalidations=${act.invalidationCount} `
|
||||
+ `invalidationDiagnostics=${act.invalidationDiagnostics.length}`,
|
||||
)
|
||||
}
|
||||
console.log(`DEMO_RESULT_JSON=${JSON.stringify(acts)}`)
|
||||
Reference in New Issue
Block a user