Expand description
§mcp-airlock
A local stdio ⇄ Streamable HTTP bridge for the Model Context Protocol. It lets AI clients that launch local MCP servers talk to remote, OAuth-protected MCP servers, with FAPI 2.0 security: Pushed Authorization Requests, PKCE and DPoP-bound tokens.
Most people use the mcp-airlock binary (see the
README and the
setup guide).
This crate exposes the same machinery as a library.
§How it works
- Each JSON-RPC message read from stdin is POSTed to the MCP server, with
the headers of its protocol era. Modern (2026-07-28) messages carry
MCP-Protocol-Version,Mcp-Method,Mcp-NameandMcp-Param-*; legacy (initialize-based) ones carry the negotiated version and the session id. - A
401or403 insufficient_scopeactivates the airlock: requests pause while the token is refreshed or, if needed, a browser login runs: RFC 9728 → RFC 8414 discovery, PAR with PKCE anddpop_jkt,issvalidation, and a DPoP-bound code exchange. - The request is retried. The response (JSON, or each event of an SSE stream) is written to stdout. Failures become JSON-RPC errors, never silence.
§Modules
proxy: theProxyengine (Streamable HTTP, airlock, DPoP, refresh).auth:AuthManager, the OAuth flows, andOidcConfig.vault: credential storage in the OS keychain or in memory.crypto: DPoP keys and proofs.config: CLI flags and environment variables.logging: the private log directory.templates: the default login result pages.
§Running the bridge
use mcp_airlock::{config::Config, run_with_vault, vault::Vault};
let config = <Config as clap::Parser>::try_parse_from([
"mcp-airlock",
"--remote-mcp-url",
"https://mcp.example.com/mcp",
])?;
// `run` picks the OS keychain; here credentials stay in memory.
let vault = Vault::in_memory("example");
run_with_vault(config, vault, tokio::io::stdin(), tokio::io::stdout()).await§Sending a single request
use mcp_airlock::auth::OidcConfig;
use mcp_airlock::config::AuthScheme;
use mcp_airlock::proxy::Proxy;
use mcp_airlock::vault::Vault;
use serde_json::json;
let proxy = Proxy::new(
"https://mcp.example.com/mcp",
"default_user",
OidcConfig {
client_id: "mcp-airlock".into(),
redirect_url: "http://127.0.0.1:8082/callback".into(),
..Default::default()
},
Vault::keyring(&mcp_airlock::vault::service_name_for("https://mcp.example.com/mcp")),
"2025-11-25",
AuthScheme::Bearer,
);
let reply = proxy
.call(json!({
"jsonrpc": "2.0",
"id": 1,
"method": "tools/list",
"params": {"_meta": {
"io.modelcontextprotocol/protocolVersion": "2026-07-28",
"io.modelcontextprotocol/clientCapabilities": {}
}}
}))
.await?;
println!("{reply:?}");Modules§
- auth
- OIDC/OAuth2 Authentication Manager
- config
- Configuration
- crypto
- DPoP Cryptographic Primitives
- logging
- Log directory setup
- proxy
- Transparent Layer 7 Bridge (Airlock)
- templates
- Default pages shown in the browser after the login callback. Override them
with
--template-dir;{{ISSUER_NAME}},{{RESOURCE_NAME}}and (failure only){{ERROR_MESSAGE}}are replaced with HTML-escaped values. - vault
- OS-Native Secure Vault
Functions§
- run
- Runs the proxy using the vault backend selected by the environment
(OS keychain unless
MCP_AIRLOCK_USE_MEMORY_VAULTis set). - run_
with_ vault - Runs the proxy with an explicit vault.
- validate_
config - Checks the URL policy before anything is sent over the network.
Type Aliases§
- Result
- Shared result type for the crate.