Skip to main content
Glama

TaskFlow — a Laravel app that AI agents can actually use

tests Laravel 13 PHP 8.4 laravel/mcp 1.0

TaskFlow is a small project and task tracker built on the Laravel 13 Vue starter kit — and the same domain is exposed to AI agents over the Model Context Protocol with the official laravel/mcp package.

  • A real Laravel app. Projects, tasks and labels with Eloquent relationships, policies, form requests, an Inertia + Vue 3 UI and 95 tests.

  • The same domain, two front doors. Everything the browser can do, an agent can do: 10 tools, 3 resources (including a URI template) and 2 prompts.

  • Real OAuth 2.1. Remote clients register themselves, the user approves a consent screen, and the token is never copied by hand — Passport fronts the HTTP endpoint, with personal access tokens for scripts.

  • Authorization written once. Relationship-scoped queries plus the same policies guard both doors, so an agent is simply another authenticated client.

Screenshots

Dashboard

Project detail

Dashboard

Project detail

Projects

Tasks

Projects

Tasks

MCP tokens

OAuth consent

Related MCP server: Headquarter

The MCP server

app/Mcp/Servers/TaskFlowServer.php is registered twice in routes/ai.php: over stdio for editor agents, and over HTTP for remote clients.

Mcp::local('taskflow', TaskFlowServer::class);

Mcp::web('/mcp/taskflow', TaskFlowServer::class)
    ->middleware(['auth:sanctum', 'throttle:mcp'])
    ->name('mcp.taskflow');

Tools

Tool

Annotations

What it does

list-projects

read-only

Projects with task counts and progress

create-project

—

Creates a project for the authenticated user

update-project

idempotent

Renames, re-statuses or re-schedules a project

delete-project

destructive

Deletes a project and its tasks

list-tasks

read-only

Filter by project, status, priority, overdue, due window, text

create-task

—

Adds a task, creating any new labels on the fly

update-task

idempotent

Title, description, status, priority, due date

complete-task

idempotent

Ticks a task off or reopens it

delete-task

destructive

Deletes a task

generate-progress-report

read-only

Streams processing/progress notifications, then the report

Resources and prompts

  • taskflow://workspace/overview — the dashboard counters as JSON, from the same WorkspaceStats class the dashboard uses.

  • taskflow://projects/{projectId}/summary — a URI template that renders one project as markdown.

  • taskflow://docs/conventions — what each status and priority means, and which tool to reach for. The only publicly cacheable primitive, because it is the only one that contains no user data.

  • weekly-review and plan-project prompts, which drive multi-tool workflows.

Run it locally

composer setup   # installs, generates the app and Passport keys, migrates, seeds, builds
npm run dev

composer setup creates storage/oauth-*.key for Passport (gitignored, as they should be) and the seeder creates the personal access client that the tokens page issues from.

Sign in with demo@taskflow.test / password. The seeder also creates a second user (ada@taskflow.test) whose work must never be visible to the demo user — that is what the scoping tests assert.

Connect an MCP client

Point the client at the server and let it do the rest: it reads /.well-known/oauth-protected-resource, registers itself through dynamic client registration, sends you to a consent screen, and exchanges the code with PKCE.

claude mcp add --transport http taskflow https://laravel-mcp.test/mcp/taskflow

Approve the screen in the browser and the client is connected. The grant is listed on the MCP tokens page and can be revoked there.

Over a personal access token — for scripts and CI

php artisan mcp:token demo@taskflow.test --name="CI" --scopes=mcp:read

Or issue one from the MCP tokens page, which also fills in three copy-ready snippets with it. Send it as a bearer token:

{
    "mcpServers": {
        "taskflow": {
            "type": "http",
            "url": "https://laravel-mcp.test/mcp/taskflow",
            "headers": { "Authorization": "Bearer ${TASKFLOW_MCP_TOKEN}" }
        }
    }
}

Local TLS gotcha. Herd's certificate is trusted by macOS (so curl works) but not by Node's bundled CA list, so a client may fail with UNABLE_TO_VERIFY_LEAF_SIGNATURE. Either point Node at Herd's CA:

export NODE_EXTRA_CA_CERTS="$HOME/Library/Application Support/Herd/config/valet/CA/LaravelValetCASelfSigned.pem"

or skip TLS entirely with php artisan serve and use http://127.0.0.1:8000/mcp/taskflow.

Over stdio (Claude Code, Cursor, …)

On the machine that runs the app, skip URLs, tokens and certificates entirely:

claude mcp add taskflow -- php /absolute/path/to/laravel-mcp/artisan mcp:serve --user=demo@taskflow.test

mcp:serve picks the user the server acts as, then starts the stdio transport. cp .mcp.json.example .mcp.json has the same thing in file form.

Inspect it

php artisan mcp:inspector taskflow      # the stdio server
php artisan mcp:inspector mcp/taskflow  # the HTTP server (keyed by route URI)

How authorization works

Three layers, in this order:

  1. Relationship-scoped lookups. Tools resolve ids through $user->projects() and $user->tasks(), so another user's row comes back as null and is reported as not found — never as forbidden. The tools cannot be used to probe for rows that are not yours.

  2. Scoped validation. create-task validates project_id with Rule::exists('projects', 'id')->where('user_id', $user->id), with an error message that tells the model to call list-projects first.

  3. The same policies as the web UI. Every mutating tool runs Gate::forUser($user)->authorize(...) against ProjectPolicy / TaskPolicy.

On the HTTP transport, Passport decides who the caller is and what it may do. The OAuth flow grants the mcp:use scope, which is full access; a personal access token can be narrowed to mcp:read, and every write tool refuses it with "This MCP token is read-only." throttle:mcp rate-limits per user.

The stdio transport is honest about what it is: mcp:serve --user= is an identity selector for local development, not a security boundary — anyone who can run artisan already has database access. It refuses to run in production.

Architecture

app/Mcp/
├── Servers/TaskFlowServer.php      # name, version, agent instructions, registry
├── Tools/                          # 10 tools
├── Resources/                      # workspace overview, project summary template, conventions
├── Prompts/                        # weekly-review, plan-project
├── Concerns/InteractsWithTaskFlowUser.php  # user resolution + scoping + token abilities
└── Support/LocalUserResolver.php   # stdio identity, refused in production

app/Support/
├── WorkspaceStats.php              # one source for the dashboard and the MCP overview
└── TaskFlowPayload.php             # one shape for Inertia props and structured content

app/Http/Controllers/Mcp/TokenController.php  # the MCP tokens module (issue, list, revoke)
resources/js/pages/mcp-tokens/Index.vue
routes/ai.php                        # Mcp::oauthRoutes() + the two transports

Deliberate omissions, with reasons:

  • No ToolSearch catalog. Ten tools fit comfortably in any client's tool list; a searchable catalog starts paying off past roughly thirty.

  • No published config/mcp.php. This server uses no OAuth, no outgoing clients and no tool-search limits, so publishing it would only add dead config.

  • No #[Cacheable] on user-scoped primitives. Only the static conventions resource is publicly cacheable.

Tests

php artisan test              # 95 tests
php artisan test --filter=Mcp # the MCP layer only
composer ci:check             # lint, types, phpstan level 7, tests — what CI runs

The tests that carry the most weight are the scoping ones: list-projects does not see another user's project, id-taking tools report it as missing, labels are never shared between users, and POST /mcp/taskflow is unauthorized without a token.

What I would do next

  • Queue the progress report once a workspace is large enough for it to be slow.

License

MIT.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    A
    quality
    D
    maintenance
    A local Model Context Protocol server providing backend tools for AI agents to manage projects and tasks with persistent storage in SQLite, enabling structured tracking of project tasks with dependencies, priorities, and statuses.
    12
    4 npm
    25
    GPL 3.0
  • A
    license
    A
    quality
    C
    maintenance
    An AI-first business and project management tool that stores data locally in Markdown and JSON files, exposed via the Model Context Protocol (MCP). Enables project, issue, client, contact, and note management through natural language.
    25
    MIT
  • A
    license
    Not graded
    quality
    D
    maintenance
    Enables AI assistants to introspect a Laravel application's routes, models, controllers, migrations, and more through the Model Context Protocol, running locally via php artisan commands and filesystem scanning.
    5 npm
    MIT