Skip to main content

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 transport
  • GET /api/v1/tools — tool catalog
  • POST /api/v1/tools/{tool_name} — JSON object arguments and a FastMCP ToolResult response
  • GET /api/v1/openapi.json — generated OpenAPI 3.1 document
  • GET /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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

aviary_mcp-0.1.0rc2.tar.gz (146.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

aviary_mcp-0.1.0rc2-py3-none-any.whl (47.4 kB view details)

Uploaded Python 3

File details

Details for the file aviary_mcp-0.1.0rc2.tar.gz.

File metadata

  • Download URL: aviary_mcp-0.1.0rc2.tar.gz
  • Upload date:
  • Size: 146.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.13

File hashes

Hashes for aviary_mcp-0.1.0rc2.tar.gz
Algorithm Hash digest
SHA256 87fd67a38b769f811437daa44d8a421264f7fdab867252005f15596781d34253
MD5 11549942adac31581397b199df47991f
BLAKE2b-256 196d115680a92d21b4b9e2bccf8e86b13a255b333f7efd809d9868acb24bdb9a

See more details on using hashes here.

Provenance

The following attestation bundles were made for aviary_mcp-0.1.0rc2.tar.gz:

Publisher: release.yml on DigiBugCat/aviary-mcp

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file aviary_mcp-0.1.0rc2-py3-none-any.whl.

File metadata

  • Download URL: aviary_mcp-0.1.0rc2-py3-none-any.whl
  • Upload date:
  • Size: 47.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.13

File hashes

Hashes for aviary_mcp-0.1.0rc2-py3-none-any.whl
Algorithm Hash digest
SHA256 d89b9d164cb2b2a428676f3489c7087a873c79a340fe67232849464fe7cb06f7
MD5 78bd92185a9c201b964ffbe76540c51f
BLAKE2b-256 a86f0883e9bb67b29882bd3f595ef19882ed65036142ef2d75c2fee2190b46d8

See more details on using hashes here.

Provenance

The following attestation bundles were made for aviary_mcp-0.1.0rc2-py3-none-any.whl:

Publisher: release.yml on DigiBugCat/aviary-mcp

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page