Mirajv1.0
EN

20. MCP server: AI assistants on Miraj

The Miraj MCP server is an endpoint built into miraj-server that lets AI assistants supporting the Model Context Protocol browse, read, modify or administer databases. Access uses a token created in SQL, bound to an account and capped at an access level. It is served over HTTP on port 7008 and disabled until you enable it.

The Miraj MCP server lets an artificial intelligence assistant (Claude, ChatGPT, DeepSeek, or any client of the MCP protocol, Model Context Protocol) work on the databases of a Miraj server: browse their structure, read the data, modify it or administer the server, according to what you grant it. The assistant needs neither a driver nor a password: it receives a token created in SQL, bound to an account and capped at an access level.

The MCP endpoint is built into miraj-server: it is the same process and the same engine as the main port (7007), on a separate HTTP port (7008 by default), disabled until you open it.

  AI assistant (Claude, ChatGPT, DeepSeek…)
         │   JSON-RPC 2.0 over HTTP or HTTPS: POST http(s)://host:7008/mcp
         │   Authorization: Bearer mjt_…
         ▼
  ┌──────────────────────────── miraj-server ────────────────────────────┐
  │  port 7007: Miraj network protocol (applications, miraj-cli, …)      │
  │  port 7008: MCP endpoint (--mcp ON)                                  │
  │     token ─► account ─► Miraj session capped at the token level      │
  │     tools ─► SQL statements ─► privileges, locks, triggers,          │
  │               foreign keys, journal, transactions, replication       │
  └──────────────────────────────────────────────────────────────────────┘

Everything goes through the engine as an ordinary SQL statement: account privileges, locks, CHECK constraints, foreign keys, triggers, write journal and replication apply exactly as for a client application. Nothing is ever written directly to the data files.

20.1 What is the MCP server for?#

MCP is an open protocol through which an AI assistant discovers tools (functions it can call, with their arguments described in JSON) and resources (documents it can read). Miraj exposes sixteen tools:

  • structure tools: list databases and their objects, describe a table, read a CREATE definition, search for a table or column by name;
  • read tools: read rows with a structured condition, read a row by its primary key, count, display an execution plan;
  • write tools: insert, modify, delete rows;
  • administration tools: create a database or a table, drop an object;
  • a free SQL tool, execute_sql, for everything else.

The structured tools are preferable to free SQL for everyday operations: the assistant supplies column names and JSON values, never SQL text; names are checked against the table and values become literals, which makes any injection impossible.

Typical uses: let a development assistant explore an application's schema, write and check queries, prepare test data, diagnose an execution plan, or, with an ADMIN token on a working database, create the tables of a prototype itself.

20.2 Which editions include the MCP server?#

