> Full SurrealDB documentation index: https://surrealdb.com/docs/llms.txt

# MCP tools

Tool payloads and ACL alignment.

SurrealDB Agent Memory's MCP server exposes **seven** tools at `/mcp`: `remember`, `recall`, `context`, `reflect`, `forget`, `upload`, and `inspect`. Each maps to one or more REST endpoints and uses the same authentication and scope semantics.

> [!NOTE]
> Older docs and some third-party snippets used names like `memory_store` or `knowledge_search`. Those prefixes are gone - use the short names above.

> [!NOTE]
> REST responses use **camelCase** (`queryMs`, `traceId`, `trace.traceId`). **Scope selectors** on the wire use DNF (`ScopeSets`): writes take **`scope`** / **`scopes`**, reads take **`lens`** - an OR of conjunctive slash-path clauses. Register paths with `agent-memory scopes create` before use. See [Contexts and scope](/docs/agent-memory/mental-model/contexts-and-scope.md#wire-format-scopesets).

## Authentication

All tools use **`Authorization: Bearer`** authentication (same as REST). Pass the token in the MCP configuration:

```json
{
  "mcpServers": {
    "agent-memory": {
      "url": "https://spectron.example.com/mcp",
      "headers": {
        "Authorization": "Bearer sp-..."
      }
    }
  }
}
```

Each API key is bound to exactly one Context, so **`context_id` is optional** on every tool: when omitted, the server resolves the Context from the bearer key. You may still pass `context_id` explicitly; a value that does not match the key's bound Context is rejected with **`401`** (masked, not `404`).

## Scope handling

Scope is layered on each tool call:

1. **Grant region** (from the API key's principal) - enforced server-side; cannot be widened past what the key allows.
2. **Per-call `scope` / `lens` argument** - DNF selector narrowing the operation within the grant (`scope`/`scopes` on writes, `lens` on `recall` / `context`).

Register paths with `agent-memory scopes create` before first use.

---

## `remember`

Persist a new fact from text. Reconciles against existing memory (supersession, uncertainty).

**Requires:** `memory:write` over the target scope.

**Input:**
```jsonc
{
  "text": "I just got promoted to CTO.",   // required
  "session_id": "sess_abc123",            // optional - creates a session if omitted
  "scope": [["org/acme/user/alice"]],     // optional DNF write selector
  "labels": ["source=onboarding"],        // optional
  "infer": "full",                        // full | triples | preview | none (default full)
  "observed_at": "2026-05-12T10:00:00Z"   // optional, full and triples only
}
```

`text` is required unless `infer` is `triples`. With `triples`, pass a **`triples`** array of structured entries instead, and no LLM runs. Each entry has an `entity` (`type` and `name`) and at most one of: `value` with a `key` (an attribute), `target` with a `key` (a relation), or `verb` (a dated action). An entry with none of these upserts the entity only. `preview` extracts but does not persist. **`observed_at`** (RFC 3339) sets the known time of the written facts.

**Output:** `mode`, `session_id`, `turn_id`, `chunk_id` (for `none`), `extraction` (the structured diff of entities, attributes, relations, instructions, uncertainties and corrections), `preview` (for `preview`), and a stand-in `trace_id` (the facts path returns the turn id today).

**Underlying REST endpoint:** `POST /api/v1/{context_id}/facts`

---

## `recall`

Search the unified substrate (experiential facts + document passages) and return ranked hits.

**Requires:** `memory:read`.

**Input:**
```jsonc
{
  "query": "What does Alice do at Acme?",  // required
  "k": 10,                                  // optional, default 10 (max 50)
  "mode": "hybrid",                         // vector | bm25 | graph | hybrid
  "lens": [["org/acme"]],                   // optional DNF read lens
  "labels": ["project=support"]             // optional key=value filter
}
```

**Output:** Ranked hits with scores and source kinds, plus `trace_id`.

**Underlying REST endpoint:** `POST /api/v1/{context_id}/query`

---

## `context`

Assemble a markdown context block (profile + preferences + relevant facts) ready for system-prompt injection.

**Requires:** `memory:read`.

**Input:**
```jsonc
{
  "query": "What does Alice do at Acme?",
  "lens": [["org/acme"]],
  "labels": ["org=acme"]
}
```

**Output:** Markdown context string plus `trace_id`.

**Underlying REST endpoint:** `POST /api/v1/{context_id}/context`

---

## `reflect`

Synthesise patterns across stored memory. With `persist: true`, writes the insight as new facts (`source.kind = "reflect"`).

**Requires:** `memory:read`; `persist: true` also needs write access in the caller's region.

**Input:**
```jsonc
{
  "query": "What recurring complaints have customers raised this month?",
  "persist": true   // optional, default false
}
```

**Output:** Reflection text, evidence refs, optional `persisted_attributes`, and `trace_id`.

**Underlying REST endpoint:** `POST /api/v1/{context_id}/reflect`

---

## `forget`

Stop believing something. Soft-deletes the memories that match a natural-language query, whatever table they are in: retrieval stops serving them under current and temporal reads, but the records stay stored, and a system-time (`atInstant`) read anchored before the forget can still see them. `purge: true` is the irreversible right-to-be-forgotten erasure that also removes that stored history.

**Requires:** `memory:forget` (not the same as `memory:write`).

**Input:**
```jsonc
{
  "query": "anything about my previous role at the old company",
  "purge": false
}
```

**Output:** `deleted`, the number of memories soft-deleted. `trace_id` is empty on this path.

**Underlying REST endpoint:** `POST /api/v1/{context_id}/forget`

---

## `upload`

Upload a document (base64 bytes) into the knowledge layer. Processing is asynchronous.

**Requires:** `memory:write`.

**Input:**
```jsonc
{
  "bytes_base64": "<RFC 4648 bytes>",  // required
  "title": "Returns Policy",
  "source": "returns.pdf",
  "mime_type": "application/pdf",
  "filename": "returns.pdf",
  "scopes": [["org/acme/team/eng"]],   // optional - or `scope` alias
  "labels": ["team=eng"]
}
```

**Output:**
```jsonc
{
  "id": "doc:01hx9…",
  "status": "queued",
  "content_hash": "blake3:…",
  "deduplicated": false,
  "version": 1
}
```

Poll status with **`inspect`** (`document:<id>`) or REST `GET .../documents/{id}`.

**Underlying REST endpoint:** `POST /api/v1/{context_id}/documents`

---

## `inspect`

Fetch a typed record by reference for explainability.

**Requires:** `memory:read`.

**Input:**
```jsonc
{
  "ref": "entity:person/alice"   // entity:<type>/<name> | trace:<id> | document:<id>
}
```

**Output:** Entity (with attributes / supersession), flat trace record, or document metadata - matching the underlying GET.

**Underlying REST endpoints:**
- `GET /api/v1/{context_id}/entities/{type}/{name}`
- `GET /api/v1/{context_id}/traces/{id}`
- `GET /api/v1/{context_id}/documents/{id}`

---

## Error handling

Operation failures return an **`isError: true`** tool result (HTTP transport stays **200**), not a JSON-RPC protocol error. The result includes the same HTTP status the REST API would return:

```jsonc
{
  "isError": true,
  "structuredContent": {
    "error": {
      "status": 404,
      "message": "Document not found"
    }
  }
}
```

| Status | Meaning | Agent action |
| --- | --- | --- |
| `401` | Missing or invalid key | Fix credentials; missing Context is masked as 401 (not 404) |
| `403` | Grant denied | Narrow scope or request access |
| `404` | Resource not found | Adjust query or id |
| `429` | Rate limit or `enforcement_blocked` | Back off and retry |
| `500` | Server fault | Retry; details are redacted |

JSON-RPC **`error`** responses are reserved for **protocol faults** only - malformed params (`-32602`), unknown tool (`-32601`), oversized `k`, and similar.

Denied operations emit the same **`authz.denied`** audit events and error metrics as REST.

---

## Tool ACL summary

| Tool | Typical grant |
|---|---|
| `remember` | `memory:write` |
| `recall` | `memory:read` |
| `context` | `memory:read` |
| `reflect` (`persist: false`) | `memory:read` |
| `reflect` (`persist: true`) | `memory:read` + write region |
| `forget` | `memory:forget` |
| `upload` | `memory:write` |
| `inspect` | `memory:read` |

## Streaming

Most tools are synchronous - the full response is returned in a single payload.

`upload` returns immediately with `queued` status; ingestion continues on the worker. `reflect` with `persist: true` on a large corpus may take several seconds (no MCP progress notifications today).
