Integrating fractal
fractal is long-term memory for AI agents. On the hosted service (getfractal.it) an agent reaches its memory two ways: as an MCP server — Claude Desktop, Claude Code, Cursor and any MCP client — or over plain HTTP, from any language.
| You're building with… | Use… |
|---|---|
| Claude / any MCP client | the MCP server, over StreamableHTTP |
| Anything that speaks HTTP | the REST endpoint /api/v1/<tool> |
MCP (Claude Desktop, Claude Code, any MCP client)
The shortest path: create a key in the panel, then
claude mcp add fractal --scope user --transport http https://getfractal.it/mcp \
--header "Authorization: Bearer fr_your_api_key"
--scope user (Claude Code) registers the connection once, for every project you work on afterward — not just the one you happen to be in right now. This is a personal long-term memory, not a per-repo tool.
Without a key: OAuth (ChatGPT, VS Code, Cursor, Antigravity)
Clients that can't send a custom header connect with OAuth 2.1 to the same https://getfractal.it/mcp. In ChatGPT: Create a custom MCP server → URL https://getfractal.it/mcp, authentication OAuth. The client finds everything by itself: /mcp answers 401 with WWW-Authenticate → /.well-known/oauth-protected-resource (RFC 9728) → /.well-known/oauth-authorization-server (RFC 8414). You sign in (Google works too), choose which memory to open (yours or one agent's) and allow it.
One OAuth, two ways for a client to introduce itself (MCP spec 2026-07-28): CIMD — the client_id is the https address of a JSON document with name and redirect_uris (ChatGPT, VS Code) — and DCR (POST /oauth/register, deprecated but kept for the transition: Antigravity). Authorization code with PKCE S256 only. The token is an ordinary fractal key, labelled «<app> (OAuth)» under API keys: revoke it there and the client gets 401 invalid_token and asks again. Keys written by hand keep working.
Making it automatic
Connecting the server only makes the tools available; it does not make an agent use them on its own. For that, add a short instruction to your client's standing prompt (for Claude Code: ~/.claude/CLAUDE.md, loaded into every session) telling it when to recall and when to write — download it or copy it below:
You have an MCP connection to fractal (memory_add, memory_recall, and the rest).
Call memory_recall when a question touches choices, preferences, or facts that may
already be saved from a previous session — not on every message, only when a relevant
fact plausibly exists. Call memory_add when a DURABLE fact emerges (a preference, a
decision and why, a constraint that will still hold in a future conversation) — not
ephemeral state about what you're doing right now.
Without this, you end up asking for recall/storage by hand every time — which works, but defeats the point of a memory that is supposed to be there without being asked.
A Claude Code skill for first-time setup
If you use Claude Code specifically, the block above can become a slash command instead of a paste: save it as ~/.claude/skills/fractal-memory/SKILL.md and Claude Code picks it up on its own — download the skill or copy it below:
---
name: fractal-memory
description: Connect this Claude Code install to a Fractal account and make memory recall/write automatic from then on.
---
# Connect Fractal
1. Ask for the account's API key (from the panel, "Chiavi API" — never type or paste a
password to get one).
2. Register the connection once for every project:
`claude mcp add fractal --scope user --transport http <host>/mcp --header "Authorization: Bearer <key>"`
3. Append this to `~/.claude/CLAUDE.md` (create it if missing) so recall/write become
automatic instead of something the user has to ask for each time:
You have an MCP connection to fractal (memory_add, memory_recall, and the rest).
Call memory_recall when a question touches choices, preferences, or facts that may
already be saved from a previous session — not on every message, only when a relevant
fact plausibly exists. Call memory_add when a DURABLE fact emerges (a preference, a
decision and why, a constraint that will still hold in a future conversation) — not
ephemeral state about what you're doing right now.
4. Confirm with `claude mcp list` — `fractal` should show `✔ Connected`.
This skill is for the setup step itself (a one-time, on-demand workflow) — the actual ongoing recall/write behavior still comes from the CLAUDE.md instruction above, not from the skill running in the background.
On the hosted service fractal speaks MCP over StreamableHTTP — POST /mcp, with Authorization: Bearer <apiKey>.
The toolset (memory_add, memory_recall, memory_nest, memory_open, memory_describe, memory_correct, memory_ingest, …). Any MCP-capable client — Claude, or a LangChain/LangGraph agent via langchain-mcp-adapters / @langchain/mcp-adapters — can consume them.
Raw HTTP / REST
If your framework isn't listed, it almost certainly speaks HTTP. Every tool is a POST:
curl -X POST https://<host>/api/tool/memory_recall \
-H "Authorization: Bearer fr_your_api_key" \
-H "Content-Type: application/json" \
-d '{"query": "what do you remember about the roof?", "k": 5}'
Response is MCP content: { "content": [{ "type": "text", "text": "..." }] } (and { "type": "image", ... } blocks when a node has embedded images). This is exactly what the Python client and the stdio proxy use.
/api/v1 — the same thing with an envelope, and a document that describes it
/api/tool/<name> is unchanged and always will be: it is what the stdio proxy speaks and what is written in configurations that have been running for months. Alongside it there is now a route shaped for a person reading a reference instead of a proxy replaying MCP:
# what can this key do? (generated from the service's own tools, and per-key)
curl -H "Authorization: Bearer fr_your_api_key" https://<host>/api/openapi.json
curl -X POST https://<host>/api/v1/memory_recall \
-H "Authorization: Bearer fr_your_api_key" -H "Content-Type: application/json" \
-d '{"query": "what do you remember about the roof?", "k": 5}'
# → {"ok":true,"tool":"memory_recall","ms":38,"text":"…","content":[…]}
Point any OpenAPI-aware tooling at /api/openapi.json and it generates a client. The document is not a second file that drifts: the service builds its MCP server for your key, asks it for tools/list the way Claude Desktop does, and renders that. An agent whose capabilities were narrowed gets a shorter document, because that is its API.
Status codes are real where the service refuses — 401 no key, 403 capability not granted, 404 no such tool, 400 broken body. When a tool complains about its own arguments you get 200 with the prose inside, because fractal's tools report domain problems in sentences and turning those into codes would mean guessing. npm run check:api pins both halves.
Both routes go through the same handleTool, so there is no second place where permissions can drift apart.
Starting a flow from outside
A flow drawn in the panel (Flussi) can be started by another service — a form, a CRM, a cron job somewhere else. In the editor, under Come parte, create the flow's secret: it is shown once and stored only as a SHA-256 fingerprint. Then:
curl -X POST "https://<host>/hook/flussi/fl_…?attendi=1" \
-H "Authorization: Bearer fwh_the_flow_secret" -H "Content-Type: application/json" \
-d '{"input": "new order 42 from Rossi"}'
# → {"esecuzione":"es_…","stato":"completata","risultato":"…"}
input (or testo) becomes the start text; any other JSON is passed whole, and a plain-text body as it is. Without ?attendi=1 the answer is an immediate 202 with the run's id; with it, the answer waits for the result for up to 25 seconds. A flow that stops on a person's approval answers in_attesa, and the person who drew it gets an email with a link to that run. The secret goes in a header (Authorization: Bearer or X-Fractal-Segreto), never in the address, which ends up in proxy logs. A wrong secret and a flow that does not exist both answer 404. The flow runs as the person who drew it, with their agents and their limit of two runs at a time (429 beyond that).
The same panel section starts a flow on a schedule (every N minutes, at least 15, or on chosen days at a time, Rome time) or when the memory changes (a memory added, corrected or imported, optionally only if its text contains a word).
Agents that run elsewhere — making their traffic visible
Agents created in the panel talk through fractal, so the service sees that traffic and draws it without anyone's cooperation. A fleet running elsewhere — Hermes, three machines, a cluster — touches nothing of ours, so it reports its own traffic: four fields over HTTP, from any language, and every call shows up in the panel's Il filo, on a map of who talked to whom.
Without OTel it's four fields over HTTP, from any language:
curl -X POST https://<host>/api/trace \
-H "Authorization: Bearer $FRACTAL_KEY" -H "Content-Type: application/json" \
-d '{"da":"pianificatore","a":"esecutore","op":"delega","nota":"three steps","dove":"hermes"}'
Three rules worth knowing:
- An agent key speaks only for itself.
dais overwritten with that agent's slug. An
account key may name anything, because it belongs to the person and a person may report on their own fleet.
- Reported is never observed. Rows that arrive here are marked
riferitoand drawn
dashed, distinct from what the service saw first-hand. Sending fonte: "osservato" changes nothing.
- No content. An operation, two names, a short label. The material agents hand each other
never enters the wire, and the exporter reads a closed list of span attributes that excludes gen_ai.input.messages and friends.
GET /api/trace returns the same rows as JSON, scoped to the key's account.