Skip to main content

lane-mcp-auth

OAuth 2.1 resource server and consent gate for an MCP server behind Lane — the Python distribution of @getonlane/mcp-auth.

Docs — overview · quickstart · the gate · reference

pip install lane-mcp-auth          # core
pip install 'lane-mcp-auth[fastmcp]'   # + the FastMCP helpers

Your server verifies bearer tokens, publishes the discovery document clients need, and refuses every tool until the caller holds a recorded, revocable grant.

Set up with one command where the server runs. It pairs with the Lane console and writes the credential to .env; the secret never passes through a browser:

python -m lane_mcp_auth setup https://acme.example/mcp --name="Acme shop"

Pass client_id and announce_secret and the server announces the resource it serves on its first guarded call, so Lane binds the hostname to the credential.

from lane_mcp_auth import LaneMcpAuth

auth = LaneMcpAuth(
    resource="https://acme.example/mcp",
    connections=connections,   # yours: this is server-side state
    exchanger=exchanger,
)

The three tiers

tier reached by declared
authenticated only — not registrable
any connected caller completing the step-up no scope
a specific authority the step-up granting it scope="…"

Registration is the floor. Omitting a scope means any connected caller, never anyone: a session that has not registered has nothing anyone can revoke, so a tool answering it would be answering something nobody can withdraw.

With FastMCP

from lane_mcp_auth.fastmcp import register_step_up_tool, guarded

register_step_up_tool(mcp, auth)

@mcp.tool()
@guarded(auth, "read_orders", scope="email")
async def read_orders(ctx) -> str:
    return await orders_for(ctx)

Purchases

A tool marked with @price never buys when an agent calls it. It answers with a proposal and a ticket; the agent builds a plan with the five lane_plan_* tools this package registers, and the user approves it in their Lane wallet. Lane then calls the tool once per approved step with an execution grant.

from lane_mcp_auth import HttpProposalRecorder, PlanForwarder, Quote
from lane_mcp_auth.fastmcp import enable_lane_auth, lane, price

enable_lane_auth(
    mcp,
    auth,
    purchase={"host": "courts.example", "recorder": HttpProposalRecorder(base_url=LANE, org_key=ORG_KEY)},
    plans=PlanForwarder(exchanger=exchanger, lane_mcp_url="https://mcp.getonlane.com/mcp"),
)

@mcp.tool()
@price(cents=2500)
async def book_court(ctx, court: str) -> str:
    return await book(court)          # runs only under an execution grant

async def quote_flight(args, claims) -> Quote:
    q = await price_for(args["offer_id"])
    return Quote(amount_cents=q.cents, description=q.summary, expires_at=q.valid_until)

@mcp.tool()
@price(quote=quote_flight)
async def book_flight(ctx, offer_id: str) -> str:
    return await book(offer_id, lane().approved_amount_cents)   # runs only under an execution grant

The docs page charging for a purchase covers reservations, what runs when, and the 7-day plan limit.

Tools that cost less than a cent

Lane bills a tool marked with @subcent on every call, in USD microdollars, from 1 to 9,999. The user approves no single call. Lane reserves the amount before your handler runs, commits it after a good result, and releases it after a failed one.

from lane_mcp_auth import HttpBillingClient, SubcentBilling
from lane_mcp_auth.fastmcp import enable_lane_auth, subcent

billing = SubcentBilling(
    client=HttpBillingClient(base_url=LANE, org_key=ORG_KEY),
    resource="https://tools.example/mcp",
)

enable_lane_auth(mcp, auth, subcent=billing)

@mcp.tool()
@subcent(micros=2500)  # $0.0025 a call
async def search_products(ctx, q: str) -> str:
    """Search the merchant catalog."""
    return await search(q)

The price reaches tools/list as lane/price. Lane hashes what the tool publishes -- the name, the description, the schemas, the Lane tags and the price -- and that hash names the revision Lane bills. Lane bills a call only while it holds that exact revision as active. Lane refuses a changed tool with tool_revision_not_active until it activates the new revision. Every other tool on the server still runs.

Never authorize on the token's scopes

claims.scopes is the token's own claim: empty before the step-up, stale after. Authority lives in the connection and is reachable only through has_scope() / effective_scopes(), because Lane decides it at exchange time and can refuse an exchange it would previously have allowed. A signed claim cannot be withdrawn.

Parity with the TypeScript

Same invariants, same names where Python idiom allows. The TypeScript package is the reference implementation; where the two could drift — scope filtering, the metadata paths, the gate's decision table — see SCOPE.md for what is implemented here and what is not yet.

Licence

MIT

Release files for lane-mcp-auth 0.10.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for lane-mcp-auth 0.10.0
File Size Uploaded
lane_mcp_auth-0.10.0.tar.gz 180.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for lane-mcp-auth 0.10.0
File Interpreter ABI Platform
lane_mcp_auth-0.10.0-py3-none-any.whl Python 3 none any Details

Total release size: 225.0 kB

Release files / lane_mcp_auth-0.10.0.tar.gz

Download URL lane_mcp_auth-0.10.0.tar.gz
Size 180.2 kB
Tags Source
SHA-256 checksum
How to use checksums
b52b48e999de952fe7c2f0cc44b62369c85846dfe68e24ab4bf472e30116ddb2
BLAKE2b-256 checksum
How to use checksums
304f27ddc0ee495b8fb9cf483db0943401d891186a7d189ca3e9402d8b35e068
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 25, 2026.

Transparency log

Release files / lane_mcp_auth-0.10.0-py3-none-any.whl

Download URL lane_mcp_auth-0.10.0-py3-none-any.whl
Size 44.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
951792ec32c9a12b60da5f674841cdc50589b3c3411f33d877226e1de76861cd
BLAKE2b-256 checksum
How to use checksums
9b54ae8eff70fa4973237f535be98aa10507007ed203ae7301b9929f36d649c9
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 25, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.10.0 This release

2 release files

0.9.0

2 release files

0.8.1

2 release files

0.8.0

2 release files

0.7.1

2 release files

0.7.0

2 release files

0.6.1

2 release files

0.6.0

2 release files

0.4.0

2 release files

0.3.1

2 release files

0.3.0

2 release files

0.2.0

2 release files

0.1.7

2 release files

0.1.6

2 release files

0.1.5

2 release files

0.1.4

2 release files

0.1.3

2 release files

0.1.2

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page