---
title: MCP Server
description: Run omgdb as a stdio MCP server exposing 48 scoped, annotated tools that a coding agent can call directly.
---

OMGDB ships a minimal [Model Context Protocol](https://modelcontextprotocol.io) server that exposes the engine directly to coding agents. Started with `omgdb mcp`, it speaks newline-delimited JSON-RPC 2.0 over stdin/stdout and advertises the database as 48 agent-callable tools built on the engine's own JSON codec.

The defining feature is its capability-scope model. The server runs at a single ceiling — `read` or `read-write` — and a tool is only callable if its required scope is within that ceiling. Enforcement happens in two layers: tools above the ceiling are never advertised in `tools/list`, and every `tools/call` re-checks the ceiling before executing. An untrusted agent can be handed a read-only database it is structurally unable to mutate.

For wiring the server into specific agent hosts, see the [agent setup guides](/docs/agents/).

## Running the server

```sh
omgdb mcp
```

This starts a stdio server at the default `read-write` scope. It reads JSON-RPC requests one per line from stdin and writes one response per line to stdout, flushing after each, until stdin closes. Blank lines, lines that do not parse as a JSON object, and objects without a string `method` are silently ignored.

To restrict the server to the read tools:

```sh
omgdb mcp --scope read
```

| Flag | Description |
| --- | --- |
| `--scope` | Capability ceiling enforced on every tool call: `read` (alias `ro`), `read-write` (default; aliases `write`, `rw`), or `dangerous` (alias `all`), which additionally unlocks the destructive `drop_collection` tool. An unrecognized value aborts. |

> **Stateless by design:** the server holds no session. Every tool takes a `path` argument naming the on-disk store, and everything that looks like session state — read cursors, planned-change tokens — is persisted as sidecar files inside the store itself. Cursors and plan tokens survive a server restart.

## Capability scopes

Scopes are ordered (`read < read-write < dangerous`); a tool is allowed if and only if its required scope is at or below the server's ceiling.

| Scope | Aliases | Tools permitted |
| --- | --- | --- |
| `read` | `ro` | The 23 read tools |
| `read-write` | `write`, `rw` | 47 tools (23 read + 24 write) |
| `dangerous` | `all` | All 48 — adds `drop_collection`, the one tool that destroys a whole collection |

### Two-layer enforcement

1. **Discovery.** `tools/list` filters the catalog to tools within the server scope. A `read` server returns 23 tools, a `read-write` server 47, a `dangerous` server all 48. A read-only agent never even sees the mutating tools — and no agent sees `drop_collection` unless the server was deliberately started with the `dangerous` ceiling.
2. **Execution.** `tools/call` re-checks the ceiling before doing any work. A disallowed call returns a tool result with `isError: true`:

   ```text
   Error: tool `insert` requires `read-write` scope but this server runs with `read` scope
   ```

So hiding a tool from `tools/list` is not the only line of defense — an agent that guesses a tool name it was never shown is still refused.

### Unknown tool names

Tool existence is checked **before** the scope check. A typo'd or invented tool name always gets ``Error: unknown tool `finds` `` — never a message suggesting the server needs more scope. A misspelling cannot be mistaken for a permissions problem, and cannot be used to fish for scope escalation.

## Tools

All 48 tools take a `path` string naming the store; documents, filters, pipelines, and update specs are JSON values. In the tables below, a trailing `?` marks a parameter as optional — everything else is listed in the tool's `inputSchema.required`, exactly matching what dispatch enforces at call time.

### Read tools (23) — scope `read`

| Tool | Parameters | Description |
| --- | --- | --- |
| `get` | `path`, `collection`, `id` | Fetch one document by exact `_id`. |
| `inspect` | `path` | List collections and document counts as JSON. |
| `list_collections` | `path` | Every known collection in stable name order. |
| `list_indexes` | `path`, `collection` | A collection's ordinary indexes in stable name order. |
| `find` | `path`, `collection`, `filter?`, `sort?`, `skip?`, `limit?`, `afterId?`, `afterSortKey?`, `project?` | Find documents matching a MongoDB-style filter; returns NDJSON. |
| `count_documents` | `path`, `collection`, `filter?` | Count matching documents through the direct query planner. |
| `distinct` | `path`, `collection`, `field`, `filter?`, `maxValues?` | Deterministic distinct values for a dotted field path. |
| `cursor_open` | `path`, `collection`, `filter?`, `batchSize?`, `sort?`, `project?` | Open a persisted read cursor and return the first batch (`batchSize` defaults to 100). |
| `cursor_next` | `path`, `token`, `batchSize?` | Read and advance the next batch from a cursor. |
| `cursor_close` | `path`, `token` | Close a cursor and remove its sidecar state. |
| `aggregate` | `path`, `collection`, `pipeline` | Run an [aggregation pipeline](/docs/aggregation/); returns NDJSON. |
| `describe` | `path` | Markdown manual of the database. |
| `dump` | `path` | Deterministic canonical export of every document. |
| `verify` | `path` | Replay the op-log and report integrity plus per-cache status as JSON. |
| `validate` | `path`, `collection` | List documents violating [validation rules](/docs/schema-validation/). |
| `explain` | `path`, `collection`, `filter` | How a query will run — index scan vs full scan. |
| `suggest_indexes` | `path`, `collection`, `filter`, `sort?` | Indexes the planner can use for this query, optionally sort-aware. |
| `diagnose` | `path`, `collection`, `filter` | Why-not debugger: per-predicate selectivity. |
| `search` | `path`, `collection`, `index`, `query`, `mode?`, `filter?`, `limit?`, `minScore?`, `highlight?`, `explain?`, `timeoutMs?` | Typed lexical, local semantic, or deterministic hybrid [full-text search](/docs/search/); semantic models must already be installed — this tool never downloads. |
| `search_index_status` | `path`, `collection`, `index?` | Search definition, generation boundary, provenance, counts, and rebuild state. |
| `vsearch` | `path`, `collection`, `field`, `query`, `k?`, `filter?` | Semantic search over a text field; `k` defaults to 5, `filter` pre-filters candidates. |
| `vstale` | `path`, `collection`, `field` | `_id`s whose persisted embedding is missing or stale. |
| `context_pack` | `path`, `collection`, `field`, `query`, `budget?`, `filter?` | Token-budgeted, cited [context bundle](/docs/context-packs/); `budget` defaults to 1000 tokens. |

### Write tools (24) — scope `read-write`

| Tool | Parameters | Description |
| --- | --- | --- |
| `insert` | `path`, `collection`, `document` | Insert one document; returns the assigned `_id`. |
| `insert_many` | `path`, `collection`, `documents` | Atomically insert an array of documents in one durable transaction. |
| `import_jsonl` | `path`, `collection`, `file`, `batchSize?` | Stream a JSON Lines file in atomic batch commits (default 1000 per batch). |
| `replace` | `path`, `collection`, `id`, `document` | Replace one document by exact `_id`, preserving `_id` identity. |
| `delete` | `path`, `collection`, `id` | Delete one document by exact `_id`. |
| `update_one` | `path`, `collection`, `filter`, `update`, `arrayFilters?`, `upsertDocument?`, `maxScannedDocuments?` | Atomically update the canonical-`_id` first match; explicit-seed upsert only. |
| `update_many` | `path`, `collection`, `filter`, `update`, `arrayFilters?`, `maxScannedDocuments?`, `maxMutations?` | Update every match in one bounded atomic commit. |
| `delete_many` | `path`, `collection`, `filter`, `maxScannedDocuments?`, `maxMutations?` | Delete every match in one bounded atomic commit. |
| `find_one_and_update` | `path`, `collection`, `filter`, `update`, `arrayFilters?`, `maxScannedDocuments?` | Atomically update the first match and return its before/after images. |
| `bulk_write` | `path`, `version`, `operations`, `maxOperations?`, `maxScannedDocumentsPerOperation?`, `maxMutations?` | Ordered CRUD program as one atomic transaction; later steps read earlier writes. |
| `define_collection` | `path`, `collection`, `spec` | Define validation rules; `spec.unique` entries also create unique indexes. |
| `create_collection` | `path`, `collection` | Create an explicitly cataloged collection, including an empty one. |
| `create_index` | `path`, `collection`, `field?`, `fields?`, `unique?`, `partialFilter?` | Create a secondary index — pass `field` for one field or `fields` for a compound index. |
| `create_named_index` | `path`, `collection`, `name`, `field?`, `fields?`, `unique?`, `partialFilter?` | Create a secondary index under an explicit stable name — an old server errors instead of silently ignoring the requested identity. |
| `drop_index` | `path`, `collection`, `name` | Drop one ordinary index by stable name. |
| `define_search_index` | `path`, `definition` | Define or update a versioned, op-log-backed [search index](/docs/search/). |
| `drop_search_index` | `path`, `collection`, `index` | Drop a search-index definition and its rebuildable acceleration files. |
| `rebuild_search_index` | `path`, `collection`, `index`, `writerMemoryBytes?` | Explicitly rebuild and verify one derived search generation. |
| `markdown_set_section` | `path`, `collection`, `id`, `sectionId`, `text` | Replace one imported-Markdown section body and refresh `_sections`. |
| `markdown_patch_frontmatter` | `path`, `collection`, `id`, `patch`, `remove?` | Patch (and optionally remove) frontmatter fields on an imported Markdown document. |
| `plan_update` | `path`, `collection`, `filter`, `update`, `arrayFilters?` | Dry-run an update: returns a token plus a before/after sample, writes nothing. |
| `apply` | `path`, `token` | Apply a planned change by token, reversibly. |
| `rollback` | `path`, `change_id` | Roll back a previously applied change by id. |
| `vsync` | `path`, `collection`, `field` | Persist or refresh embeddings into `<collection>.__vectors`. |

### Dangerous tools (1) — scope `dangerous`

| Tool | Parameters | Description |
| --- | --- | --- |
| `drop_collection` | `path`, `collection` | Drop a collection with all of its documents and index metadata. The only tool above `read-write`: it is never advertised or callable unless the server was started with `--scope dangerous`. |

### Argument requiredness

Requiredness is declared **per tool** and matches execution exactly:

- `find` and `cursor_open` treat a missing `filter` as `{}` (match all) — it is optional in their schemas.
- `explain`, `suggest_indexes`, `diagnose`, and `plan_update` **require** `filter`: an introspection or mutation plan against an implicit match-all is too easy to trigger by accident, so a missing filter is a ``missing argument `filter` `` error. Pass `{}` explicitly to mean "all documents".
- `explain` and `diagnose` additionally refuse a collection that does not exist, with a did-you-mean hint — a typo never yields confidently wrong planner advice.

## Safety annotations

Every tool carries an `annotations` object so an MCP host can reason about tool safety, and the annotations reflect what each tool actually does:

| Annotation | Meaning |
| --- | --- |
| `readOnlyHint` | `true` for the 14 tools that touch nothing on disk. Notably `false` for the three `cursor_*` tools: they run at `read` scope (they cannot change your data) but persist cursor state inside the store. |
| `idempotentHint` | `true` where re-running with the same arguments changes nothing further — the read-only tools plus `delete` and the two Markdown edits. |
| `destructiveHint` | `true` for exactly five tools: `delete`, `apply`, `rollback`, `markdown_set_section`, and `markdown_patch_frontmatter` — the calls that overwrite or discard existing data. |
| `requiredScope` | The tool's required scope as a string (`read` / `read-write`). An OMGDB-specific annotation beyond the standard MCP set; hosts can use it to fence tools without parsing descriptions. |

## JSON-RPC methods

| Method | Behavior |
| --- | --- |
| `initialize` | Returns `protocolVersion` `"2024-11-05"`, `capabilities.tools`, and `serverInfo` `{"name":"omgdb","version":...}`. Client params are ignored. |
| `tools/list` | Returns the scope-filtered tool catalog with schemas and annotations. |
| `tools/call` | Dispatches a tool by `params.name` with `params.arguments`. |
| `notifications/*` | No reply, per JSON-RPC notification semantics. |

### Error handling

Protocol-level problems are JSON-RPC errors:

| Code | Message | Cause |
| --- | --- | --- |
| `-32601` | `method not found` | Method is not `initialize`, `tools/list`, `tools/call`, or a notification. |
| `-32602` | `invalid params` | `params` is not an object. |
| `-32602` | `missing tool name` | `params.name` is not a string. |

Tool-level failures — an unknown tool name, a refused-by-scope call, a missing argument, or an engine error — are **not** JSON-RPC errors. They return a normal result envelope whose `content` is a text block and whose `isError` is `true`, so an agent can read the message and repair the call.

## A complete session, copy-paste

Create a store, then drive the server over stdin — three requests, one per line:

```sh
omgdb create app.omgdb
omgdb insert app.omgdb users '{"name":"ana","age":30}'

printf '%s\n' \
  '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}' \
  '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}' \
  '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"find","arguments":{"path":"app.omgdb","collection":"users","filter":{"age":{"$gte":21}}}}}' \
| omgdb mcp
```

The `initialize` response identifies the server:

```json
{"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"2024-11-05","capabilities":{"tools":{}},"serverInfo":{"name":"omgdb","version":"..."}}}
```

`tools/list` returns 47 tools at the default scope (23 with `--scope read`, all 48 with `--scope dangerous`), each with an `inputSchema` and `annotations`. The `find` entry looks like:

```json
{
  "name": "find",
  "description": "Find documents matching a MongoDB-style filter",
  "inputSchema": {
    "type": "object",
    "properties": {
      "path": {"type": "string"},
      "collection": {"type": "string"},
      "filter": {"type": "object"},
      "sort": {"type": "object"},
      "skip": {"type": "integer"},
      "limit": {"type": "integer"},
      "afterId": {},
      "afterSortKey": {"type": "object"},
      "project": {"type": "object"}
    },
    "required": ["path", "collection"]
  },
  "annotations": {
    "readOnlyHint": true,
    "idempotentHint": true,
    "destructiveHint": false,
    "requiredScope": "read"
  }
}
```

And the `find` call returns the matching document as NDJSON inside a text block.

If the server had been started with `--scope read`, a call to a write tool such as `insert` would come back as a result flagged as an error, not an insert:

```json
{"jsonrpc":"2.0","id":4,"result":{"content":[{"type":"text","text":"Error: tool `insert` requires `read-write` scope but this server runs with `read` scope"}],"isError":true}}
```

## Related

- [Agent setup guides](/docs/agents/) — wiring the server into specific agent hosts.
- [Agent mutations](/docs/agent-mutations/) — the `plan_update` / `apply` / `rollback` workflow surfaced by the write tools.
- [CLI reference](/docs/cli/) — the full `omgdb` command surface, including the equivalent direct commands.
