chore: import upstream snapshot with attribution
Deploy Docs / deploy-docs (push) Failing after 1s
Conformance Tests / client-conformance (push) Failing after 3s
Conformance Tests / server-conformance (push) Failing after 1s
GitHub Actions Security Analysis / zizmor (push) Failing after 1s
CI / checks (push) Failing after 59m20s
CI / all-green (push) Waiting to run
Deploy Docs / deploy-docs (push) Failing after 1s
Conformance Tests / client-conformance (push) Failing after 3s
Conformance Tests / server-conformance (push) Failing after 1s
GitHub Actions Security Analysis / zizmor (push) Failing after 1s
CI / checks (push) Failing after 59m20s
CI / all-green (push) Waiting to run
This commit is contained in:
@@ -0,0 +1,161 @@
|
||||
"""MCP Resource Server with Token Introspection.
|
||||
|
||||
This server validates tokens via Authorization Server introspection and serves MCP resources.
|
||||
Demonstrates RFC 9728 Protected Resource Metadata for AS/RS separation.
|
||||
|
||||
NOTE: this is a simplified example for demonstration purposes.
|
||||
This is not a production-ready implementation.
|
||||
"""
|
||||
|
||||
import datetime
|
||||
import logging
|
||||
from typing import Any, Literal
|
||||
|
||||
import click
|
||||
from pydantic import AnyHttpUrl
|
||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
|
||||
from mcp.server.auth.settings import AuthSettings
|
||||
from mcp.server.mcpserver.server import MCPServer
|
||||
|
||||
from .token_verifier import IntrospectionTokenVerifier
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ResourceServerSettings(BaseSettings):
|
||||
"""Settings for the MCP Resource Server."""
|
||||
|
||||
model_config = SettingsConfigDict(env_prefix="MCP_RESOURCE_")
|
||||
|
||||
# Server settings
|
||||
host: str = "localhost"
|
||||
port: int = 8001
|
||||
server_url: AnyHttpUrl = AnyHttpUrl("http://localhost:8001/mcp")
|
||||
|
||||
# Authorization Server settings
|
||||
auth_server_url: AnyHttpUrl = AnyHttpUrl("http://localhost:9000")
|
||||
auth_server_introspection_endpoint: str = "http://localhost:9000/introspect"
|
||||
# No user endpoint needed - we get user data from token introspection
|
||||
|
||||
# MCP settings
|
||||
mcp_scope: str = "user"
|
||||
|
||||
# RFC 8707 resource validation
|
||||
oauth_strict: bool = False
|
||||
|
||||
|
||||
def create_resource_server(settings: ResourceServerSettings) -> MCPServer:
|
||||
"""Create MCP Resource Server with token introspection.
|
||||
|
||||
This server:
|
||||
1. Provides protected resource metadata (RFC 9728)
|
||||
2. Validates tokens via Authorization Server introspection
|
||||
3. Serves MCP tools and resources
|
||||
"""
|
||||
# Create token verifier for introspection with RFC 8707 resource validation
|
||||
token_verifier = IntrospectionTokenVerifier(
|
||||
introspection_endpoint=settings.auth_server_introspection_endpoint,
|
||||
server_url=str(settings.server_url),
|
||||
validate_resource=settings.oauth_strict, # Only validate when --oauth-strict is set
|
||||
)
|
||||
|
||||
# Create MCPServer server as a Resource Server
|
||||
app = MCPServer(
|
||||
name="MCP Resource Server",
|
||||
instructions="Resource Server that validates tokens via Authorization Server introspection",
|
||||
debug=True,
|
||||
# Auth configuration for RS mode
|
||||
token_verifier=token_verifier,
|
||||
auth=AuthSettings(
|
||||
issuer_url=settings.auth_server_url,
|
||||
required_scopes=[settings.mcp_scope],
|
||||
resource_server_url=settings.server_url,
|
||||
),
|
||||
)
|
||||
# Store settings for later use in run()
|
||||
app._resource_server_settings = settings # type: ignore[attr-defined]
|
||||
|
||||
@app.tool()
|
||||
async def get_time() -> dict[str, Any]:
|
||||
"""Get the current server time.
|
||||
|
||||
This tool demonstrates that system information can be protected
|
||||
by OAuth authentication. User must be authenticated to access it.
|
||||
"""
|
||||
|
||||
now = datetime.datetime.now()
|
||||
|
||||
return {
|
||||
"current_time": now.isoformat(),
|
||||
"timezone": "UTC", # Simplified for demo
|
||||
"timestamp": now.timestamp(),
|
||||
"formatted": now.strftime("%Y-%m-%d %H:%M:%S"),
|
||||
}
|
||||
|
||||
return app
|
||||
|
||||
|
||||
@click.command()
|
||||
@click.option("--port", default=8001, help="Port to listen on")
|
||||
@click.option("--auth-server", default="http://localhost:9000", help="Authorization Server URL")
|
||||
@click.option(
|
||||
"--transport",
|
||||
default="streamable-http",
|
||||
type=click.Choice(["sse", "streamable-http"]),
|
||||
help="Transport protocol to use ('sse' or 'streamable-http')",
|
||||
)
|
||||
@click.option(
|
||||
"--oauth-strict",
|
||||
is_flag=True,
|
||||
help="Enable RFC 8707 resource validation",
|
||||
)
|
||||
def main(port: int, auth_server: str, transport: Literal["sse", "streamable-http"], oauth_strict: bool) -> int:
|
||||
"""Run the MCP Resource Server.
|
||||
|
||||
This server:
|
||||
- Provides RFC 9728 Protected Resource Metadata
|
||||
- Validates tokens via Authorization Server introspection
|
||||
- Serves MCP tools requiring authentication
|
||||
|
||||
Must be used with a running Authorization Server.
|
||||
"""
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
try:
|
||||
# Parse auth server URL
|
||||
auth_server_url = AnyHttpUrl(auth_server)
|
||||
|
||||
# Create settings
|
||||
host = "localhost"
|
||||
server_url = f"http://{host}:{port}/mcp"
|
||||
settings = ResourceServerSettings(
|
||||
host=host,
|
||||
port=port,
|
||||
server_url=AnyHttpUrl(server_url),
|
||||
auth_server_url=auth_server_url,
|
||||
auth_server_introspection_endpoint=f"{auth_server}/introspect",
|
||||
oauth_strict=oauth_strict,
|
||||
)
|
||||
except ValueError as e:
|
||||
logger.error(f"Configuration error: {e}")
|
||||
logger.error("Make sure to provide a valid Authorization Server URL")
|
||||
return 1
|
||||
|
||||
try:
|
||||
mcp_server = create_resource_server(settings)
|
||||
|
||||
logger.info(f"🚀 MCP Resource Server running on {settings.server_url}")
|
||||
logger.info(f"🔑 Using Authorization Server: {settings.auth_server_url}")
|
||||
|
||||
# Run the server - this should block and keep running
|
||||
mcp_server.run(transport=transport, host=host, port=port)
|
||||
logger.info("Server stopped")
|
||||
return 0
|
||||
except Exception:
|
||||
logger.exception("Server error")
|
||||
return 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main() # type: ignore[call-arg]
|
||||
Reference in New Issue
Block a user