Reference
LangChain
@alter_tool decorator and AlterMCPInterceptor for LangChain / LangGraph.
The LangChain bridge lets a vault.request() call inside a LangChain tool participate in the agent’s audit trace automatically: run_id and thread_id are read from the RunnableConfig and tagged onto every outbound provider call.
Requires the langchain extra:
pip install 'alter-sdk[langchain]'from alter_sdk import Appfrom alter_sdk.langchain import alter_tool, AlterMCPInterceptor
alter_app = App(api_key="alter_rk_…")
@alter_tool(alter_app, provider="provider-id")async def list_resources(query: str) -> str: """List resources matching a query.""" resp = await alter_app.request( "GET", "https://api.provider.example/v1/resources", provider="provider-id", query_params={"q": query}, ) return resp.text@alter_tool
Section titled “@alter_tool”A decorator that produces a real langchain_core.tools.StructuredTool from an async function. The returned tool can be passed directly to a LangChain agent or LangGraph node.
def alter_tool( vault: App | Agent, provider: str, *, label: str | None = None, name: str | None = None, description: str | None = None,) -> Callable[[F], StructuredTool]| Parameter | Type | Default | Description |
|---|---|---|---|
vault | App | Agent | — | SDK client instance. Positional. |
provider | str | — | OAuth provider id (e.g. "provider-id"). Positional. |
label | str | None | None | Optional sibling-grant label used by the Connect-session fallback. |
name | str | None | None | Tool name. Defaults to the function name. |
description | str | None | None | Tool description. Defaults to the function docstring. |
Use app.with_constraints(...) / agent.with_constraints(...) before passing the SDK client to alter_tool() when a tool needs enforceable scope narrowing or a request rule.
Returns: a decorator that, applied to an async function, returns a StructuredTool.
Raises:
ImportError—langchain-corenot installed.TypeError— decorated function is not a coroutine, or declares a reserved parameter name (config,callbacks).
The wrapper:
- Accepts LangChain’s injected
configkwarg via signature injection — the user’s function does NOT need to declare**kwargsorconfig=. - Extracts
run_id(top-level ormetadata.run_id) andthread_id(configurable.thread_id). - Sets an ambient audit ContextVar so every
vault.request()inside the body picks up{"tool": "<name>", "framework": "langchain", "run_id": ..., "thread_id": ...}as its defaultcontext=. - Catches
GrantNotFoundErrorand returns a human-readable string containing a fresh Connect URL. - Catches
ScopeReauthRequiredErrorand returns a re-auth message. - Catches
BackendErrorwithambiguous_grantand lists the available accounts so the LLM can ask the user which one to use.
from langchain.agents import create_react_agent
tools = [list_resources]agent = create_react_agent(llm, tools=tools)AlterMCPInterceptor
Section titled “AlterMCPInterceptor”An interceptor for langchain-mcp-adapters that injects the calling user’s token as a bearer header on outbound MCP tool calls. The langchain extra installs the adapter and its LangGraph runtime dependency. The interceptor leaves your input untouched and exposes a secured connection copy for MultiServerMCPClient:
from langchain_mcp_adapters.client import MultiServerMCPClientfrom alter_sdk.langchain import AlterMCPInterceptor
connections = { "alter": { "transport": "streamable_http", "url": "https://mcp.example.com/mcp", },}interceptor = AlterMCPInterceptor(connections)
client = MultiServerMCPClient( interceptor.connections, tool_interceptors=[interceptor],)tools = await client.get_tools()Use canonical streamable_http or sse connections with HTTPS URLs. The interceptor protects initial MCP discovery, session setup, and tool calls; unsafe destinations, redirects, DNS failures, unknown server names, and origin changes are rejected before a downstream request is sent. Its DNS validation result is consumed by the socket-pinned transport without a second lookup.
The interceptor treats user_token from the request’s runtime context as an opaque string. A valid value is sent as Authorization: Bearer …. For a missing or invalid value, the interceptor does not add or replace that header, and all existing request headers are preserved.
Before injecting, it requires HTTPS and applies the same special-use network policy as the Alter backend, including private IPv4 destinations encoded as IPv4-compatible, IPv4-mapped, the built-in well-known/local NAT64 prefixes, or 6to4 IPv6 addresses. If your network uses another RFC 6052 Pref64, pass it explicitly:
interceptor = AlterMCPInterceptor( connections, nat64_prefixes=("64:ff9b:2:3400::/56",),)RFC 6052 prefix lengths /32, /40, /48, /56, /64, and /96 are supported. Destination checks apply even when no user token is present.