Skip to content

Conversation

@markandrus
Copy link
Contributor

Assume I setup my MCP server at https://example.com/api/mcp.

I could consider all of https://example.com to be my protected resource, as we currently do in the README.md. In this case, the following resource metadata URL, the returned resource identifier (resource), and the WWW-Authenticate header are correct:

https://example.com/.well-known/oauth-protected-resource
{
  "resource": "https://example.com",
  "authorization_servers": ["https://authorization-server.com"]
}
WWW-Authenticate: Bearer error="invalid_token",
  error_description="No authorization provided",
  resource_metadata="https://example.com/.well-known/oauth-protected-resource"

However, IIUC RFC 9728, I could also consider https://example.com/api/mcp to be the protected resource. In this case, the following resource metadata URL, the returned resource identifier (resource), and the WWW-Authenticate header would be correct:

https://example.com/.well-known/oauth-protected-resource/api/mcp
{
  "resource": "https://example.com/api/mcp",
  "authorization_servers": ["https://authorization-server.com"]
}
WWW-Authenticate: Bearer error="invalid_token",
  error_description="No authorization provided",
  resource_metadata="https://example.com/.well-known/oauth-protected-resource/api/mcp"

In order to support this, we'd call protectedResourceHandler from

app/.well-known/oauth-protected-resource/api/mcp/route.ts

and update the way that the resource identifier (resource) is computed.

Instead of using

new URL(req.url).origin

we could do

req.url.toString().replace("/.well-known/oauth-protected-resource", "")

which should work for both cases:

req.url Resource Identifier
https://example.com/.well-known/oauth-protected-resource https://example.com
https://example.com/.well-known/oauth-protected-resource/api/mcp https://example.com/api/mcp

Also, I kept the variable name resourceUrl; however, I don't think that's semantically correct:

https://github.com/vercel/mcp-adapter/blob/592125c3c567213fdf5e5ca5cc7e7b1a090469e0/src/auth/auth-metadata.ts#L52

A resource server can host multiple protected resources, and so I think the variable would be better named resource and documented as something like this:

 * @param resource - the protected resource identifier this metadata pertains to

If we agree, I could open a follow-up PR to address this.

@markandrus markandrus requested review from allenzhou101 and quuu July 14, 2025 22:14
@allenzhou101
Copy link
Contributor

Makes sense! I think adding the resource or resourceUrl param as optional to the protectedResourceHandler could make more sense though, as it would cover any edge case and still be relatively straightforward to implement. I worry that req.url.toString().replace("/.well-known/oauth-protected-resource", "") won't be that robust?

A resource server can host multiple protected resources, and so I think the variable would be better named resource and documented as something like this:

Fair, but I think that field will ultimately always be a URL. So maybe we can change the docstring and leave the variable?

@allenzhou101
Copy link
Contributor

Could add the optional params resourceUrl and additionalMetadata to the protectedResourceHandler?

@markandrus
Copy link
Contributor Author

markandrus commented Jul 14, 2025

Fair, but I think that field will ultimately always be a URL. So maybe we can change the docstring and leave the variable?

Ah yeah, I think we could just update the doc string. 👍

I think adding the resource or resourceUrl param as optional to the protectedResourceHandler could make more sense though, as it would cover any edge case and still be relatively straightforward to implement.

We could do that; however, I'm worried that makes it easier to use incorrectly. If we have the ability to set resource or resourceUrl, then that creates a new class of bugs that could be avoided. For example, this would be a bug, no?

// app/.well-known/oauth-protected-resource/foo/route.ts

const handler = protectedResourceHandler({
  resource: "/bar", // ← Doesn't match the URL ("/foo").
                    //   Also, need to remember to use
                    //   a relative path.
  authServerUrls: [authServerUrl],
});

For this reason, I think it's better if we can figure out resource automatically.

I worry that req.url.toString().replace("/.well-known/oauth-protected-resource", "") won't be that robust?

I was checking out this section of the RFC:

Protected resources supporting metadata MUST make a JSON document containing metadata as specified in Section 2 available at a URL formed by inserting a well-known URI string into the protected resource's resource identifier between the host component and the path and/or query components, if any. By default, the well-known URI string used is /.well-known/oauth-protected-resource.

It says "between the host component and the path and/or query components". I suppose instead of the replace call on the whole string, I could actually work on the the URL's pathname?

const resourceUrl = new URL(req.url);
resourceUrl.pathname = resourceUrl.pathname
  .replace(/^\/\.well-known\/oauth-protected-resource/, "");

This seems to be the inverse of the RFC specifies to do, and so should be robust enough?

@allenzhou101
Copy link
Contributor

Makes sense, however—in that same section, the RFC also mentions that there can be custom .well-known paths.

Different applications utilizing OAuth protected resources in application-specific ways MAY define and register different well-known URI path suffixes for publishing protected resource metadata used by those applications. For instance, if the Example application uses an OAuth protected resource in an Example-specific way and there are Example-specific metadata values that it needs to publish, then it might register and use the example-protected-resource URI path suffix and publish the metadata document at the URL formed by inserting /.well-known/example-protected-resource between the host and path and/or query components of the protected resource's resource identifier. Alternatively, many such applications will use the default well-known URI string /.well-known/oauth-protected-resource, which is the right choice for general-purpose OAuth protected resources, and not register an application-specific one.

In this case the aforementioned pathname parsing wouldn't work.

@markandrus
Copy link
Contributor Author

Good point. I think we have a couple options here. WDYT?

  1. Don't support non-standard well-known URI path suffixes. vercel/mcp-adapter is about making it easy to build MCP servers. MCP clients aren't going to know how to use non-standard well-known URI path suffixes. Therefore, we don't need to support them in protectedResourceHandler.

  2. Support additional well-known URI path suffixes by relaxing the RegExp. For example,

    const resourceUrl = new URL(req.url);
    resourceUrl.pathname = resourceUrl.pathname
      .replace(/^\/\.well-known\/[^\/]*/, "");

@allenzhou101
Copy link
Contributor

Yeah, there's always the workaround of using the generatePRM function that's used to define the protectedResourceHandler for more custom use cases. Maybe we should export the cors settings as well, but it's pretty easy to write yourself.

@markandrus markandrus force-pushed the feat-protectedResourceHandler-support-more-protected-resource-identifiers branch from bc20e33 to fce6a32 Compare July 16, 2025 07:24
@markandrus markandrus force-pushed the feat-protectedResourceHandler-support-more-protected-resource-identifiers branch from fce6a32 to 31359d2 Compare July 16, 2025 07:25
Copy link
Contributor

@allenzhou101 allenzhou101 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I kinda prefer the readability of this instead of the ternary but up to you:

let resource = resourceUrl.toString();
    if (resourceUrl.pathname === '/') {
      resource = resource.replace(/\/$/, '');
    }

@markandrus markandrus merged commit bf27217 into main Jul 17, 2025
1 check passed
@markandrus markandrus deleted the feat-protectedResourceHandler-support-more-protected-resource-identifiers branch July 17, 2025 10:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants