MCP Server

ArcadeDB includes a built-in Model Context Protocol (MCP) server that allows AI assistants and LLM-based tools to interact with the database. The MCP server exposes database operations as tools that AI clients can discover and invoke using the standard MCP protocol.

The MCP server implements the MCP specification (version 2025-03-26) over HTTP using the JSON-RPC 2.0 protocol.

Endpoint

All MCP requests are sent as POST requests to a single endpoint:

POST /api/v1/mcp

Requests must include a valid Authorization header (Basic Auth or Bearer token) and a JSON-RPC 2.0 body.

An alternative stdio transport carries the same protocol over standard input and output, for clients that launch their MCP servers as a child process rather than connecting to one over the network.

The MCP server ships as the optional mcp module and is installed automatically when its jar is on the classpath, so no SERVER_PLUGINS entry is needed. The standard distribution includes it. A distribution assembled without it, through the arcadedb-builder.sh module selection described in Custom Package Builder, registers neither endpoint and answers 404 on both.

Configuration

The MCP server is configured via the file config/mcp-config.json in the ArcadeDB server directory. You can also manage the configuration at runtime through the configuration endpoint.

Example mcp-config.json
{
  "enabled": true,
  "allowReads": true,
  "allowInsert": true,
  "allowUpdate": true,
  "allowDelete": false,
  "allowSchemaChange": false,
  "allowAdmin": false,
  "profile": "rag",
  "allowedUsers": ["root", "analyst"],
  "principalProfiles": {
    "analyst": "rag"
  },
  "databases": {
    "analytics": {
      "allowInsert": false,
      "allowedUsers": ["analyst"]
    }
  }
}
Property Default Description

enabled

false

Enable or disable the MCP server. When disabled, all MCP requests return an error.

allowReads

true

Allow read-only queries (SELECT, MATCH, etc.).

allowInsert

false

Allow INSERT operations.

allowUpdate

false

Allow UPDATE operations.

allowDelete

false

Allow DELETE operations.

allowSchemaChange

false

Allow schema modifications (CREATE TYPE, CREATE PROPERTY, CREATE INDEX, etc.).

allowAdmin

false

Allow administrative operations.

profile

"all"

Server-global tool profile: "all", "rag", or "admin". See Tool Profiles.

principalProfiles

{}

Optional per-principal tool profiles, keyed by user name or API token name. Intersected with profile, so an entry can only narrow the tool surface. See Per-Principal Profiles.

allowedUsers

["root"]

List of usernames allowed to use the MCP server. Use "*" to allow all authenticated users.

allowedOrigins

[]

Extra browser Origin values accepted by the HTTP transport, on top of the loopback and same-host origins that are always allowed. See Transport.

databases

{}

Optional per-database permission and user restrictions. See Per-Database Scoping.

Tool Profiles

Tool profiles keep the surface presented to an agent focused. The selected profile filters tools/list and is enforced again by tools/call, so a client cannot invoke a hidden tool directly. A profile never grants an operation: read, write, schema, administrative, user, origin, and native database authorization checks remain independent and mandatory.

Profile Tool membership

all

Every tool registered by the running ArcadeDB version. This is the default.

rag

list_databases, get_schema, query, sample_records, vector_search, hybrid_search, full_text_search, upsert_entity, and upsert_relationship.

admin

list_databases, get_schema, query, execute_command, server_status, profiler_start, profiler_stop, profiler_status, get_server_settings, and set_server_setting.

Only registered tools are advertised. For example, a version that does not include hybrid_search does not expose it merely because the name belongs to the rag profile. The agent-memory upsert tools in rag remain unavailable unless their independent write permissions are enabled; all write permissions default to false.

The profile setting is server-global and applies to every principal that has no entry in principalProfiles. Per-database overrides restrict permissions and users only; they cannot select a different profile. See Per-Database Scoping.

Profiles filter prompts as well, because a prompt’s text names tools: a prompt is offered only when every tool it names survives the profile. See Prompts. Resources are not filtered by profile.

Per-Principal Profiles

Entries under principalProfiles assign a tool profile to an individual authenticated principal. The effective profile is the intersection of the server-global profile and the principal entry: a tool is advertised and callable only when both profiles contain it. An entry can therefore only narrow the surface, and never grants a tool that the global profile hides. As with the global profile, an intersection never grants a permission: read, write, schema, administrative, database, user, and origin checks remain independent and mandatory.

A principal is keyed by its user name. An API token can be keyed either canonically as apitoken:<token-name> or by the bare token name, the same two spellings that allowedUsers accepts. When both spellings are present, the canonical apitoken: entry wins.

Example: a retrieval agent restricted to the rag tools on an otherwise unrestricted server
{
  "enabled": true,
  "profile": "all",
  "allowedUsers": ["root", "retrieval-agent", "retrieval-token"],
  "principalProfiles": {
    "retrieval-agent": "rag",
    "apitoken:retrieval-token": "rag"
  }
}

Profile names are case-insensitive on input and are serialized in lowercase. A tools/call for a tool excluded by the intersection is rejected with an error naming both profiles, so a client cannot bypass the filtered tools/list by invoking the tool directly. The initialize instructions follow the effective profile: they describe the rag surface when the intersection matches the rag profile, and otherwise describe the general or the restricted surface.

The runtime configuration endpoint merges principalProfiles by principal name. Set one principal to null to remove that entry, or set the top-level principalProfiles value to null to clear all per-principal profiles:

{
  "principalProfiles": {
    "retrieval-agent": null,
    "reporting-agent": "rag"
  }
}

Per-Database Scoping

Entries under databases further restrict the server-global MCP policy for named databases. An omitted local field inherits its global value. A local false can deny an operation, but a local true cannot enable an operation denied globally. Similarly, a local allowedUsers list is an additional restriction: an authenticated principal must satisfy both the global and local lists. ArcadeDB’s native database authorization remains an independent mandatory check.

Each database override accepts allowReads, allowInsert, allowUpdate, allowDelete, allowSchemaChange, allowAdmin, and allowedUsers. Unknown fields are rejected rather than silently inherited. An override naming a database that does not currently exist produces a startup warning but is retained so it can apply if that database is created later.

Example per-database restrictions
{
  "enabled": true,
  "allowReads": true,
  "allowInsert": true,
  "allowedUsers": ["root", "tenant-agent"],
  "databases": {
    "tenant-graph": {
      "allowInsert": false,
      "allowedUsers": ["tenant-agent"]
    }
  }
}

The runtime configuration endpoint merges updates by database name. A non-null entry replaces the complete override for that database, so fields omitted from the replacement inherit the global policy. Set one database entry to null to remove that override, or set the top-level databases value to null to clear all database overrides:

{
  "databases": {
    "tenant-graph": null,
    "reporting": {
      "allowReads": true,
      "allowInsert": false
    }
  }
}

Database discovery, server status, and schema resources omit databases whose effective policy denies the authenticated principal or read access.

Configuration Endpoint

The MCP configuration can be managed at runtime via the /api/v1/mcp/config endpoint.

Get current configuration:

curl -u root:arcadedb-password \
  http://localhost:2480/api/v1/mcp/config

Update configuration (root user only):

curl -u root:arcadedb-password \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{"enabled": true, "allowReads": true, "allowInsert": true}' \
  http://localhost:2480/api/v1/mcp/config

MCP Protocol Methods

The MCP server supports the following JSON-RPC 2.0 methods:

Method Description

initialize

Handshake that returns the protocol version and server capabilities.

notifications/initialized

Client notification acknowledging initialization.

tools/list

Returns the list of available tools with their JSON schemas.

tools/call

Executes a specific tool by name with the provided arguments.

resources/list

Returns the readable resources. See Resources.

resources/read

Returns the contents of one resource, addressed by URI. See Resources.

prompts/list

Returns the available guided prompt templates. See Prompts.

prompts/get

Renders one prompt with its arguments substituted. See Prompts.

ping

Health check that returns a pong response.

The initialize response advertises all three surfaces under capabilities, as tools, resources, and prompts. None of them emits change notifications and resources cannot be subscribed to, so listChanged and subscribe are all false.

The response also carries an instructions string describing the surface the client actually received. It follows the effective tool profile, so a rag client is told about the retrieval and upsert tools and the guided prompts, while a client whose profile hides most of the surface is told to use only what tools/list returned.

Example: Initialize

curl -u root:arcadedb-password \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","clientInfo":{"name":"my-client","version":"1.0"}}}' \
  http://localhost:2480/api/v1/mcp

Example: List available tools

curl -u root:arcadedb-password \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":2,"method":"tools/list"}' \
  http://localhost:2480/api/v1/mcp

Available Tools

The MCP server exposes a version-dependent set of tools that AI clients can invoke. The commonly used tools described below include list_databases, get_schema, query, execute_command, server_status, sample_records, vector_search, full_text_search, hybrid_search, upsert_entity, and upsert_relationship. The operator tools profiler_start, profiler_stop, profiler_status, get_server_settings, and set_server_setting are described below as well. Call tools/list for the authoritative list, including the full input schema of every tool.

list_databases

Lists all databases accessible to the authenticated user.

Parameters: None

Example:

curl -u root:arcadedb-password \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"list_databases","arguments":{}}}' \
  http://localhost:2480/api/v1/mcp

Response:

{
  "databases": ["mydb", "analytics"]
}

get_schema

Retrieves the full schema of a database, including all types (vertex, edge, document), their properties, and indexes.

Parameters:

Parameter Required Description

database

Yes

The name of the database.

Example:

curl -u root:arcadedb-password \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"get_schema","arguments":{"database":"mydb"}}}' \
  http://localhost:2480/api/v1/mcp

Response:

{
  "database": "mydb",
  "types": [
    {
      "name": "Person",
      "category": "vertex",
      "parentTypes": ["V"],
      "properties": [
        {
          "name": "name",
          "type": "STRING",
          "mandatory": true,
          "readonly": false,
          "notNull": true
        }
      ],
      "indexes": [
        {
          "name": "Person[name]",
          "type": "LSM_TREE",
          "properties": ["name"],
          "unique": true
        }
      ]
    }
  ]
}

query

Executes a read-only (idempotent) query against a database. The server performs semantic analysis to ensure the query does not modify data. If a write operation is detected, the request is rejected.

Parameters:

Parameter Required Description

database

Yes

The name of the database.

language

Yes

Query language: sql, cypher, gremlin, graphql, mongo.

query

Yes

The query string to execute.

limit

No

Maximum number of records to return (default: 1000).

Example:

curl -u root:arcadedb-password \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"query","arguments":{"database":"mydb","language":"sql","query":"SELECT FROM Person LIMIT 10"}}}' \
  http://localhost:2480/api/v1/mcp

Response:

{
  "records": [
    {"@rid": "#1:0", "@type": "Person", "name": "Alice"},
    {"@rid": "#1:1", "@type": "Person", "name": "Bob"}
  ],
  "count": 2
}

execute_command

Executes a non-idempotent command (write operations, schema changes, administrative commands). The server checks the command against the MCP configuration permissions before execution.

Parameters:

Parameter Required Description

database

Yes

The name of the database.

language

Yes

Command language: sql, sqlscript, cypher, gremlin, graphql, mongo.

command

Yes

The command string to execute.

limit

No

Maximum number of records to return (default: 1000).

Permission mapping:

The server analyzes the command semantics and checks against the MCP configuration:

Operation Type Required Permission

INSERT

allowInsert

UPDATE

allowUpdate

DELETE

allowDelete

CREATE TYPE, CREATE PROPERTY, CREATE INDEX, etc.

allowSchemaChange

Administrative operations

allowAdmin

SELECT, MATCH

allowReads

Example:

curl -u root:arcadedb-password \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"execute_command","arguments":{"database":"mydb","language":"sql","command":"INSERT INTO Person SET name = '\''Charlie'\''"}}}' \
  http://localhost:2480/api/v1/mcp

Response:

{
  "records": [
    {"@rid": "#1:2", "@type": "Person", "name": "Charlie"}
  ],
  "count": 1
}

server_status

Returns server metadata including version, available query languages, and accessible databases.

Parameters: None

Example:

curl -u root:arcadedb-password \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"server_status","arguments":{}}}' \
  http://localhost:2480/api/v1/mcp

Response:

{
  "version": "26.5.1",
  "serverName": "ArcadeDB_0",
  "languages": ["sql", "sqlscript", "cypher", "gremlin", "graphql", "mongo"],
  "databases": ["mydb", "analytics"],
  "ha": {
    "clusterName": "arcadedb",
    "leader": "server1",
    "replicas": ["server2", "server3"]
  }
}
The ha field is only included when High Availability is enabled and the user has admin permissions.

sample_records

Returns the first records in storage order from selected database types so an agent can inspect actual property-value shapes before constructing a query. This is not a random or representative sample.

Parameters:

Parameter Required Description

database

Yes

The name of the database.

types

No

Up to 20 type names. Explicit names may include edge types. When omitted, ArcadeDB selects the first 20 vertex and document type names in alphabetical order and excludes edge types.

limit

No

Maximum records per type, from 1 through 20 (default: 3).

Example:

curl -u root:arcadedb-password \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"sample_records","arguments":{"database":"mydb","types":["Person","Company"],"limit":3}}}' \
  http://localhost:2480/api/v1/mcp

Response:

{
  "samples": {
    "Person": [
      {"@rid": "#1:0", "@type": "Person", "name": "Alice"}
    ],
    "Company": [
      {"@rid": "#2:0", "@type": "Company", "name": "Acme"}
    ]
  },
  "sampledTypes": 2,
  "availableTypes": 2,
  "recordsReturned": 2,
  "truncated": false
}

availableTypes always reports the number of eligible vertex and document types in the database, even when types is supplied. When types is omitted, truncated indicates that more than 20 eligible types exist. The generated queries are checked as read-only, and serialized records do not include vertex edge collections.

Searches a dense LSM_VECTOR or sparse LSM_SPARSE_VECTOR index with a pre-computed query vector. ArcadeDB does not generate embeddings; the caller must supply the vector produced by its embedding model.

Parameters:

Parameter Required Description

database

Yes

The name of the database.

indexName

Yes

The name of an LSM_VECTOR or LSM_SPARSE_VECTOR index.

queryVector

Yes

Dense vector values, or sparse weights corresponding to queryIndices.

queryIndices

Sparse only

Sparse dimension identifiers corresponding to the weights in queryVector. Omit to use each queryVector position as its dimension.

k

Yes

Maximum results, from 1 through 1,000.

efSearch

No

Dense-index search beam width. Higher values can improve recall at additional cost. This option is rejected for sparse indexes.

filter

No

Read-only SQL WHERE predicate applied to a bounded candidate set.

sparse

No

Use vector.sparseNeighbors against an LSM_SPARSE_VECTOR index (default: false).

The optional filter is evaluated against each expanded neighbor row. Record properties are flattened into that row, and @rid, @type, record, plus distance for dense results or score for sparse results are also available.

Dense-vector example:

curl -u root:arcadedb-password \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"vector_search","arguments":{"database":"research","indexName":"Paper[embedding]","queryVector":[0.12,-0.08,0.31],"k":20,"efSearch":100,"filter":"publishedYear >= 2024"}}}' \
  http://localhost:2480/api/v1/mcp

Response:

{
  "indexName": "Paper[embedding]",
  "sparse": false,
  "scoring": "distance_lower_is_better:COSINE",
  "candidateLimit": 160,
  "truncated": true,
  "count": 20,
  "results": [
    {
      "rid": "#12:34",
      "type": "Paper",
      "distance": 0.083,
      "properties": {
        "title": "Example paper",
        "publishedYear": 2025
      }
    }
  ]
}

Dense results expose only distance, where lower is better. Sparse results expose only score, where higher is better. The response-level scoring value also records the active dense similarity function or sparse scoring modifier.

Without a filter, candidateLimit equals k. With a filter, ArcadeDB examines up to min(k * 8, 8000) vector candidates before applying the predicate. truncated is true when the returned result window is full (count equals k), so more matches may exist. When it is false, fewer than k matches survived the current candidate window. For a filtered search, that does not prove that no matching vectors exist beyond candidateLimit; increasing k also expands the candidate window until the 8,000-candidate ceiling is reached.

Searches a full-text index and returns the matching records ranked by relevance score. Requires read permission.

Address the index either by indexName, or by typeName with optional properties. When both forms are supplied, indexName wins.

Parameters:

Parameter Required Description

database

Yes

The name of the database.

queryText

Yes

The full-text query. A blank query is rejected.

indexName

Either

The name of the full-text index, for example Article[content] or Article[title,content].

typeName

Either

The type carrying the index, for example Article. An index declared on a supertype is named for the supertype.

properties

No

Indexed properties, used with typeName, given in the order the index declares them.

limit

No

Maximum results (default: 10).

Query syntax:

Form Meaning

+a +b

Both terms required.

a -b

Excludes b.

a b

Matches either term.

"exact phrase"

All terms must appear in the same record. Term order is not enforced.

pre*

Prefix match.

term~

Fuzzy match.

field:term

Restricts to one property of a multi-property index.

term^2

Boosts a term.

Example:

curl -u root:arcadedb-password \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"full_text_search","arguments":{"database":"research","indexName":"Paper[abstract]","queryText":"+graph +retrieval -sql","limit":5}}}' \
  http://localhost:2480/api/v1/mcp

Response:

{
  "indexName": "Paper[abstract]",
  "similarity": "BM25",
  "count": 2,
  "results": [
    {
      "rid": "#12:34",
      "score": 4.71,
      "properties": {
        "title": "Graph retrieval over citation networks"
      }
    }
  ]
}

The similarity field reports whether scores are BM25 or legacy CLASSIC coordination counts. The limit is pushed down per bucket, so the search merges at most bucket count * limit entries rather than every match in the index. Because of that push-down, a record deleted concurrently between the index scan and the record read is skipped without being back-filled, so a search can legitimately return fewer than limit results.

Retrieves records by fusing up to three retrieval legs into a single ranked list: a vector search, an optional full-text search, and an optional graph expansion. Requires read permission. As with vector_search, ArcadeDB does not generate embeddings.

The graph expansion is seeded from the records the other legs found and walks outward from them, so a neighbor of a strong match can itself rank. Expanded records carry the number of hops from their seed and the path back to it.

Parameters:

Parameter Required Description

database

Yes

The name of the database.

vectorIndexName

Yes

The name of an LSM_VECTOR or LSM_SPARSE_VECTOR index.

queryVector

Yes

Dense vector values, or sparse weights corresponding to queryIndices.

k

Yes

Maximum results after fusion, from 1 through 1,000.

queryIndices

Sparse only

Sparse dimension identifiers corresponding to the weights in queryVector.

sparse

No

Use vector.sparseNeighbors against an LSM_SPARSE_VECTOR index (default: false).

efSearch

No

Dense-index search beam width. Rejected for sparse indexes.

filter

No

Read-only SQL WHERE predicate applied to the vector leg’s bounded candidate set.

fulltextIndexName

With fulltextQuery

The full-text index for the second leg.

fulltextQuery

With fulltextIndexName

The full-text query for the second leg.

expand

No

Graph expansion leg. See below.

fusionStrategy

No

RRF, DBSF, or LINEAR (default: RRF).

weights

No

Per-leg fusion weights, keyed by leg name.

Supplying only one of fulltextIndexName and fulltextQuery is rejected: half a leg is always a mistake, and ignoring it silently would return a plausible result set that quietly dropped what the caller asked for.

The expand object:

Field Required Description

edgeTypes

No

Edge types to traverse. Omit to traverse every edge type.

direction

No

out, in, or both (default: out).

maxDepth

No

Hops to walk, from 1 through 3 (default: 1).

An unknown edge type is rejected, listing the available ones. Traversing a name that matches nothing would otherwise return an empty neighborhood without an error, silently degrading the search to plain two-way fusion. maxDepth above 3 is rejected rather than clamped, and the cap is enforced server-side regardless of the value sent. Graph expansion requires a vertex type; requesting it against a document type is rejected.

Fusion strategies:

RRF (Reciprocal Rank Fusion) is rank-only and ignores per-row scores. DBSF and LINEAR normalize per-source scores and therefore require a numeric score on every row of every source.

The graph expansion leg ranks by traversal order and carries no score, so DBSF and LINEAR cannot be combined with expand and that combination is rejected. Both remain available for vector plus full-text fusion.

Weights:

weights accepts vector, fulltext, and expand. The first two default to 1.0 and expand defaults to 0.5, because under RRF a rank-one expanded record would otherwise score exactly what the nearest neighbor scores, letting an arbitrary one-hop neighbor tie the best semantic match.

A weight naming a leg the request does not ask for is rejected, as is an unknown key, a negative value, and a non-numeric value. A weight of 0.0 is accepted: it keeps a leg’s records in the candidate set and in each result’s sources while contributing nothing to the ranking.

Example:

curl -u root:arcadedb-password \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"hybrid_search","arguments":{"database":"research","vectorIndexName":"Paper[embedding]","queryVector":[0.12,-0.08,0.31],"fulltextIndexName":"Paper[abstract]","fulltextQuery":"graph retrieval","expand":{"edgeTypes":["CITES"],"direction":"out","maxDepth":2},"fusionStrategy":"RRF","k":10}}}' \
  http://localhost:2480/api/v1/mcp

Response:

{
  "vectorIndexName": "Paper[embedding]",
  "sparse": false,
  "scoring": "distance_lower_is_better:COSINE",
  "fused": true,
  "fusionStrategy": "RRF",
  "fulltextIndexName": "Paper[abstract]",
  "legs": {
    "vector": { "count": 40 },
    "fulltext": { "indexName": "Paper[abstract]", "similarity": "BM25", "count": 27 },
    "expand": {
      "direction": "out",
      "edgeTypes": ["CITES"],
      "maxDepth": 2,
      "truncated": false,
      "seedCount": 61,
      "seedsTruncated": false,
      "count": 133
    }
  },
  "truncated": true,
  "count": 10,
  "results": [
    {
      "rid": "#12:34",
      "fusedScore": 0.0325,
      "sources": ["vector", "fulltext"],
      "properties": { "title": "Graph retrieval over citation networks" }
    },
    {
      "rid": "#12:99",
      "fusedScore": 0.0161,
      "sources": ["expand"],
      "depth": 1,
      "path": ["#12:34", "#12:99"],
      "properties": { "title": "Citation graphs as retrieval context" }
    }
  ]
}

sources names the legs that contributed to each result, so a fused score can be interpreted rather than taken on faith: it distinguishes a top semantic match from a record that arrived two hops from something relevant. depth and path appear only on results the expansion leg contributed, and path starts at the seed and ends at the result itself.

fused reports whether fusion actually ran. Fusion requires at least two sources, so a request naming neither fulltextQuery nor expand cannot be fused: that response sets fused to false and its results carry the vector leg’s native distance or score instead of fusedScore. The full-text index name and similarity are reported under legs whenever that leg ran, including when it matched nothing.

The response carries several independent budgets. Top-level truncated means the result window filled (count equals k), so raising k may surface more. legs.expand.truncated means the traversal hit its fan-out cap, so the neighborhood was only partly explored; narrowing edgeTypes or maxDepth gives a more complete picture of the part that matters. legs.expand.seedsTruncated means the retrieval legs produced more candidate seeds than the traversal budget accepts, so expansion started from a subset.

For pure two-way fusion without graph expansion, vector.fuse remains available through the query tool.

upsert_entity

Creates or updates a single vertex addressed by a match key, without duplicating it. Requires both insert and update permission, because a merge resolves to a create or an update depending on what already exists.

The vertex is matched, or created, by the matchKeys property/value pairs, and then any setProperties are written. Repeated calls with identical matchKeys resolve to the same vertex. Values are bound as parameters, so they are safe to pass verbatim; identifiers are quoted.

Parameters:

Parameter Required Description

database

Yes

The name of the database.

typeName

Yes

The vertex type. Created automatically if it does not exist.

matchKeys

Yes

property/value pairs used as the match key. Must be non-empty; values should be scalars.

setProperties

No

property/value pairs to write on the matched or created vertex.

Create a UNIQUE index on the matchKeys properties. Without one the match is a full type scan on every call, and two concurrent upserts carrying the same keys can each create a vertex, which is exactly the duplication the tool exists to prevent.

Example:

curl -u root:arcadedb-password \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"upsert_entity","arguments":{"database":"memory","typeName":"Person","matchKeys":{"email":"[email protected]"},"setProperties":{"name":"Ada Lovelace","role":"analyst"}}}}' \
  http://localhost:2480/api/v1/mcp

upsert_relationship

Creates or updates a single directed edge between two vertices, without duplicating it. Requires both insert and update permission.

Each endpoint is matched, or created, by its own match keys; then the edge of type relType between them is matched or created, and any relProperties are written. Repeated calls between the same resolved endpoints with the same relType resolve to the same edge.

Parameters:

Parameter Required Description

database

Yes

The name of the database.

fromType

Yes

The source vertex type. Created automatically if it does not exist.

fromMatchKeys

Yes

property/value pairs identifying the source vertex. Must be non-empty.

toType

Yes

The destination vertex type. Created automatically if it does not exist.

toMatchKeys

Yes

property/value pairs identifying the destination vertex. Must be non-empty.

relType

Yes

The edge type. Created automatically if it does not exist.

relProperties

No

property/value pairs to write on the matched or created edge.

As with upsert_entity, create a UNIQUE index on each endpoint’s match-key properties. Without one each endpoint match is a full type scan, and concurrent calls can create duplicate endpoints.

Example:

curl -u root:arcadedb-password \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"upsert_relationship","arguments":{"database":"memory","fromType":"Person","fromMatchKeys":{"email":"[email protected]"},"toType":"Project","toMatchKeys":{"code":"ANALYTICAL-ENGINE"},"relType":"WORKS_ON","relProperties":{"since":1843}}}}' \
  http://localhost:2480/api/v1/mcp

The edge properties are written in a trailing assignment, so the edge is keyed by its endpoints and type alone and is never duplicated by a change to its properties.

profiler_start

Starts the query profiler, which records every query with its execution time and plan. Requires admin permission.

The profiler auto-stops after timeoutSeconds, so a session left running does not profile indefinitely. Use profiler_stop to end it early and collect the results, or profiler_status to check progress while it runs.

Parameters:

Parameter Required Description

timeoutSeconds

No

Recording duration, from 1 through 3,600 seconds (default: 60). The profiler stops itself when it elapses.

Example:

curl -u root:arcadedb-password \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"profiler_start","arguments":{"timeoutSeconds":120}}}' \
  http://localhost:2480/api/v1/mcp

Response:

{
  "status": "started",
  "timeoutSeconds": 120,
  "message": "Query profiler started. It will auto-stop after 120 seconds. Use profiler_stop to stop early and retrieve results."
}

Only one profiling session runs at a time. Starting the profiler while it is already recording does not restart it, extend it, or raise an error: the call returns status: "already_recording" and leaves the session untouched, so an agent retrying a start cannot silently discard the data collected so far.

profiler_stop

Stops the query profiler and returns the data captured during the session. Requires admin permission. Takes no parameters.

Results are aggregated per query and include execution counts, timing (minimum, maximum, average, and p99), and execution-plan step costs. They are also written to disk, so a session can be retrieved after the fact.

Example:

curl -u root:arcadedb-password \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"profiler_stop","arguments":{}}}' \
  http://localhost:2480/api/v1/mcp

If the profiler was not recording, the call returns status: "not_recording" rather than an error, so stopping an already-stopped profiler is safe to retry.

profiler_status

Reports whether the profiler is recording and returns the current or most recent results. Requires admin permission. Takes no parameters.

Where profiler_stop ends the session, this reads it without disturbing it, so it is the safe call for an agent that wants to check progress mid-session. Alongside the captured queries and their timing statistics, the response carries server metric snapshots taken during the session.

Example:

curl -u root:arcadedb-password \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"profiler_status","arguments":{}}}' \
  http://localhost:2480/api/v1/mcp

get_server_settings

Returns the server-level configuration settings with their current values, defaults, and descriptions. Takes no parameters.

This tool requires read permission, not admin permission, even though it belongs to the admin tool profile. Profile membership and permission gating are independent: see Tool Profiles.

Only settings whose scope is the server are returned; database-scoped settings are excluded.

Example:

curl -u root:arcadedb-password \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"get_server_settings","arguments":{}}}' \
  http://localhost:2480/api/v1/mcp

Response:

{
  "settings": [
    {
      "key": "arcadedb.asyncWorkerThreads",
      "value": 7,
      "description": "Number of asynchronous worker threads. 0 (default) = available cores minus 1",
      "overridden": true,
      "default": 0
    },
    {
      "key": "arcadedb.ha.clusterToken",
      "value": "*****",
      "description": "Token to authenticate a server joining the cluster",
      "overridden": true,
      "default": "*****"
    }
  ]
}

overridden is true when the setting has been given an explicit value rather than inheriting its default.

Secret settings are masked as * in both value and default. The rule covers the HA cluster token and every key containing password, and it is applied by the server rather than by the client, so a secret is never serialized into the response in the first place.

set_server_setting

Updates a server configuration setting at runtime. Requires admin permission.

Changes take effect immediately. Whether they survive a restart depends on the individual setting, so treat this as a runtime adjustment rather than a way to persist configuration. Call get_server_settings first to see the available keys and their current values.

Parameters:

Parameter Required Description

key

Yes

The configuration key, for example arcadedb.asyncWorkerThreads.

value

Yes

The new value, as a string.

An unknown key is rejected by name rather than silently accepted, so a typo cannot masquerade as a successful update.

Example:

curl -u root:arcadedb-password \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"set_server_setting","arguments":{"key":"arcadedb.asyncWorkerThreads","value":"8"}}}' \
  http://localhost:2480/api/v1/mcp

Response:

{
  "key": "arcadedb.asyncWorkerThreads",
  "previousValue": "7",
  "newValue": "8",
  "message": "Setting 'arcadedb.asyncWorkerThreads' updated successfully."
}

The response reports the replaced value so a change can be reverted without a separate read.

For a secret setting, previousValue is masked as *, using the same rule get_server_settings applies. Otherwise the setter would disclose through the value it replaced exactly what the getter withholds. The mask depends on the setting rather than on whether it currently holds a value, so an unset secret cannot be distinguished from a set one.

Resources

Besides tools, the MCP server exposes a read-only Resources surface. Every database publishes its schema at:

arcadedb://{database}/schema

The resource is served with the MIME type application/json and its text payload is the same document that the get_schema tool returns. A client that reads the resource at session start therefore obtains the schema without spending a tool call.

Example: list resources

curl -u root:arcadedb-password \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"resources/list"}' \
  http://localhost:2480/api/v1/mcp

Response:

{
  "resources": [
    {
      "uri": "arcadedb://mydb/schema",
      "name": "mydb schema",
      "description": "Schema of the ArcadeDB database 'mydb': types (vertex, edge, document), properties, indexes, inheritance.",
      "mimeType": "application/json"
    }
  ]
}

