mcp-firebird
A Model Context Protocol server for Firebird 2.5 – 5.0, written in Delphi with the official `fbclient` driver. It lets an AI assistant document schemas, analyze query plans, advise on indexes (which to add and which to drop), audit schema health, and drive goal-based optimization. Read-only by default.
Documentation
English · Italiano · Español · Deutsch
MCP Firebird
Ask your AI assistant why a query is slow, and get an answer worth acting on.
A Model Context Protocol server for Firebird 2.5 to 5.0,
written in Delphi against the official fbclient driver. Connect it to a database and an assistant
can read your access plans, tell you which indexes are missing and which ones are dead weight,
audit a table's health, and find the transactions left open that are holding garbage collection
back.
You can also hand it a goal, "this query must stop scanning NATURAL", "it has to come in under 200 ms", and let it work: it applies a change, measures it again on the database, and tries again if that was not enough. What decides the goal is met is the measurement, not the assistant.
These are not the generic index tips you can find in any article. The answers come out of your
database: the server asks Firebird for the query's execution plan (SET PLANONLY), queries the
monitoring tables (MON$), and counts how many distinct values a column really holds before
claiming an index on it would pay off.
Every answer arrives in three parts. Finding: what it found, and why it is a problem. SQL: the statement that fixes it, already written. Verify: how to check the fix actually worked. No tool writes to the database. The server reads, and the SQL it hands you is yours to run, when and if you decide to.
Built with mcp-server-delphi, which in turn stands on DelphiMVCFramework. This server is a complete, real-world example of what you can build with them.
- Transport: stdio (JSON-RPC 2.0, MCP protocol
2025-03-26) - Server identity:
mcp-firebirdv0.1.0 - Engine support: Firebird 2.5, 3.0, 4.0, 5.0 (capability-detected at runtime)
- Safety: read-only analysis; no tool runs DDL or write SQL
- Free for your own databases, at any scale, with no key and no expiry. A licence is needed only to hand the software to somebody else: resell it inside a product of yours, leave it installed at a client's, expose it as a service (licence details)
- Enterprise edition, sold separately: it examines the Firebird server,
not just the database. It reads
firebird.conf, the machine's RAM and CPUs,firebird.logand the Trace API. You want it when the schema is in order and the database is still slow
Table of contents
- What it does
- Editions & licensing
- Enterprise edition
- How it uses mcp-server-delphi
- Prerequisites
- Build
- Configuration (
.env) - Run & verify manually
- Install it into your AI agent: Claude Desktop · Claude Code · Gemini CLI · OpenCode · Cursor / VS Code · generic
- Using it from Claude: worked examples
- Tool reference
- Testing the project
- Troubleshooting
What it does
Tools (9 free, plus 5 Enterprise announced in tools/list)
| Tool | Arguments | Purpose |
|---|---|---|
fb_info | (none) | Engine version + detected capabilities (JSON) |
fb_list_tables | (none) | List user tables |
fb_generate_documentation | table_name? | Markdown docs (columns, PK, indexes) for one table, or the whole database |
fb_analyze_query | sql | Access-plan analysis: NATURAL-scan + external-SORT detection |
fb_suggest_indexes | sql | New-index suggestions from NATURAL-scanned predicates (ready-to-run DDL) |
fb_suggest_index_drops | table_name | Flags duplicate / redundant-prefix / inactive / low-selectivity indexes |
fb_audit_table | table_name | Schema-health audit: missing PK, over-indexing, stale statistics |
fb_evaluate_goal | goal_type, target, threshold | Deterministic goal check (drives the optimization loop) |
fb_monitor_transactions | stale_minutes? | Transaction/sweep health: OIT/OAT/Next gap, blocking long-running transactions (with their last SQL statement) |
Every advisory comes with a Finding, ready-to-run SQL, and a Verify step.
Prompts (2)
optimization_goal, the goal-driven loop: set an objective, the assistant iterates thefb_*tools and re-checksfb_evaluate_goaluntil it reportsmet: true(with a max-iterations / no-progress safety stop).health_check: guided whole-database health review.
Resources (1)
firebird://schema: the live database schema as a single resource.
Editions & licensing
Short version: if you are using it on your own databases, it is free, and it stays free. No trial, no expiry, no licence key, no seat count, no limit on how many tables or databases you point it at. Install it, use it in production, use it every day. Nothing phones home.
The one thing you cannot do is hand it to somebody else.
MCP Firebird is source-available, not open source as the Open Source Initiative defines the term. Saying that plainly matters more than a badge: from v0.2.0 it is licensed under the PolyForm Internal Use License 1.0.0. Versions up to and including v0.1.0 were released under Apache-2.0 and remain so for everyone who received them: a licence already granted cannot be revoked, and this project does not pretend otherwise.
What you may do, free of charge
- Run it against any database you like: yours, your employer's, your client's. Development, staging, production, all of them.
- Run it at any scale. A hundred tables or ten thousand; one database or fifty.
- Use it in your consulting practice. Diagnose, tune, audit and support your clients' Firebird databases with it, and charge them for your time. It is your tool; keep it.
- Read the source. All of it. Learn from it, and use what you learn.
- Modify it. Fix a bug, add a detector, change a message. Run your modified build.
- Use it in a company of any size, commercial or not, for-profit or not, with no fee and no registration.
What requires a licence
One idea, expressed three ways: letting the software out of your hands.
- Redistributing it. Publishing a fork, uploading a build, putting it on a CD, sending the binary to a customer, leaving it installed on a client's server when the engagement ends.
- Embedding it in a product you sell. Shipping it inside your ERP, your installer, your Docker image, your appliance, in source or binary form, modified or not.
- Offering it as a service. Standing it up behind an API or a hosted agent that people outside your organisation can reach.
Where the software runs, and whose database it examines, is your business. Where copies of it end up is ours.
If your case is one of these, the licence exists and it is not expensive relative to what you are building with it. Write to d.teti@bittime.it.
When you need to buy a licence: worked cases
| Your situation | Licence needed? |
|---|---|
| Your DBA runs it against the company's production Firebird every morning | No. |
| Your team of forty developers each run it locally | No. No seat count, no registration. |
| You are a consultant, and you run it from your laptop against your client's database | No. It is your tool and it stays your tool. Charge them what you like. |
| Same, but you are sitting at the client's desk, running it on their server | No. Take it with you when you leave. |
| You leave a copy of it installed on your client's server when you go | Yes. The software left your hands. |
| You are a hosting provider, and you run it against the databases you host | No. |
| ...and you give your customers a button that runs it for them | Yes. That is offering it as a service. |
| You ship it inside your Delphi ERP so your customers get "AI database tuning" | Yes. Embedding in a product you supply. |
| You publish a fork on GitHub with your improvements | Yes. Talk to us first: we would rather merge it. |
| You are writing a blog post, a talk, or a university course about it | No. Read it, quote it, teach it. |
You are on v0.1.0, which you obtained under Apache-2.0 | No. That version stays Apache-2.0 for you forever. |
The rule behind the table, if you would rather reason than look things up: ask where the software ends up, never what you did with it. As long as every copy of MCP Firebird stays in your hands, you owe nothing: not for the scale you run it at, not for the money it makes you, not for whose database you point it at. The moment a copy leaves, we should talk.
There is also a paid Enterprise edition, a different product, not a crippled free tier. Everything described in the rest of this README is in the free one.
Enterprise edition
Where the free edition ends
The free edition is not a demo, and it is not the Enterprise edition with the good parts taken out. It is the whole of one job, done properly: it makes the database answer for itself.
It reads your schema. It explains your plans. It finds the index you are missing and the four you do not need. It catches the missing primary key, the stale statistics, the transaction pinning garbage collection since Tuesday. For most databases, most of the time, that is where the problem is, and that is where the problem gets fixed. Plenty of people will use it for years and never need anything else, and they will never be asked for a cent.
Then one day it comes back and tells you the truth: your schema is fine. Your indexes are fine. No natural scans, no external sorts, statistics fresh. And the database is still slow.
That is the line. The free edition has answered its question honestly and completely, and the
answer is that the problem is not in the database. It is in the machine underneath it, and no
SELECT will ever show you that. Not because the tool is holding back, but because SQL cannot
see outside its own process.
Where the Enterprise edition begins
It is 2 GB of page buffers on a host with 8 GB of RAM. It is forced writes switched off for a
batch load two years ago and never switched back. It is LockHashSlots still at its 2010 default
under four hundred connections, an index four levels deep, a bugcheck written to firebird.log
every Tuesday at 03:00 that nobody reads.
The free edition connects to Firebird the way your application does: an ordinary SQL connection,
with ordinary rights. The Enterprise edition asks for more. It attaches to the Services
Manager as an administrator (which is how it streams firebird.log back, drives the Trace API,
and reads the physical storage report), and it reads the server's own configuration and hardware.
That is a different privilege, a different blast radius, and a different conversation with
whoever owns the server. Hence a different product.
And it does not stop at telling you what is wrong. It runs the experiment. Capture a baseline under real load, change exactly one parameter, measure again, compare the distributions (the p95 and the p99, never the average), and keep the change or put it back. Nobody sells you a number. The database tells you the number.
| Free | Enterprise | |
|---|---|---|
| Schema, docs, plans, index advice, schema audit | ✅ | ✅ |
Transaction & sweep health (MON$) | ✅ | ✅ |
fb_diagnose: the entry point, what is already known and the ordered route to run next | ❌ | ✅ |
fb_analyze_config: firebird.conf / databases.conf, read against this engine, this architecture, this workload | ❌ | ✅ |
fb_analyze_storage: index depth, page fill ratios, record-version chains, page distribution | ❌ | ✅ |
fb_parse_log (firebird.log): errors, sweeps, bugchecks, crashes | ❌ | ✅ |
fb_capture_trace (Trace API): the real workload, and what actually costs | ❌ | ✅ |
fb_analyze_host: RAM against page buffers, CPU against parallel workers, storage class | ❌ | ✅ |
| Baselines, distributions, before/after comparison: the experiment | ❌ | ✅ |
Note what is not in that table: nothing was moved out of the free edition to build the paid one. Every free tool stays free, and the ones still to be written stay on the side of the line they belong to. The boundary is not a paywall drawn through a feature list. It is the line between querying a database and administering a server, and the free edition was always on one side of it.
The hard part was never parsing firebird.conf; anyone can parse an INI file. And nobody can
honestly hand you the right value for LockHashSlots: Firebird's own documentation states no
optimum for it, nor for the page cache, nor for the sort cache. What experience buys you is
knowing which parameter your symptom implicates: that throughput collapsing under concurrency
while the CPU stays calm points at the lock table and never at the page cache. That map is the
product. The value at the end of it is not asserted. It is measured, on your database, under
your load.
You will know when you need it, because the free edition will have told you.
The six tools above already appear in tools/list in the free edition, so your assistant can see
them and say what it would do with them. Call one and it tells you how to get it.
Enterprise licences, commercial licences and support subscriptions: d.teti@bittime.it
How it uses mcp-server-delphi
Every tool is a plain Delphi method decorated with attributes from
mcp-server-delphi. The framework turns the
class into an MCP tool provider, generates the JSON-RPC schema from the attributes, and wires it
to the stdio transport: no protocol code in this repo. From providers/FirebirdToolsU.pas:
TFirebirdTools = class(TMCPToolProvider)
public
[MCPTool('fb_info', 'Engine version, dialect, charset and detected capabilities of the configured Firebird database')]
function FbInfo: TMCPToolResult;
[MCPTool('fb_generate_documentation', 'Markdown documentation — columns, primary key, indexes — for one table, or for the whole database when table_name is empty')]
function FbGenerateDocumentation([MCPParam('Table name; leave empty for the whole database', TMCPParamPresence.Optional)] const table_name: string): TMCPToolResult;
[MCPTool('fb_analyze_query', 'Returns and analyzes the access plan of a SQL query (flags NATURAL scans and external sorts)')]
function FbAnalyzeQuery([MCPParam('The SQL query to analyze')] const sql: string): TMCPToolResult;
end;
Prompts (providers/FirebirdPromptsU.pas) and resources (providers/FirebirdResourcesU.pas) use
the same attribute approach. See the mcp-server-delphi
repository for the full attribute reference.
Prerequisites
- Windows x64 (the server is a native Win64 console app).
- A Firebird client library (
fbclient.dll) matching, or newer than, your target server. A 5.0fbclient.dllconnects fine to 2.5-5.0 servers. - A reachable Firebird database to point at.
The download ships no fbclient.dll on purpose: the right one is your server's own, and a
mismatched client is worse than none. Point firebird.client_lib at it (see below).
Build
To build from source you also need Delphi 13 Athens (RAD Studio 37.0) with FireDAC, and
DMVCFramework plus the mcp-server-delphi library checked out locally. For the test matrix,
the Firebird zip-kits under fb_versions/ and Python 3 with pytest.
Search paths the project expects (set once in app/MCPFirebird.dproj):
C:\DEV\mcp-server-delphi\sources
<DMVCFramework>\sources (every sources subfolder DMVC needs)
C:\DEV\mcp-firebird\sources
C:\DEV\mcp-firebird\providers
Build the Win64 Debug app from the repo root:
cmd /c _build_app.bat
_build_app.bat calls rsvars.bat then msbuild app\MCPFirebird.dproj /t:Clean;Build /p:Config=Debug /p:Platform=Win64.
The executable lands at bin\MCPFirebird.exe.
(There is a matching _build_core.bat for the DUnitX test project.)
Configuration (.env)
By default the server reads its configuration from a .env file in the same folder as the
executable, so where that is depends on how you got the exe:
| exe | copy the template with | |
|---|---|---|
| downloaded release | MCPFirebird.exe (the folder you unzipped) | Copy-Item .env.example .env |
| built from source | bin\MCPFirebird.exe | Copy-Item bin\.env.example bin\.env |
Then edit it. (.env.example starts with a dot: ls and Explorer hide it unless you ask for
hidden files. It is in the zip.)
Choosing a different config folder: --env <dir>
By default the .env is read from the executable's own folder. Pass --env <dir> to read it
from another folder instead. The argument is a directory (the folder that contains the
.env), not the file itself:
MCPFirebird.exe --env C:\configs\prod # reads C:\configs\prod\.env
MCPFirebird.exe --env=C:\configs\prod # the --env=<dir> form also works
MCPFirebird.exe --env ..\shared # relative paths resolve against the working directory
MCPFirebird.exe # no argument -> reads <exe folder>\.env
MCPFirebird.exe --env C:\configs\prod\.env # WRONG -> stops with an error (see below)
--envis a folder, never the.envfile. If you point it at the file (e.g....\prod\.env) the server refuses to start and prints the fix on stderr (which MCP clients surface in their server logs) instead of silently starting with an empty config:MCPFirebird: --env must point at the FOLDER that contains the .env file, not at the file itself. got: C:\configs\prod\.env use this: C:\configs\prod
How the argument reaches the server. MCP clients don't go through a shell. They spawn the
executable directly with a command plus an args array, where each array element becomes one
separate argument. So there is no shell quoting to worry about (paths with spaces are fine), and you
write the directory as its own array element. Two equivalent forms:
| Form | args value |
|---|---|
| separate | ["--env", "C:\\configs\\prod"] |
| joined | ["--env=C:\\configs\\prod"] |
Path notes (Windows): in JSON, backslashes must be doubled ("C:\\configs\\prod"), or use
forward slashes, which Windows accepts and don't need escaping ("C:/configs/prod"). Prefer an
absolute path in MCP clients: the working directory they launch with is unpredictable, so
relative paths are unreliable there. Every startup logs the resolved folder to
logs\MCPFirebird.NN.mcp.log:
Boot: .env directory "C:\configs\prod" (.env exists=True)
Note: logs are always written to a
logs\subfolder next to the executable (logs\beside the exe), regardless of--env.
Passing --env from each MCP client
Claude Desktop (%APPDATA%\Claude\claude_desktop_config.json), Claude Code (.mcp.json),
Cursor (.cursor/mcp.json) and VS Code (.vscode/mcp.json) all use the same shape, a
command plus an args array:
{
"mcpServers": {
"firebird": {
"command": "C:\\Tools\\MCPFirebird\\MCPFirebird.exe",
"args": ["--env", "C:\\configs\\prod"]
}
}
}
Claude Code can also add it from the CLI:
claude mcp add firebird -- "C:\Tools\MCPFirebird\MCPFirebird.exe" --env "C:\configs\prod"
Gemini CLI (~/.gemini/settings.json), same mcpServers shape:
{
"mcpServers": {
"firebird": {
"command": "C:\\Tools\\MCPFirebird\\MCPFirebird.exe",
"args": ["--env", "C:\\configs\\prod"]
}
}
}
OpenCode (opencode.json). Note the difference: command is a single array that already
includes the arguments (there is no separate args field):
{
"$schema": "https://opencode.ai/config.json",
"mcp": {
"firebird": {
"type": "local",
"command": ["C:\\Tools\\MCPFirebird\\MCPFirebird.exe", "--env", "C:\\configs\\prod"],
"enabled": true
}
}
}
Serving several databases from one build
Register the same executable more than once with different --env folders. Each folder holds
its own .env:
{
"mcpServers": {
"firebird-prod": {
"command": "C:\\Tools\\MCPFirebird\\MCPFirebird.exe",
"args": ["--env", "C:\\configs\\prod"]
},
"firebird-test": {
"command": "C:\\Tools\\MCPFirebird\\MCPFirebird.exe",
"args": ["--env", "C:\\configs\\test"]
}
}
}
C:\configs\prod\.env <- production host/port/database
C:\configs\test\.env <- test host/port/database
The client then shows two independent servers (firebird-prod, firebird-test), each connected to
its own database.
| Key | Default | Meaning |
|---|---|---|
firebird.host | localhost | Server host (TCP). Use the real host/IP for remote DBs |
firebird.port | 3050 | Server port |
firebird.database | (empty) | Full path (or alias) of the database on the server |
firebird.user | SYSDBA | Login user |
firebird.password | masterkey | Login password |
firebird.charset | UTF8 | Connection character set |
firebird.client_lib | (empty) | Full path to fbclient.dll to load |
logger.config.file | loggerpro.stdio.json | File-logger config (logs go to file only; stdout stays pure JSON-RPC) |
Example .env:
firebird.host=localhost
firebird.port=3050
firebird.database=C:\data\MYAPP.FDB
firebird.user=SYSDBA
firebird.password=masterkey
firebird.charset=UTF8
firebird.client_lib=C:\Program Files\Firebird\Firebird_5_0\fbclient.dll
logger.config.file=loggerpro.stdio.json
Why a file and not client-passed env vars? The dotEnv strategy is file-then-env: the
.envfile takes priority, OS environment variables are the fallback. Configuring via.envworks identically across every MCP client because it is read relative to the.exe, regardless of the client's working directory. Keep this file out of version control (it is already.gitignored): it holds credentials.
Run & verify manually
The server speaks JSON-RPC over stdin/stdout. You can smoke-test it without any MCP client by piping framed JSON lines into it. From PowerShell:
$msgs = @(
'{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"manual","version":"1"}}}'
'{"jsonrpc":"2.0","id":2,"method":"tools/list"}'
'{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"fb_info","arguments":{}}}'
) -join "`n"
$msgs | & .\MCPFirebird.exe
Expected: an initialize result naming mcp-firebird, a tools/list with the 10 fb_* tools,
and fb_info returning the live engine_version. (Logs appear under logs\; stdout is
pure JSON-RPC.)
Install it into your AI agent (Claude, Gemini, Cursor, …)
This is how the server gets in front of an AI agent: you register it in the agent's configuration, and from then on the agent can call its tools while answering you. Nothing runs as a service and nothing listens on a port. The agent starts the executable itself, as a child process, and talks to it over stdin/stdout (this is what MCP calls a stdio server). Close the agent and the server is gone with it.
So the whole installation is: tell the agent one command (the absolute path to
MCPFirebird.exe) in the file or CLI its vendor gives you for the purpose. Recipes for the
common agents follow; all of them are the same command in different syntax. The database
connection is not part of it: the server reads that from the .env beside the executable (above),
or from --env <dir>.
Claude Desktop
Edit %APPDATA%\Claude\claude_desktop_config.json:
{
"mcpServers": {
"firebird": {
"command": "C:\\Tools\\MCPFirebird\\MCPFirebird.exe"
}
}
}
Restart Claude Desktop. The fb_* tools, the optimization_goal / health_check prompts, and
the firebird://schema resource appear in the client.
Claude Code (CLI)
Add it with one command (local stdio server):
claude mcp add firebird -- "C:\Tools\MCPFirebird\MCPFirebird.exe"
Or commit a project-scoped .mcp.json at the repo root so teammates inherit it:
{
"mcpServers": {
"firebird": {
"command": "C:\\Tools\\MCPFirebird\\MCPFirebird.exe",
"args": [],
"env": {}
}
}
}
Verify with claude mcp list (or /mcp inside a session).
Gemini CLI
Edit ~/.gemini/settings.json (or a project-level .gemini/settings.json):
{
"mcpServers": {
"firebird": {
"command": "C:\\Tools\\MCPFirebird\\MCPFirebird.exe",
"args": [],
"cwd": "C:\\Tools\\MCPFirebird",
"timeout": 30000,
"trust": false
}
}
}
Then /mcp inside Gemini CLI lists the server and its tools. Setting cwd to the exe's folder
keeps the logs\ directory tidy (the .env is found via the exe path regardless).
OpenCode
Edit opencode.json (global ~/.config/opencode/opencode.json or per-project) and register a
local MCP server (command is an argv array):
{
"$schema": "https://opencode.ai/config.json",
"mcp": {
"firebird": {
"type": "local",
"command": ["C:\\Tools\\MCPFirebird\\MCPFirebird.exe"],
"enabled": true
}
}
}
Cursor / VS Code
Cursor reads .cursor/mcp.json; VS Code (and MCP-aware extensions) read .vscode/mcp.json.
Both use the same shape:
{
"mcpServers": {
"firebird": {
"command": "C:\\Tools\\MCPFirebird\\MCPFirebird.exe"
}
}
}
Any other MCP client
The server is a standard stdio MCP server. Whatever the client's config format, give it:
- command:
C:\Tools\MCPFirebird\MCPFirebird.exe - args: (none), or
["--env", "C:\\configs\\prod"]to use a.envfrom another folder - transport: stdio
- env: (none required), connection comes from the
.env
Tip: to point different clients at different databases, give each one a different
--env <dir>(a folder with its own.env). No need to copy the whole install folder. For example, in a Claude Code.mcp.json:{ "mcpServers": { "firebird": { "command": "C:\\Tools\\MCPFirebird\\MCPFirebird.exe", "args": ["--env", "C:\\configs\\prod"] } } }
Using it from Claude
Once the server is registered, you talk to Claude in plain language. It picks the right fb_*
tool, runs it against your configured database, and turns the result into ready-to-run SQL. The
exchanges below assume the seeded demo database; swap in your own table and column names.
In Claude Desktop the tools appear automatically and the two prompts show up as commands (the 🔌 / "+" menu). In Claude Code run
/mcpto inspect the server, and the prompts are available as slash commands. You can always nudge it explicitly: "use the firebird tools".
1. Get your bearings
You: What Firebird version am I connected to, and which features are available?
Claude calls fb_info and reports the engine version, dialect, charset and detected
capabilities (MON$ tables, explained plans, BOOLEAN, INT128, timezones, parallel workers).
You: List the tables in the database.
→ fb_list_tables → CUSTOMERS, ORDERS, NOPK_LOG, OVERIDX, STALE_T, …
2. Document a schema
You: Document the CUSTOMERS table.
→ fb_generate_documentation → columns, the CUSTOMER_ID primary key and the indexes.
You: Generate full Markdown documentation for the whole database and put it in a file.
→ fb_generate_documentation again (no table = whole DB). Claude returns the Markdown; ask it to
save the text to docs/schema.md if you want it on disk.
3. Diagnose a slow query and fix it
You: This query is slow, why?
SELECT * FROM CUSTOMERS WHERE CITY = 'Rome'
→ fb_analyze_query → "⚠️ NATURAL scan on CUSTOMERS: the filtered column CITY is not
usefully indexed."
You: Suggest an index that fixes it.
→ fb_suggest_indexes → a ready-to-run statement plus how to verify:
CREATE INDEX IDX_CUSTOMERS_CITY ON CUSTOMERS (CITY);
-- Verify: re-run fb_analyze_query; the NATURAL scan on CUSTOMERS should be gone.
You: And this one?
SELECT * FROM CUSTOMERS ORDER BY CITY
→ fb_analyze_query flags an external SORT (no usable index for the ordering).
4. Clean up redundant indexes
You: Which indexes on ORDERS can I safely drop?
→ fb_suggest_index_drops → flags IDX_ORDERS_CUSTOMER_DUP as a duplicate of the
system foreign-key index, with the DROP INDEX statement and a verify step.
You: Do the same for CUSTOMERS.
→ flags the redundant left-prefix (IDX_CUST_NAME), the inactive index (IDX_CUST_CITY) and
the low-selectivity index (IDX_CUST_STATUS).
5. Audit schema health
You: Audit the NOPK_LOG table.
→ fb_audit_table → "🛑 critical: Table NOPK_LOG has no PRIMARY KEY …" with the
ALTER TABLE … ADD CONSTRAINT fix. On OVERIDX it reports over-indexing; on STALE_T it
reports stale statistics with the SET STATISTICS INDEX … fix.
You: Run a full health check on the database.
→ Claude uses the health_check prompt: fb_info → fb_list_tables → fb_suggest_index_drops
per table → a single summary grouped by table with all the ready-to-run SQL.
6. Goal-driven optimization (iterate until met)
The optimization_goal prompt makes Claude loop: measure → suggest → re-measure, stopping as
soon as the goal is met (or it can't improve).
You: Use the optimization_goal prompt. Keep optimizing until this query no longer does a natural scan:
SELECT * FROM CUSTOMERS WHERE CITY = 'Rome'
Claude:
- Calls
fb_evaluate_goal(goal_type=query_no_natural_scan) →met: false(baseline). - Calls
fb_analyze_query+fb_suggest_indexes, presentsCREATE INDEX IDX_CUSTOMERS_CITY …. - You run the SQL (writes are off by default, see Safety).
- Calls
fb_evaluate_goalagain →met: true, and stops with the result.
You can also state the goal numerically, e.g. "get this query under 50 ms"
(goal_type=query_time_ms, threshold=50).
Worked session: optimizing a query on employee.fdb
A full round-trip against the stock employee sample database that ships with Firebird
(examples/empbuild/employee.fdb). The outputs below are verbatim tool results.
You: Analyze this Firebird query and suggest improvements:
SELECT emp_no, first_name, last_name, salary FROM employee WHERE salary > 60000
1. Baseline. fb_analyze_query returns (engine 3.0.12):
PLAN (EMPLOYEE NATURAL)
NATURAL scan on: EMPLOYEE. Run
fb_suggest_indexeson this query for ready-to-run DDL.
NATURAL means Firebird reads every row of EMPLOYEE and throws away those with
salary <= 60000: there is no index on SALARY to seek with.
2. Confirm the problem. fb_evaluate_goal (goal_type=query_no_natural_scan):
{ "goal_type": "query_no_natural_scan", "measured": 1.0, "met": false,
"iteration_hint": "plan: PLAN (EMPLOYEE NATURAL)", "engine_version": "3.0.12" }
3. Get the fix. fb_suggest_indexes:
CREATE INDEX IDX_EMPLOYEE_SALARY ON EMPLOYEE (salary);
Verify: re-run
fb_analyze_query; the plan should useIDX_EMPLOYEE_SALARYand no longer showEMPLOYEE NATURAL. Then runSET STATISTICS INDEX IDX_EMPLOYEE_SALARY;to refresh selectivity.
4. Apply it (the server is read-only: run the DDL yourself), then re-analyze: the plan becomes PLAN (EMPLOYEE INDEX (IDX_EMPLOYEE_SALARY)) and
fb_evaluate_goal returns met: true.
When not to add the index. The win comes from salary > 60000 being selective (few rows).
If the predicate matched most of the table (e.g. salary > 0), the NATURAL scan is actually the
cheaper plan and the index would just add write overhead. Not every NATURAL scan is a bug.
Tool reference
A few call examples (MCP tools/call arguments):
// Describe a table (omit table_name for the whole database)
{ "name": "fb_generate_documentation", "arguments": { "table_name": "CUSTOMERS" } }
// Analyze a query's plan (flags NATURAL scans and external SORTs)
{ "name": "fb_analyze_query", "arguments": { "sql": "SELECT * FROM CUSTOMERS WHERE CITY = 'Rome'" } }
// Suggest indexes for a slow query
{ "name": "fb_suggest_indexes", "arguments": { "sql": "SELECT * FROM CUSTOMERS WHERE CITY = 'Rome'" } }
// Which indexes to drop on a table
{ "name": "fb_suggest_index_drops", "arguments": { "table_name": "ORDERS" } }
// Schema-health audit
{ "name": "fb_audit_table", "arguments": { "table_name": "NOPK_LOG" } }
// Goal check: "this query must no longer do a NATURAL scan"
{ "name": "fb_evaluate_goal",
"arguments": { "goal_type": "query_no_natural_scan",
"target": "SELECT * FROM CUSTOMERS WHERE CITY = 'Rome'",
"threshold": 0 } }
fb_evaluate_goal goal_type values supported in M1: query_no_natural_scan,
query_time_ms, no_redundant_indexes. See
docs/firebird-problem-catalog.md for every problem the
tools detect, the fixture that provokes it, and the milestone it lands in.
Enterprise tools
These six appear in tools/list here too, so your assistant knows they exist and can tell you
what it would do with one. Calling them in this edition returns an isError result explaining
how to get them. They are implemented in the Enterprise edition, which
attaches to the Services Manager as an administrator and reads the server's configuration and
hardware: privileges this edition never asks for.
| Tool | Arguments | What it does |
|---|---|---|
fb_diagnose | (none) | Start here when something is wrong and you do not know why: what is already known, what to ask, and the route to follow |
fb_analyze_config | (none) | Reads firebird.conf and databases.conf and reports every setting that matters (page buffers, TempCacheLimit, LockHashSlots, MaxUnflushedWrites, GCPolicy, parallel workers) against this engine version and this server architecture, since the defaults and even the existence of a parameter change across both |
fb_analyze_storage | table_name? | The physical picture no SELECT can show: index depth, page fill ratios, record-version chain length, page distribution |
fb_parse_log | (none) | Streams firebird.log back over the Services API and separates the noise from what matters: bugchecks, page corruption, I/O errors, sweeps that ran, or never did |
fb_capture_trace | (none) | Opens a bounded Trace API session, samples the real workload, and ranks the statements that actually cost, as a latency distribution, not an average |
fb_analyze_host | config_dir? | The engine against its hardware: RAM versus the memory the configuration actually commits, core count versus MaxParallelWorkers and CpuAffinityMask, free space versus the size of the database, and whether the pages it misses cost a seek |
Plus the part that makes them a product rather than a report: baselines and experiments. Take a measurement under real load, change one parameter, take another, and get a verdict on whether the tail moved, with a rollback if it did not.
Firebird's documentation states no optimal value for most of these parameters, and neither will we. What the Enterprise edition brings is the map from a symptom to the parameter that explains it, and a harness that proves the change worked on your database.
Testing the project
The suite runs the DUnitX core tests against real Firebird servers (2.5 → 5.0) plus a Python stdio compliance suite and a core-boundary check.
Prerequisites for the test matrix
- Firebird zip-kits present under
fb_versions/(paths/ports intests/fbkit.versions.psd1). Ports: 2.5 → 3070, 3.0 → 3053, 4.0 → 3054, 5.0 → 3055. - One-time per kit: the zip-kits ship without a usable
SYSDBA. With the server stopped, create it in embedded mode (needed for 3.0/4.0/5.0; 2.5 works out of the box):
(<kit>\isql.exe -user SYSDBA "<kit>\security<N>.fdb" CREATE USER SYSDBA PASSWORD 'masterkey'; COMMIT; QUIT;security3.fdb/security4.fdb/security5.fdb). - Python 3 with
pytest(python -m pip install pytest).
Run everything (one command)
pwsh tests/run_all.ps1
Or via PyInvoke (tasks.py)
A tasks.py wraps the whole build + test workflow (python -m pip install invoke):
invoke --list # show all tasks
invoke build # build the core test project + the MCP app
invoke core --version 5.0 # core suite against one FB version (start/seed/test/stop)
invoke matrix # core suite across every present FB version
invoke compliance # Python stdio MCP compliance suite (on FB 5.0)
invoke boundary # enforce the core/MVCFramework boundary
invoke all # full run_all.ps1 (matrix + boundary + compliance)
For each present kit it: starts the server → seeds a fresh TESTDB.FDB → runs the core exe →
stops the server; then runs the boundary check and the Python suite on 5.0. Expected tail:
==== Core suite on FB 2.5 ==== ... 27 passed / 3 ignored
==== Core suite on FB 3.0 ==== ... 27 passed / 3 ignored
==== Core suite on FB 4.0 ==== ... 27 passed / 3 ignored
==== Core suite on FB 5.0 ==== ... 27 passed / 3 ignored
Core boundary OK: no MVCFramework imports in sources/
7 passed
ALL SUITES PASSED
(The 3 ignored tests are M2-pending detectors, kept visible as backlog.)
Run against a single version
pwsh tests/fbkit.ps1 -Action start -Version 5.0
pwsh tests/seed/make_seed.ps1 -Version 5.0
$env:FBTEST_PORT='3055'
$env:FBTEST_DB='C:\DEV\mcp-firebird\tests\seed\TESTDB.FDB'
$env:FBTEST_CLIENTLIB=(pwsh tests/fbkit.ps1 -Action client -Version 5.0)
& 'C:\DEV\mcp-firebird\tests\coreproject\MCPFirebirdCoreTests.exe'
pwsh tests/fbkit.ps1 -Action stop -Version 5.0
Python compliance only
pwsh tests/fbkit.ps1 -Action start -Version 5.0
pwsh tests/seed/make_seed.ps1 -Version 5.0
python -m pytest tests/test_mcp_firebird_stdio.py -v
pwsh tests/fbkit.ps1 -Action stop -Version 5.0
Troubleshooting
| Symptom | Likely cause / fix |
|---|---|
| Client shows the server but no tools | .env missing or DB unreachable: the server starts but tools fail on connect. Test with the manual smoke test. |
Your user name and password are not defined (SQLSTATE 28000) | Wrong credentials, or a zip-kit without SYSDBA. See the one-time init above. |
| Analysis tools return empty / no NATURAL scan on a remote DB | Ensure firebird.host is the real host (the plan analyzer uses the configured host). |
fbclient.dll not found / wrong bitness | Set firebird.client_lib to a Win64 fbclient.dll; a 5.0 client works against 2.5-5.0. |
| stdout has non-JSON noise | Logging must go to file only: keep logger.config.file=loggerpro.stdio.json. |
| Port 3050 already in use by another Firebird | Use a distinct port (the test harness puts FB 2.5 on 3070 for this reason). |
Safety & compatibility
- Read-only. No tool runs DDL or write SQL. The SQL an advisory hands you is yours to run, when and if you decide to. Tools that apply a change themselves are planned, and when they arrive they will be off unless you turn them on.
- Cross-version. Capability detection adapts feature use (MON$ tables, explained plans, BOOLEAN, INT128, timezones, parallel workers) to the connected engine; validated on FB 2.5 / 3.0 / 4.0 / 5.0.
- Single configured database per server instance (run multiple instances for multiple DBs).
License
From v0.2.0, licensed under the PolyForm Internal Use License 1.0.0: free on your
own databases, at any scale, and a licence is needed only to pass the software on to somebody else.
See Editions & licensing for what that means in practice, and NOTICE.
v0.1.0 and earlier were released under Apache-2.0, and stay that way for everyone who received them.
MCP Firebird is a showcase for mcp-server-delphi. If you build your own MCP server in Delphi, start there.