Spaces:
Sleeping
Sleeping
| """ | |
| 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, | |
| ), | |
| ) | |
| 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 | |
| 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 | |
| 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] | |