Developer guide
Build an adapter
An ai4all adapter is a small MCP server. Which transport you implement depends on which circle you belong to: Group A adapters run as local child processes over stdio; Group B adapters answer streamable HTTP. Everything else — schema shape, erasure contract, error codes — is identical.
Group A · stdio
The host spawns your adapter as a child process and speaks newline-delimited JSON-RPC over stdin/stdout. No port is opened, no packet leaves the device. Write logs to stderr — anything on stdout that is not a JSON-RPC message corrupts the stream.
Group B · HTTP stream
Your adapter answers HTTP POST with either a JSON response or an SSE stream. Every request must be independently authorised — the host will not maintain a session for you, and any node may send the next call.
1 · Declare your tools
The schema is the contract the pre-execution firewall enforces. Be strict: name every property, type every property, mark what is required, and set additionalProperties: false. A loose schema is a firewall bypass you wrote yourself.
{
"name": "memory_search",
"description": "Search connected memory networks and return ranked spans.",
"inputSchema": {
"type": "object",
"properties": {
"query": { "type": "string", "description": "Natural-language search." },
"scope": { "type": "string", "enum": ["sovereign", "non-sovereign", "both"] },
"limit": { "type": "integer", "description": "Maximum spans to return." }
},
"required": ["query"],
"additionalProperties": false
}
}2a · Group A adapter (stdio)
# Group A — sovereign adapter, spawned as a child process.
# The host writes JSON-RPC to stdin and reads it from stdout.
# Nothing leaves the machine.
import sys, json
TOOLS = [{
"name": "silmari_active_forget",
"description": "Cryptographically erase a memory block on this device.",
"inputSchema": {
"type": "object",
"properties": {"block_id": {"type": "string"}},
"required": ["block_id"],
},
}]
def respond(rid, result=None, error=None):
msg = {"jsonrpc": "2.0", "id": rid}
msg["error" if error else "result"] = error or result
sys.stdout.write(json.dumps(msg) + "\n")
sys.stdout.flush()
for line in sys.stdin:
if not line.strip():
continue
req = json.loads(line)
rid = req.get("id")
if rid is None: # notification: never reply
continue
if req.get("method") == "tools/list":
respond(rid, {"tools": TOOLS})
elif req.get("method") == "tools/call":
args = req.get("params", {}).get("arguments", {})
block = args.get("block_id")
# ... perform the local erasure ...
respond(rid, {"content": [{"type": "text",
"text": f"erased {block}"}]})
else:
respond(rid, error={"code": -32601, "message": "Method not found"})2b · Group B client (streamable HTTP)
// Group B — cloud adapter over streamable HTTP.
// Stateless: every request carries its own credential. No session to resume.
package groupb
import (
"bytes"
"encoding/json"
"net/http"
"time"
)
type Request struct {
JSONRPC string `json:"jsonrpc"`
ID any `json:"id"`
Method string `json:"method"`
Params json.RawMessage `json:"params"`
}
func Call(endpoint, token string, req Request) (*http.Response, error) {
body, err := json.Marshal(req)
if err != nil {
return nil, err
}
r, err := http.NewRequest(http.MethodPost, endpoint, bytes.NewReader(body))
if err != nil {
return nil, err
}
r.Header.Set("Content-Type", "application/json")
r.Header.Set("Accept", "application/json, text/event-stream")
// Self-describing credential — not a sticky session id.
r.Header.Set("Authorization", "Bearer "+token)
client := &http.Client{Timeout: 1500 * time.Millisecond}
return client.Do(r)
}3 · Implement the erasure contract
Every adapter must answer a forget request, and answering it means more than deleting a row. Enumerate what your backend derived from that record — embeddings, summaries, cached spans, graph edges — and remove those too. Then report honestly what you could not reach; a partial erasure that reports success is worse than one that reports failure.
Enumerate
List every derivative your store created from the source record.
Erase
Remove the source and its derivatives in one operation where possible.
Attest
Return what was erased, and what remains outside your reach.
4 · Error codes the host expects
- -32700
- Parse error — the bytes were not valid JSON.
- -32600
- Invalid Request — the envelope is malformed (bad jsonrpc, missing id).
- -32601
- Method not found — the method is unknown to this adapter.
- -32602
- Invalid params — arguments failed schema assertion.
- -32603
- Internal error — your adapter failed for its own reasons.
- -32000
- Blocked by pre-execution firewall (ai4all host-specific).
Test it before you ship it
Paste your tool schema and a representative call into the sandbox. It runs the same envelope validation, schema assertion, and firewall rules the host will run — so a payload that clears the sandbox will clear the host.