Frameworks
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="google")async def list_emails(query: str) -> str: """List emails matching a query.""" resp = await alter_app.request( "GET", "https://gmail.googleapis.com/gmail/v1/users/me/messages", provider="google", 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, *, scope: list[str] | None = None, constraints: dict[str, Any] | 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. "google", "github"). Positional. |
scope | list[str] | None | None | Reserved code-level scope declaration. Stored on tool.metadata["alter_scope"]. Not yet forwarded to request(). |
constraints | dict | None | None | Reserved code-level constraints. Stored on tool.metadata["alter_constraints"]. |
name | str | None | None | Tool name. Defaults to the function name. |
description | str | None | None | Tool description. Defaults to the function docstring. |
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_emails]agent = create_react_agent(llm, tools=tools)AlterMCPInterceptor
Section titled “AlterMCPInterceptor”An interceptor for langchain-mcp-adapters that injects the calling user’s JWT as a bearer header on outbound MCP tool calls. The interceptor takes no arguments and is passed to MCPToolkit directly:
from langchain_mcp_adapters import MCPToolkitfrom alter_sdk.langchain import AlterMCPInterceptor
interceptor = AlterMCPInterceptor()
toolkit = MCPToolkit( server="https://mcp.example.com", interceptor=interceptor,)The interceptor reads user_token from the request’s runtime context and forwards it to the MCP server as Authorization: Bearer …. It enforces an SSRF and scheme allowlist before injecting; requests that fail those checks are forwarded WITHOUT the bearer header so the MCP server can surface a clean authentication failure.