Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/mcp/server/auth/handlers/revoke.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class RevocationRequest(BaseModel):
token: str
token_type_hint: Literal["access_token", "refresh_token"] | None = None
client_id: str
client_secret: str | None
client_secret: str | None = None


class RevocationErrorResponse(BaseModel):
Expand Down
23 changes: 23 additions & 0 deletions tests/server/mcpserver/auth/test_auth_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -1657,3 +1657,26 @@ async def test_authorize_invalid_scope(
# State should be preserved
assert "state" in query_params
assert query_params["state"][0] == "test_state"


@pytest.mark.anyio
async def test_revoke_public_client_without_client_secret(
test_client: httpx2.AsyncClient, mock_oauth_provider: MockOAuthProvider
):
response = await test_client.post(
"/register",
json={
"redirect_uris": ["https://client.example.com/callback"],
"token_endpoint_auth_method": "none",
},
)
assert response.status_code == 201
client_id = response.json()["client_id"]
mock_oauth_provider.tokens["public_token"] = AccessToken(
token="public_token", client_id=client_id, scopes=["read"], expires_at=int(time.time()) + 3600
)

response = await test_client.post("/revoke", data={"client_id": client_id, "token": "public_token"})

assert response.status_code == 200
assert await mock_oauth_provider.load_access_token("public_token") is None
Loading