Skip to main content

lane-mcp-auth

Lane sign-in for an MCP server, in Python. The TypeScript distribution is @getonlane/mcp-auth.

What it does for your MCP server

  • Lane sign-in for agents. Lane is your OAuth authorization server. The library verifies each bearer, serves the protected-resource document, and refuses every tool until the agent registers a session with your server.
  • Scopes, guest tools and step-up. A tool names the scopes it needs. A guest calls your public read tools with no Lane account. A missing scope starts a new Lane sign-in.
  • Purchases that a human approves. A priced tool answers with a proposal. The user approves the plan in their Lane wallet. Lane then calls the tool with an execution grant, and the library consumes each grant once.
  • Plan step events. Lane tells your server what happened to each step it sold, so your server fulfils, frees stock or cancels.
  • Telemetry. Lane records sign-ins and tool calls for your server, and on_gate_event gives you each gate decision in your own process.

Install

pip install "lane-mcp-auth[fastmcp]"
python -m lane_mcp_auth setup https://acme.com/mcp --name="Acme shop"

setup pairs your terminal with the Lane console and writes LANE_CLIENT_ID, LANE_CLIENT_SECRET, LANE_VERIFICATION, LANE_RESOURCE and LANE_ISSUER to .env. The [fastmcp] extra adds only the FastMCP adapter.

Example

One gated tool on FastMCP 4, served over HTTP. The store keeps connections in memory, so it is correct for one process only.

import os

from fastmcp import Context, FastMCP
from fastmcp.server.dependencies import get_http_request
from fastmcp.server.middleware import Middleware
from lane_mcp_auth import ConnectionKey, ConnectionRecord, HttpTokenExchanger, LaneMcpAuth
from lane_mcp_auth.fastmcp import CLAIMS_ATTR, enable_lane_auth, requires
from starlette.middleware import Middleware as AsgiMiddleware
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.responses import Response


class MemoryConnections:
    def __init__(self) -> None:
        self.rows: dict[tuple[str, str], ConnectionRecord] = {}

    async def get(self, key: ConnectionKey) -> ConnectionRecord | None:
        return self.rows.get((key.sub, key.client_id))

    async def put(self, key: ConnectionKey, record: ConnectionRecord) -> ConnectionRecord:
        self.rows[(key.sub, key.client_id)] = record
        return record


auth = LaneMcpAuth(
    resource=os.environ["LANE_RESOURCE"],
    client_id=os.environ["LANE_CLIENT_ID"],
    announce_secret=os.environ["LANE_CLIENT_SECRET"],
    verification=os.environ.get("LANE_VERIFICATION"),
    connections=MemoryConnections(),
    exchanger=HttpTokenExchanger(
        client_id=os.environ["LANE_CLIENT_ID"],
        client_secret=os.environ["LANE_CLIENT_SECRET"],
    ),
)

mcp = FastMCP("acme")


class AttachLaneClaims(Middleware):
    async def on_call_tool(self, context, call_next):
        claims = get_http_request().state.lane_claims
        setattr(context.fastmcp_context.request_context, CLAIMS_ATTR, claims)
        return await call_next(context)


class VerifyLaneBearer(BaseHTTPMiddleware):
    async def dispatch(self, request, call_next):
        if request.url.path.startswith("/.well-known/"):
            return await call_next(request)
        header = request.headers.get("authorization", "")
        claims = auth.authenticate(header.removeprefix("Bearer ").strip() or None)
        if claims is None:
            return Response(status_code=401, headers={"WWW-Authenticate": auth.challenge()})
        request.state.lane_claims = claims
        return await call_next(request)


mcp.add_middleware(AttachLaneClaims())
enable_lane_auth(mcp, auth)


@mcp.tool()
@requires("read")
async def order_history(ctx: Context) -> str:
    return "two orders"


for path in filter(None, auth.metadata_paths()):
    @mcp.custom_route(path, methods=["GET"])
    async def lane_document(request) -> Response:
        return Response(auth.protected_resource_document(), media_type="application/json")


app = mcp.http_app(middleware=[AsgiMiddleware(VerifyLaneBearer)])

Serve app with any ASGI server, for example uvicorn server:app. An agent that calls order_history is told to call lane_register_session first. After that call, the tool runs when the connection holds <your host>/read.

Documentation

Start with the quickstart.

Licence

MIT

Metadata

Release files for lane-mcp-auth 1.0.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 1.0.0
File Size Uploaded Provenance Actions
lane_mcp_auth-1.0.0.tar.gz 199.6 kB With provenance Details for lane_mcp_auth-1.0.0.tar.gz

Built distribution (wheel)

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

Total release size: 250.6 kB

Release files / lane_mcp_auth-1.0.0.tar.gz

Download URL lane_mcp_auth-1.0.0.tar.gz
Size 199.6 kB
Tags Source
SHA-256 checksum
How to use checksums
285d35b9c00be17a2729183ef297cd580c996da3a2c38396eba61e6d57f9fcad
BLAKE2b-256 checksum
How to use checksums
8c556fc51a2acdc68641f055b1d10056346d10f4b07eac87c08648c8a3e5f81f
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 Oct 5, 2026.

Transparency log

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

Download URL lane_mcp_auth-1.0.0-py3-none-any.whl
Size 51.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
60d82fb4c1a7711af9be6c5eafeeeb40640613d89a79c69dea5fc33ff7a15efa
BLAKE2b-256 checksum
How to use checksums
cac40b03f85a6da444c367991e1d47736c4e297f7dbf08e9794ebfe1fc03fd66
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 Oct 5, 2026.

Transparency log

Security for lane-mcp-auth 1.0.0

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.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for lane_mcp_auth-1.0.0.tar.gz

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 Oct 5, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

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