Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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...APIStart here
Signed LangChain tool resultsjacs_signing_middleware, signed_toolLangChain / LangGraph section below
Signed LangGraph ToolNode outputsjacs_wrap_tool_call, with_jacs_signingLangChain / LangGraph section below
Signed FastAPI responses and verification of signed POST bodiesJacsMiddleware, jacs_routeFastAPI section below
Signed Anthropic tool return valuesjacs.adapters.anthropic.signed_toolAnthropic 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=False or verify_requests=False to narrow the behavior
  • a2a=True exposes 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=True permits raw output after signing fails
  • allow_unverified_passthrough=True permits unverifiable input to continue
  • allow_plain_signature_fallback=True permits 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