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.
Install the public release candidate from PyPI:
python -m pip install 'aviary-mcp==0.1.0rc4'
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. New applications choose
one explicit connector and then call ordinary app.run().
Finch.local makes the application own a dedicated, zero-config Finch child:
from aviary_mcp import AviaryMCP, Finch
app = AviaryMCP(
"calculator",
finch=Finch.local(
path="calculator",
binary="/usr/local/bin/finch",
),
)
@app.tool
def add(a: int, b: int) -> int:
return a + b
app.run()
The absolute binary path is deliberately explicit: AviaryMCP does not download
or silently trust a finch found on ambient PATH. It requires Finch 1.6.0 or
newer and validates the stable finch version --json contract before launch.
The child runs only finch aviary serve, which ignores every finch.yml, skips
CLI/admin state, and disables hub-pushed binary updates. A non-secret project ID
and lock live under .aviary/finch; service refresh credentials live in private
per-user state keyed by that project ID, outside Git and Docker build contexts.
Finch.agent connects the application to a separately supervised agent over an
explicit Unix socket:
app = AviaryMCP(
"calculator",
finch=Finch.agent(
path="calculator",
socket="/Users/me/.finch/run/control.sock",
),
)
app.run()
Both modes expose the same application and exact route allowlist: /mcp,
/api/v1, and /birdz. Tool decorators need no Finch-specific route code.
Private key access is the default and automatically installs the Finch caller
assertion verifier. Use edge_auth="public" only for a deliberately public app.
Custom staging/self-hosted hubs must supply matching hub, issuer, and
jwks_url roots; production roots are fixed.
The legacy app.run(expose="finch", ...) surface remains supported for existing
deployments, and create_finch_exposure() remains public for async supervisors
that need explicit lifecycle control.
The equivalent legacy form is:
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.0rc4.tar.gz.
File metadata
- Download URL: aviary_mcp-0.1.0rc4.tar.gz
- Upload date:
- Size: 155.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
79a32fc4c6cfca141f599e47de25418554c321322a4ae69981d0214fc229b801
|
|
| MD5 |
c8d93cd96ef99828969998dcac42a48d
|
|
| BLAKE2b-256 |
004012cad710d047bb99f4e3cbc3da85a7168e6b3b0cb747d33fe8e8b9aacf45
|
Provenance
The following attestation bundles were made for aviary_mcp-0.1.0rc4.tar.gz:
Publisher:
release.yml on DigiBugCat/aviary-mcp
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
aviary_mcp-0.1.0rc4.tar.gz -
Subject digest:
79a32fc4c6cfca141f599e47de25418554c321322a4ae69981d0214fc229b801 - Sigstore transparency entry: 2136716085
- Sigstore integration time:
-
Permalink:
DigiBugCat/aviary-mcp@e9a9bfc58d3ee11d54ea36965dd1ada81f673b3c -
Branch / Tag:
refs/tags/v0.1.0rc4 - Owner: https://github.com/DigiBugCat
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@e9a9bfc58d3ee11d54ea36965dd1ada81f673b3c -
Trigger Event:
push
-
Statement type:
File details
Details for the file aviary_mcp-0.1.0rc4-py3-none-any.whl.
File metadata
- Download URL: aviary_mcp-0.1.0rc4-py3-none-any.whl
- Upload date:
- Size: 54.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f960ef989fb4d74101ef2444aabb5dd824e1d0458685f3ae694fe7d637d76cc8
|
|
| MD5 |
306a93e3b3a022bde933a17258af9b3c
|
|
| BLAKE2b-256 |
2cd4ae80196634f53bf1fbac0771b5e19a500666438b121d67689a6a962767db
|
Provenance
The following attestation bundles were made for aviary_mcp-0.1.0rc4-py3-none-any.whl:
Publisher:
release.yml on DigiBugCat/aviary-mcp
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
aviary_mcp-0.1.0rc4-py3-none-any.whl -
Subject digest:
f960ef989fb4d74101ef2444aabb5dd824e1d0458685f3ae694fe7d637d76cc8 - Sigstore transparency entry: 2136716095
- Sigstore integration time:
-
Permalink:
DigiBugCat/aviary-mcp@e9a9bfc58d3ee11d54ea36965dd1ada81f673b3c -
Branch / Tag:
refs/tags/v0.1.0rc4 - Owner: https://github.com/DigiBugCat
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@e9a9bfc58d3ee11d54ea36965dd1ada81f673b3c -
Trigger Event:
push
-
Statement type: