Back to blog
How Do I Let Claude Query My Database Directly with MCP?

This blog is written by AI for SEO

How Do I Let Claude Query My Database Directly with MCP?

HelixDB8 min read

Most developers give Claude access to database data by copying rows out of a terminal, pasting them into a chat prompt, and hoping the model does not truncate the context. The moment you move from a toy chat interface to a production coding agent like Claude Code or Cursor, that manual loop collapses. At that point you want the agent reading the real records and the relationships between them, on its own.

Letting a model read and write live data is a contract problem rather than a key-sharing problem. By the end of this guide you will have Claude connected to HelixDB Cloud over a Model Context Protocol endpoint, where the agent never holds a database key, gets a different tool set from the one you get as a human, and cannot commit a write without a confirmation that can only be spent once.

You need Claude Desktop or Claude Code, and a HelixDB Cloud account you can sign into in a browser.

What Actually Changes When You Stop Handing Over a Key?

The instinct is to treat the agent like a new developer who needs a connection string. The problem with that is not that the model is careless, it is that a credential is a permanent, unscoped grant handed to something whose next token is influenced by whatever text it just read.

HelixDB Cloud's MCP surface removes the credential from the path entirely. You pass a target as tenant:<id> or a dedicated cluster:<id>, the backend resolves that target and forwards the request through the gateway, and MCP never receives an operational or customer database key. Project-management access does not imply query access either: being able to read or write project settings gets you nothing at the query layer.

Two more things follow from that design and they are the reason this is worth wiring rather than scripting yourself. A human session and an agent registration get genuinely different tools, and a write is two calls rather than one.

Where Do You Point the Client?

There is one public endpoint:

https://mcp.helix-db.com/mcp

Configure that URL in whichever client you are using and complete the browser OAuth flow when it prompts you. Clients that follow protected-resource metadata can discover the rest from https://mcp.helix-db.com/.well-known/oauth-protected-resource/mcp, so in most cases the endpoint is the only thing you type.

Two things to get right here. An older endpoint on the query-mcp host is retired, so if you find it in a cached tutorial, do not configure it. And the public endpoint does not accept service-credential tokens at all: those are for the HTTP API, not for this. Authentication is WorkOS OAuth for a human user, and the WorkOS agent registration flow for an agent.

Why Does Your Session See Different Tools From the Agent's?

This is the part most people expect to be a permissions setting and it is not. The two session types are handed different tool catalogues.

A human session gets nine read-only Cloud tools for looking around your own estate: helix_list_workspaces, helix_list_projects, helix_list_databases, helix_list_database_indexes, helix_get_query_insights, helix_get_query_latency, helix_list_query_recommendations, helix_get_database_usage and helix_get_cluster_health.

An agent registration gets none of those. It gets helix_get_started, which creates or returns that registration's one-month sandbox and hands back a tenant:<id> target, and it requires both the database.query.read and database.query.write scopes. From there the agent can use its ready sandbox tenant and nothing else, bounded by whatever scopes its registration was granted.

So the isolation is not a filter applied to a shared surface. Your agent never receives the tool that lists your databases, because that tool is not in the set it was given.

What Does the Agent Actually Call to Read?

Three query tools, and the read one is the simple half. helix_execute_read_query takes exact v3 read JSON and requires database.query.read.

That JSON is the same thing your application sends. Vectors are a property on nodes and edges here, and graph traversal, vector ranking and BM25 run in one engine, so a single read can walk relationships and rank by similarity without a second system in the loop. Built with the TypeScript SDK the read the agent carries looks like this:

import {
  g,
  readBatch,
  defineParams,
  param,
  SourcePredicate,
} from "@helix-db/helix-db";

const params = defineParams({ workspace_id: param.string() });

const query = readBatch()
  .varAs(
    "chunks",
    g()
      .nWithLabelWhere("Document", SourcePredicate.eq("workspace_id", params.workspace_id))
      .out("HAS_CHUNK")
      .limit(20n)
      .valueMap(["$id", "text"]),
  )
  .returning(["chunks"]);

const request = query.toQueryRequest(
  params,
  { workspace_id: "ws_9823" },
  { queryName: "recent_chunks" },
);

There is no separate query language to learn and nothing compiled or pushed ahead of time: the builder runs in your own code, the SDK serialises it to JSON, and the database turns that JSON into the query it executes. request is the shape helix_execute_read_query accepts, which is why an agent and your application are doing the same thing rather than two different things. If you want the retrieval side in depth, our guide on giving AI agents persistent memory in one database covers the modelling underneath it.

