""" 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 os import click from pydantic import AnyHttpUrl from pydantic_settings import BaseSettings, SettingsConfigDict from mcp.server.auth.settings import AuthSettings from mcp.server.fastmcp.server import FastMCP from token_verifier import IntrospectionTokenVerifier logger = logging.getLogger(__name__) as_host_name = os.getenv('AS_HOST_NAME', 'localhost') rs_host_name = os.getenv('RS_HOST_NAME', 'localhost') class ResourceServerSettings(BaseSettings): """Settings for the MCP Resource Server.""" model_config = SettingsConfigDict(env_prefix="MCP_RESOURCE_") # Server settings host: str = "0.0.0.0" port: int = 7860 server_url: AnyHttpUrl = AnyHttpUrl(F"https://{rs_host_name}") # Authorization Server settings auth_server_url: AnyHttpUrl = AnyHttpUrl(F"https://{as_host_name}") auth_server_introspection_endpoint: str =F"https://{as_host_name}/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 = True def __init__(self, **data): """Initialize settings with values from environment variables.""" super().__init__(**data) def create_resource_server(settings: ResourceServerSettings) -> FastMCP: """ 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 FastMCP server as a Resource Server app = FastMCP( name="MCP Resource Server", instructions="Resource Server that validates tokens via Authorization Server introspection", host=settings.host, port=settings.port, 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, ), ) @app.tool() async def ls() -> list[str]: """ list cerrent directory. This tool demonstarates the user can get a listing of the current directory """ listing = os.listdir('.') return listing @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=7860, help="Port to listen on") @click.option("--auth-server", default=F"https://{as_host_name}", 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 = rs_host_name server_url = f"https://{host}" #:{port}" settings = ResourceServerSettings( host="0.0.0.0", 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) logger.info("Server stopped") return 0 except Exception: logger.exception("Server error") return 1 if __name__ == "__main__": main() # type: ignore[call-arg]