One endpoint
whichever agent you run
CameoDB serves MCP at http://localhost:9480/mcp. Add the entry for your client and your data becomes a tool the agent can reach directly.
# Claude Code
claude mcp add --transport http cameodb http://localhost:9480/mcp
{
"mcpServers": {
"cameodb": {
"type": "http",
"url": "http://localhost:9480/mcp"
}
}
}
{
"mcpServers": {
"cameodb": {
"command": "npx",
"args": ["-y", "mcp-remote", "http://localhost:9480/mcp"]
}
}
}
Claude Code
claude mcp add --transport http, or "type": "http" in .mcp.json.
Claude Desktop
Through mcp-remote in claude_desktop_config.json.
Cursor
"url" in .cursor/mcp.json.
Windsurf
"serverUrl" in mcp_config.json.
A key, not a URL
With security on, an agent presents the same Authorization: Bearer key the HTTP API takes, with the same capabilities and index scope. An agent sees exactly the tools its key can call, and since every tool is a read, a read key covers them all.
claude mcp add --transport http cameodb \
http://localhost:9480/mcp \
--header "Authorization: Bearer $CAMEODB_KEY"
Six tools, and the agent
teaches itself the rest
Nobody pastes a schema into a prompt. The agent finds the indexes, reads the types of the one it wants, has its query checked and corrected before spending a search on it, then asks. Every step answers the next one, so the engine teaches the agent how to query it, and a new index joins that loop the moment it exists.
list_indexesWhat is here. Every index the key can see, with its description, document count and field names. A new index appears without configuration.describe_indexOne index in detail: field types, the indexed, fast and shadow flags, and per-type query hints.validate_queryThe syntax guide, a dry run and a correction. Reports what parses, what the engine would rewrite it to, which clauses would be dropped, and names the field you meant: Unknown field ‘titel’. Did you mean: title (text)?search_indexFull-text search over one index, with field projection, sorting and paging. Each query runs exactly as written, and any clause the engine set aside is named.search_across_indexesThe same question against several indexes at once, run concurrently and merged into one ranked list.get_catalog_statsTotals across every index the key can see: indexes, documents, fields and bytes. For one index, describe_index has the numbers.Read-only, closed world
Every tool is annotated readOnlyHint and openWorldHint: false. They reach this node’s own indexes and nothing else, which is a property an agent host can check rather than a promise it has to take.
A typo is refused, not ignored
Each schema sets additionalProperties: false, so a call carrying an argument its tool does not take fails by name. A misspelled limit would otherwise return the default ten hits and read as the whole answer.
One result shape
Read content[0].text and let isError decide whether to parse it or read it. Every protocol revision gets that same shape, so a client needs no branch for the one it negotiated.
It starts with a description
An agent learns your data from list_indexes and describe_index, and a good description is what turns field names into meaning. Both tools return the index’s and each field’s description word for word. The repository’s index-describe skill profiles a live index, checks every statement against the data, and writes a schema update with the descriptions ready to apply.
On connect, every client receives a short brief on reading CameoDB’s answers, and the full workflow is there as the cameodb-orchestrator prompt. Indexes are also MCP resources: cameodb://indexes for the catalog, cameodb://indexes/{index}/schema for one schema. Transport is Streamable HTTP on POST /mcp, with the older SSE pair kept for clients already configured against it.
Declare it once.
Query it any way
Eleven core raw types, with aliases so a schema written in SQL, Python or JavaScript terms maps cleanly onto the engine.
float, double, decimal → f64Python, SQLinteger, int, number, signed → i64Python, JavaScriptunsigned, uint → u64C/C++, Rustdatetime, timestamp → dateSQL, Pythonobject, document → jsonJavaScript, Pythoncategory, tag → facetCommon terminologyLists
Every field is multivalued, so there is no array type to declare. Give a field i64 and [9, 12] stores two values of it, each one matched on its own by an exact or range query. Text and json fields take a list whole, as their own JSON.
Built for operators
not just scripts
Launch with ./cameodb client -i for a REPL that completes against your live schema, and types the same grammar the agent sends.
Completion from the node
Index names and fields with their types, refreshed after connect, list and every schema change you make at the prompt.
Load from anywhere
CSV, TSV and JSON families, local or over HTTP(S), Gzip and Zip decompressed in flight.
Left open all day
Persistent history with Ctrl-R, colorized output that falls back to plain in a pipe, and a prompt a slow query cannot freeze.
The commands, the completion behaviour and the ingest flags are on the client page.
Composable by design
Operators combine, so an agent can narrow a result set incrementally rather than reformulating from scratch.
status:active AND region:IN [eu us]Term match intersected with a set.title:"index segment"~2Phrase with slop, tolerating word distance.created:>=2024-01-01 AND level:errorOpen-ended date bound plus an exact term.src_ip:[10.0.0.1 TO 10.0.0.255]Inclusive range across an IP field.path:var* AND NOT level:debugPrefix match with a negation."thanks for your contrib"*Phrase prefix: the last term is a wildcard.Why this matters for agents
A predictable grammar is one an LLM can generate reliably. Combined with key lookups that go straight to the store, the agent can probe and refine many times inside one reasoning step.
Every operator, with the field types it applies to, is on the query syntax page. An agent gets the same reference from validate_query without leaving the conversation.
No agent, no client
just the endpoint
MCP is one way in and the REPL is another. An application wants neither. It wants JSON over HTTP against the same indexes, with the same query grammar in the body.
curl -s -X POST http://localhost:9480/api/books/search \
-H 'content-type: application/json' \
-d '{"query":"title:\"Harry Potter\"","limit":7}'
POST /api/{index}/searchSearch one index. /search/stream returns NDJSON, one hit per line. readPUT /api/{index}/documentWrite one document; DELETE removes it. writePOST /api/{index}/_bulkBatched write, and where the throughput is. _bulk/delete is its counterpart. writePATCH /api/{index}/_schemaPromote discovered fields, or add new ones. index-adminGET /_indexesEvery index this key may see. readGET /_admin/memoryProcess and allocator statistics; /_admin/workers reports the pool. node-adminGET /_cluster/healthThe one public route. noneA key travels in Authorization: Bearer and nowhere else: a query parameter is not a credential and is refused. An unknown path answers 401 without a key and 404 with one, so probing tells an unauthenticated caller nothing.