Native library reference. Extended CLI examples use
jacs-compat. This source reference can include unreleased changes; see the package guide for installation and MCP profiles for available tools.
Framework Adapters
Use adapters when the model already runs inside your Python app and you want provenance at the framework boundary, not a separate MCP server.
Choose The Adapter
| If you need... | API | Start here |
|---|---|---|
| Signed LangChain tool results | jacs_signing_middleware, signed_tool | LangChain / LangGraph section below |
Signed LangGraph ToolNode outputs | jacs_wrap_tool_call, with_jacs_signing | LangChain / LangGraph section below |
| Signed FastAPI responses and verification of signed POST bodies | JacsMiddleware, jacs_route | FastAPI section below |
| Signed Anthropic tool return values | jacs.adapters.anthropic.signed_tool | Anthropic section below |
Install only the extra you need:
pip install jacs[langchain]
pip install jacs[fastapi]
pip install jacs[anthropic]
Optional: jacs[langgraph] (LangGraph ToolNode), jacs[ws] (WebSockets). See pyproject.toml for the full list.
LangChain / LangGraph
This is the smallest JACS path if your model already lives in LangChain.
LangChain middleware
from langchain.agents import create_agent
from jacs.client import JacsClient
from jacs.adapters.langchain import jacs_signing_middleware
client = JacsClient.quickstart(name="langchain-agent", domain="langchain.local")
agent = create_agent(
model="openai:gpt-4o",
tools=[search_tool, calc_tool],
middleware=[jacs_signing_middleware(client=client)],
)
LangGraph ToolNode
from jacs.adapters.langchain import with_jacs_signing
tool_node = with_jacs_signing([search_tool, calc_tool], client=client)
Wrap one tool instead of the whole graph
from jacs.adapters.langchain import signed_tool
signed_search = signed_tool(search_tool, client=client)
The executable example to start from in this repo is archive/native/jacspy/examples/langchain/signing_callback.py.
FastAPI / Starlette
Use this when the trust boundary is an API route instead of an MCP transport.
from fastapi import FastAPI
from jacs.client import JacsClient
from jacs.adapters.fastapi import JacsMiddleware
client = JacsClient.quickstart(name="api-agent", domain="api.local")
app = FastAPI()
app.add_middleware(JacsMiddleware, client=client)
Useful options:
- Signing failures and invalid signed POST bodies fail closed by default
sign_responses=Falseorverify_requests=Falseto narrow the behaviora2a=Trueexposes discovery documents cached at middleware initialization; use the A2A serving guide for renewal-aware routes
Request verification only examines POST JSON objects containing jacsSignature.
Unsigned, empty and non-JSON bodies, and other HTTP methods, continue to the
application even with strict=True. This middleware verifies documents; it
does not authenticate every request or bind the signature to HTTP method, URL
and audience. Enforce required authentication separately.
Legacy compatibility options are explicit and dangerous:
allow_unsigned_output=Truepermits raw output after signing failsallow_unverified_passthrough=Truepermits unverifiable input to continueallow_plain_signature_fallback=Truepermits an attestation request to downgrade to a plain signature, dropping attestation claims
strict=True always disables all three fallbacks. Do not use these options at
a trust boundary unless the caller separately marks the data as unsigned or
unverified.
For auth-style endpoints, replay protection is available:
app.add_middleware(
JacsMiddleware,
client=client,
strict=True,
auth_replay_protection=True,
auth_max_age_seconds=30,
auth_clock_skew_seconds=5,
)
This checks freshness and duplicate signatures only for verified signed POST bodies. Its replay cache is local to the middleware instance; it is lost on restart and is not shared across workers or replicas. It does not make unsigned requests fail closed. See the request authentication and replay guide for a complete authentication boundary.
To sign only one route:
from jacs.adapters.fastapi import jacs_route
@app.get("/signed")
@jacs_route(client=client)
async def signed_endpoint():
return {"ok": True}
Anthropic / Claude SDK
Use the Anthropic adapter when you want signed return values from normal Python tool functions:
from jacs.adapters.anthropic import signed_tool
@signed_tool(client=client)
def get_weather(location: str) -> str:
return f"Weather in {location}: sunny"
When To Use MCP Instead
Choose Python MCP Integration instead of adapters when:
- the model is outside your process and talks over MCP
- you want an MCP tool suite for JACS operations
- you need the same server to be usable by external MCP clients