Turn a database connection into a read-only MCP server in minutes.
mcp-forge reads the structure of your database, asks an AI model to design a set of query tools for it, lets you review and switch each tool on or off, and publishes the result as an MCP server that Claude Code, Claude Desktop, Cursor or any other MCP client can use.
Built with TypeScript, Next.js, TanStack Query, Effect and Bun. MySQL / MariaDB and SQLite are supported today; other engines plug into the same driver interface.
⚠️ Early development: storage format and UI can still change.
- Connect – create a project with the connection settings of your database (use a read-only user).
- Introspect – tables, columns, keys, foreign keys and comments are read from the catalog. No data is read.
- Propose tools – the schema is sent to the AI model, which answers with parameterized
SELECTstatements, names, descriptions and typed parameters. An offline generator ("basic tools": lookups, listings, searches, counts) works without any API key. - Curate – every tool is validated (single read-only statement, declared parameters match the SQL). Toggle, edit, test-run or delete tools, or write your own.
- Publish – the project gets an access token and its enabled tools are served over MCP. Changes to the tool list are live immediately.
- Read-only by construction: a SQL guard allows one
SELECT/WITHstatement and rejects DML, DDL, locking clauses,SELECT ... INTO, dangerous functions (SLEEP,LOAD_FILE, ...) and system schemas. Values are bound as parameters, never interpolated. - Bounded queries: a
LIMITis added when missing, results are truncated toMAX_ROWS, queries are cancelled afterQUERY_TIMEOUT. - AI providers: Claude (Anthropic), OpenAI, or any OpenAI-compatible local server (llama.cpp, Ollama, LM Studio, vLLM, ...), selectable per request from the UI. Only the schema is sent, never data.
- Two transports: stateless Streamable HTTP endpoint per project (
/mcp/<project id>, bearer token) and a stdio command for clients that spawn local processes. - Schema as MCP resource: published servers expose
schema://database. - Management UI in English and Italian, light and dark theme (follows the system), usable on phone and desktop.
- Bun 1.2 or newer (Linux, macOS, Windows)
- A database: MySQL 5.7+/8.x or MariaDB 10.3+ (with a read-only user), or a SQLite file readable by the server
- For AI proposals: an Anthropic API key, an OpenAI API key, or a local OpenAI-compatible server
git clone https://github.com/speleoalex/mcp-forge.git
cd mcp-forge
bun install
cp .env.example .env # set ANTHROPIC_API_KEY or OPENAI_API_KEY for AI proposals
bun run dev # http://127.0.0.1:3000Open the UI, create a project, test the connection, introspect the schema, generate tools, publish. The Publish card shows ready-to-paste configuration for MCP clients.
Production build:
bun run build
bun run start # http://127.0.0.1:3000Both scripts bind to 127.0.0.1. To listen on another interface run bun --bun next start -H 0.0.0.0 and put authentication in front (see Security).
cp .env.example .env # optional: AI provider settings, PUBLIC_BASE_URL, ...
docker compose up -d --buildThe UI is reachable on http://127.0.0.1:3000. Projects live in the forge-data volume. PORT in .env selects the published port, PUBLIC_BASE_URL the address MCP clients will use.
- Database on the Docker host: use
host.docker.internalas database host (the database must accept connections from the Docker network). - stdio transport from the container:
docker compose run --rm -T app bun src/cli/mcp-stdio.ts --project <project id>
| Client | How |
|---|---|
| Claude Code | claude mcp add --transport http <name> http://127.0.0.1:3000/mcp/<project id> --header "Authorization: Bearer <token>" |
| HTTP clients (Claude Desktop connectors, Cursor, custom agents) | {"mcpServers": {"<name>": {"type": "http", "url": "...", "headers": {"Authorization": "Bearer <token>"}}}} |
stdio clients (claude_desktop_config.json) |
bun <path to>/src/cli/mcp-stdio.ts --project <project id> with DATA_DIR set to this installation's data directory |
The UI generates all three snippets with the right values. The HTTP endpoint is stateless: each POST carries a complete JSON-RPC exchange and gets a JSON response; GET and DELETE return 405.
The stdio command can also be run by hand:
bun run mcp:stdio -- --project "Shop analytics" # by name or id
bun run mcp:stdio -- --project <id> --allow-draft # serve an unpublished projectSettings are environment variables; .env in the project directory is read by Next.js and by Bun. Empty values count as unset, so the placeholders in .env.example can stay.
| Variable | Default | Description |
|---|---|---|
PORT |
3000 |
Port of the HTTP server (UI, API and MCP endpoint) |
PUBLIC_BASE_URL |
request origin | Base URL written in client snippets; set it behind a reverse proxy |
DATA_DIR |
./data |
Where projects are stored as JSON documents |
MAX_ROWS |
200 |
Maximum rows returned by one tool call |
QUERY_TIMEOUT |
15 seconds |
Maximum duration of one query (Effect duration syntax) |
MCP_REQUIRE_AUTH |
true |
Require Authorization: Bearer <project token> on the MCP endpoint |
SQLITE_ROOT |
– | When set, SQLite projects may only open database files inside this directory |
AI_PROVIDER |
anthropic |
Provider preselected in the UI: anthropic or openai (the latter also for OpenAI-compatible servers). Both stay available |
MAX_PROPOSED_TOOLS |
25 |
Default upper bound of tools per proposal request |
ANTHROPIC_API_KEY |
– | Anthropic credential (sk-ant-...); ANTHROPIC_AUTH_TOKEN and ant auth login profiles also work |
ANTHROPIC_MODEL |
claude-opus-5 |
Anthropic model used to propose tools |
ANTHROPIC_SERVER_FALLBACKS |
true |
Server-side refusal fallbacks (fallbacks: "default") |
OPENAI_API_KEY |
– | OpenAI credential; optional for local servers |
OPENAI_BASE_URL |
https://api.openai.com/v1 |
OpenAI-compatible endpoint, e.g. http://127.0.0.1:8080/v1 |
OPENAI_MODEL |
gpt-5.5 |
Model name as known to the endpoint (for Ollama the tag, e.g. qwen3:32b) |
OPENAI_REASONING_EFFORT |
– | none, minimal, low, medium, high, xhigh or max; sent only when set |
Any server implementing POST /v1/chat/completions works. A JSON-schema response_format is requested; if the server rejects it, one retry is made in json_object mode and the answer is validated locally either way. Nothing leaves your machine.
llama-server -m ./models/qwen3-32b-q4_k_m.gguf --port 8080 -c 32768
# .env
AI_PROVIDER=openai
OPENAI_BASE_URL=http://127.0.0.1:8080/v1
OPENAI_MODEL=qwen3-32bLarge schemas produce long prompts: give the server enough context and prefer models that follow JSON schemas reliably.
mcp-forge is meant to run inside a trusted network by the person who owns the database credentials.
- Use a read-only database user. The SQL guard is a second line of defence, not a replacement for privileges. SQLite files are always opened read-only, with extension loading disabled; set
SQLITE_ROOTto confine projects to one directory. - Credentials are stored in
DATA_DIRas JSON files with mode0600. The password is never returned by the API. - The UI and REST API have no authentication. Keep the default
127.0.0.1bind or put an authenticating reverse proxy in front. - MCP endpoints require a per-project bearer token unless
MCP_REQUIRE_AUTH=false. Tokens can be rotated from the UI. - Only the schema is sent to the AI provider, and with a local server nothing leaves your machine. Tool execution never involves the AI.
- AI output is validated, not trusted: non-
SELECTstatements, dangerous functions and mismatched parameters are rejected before a tool is saved and again at execution time.
bun run dev # UI + API + MCP endpoint with live reload
bun run typecheck # tsc --noEmit
bun test # unit tests
bun run build # production build
bun run e2e # end-to-end test against a MySQL seeded with scripts/e2e-seed.sqlArchitecture, REST API and how to add a database engine or an AI provider: see CONTRIBUTING.md.
- PostgreSQL driver
- Optional authentication for the management UI
- Encrypted credential storage
- Per-tool usage statistics and query log