-
Notifications
You must be signed in to change notification settings - Fork 1.9k
MCP server separation into Authorization Server (AS) and Resource Server (RS) roles per spec PR #338 #982
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry for reviewing a draft, but I'm not sure if I'll have time to help further here.
"type": "http.response.start", | ||
"status": status_code, | ||
"headers": [ | ||
(b"content-type", b"application/json"), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think it makes sense for MCP to be using the low level ASGI API. I know it's not introduced in this PR, but it makes me sad. 😞
This is missing content-length
btw.
@click.command() | ||
@click.option("--port", default=9000, help="Port to listen on") | ||
@click.option("--host", default="localhost", help="Host to bind to") | ||
def main(port: int, host: str) -> int: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will give a lot of maintenance burden. People will ask a lot of uvicorn's parameters to be added here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The purpose of this file is demonstrate mcp oauth concepts, not to be a production server. Removed the host as it's not needed, but port is useful
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, my bad. All good here.
examples/servers/simple-auth/mcp_simple_auth/github_oauth_provider.py
Outdated
Show resolved
Hide resolved
# Server settings | ||
host: str = "localhost" | ||
port: int = 8000 | ||
server_url: AnyHttpUrl = AnyHttpUrl("http://localhost:8000") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pydantic is appending a trailing slash on AnyHttpUrl
- I think we should stop using AnyHttpUrl
and use plain str
in the whole code source.
Even if you are setting up http://localhost:8000
, the real value will be http://localhost:8000/
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
noted, this should be done outside of this PR, added to follow ups
server_url: AnyHttpUrl = AnyHttpUrl("http://localhost:8000") | ||
github_callback_path: str = "http://localhost:8000/github/callback" | ||
|
||
def __init__(self, **data): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is this for? Why is there no type hints?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Without the __init__
, you get the error "Arguments missing for parameters 'github_client_id', 'github_client_secret'" because Pydantic expects these to be explicitly passed, not understanding they should come from environment variables. One option would be to just make then optional
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's the nature of BaseSettings
. Is the idea that they can either be set programmatically or via env var?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, moved to optional. Using github in the example servers brings too much complexity, like having secrets etc, will remove it in the follow up PR.
@ihrpr Hi! Thanks so much for driving this change, do you have any sense of when this PR might be merged? 🙏 (ballpark estimate) |
src/mcp/server/auth/settings.py
Outdated
@@ -15,9 +15,15 @@ class RevocationOptions(BaseModel): | |||
class AuthSettings(BaseModel): | |||
issuer_url: AnyHttpUrl = Field( | |||
..., | |||
description="URL advertised as OAuth issuer; this should be the URL the server " "is reachable at", | |||
description="Base URL where this server is reachable. For AS: OAuth issuer URL. For RS: Resource server URL.", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
issuer
means something specific in OAuth parlance, so I'd prefer we not overload it here if we can avoid it. (mentioned here for AS metadata, and here for PRM metadata)
I put a commit here that changes this around: be4ddd3
I didn't push directly (still need to test it), but curious for your thoughts on that.
That means we'll always do the PRM endpoint, even if the server is an "all-in-one". (Which to clarify, all-in-one is still supported! but in that case we do want both the PRM and the AS metadata endpoints, the PRM will just point to itself.)
so with that change issuer_url
will always be the URL of the authorization server.
The most unfortunate thing in that change is crafting the "server" URL, in order to put it in the www-authenticate
and as the resource
parameter.
I added a function to do it from host
+ port
, but it feels a little jank. The other alternative would be to pass in a new parameter that's the server URL. lmkwyt
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The other alternative would be to pass in a new parameter that's the server URL. lmkwyt
I think it's cleaner to have a parameter
@ihrpr @Kludex Is it possible to cleanup AuthSettings and the authorization server provider code ... MCP sdk doesn't need to have utils to implement auth server. This is too much unnecessary complexity, and looks very unclean. Can we please just point to existing libraries which implement oauth very well already https://github.com/authlib/authlib for Authorization server |
With the new spec, MCP server acts as RS, so there is no need to support AS in the future, for now leaving it for backwards compatibility, and yes, ideally it should be cleaned up. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pending doc string, otherwise LGTM
Background: modelcontextprotocol/modelcontextprotocol#338
Closes: #921
Implementation:
TokenVerifier
protocol to decouple token validation from OAuth provider logicExample:
Follow up: