Skip to content
API Reference
Guides
MCP Servers

Build an MCP Server

Create an OAuth-protected MCP server with Keycard

MCP servers expose tools to AI agents — but without authentication, any agent can call any tool with no accountability. Keycard adds OAuth-based authentication to your MCP server so that every tool call is tied to a verified user, scoped to explicit permissions, and logged for audit.

This guide walks through building a protected MCP server from scratch — first a hello world, then adding delegated access to external APIs — with examples in Python, TypeScript, and Go.

  • Python 3.10+, Node.js 18+, or Go 1.21+
  • Keycard account with access to Console
  • Cursor IDE or another MCP-compatible client for testing
Terminal window
pip install keycardai-mcp-fastmcp fastmcp

Your MCP server needs to be registered as a protected Resource in Keycard so that AI agents can authenticate against it.

  1. In Keycard Console, navigate to ResourcesCreate Resource

  2. Configure the resource:

    Resource NameMy MCP Server (Local Dev)
    Resource Identifierhttp://localhost:8000/mcp
    Credential ProviderZone Provider
  3. Click Create

Create a .env file in your project root:

Terminal window
KEYCARD_ZONE_ID=your-zone-id
MCP_SERVER_URL=http://localhost:8000
import os
from fastmcp import FastMCP
from keycardai.mcp.integrations.fastmcp import AuthProvider
auth_provider = AuthProvider(
zone_id=os.getenv("KEYCARD_ZONE_ID", "your-zone-id"),
mcp_server_name="Hello World Server",
mcp_base_url=os.getenv("MCP_SERVER_URL", "http://localhost:8000"),
)
auth = auth_provider.get_remote_auth_provider()
mcp = FastMCP("Hello World Server", auth=auth)
@mcp.tool()
def hello_world(name: str) -> str:
"""Say hello to an authenticated user."""
return f"Hello, {name}! You are authenticated."
def main():
mcp.run(transport="streamable-http")
if __name__ == "__main__":
main()

AuthProvider handles all OAuth metadata and token verification. It connects your server to your Keycard zone so that MCP clients can discover how to authenticate. get_remote_auth_provider() returns the auth configuration that FastMCP needs, and mcp.run() starts the server.

  1. Start the server

    Terminal window
    python -m my_server
  2. Configure your coding agent

    Add the MCP server to your agent’s configuration. Use the URL your server is running on (Python defaults to port 8000, TypeScript to 8080).

    Cursor — add to .cursor/mcp.json:

    {
    "mcpServers": {
    "my-mcp-server": {
    "url": "http://localhost:8000/mcp"
    }
    }
    }

    Claude Code — run in your terminal:

    Terminal window
    claude mcp add --transport http my-mcp-server http://localhost:8000/mcp
  3. Authenticate and test

    1. Restart your coding agent to detect the new MCP server
    2. Connect to the MCP server when prompted
    3. Complete the OAuth flow with Keycard
    4. Test the tool — ask your agent: "Run the hello_world tool with my name"
  4. Verify in Keycard Console

    Check Audit Logs for:

    users:authenticateYou logged in successfully
    users:authorizeYour access was authorized
    credentials:issueAccess token was issued

Your MCP server is now protected — every tool call is tied to a verified user, scoped by policy, and logged for audit.

When deploying your MCP server to production:

  • Update the resource identifier in Keycard Console to your production URL (e.g., https://my-mcp-server.example.com/mcp) — it must use HTTPS
  • Set environment variables securely in your hosting platform (KEYCARD_ZONE_ID or KEYCARD_ZONE_URL, KEYCARD_CLIENT_ID, KEYCARD_CLIENT_SECRET, PORT) — never commit secrets to source control