keycardai-fastmcp
FastMCP integration for Keycard OAuth: protect FastMCP servers with Keycard authentication and run delegated OAuth 2.0 token exchange (RFC 8693) for downstream APIs.
This is the canonical home for the integration. The old name,
keycardai-mcp-fastmcp, is retired; its final release (0.21.0) remains on
PyPI so existing pins keep resolving.
Installation
pip install keycardai-fastmcp
Quick Start
from fastmcp import FastMCP
from keycardai.fastmcp import AccessContext, AuthProvider
auth_provider = AuthProvider(
zone_id="abc1234",
mcp_server_name="My Server",
mcp_base_url="http://localhost:8000",
)
mcp = FastMCP("My Server", auth=auth_provider.get_remote_auth_provider())
@mcp.tool()
async def call_external_api(
query: str,
access: AccessContext = auth_provider.grant("https://api.example.com"),
):
token = access.access("https://api.example.com").access_token
return f"Results for {query} (token starts with {token[:8]})"
Declaring the grant as a typed parameter default injects the populated
AccessContext per request; the parameter never appears in the tool's input
schema. Exchange failures are recorded on the AccessContext (check
access.has_errors() / access.get_errors()), never raised. Granting
multiple resources is all-or-nothing: if any exchange fails, the context
carries that resource's error and no tokens.
If you lint with flake8-bugbear or Ruff's B008 rule (function call in
argument default), exempt your tool modules: the call-in-default is the
intended spelling here, the same pattern as FastAPI's Depends. In
pyproject.toml:
[tool.ruff.lint.per-file-ignores]
"src/my_server/tools/*.py" = ["B008"]
Migrating from the decorator form
The decorator form (@auth_provider.grant(...) above the tool) still works
from the same object. Reading the result via ctx.get_state("keycardai") is
deprecated and emits a DeprecationWarning; helpers that only hold the
FastMCP Context can use await AccessContext.from_context(ctx) instead.
The warning fires once per tool, at decoration time (module import). If your
test or CI setup escalates warnings to errors (-W error,
filterwarnings = ["error"] in pytest config), importing a server module
that still uses the old form will raise instead of warn. Either migrate the
tools to the injected-parameter form, or allow this warning explicitly:
filterwarnings = ["error", 'default:Tool .* uses the grant decorator:DeprecationWarning']
Configuration from the environment
AuthProvider reads its zone and application credential from the environment
when they are not passed explicitly.
Zone: set KEYCARD_ZONE_URL to the full zone URL. KEYCARD_ZONE_ID and
KEYCARD_BASE_URL still work but emit a DeprecationWarning; migrate as
follows:
| Deprecated | Canonical replacement |
|---|---|
KEYCARD_ZONE_ID="abc1234" |
KEYCARD_ZONE_URL="https://abc1234.keycard.cloud" |
KEYCARD_ZONE_ID="abc1234" + KEYCARD_BASE_URL="https://custom.example.com" |
KEYCARD_ZONE_URL="https://abc1234.custom.example.com" |
Application credential: discovery is delegated to
keycardai.oauth.server.discover_credential. KEYCARD_APPLICATION_CREDENTIAL_TYPE
selects client_secret, workload_identity or web_identity
(eks_workload_identity is accepted as a legacy alias). Without a selector,
exactly one source may be configured: KEYCARD_CLIENT_ID +
KEYCARD_CLIENT_SECRET, an injected workload token file
(AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE, AWS_WEB_IDENTITY_TOKEN_FILE,
AZURE_FEDERATED_TOKEN_FILE or KEYCARD_EKS_WORKLOAD_IDENTITY_TOKEN_FILE), or
KEYCARD_WEB_IDENTITY_KEY_STORAGE_DIR. More than one source with no selector
fails at startup with an ambiguity error naming the selector as the remedy.
Testing
Fake delegated access without patching internals:
from keycardai.fastmcp.testing import mock_access_context
with mock_access_context(access_token="fake_token"):
... # grants resolve to an AccessContext serving fake_token
The bare access_token form serves the token for any resource, so it will
not catch a mistyped resource URL in an access(...) call. Pass
resource_tokens={...} when the test should enforce which resources the
tool reads; resources outside the dict raise ResourceAccessError, matching
production.
Migration from keycardai-mcp-fastmcp
The keycardai-mcp-fastmcp package is retired. Its final release, 0.21.0,
stays on PyPI so existing pins keep resolving: from keycardai.mcp.integrations.fastmcp import AuthProvider emits a
DeprecationWarning pointing here and returns the same class. New code
should depend on keycardai-fastmcp and import from keycardai.fastmcp.
Release files for keycardai-fastmcp 0.7.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| keycardai_fastmcp-0.7.0.tar.gz | 163.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| keycardai_fastmcp-0.7.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 184.7 kB
Release files / keycardai_fastmcp-0.7.0.tar.gz
| Download URL | keycardai_fastmcp-0.7.0.tar.gz |
|---|---|
| Size | 163.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
9af5d3883092a574033179c8659c1330cdcaa7233bac82d4102351fe059c64c4
|
|
BLAKE2b-256 checksum How to use checksums |
91ebfae59b1d050f81ab05bc4f9730494bb1cb598cadbc427f3e7a7799efd057
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.10 {"installer":{"name":"uv","version":"0.12.10","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / keycardai_fastmcp-0.7.0-py3-none-any.whl
| Download URL | keycardai_fastmcp-0.7.0-py3-none-any.whl |
|---|---|
| Size | 21.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
2a8fd7db94ff7b6d75a88d7bda881175d85466b9129d4830db9dba733eae657d
|
|
BLAKE2b-256 checksum How to use checksums |
ff96c488ebd1b45161250f3827b38955d8e4739159d19f89e9f9540028c6855b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.10 {"installer":{"name":"uv","version":"0.12.10","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|