letta-appserver-mcp
README.md
# letta-appserver-mcp
Granular MCP server (Streamable HTTP) for the **Letta App Server** (`:4500`).
Letta is the single source of truth for memory — no local fallback.
## Tools (13)
Agents: `agent_create`, `agent_get`, `agent_list` (optional
`query`/`name`/`tags`/`limit`/`order` filters), `agent_lookup`,
`agent_update` (optional `system`/`modelSettings`/
`contextWindowLimit`/`hidden` in addition to name/description/model/
tags; persona/human are create-time only — stored agents carry a single
`system` prompt), `agent_delete` (requires `confirm:true`),
`models_list`, `bridge_health`.
Memory: `memory_save`, `memory_search` (retrieval only — you reason over
the answer), `memory_get`, `memory_delete`.
Session: `session_ask` — reason with the agent's memory and answer.
One-shot turn, files nothing durable. Use for synthesis/advice/
explanation; use `memory_search` for retrieval.
### Request / response contracts
Every tool takes a JSON object (see its schema) and returns a strict
envelope: `{ok:true, data:{…}}` or `{ok:false, error:{code, message}}`.
`agent_id` everywhere accepts an id **or** a name.
| Tool | Request body | Response `data` |
|---|---|---|
| `agent_create` | `{persona?, human?, name?, description?, model?, embedding?, tags?}` | `{agent_id, agent_name, agent}` |
| `agent_get` | `{agent_id}` | `{…agent, agent_name}` |
| `agent_list` | `{query?, name?, tags?, limit? (default DEFAULT_LIST_LIMIT), order?}` | `[{…agent}]` |
| `agent_lookup` | `{name?}` | `[{agent_id, name, model, tags}]` |
| `agent_update` | `{agent_id, name?, description?, model?, system?, modelSettings?, contextWindowLimit?, hidden?, tags?}` | `{…agent, agent_name}` |
| `agent_delete` | `{agent_id, confirm:true}` | `{deleted, agent_id, agent_name}` |
| `models_list` | `{}` | catalog object |
| `bridge_health` | `{}` | `{bridge_configured, letta_url, models_reachable, model_count?}` |
| `memory_save` | `{agent_id, text, tags?}` | `{saved, agent_id, agent_name, tags, detail}` |
| `memory_search` | `{agent_id, query, top_k?}` | `{agent_id, agent_name, query, top_k, answer}` |
| `memory_get` | `{agent_id}` | `{agent_id, agent_name, blocks}` |
| `memory_delete` | `{agent_id, id}` | `{deleted, agent_id, agent_name, ref, detail}` |
| `session_ask` | `{agent_id, message}` | `{agent_id, agent_name, reply}` |
`memory_search` retrieves (you reason); `session_ask` reasons (it
answers); `memory_save` files. Neither ask path instructs a durable
write — "saves nothing" is a prompt contract, not a storage lock.
Every tool that acts on an agent takes a required `agent_id` — the MCP
keeps no default agent. Which project uses which agent is decided per
request by the caller; one project may use many agents.
Every `agent_id` parameter accepts an id **or** a name (`"dhruba"` works;
exact match preferred, substring fallback, errors on miss/ambiguity), and
every response echoes `agent_name` next to `agent_id`.
New sessions bootstrap ids via `agent_lookup` (compact id/name/model —
no system-prompt dump; optional name filter). `block-registry.json` in
backups is the offline id record.
`agent_create` takes optional `model` / `embedding` handles, falling back
to `DEFAULT_MODEL` / `DEFAULT_EMBEDDING` (all empty by default — the server
decides when omitted). Pass them explicitly per call; change them later
via `agent_update`.
All responses are a strict envelope:
`{ok:true, data:{…}}` or `{ok:false, error:{code, message}}` (see the
table above for each tool's `data` shape).
## Setup — full stack (App Server + MCP, one command)
```bash
cp .env.example .env # set LETTA_APP_SERVER_TOKEN + MCP_AUTH_TOKEN (+ model path below)
docker compose up -d
curl http://[IP_ADDRESS]:4500/readyz # App Server (letta-code image)
curl http://[IP_ADDRESS]:6507/healthz # MCP server
```
Zero-cost path (no API keys): install Ollama on the host, `ollama pull
qwen3`, then connect it once with `docker exec -it <app-server-container>
letta connect ollama` pointed at `http://host.docker.internal:11434`, and
pass that model in `agent_create`. Note weaker local models can behave
unexpectedly as agent drivers — prefer a tool-capable one. Paid path: set
`ANTHROPIC_API_KEY` or `OPENAI_API_KEY` in .env instead.
`docker-compose.yml` runs the App Server straight from the prebuilt
`ghcr.io/letta-ai/letta-code` image (same `letta server` startup as the
official `letta-app-server-deploy` repo) alongside this MCP, on a shared
network with persistent `letta-state` / `letta-workspace` volumes. Pin it
with `LETTA_CODE_VERSION` (kept compatible with
`@letta-ai/letta-agent-sdk` in `package.json`). All ports, tokens, and
limits come from .env — see .env.example.
`agent_create` accepts optional `model` / `embedding` handles, falling back
to `DEFAULT_MODEL` / `DEFAULT_EMBEDDING` (all empty by default — the server
decides when omitted). `models_list` shows the LLM catalog; embeddings have
no catalog endpoint, so set `DEFAULT_EMBEDDING` to a provider-qualified
handle. For free local embeddings: `docker compose --profile
local-embeddings up -d`, `docker exec ollama ollama pull
qwen3-embedding:0.6b`, then `OLLAMA_BASE_URL=http://ollama:11434/v1` plus
`DEFAULT_EMBEDDING=ollama/qwen3-embedding:0.6b` in .env.
`SESSION_CWD=/workspace` is the directory sessions work in — a persistent
volume where the agent reads/writes files during turns.
## Setup — MCP alone (against an existing App Server)
```bash
cp .env.example .env # set LETTA_APP_SERVER_URL=http://[IP_ADDRESS]:4500 + both tokens
npm install
npm run build
npm start # $MCP_HOST:$MCP_PORT/mcp, GET /healthz
```
## OpenCode wiring (HTTP, authenticated)
```json
{ "mcp": { "letta-mcp": {
"type": "remote",
"url": "http://localhost:6507/mcp",
"headers": { "Authorization": "Bearer {env:MCP_AUTH_TOKEN}" },
"enabled": true } } }
```
`/mcp` requires the `MCP_AUTH_TOKEN` Bearer token (`401` without it);
`/healthz` stays open for container healthchecks. The server refuses to
start if `MCP_AUTH_TOKEN` is unset.
## Config
Everything is env-driven — see .env.example. No ports, URLs, timeouts,
limits, or default prompt text are hardcoded in `src/`.
Model provider keys live on the App Server, never here.This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues