Skip to content

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:

Terminal window
pip install 'alter-sdk[langchain]'
from alter_sdk import App
from 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

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]
ParameterTypeDefaultDescription
vaultApp | AgentSDK client instance. Positional.
providerstrOAuth provider id (e.g. "provider-id"). Positional.
labelstr | NoneNoneOptional sibling-grant label used by the Connect-session fallback.
namestr | NoneNoneTool name. Defaults to the function name.
descriptionstr | NoneNoneTool 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:

  • ImportErrorlangchain-core not installed.
  • TypeError — decorated function is not a coroutine, or declares a reserved parameter name (config, callbacks).

The wrapper:

  1. Accepts LangChain’s injected config kwarg via signature injection — the user’s function does NOT need to declare **kwargs or config=.
  2. Extracts run_id (top-level or metadata.run_id) and thread_id (configurable.thread_id).
  3. Sets an ambient audit ContextVar so every vault.request() inside the body picks up {"tool": "<name>", "framework": "langchain", "run_id": ..., "thread_id": ...} as its default context=.
  4. Catches GrantNotFoundError and returns a human-readable string containing a fresh Connect URL.
  5. Catches ScopeReauthRequiredError and returns a re-auth message.
  6. Catches BackendError with ambiguous_grant and 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)

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 MultiServerMCPClient
from 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.

Report an issue with this page

Necessary

Required for sign-in, security, authorization, and remembering your choices.

Always active

Analytics

Helps us understand which product and documentation features are useful.

Performance diagnostics

Uses performance tracing and privacy-masked session replay to diagnose problems.

You can change these choices at any time from Cookie settings.