datomatic/laravel-database-mcp
A Laravel package that exposes your database to AI assistants via the Model Context Protocol. Browse schemas, inspect tables, and run safe read queries from MCP clients like Claude Desktop or Cursor, making database exploration conversational.
Install and wire it up
composer require datomatic/laravel-database-mcp
php artisan vendor:publish --tag="laravel-database-mcp-config"
Then register the server where your Laravel MCP setup expects it (typically routes/ai.php or the mcp section of config/ai.php, depending on your laravel/mcp version). v0.2.0 requires laravel/mcp 1.0+, so bump that first if you're still on a 0.x pin — Composer will usually resolve it, but check composer why laravel/mcp if the install is stuck.
Where to look first
src/Tools or similar) — this is what your AI client actually sees: list tables, describe a table, run a query.php artisan list mcp / the laravel/mcp docs for how to inspect and test the server locally.First useful use case
Point Claude Desktop (or any MCP client) at your local server and ask: "List the tables, then show me how orders relates to users and order_items." That exercises every tool in the package at once and immediately tells you whether the exposed schema is useful or noisy.
Separate the DB user from your app user. Create a dedicated read-only MySQL/Postgres user for the MCP connection and set it in the package config's connection block. This is the single highest-value configuration decision and it survives every refactor.
Expose a curated subset, not the whole schema. Large Laravel apps have 80+ tables including jobs, failed_jobs, telescope_entries, personal_access_tokens. Every one of those eats context and invites the model to answer from the wrong table. Use the config's table allow/deny lists to expose the domain tables only.
Per-environment registration. Register the server conditionally so it only exists in local (and optionally staging):
if (app()->environment('local')) {
// register database MCP server
}
Never register it in production, even with a read-only user — the endpoint is an unauthenticated SQL surface unless you put auth in front of it.
Pair it with app-specific tools. The DB tools answer "what does the schema look like." They can't answer "what does OrderStatus::Paid mean" or "which queue handles refunds." Add a small custom tool or a markdown context resource alongside it so the model doesn't reverse-engineer business logic from column names.
Use it as a schema oracle, not a data oracle. The best workflow is: model reads schema → model writes the query → you run the query in Tinker or your SQL client. Letting it run exploratory SELECT * on a table with 4M rows is where this gets slow and expensive.
Testing. Keep one test that boots the MCP server and asserts the tool list — it catches the kind of namespace/registration drift that v0.2.0's refactor can introduce.
v0.2.0 is a "quiet" breaking release. The changelog says "update constraint + refactoring," which usually means moved namespaces and possibly renamed tool classes or config keys. After upgrading:
diff against yours before overwriting — config keys may have been added or renamed during the refactor.composer dump-autoload won't save you from a moved class.Pin laravel/mcp deliberately. v0.2.0 allows ^1.0, which means composer update can now move you across minor laravel/mcp releases you haven't tested. If you want stability, pin the exact minor you validated.
Read-only is a convention, not a guarantee. Verify what the package actually enforces. If it filters statements by keyword or prefix, that is bypassable. The database grant is the real control.
Context blowout on wide schemas. describe table on a 40-column table with long enum comments can consume thousands of tokens. Prune column comments in the DB, or restrict to the tables that matter.
Timeouts and row caps. Set a statement timeout on the read-only DB user (SET statement_timeout / max_execution_time) as a backstop. A model-generated unindexed join will otherwise hang your request until PHP maxes out.
Transport confusion. stdio vs. HTTP/SSE trips people up constantly — stdio servers must be launched from the client config, HTTP servers must be registered as routes and reached over the network. If the client says "no tools found," confirm you're using the transport the package actually ships, not the one in the blog post you followed.
Debugging. Start with the Laravel log; MCP tool errors are often swallowed and returned as generic failure text to the client. If the server registers but tools fail, hit the tool class directly in Tinker to separate "MCP plumbing broken" from "query broken."
Extension point. The cleanest customization is a new tool class that composes the package's connection handling but encodes your own guardrails (e.g. a explain_query tool, or a sample_table tool that hardcodes LIMIT 5). Prefer that over forking the package — the refactor in v0.2.0 is a preview of how much churn internals see.
How can I help you explore Laravel packages today?