EditionMCP endpoint
EnterpriseAvailable
ClusterAvailable
DeveloperAvailable (with the edition's own limits: one core, 20 GB, 24 hours)
ExpressNot available: with mcp = ON, the server starts, displays a warning and does not open the MCP port; CREATE MCP TOKEN is refused (error 9001 "MCP is not available in the Miraj Express edition")

20.3 How do I enable and configure the MCP server?#

20.3.1 Variables#

The endpoint is configured like the main port: default value, then the miraj_config.xml file, then the command line, the command line taking precedence (see 11.2). It is disabled by default.

Variable (miraj_config.xml)ArgumentDefaultValuesRole
mcp--mcp ON|OFFOFFON, OFF (also true, false, 1, 0)Opens the endpoint or not
mcp_port--mcp-port <port>70080 to 65535TCP port of the endpoint, distinct from port; 0: port chosen by the system, displayed at startup
mcp_bind--mcp-bind <address>127.0.0.1IP address or nameListening address, independent of bind; outside the loopback, TLS is mandatory (20.3.5)
mcp_idle_timeout--mcp-idle-timeout <s>3001 to 31,536,000 secondsAn MCP session left without a request for this delay is closed; its open transaction is rolled back
mcp_statement_timeout--mcp-statement-timeout <s>300 to 31,536,000 seconds, fractions allowed (2.5); 0: no limitMaximum duration of a tool call (errors 1969 or 1317 beyond it, see 20.13)
mcp_max_rows--mcp-max-rows <n>50001 to 1,000,000Cap on the rows returned by a result, whatever the requested limit

The loopback is written exactly 127.0.0.1, localhost or ::1; any other address, including 0.0.0.0 (all interfaces), is considered public.

The mcp* variables appear in miraj_default.xml (regenerated at each startup) and in the MCP group of the graphical configuration editor, which flags an mcp_port equal to port and a public mcp_bind without a certificate.

20.3.2 Through the miraj_config.xml file#

<?xml version="1.0" encoding="UTF-8"?>
<miraj>
    <!-- MCP endpoint open (AI assistants, CREATE MCP TOKEN tokens) -->
    <mcp>ON</mcp>
    <mcp_port>7008</mcp_port>
    <mcp_bind>127.0.0.1</mcp_bind>
    <mcp_idle_timeout>300</mcp_idle_timeout>
    <mcp_statement_timeout>30</mcp_statement_timeout>
    <mcp_max_rows>5000</mcp_max_rows>
</miraj>

20.3.3 Through the command line#

miraj-server.exe --root D:\miraj\data --log --mcp ON
miraj-server.exe --root D:\miraj\data --log --mcp ON --mcp-port 7100 --mcp-max-rows 1000

Always start the server with --log: session openings, tool calls and refusals are then recorded (20.13). An argument takes precedence over the file in both directions: --mcp OFF closes an endpoint enabled by the file, --mcp-port 0 replaces the file's port.

20.3.4 Startup messages and configuration errors#

SituationMessageEffect
Endpoint openMiraj <version> <edition> - MCP endpoint on 127.0.0.1:7008. (followed by , TLS with a certificate)Port opened after the main port
mcp = OFF but an mcp_* variable givenNote: mcp_* variables ignored, the MCP endpoint is disabled (mcp = OFF; --mcp ON to open it).Server started, MCP port closed
Express editionWARNING: the MCP endpoint requires the Enterprise, Cluster or Developer edition; MCP port not opened.Server started, MCP port closed
mcp_port equal to portMCP endpoint: mcp_port (7007) is also the main port; choose another port (--mcp-port); server stopped.Server stopped (exit code 2)
mcp_bind outside the loopback without a certificateMCP endpoint: listening on 0.0.0.0 outside the loopback without TLS refused; provide a certificate (--tls-cert, --tls-key) or keep mcp_bind on 127.0.0.1; server stopped.Server stopped (code 2)
Port already takenMCP endpoint: unable to listen on 127.0.0.1:7008: … ; server stopped.Server stopped (code 2)
Value out of rangeInvalid value for mcp_port: 70000 (from 0 to 65535), Invalid value for mcp_max_rows: 0 (from 1 to 1000000)…; --mcp: "maybe" unknown (ON, OFF)Server stopped (code 2)

The port and TLS rules are checked before the databases are opened: a wrong configuration is never discovered after a long load.

20.3.5 TLS (HTTPS)#

The endpoint uses the same certificate as the main port (tls_cert and tls_key in miraj_config.xml, or --tls-cert and --tls-key, see chapter 11):

  • without a certificate, the endpoint speaks plain HTTP and can only listen on the loopback;
  • with a certificate, all MCP connections are encrypted, including on the loopback: the address becomes https://host:7008/mcp and a plain client gets no response. --require-tls and --allow-plain-with-tls only concern the main port;
  • outside the loopback (public mcp_bind), the certificate is mandatory: without it the server refuses to start.
miraj-server.exe --root D:\miraj\data --log --mcp ON --mcp-bind 0.0.0.0 ^
                 --tls-cert D:\miraj\tls\cert.pem --tls-key D:\miraj\tls\key.pem

The client must trust the certificate: with a certificate issued by an internal authority or self-signed, declare it to the client (for clients written in Node.js, such as many command-line clients, the NODE_EXTRA_CA_CERTS environment variable points to a PEM file of additional authorities).

20.4 How do MCP tokens work?#

An assistant authenticates with a token: a secret mjt_… presented in the HTTP header Authorization: Bearer mjt_…. The token belongs to a Miraj account, whose privileges it takes, and carries an access level that caps them (20.5). It is revoked without touching the account.

20.4.1 CREATE MCP TOKEN#

CREATE MCP TOKEN name [FOR account]
       ACCESS {STRUCTURE | READ | WRITE | ADMIN}
       [DATABASES (db1, db2, ...)]
       [EXPIRE {NEVER | INTERVAL n DAY}]
  • name: identifier, backtick-quoted identifier or string, 1 to 64 characters (1470 beyond). It is unique on the server, case-insensitively, whatever the account (error 9035 if it is taken).
  • FOR account: 'user'@'host' or user, as in CREATE USER; without FOR, the token belongs to the session's account. An unknown role or account is refused (1396).
  • ACCESS: level of the token (20.5). Any other word is a syntax error (1064).
  • DATABASES (…): scope of the token; without this clause, all the databases the account can reach. Names are not checked at creation: a database created later under that name enters the scope.
  • EXPIRE INTERVAL n DAY: the token expires n days after its creation (n ≥ 1; 0 is a syntax error); EXPIRE NEVER (default): never.
  • The clauses that follow ACCESS may be written in any order, each once.

The statement returns one row with the secret, displayed this one time only:

CREATE MCP TOKEN assistant_ventes FOR 'app'@'localhost'
       ACCESS READ DATABASES (ventes, catalogue) EXPIRE INTERVAL 90 DAY;
+------------------+------------------------------+-------------------+--------+---------------------+
| name             | token                        | account           | access | expires             |
+------------------+------------------------------+-------------------+--------+---------------------+
| assistant_ventes | mjt_Xq7mPt3hKc9WbZ2rNf8sLd4v | 'app'@'localhost' | READ   | 2026-12-24 10:15:00 |
+------------------+------------------------------+-------------------+--------+---------------------+

The secret is mjt_ followed by 24 characters drawn by the system generator. Miraj keeps only its SHA-256 fingerprint, in the encrypted account vault: a lost secret cannot be recovered, you must drop the token and create another. expires is in UTC, NULL for a token without expiration.

20.4.2 SHOW MCP TOKENS#

SHOW MCP TOKENS;
+------------------+------+-----------+--------+-------------------+---------------------+---------------------+
| Name             | User | Host      | Access | Databases         | Created             | Expires             |
+------------------+------+-----------+--------+-------------------+---------------------+---------------------+
| assistant_ventes | app  | localhost | READ   | ventes,catalogue  | 2026-09-25 10:15:00 | 2026-12-24 10:15:00 |
+------------------+------+-----------+--------+-------------------+---------------------+---------------------+

One row per token, sorted by name; Databases is NULL without a scope, Expires NULL without expiration; dates in UTC. The secret and its fingerprint are never shown. An account that has the CREATE USER privilege sees all the tokens of the server, the others only those of their own account. Miraj does not keep a "last used" date for tokens.

20.4.3 DROP MCP TOKEN#

DROP MCP TOKEN assistant_ventes;
DROP MCP TOKEN IF EXISTS assistant_ventes;   -- warning 9036 if it does not exist

Revocation is immediate: the token is rechecked on every HTTP request, so the next request of an already open session receives 401. Without IF EXISTS, an unknown token gives error 9036.

20.4.4 Required rights#

OperationRight
Create or drop a token of your own accountNone
Create or drop a token of another accountCREATE USER (1227 otherwise); plus SYSTEM_USER if that account holds SYSTEM_USER
SHOW MCP TOKENSNone (your own tokens); CREATE USER to see them all

Tokens are managed through the main port (miraj-cli, application, administration tool), never from an MCP session: even with an ADMIN token, CREATE MCP TOKEN and DROP MCP TOKEN are refused there (9034). A limited token can therefore never forge a broader token. SHOW MCP TOKENS is only allowed there at the ADMIN level.

20.4.5 Token and account#

  • Privileges: the token grants none; the session has those of the account (and of its default roles), reduced by the level and scope of the token.
  • Account host: the address of the MCP client must be accepted by the account's host, as for a password connection. A token of 'app'@'localhost' can only be used from the server machine; for an assistant on another machine, bind the token to an account whose host accepts that machine ('app'@'10.0.0.%', for example).
  • Locked account (ALTER USER … ACCOUNT LOCK): its tokens are refused (3118 in the journal, 401 for the client) until it is unlocked.
  • DROP USER deletes the account's tokens.
  • Storage: tokens are kept in the account vault; they survive a restart and, under Cluster, follow the accounts.

20.4.6 Token errors#

CodeCase
9001CREATE MCP TOKEN in the Express edition
9034Token management from an MCP session; SHOW MCP TOKENS below the ADMIN level
9035Token name already taken on the server
9036DROP MCP TOKEN of an unknown token (warning with IF EXISTS)
1396Unknown account, or role, in FOR
1227Token of another account without CREATE USER
1470Empty token name or one longer than 64 characters
1064Unknown level, EXPIRE INTERVAL 0 DAY

At connection, an unknown secret, an expired token, an account that is a role or whose host does not accept the client give 1045, a locked account 3118; in all cases the MCP client receives a 401 without detail, the precise error being recorded only in the journal.

20.5 Which access levels exist?#

Four levels, from the most restricted to the broadest: STRUCTURE < READ < WRITE < ADMIN.

20.5.1 Tools by level#

tools/list shows the assistant only the tools of its level; calling a tool above it is refused (JSON-RPC error -32602, 20.11).

LevelTools
STRUCTURElist_databases, list_objects, describe_table, show_create, search_schema
READ+ read_rows, get_row, count_rows, explain, execute_sql
WRITE+ insert_rows, update_rows, delete_rows
ADMIN+ create_database, create_table, drop_object (all sixteen tools)

20.5.2 Allowed and refused statements#

Each statement executed by the session (through execute_sql, through a tool, or inside a routine or a trigger) requires a level according to its nature; above the token's level, it is refused with error 9034:

LevelAllowedRefused (9034)
STRUCTURESHOW, DESCRIBE, USE, session SET, transactions, SELECT without a table or on information_schema onlyAny data read: SELECT on a table, including in a subquery (SET @n = (SELECT …)), EXPLAIN, CALL; any write
READ+ SELECT, SELECT … INTO variables, EXPLAIN, CALL, CHECK TABLE, LOCK TABLES … READ, REFRESH VIEW, LISTEN, UNLISTEN, WAIT FOR CHANGES, SHOW LISTENERSINSERT, UPDATE, DELETE, REPLACE, LOAD DATA, NOTIFY, temporary tables, and any write in the body of a routine or trigger
WRITE+ INSERT, UPDATE, DELETE, REPLACE, LOAD DATA, NOTIFY, LOCK TABLES … WRITE, CREATE / DROP TEMPORARY TABLEDDL (CREATE, ALTER, DROP, RENAME of databases, tables, views, routines, triggers, events), TRUNCATE, REPAIR / OPTIMIZE TABLE, accounts and GRANT / REVOKE, BACKUP / RESTORE, SET GLOBAL, KILL, FLUSH, SELECT … INTO OUTFILE
ADMINEverything the account allowsMCP token management (always)

Two session settings are reserved to the ADMIN level: SET max_statement_time and SET max_execution_time, because the endpoint sets the mcp_statement_timeout delay there. SET ROLE remains allowed at all levels: the privileges of the activated roles remain reduced to the token's level.

The message names the statement and the level:

ERROR 9034 (HY000): INSERT is not allowed at MCP access level READ

20.5.3 A cap added on top of privileges#

The level never grants a privilege: it removes those that exceed it. An MCP session can only do what its account can do and what its level allows. Even root is capped by a READ token; an ADMIN token of an account that only has SELECT on a table reads only that table.

Concretely, the effective privileges of the session are reduced to those of the level:

LevelPrivileges kept
STRUCTURESHOW VIEW, SHOW DATABASES, REFERENCES
READ+ SELECT, EXECUTE, LOCK TABLES
WRITE+ INSERT, UPDATE, DELETE, CREATE TEMPORARY TABLES
ADMINAll those of the account

An object on which the account has a privilege removed by the level remains visible: a STRUCTURE token describes a table the account can read, without being able to read it. Under ADMIN, the privileges removed by the level (PROCESS, FILE…) come back; below it, SHOW PROCESSLIST thus shows only the account's own sessions.

20.5.4 The DATABASES scope#

With DATABASES (…), the other databases no longer exist for the session, even for an account that has all global privileges:

  • SHOW DATABASES, list_databases, search_schema and information_schema do not show them;
  • naming them is refused as if the account had no right on them (1044, 1142), USE other_db included;
  • privileges granted at the global level (ON *.*) apply on each database of the scope, without global SHOW DATABASES or global GRANT option; CREATE DATABASE of a database outside the scope is refused (1044).

20.5.5 Routines, triggers and views#

  • The body of a procedure, a function or a trigger is subject to the session's level, statement by statement: with a READ token, CALL of a procedure that inserts is refused (9034) at the moment of the INSERT, as is a SELECT that calls a function that writes. PREPARE passes, EXECUTE is checked on the prepared statement.
  • With a WRITE token, triggers fire normally; a procedure that does DDL is refused.
  • Definer rights (view, SQL SECURITY DEFINER routine) are not reduced: a view reads what its definer allows it, as for any account. The statements of its body remain subject to the level. Take this into account before opening to a token a database that contains views or routines defined by a broader account.
  • A refusal occurs before the implicit commit of a DDL: an open transaction is not committed by a refused CREATE TABLE.

20.6 How do I connect an MCP client?#

The endpoint speaks the MCP "Streamable HTTP" transport: a single address, http://host:port/mcp (or https://… with a certificate), and the token in the Authorization header.

20.6.1 Generic MCP client (JSON file)#

Most MCP clients describe their servers in a JSON file. For example, the .mcp.json file of a project:

{
  "mcpServers": {
    "miraj": {
      "type": "http",
      "url": "https://db-server.example.local:7008/mcp",
      "headers": {
        "Authorization": "Bearer mjt_Xq7mPt3hKc9WbZ2rNf8sLd4v"
      }
    }
  }
}

The names of the keys (type, transport, headers…) vary from one client to another: refer to its documentation. Two constants: the transport is HTTP ("streamable HTTP"), and the Authorization: Bearer <secret> header must accompany every request. The secret gives access to your data: do not put this file under version control with the secret in clear (prefer, if the client allows it, an environment variable).

20.6.2 Clients that can only launch a process (stdio)#

Some clients only speak MCP to a process they launch (stdio transport). A miraj-mcp bridge (stdio to HTTP, token in an environment variable) is planned but not yet delivered. In the meantime, use a client that accepts the HTTP transport, or a third-party stdio-to-HTTP bridge able to forward the Authorization header (not provided or validated by Miraj).

20.7 Tool reference#

20.7.1 Shape of responses and errors#

A tool call (tools/call) always returns:

{
  "content": [{"type": "text", "text": "…the same JSON as structuredContent, as text…"}],
  "structuredContent": { … },
  "isError": false
}
  • structuredContent follows the tool's output schema (outputSchema); content[0].text is its text copy, for clients that do not read structured results.
  • An SQL error or refused arguments are not protocol errors: the call succeeds with isError: true and an error field, so that the assistant reads the error and corrects its call:
{"results": [], "warnings": [],
 "error": {"code": 9034, "sqlstate": "HY000", "message": "INSERT is not allowed at MCP access level READ"}}
  • Malformed arguments (unknown argument, wrong type, missing value, unknown operator…) give error 1210 (Incorrect arguments to read_rows ('where' is required; …)), before any execution. An unknown column name gives 1054.
  • Read results all have the same shape (read_rows, get_row, explain, execute_sql):
{
  "results": [
    {"columns": [{"name": "id", "type": "int"}, {"name": "nom", "type": "varchar(20)"}],
     "rows": [[2, "cahier"]],
     "row_count": 1, "truncated": true,
     "affected_rows": null, "last_insert_id": null}
  ],
  "warnings": []
}

truncated is true when rows were not returned (row or size limit); warnings lists at most 64 warnings {code, message}.

  • Common safeguards: at most mcp_max_rows rows per result, and about 340 KB of rows as JSON per call (the whole response stays under 1 MB); beyond that, truncated: true.
  • Visibility: tools see what SHOW would show the capped account. An invisible database gives 1044; a table on which the account has no right gives 1142, whether or not it exists (its existence is not revealed); then 1049 (unknown database) and 1146 (unknown table). information_schema is not served by the structured tools (1210): query it through execute_sql.
  • Database, table and column names are compared case-insensitively; results return the exact name.

20.7.2 list_databases — STRUCTURE#

Lists the databases visible to the token (account privileges and DATABASES scope), without information_schema.

ArgumentTypeRequiredRole
(none)

Result: {"databases": ["catalogue", "ventes"]}.

{"jsonrpc": "2.0", "id": 2, "method": "tools/call", "params": {"name": "list_databases", "arguments": {}}}

20.7.3 list_objects — STRUCTURE#

Lists the objects of a database: tables (with an estimated row count), views, procedures, functions, triggers, events. An object on which the account has no right is not listed; routines and events require a right on the database, a trigger is visible with its table.

ArgumentTypeRequiredRole
databasestringyesDatabase
kindtable, view, procedure, function, trigger, eventnoA single kind of object (1210 for another value)

Result: {"database": …, "objects": [ … ]}, each object with name and kind, plus rows (tables: physical rows minus deleted rows), table, timing (BEFORE, AFTER) and event (INSERT, UPDATE, DELETE) for a trigger, status for an event, comment for a routine or an event.

{"name": "list_objects", "arguments": {"database": "ventes"}}
{"database": "ventes", "objects": [
  {"name": "article", "kind": "table", "rows": 3},
  {"name": "v_stock", "kind": "view"},
  {"name": "maj_stock", "kind": "trigger", "table": "ligne", "timing": "AFTER", "event": "INSERT"}]}

20.7.4 describe_table — STRUCTURE#

Describes a table or a view without reading its data.

ArgumentTypeRequiredRole
databasestringyesDatabase
tablestringyesTable or view

Result for a table: database, table, kind (table), rows (estimated), columns (for each one name, full SQL type such as decimal(8,2), nullable, default as text or null, key PRI/UNI/MUL, extra such as auto_increment, comment), primary_key, unique and indexes ({name, columns}), vector_index ({name, column, m, distance} or null), foreign_keys ({name, columns, ref_database, ref_table, ref_columns, on_delete, on_update}), checks ({name, expression, enforced}). For a view: kind view and its columns only.

{"name": "describe_table", "arguments": {"database": "ventes", "table": "article"}}
{"database": "ventes", "table": "article", "kind": "table", "rows": 3,
 "columns": [
   {"name": "id", "type": "int", "nullable": false, "default": null, "key": "PRI", "extra": "auto_increment", "comment": ""},
   {"name": "prix", "type": "decimal(8,2)", "nullable": true, "default": null, "key": "", "extra": "", "comment": ""}],
 "primary_key": ["id"], "unique": [], "indexes": [], "vector_index": null, "foreign_keys": [], "checks": []}

20.7.5 show_create — STRUCTURE#

Returns the SQL definition (CREATE …) of an object, like SHOW CREATE.

ArgumentTypeRequiredRole
databasestringyesDatabase
namestringyesObject name
kindtable, view, procedure, function, trigger, eventnoWithout kind, the name is searched as a table, then as a view

Result: {"database", "name", "kind", "sql"}.

{"name": "show_create", "arguments": {"database": "ventes", "name": "article"}}
{"database": "ventes", "name": "article", "kind": "table", "sql": "CREATE TABLE `article` (…)"}

20.7.6 search_schema — STRUCTURE#

Searches for tables, views, columns, procedures and functions whose name contains a text, in all visible databases. Case-insensitive search, plain text (no % or _ wildcards). Routines require a right on the database, as in list_objects.

ArgumentTypeRequiredRole
patternstringyesText searched in names
limitinteger, 1 to 1000noMaximum matches returned (100 by default)

Result: {"matches": [ … ], "truncated": …}, each match with database and kind (table, view, column, procedure or function): table for a table, a view or a column (plus column and type for a column), name for a routine.

{"name": "search_schema", "arguments": {"pattern": "prix"}}
{"matches": [{"database": "ventes", "table": "article", "kind": "column", "column": "prix", "type": "decimal(8,2)"}],
 "truncated": false}

20.7.7 read_rows — READ#

Reads the rows of a table or a view without writing SQL.

ArgumentTypeRequiredRole
databasestringyesDatabase
tablestringyesTable or view
columnsarray of namesnoColumns returned (all by default, or if the array is empty)
whereobjectnoStructured condition (20.8)
order_byarraynoColumn names, or {"column": name, "desc": true}; by default, the primary key (stable pagination)
limitinteger ≥ 1noMaximum rows returned: 100 by default, brought down to mcp_max_rows
offsetinteger ≥ 0noRows skipped first

Result: common read shape (20.7.1), a single result; truncated is exact: it is true if at least one more row than limit exists.

{"name": "read_rows", "arguments": {
  "database": "ventes", "table": "article",
  "columns": ["id", "nom"],
  "where": {"column": "prix", "op": ">=", "value": 2},
  "order_by": [{"column": "prix", "desc": true}],
  "limit": 1}}
{"results": [{"columns": [{"name": "id", "type": "int"}, {"name": "nom", "type": "varchar(20)"}],
  "rows": [[2, "cahier"]], "row_count": 1, "truncated": true, "affected_rows": null, "last_insert_id": null}],
 "warnings": []}

To paginate, keep the default ordering and increase offset by limit as long as truncated is true. A column or sort-order name that is not an exact column name ("prix DESC", "id, nom FROM x") is refused (1054).

20.7.8 get_row — READ#

Reads the row of a table whose primary key equals key.

ArgumentTypeRequiredRole
databasestringyesDatabase
tablestringyesTable (with a primary key)
keyobject {column: value}yesAll the columns of the primary key, and only them

Result: common read shape, zero or one row. An incomplete key, a column outside the key or a table without a primary key (a view, for example) are refused (1210).

{"name": "get_row", "arguments": {"database": "ventes", "table": "article", "key": {"id": 3}}}
{"results": [{"columns": [ … ], "rows": [[3, "règle", 3.25, -1, null]], "row_count": 1, "truncated": false,
  "affected_rows": null, "last_insert_id": null}], "warnings": []}

20.7.9 count_rows — READ#

Counts the rows of a table or a view, optionally those that satisfy a condition.

ArgumentTypeRequiredRole
databasestringyesDatabase
tablestringyesTable or view
whereobjectnoStructured condition (20.8)

Result: {"count": n, "warnings": []}.

{"name": "count_rows", "arguments": {"database": "ventes", "table": "article",
  "where": {"column": "nom", "op": "like", "value": "%e%"}}}
{"count": 2, "warnings": []}

20.7.10 explain — READ#

Displays the execution plan of one SELECT, UPDATE, DELETE or INSERT … SELECT statement without executing it (EXPLAIN).

ArgumentTypeRequiredRole
sqlstringyesA single statement

Result: common read shape, one row per table access (at most 1000 rows). A text with several statements (SELECT 1; DROP DATABASE ventes) or another kind of statement is refused (1210): a ; cannot smuggle in a second statement. A syntax error returns 1064.

{"name": "explain", "arguments": {"sql": "SELECT nom FROM ventes.article WHERE id = 1"}}

20.7.11 insert_rows — WRITE#

Inserts up to 1000 rows, each given as {column: value}.

ArgumentTypeRequiredRole
databasestringyesDatabase
tablestringyesTable
rowsarray of 1 to 1000 objectsyesRows; an omitted column takes its default value
on_duplicateerror, ignore, updatenoRow whose key already exists: error 1062 (error, default), row skipped (ignore, like INSERT IGNORE), existing row updated with the given non-primary-key values (update, like ON DUPLICATE KEY UPDATE)

Rows that give the same columns are inserted by a single statement; different sets of columns give several statements, executed in a transaction opened by the tool and rolled back at the first failure (nothing is inserted). If the session already has an open transaction, the tool works in it without committing it. CHECK constraints (4025), foreign keys (1452), uniqueness and triggers apply.

Result: {"affected_rows", "last_insert_id", "statements", "warnings"}; last_insert_id is the first AUTO_INCREMENT value generated by the last insertion, null if the table has no AUTO_INCREMENT column; statements: statements executed. On failure, error and affected_rows at 0 if the tool's transaction was rolled back.

{"name": "insert_rows", "arguments": {"database": "ventes", "table": "article",
  "rows": [{"nom": "gomme", "prix": 0.5}, {"nom": "feutre", "prix": 1}]}}
{"affected_rows": 2, "last_insert_id": 4, "statements": 1, "warnings": []}

20.7.12 update_rows — WRITE#

Modifies the rows that satisfy a condition.

ArgumentTypeRequiredRole
databasestringyesDatabase
tablestringyesTable
setobject {column: value}yesNew values (at least one column)
whereobjectyes, unless all_rowsStructured condition (20.8)
all_rowsbooleannotrue to modify all rows without a condition

A missing or empty where ({}, {"and": []}) is refused (1210) without "all_rows": true: an assistant does not modify an entire table by oversight. Result: {"affected_rows", "last_insert_id" (null), "statements", "warnings"}.

{"name": "update_rows", "arguments": {"database": "ventes", "table": "article",
  "set": {"prix": 9.99}, "where": {"column": "id", "op": "=", "value": 2}}}
{"affected_rows": 1, "last_insert_id": null, "statements": 1, "warnings": []}

20.7.13 delete_rows — WRITE#

Deletes the rows that satisfy a condition; same rule as update_rows for where and all_rows.

ArgumentTypeRequiredRole
databasestringyesDatabase
tablestringyesTable
whereobjectyes, unless all_rowsStructured condition (20.8)
all_rowsbooleannotrue to delete all rows
{"name": "delete_rows", "arguments": {"database": "ventes", "table": "article",
  "where": {"column": "nom", "op": "=", "value": "gomme"}}}
{"affected_rows": 1, "last_insert_id": null, "statements": 1, "warnings": []}

20.7.14 create_database — ADMIN#

ArgumentTypeRequiredRole
namestring, 1 to 64 charactersyesDatabase name
charsetstringnoCharacter set (utf8mb4): letters, digits and _ only
collationstringnoCollation (utf8mb4_general_ci), same characters
if_not_existsbooleannoNo error if the database exists (warning 1007)

Result: {"sql", "warnings"}, sql being the statement executed, returned even in case of error.

{"name": "create_database", "arguments": {"name": "atelier"}}
{"sql": "CREATE DATABASE `atelier`", "warnings": []}

20.7.15 create_table — ADMIN#

Creates a table from a structured definition. Miraj writes the canonical SQL text (identifiers in backticks, escaped strings, types taken from an allow list, CHECK expressions re-parsed then rewritten) before executing it: nothing is copied as is.

ArgumentTypeRequiredRole
databasestringyesDatabase
tablestringyesTable name
columnsarray of objects (at least one)yesColumns, see below
primary_keyarray of namesnoPrimary key
uniquearray of arrays of namesnoUNIQUE constraints, one per list
indexesarray of {name?, columns}noSecondary indexes
foreign_keysarray of objectsno{name?, columns, ref_database?, ref_table, ref_columns, on_delete?, on_update?}; ref_database defaults to the table's database; actions RESTRICT, CASCADE, SET NULL, SET DEFAULT, NO ACTION
checksarray of stringsnoCHECK expressions ("prix >= 0"), without subquery or parameter
vector_indexobjectnoHNSW vector index, see below
partitioningobjectnoPartitioning, see below
if_not_existsbooleannoNo error if the table exists

Each column:

KeyTypeRequiredRole
namestringyesName (1 to 64 characters)
typestringyesSQL type: TINYINT, SMALLINT, MEDIUMINT, INT, BIGINT (optional size), BIT, FLOAT, DOUBLE, DECIMAL(p,s), BOOLEAN, CHAR(n), VARCHAR(n), BINARY(n), VARBINARY(n), TEXT and BLOB (and their TINY, MEDIUM, LONG variants), DATE, TIME, DATETIME, TIMESTAMP (optional precision), YEAR, JSON, UUID, VECTOR(n), ENUM, SET; UNSIGNED for integers, DECIMAL, FLOAT and DOUBLE. Spatial types are refused
valuesarray of stringsfor ENUM and SETMembers
nullablebooleannotrue by default; false: NOT NULL
defaultJSON valuenoLiteral value (null, boolean, number, string, {"hex": …}); "CURRENT_TIMESTAMP" or "CURRENT_TIMESTAMP(n)" for a date and time column. Any other expression ("NOW()") becomes a string
auto_incrementbooleannoAUTO_INCREMENT
commentstringnoComment

An unknown key, a type outside the list, a wrong number of parameters (VARCHAR without a size) are refused (1210) before any execution. Result: {"sql", "warnings"}.

{"name": "create_table", "arguments": {
  "database": "atelier", "table": "commande",
  "columns": [
    {"name": "id", "type": "int", "nullable": false, "auto_increment": true},
    {"name": "client", "type": "int", "nullable": false},
    {"name": "montant", "type": "decimal(10,2)", "default": 0},
    {"name": "cree", "type": "datetime", "default": "CURRENT_TIMESTAMP"}],
  "primary_key": ["id"],
  "indexes": [{"name": "i_montant", "columns": ["montant"]}],
  "foreign_keys": [{"columns": ["client"], "ref_table": "client", "ref_columns": ["id"], "on_delete": "CASCADE"}],
  "checks": ["montant >= 0"]}}
{"sql": "CREATE TABLE `atelier`.`commande` (`id` INT NOT NULL AUTO_INCREMENT, `client` INT NOT NULL, `montant` DECIMAL(10,2) DEFAULT 0, `cree` DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `i_montant` (`montant`), FOREIGN KEY (`client`) REFERENCES `atelier`.`client` (`id`) ON DELETE CASCADE, CHECK (…))",
 "warnings": []}

Vector index (vector_index):

KeyTypeRequiredRole
columnstringyesVECTOR(n) column declared NOT NULL
namestringnoName (by default, that of the column)
mintegernoLinks per node (3 to 200, 1912 otherwise)
distanceeuclidean, cosine, dotnoMetric served
commentstringnoComment

Partitioning (partitioning, PARTITION BY clause):

KeyTypeRequiredRole
methodstringyesRANGE, RANGE COLUMNS, LIST, LIST COLUMNS, HASH, LINEAR HASH, KEY, LINEAR KEY
expressionstringfor RANGE, LIST, HASHExpression, re-parsed then rewritten like a CHECK
columnsarray of namesfor … COLUMNSColumns; optional for KEY (primary key)
algorithmintegernoKEY algorithm
countinteger, 1 to 8192noPARTITIONS n (HASH, KEY)
partitionsarray of objectsfor RANGE, LIST{name, less_than?, values_in?, comment?, subpartitions?}: less_than is an array of literals ({"maxvalue": true} for MAXVALUE) or the string "MAXVALUE"; values_in, an array of literals (one array per value for LIST COLUMNS on several columns)
subpartitionobjectno{method, expression?, columns?, algorithm?, count?} with HASH, LINEAR HASH, KEY or LINEAR KEY

The engine's rules (primary key containing the partitioning columns, increasing bounds…) apply: their errors are returned as is. Table options, generated columns and ON UPDATE are not supported by this tool: use execute_sql.

20.7.16 drop_object — ADMIN#

Drops a table, a view or an entire database with its data. Irreversible.

ArgumentTypeRequiredRole
kindtable, view, databaseyesKind of object
namestringyesObject name
databasestringfor a table or a viewDatabase of the object
confirmstringyesMust repeat name exactly (same case); otherwise nothing is dropped (1210)

Result: {"sql", "warnings"}.

{"name": "drop_object", "arguments": {"database": "atelier", "name": "commande", "kind": "table", "confirm": "commande"}}
{"sql": "DROP TABLE `atelier`.`commande`", "warnings": []}

Routines, triggers, events and indexes are dropped through execute_sql.

20.7.17 execute_sql — READ and beyond#

Executes an SQL script (one or several statements separated by ;) and returns each result. The token's level filters what passes (20.5.2): with a READ token, the tool only reads.

ArgumentTypeRequiredRole
sqlstringyesSQL script
paramsarraynoValues of the ? markers, in order (20.9)
max_rowsinteger ≥ 1noRows kept per result: 100 by default, brought down to mcp_max_rows

Result: common read shape, one result per statement; a statement that writes gives a result without columns with affected_rows and last_insert_id. On error, the results already obtained are kept, followed by error; the following statements of the script are not executed.

{"name": "execute_sql", "arguments": {
  "sql": "SELECT id, nom, prix, gros, image FROM ventes.article WHERE id <= ? ORDER BY id",
  "params": [2]}}
{"results": [{"columns": [{"name": "id", "type": "int"}, {"name": "nom", "type": "varchar(20)"},
    {"name": "prix", "type": "decimal(8,2)"}, {"name": "gros", "type": "bigint"}, {"name": "image", "type": "varbinary(4)"}],
  "rows": [[1, "stylo", 1.50, "9007199254740993", "0x00FF"], [2, "cahier", 20.00, 7, null]],
  "row_count": 2, "truncated": false, "affected_rows": null, "last_insert_id": null}],
 "warnings": []}

Good to know:

  • the MCP session is persistent: session variables, USE, temporary tables and open transactions (START TRANSACTION in one call, COMMIT in another) are kept from one call to the next, until the session is closed;
  • max_rows bounds what is returned, not what is computed: for a large table, write a LIMIT in the query;
  • LOAD DATA LOCAL INFILE is refused (1148): the MCP client has no file to supply;
  • the tool's annotations follow the level: read-only at READ, destructive beyond.

20.8 Structured where conditions#

read_rows, count_rows, update_rows and delete_rows take their condition as a JSON object, never as SQL text.

20.8.1 Simple condition#

{"column": "prix", "op": ">=", "value": 10}
opEquivalent SQLvalue
=, != (or <>)=, <>a value; with null, IS NULL / IS NOT NULL
<, <=, >, >=comparisona value
like, not_likeLIKE, NOT LIKEa string, SQL wildcards % and _
in, not_inIN (…), NOT IN (…)an array of 1 to 10,000 values
betweenBETWEEN a AND ban array of two bounds
is_null, is_not_nullIS NULL, IS NOT NULLnone (value refused)

column must be a column name of the table (1054 otherwise, case-insensitively); values become literals: "' OR 1=1 --" remains a string compared as is.

20.8.2 Groups#

{"and": [ condition, … ]}      {"or": [ condition, … ]}      {"not": condition}

Each group is the only key of its object. Example:

{"and": [
  {"column": "prix", "op": ">=", "value": 10},
  {"or": [{"column": "nom", "op": "=", "value": "a"}, {"column": "nom", "op": "=", "value": "b"}]},
  {"not": {"column": "id", "op": "=", "value": 3}}
]}

is equivalent to prix >= 10 AND (nom = 'a' OR nom = 'b') AND NOT (id = 3).

Rules:

  • {} and {"and": []} set no condition (and count as a missing where for update_rows and delete_rows); an empty or is refused rather than read as "all rows";
  • nesting depth 32 at most, 1000 simple conditions at most;
  • an unknown key ("sql": "1=1") or a text condition ("where": "id = 1") is refused (1210).

20.9 JSON values#

20.9.1 Returned values#

SQL typeJSON returnedExample
Integersnumber; string beyond 2^53 (9,007,199,254,740,992) in absolute value, for JSON readers that read numbers as floating point7, "9007199254740993"
DECIMALnumber written exactly, without going through a floating point1.50
FLOAT, DOUBLEnumber; null for an infinite value or NaN2.5
Strings, JSONstring"stylo"
Dates and timesstring in SQL format"2026-09-25 10:15:00"
Binary (BINARY, VARBINARY, BLOB)hexadecimal string 0x… in uppercase"0x00FF"
NULLnullnull

A decimal is exact in the response text (1.50); a JSON reader that converts it to floating point may display it as 1.5. The SQL type of each column appears in columns[].type.

20.9.2 Values given#

In where, rows, set, key and default:

JSONSQL value
null, true, falseNULL, TRUE, FALSE
integerinteger; beyond the range of signed 64-bit integers, passed as a string of digits converted by the column
number with a decimal point and no exponent (12.50, at most 18 decimals)exact decimal
number with exponent (1e300)floating point
stringstring; dates are written "YYYY-MM-DD" or "YYYY-MM-DD hh:mm:ss"
{"hex": "00ff"} (0x prefix accepted) or {"base64": "AP8="}bytes
object or array, for a JSON column of a tableserialized JSON text

An array or another object is refused for a non-JSON column (1210).

In the params of execute_sql, the same values, with two differences: a number with a decimal point is passed there as a floating point (give a string, "12.50", for an exact decimal), and objects or arrays are only accepted in the {"hex"} / {"base64"} form.

20.10 MCP resources#

In addition to tools, the endpoint publishes JSON resources (application/json), filtered by the same privileges as the STRUCTURE tools:

URIContent
miraj://{base}Objects of the database, like list_objects
miraj://{base}/{table}Description of the table or view, like describe_table, plus create: its CREATE definition
  • resources/list returns one resource per visible database (miraj://ventes, name ventes, title Database ventes);
  • resources/templates/list returns the two templates miraj://{database} and miraj://{database}/{table};
  • resources/read returns {"contents": [{"uri", "mimeType", "text"}]}, text being the JSON of the content;
  • names other than letters, digits, -, ., _ and ~ are percent-encoded (miraj://ma%20base/a%2Fb);
  • a malformed URI gives JSON-RPC error -32602, a resource that is not found or refused -32002;
  • subscription to resource changes is not offered.

20.11 Protocol (for integrators)#

This section describes the exchanges for those who write their own client; an ordinary MCP client handles them on its own.

20.11.1 Transport#

  • Streamable HTTP without an SSE stream: each JSON-RPC request is a POST /mcp whose response is an application/json body; HTTP/1.1 (persistent connections) or HTTP/1.0.
  • A single message per request: batches (JSON array) are refused (-32600).
  • Body of 16 MB at most; Transfer-Encoding: chunked and Expect: 100-continue accepted.
  • DELETE /mcp closes the session; GET /mcp (server stream) is not offered (405); any other path: 404.
  • Recognized protocol versions: 2025-11-25, 2025-06-18, 2025-03-26. initialize retains the client's if it is recognized, otherwise proposes the most recent.

20.11.2 Headers#

HeaderMeaningRule
Authorization: Bearer mjt_…requestRequired on every request, DELETE included (401 otherwise)
Content-Type: application/jsonrequestIf present, must be application/json (415 otherwise)
Mcp-Session-Idresponse to initialize, then every request32 hexadecimal characters drawn at random; required after initialize (400 without it, 404 unknown or expired); a session only accepts the token that opened it (401)
MCP-Protocol-VersionrequestOptional; if present, must be a recognized version (400 otherwise)
OriginrequestIf present, must be a loopback origin (http(s)://localhost, 127.0.0.1 or [::1], any port), otherwise 403: protection against web pages that would target the server from a browser
WWW-Authenticate: Bearer realm="miraj"401 response
Cache-Control: no-storeevery response

20.11.3 Course of a session#

POST /mcp   initialize                  → 200, Mcp-Session-Id header, capabilities, instructions
POST /mcp   notifications/initialized   → 202 (no body)
POST /mcp   tools/list                  → 200, tools of the token's level
POST /mcp   tools/call                  → 200, tool result (possible isError)
DELETE /mcp                             → 204, session closed, open transaction rolled back

With curl (POSIX shell syntax):

curl -i http://127.0.0.1:7008/mcp \
     -H "Authorization: Bearer mjt_Xq7mPt3hKc9WbZ2rNf8sLd4v" \
     -H "Content-Type: application/json" \
     -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"test","version":"1"}}}'

The result of initialize announces serverInfo (name miraj, title Miraj, version), the tools and resources capabilities (without listChanged or subscription) and an instructions text that presents to the assistant the token, the account, the level, the scope and the row limit.

Methods served: initialize, ping, tools/list, tools/call, resources/list, resources/templates/list, resources/read, and the notifications notifications/initialized and notifications/cancelled (which interrupts the current call, like KILL QUERY). Any other notification, and any response from the client, receives 202 and is ignored; any other method (prompts/list, for example) gives -32601. A session processes one request at a time: simultaneous requests on the same session run one after the other.

20.11.4 HTTP codes#

CodeCase
200JSON-RPC response (success, JSON-RPC error or tool result with isError)
202Client notification or response acknowledged, without a body
204DELETE: session closed
400Unreadable JSON (-32700), invalid message (-32600), missing Mcp-Session-Id, unknown MCP-Protocol-Version, malformed HTTP request
401Missing, unknown, expired, revoked token, locked account or refused host; session opened by another token
403Origin foreign to the loopback; address blocked after too many failures (message of error 1129; with TLS, the connection of a blocked address is simply closed, without a response)
404Unknown or expired session ("Session not found or expired: initialize a new session"); path other than /mcp
405Method other than POST and DELETE (Allow: POST, DELETE header)
413Body larger than 16 MB
415Content-Type other than application/json
431Headers too long (32 KB) or too many (64)
501Transfer-Encoding other than chunked
503max_connections reached when opening a session (message of error 1040)
505HTTP version other than 1.0 and 1.1

A connection must present its first request within 10 seconds (TLS handshake included), and a started request must arrive in full within 30 seconds.

20.11.5 JSON-RPC errors#

CodeCase
-32700Body that is not JSON
-32600Invalid message: batch, jsonrpc other than "2.0", id that is neither a string nor a number, missing method; repeated initialize in a session; missing Mcp-Session-Id; unknown protocol version; refused Content-Type
-32601Unknown method
-32602tools/call without a name, unknown tool or above the token's level, arguments that is not an object; resources/read without uri or with a malformed URI
-32002Resource not found or refused
-32000Server error accompanying an HTTP code 401, 403, 404 or 503

SQL errors and refused arguments are not JSON-RPC errors: they arrive in an isError: true result (20.7.1).

20.12 How do I secure the MCP server?#

  • The lowest useful level. A STRUCTURE token is enough for an assistant to write queries from the schema; READ for it to check them on the data; WRITE and ADMIN only on a working or test database.
  • A dedicated account. The level caps, it does not replace privileges: create an account specific to the assistant, with only the necessary GRANTs, rather than a token of root.
  • A per-database scope (DATABASES (…)) and an expiration (EXPIRE INTERVAL n DAY): a secret forgotten in a configuration file stops opening anything.
  • One token per use (per assistant, per workstation, per project), to revoke one without touching the others: DROP MCP TOKEN takes effect from the next request.
  • Protect the secret. It is shown only once; keep it in the secrets manager or the client's configuration, never in a source repository.
  • Loopback by default. Leave mcp_bind on 127.0.0.1 when the assistant runs on the server machine; outside the loopback, TLS is mandatory, and the account's host must accept the assistant's machine.
  • Controlled origin. A request carrying an Origin header foreign to the loopback is refused (403): a web page cannot use a workstation's browser to reach the endpoint.
  • Secret-free journal. With --log, the journal records the token name, the account, the tool, the duration, the number of rows and the error code, never the secret or the data; the SQL of execute_sql is truncated there to 200 characters, passwords masked.
  • Timeouts. mcp_statement_timeout bounds each call, mcp_idle_timeout closes forgotten sessions and rolls back their transaction; only an ADMIN token can raise max_statement_time for its session, never the endpoint's call timeout.
  • Authentication failures. A wrong secret counts as an authentication failure for the client's address, exactly as on the main port (11.5.2): delayed response, then the address is blocked after max_connect_errors consecutive failures, even with a good token (the failure registry is that of the main port). A missing Authorization header is not counted. To unblock:
FLUSH HOSTS;   -- RELOAD privilege
  • Confirmed deletion. drop_object requires repeating the name; update_rows and delete_rows require a condition unless all_rows: true. These safeguards prevent mistakes, they do not replace a suitable level: an ADMIN token can delete everything through execute_sql.

20.13 Monitoring#

20.13.1 Sessions#

Each MCP session is an ordinary Miraj session:

  • it appears in SHOW PROCESSLIST (and information_schema.PROCESSLIST) under the user of the token's account, the Host column being mcp:<token name>@<client host>, for example mcp:assistant@localhost; USER() and CURRENT_USER() show nothing of it and an account without PROCESS sees these sessions like the other sessions of its user;
  • it takes a max_connections slot (503 beyond);
  • KILL QUERY id interrupts the current call (1317 for the assistant), KILL CONNECTION id closes the session (404 on the next request, transaction rolled back); id is the session number from SHOW PROCESSLIST, also written in square brackets in the journal.

The endpoint's housekeeping thread closes a session left without a request for mcp_idle_timeout seconds: its open transaction is rolled back and the client receives 404 on its next request (it must reopen a session through initialize). It also abandons the call that exceeds mcp_statement_timeout: the current statement stops with error 1969 (per-statement limit) or 1317 (entire call interrupted), and the session remains usable.

20.13.2 Journal#

With --log, the endpoint writes:

[12] MCP session opened from 127.0.0.1, token lecteur, account app@localhost, level READ, protocol 2025-06-18, client mon-client
[12] MCP tools/call read_rows, token lecteur, account app@localhost: 2.4 ms, 1 row(s) -- read_rows ventes.article
[12] MCP tools/call execute_sql, token lecteur, account app@localhost: 0.8 ms, 0 row(s), error 9034 -- INSERT INTO ventes.article (nom) VALUES ('gomme')
[12] MCP session closed by the client, token lecteur
[13] MCP session closed (idle or stopped), token lecteur
MCP ! 1045 (28000): Access denied for user 'mcp'@'10.0.0.5' (using password: YES) -- from 10.0.0.5
SECURITY: address 10.0.0.5 blocked after 100 consecutive authentication failures (max_connect_errors); FLUSH HOSTS unblocks it

Successful calls and session openings are displayed on the console; failed calls, authentication refusals and SECURITY events are additionally recorded in <root>\miraj\server.log.

20.14 Known limitations#

  • The miraj-mcp stdio bridge, for clients that can only launch a process, is not yet delivered (20.6.2).
  • No SSE stream or server notification (GET /mcp: 405), no JSON-RPC batch, no MCP prompts, no resource subscription.
  • create_table creates neither table options, nor generated columns, nor ON UPDATE; drop_object only drops tables, views and databases: the rest goes through execute_sql (ADMIN token).
  • search_schema does not search trigger and event names.
  • get_row requires a primary key: no key read on a view; the JSON columns of a view do not receive an object or array as a value.
  • Tool descriptions, session instructions and protocol messages are in English.
  • No last-used date for tokens.
  • Cluster edition: replication of tokens with accounts and replay on the secondaries of the DDL created by create_table remain to be validated by a cluster test.

20.15 See also#