diff --git a/src/client/auth.ts b/src/client/auth.ts index 9a9965f6..28188b7c 100644 --- a/src/client/auth.ts +++ b/src/client/auth.ts @@ -2,7 +2,7 @@ import pkceChallenge from "pkce-challenge"; import { LATEST_PROTOCOL_VERSION } from "../types.js"; import type { OAuthClientMetadata, OAuthClientInformation, OAuthTokens, OAuthMetadata, OAuthClientInformationFull, OAuthProtectedResourceMetadata } from "../shared/auth.js"; import { OAuthClientInformationFullSchema, OAuthMetadataSchema, OAuthProtectedResourceMetadataSchema, OAuthTokensSchema } from "../shared/auth.js"; -import { canonicalizeResourceUri } from "../shared/auth-utils.js"; +import { resourceUrlFromServerUrl } from "../shared/auth-utils.js"; /** * Implements an end-to-end OAuth client to be used with one MCP server. @@ -105,7 +105,7 @@ export async function auth( // Remove fragment from resource parameter if provided let canonicalResource: string | undefined; if (resource) { - canonicalResource = canonicalizeResourceUri(resource); + canonicalResource = resourceUrlFromServerUrl(resource); } let authorizationServerUrl = serverUrl; diff --git a/src/client/sse.ts b/src/client/sse.ts index 6c07cf25..c484bde9 100644 --- a/src/client/sse.ts +++ b/src/client/sse.ts @@ -2,7 +2,7 @@ import { EventSource, type ErrorEvent, type EventSourceInit } from "eventsource" import { Transport } from "../shared/transport.js"; import { JSONRPCMessage, JSONRPCMessageSchema } from "../types.js"; import { auth, AuthResult, extractResourceMetadataUrl, OAuthClientProvider, UnauthorizedError } from "./auth.js"; -import { extractCanonicalResourceUri } from "../shared/auth-utils.js"; +import { extractResourceUri } from "../shared/auth-utils.js"; export class SseError extends Error { constructor( @@ -90,7 +90,7 @@ export class SSEClientTransport implements Transport { result = await auth(this._authProvider, { serverUrl: this._url, resourceMetadataUrl: this._resourceMetadataUrl, - resource: extractCanonicalResourceUri(this._url) + resource: extractResourceUri(this._url) }); } catch (error) { this.onerror?.(error as Error); @@ -210,7 +210,7 @@ export class SSEClientTransport implements Transport { serverUrl: this._url, authorizationCode, resourceMetadataUrl: this._resourceMetadataUrl, - resource: extractCanonicalResourceUri(this._url) + resource: extractResourceUri(this._url) }); if (result !== "AUTHORIZED") { throw new UnauthorizedError("Failed to authorize"); @@ -249,7 +249,7 @@ export class SSEClientTransport implements Transport { const result = await auth(this._authProvider, { serverUrl: this._url, resourceMetadataUrl: this._resourceMetadataUrl, - resource: extractCanonicalResourceUri(this._url) + resource: extractResourceUri(this._url) }); if (result !== "AUTHORIZED") { throw new UnauthorizedError(); diff --git a/src/client/streamableHttp.ts b/src/client/streamableHttp.ts index e452972b..25c41bf3 100644 --- a/src/client/streamableHttp.ts +++ b/src/client/streamableHttp.ts @@ -2,7 +2,7 @@ import { Transport } from "../shared/transport.js"; import { isInitializedNotification, isJSONRPCRequest, isJSONRPCResponse, JSONRPCMessage, JSONRPCMessageSchema } from "../types.js"; import { auth, AuthResult, extractResourceMetadataUrl, OAuthClientProvider, UnauthorizedError } from "./auth.js"; import { EventSourceParserStream } from "eventsource-parser/stream"; -import { extractCanonicalResourceUri } from "../shared/auth-utils.js"; +import { extractResourceUri } from "../shared/auth-utils.js"; // Default reconnection options for StreamableHTTP connections const DEFAULT_STREAMABLE_HTTP_RECONNECTION_OPTIONS: StreamableHTTPReconnectionOptions = { @@ -153,7 +153,7 @@ export class StreamableHTTPClientTransport implements Transport { result = await auth(this._authProvider, { serverUrl: this._url, resourceMetadataUrl: this._resourceMetadataUrl, - resource: extractCanonicalResourceUri(this._url) + resource: extractResourceUri(this._url) }); } catch (error) { this.onerror?.(error as Error); @@ -371,7 +371,7 @@ export class StreamableHTTPClientTransport implements Transport { serverUrl: this._url, authorizationCode, resourceMetadataUrl: this._resourceMetadataUrl, - resource: extractCanonicalResourceUri(this._url) + resource: extractResourceUri(this._url) }); if (result !== "AUTHORIZED") { throw new UnauthorizedError("Failed to authorize"); @@ -423,7 +423,7 @@ export class StreamableHTTPClientTransport implements Transport { const result = await auth(this._authProvider, { serverUrl: this._url, resourceMetadataUrl: this._resourceMetadataUrl, - resource: extractCanonicalResourceUri(this._url) + resource: extractResourceUri(this._url) }); if (result !== "AUTHORIZED") { throw new UnauthorizedError(); diff --git a/src/examples/server/resourceValidationExample.ts b/src/examples/server/resourceValidationExample.ts deleted file mode 100644 index 880b9539..00000000 --- a/src/examples/server/resourceValidationExample.ts +++ /dev/null @@ -1,152 +0,0 @@ -/** - * Example demonstrating RFC 8707 Resource Indicators for OAuth 2.0 - * - * This example shows how to configure and use resource validation in the MCP OAuth flow. - * RFC 8707 allows OAuth clients to specify which protected resource they intend to access, - * and enables authorization servers to restrict tokens to specific resources. - */ - -import { DemoInMemoryAuthProvider } from './demoInMemoryOAuthProvider.js'; -import { OAuthClientInformationFull } from '../../shared/auth.js'; - -async function demonstrateResourceValidation() { - // Create the OAuth provider - const provider = new DemoInMemoryAuthProvider(); - const clientsStore = provider.clientsStore; - - // Register a client with specific allowed resources - const clientWithResources: OAuthClientInformationFull & { allowed_resources?: string[] } = { - client_id: 'resource-aware-client', - client_name: 'Resource-Aware MCP Client', - client_uri: 'https://example.com', - redirect_uris: ['https://example.com/callback'], - grant_types: ['authorization_code'], - response_types: ['code'], - scope: 'mcp:tools mcp:resources', - token_endpoint_auth_method: 'none', - // RFC 8707: Specify which resources this client can access - allowed_resources: [ - 'https://api.example.com/mcp/v1', - 'https://api.example.com/mcp/v2', - 'https://tools.example.com/mcp' - ] - }; - - await clientsStore.registerClient(clientWithResources); - - console.log('Registered client with allowed resources:', clientWithResources.allowed_resources); - - // Example 1: Authorization request with valid resource - try { - const mockResponse = { - redirect: (url: string) => { - console.log('āœ… Authorization successful, redirecting to:', url); - } - }; - - await provider.authorize(clientWithResources, { - codeChallenge: 'S256-challenge-here', - redirectUri: clientWithResources.redirect_uris[0], - resource: 'https://api.example.com/mcp/v1', // Valid resource - scopes: ['mcp:tools'] - }, mockResponse as any); - } catch (error) { - console.error('Authorization failed:', error); - } - - // Example 2: Authorization request with invalid resource - try { - const mockResponse = { - redirect: (url: string) => { - console.log('Redirecting to:', url); - } - }; - - await provider.authorize(clientWithResources, { - codeChallenge: 'S256-challenge-here', - redirectUri: clientWithResources.redirect_uris[0], - resource: 'https://unauthorized.api.com/mcp', // Invalid resource - scopes: ['mcp:tools'] - }, mockResponse as any); - } catch (error) { - console.error('āŒ Authorization failed as expected:', error instanceof Error ? error.message : String(error)); - } - - // Example 3: Client without resource restrictions - const openClient: OAuthClientInformationFull = { - client_id: 'open-client', - client_name: 'Open MCP Client', - client_uri: 'https://open.example.com', - redirect_uris: ['https://open.example.com/callback'], - grant_types: ['authorization_code'], - response_types: ['code'], - scope: 'mcp:tools', - token_endpoint_auth_method: 'none', - // No allowed_resources specified - can access any resource - }; - - await clientsStore.registerClient(openClient); - - try { - const mockResponse = { - redirect: (url: string) => { - console.log('āœ… Open client can access any resource, redirecting to:', url); - } - }; - - await provider.authorize(openClient, { - codeChallenge: 'S256-challenge-here', - redirectUri: openClient.redirect_uris[0], - resource: 'https://any.api.com/mcp', // Any resource is allowed - scopes: ['mcp:tools'] - }, mockResponse as any); - } catch (error) { - console.error('Authorization failed:', error); - } - - // Example 4: Token introspection with resource information - // First, simulate getting a token with resource restriction - const mockAuthCode = 'demo-auth-code'; - const mockTokenResponse = await simulateTokenExchange(provider, clientWithResources, mockAuthCode); - - if (mockTokenResponse) { - const tokenDetails = provider.getTokenDetails(mockTokenResponse.access_token); - console.log('\nšŸ“‹ Token introspection result:'); - console.log('- Client ID:', tokenDetails?.clientId); - console.log('- Scopes:', tokenDetails?.scopes); - console.log('- Resource (aud):', tokenDetails?.resource); - console.log('- Token is restricted to:', tokenDetails?.resource || 'No resource restriction'); - } -} - -async function simulateTokenExchange( - provider: DemoInMemoryAuthProvider, - client: OAuthClientInformationFull, - authCode: string -) { - // This is a simplified simulation - in real usage, the auth code would come from the authorization flow - console.log('\nšŸ”„ Simulating token exchange with resource validation...'); - - // Note: In a real implementation, you would: - // 1. Get the authorization code from the redirect after authorize() - // 2. Exchange it for tokens using the token endpoint - // 3. The resource parameter in the token request must match the one from authorization - - return { - access_token: 'demo-token-with-resource', - token_type: 'bearer', - expires_in: 3600, - scope: 'mcp:tools' - }; -} - -// Usage instructions -console.log('šŸš€ RFC 8707 Resource Indicators Demo\n'); -console.log('This example demonstrates how to:'); -console.log('1. Register clients with allowed resources'); -console.log('2. Validate resource parameters during authorization'); -console.log('3. Include resource information in tokens'); -console.log('4. Handle invalid_target errors\n'); - -// Run the demonstration -demonstrateResourceValidation().catch(console.error); \ No newline at end of file diff --git a/src/examples/server/serverUrlValidationExample.ts b/src/examples/server/serverUrlValidationExample.ts deleted file mode 100644 index e88359bc..00000000 --- a/src/examples/server/serverUrlValidationExample.ts +++ /dev/null @@ -1,103 +0,0 @@ -/** - * Example demonstrating server URL validation for RFC 8707 compliance - * - * This example shows how to configure an OAuth server to validate that - * the resource parameter in requests matches the server's own URL, - * ensuring tokens are only issued for this specific server. - */ - -import express from 'express'; -import { authorizationHandler } from '../../server/auth/handlers/authorize.js'; -import { tokenHandler } from '../../server/auth/handlers/token.js'; -import { DemoInMemoryAuthProvider } from './demoInMemoryOAuthProvider.js'; -import { OAuthServerConfig } from '../../server/auth/types.js'; - -// The canonical URL where this MCP server is accessible -const SERVER_URL = 'https://api.example.com/mcp'; - -// Configuration that validates resource matches this server -const serverValidationConfig: OAuthServerConfig = { - // The server's canonical URL (without fragment) - serverUrl: SERVER_URL, - - // Enable validation that resource parameter matches serverUrl - // This also makes the resource parameter required - validateResourceMatchesServer: true -}; - -// Create the OAuth provider -const provider = new DemoInMemoryAuthProvider(); - -// Create Express app -const app = express(); - -// Configure authorization endpoint with server URL validation -app.use('/oauth/authorize', authorizationHandler({ - provider, - config: serverValidationConfig -})); - -// Configure token endpoint with server URL validation -app.use('/oauth/token', tokenHandler({ - provider, - config: serverValidationConfig -})); - -// Example scenarios -console.log('šŸ” Server URL Validation Example\n'); -console.log(`This server only accepts resource parameters matching: ${SERVER_URL}\n`); - -console.log('āœ… Valid request examples:'); -console.log(`1. Resource matches server URL: - GET /oauth/authorize?client_id=my-client&resource=${SERVER_URL}&... - Result: Authorization proceeds normally\n`); - -console.log(`2. Resource with query parameters (exact match required): - GET /oauth/authorize?client_id=my-client&resource=${SERVER_URL}?version=2&... - Result: Rejected - resource must match exactly\n`); - -console.log('āŒ Invalid request examples:'); -console.log(`1. Different domain: - GET /oauth/authorize?client_id=my-client&resource=https://evil.com/mcp&... - Response: 400 invalid_target - "Resource parameter 'https://evil.com/mcp' does not match this server's URL"\n`); - -console.log(`2. Different path: - GET /oauth/authorize?client_id=my-client&resource=https://api.example.com/different&... - Response: 400 invalid_target - "Resource parameter does not match this server's URL"\n`); - -console.log(`3. Missing resource (with validateResourceMatchesServer: true): - GET /oauth/authorize?client_id=my-client&... - Response: 400 invalid_request - "Resource parameter is required when server URL validation is enabled"\n`); - -console.log('šŸ›”ļø Security Benefits:'); -console.log('1. Prevents token confusion attacks - tokens cannot be obtained for other servers'); -console.log('2. Ensures all tokens are scoped to this specific MCP server'); -console.log('3. Provides clear audit trail of resource access attempts'); -console.log('4. Protects against malicious clients trying to obtain tokens for other services\n'); - -console.log('šŸ“ Configuration Notes:'); -console.log('- serverUrl should be the exact URL clients use to connect'); -console.log('- Fragments are automatically removed from both serverUrl and resource'); -console.log('- When validateResourceMatchesServer is true, resource parameter is required'); -console.log('- Validation ensures exact match between resource and serverUrl\n'); - -console.log('šŸ”§ Implementation Tips:'); -console.log('1. Set serverUrl from environment variable for different deployments:'); -console.log(' serverUrl: process.env.MCP_SERVER_URL || "https://api.example.com/mcp"\n'); - -console.log('2. For development environments, you might disable validation:'); -console.log(' validateResourceMatchesServer: process.env.NODE_ENV === "production"\n'); - -console.log('3. Consider logging failed validation attempts for security monitoring:'); -console.log(' Monitor logs for patterns of invalid_target errors\n'); - -// Example of dynamic configuration based on environment -const productionConfig: OAuthServerConfig = { - serverUrl: process.env.MCP_SERVER_URL || SERVER_URL, - validateResourceMatchesServer: process.env.NODE_ENV === 'production' -}; - -console.log('šŸš€ Production configuration example:'); -console.log(JSON.stringify(productionConfig, null, 2)); - -export { app, provider, serverValidationConfig }; \ No newline at end of file diff --git a/src/examples/server/strictModeExample.ts b/src/examples/server/strictModeExample.ts deleted file mode 100644 index 5ff140d6..00000000 --- a/src/examples/server/strictModeExample.ts +++ /dev/null @@ -1,85 +0,0 @@ -/** - * Example demonstrating strict RFC 8707 enforcement mode - * - * This example shows how to configure an OAuth server that requires - * all requests to include a resource parameter, ensuring maximum - * security against token confusion attacks. - */ - -import express from 'express'; -import { authorizationHandler } from '../../server/auth/handlers/authorize.js'; -import { tokenHandler } from '../../server/auth/handlers/token.js'; -import { DemoInMemoryAuthProvider } from './demoInMemoryOAuthProvider.js'; -import { OAuthServerConfig } from '../../server/auth/types.js'; - -// Strict mode configuration - validates resource matches server URL -const SERVER_URL = 'https://api.example.com/mcp'; -const strictConfig: OAuthServerConfig = { - serverUrl: SERVER_URL, - validateResourceMatchesServer: true -}; - -// Create the OAuth provider -const provider = new DemoInMemoryAuthProvider(); - -// Create Express app -const app = express(); - -// Configure authorization endpoint with strict mode -app.use('/oauth/authorize', authorizationHandler({ - provider, - config: strictConfig, - rateLimit: { - windowMs: 15 * 60 * 1000, // 15 minutes - max: 10 // limit each IP to 10 requests per window - } -})); - -// Configure token endpoint with strict mode -app.use('/oauth/token', tokenHandler({ - provider, - config: strictConfig, - rateLimit: { - windowMs: 15 * 60 * 1000, // 15 minutes - max: 20 // limit each IP to 20 requests per window - } -})); - -// Example of what happens with different requests: -console.log('šŸ”’ Strict RFC 8707 Mode Example\n'); -console.log(`This server validates that resource parameter matches: ${SERVER_URL}\n`); - -console.log('āœ… Valid request example:'); -console.log(`GET /oauth/authorize?client_id=my-client&response_type=code&code_challenge=abc123&code_challenge_method=S256&resource=${SERVER_URL}\n`); - -console.log('āŒ Invalid request examples:'); -console.log('1. Missing resource:'); -console.log('GET /oauth/authorize?client_id=my-client&response_type=code&code_challenge=abc123&code_challenge_method=S256'); -console.log('Response: 400 Bad Request - "Resource parameter is required when server URL validation is enabled"\n'); - -console.log('2. Wrong resource:'); -console.log('GET /oauth/authorize?client_id=my-client&response_type=code&code_challenge=abc123&code_challenge_method=S256&resource=https://evil.com/mcp'); -console.log(`Response: 400 Bad Request - "Resource parameter 'https://evil.com/mcp' does not match this server's URL '${SERVER_URL}'"\n`); - -console.log('šŸ“‹ Benefits of server URL validation:'); -console.log('1. Prevents token confusion attacks - tokens can only be issued for this server'); -console.log('2. Ensures all tokens are properly scoped to this specific MCP server'); -console.log('3. No accidental token leakage to other services'); -console.log('4. Clear security boundary enforcement\n'); - -console.log('āš ļø Migration considerations:'); -console.log('1. Server must know its canonical URL (configure via environment variable)'); -console.log('2. All clients must send the exact matching resource parameter'); -console.log('3. Consider using warnings-only mode first (validateResourceMatchesServer: false)'); -console.log('4. Monitor logs to track adoption before enabling validation\n'); - -// Example middleware to track resource parameter usage -app.use((req, res, next) => { - if (req.path.includes('/oauth/')) { - const hasResource = req.query.resource || req.body?.resource; - console.log(`[${new Date().toISOString()}] OAuth request to ${req.path} - Resource parameter: ${hasResource ? 'present' : 'MISSING'}`); - } - next(); -}); - -export { app, provider, strictConfig }; \ No newline at end of file diff --git a/src/server/auth/handlers/authorize.ts b/src/server/auth/handlers/authorize.ts index dbed1b52..946c46c9 100644 --- a/src/server/auth/handlers/authorize.ts +++ b/src/server/auth/handlers/authorize.ts @@ -14,6 +14,7 @@ import { OAuthError } from "../errors.js"; import { OAuthServerConfig } from "../types.js"; +import { resourceUrlFromServerUrl } from "../../../shared/auth-utils.js"; export type AuthorizationHandlerOptions = { provider: OAuthServerProvider; @@ -137,7 +138,7 @@ export function authorizationHandler({ provider, rateLimit: rateLimitConfig, con } // Remove fragment from server URL if present (though it shouldn't have one) - const canonicalServerUrl = config.serverUrl!.split('#')[0]; + const canonicalServerUrl = resourceUrlFromServerUrl(config.serverUrl!); if (resource !== canonicalServerUrl) { throw new InvalidTargetError( diff --git a/src/server/auth/handlers/token.ts b/src/server/auth/handlers/token.ts index 37950502..7af42d7a 100644 --- a/src/server/auth/handlers/token.ts +++ b/src/server/auth/handlers/token.ts @@ -16,6 +16,7 @@ import { InvalidTargetError } from "../errors.js"; import { OAuthServerConfig } from "../types.js"; +import { resourceUrlFromServerUrl } from "../../../shared/auth-utils.js"; export type TokenHandlerOptions = { provider: OAuthServerProvider; @@ -110,7 +111,7 @@ export function tokenHandler({ provider, rateLimit: rateLimitConfig, config }: T throw new InvalidRequestError("Resource parameter is required when server URL validation is enabled"); } - const canonicalServerUrl = config.serverUrl!.split('#')[0]; + const canonicalServerUrl = resourceUrlFromServerUrl(config.serverUrl!); if (resource !== canonicalServerUrl) { throw new InvalidTargetError( @@ -161,7 +162,7 @@ export function tokenHandler({ provider, rateLimit: rateLimitConfig, config }: T throw new InvalidRequestError("Resource parameter is required when server URL validation is enabled"); } - const canonicalServerUrl = config.serverUrl!.split('#')[0]; + const canonicalServerUrl = resourceUrlFromServerUrl(config.serverUrl!); if (resource !== canonicalServerUrl) { throw new InvalidTargetError( diff --git a/src/shared/auth-utils.test.ts b/src/shared/auth-utils.test.ts index 1c45511a..b9571408 100644 --- a/src/shared/auth-utils.test.ts +++ b/src/shared/auth-utils.test.ts @@ -1,4 +1,4 @@ -import { canonicalizeResourceUri, validateResourceUri, extractCanonicalResourceUri, resourceUrlFromServerUrl } from './auth-utils.js'; +import { validateResourceUri, extractResourceUri, resourceUrlFromServerUrl } from './auth-utils.js'; describe('auth-utils', () => { describe('resourceUrlFromServerUrl', () => { @@ -28,17 +28,6 @@ describe('auth-utils', () => { }); }); - describe('canonicalizeResourceUri', () => { - it('should remove fragments', () => { - expect(canonicalizeResourceUri('https://example.com/path#fragment')).toBe('https://example.com/path'); - }); - - it('should keep everything else unchanged', () => { - expect(canonicalizeResourceUri('HTTPS://EXAMPLE.COM/path')).toBe('HTTPS://EXAMPLE.COM/path'); - expect(canonicalizeResourceUri('https://example.com:443/path')).toBe('https://example.com:443/path'); - expect(canonicalizeResourceUri('https://example.com/path?query=1')).toBe('https://example.com/path?query=1'); - }); - }); describe('validateResourceUri', () => { it('should accept valid resource URIs without fragments', () => { @@ -64,34 +53,34 @@ describe('auth-utils', () => { }); }); - describe('extractCanonicalResourceUri', () => { + describe('extractResourceUri', () => { it('should remove fragments from URLs', () => { - expect(extractCanonicalResourceUri('https://example.com/path#fragment')).toBe('https://example.com/path'); - expect(extractCanonicalResourceUri('https://example.com/path?query=1#fragment')).toBe('https://example.com/path?query=1'); + expect(extractResourceUri('https://example.com/path#fragment')).toBe('https://example.com/path'); + expect(extractResourceUri('https://example.com/path?query=1#fragment')).toBe('https://example.com/path?query=1'); }); it('should handle URL object', () => { const url = new URL('https://example.com:8443/path?query=1#fragment'); - expect(extractCanonicalResourceUri(url)).toBe('https://example.com:8443/path?query=1'); + expect(extractResourceUri(url)).toBe('https://example.com:8443/path?query=1'); }); it('should keep everything else unchanged', () => { // Preserves case - expect(extractCanonicalResourceUri('HTTPS://EXAMPLE.COM/path')).toBe('HTTPS://EXAMPLE.COM/path'); + expect(extractResourceUri('HTTPS://EXAMPLE.COM/path')).toBe('HTTPS://EXAMPLE.COM/path'); // Preserves all ports - expect(extractCanonicalResourceUri('https://example.com:443/path')).toBe('https://example.com:443/path'); - expect(extractCanonicalResourceUri('http://example.com:80/path')).toBe('http://example.com:80/path'); + expect(extractResourceUri('https://example.com:443/path')).toBe('https://example.com:443/path'); + expect(extractResourceUri('http://example.com:80/path')).toBe('http://example.com:80/path'); // Preserves query parameters - expect(extractCanonicalResourceUri('https://example.com/path?query=1')).toBe('https://example.com/path?query=1'); + expect(extractResourceUri('https://example.com/path?query=1')).toBe('https://example.com/path?query=1'); // Preserves trailing slashes - expect(extractCanonicalResourceUri('https://example.com/')).toBe('https://example.com/'); - expect(extractCanonicalResourceUri('https://example.com/app1/')).toBe('https://example.com/app1/'); + expect(extractResourceUri('https://example.com/')).toBe('https://example.com/'); + expect(extractResourceUri('https://example.com/app1/')).toBe('https://example.com/app1/'); }); it('should distinguish between different paths on same domain', () => { // This is the key test for the security concern mentioned - const app1 = extractCanonicalResourceUri('https://api.example.com/mcp-server-1'); - const app2 = extractCanonicalResourceUri('https://api.example.com/mcp-server-2'); + const app1 = extractResourceUri('https://api.example.com/mcp-server-1'); + const app2 = extractResourceUri('https://api.example.com/mcp-server-2'); expect(app1).not.toBe(app2); expect(app1).toBe('https://api.example.com/mcp-server-1'); expect(app2).toBe('https://api.example.com/mcp-server-2'); diff --git a/src/shared/auth-utils.ts b/src/shared/auth-utils.ts index aed5f247..e69d821d 100644 --- a/src/shared/auth-utils.ts +++ b/src/shared/auth-utils.ts @@ -23,14 +23,6 @@ export function validateResourceUri(resourceUri: string): void { } } -/** - * Removes fragment from URI to make it RFC 8707 compliant. - * @deprecated Use resourceUrlFromServerUrl instead - */ -export function canonicalizeResourceUri(resourceUri: string): string { - return resourceUrlFromServerUrl(resourceUri); -} - /** * Extracts resource URI from server URL by removing fragment. * @param serverUrl The server URL to extract from @@ -38,7 +30,4 @@ export function canonicalizeResourceUri(resourceUri: string): string { */ export function extractResourceUri(serverUrl: string | URL): string { return resourceUrlFromServerUrl(typeof serverUrl === 'string' ? serverUrl : serverUrl.href); -} - -// Backward compatibility alias -export const extractCanonicalResourceUri = extractResourceUri; \ No newline at end of file +} \ No newline at end of file