Skip to content

OAuth resource indicator from protected resource metadata is normalized with a trailing slash #1968

@huven

Description

@huven

Description

When protected resource metadata contains a pathless resource URI, the TypeScript SDK normalizes it through new URL(...).href, which changes the resource indicator value by adding a trailing slash.

For example, protected resource metadata may contain:

{
  "resource": "https://example.com",
  "authorization_servers": ["https://login.microsoftonline.com/{tenant}/oauth2/v2.0"],
  "scopes_supported": ["https://example.com/mcp:tools"]
}

The SDK then constructs OAuth requests with:

resource=https%3A%2F%2Fexample.com%2F

instead of:

resource=https%3A%2F%2Fexample.com

This changes the resource identifier from the value explicitly published in protected resource metadata.

Impact

This breaks OAuth with providers that require the resource parameter to exactly match the resource implied by the requested scopes. Microsoft Entra ID returns:

AADSTS9010010: The resource parameter provided in the request doesn't match with the requested scopes.

Removing the trailing slash makes the request work.

Expected behavior

If protected resource metadata explicitly provides "resource": "https://example.com", then the SDK should preserve that exact value when adding the OAuth resource parameter to both the authorization request and the token request.

Validation can still parse the value as a URL, but serialization should use the original metadata string rather than URL.href.

Actual behavior

The SDK serializes the resource via URL.href, which changes a pathless URI:

new URL("https://example.com").href
// "https://example.com/"

Likely cause

In the current SDK code, selectResourceURL() returns a URL object:

return new URL(resourceMetadata.resource);

Then the authorization and token request code serializes with .href:

authorizationUrl.searchParams.set("resource", resource.href);
tokenRequestParams.set("resource", resource.href);

This turns "https://example.com" into "https://example.com/".

Suggested fix

Preserve the resource metadata value as a string after validation. For example, return resourceMetadata.resource from selectResourceURL() and serialize with String(resource) rather than resource.href.

This may require changing the internal resource type from URL | undefined to string | URL | undefined, or introducing a small alias for OAuth resource indicators.

Related issue

This appears to be the SDK-level root cause of this Inspector issue:

modelcontextprotocol/inspector#927

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions