Skip to main content
Glama
0mandrock1

mcp-artisan

by 0mandrock1

mcp-artisan

Point an AI agent at a Laravel codebase and ask it to explain the routes, the data model, the queued jobs, or a config value — without giving it any way to change the code, run a console command, or read a secret. mcp-artisan is a Model Context Protocol server that exposes a Laravel project to an agent read-only. The interesting part is not the list of things it can report; it is that the things it cannot do are enforced structurally, not by convention.

The server runs over stdio, speaks MCP, and needs no framework installed and no LLM API key. It works against a project whether or not PHP is present on the machine.

The problem

An agent is genuinely useful on a large PHP application: "which routes are unauthenticated?", "what does the orders table look like?", "which jobs run on the payments queue?". But the moment you hand a general-purpose agent a shell and a project directory to answer those questions, you have also handed it the ability to run php artisan migrate:fresh, read .env, or edit a controller. On a production or legacy codebase that trade is unacceptable, so the questions go unanswered or a human does the archaeology by hand.

The useful shape is a tool that can only look. mcp-artisan gives an agent a faithful, bounded map of the application and makes mutation impossible by construction — there is no write path and no arbitrary-command path to abuse.

Related MCP server: Local Code MCP Server

Threat model

The assumed adversary is the agent itself: a capable, possibly-confused, or prompt-injected LLM that will call any tool it is offered with any argument it can construct. The design goal is that no sequence of tool calls can change the project, exfiltrate a secret, or read a file outside the project root.

What that means in practice, and where each guarantee lives:

  • No mutation. The server exposes no tool that writes. The static backend only ever opens files for reading; the artisan backend only ever runs verbs from a frozen allowlist (route:list, migrate:status, about), all of which are read-only. The allowlist is checked before a subprocess is created (mcp_artisan/artisan_backend.py), so a request for migrate or tinker never reaches php.

  • No arbitrary commands. Artisan verbs are constants in the code, never built from tool input. The subprocess is invoked with a fixed argument vector (no shell), so there is nothing to inject into, and each call has a hard wall-clock timeout.

  • No secret disclosure. .env is never read. Config values come from config/*.php, and every value is passed through key-based redaction (mcp_artisan/redaction.py): a key matching password, secret, token, key, dsn, and similar returns <redacted>, including nested keys inside a returned subtree. Redaction is deliberately broad — it prefers hiding a harmless value to leaking a credential.

  • No path escape. Every filesystem read is funnelled through one containment check (mcp_artisan/paths.py) that resolves the real path and rejects anything outside the configured project root, including via a crafted config key or a symlink that points out of the tree.

Every one of these is covered by a negative test; see tests/test_paths.py, tests/test_config_redaction.py, tests/test_artisan_backend.py, and tests/test_server_protocol.py.

What is explicitly out of scope: this is not a sandbox for the agent's other tools, and it does not defend against a compromised host or a malicious PHP runtime. It hardens the one surface it owns — introspection of the codebase.

Tools and resources

Tools (all read-only):

Tool

Returns

project_summary

Counts and versions, in one call, for cheap orientation.

list_routes

Every route: methods, URI, name, middleware, controller@action.

describe_route(name_or_uri)

One route by name or URI.

list_migrations

Migration files on disk, plus applied/pending state when a database is reachable.

list_models

Eloquent models: table, $fillable, $casts, relations.

list_jobs_and_queues

Queued job classes with $queue, $tries, $timeout.

config_get(key)

Dotted config lookup with mandatory secret redaction.

composer_deps

Direct requires with versions, plus Laravel and PHP versions.

Resources: laravel://routes, laravel://schema, laravel://composer — the same data as JSON documents, for clients that prefer resources to tool calls.

Two backends, auto-detected

  1. static (default, and the only one used in CI). Pure parsing of composer.json, routes/*.php, database/migrations/*.php, app/Models/*.php, app/Jobs/*.php, and config/*.php. Requires no PHP.

  2. artisan. Used only when php is on PATH and php artisan about succeeds. Routes and migration status then come from route:list --json and migrate:status, which are exact where the static parser is best-effort. Models, jobs, config, and composer data stay static even here — no read-only artisan verb exposes them.

The backend is chosen once at startup and reported in every response so an agent knows how much to trust the data. Set MCP_ARTISAN_STATIC_ONLY=1 to force the static backend even when PHP is available.

Usage

Install and run against a project:

pip install -e .
MCP_ARTISAN_PROJECT=/path/to/laravel-app python -m mcp_artisan

Or register it with an MCP client (the entry point is the mcp-artisan console script):

{
  "mcpServers": {
    "artisan": {
      "command": "mcp-artisan",
      "env": { "MCP_ARTISAN_PROJECT": "/path/to/laravel-app" }
    }
  }
}

The project root defaults to the current working directory if MCP_ARTISAN_PROJECT is unset.

Reproducing the numbers

This repository ships a small hand-built fixture Laravel app under tests/fixtures/laravel-app (no framework was downloaded). Every count quoted below is produced by a command here, not asserted in prose.

python scripts/demo.py

prints, for the fixture: 8 tools exposed, and a project_summary of 6 routes, 2 models, 2 migrations, 2 jobs, 3 direct dependencies, on the static backend — followed by a redacted database password to show the masking.

The artisan allowlist is three verbs:

python -c "from mcp_artisan.artisan_backend import ALLOWED_VERBS; print(sorted(ALLOWED_VERBS))"
# ['about', 'migrate:status', 'route:list']

The full test suite (unit tests for both backends, plus protocol-level tests that drive the server through an in-memory MCP client) runs with:

pip install -e ".[dev]"
pytest

CI runs exactly this on every push (.github/workflows/ci.yml), with no PHP installed and no secrets.

Design notes

Choices made in the interest of a smaller, safer, more predictable server. Where a fuller behaviour was possible, the simpler option was taken and recorded here.

  • The static route parser does not resolve Route::group prefixes/middleware, Route::resource expansion, or closures' internals. It parses top-level Route::<verb> statements and their chained ->name()/->middleware(). This is a best-effort map; where exactness matters, the artisan backend provides the real route table. Closures are reported with an action of Closure.

  • The static backend cannot know which migrations have run — that needs a database connection. It lists the files and reports applied/pending as unknown rather than guessing. The artisan backend fills in real status.

  • The config parser understands a subset of PHP: scalars, arrays (both [...] and array(...)), env() (evaluated as its default argument only, since .env is never read), string concatenation, and Class::class. Anything it cannot evaluate — storage_path(), closures, other calls — resolves to null rather than raising. It never executes PHP.

  • Redaction is keyed on the config key, not the value, because the key is the trustworthy signal. It over-redacts by design: a false positive hides a value the agent could have seen; a false negative leaks a credential.

  • composer_deps reports the require block only, not require-dev, since the target is the application's runtime dependency surface.

  • Default model table names are derived with a small snake-case + pluralisation rule (Userusers, Categorycategories). It covers the common cases, not every English irregular; models with an explicit $table are always exact.

  • Jobs are read from app/Jobs only. Queueable classes elsewhere are not discovered by the static backend.

  • The backend is selected once at startup, not per call, so a project does not flip between backends mid-session.

Layout

mcp_artisan/
  paths.py            project-root resolution and the path-containment check
  redaction.py        key-based secret redaction
  phpparse.py         defensive parser for the PHP subset in config/routes/models
  static_backend.py   source-only parsers (the default backend)
  artisan_backend.py  guarded, allowlisted php-artisan subprocess backend
  service.py          backend selection and the per-tool responses
  server.py           the MCP tool/resource surface over stdio
tests/
  fixtures/laravel-app/   hand-built minimal Laravel app
  test_*.py               unit, protocol, and negative safety tests

Requirements

Python 3.12+. The only runtime dependency is the official mcp SDK. PHP is optional and only enables the artisan backend.

License

MIT.

Install Server
A
license - permissive license
A
quality
C
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Servers

  • A
    license
    -
    quality
    C
    maintenance
    Provides secure and efficient tools for codebase analysis, including file management, metadata retrieval, and dependency tree traversal. It allows LLMs to explore project structures and search for configuration files within a restricted root directory.
    Last updated
    21
    3
    MIT
  • F
    license
    B
    quality
    D
    maintenance
    Provides LLMs with safe, read-only access to local codebases for searching, reading files, and finding function definitions. All source code remains local, ensuring privacy while enabling AI assistants to explore project structures and functionality.
    Last updated
    4
  • F
    license
    A
    quality
    D
    maintenance
    Provides AI assistants with direct access to Laravel documentation, coding rules, and implementation templates stored locally. It enables searching documentation, retrieving design system guides, and accessing domain-specific code examples to streamline Laravel development.
    Last updated
    8
  • A
    license
    -
    quality
    C
    maintenance
    Provides AI agents with secure, read-only file system access to analyze and understand project codebases, enabling multi-repository context aggregation and cross-project code tracing.
    Last updated
    5
    MIT

View all related MCP servers

Related MCP Connectors

  • Read-only tools over the Safer Agentic AI framework: 238 patterns + 14 heuristics.

  • Give your AI agent a persistent map of your project's structure, dependencies, and bugs.

  • Read-only bank access for your AI agent. Connects Claude, ChatGPT, Cursor, Gemini, Codex.

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/0mandrock1/mcp-artisan'

If you have feedback or need assistance with the MCP directory API, please join our Discord server