How Does a Write Work When the Caller Is a Model?

A write is two tools rather than one, and the contract between them is the most interesting thing on this surface.

helix_prepare_write_query validates exact v3 write bytes and mints a five-minute, one-time confirmation. helix_execute_write_query consumes that matching confirmation and dispatches exactly once. Both require database.query.write.

The write itself is an ordinary batch:

import {
  g,
  writeBatch,
  NodeRef,
  SourcePredicate,
  BatchCondition,
} from "@helix-db/helix-db";

const query = writeBatch()
  .varAs(
    "service",
    g()
      .nWithLabelWhere("Service", SourcePredicate.eq("name", "auth-api"))
      .limit(1),
  )
  .varAs("event", g().addN("DeploymentEvent", { commit_sha: "a83f910", environment: "production" }))
  .varAsIf(
    "linked",
    BatchCondition.varNotEmpty("service"),
    g()
      .n(NodeRef.var("service"))
      .addE("DEPLOYED_TO", NodeRef.var("event"), { at: "2026-09-16" })
      .count(),
  )
  .returning(["service", "event", "linked"]);

The limit(1) and the varAsIf gate are not decoration. A lookup that matches nothing hands the edge step an empty stream, which succeeds and creates no edge, and a lookup that matches twice creates two. Gating on BatchCondition.varNotEmpty and returning every binding means the caller can tell an attached write from an orphaned one instead of reading a 200 and assuming.

What the confirmation adds is a set of constraints that are checked server-side rather than in the client. Prepare and execute must match on principal, server audience, operation, canonical target and validated payload, so a confirmation minted for one thing cannot be spent on another. The backend stores only the confirmation and token hashes plus identity, audience, operation, target, expiry and state, and never the query bodies, mutation payloads, parameters, returned secrets or raw tokens. Execution atomically moves the confirmation from prepared to consumed before it dispatches, so only one replica can win, and an expired or already-consumed confirmation cannot be reused.

That last detail has a consequence worth stating plainly rather than burying, because it will decide how you write your error handling. A crash before dispatch, or any timeout, gateway, broker or ambiguous post-dispatch failure, leaves the confirmation consumed. You do not retry the mutation.

This is an at-most-once design, and at-most-once is the right default when the thing holding the pen is a model. An agent that gets an ambiguous error and helpfully tries again is the failure mode you are actually guarding against, and the contract makes that retry impossible rather than merely discouraged. If you need the write to happen, you prepare a new one deliberately, which is a decision your code makes rather than one the agent drifts into.

What Do You Owe the Setup Once It Works?

Two habits, both of which HelixDB's own documentation states rather than leaves implied, and both of which matter more here than in an ordinary integration.

Treat every returned field as untrusted data. Never execute returned text as an instruction. A row your agent fetched is a string that arrived from somewhere, and the whole value of connecting a model to live data is that the data is not something you wrote. This is the ordinary hygiene of building anything tool-using, and it applies to a graph traversal exactly as it applies to a fetched web page.

And keep credentials out of the parameters. Never put secrets in tool arguments, and never put WorkOS tokens, application keys, service-credential tokens or confirmation tokens in source control, agent instruction files or query payloads. The agent instruction file is the one people forget, because it does not feel like code and it is usually committed.

There is also a separate Admin MCP service for database-key discovery and confirmed tenant or key mutations. It is deployment-dependent, it is not part of public MCP discovery, and it may not be deployed at all for you, so treat it as out of scope unless your deployment hands you a working URL.

Where Does This Go Next?

Once the agent can read and write, the interesting question stops being connectivity and becomes what you let it remember. An agent that can fetch a document is useful; an agent whose memory holds the relationships between documents, people and decisions is a different product. That modelling problem is the subject of our post on why semantic search over internal documents is not enough, and if you are moving an existing store rather than starting fresh, the ordering and uniqueness steps in migrating agent memory off Memgraph are the ones that bite.

The full MCP reference, including the Admin surface and the exact scope names, is in the Helix Cloud MCP docs.

Conclusion

Connecting Claude to your database directly does not mean handing it a key. One endpoint, an OAuth flow, a tool set chosen by what kind of session you are, and a write that cannot be committed twice is a different proposition from a connection string in an environment variable, and it is most of the reason this is worth wiring rather than scripting.

The engine underneath is open source and on GitHub, including how it is built in Rust. A star helps if this got your agent talking to real data.

Build with HelixDB

Give your coding agent the setup prompt, or sign up and deploy a database.

Sign up