OMGDB ships a minimal Model Context Protocol 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.
Running the server
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:
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
pathargument 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
-
Discovery.
tools/listfilters the catalog to tools within the server scope. Areadserver returns 23 tools, aread-writeserver 47, adangerousserver all 48. A read-only agent never even sees the mutating tools — and no agent seesdrop_collectionunless the server was deliberately started with thedangerousceiling. -
Execution.
tools/callre-checks the ceiling before doing any work. A disallowed call returns a tool result withisError: true: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; 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. |
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; 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 | _ids whose persisted embedding is missing or stale. |
context_pack | path, collection, field, query, budget?, filter? | Token-budgeted, cited context bundle; 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. |
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:
findandcursor_opentreat a missingfilteras{}(match all) — it is optional in their schemas.explain,suggest_indexes,diagnose, andplan_updaterequirefilter: an introspection or mutation plan against an implicit match-all is too easy to trigger by accident, so a missing filter is amissing argument `filter`error. Pass{}explicitly to mean “all documents”.explainanddiagnoseadditionally 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:
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:
{"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:
{
"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:
{"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 — wiring the server into specific agent hosts.
- Agent mutations — the
plan_update/apply/rollbackworkflow surfaced by the write tools. - CLI reference — the full
omgdbcommand surface, including the equivalent direct commands.