Skip to main content
Glama
lizarragajorge

MCP character catalog

MCP character catalog

An educational Python MCP server with four bundled character records and two read-only tools. The official MCP SDK serves Streamable HTTP at /mcp; /health is anonymous and returns {"status":"ok"}. There is no model or external catalog.

The hosted app uses Entra-backed OAuth with Dynamic discovery. FastMCP's maintained OAuthProxy supplies registration, browser consent and S256 PKCE; the official SDK still serves the tools. Broker tokens reference encrypted upstream Entra tokens, whose signature, tenant, API audience, lifetime and delegated Catalog.Read scope are also verified. API keys and raw Entra tokens cannot bypass broker authentication.

Project layout

  • catalog/: tools, configuration, authentication and safe JSON logging.

  • infra/, azure.yaml, scripts/deploy.py: Azure deployment.

  • tests/, .github/workflows/checks.yml: regression tests and CI.

  • Editable workshop diagram: agenda and architecture.

Related MCP server: d365-bc-mcp

Develop and test

Use Python 3.14 (tested; supported range 3.12-3.14). No Docker or Azure account is needed for local tests.

python3 -m venv .venv
. .venv/bin/activate
python -m pip install --require-hashes -r requirements.txt -r requirements-dev.txt
pytest -q
ruff check .
ruff format --check .

For an offline development server, use the existing API-key mode:

export MCP_AUTH_MODE=api_key
export MCP_ALLOWED_HOSTS=localhost,127.0.0.1
export MCP_API_KEY="$(python -c 'import secrets; print(secrets.token_urlsafe(32))')"
python -m uvicorn catalog.server:create_app --factory \
  --host 127.0.0.1 --port 8000 --no-access-log

Clients must send that key in X-API-Key; query-string keys are not accepted. Do not paste credentials into chat or recordings. Visiting /mcp in a browser is not an MCP test; the suite exercises actual SDK clients in modern and legacy modes, including tool contracts and failure cases.

Tool

Behavior

list_characters(affiliation?)

Returns {characters, count}. Omitted/null means all four; Rebel Alliance returns Luke and Leia. Unknown affiliation succeeds with an empty list.

get_character(name)

Returns {name, homeworld, affiliation}; a missing character is an MCP NOT_FOUND tool error.

Matching is exact, case-insensitive and trims whitespace. Blank, non-string and over-100-character inputs fail; tools must not guess absent facts.

Deploy or update Azure

Cloud deployment requires approval and incurs charges. Prerequisites: Azure CLI, azd, Bicep, an approved subscription/region and permissions to create resources, run ACR builds and assign roles. Azure CLI and azd must use the same subscription. Local Docker is not required: ACR builds the Linux image remotely.

The approved environment already has two single-tenant Entra registrations: an API exposing delegated Catalog.Read with v2 access tokens, and a confidential upstream client permitted that scope. Their IDs are in private .azure/entra-registration.json. Do not create duplicates or add Graph-wide permissions. For another approved environment, create equivalent registrations.

Keep deployment settings in the existing ignored, mode-0600 .env.broker:

Setting

Value

MCP_AUTH_MODE

broker

MCP_ENTRA_TENANT_ID

Tenant UUID

MCP_ENTRA_AUDIENCE

API application/client UUID

MCP_BROKER_CLIENT_ID

Confidential upstream client UUID

MCP_BROKER_CLIENT_SECRET

Its credential value

MCP_BROKER_SIGNING_KEY

Stable generated ASCII secret, at least 43 characters

MCP_BROKER_ENCRYPTION_KEY

Independent, stable Fernet key

For a new environment, generate the signing key with secrets.token_urlsafe(48) and the encryption key with cryptography.fernet.Fernet.generate_key(), storing the results securely. Do not regenerate keys during normal deployment.

With the virtual environment active and the existing approved azd environment selected:

set -a
. ./.env.broker
set +a
azd provision

This validates configuration, provisions infrastructure, remotely builds the image and deploys it with secure temporary parameters. Use azd provision, not azd deploy, for updates. Never put secrets in azd env set, command arguments, source control or debug logs.

Broker mode creates a separate app (base app name truncated to 25 characters plus -broker), retaining any original direct-Entra app. Use the effective name printed by the hook; azd's base app-name output is not the broker app name:

az containerapp show --resource-group <resource-group> \
  --name <broker-app-name> --query properties.configuration.ingress.fqdn -o tsv

Register https://<broker-fqdn>/auth/callback as an exact Web redirect on the upstream Entra client, preserving other approved callbacks. Set private MCP_URL and MCP_RESOURCE_URL to https://<broker-fqdn>/mcp, and MCP_ALLOWED_HOSTS to its hostname. Bicep derives resource/host, Azure Tables endpoint and managed-identity settings at deployment; private-network broker storage is not accessible from an ordinary local development server.

