Skip to content

feat(mcp): laravel/mcp 1.x and the MCP 2026-07-28 protocol - #3957

Merged
marcelfolaron merged 1 commit into
masterfrom
feat/laravel-mcp-1
Oct 20, 2026
Merged

marcelfolaron merged 1 commit into
masterfrom
feat/laravel-mcp-1

Conversation

@marcelfolaron

@marcelfolaron marcelfolaron commented Oct 20, 2026 •

Copy link
Copy Markdown
Collaborator

Phase 1 of the MCP protocol plan: laravel/mcp v0.1.1 → 1.x (1.1.0), which brings the MCP 2026-07-28 protocol and keeps the initialize handshake for older clients. Builds on the Laravel 12 upgrade (#3956).

Plugins counterpart: Leantime/plugins#132 (server class, endpoint, call tracking, plugin tools). Merge both together, then bump app/Plugins to plugins main here.

What changes

  • composer: laravel/mcp ^1.0, which brings nette/utils 4.1.6. illuminate/json-schema already ships with Laravel 12.
  • The 57 core tools (app/Domain/*/Tools) move to the 1.x tool API:
    • schema(JsonSchema): array replaces the chained ToolInputSchema.
    • handle(Request): Response replaces handle(array): ToolResult.
    • Each handler reads $request->all() into the same $arguments array, so the bodies are unchanged apart from ToolResult:: → Response::.
    • The schema chains were converted by a script. SearchTool, whose description is built at runtime, was ported by hand, and every file was reviewed.

The tool contract

I snapshotted tools/list on master before the port and diffed it against the ported server. Names, descriptions, arguments and required lists are unchanged. The only differences are intended:

  • Array arguments now declare items: integer for id lists (findTasks.projectIds, scheduleDay.taskIds), object for record lists, and string for search.types. Strict clients (OpenAI- and Gemini-style tool schemas) require items.
  • Every tool has a title (e.g. "Get iCal URL", "Get Parent KPIs"). Without one, 1.x would derive "Add Comment Tool" from the class name.
  • Annotations:
    • tools that only add data get destructiveHint: false (add*, create*, bulkAdd*, logTime, breakdownTask)
    • deleteEvent gets destructiveHint: true
    • edit tools get idempotentHint
    • The 29 readOnlyHint tools are unchanged.
    • Per the spec, a tool without readOnlyHint is presumed destructive, so clients would otherwise ask for confirmation on every addTask.
  • getICalUrl's empty properties is a JSON object again; v0.1.1 sent [].

Behaviour changes for clients

  • Legacy clients: they keep connecting. initialize with 2025-11-25, 2025-06-18 or 2025-03-26 gets that revision back. Anything older, e.g. 2024-11-05, is offered 2025-11-25 instead of echoed, as the spec requires.
  • JSON-RPC errors now use HTTP status codes: an unknown tool, invalid params or a header mismatch returns 400, an unknown method 404, an internal error 500. Tool failures are still a 200 with isError: true.
  • 2026-07-28 clients:
    • must send MCP-Protocol-Version, Mcp-Method and (for tools/call) Mcp-Name, matching the body
    • a missing or mismatched header gets 400 / -32020
  • Every result carries resultType: "complete".
  • tools/list and server/discover carry ttlMs: 900000 and cacheScope: "private".

Tests

  • McpCest (group mcp):
    • Kept: the legacy handshake tests.
    • Added: 2026-07-28 tests for:
      • server/discover
      • a single-page tools/list with the private cache hint and the full expected tool list
      • tools/call with Mcp-Name
      • a header mismatch returning 400 / -32020
    • Changed: an unknown tool is now asserted as a JSON-RPC -32602 with HTTP 400.
  • The 4 tool unit tests now call handle(new Request([...])).
  • tests/Httprequests/MCP.http is rewritten for both client generations.

Verification

  • Unit suite: 2006 of 2010 green.
    • The other 4 (ConsoleKernelPluginLoadingTest ×2, MenuRepositoryTest ×2) fail only when plugins are checked out locally. They fail the same way on master in that setup, and pass with plugins absent, as in CI.
    • The 7 McpServer plugin tests are green.
  • Static checks: make phpstan clean; Pint clean.
  • HTTP, against a worktree site on a copy of the dev DB (plugins worktree loaded via LEAN_PLUGINS, since requires: >=3.13 drops McpServer on core 3.12.0):
    • Legacy initialize, a notification returning 202, legacy and 2026-07-28 tools/list (59 tools, no nextCursor, private 15-minute hint).
    • server/discover, tools/call with Mcp-Name, a header mismatch (400 / -32020), an unknown tool (400 / -32602).
    • 401 without credentials, 405 on GET. Both x-api-key and Bearer tokens work.
    • addTask → editTask → getTicket → search, and getCalendar.
  • stdio: php bin/leantime mcp:start leantime answers initialize and tools/list.
  • Real client: the MCP Inspector CLI (the official TypeScript SDK, as Claude Code uses) listed all 59 tools and ran addComment → getComments.
  • Not run: the mcp acceptance group. It isn't in CI (OSS checkout has no plugins), and locally it needs the 3.13 version bump: plugins main already requires >=3.13, so /mcp returns 404 on core 3.12.0.

After merge

  • Bump app/Plugins to plugins main (after the plugins PR is squash-merged).
  • Release McpServer 2.0.0 via the plugins Release Plugin workflow (major).

🤖 Generated with Claude Code

Moves the MCP stack from laravel/mcp v0.1.1 to 1.x (1.1.0). The McpServer plugin's side (server
class, endpoint, call tracking) ships in Leantime/plugins and must be merged with this.

- composer: laravel/mcp ^1.0 (brings nette/utils 4.1.6). illuminate/json-schema ships with
  Laravel 12.
- The 57 core tools in app/Domain/*/Tools move to the 1.x tool API:
  - schema(JsonSchema): array instead of the chained ToolInputSchema
  - handle(Request): Response instead of handle(array): ToolResult; the handlers read
    $request->all() and are otherwise unchanged
- The tool contract is unchanged (diffed tools/list before and after): same names, descriptions,
  arguments and required lists. Intended additions only:
  - array arguments declare their items (integer for id lists, object for record lists, string
    for search types), which strict clients require
  - every tool has a title
  - tools that only add data are marked destructiveHint: false, deleteEvent destructiveHint:
    true, edit tools idempotentHint
  - getICalUrl's empty schema is a JSON object again (v0.1.1 serialized it as [])
- McpCest:
  - keeps the legacy initialize tests (2025-06-18, 2025-11-25)
  - adds 2026-07-28 tests: server/discover, a single-page tools/list with a private cache hint
    and the full expected tool list, tools/call with Mcp-Name, header mismatch → 400/-32020
  - an unknown tool is now a JSON-RPC error (400/-32602) instead of an isError result
- tests/Httprequests/MCP.http covers both client generations.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings October 20, 2026 20:21
@marcelfolaron
marcelfolaron requested a review from a team as a code owner October 20, 2026 20:21
@marcelfolaron
marcelfolaron requested review from broskees and removed request for a team October 20, 2026 20:21

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Coordinated private-plugin compatibility remains unverified here, and the MCP acceptance suite has not been run.

No open issues 🎉

What changed in this PR

Upgrades Leantime’s MCP integration to laravel/mcp 1.x following the Laravel 12 upgrade, adding MCP 2026-07-28 support while retaining legacy handshakes.

Changes:

  • Migrates 57 core tools to the new schema, request and response APIs.
  • Adds tool titles, operation annotations and array item types.
  • Updates unit tests, protocol acceptance tests and HTTP examples.
File Description
tests/​Unit/​app/​Domain/​Tickets/​Tools/​GetStatusLabelsToolTest.php Updates request and response assertions.
tests/​Unit/​app/​Domain/​Tickets/​Tools/​BulkAddTasksToolTest.php Updates bulk-result assertions.
tests/​Unit/​app/​Domain/​Projects/​Tools/​GetUsersAssignedToProjectToolTest.php Updates request and response assertions.
tests/​Unit/​app/​Domain/​Goalcanvas/​Tools/​GetParentKPIsToolTest.php Updates request and response assertions.
tests/​Httprequests/​MCP.http Documents both client generations.
tests/​Acceptance/​API/​McpCest.php Adds modern protocol contract checks.
composer.lock Locks MCP 1.1.0 and updated dependencies.
composer.json Requires MCP 1.x.
app/​Domain/​Timesheets/​Tools/​StopTimerTool.php Migrates timer-stop tool API.
app/​Domain/​Timesheets/​Tools/​StartTimerTool.php Migrates timer-start tool API.
app/​Domain/​Timesheets/​Tools/​LogTimeTool.php Migrates API; marks logging non-destructive.
app/​Domain/​Timesheets/​Tools/​GetWeeklyTimesheetsTool.php Migrates weekly timesheet tool API.
app/​Domain/​Timesheets/​Tools/​GetUserTimesheetsTool.php Migrates user timesheet tool API.
app/​Domain/​Timesheets/​Tools/​GetTimesheetSummaryTool.php Migrates timesheet summary tool API.
app/​Domain/​Timesheets/​Tools/​GetProjectTimesheetsTool.php Migrates project timesheet tool API.
app/​Domain/​Tickets/​Tools/​GetTaskTool.php Migrates task retrieval tool API.
app/​Domain/​Tickets/​Tools/​GetTaskStatusSummaryTool.php Migrates status summary tool API.
app/​Domain/​Tickets/​Tools/​GetStatusLabelsTool.php Migrates status-label tool API.
app/​Domain/​Tickets/​Tools/​GetMilestoneTool.php Migrates milestone retrieval tool API.
app/​Domain/​Tickets/​Tools/​GetAllStatusLabelsByUserIdTool.php Migrates user status-label tool API.
app/​Domain/​Tickets/​Tools/​FindTasksTool.php Migrates API; types project ID items.
app/​Domain/​Tickets/​Tools/​FindMilestonesTool.php Migrates milestone search tool API.
app/​Domain/​Tickets/​Tools/​EditTaskTool.php Migrates API; adds idempotency hint.
app/​Domain/​Tickets/​Tools/​EditMilestoneTool.php Migrates API; adds idempotency hint.
app/​Domain/​Tickets/​Tools/​CreateSubtasksForTaskTool.php Migrates API and subtask array schema.
app/​Domain/​Tickets/​Tools/​BulkScheduleTasksTool.php Migrates API and schedule array schema.
app/​Domain/​Tickets/​Tools/​BulkEditTasksTool.php Migrates API and update array schema.
app/​Domain/​Tickets/​Tools/​BulkAddTasksTool.php Migrates API and task array schema.
app/​Domain/​Tickets/​Tools/​AddTaskTool.php Migrates API; preserves status union.
app/​Domain/​Tickets/​Tools/​AddSubtaskTool.php Migrates API; preserves status union.
app/​Domain/​Tickets/​Tools/​AddMilestoneTool.php Migrates API; marks creation non-destructive.
app/​Domain/​Tickets/​Tools/​AddMilestonesForProjectTool.php Migrates API and milestone array schema.
app/​Domain/​Search/​Tools/​SearchTool.php Migrates API; preserves dynamic descriptions.
app/​Domain/​Projects/​Tools/​PatchProjectTool.php Migrates API; adds idempotency hint.
app/​Domain/​Projects/​Tools/​GetUsersAssignedToProjectTool.php Migrates project-member tool API.
app/​Domain/​Projects/​Tools/​GetProjectTool.php Migrates project retrieval tool API.
app/​Domain/​Projects/​Tools/​GetProjectsAssignedToUserTool.php Migrates assigned-project tool API.
app/​Domain/​Projects/​Tools/​GetFullProjectOverviewTool.php Migrates project overview tool API.
app/​Domain/​Projects/​Tools/​GetAllProjectsTool.php Migrates project listing tool API.
app/​Domain/​Projects/​Tools/​FindProjectTool.php Migrates project search tool API.
app/​Domain/​Projects/​Tools/​EditProjectTool.php Migrates API; adds idempotency hint.
app/​Domain/​Projects/​Tools/​AddProjectTool.php Migrates API; marks creation non-destructive.
app/​Domain/​Goalcanvas/​Tools/​GetParentKPIsTool.php Migrates parent KPI tool API.
app/​Domain/​Goalcanvas/​Tools/​GetGoalTool.php Migrates goal retrieval tool API.
app/​Domain/​Goalcanvas/​Tools/​GetGoalsByMilestoneTool.php Migrates milestone-goal tool API.
app/​Domain/​Goalcanvas/​Tools/​GetChildGoalsTool.php Migrates child-goal tool API.
app/​Domain/​Goalcanvas/​Tools/​GetAllGoalsTool.php Migrates goal listing tool API.
app/​Domain/​Goalcanvas/​Tools/​EditGoalTool.php Migrates API; adds idempotency hint.
app/​Domain/​Goalcanvas/​Tools/​CreateGoalTool.php Migrates API; marks creation non-destructive.
app/​Domain/​Goalcanvas/​Tools/​CreateGoalboardTool.php Migrates API; marks creation non-destructive.
app/​Domain/​Comments/​Tools/​PollCommentsTool.php Migrates comment polling tool API.
app/​Domain/​Comments/​Tools/​GetCommentsTool.php Migrates comment retrieval tool API.
app/​Domain/​Comments/​Tools/​GetAllProjectCommentsTool.php Migrates project-comment tool API.
app/​Domain/​Comments/​Tools/​AddProjectStatusUpdateTool.php Migrates API; marks additions non-destructive.
app/​Domain/​Comments/​Tools/​AddCommentTool.php Migrates API; marks additions non-destructive.
app/​Domain/​Calendar/​Tools/​ScheduleTaskOnCalendarTool.php Migrates API; adds idempotency hint.
app/​Domain/​Calendar/​Tools/​ScheduleDayTool.php Migrates API; types event and ID arrays.
app/​Domain/​Calendar/​Tools/​GetICalUrlTool.php Uses default schema and new response API.
app/​Domain/​Calendar/​Tools/​GetCalendarTool.php Migrates calendar retrieval tool API.
app/​Domain/​Calendar/​Tools/​EditCalendarEventTool.php Migrates API; adds idempotency hint.
app/​Domain/​Calendar/​Tools/​DeleteCalendarEventTool.php Migrates API; marks deletion destructive.
app/​Domain/​Calendar/​Tools/​BulkEditCalendarEventsTool.php Migrates API and update array schema.
app/​Domain/​Calendar/​Tools/​BulkAddCalendarEventsTool.php Migrates API and event array schema.
app/​Domain/​Calendar/​Tools/​BreakdownTaskTool.php Migrates API and subtask array schema.
app/​Domain/​Calendar/​Tools/​AddCalendarEventTool.php Migrates API; marks creation non-destructive.

Review effort: Balanced

@marcelfolaron
marcelfolaron merged commit 30dfbb2 into master Oct 20, 2026
12 checks passed
@marcelfolaron
marcelfolaron deleted the feat/laravel-mcp-1 branch October 20, 2026 20:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants