How I Built a Support Ticket MCP Server in Laravel
<p>Model Context Protocol is much easier to understand when it is attached to a real workflow.</p> <p>In my Laravel project, I wanted a support-focused MCP server that could do three concrete things:</p> <ul> <li>create a support ticket</li> <li>read a ticket from a resource URI</li> <li>generate a polished summary that is ready to send to a customer or teammate</li> </ul> <p>Instead of building a generic demo, I kept the scope narrow and useful. The result is a local <code>support</code> MCP server powered by <code>laravel/mcp</code>, with one tool, one resource, and one prompt working together as a small support system.</p> <h2> Why this is a good MCP use case </h2> <p>A lot of MCP examples stop at "here is a tool that returns some JSON." That is fine for learning the API, but it does no
Model Context Protocol is much easier to understand when it is attached to a real workflow.
In my Laravel project, I wanted a support-focused MCP server that could do three concrete things:
-
create a support ticket
-
read a ticket from a resource URI
-
generate a polished summary that is ready to send to a customer or teammate
Instead of building a generic demo, I kept the scope narrow and useful. The result is a local support MCP server powered by laravel/mcp, with one tool, one resource, and one prompt working together as a small support system.
Why this is a good MCP use case
A lot of MCP examples stop at "here is a tool that returns some JSON." That is fine for learning the API, but it does not show why the protocol is interesting.
Support workflows are a better fit because they combine three different kinds of interaction:
-
an action that changes state,
-
a resource that can be fetched again later,
-
a prompt that turns raw operational data into something a human can actually use.
That maps cleanly to MCP:
-
Tool: create the ticket.
-
Resource: fetch the ticket by identifier.
-
Prompt: transform the ticket into a professional summary.
This separation is what I like most about the approach. The AI client does not need custom knowledge about my Laravel app. It only needs to know which MCP capabilities are available.
Registering the server in Laravel
The first step in my project was registering a local MCP server in routes/ai.php:
use App\Mcp\Servers\SupportServer; use Laravel\Mcp\Facades\Mcp;use App\Mcp\Servers\SupportServer; use Laravel\Mcp\Facades\Mcp;Mcp::local('support', SupportServer::class);`
Enter fullscreen mode
Exit fullscreen mode
That gives the app a dedicated MCP entry point for support operations. I also have a separate CRM server in the same project, which keeps responsibilities isolated instead of pushing every AI capability into one giant surface area.
Defining the support server
The server itself is intentionally small. Its job is to compose capabilities, not to hold business logic. In app/Mcp/Servers/SupportServer.php, I register the three pieces that make the workflow useful:
class SupportServer extends Server { protected array $tools = [ CreateTicketTool::class, ];class SupportServer extends Server { protected array $tools = [ CreateTicketTool::class, ];protected array $resources = [ TicketResource::class, ];
protected array $prompts = [ SummarizeTicketPrompt::class, ]; }`
Enter fullscreen mode
Exit fullscreen mode
That structure is one of the reasons Laravel feels comfortable here. The framework already encourages small classes with clear responsibilities, and laravel/mcp fits that style well.
The ticket creation tool
The first capability I needed was a way for an AI client to create a support ticket safely.
In app/Mcp/Tools/CreateTicketTool.php, the tool validates three inputs:
-
title
-
description
-
priority
It then creates the ticket and returns a structured response with the fields an agent actually needs next:
return Response::structured([ 'id' => $ticket->id, 'title' => $ticket->title, 'status' => $ticket->status, 'priority' => $ticket->priority, ]);return Response::structured([ 'id' => $ticket->id, 'title' => $ticket->title, 'status' => $ticket->status, 'priority' => $ticket->priority, ]);Enter fullscreen mode
Exit fullscreen mode
This is an important detail. I do not want the tool to return a blob of unstructured text when the next step may depend on the new ticket ID. MCP tools are much more useful when they return predictable data that another step can consume immediately.
I also defined the schema explicitly. That gives the client a machine-readable contract instead of forcing it to guess the arguments:
public function schema(JsonSchema $schema): array { return [ 'title' => $schema->string()->required(), 'description' => $schema->string()->required(), 'priority' => $schema->string() ->enum(['low', 'medium', 'high']) ->default('medium'), ]; }public function schema(JsonSchema $schema): array { return [ 'title' => $schema->string()->required(), 'description' => $schema->string()->required(), 'priority' => $schema->string() ->enum(['low', 'medium', 'high']) ->default('medium'), ]; }Enter fullscreen mode
Exit fullscreen mode
For support workflows, this matters a lot. Validation rules are not just a backend concern anymore. They become part of the interface between your Laravel app and the AI client.
The ticket resource
Creating a ticket is only half of the story. Once the ticket exists, an agent should be able to retrieve it again without calling a mutation tool.
That is where the resource comes in. In app/Mcp/Resources/TicketResource.php, the resource receives a ticket identifier, loads the record, and returns a readable text representation:
return Response::text( "Ticket #{$ticket->id}\n". "Titre: {$ticket->title}\n". "Statut: {$ticket->status}\n". "Priorité: {$ticket->priority}\n\n". "Description:\n{$ticket->description}" );return Response::text( "Ticket #{$ticket->id}\n". "Titre: {$ticket->title}\n". "Statut: {$ticket->status}\n". "Priorité: {$ticket->priority}\n\n". "Description:\n{$ticket->description}" );Enter fullscreen mode
Exit fullscreen mode
I like this pattern because it makes the ticket easy to reuse in multiple contexts:
-
an assistant can read it before answering a user
-
another prompt can summarize it
-
a human can inspect the output without decoding JSON
This is a small design choice, but it makes the system more composable. A tool changes state. A resource exposes state. Keeping those responsibilities separate leads to cleaner MCP surfaces.
The prompt that turns raw data into a ready-to-send summary
The third piece is the one that makes the demo feel like a real support workflow rather than a CRUD exercise.
In app/Mcp/Prompts/SummarizeTicketPrompt.php, I created a prompt that takes:
-
a tone
-
the raw ticket_text
The prompt then instructs the model to return a structured support summary covering:
-
the problem
-
the impact
-
the suggested priority
-
the next action.
Here is the core idea:
return [ Response::text( "You are a support assistant. Summarize the ticket below in a {$tone} tone. ". "Return: problem, impact, suggested priority, next action." )->asAssistant(), Response::text($ticket_text), ];return [ Response::text( "You are a support assistant. Summarize the ticket below in a {$tone} tone. ". "Return: problem, impact, suggested priority, next action." )->asAssistant(), Response::text($ticket_text), ];Enter fullscreen mode
Exit fullscreen mode
This is the part many teams skip. They expose data, but they do not shape the final output. In support, the final wording matters:
-
internal teams may want concise operational summaries
-
customer-facing teams may want a more professional tone
-
some situations need a friendlier message without losing the technical facts.
Packaging that as an MCP prompt means the formatting logic is reusable instead of being reinvented in every client.
Why I like this architecture
What I ended up with is not a huge system. It is a very small one. That is exactly why it works well.
Each MCP primitive has a clear role:
-
the tool creates the record,
-
the resource exposes the record,
-
the prompt transforms the record into communication.
That separation makes the project easier to extend. If I want to go further, I already know the next logical steps:
-
add a tool to update ticket status,
-
expose a resource for recent tickets,
-
create prompts for escalation notes or customer replies,
-
connect the same support server to external AI clients.
Laravel gives me the application structure, validation, and persistence patterns I already trust. MCP adds a clean interface layer on top of that.
Final thoughts
The main lesson from this project is that MCP becomes much more compelling when you model a real workflow instead of isolated demo commands.
In my Laravel app, the support server is useful because it does three connected things well:
-
it creates a ticket with validated input,
-
it lets the client read that ticket back as a resource,
-
it provides a prompt that converts raw ticket data into a professional summary.
That is enough to move from "AI can call my backend" to "AI can participate in a support flow with clear boundaries."
If you are experimenting with laravel/mcp, I would strongly recommend starting with a narrow domain like support, sales handoff, onboarding, or incident triage. Pick one workflow, then model it with one tool, one resource, and one prompt. You will learn much more than you would from a generic demo.
If I keep iterating on this project, the next step will be turning this support server into a fuller ticket operations layer with richer resources and multi-step support actions. But even in its current form, it already shows why Laravel and MCP fit together so naturally.
Sign in to highlight and annotate this article

Conversation starters
Daily AI Digest
Get the top 5 AI stories delivered to your inbox every morning.
More about
modelavailableupdateKnowledge Map
Connected Articles — Knowledge Graph
This article is connected to other articles through shared AI topics and tags.
More in Products

FinancialContent - Hardison Co. Announces Project20x White-Label Platform, Creating a Universal AI Engine for Healthcare, Government, and Social Services - FinancialContent
FinancialContent - Hardison Co. Announces Project20x White-Label Platform, Creating a Universal AI Engine for Healthcare, Government, and Social Services FinancialContent





Discussion
Sign in to join the discussion
No comments yet — be the first to share your thoughts!