An opinionated FastMCP runtime for authenticated MCP and REST services through Finch
Project description
AviaryMCP
AviaryMCP is a release-candidate, opinionated FastMCP runtime for publishing one tool definition through MCP and a generated REST/OpenAPI interface. It extends FastMCP through public APIs rather than maintaining a source fork, so MCP and HTTP share the same registry, validation, middleware, handler, and authorization decision.
from aviary_mcp import AviaryMCP
app = AviaryMCP("calculator")
@app.tool
def add(a: int, b: int) -> int:
"""Add two integers."""
return a + b
app.run(transport="http", host="127.0.0.1", port=8000)
access="local" is the safe default: HTTP may bind only to loopback, and
in-process/stdio calls receive a local principal. An unauthenticated network
service requires explicit access="public". Supplying auth= (or a FastMCP
3.4 token verifier through mcp_auth=) selects private mode.
An authenticated REST face uses normalized principals and operation-centric scopes:
from aviary_mcp import AviaryMCP, Principal, StaticKeyAuth
app = AviaryMCP(
"calculator",
auth=StaticKeyAuth({
# Use a generated, high-entropy secret in production. The provider
# immediately retains only its SHA-256 digest.
"development-secret": Principal(
subject="example-client",
scopes={"tool:call:add"},
),
}),
)
LocalAuth, StaticKeyAuth, FinchAssertionAuth, AnyOf, and AllOf
normalize callers into a Principal. One outer ASGI authentication boundary
protects /mcp, generated REST routes, and application custom routes together.
The generated REST operation calls
invoke_tool, which checks tool:call:<tool-name> (plus any scopes registered
with require_scopes) before entering FastMCP's normal validation, middleware,
and handler path. When application auth is configured, the generated catalog
and OpenAPI routes also require authentication so they do not disclose a
private service's capability schema. Custom transports can obtain identical
policy behavior by supplying their normalized principal to invoke_tool.
The HTTP server exposes:
POST /mcp— FastMCP's streamable HTTP MCP transportGET /api/v1/tools— tool catalogPOST /api/v1/tools/{tool_name}— JSON object arguments and a FastMCPToolResultresponseGET /api/v1/openapi.json— generated OpenAPI 3.1 documentGET /birdz— unauthenticated application liveness
Every non-health HTTP body, including /mcp, is bounded to 1 MiB by default
(max_json_request_bytes=). Generated errors use a stable JSON envelope, and
private OpenAPI documents publish their configured security schemes.
Finch edge identity uses an asymmetric, short-lived caller assertion:
from aviary_mcp import AviaryMCP, FinchAssertionAuth
app = AviaryMCP(
"calculator",
auth=FinchAssertionAuth(tenant="andrew", service="calculator"),
)
The verifier fetches the rotating ES256 public JWKS from
https://jwks.finchmcp.com/.well-known/finch-jwks.json and binds each request
to its audience, method, exact local path and query, raw body digest, expiry,
and one-time assertion ID. Multi-process deployments must inject a shared
atomic replay store; the default replay store is process-local. Finch exposure
rejects local-trust auth trees and can warm the JWKS before registering ready.
The Finch control client can hold a dynamic registration lease against the local Go agent contract:
from aviary_mcp import FinchClient, Registration
client = FinchClient()
async with client.maintain(Registration(
"calculator",
"http://127.0.0.1:8000",
routes=("/mcp", "/api/v1"),
)):
...
The Finch exposure controller supervises the HTTP server, dynamic lease,
first-run approval, readiness, and shutdown together. Most applications use it
through app.run(expose="finch"); create_finch_exposure() is public for async
supervisors that need explicit lifecycle control.
For a Finch-native application, the runtime now owns that lifecycle directly:
from aviary_mcp import AviaryMCP, FinchAssertionAuth
app = AviaryMCP(
"calculator",
access="private",
auth=FinchAssertionAuth(tenant="andrew", service="calculator"),
)
app.run(
expose="finch",
app_path="calculator",
edge_auth="key", # private default; public is a separate approval
enrollment_output="auto", # TTY instructions or one-line container JSON
)
The app registers /mcp, its configured api_base, and /birdz as one exact
allowlist. On first run, device approval is the default: the local Finch agent
generates the proof key and returns only a verification URL, user code, and
machine fingerprint. The SDK prints that safe prompt, waits while the operator
approves the exact service, routes, edge mode, and fingerprint, and resumes when
Finch has atomically saved a service-scoped credential. AviaryMCP never receives
a device secret, CLI/admin token, join ticket, or refresh credential.
edge_auth="key" is private by default. It requires an AviaryMCP app with
FinchAssertionAuth, and the assertion service must match app_path.
edge_auth="public" requires an explicitly access="public", unauthenticated
app plus a separate public-edge confirmation in the browser; the two modes
cannot be mixed. /birdz is process liveness, while /birdz/ready returns 200
only after Finch confirms a live relay.
Try it:
python -m venv .venv
.venv/bin/pip install -e '.[test]'
.venv/bin/python examples/calculator.py
curl http://127.0.0.1:8000/api/v1/tools
curl -X POST http://127.0.0.1:8000/api/v1/tools/add \
-H 'content-type: application/json' -d '{"a": 20, "b": 22}'
Production containers
The repository includes a multi-stage, non-root reference Dockerfile and a
sidecar Compose deployment under examples/docker-compose/.
The default first run is credentialless: start the stack, read the one-line safe
enrollment event from the app logs, open verification_uri_complete, and approve
the displayed manifest. Finch retains the resulting scoped credential in its
private persistent state volume, and AviaryMCP pins the approved tenant into its
assertion verifier before becoming ready. FINCH_TENANT is an optional stricter
account pin, not required configuration. The application receives only a
dedicated-group Unix control socket and never receives Finch credentials.
Dependency artifacts are pinned in uv.lock; release builds can also gate the
complete lock with scripts/dependency-lock-sha256.sh.
Container liveness and readiness are intentionally separate. /birdz shows
that the app process is serving; /birdz/ready stays unavailable through device
approval and relay startup, then becomes healthy only when the Finch relay is
live. Orchestrators should gate traffic and rollout completion on readiness.
See docs/production.md for release gates, container
security boundaries, deployment/rollback steps, and the remaining cloud and
operator decisions.
Release-candidate boundaries
The runtime deliberately uses FastMCP 3.x public APIs and does not vendor or
patch FastMCP. FastMCP child servers can be composed with the public live
mount() API; see docs/composition.md. REST
streaming/background tasks and idempotency remain future work. The generated HTTP operation invokes
FastMCP.call_tool, so FastMCP middleware and input validation are shared rather
than reimplemented.
Current MCP authentication boundary
FastMCP token verifiers passed as mcp_auth= are adapted through their public
verify_token() API and participate in the same outer request boundary as
Aviary providers. This supports direct bearer JWT/OAuth access without a split
REST/MCP configuration. The adapter does not mount a FastMCP OAuth provider's
authorization-server or discovery routes; Finch owns OAuth at the edge for this
release candidate. Hosted standalone OAuth discovery remains a post-pilot item.
FinchAssertionAuth includes a bounded in-process replay cache by default. A
multi-process or horizontally scaled application must provide an atomic shared
replay store before production traffic so a one-time assertion cannot be
accepted by two workers.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file aviary_mcp-0.1.0rc1.tar.gz.
File metadata
- Download URL: aviary_mcp-0.1.0rc1.tar.gz
- Upload date:
- Size: 145.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
efe0e2bf0f5ad29412e9c23c1822e96aa8c29b4fa2e4389441344f460fac324d
|
|
| MD5 |
d9cdc62037d61c4e97050a32da79da08
|
|
| BLAKE2b-256 |
f148e48400ec69b5aac06a2071d389fe443957c8fd87e160981928126cba9eaa
|
File details
Details for the file aviary_mcp-0.1.0rc1-py3-none-any.whl.
File metadata
- Download URL: aviary_mcp-0.1.0rc1-py3-none-any.whl
- Upload date:
- Size: 47.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
96a73fab334f17e7af726d456ddcbfb7a74ef56d44d07de0abcc1f3d9efd2c8b
|
|
| MD5 |
078fc883d42c6cefe52e2edd839adc58
|
|
| BLAKE2b-256 |
04f1d693be8043f7d40b5d130a3d7f9d107ab9aebec52f853eda8e29fe3c3566
|