Example: read a resource

curl -u root:arcadedb-password \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":2,"method":"resources/read","params":{"uri":"arcadedb://mydb/schema"}}' \
  http://localhost:2480/api/v1/mcp

Response:

{
  "contents": [
    {
      "uri": "arcadedb://mydb/schema",
      "mimeType": "application/json",
      "text": "{\"database\":\"mydb\",\"types\":[...]}"
    }
  ]
}

Resources are gated by the same policy as reads. allowReads must be enabled globally, the effective per-database policy must allow the authenticated principal to read that database, and ArcadeDB’s native database authorization applies as usual. Tool profiles do not filter resources: a rag or admin profile changes the tool list only.

resources/list is a discovery call that most clients issue unprompted at session start, so a denial is reported as an empty resources array rather than as an error, and databases the principal cannot read are omitted. resources/read rejects an unknown database, an unauthorized database, and a malformed URI identically with JSON-RPC error -32002 (Resource not found), so the surface cannot be used to probe which databases exist. When allowReads is disabled, resources/read returns error -32600.

Prompts

The MCP server also exposes a Prompts surface: guided templates that steer a model through a task using the retrieval and agent-memory tools, instead of leaving it to hand-write vector or MERGE syntax. A client typically offers them to the user as named commands.

Prompt Purpose

graphrag_query

Answer a question over a database with its retrieval tools, citing the records the answer came from.

build_knowledge_graph

Extract entities and relationships from text and write them into a database without creating duplicates.

Both prompts take two required arguments.

Prompt Argument Description

graphrag_query

database

The name of the database to search.

question

The question to answer from the contents of the database.

build_knowledge_graph

database

The name of the database to write into.

sourceText

The text to extract entities and relationships from.

The rendered messages are static text with the arguments substituted verbatim, so prompts/get opens no database and reads no data.

graphrag_query directs the model to load the schema first, choose between vector_search, full_text_search, hybrid_search, and query according to the shape of the question, supply its own embedding vector because ArcadeDB does not generate one, and cite every claim with the @rid it came from.

build_knowledge_graph centers on match-key selection, which is what keeps repeated extraction from duplicating vertices. It directs the model to read the existing schema and reuse its types, choose the smallest stable match key per entity, prefer one backed by a UNIQUE index and report a missing one rather than creating it, then call upsert_entity once per entity and upsert_relationship once per relationship, reusing exactly the same match keys for the endpoints.

Availability

A prompt is a script that names tools by name, so it is offered only when the caller can actually run it. Each prompt is listed, and rendered, only when both conditions hold:

  • every tool its text names is allowed by the caller’s effective tool profile, and

  • the permissions those tools require are granted server-globally.

Prompt Tools named Permissions required

graphrag_query

get_schema, query, vector_search, full_text_search, hybrid_search

allowReads

build_knowledge_graph

get_schema, upsert_entity, upsert_relationship

allowReads, allowInsert, allowUpdate

Neither prompt is therefore available under the admin profile, which contains none of the retrieval or upsert tools. Under rag and all, graphrag_query is available whenever reads are permitted, and build_knowledge_graph additionally requires the two write permissions, which are false by default.

These checks are evaluated against the server-global permission flags, because no database is resolved while a prompt is rendered. Per-database overrides still apply to every tool call the model subsequently makes.

Like resources/list, prompts/list is a discovery call most clients issue unprompted at session start, so a caller entitled to no prompts receives an empty prompts array rather than an error. Availability is then re-checked by prompts/get, for the same reason tools/call re-checks the profile after tools/list has already filtered: an unavailable prompt is refused with error -32600 even when it is requested by name. An unknown prompt name, or a missing argument, returns -32602.

Example: list prompts

curl -u root:arcadedb-password \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"prompts/list"}' \
  http://localhost:2480/api/v1/mcp

Response:

{
  "prompts": [
    {
      "name": "graphrag_query",
      "description": "Answer a question over an ArcadeDB database using its retrieval tools, citing the records the answer came from.",
      "arguments": [
        {"name": "database", "description": "The name of the database to search", "required": true},
        {"name": "question", "description": "The question to answer from the contents of the database", "required": true}
      ]
    }
  ]
}

Example: render a prompt

curl -u root:arcadedb-password \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":2,"method":"prompts/get","params":{"name":"graphrag_query","arguments":{"database":"research","question":"Which papers cite the original RRF paper?"}}}' \
  http://localhost:2480/api/v1/mcp

Response:

{
  "description": "Answer a question over an ArcadeDB database using its retrieval tools, citing the records the answer came from.",
  "messages": [
    {
      "role": "user",
      "content": {
        "type": "text",
        "text": "Answer the following question using the ArcadeDB database 'research'.\n\n<question>\n..."
      }
    }
  ]
}

Each argument is substituted inside a delimiter block that marks it as data rather than procedure. Because the value is substituted verbatim, a value that itself carries the closing tag would end that block early and have the remaining text read as instructions. The server therefore suffixes both delimiters with an unpredictable token whenever the value contains such a tag, which moves the boundary instead of altering the value. An ordinary argument carries no such tag and renders against the constant delimiters shown above.

Transport

