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] | |