Connect Copilot Studio

  1. Use an account/environment with Copilot authoring, connection permissions, generative orchestration and tenant data-policy approval.

  2. Open Tools > Add a tool > New tool > Model Context Protocol.

  3. Enter the broker /mcp URL from .env.broker, not the original direct-Entra URL. Select OAuth 2.0 > Dynamic discovery. No client ID or secret is entered.

  4. Create a connection, approve broker consent, then sign in and consent to Catalog.Read in Entra. Tenant policy may require admin approval.

  5. Add to the agent and confirm list_characters and get_character. Test all characters, the two Rebels, and a nonexistent name; inspect actual tool arguments, results and error status rather than just the agent's answer.

The default broker callback allowlist covers https://global.consent.azure-apim.net/redirect and its path descendants. If Copilot supplies a regional callback, add that exact trusted HTTPS URI to MCP_BROKER_REDIRECT_URIS and redeploy. Copilot callbacks go in this allowlist, not on the upstream Entra app. Never allow arbitrary redirects. UI labels and feature availability vary by tenant; see Microsoft's onboarding guide.

Operations and verification

  • Check /health, /.well-known/oauth-protected-resource/mcp and /.well-known/oauth-authorization-server; missing/invalid credentials at /mcp must return 401. Hosted ingress is HTTPS-only.

  • OAuth state is Fernet-encrypted in private-endpoint Azure Tables. Managed identity has AcrPull plus Table Data Contributor on the dedicated table; public storage and shared-key access are disabled. Storage/key-fetch outages return 503, not anonymous access. Expiry checks are not physical retention cleanup; manage retained state explicitly.

  • Broker mode is capped at one replica: shared storage does not make in-process refresh locks distributed. Registration is limited to 16 KiB and 60 attempts/minute/process; this is a workshop, not production abuse protection. Default minimum replicas is zero; use MCP_MIN_REPLICAS=1 when rehearsing if approved, accepting idle charges. ACR, networking, private endpoints/storage, compute and Copilot Studio can incur charges.

  • Preserve signing/encryption keys. Rotate the upstream credential before its 90-day expiry and redeploy. Signing-key rotation requires reconnection; encryption-key rotation needs a state migration or explicitly approved reset.

  • Live discovery, DCR, consent-to-Entra redirects and registration persistence after replica replacement were verified. Tests simulate the delegated exchange. Actual Copilot Studio sign-in, refresh and hosted tool calls remain tenant checks, not proven by local tests. No group, per-record authorization or downstream on-behalf-of flow is implemented; connection identity behavior depends on the Copilot tool/channel configuration.

  • Keep ignored .env, .env.entra, .env.broker and .azure/ private. The first two retain local/previous direct-mode settings, not the current broker setup.

  • Cleanup requires approval: remove the dedicated Copilot connection, then use azd down only for the dedicated workshop environment. Entra registrations, service principals and consent are outside that resource-group cleanup.

Dependencies are declared in pyproject.toml and hash-locked separately for runtime/development. To intentionally refresh, use uv pip compile pyproject.toml --universal --generate-hashes --no-header --no-annotate -o requirements.txt and repeat with --group dev -o requirements-dev.txt; review and rerun checks.

License and attribution

Code, tests and documentation use the Microsoft MIT license. Dependencies retain their own licenses. The Azure Container Apps icon embedded in the editable diagram is from Microsoft's Azure Public Service Icons V24, used unchanged under Microsoft's architectural/training use terms; it is not covered by this project's MIT license. Star Wars names and minimal facts are an independently written educational fixture, not a copied dataset. This project is unofficial and has no affiliation with Microsoft, Disney or Lucasfilm. No character artwork or franchise logos are included.

Related MCP Connectors

Related MCP Servers

  • A
    license
    A
    quality
    C
    maintenance
    Read-only MCP server for Dynamics 365 Business Central, enabling AI clients to query live ERP data like customers, items, orders, and invoices via GET-only APIs with dual authentication and stdio/HTTP transports.
    10
    MIT
  • F
    license
    A
    quality
    C
    maintenance
    Read-only MCP server that exposes Rick and Morty characters, locations, and episodes as tools and resources, with input validation, anti-SSRF protection, rate limiting, and output sanitization.
    7
    -
  • A
    license
    Not graded
    quality
    B
    maintenance
    Exposes a small in-memory directory of ten people over streamable HTTP, letting an AI agent list every entry or look someone up by name or email.
    MIT