The same protocol surface is served over two transports: HTTP, and standard input and output. Tools, resources, prompts, and every permission and profile rule behave identically on both; only the envelope differs.

HTTP

The HTTP endpoint follows the Streamable HTTP transport rules of MCP 2025-03-26.

HTTP methods. JSON-RPC is carried over POST only. The server does not offer a Server-Sent Events stream, so any other method (including GET) is answered with 405 Method Not Allowed and an Allow: POST header.

Requests and notifications. A request must carry an id that is a string or an integer; null, fractional and structured ids are rejected with JSON-RPC error -32600. A notification is a message with a method and no id: the server never answers one, and a POST that carried only notifications returns 202 Accepted with an empty body.

Before version 26.8.1 a notification-only POST returned 204 No Content, and notifications other than notifications/initialized were answered with a Method not found error carrying a null id.

Batches. A top-level JSON array is accepted on both the HTTP and the stdio transport. Every element is dispatched independently and only the elements that are requests contribute a response, so a batch of notifications alone yields 202 Accepted with no body and a mixed batch yields an array holding just the request responses.

curl -u root:arcadedb-password \
  -X POST \
  -H "Content-Type: application/json" \
  -d '[{"jsonrpc":"2.0","id":1,"method":"ping"},{"jsonrpc":"2.0","id":2,"method":"tools/list"}]' \
  http://localhost:2480/api/v1/mcp

Origin validation. To mitigate DNS rebinding, a request carrying an Origin header is accepted only when the origin is a loopback address, matches the host the request was addressed to, or is listed in allowedOrigins. Anything else is rejected with 403. A request with no Origin is accepted, because non-browser MCP clients do not send one and the header is attached by the browser precisely in the case the check is meant to catch. Setting allowedOrigins to ["*"] accepts any origin and disables this protection: use it only when the endpoint is fronted by a proxy that performs its own origin control.

Stdio

Clients that launch their MCP servers as a child process, rather than connecting to one over the network, speak JSON-RPC 2.0 over standard input and output with one message per line. The distribution ships that transport as bin/mcp-stdio.sh, which starts an ArcadeDB server and serves MCP on its stdin and stdout.

export ARCADEDB_SETTINGS="-Darcadedb.server.rootPassword=arcadedb-password"
bin/mcp-stdio.sh

The root password is mandatory: the process authenticates as root and exits with an error if arcadedb.server.rootPassword is unset. Because the client owns stdout, the server redirects its own logging to stderr so that log lines cannot corrupt the JSON-RPC stream.

Stdio mode enables the MCP server for the lifetime of the process regardless of the enabled flag, since the client started it precisely to speak MCP. Everything else in config/mcp-config.json still applies unchanged: permissions, tool profile, per-database overrides, and the allowedUsers list, which must admit root. Origin validation does not apply, because there is no HTTP request to carry an Origin header.

A line holding a top-level JSON array is dispatched as a batch, exactly as over HTTP. A message that produces no response, such as a notification or a batch of them, is answered with no output line at all.

Example client configuration
{
  "mcpServers": {
    "arcadedb": {
      "command": "/opt/arcadedb/bin/mcp-stdio.sh",
      "env": {
        "ARCADEDB_SETTINGS": "-Darcadedb.server.rootPassword=arcadedb-password"
      }
    }
  }
}

Security

The MCP server enforces multiple layers of security:

  • Authentication — All requests require valid ArcadeDB credentials (Basic Auth or Bearer token).

  • User authorization — Users can only access databases they are authorized for in the ArcadeDB security configuration.

  • MCP permissions — The MCP configuration controls which operations are allowed (reads, inserts, updates, deletes, schema changes, admin).

  • User allow-list — The allowedUsers setting restricts which users can access the MCP server.

  • Per-database restrictions — Optional database entries can further restrict operations and users without granting authority above the global policy.

  • Tool profiles — Hidden tools are removed from discovery and rejected if invoked directly; profiles do not grant permissions.

  • Per-principal profiles — An individual principal can be narrowed further; the effective profile is the intersection with the global one. See Per-Principal Profiles.

  • Prompt gating — A prompt is offered, and rendered, only when the tools its text names are visible to the caller and the permissions those tools need are granted. See Prompts.

  • Origin validation — Cross-origin browser requests are rejected to mitigate DNS rebinding. See [mcp-origin].

  • Semantic analysis — Queries and commands are analyzed to detect their operation type and enforce the appropriate permission.

Using with AI Clients

The ArcadeDB MCP server is compatible with any MCP-compliant client. To connect an AI assistant to ArcadeDB, configure the client with the MCP endpoint URL and authentication credentials, or launch bin/mcp-stdio.sh as a child process for clients that prefer the stdio transport.

Example configuration for an MCP client:

{
  "mcpServers": {
    "arcadedb": {
      "url": "http://localhost:2480/api/v1/mcp",
      "headers": {
        "Authorization": "Basic cm9vdDphcmNhZGVkYi1wYXNzd29yZA=="
      }
    }
  }
}

Once connected, the AI client automatically discovers the available tools via tools/list and can use them to explore databases, query data, and execute commands within the configured permissions. A client that supports the other two surfaces should also read arcadedb://{database}/schema from Resources at session start, which supplies the schema without spending a tool call, and offer the Prompts its profile exposes.