|
| 1 | +"""Simple MCP Server with GitHub OAuth Authentication.""" |
| 2 | + |
| 3 | +import logging |
| 4 | +import secrets |
| 5 | +import time |
| 6 | +from typing import Any, Literal |
| 7 | + |
| 8 | +import click |
| 9 | +from pydantic import AnyHttpUrl |
| 10 | +from pydantic_settings import BaseSettings, SettingsConfigDict |
| 11 | +from starlette.exceptions import HTTPException |
| 12 | +from starlette.requests import Request |
| 13 | +from starlette.responses import JSONResponse, RedirectResponse, Response |
| 14 | + |
| 15 | +from mcp.server.auth.middleware.auth_context import get_access_token |
| 16 | +from mcp.server.auth.provider import ( |
| 17 | + AccessToken, |
| 18 | + AuthorizationCode, |
| 19 | + AuthorizationParams, |
| 20 | + OAuthAuthorizationServerProvider, |
| 21 | + RefreshToken, |
| 22 | + TokenValidator, |
| 23 | + construct_redirect_uri, |
| 24 | +) |
| 25 | +from mcp.server.auth.settings import AuthSettings, ClientRegistrationOptions |
| 26 | +from mcp.server.fastmcp.server import FastMCP |
| 27 | +from mcp.shared._httpx_utils import create_mcp_http_client |
| 28 | +from mcp.shared.auth import OAuthClientInformationFull, OAuthToken |
| 29 | + |
| 30 | +logger = logging.getLogger(__name__) |
| 31 | + |
| 32 | + |
| 33 | +class ServerSettings(BaseSettings): |
| 34 | + """Settings for the simple GitHub MCP server.""" |
| 35 | + |
| 36 | + model_config = SettingsConfigDict(env_prefix="MCP_GITHUB_") |
| 37 | + |
| 38 | + # Server settings |
| 39 | + host: str = "localhost" |
| 40 | + port: int = 8000 |
| 41 | + server_url: AnyHttpUrl = AnyHttpUrl("http://localhost:8000") |
| 42 | + mcp_scope: str = "user" |
| 43 | + |
| 44 | + def __init__(self, **data): |
| 45 | + """Initialize settings with values from environment variables. |
| 46 | +
|
| 47 | + Note: github_client_id and github_client_secret are required but can be |
| 48 | + loaded automatically from environment variables (MCP_GITHUB_GITHUB_CLIENT_ID |
| 49 | + and MCP_GITHUB_GITHUB_CLIENT_SECRET) and don't need to be passed explicitly. |
| 50 | + """ |
| 51 | + super().__init__(**data) |
| 52 | + |
| 53 | + |
| 54 | +def create_simple_mcp_server(settings: ServerSettings) -> FastMCP: |
| 55 | + """Create a simple FastMCP server with GitHub OAuth.""" |
| 56 | + |
| 57 | + auth_settings = AuthSettings( |
| 58 | + issuer_url=settings.server_url, |
| 59 | + client_registration_options=ClientRegistrationOptions( |
| 60 | + enabled=True, |
| 61 | + valid_scopes=[settings.mcp_scope], |
| 62 | + default_scopes=[settings.mcp_scope], |
| 63 | + ), |
| 64 | + required_scopes=[settings.mcp_scope], |
| 65 | + ) |
| 66 | + |
| 67 | + app = FastMCP( |
| 68 | + name="Simple GitHub MCP Server", |
| 69 | + instructions="A simple MCP server with GitHub OAuth authentication", |
| 70 | + host=settings.host, |
| 71 | + port=settings.port, |
| 72 | + debug=True, |
| 73 | + auth=auth_settings, |
| 74 | + token_validator=TokenValidator(), |
| 75 | + protected_resource_metadata={"resource": "asdasd", "authorization_servers": ["https://auth.devramp.ai"], "scopes_supported": ["user"]} |
| 76 | + ) |
| 77 | + |
| 78 | + @app.tool() |
| 79 | + async def get_user_profile() -> dict[str, Any]: |
| 80 | + """Get the authenticated user's GitHub profile information. |
| 81 | +
|
| 82 | + This is the only tool in our simple example. It requires the 'user' scope. |
| 83 | + """ |
| 84 | + return {"user": "asdasd"} |
| 85 | + |
| 86 | + return app |
| 87 | + |
| 88 | + |
| 89 | +@click.command() |
| 90 | +@click.option("--port", default=8000, help="Port to listen on") |
| 91 | +@click.option("--host", default="localhost", help="Host to bind to") |
| 92 | +@click.option( |
| 93 | + "--transport", |
| 94 | + default="streamable-http", |
| 95 | + type=click.Choice(["sse", "streamable-http"]), |
| 96 | + help="Transport protocol to use ('sse' or 'streamable-http')", |
| 97 | +) |
| 98 | +def main(port: int, host: str, transport: Literal["sse", "streamable-http"]) -> int: |
| 99 | + """Run the simple GitHub MCP server.""" |
| 100 | + logging.basicConfig(level=logging.INFO) |
| 101 | + |
| 102 | + try: |
| 103 | + # No hardcoded credentials - all from environment variables |
| 104 | + settings = ServerSettings(host=host, port=port) |
| 105 | + except ValueError as e: |
| 106 | + logger.error( |
| 107 | + "Failed to load settings. Make sure environment variables are set:" |
| 108 | + ) |
| 109 | + logger.error(" MCP_GITHUB_GITHUB_CLIENT_ID=<your-client-id>") |
| 110 | + logger.error(" MCP_GITHUB_GITHUB_CLIENT_SECRET=<your-client-secret>") |
| 111 | + logger.error(f"Error: {e}") |
| 112 | + return 1 |
| 113 | + |
| 114 | + mcp_server = create_simple_mcp_server(settings) |
| 115 | + logger.info(f"Starting server with {transport} transport") |
| 116 | + mcp_server.run(transport=transport) |
| 117 | + return 0 |
0 commit comments