How I Built an MCP Server in PHP for Fayyaz Travels Software engineer Manish Shahi built an MCP server in PHP for travel agency Fayyaz Travels, a client of Inncelerator, enabling an AI agent to pull live website analytics (traffic, leads) from the company's existing PHP stack via MySQL and Redis. The server currently handles read-only analytics tools, with plans to add flight, hotel, holiday, and ticket-email tools on the same server later. Shahi chose PHP over Node or Python because the website runs on PHP, and he started with analytics as a safer first tool to avoid touching customer bookings. ← Tips & Tricks /tips How I Built an MCP Server in PHP for Fayyaz Travels By Manish Shahi /about Software Engineer • AI Developer Table of contents 22 I was half a chai deep when another travel AI /glossary/artificial-intelligence demo did the usual thing: pretty answer, zero live data, human still opening admin tabs. That gap is what Fayyaz Travels https://fayyaztravels.com/?utm source=manish.sh&utm medium=blog&utm campaign=how-i-built-mcp-in-php-for-fayyaz-travels&ref=manish.sh needed closed. They are a client of Inncelerator https://inncelerator.com/?utm source=manish.sh&utm medium=blog&utm campaign=how-i-built-mcp-in-php-for-fayyaz-travels&ref=manish.sh . They did not want another chatbot that guesses. They wanted the model to pull real numbers from their PHP stack. So I built an MCP /glossary/mcp server in PHP. Today it only does website analytics traffic, leads, that kind of site readout . Flights, hotels, holiday packages, and ticket email are planned on the same server later. Most people still assume MCP means Node. I stayed in PHP. Why I started with analytics not flights It is tempting to jump straight to “check flights” or “book Dubai.” That is also how you ship a mess. Analytics is a safer first tool: read-only, clear inputs, easy to cache. If something is wrong, you find out without touching customers or bookings. So day one for Fayyaz Travels looked like this: | Live now | Later same MCP | |---|---| | Website analytics tools | check flights , check hotels , lookup holiday | | “How did the site do last 30 days?” | Draft / send ticket replies with approval | | PHP + Redis + MySQL | Still PHP + Redis + MySQL, just more tools | The problem I kept hitting For Fayyaz Travels the day-one ask was simple: “How did the site do last week? Traffic? Leads?” The answer already lived in their PHP stack MySQL + Redis . Getting it still meant opening analytics panels, squinting at charts, and pasting numbers into chat. An agent /glossary/ai-agent could write a nice summary, but it had no live feed. So it guessed, stalled, or waited for a human to copy-paste. That is the same gap as those flashy travel demos, just less glamorous: the model talks, the system stays locked. | Shortcut | Why it sucks | |---|---| | Paste dashboard screenshots into the | context /glossary/context-window API /glossary/api per metricI needed a thin plug into their existing PHP world. MCP /glossary/mcp is that plug. “Check the site numbers” becomes a tool call, not a scavenger hunt. MCP in plain words MCP is a plug between the chat app and your real systems here: the Fayyaz Travels PHP site . - The chat asks: “What jobs can you do?” - Your server answers with a short menu of tools for now: website analytics . - Someone asks in plain English: “How did traffic look last 7 days?” - The chat picks the right tool, your PHP code runs, live numbers come back. No dumping the whole database into the chat. No guessing. Just: ask → tool → answer. - Client asks what tools exist → tools/list - Client runs one → tools/call - Transport: stdio locally, or HTTP + SSE on a network - Messages: JSON-RPC MCP does not replace the website or booking stack. It is a socket so Claude /glossary/claude , Cursor, or an internal chat can call PHP tools with a schema. For this project, the live tool surface is analytics. Everything else in the table is roadmap, same server later: | Job | Without MCP | Status | |---|---|---| | Site analytics traffic, leads | Open dashboards, paste into chat | Live | | Flights | Tabs and copy-paste | Planned check flights | | Hotels | Supplier soup | Planned check hotels | | Holidays | “I’ll forward the PDF” | Planned lookup holiday | | Ticket doubts | Inbox ↔ CRM ping-pong | Planned draft, then send with approval | Why PHP? Because that is the website Most MCP tutorials are Node or Python. Cool. Their site is not. Fayyaz Travels https://fayyaztravels.com/?utm source=manish.sh&utm medium=blog&utm campaign=how-i-built-mcp-in-php-for-fayyaz-travels&ref=manish.sh already runs: | Layer | Stack | |---|---| | Server | PHP | | Front | HTML, Bootstrap, JS | | Cache | Redis | | DB | MySQL | | Speed | Custom PHP scripts that keep pages snappy | Putting MCP in a second language meant copying MySQL access, Redis keys, and those speed scripts. I refused. MCP lives where the site lives. What I aimed for: - Reuse PHP + MySQL + Redis - Keep agent calls as fast as the site’s own scripts - Run MCP from CLI, off the public request path - Stdio first, SSE when I needed a network face Pieces that mattered: PHP CLI, php://stdin / php://stdout , a PHP MCP library for discovery + SSE, and the same Redis/MySQL the Bootstrap front already leans on. How a call actually moves sequenceDiagram participant Agent as Agent client participant MCP as PHP MCP server participant Data as Redis / MySQL Agent- MCP: 1. tools/list MCP-- Agent: 2. tool names + schemas Agent- MCP: 3. tools/call MCP- Data: 4. read cache, then MySQL if needed Data-- MCP: 5. slim payload MCP-- Agent: 6. JSON-RPC result stdio / SSE Note over Agent: 7. Model answers with real numbers You own what is allowed . The model owns when to call. That split matters even for read-only analytics, and more once mailers or flight search land later. Analytics path live php flowchart TD A "tools/call get website analytics " -- B "Validate range" B -- C{"Redis GET analytics summary "} C -- |hit| D "Slim JSON" C -- |miss| E "MySQL aggregate" E -- F "Redis SETEX short TTL " F -- D D -- G "JSON-RPC result back to agent " classDef box fill: f7f5f0,stroke: 2a2a2a,color: 2a2a2a,stroke-width:1px classDef decision fill: eef6f4,stroke: 2a2a2a,color: 2a2a2a,stroke-width:1px class A,B,D,E,F,G box class C decision Fake numbers in the samples below. Real payloads stay small on purpose: totals and trends, not event dumps. Same pattern later for flights not live php flowchart TD A "tools/call check flights, planned " -- B "Validate args" B -- C{"Redis GET flight search key "} C -- |hit| D "Top N options" C -- |miss| E "Search / MySQL" E -- F "Redis SETEX short TTL " F -- D D -- G "JSON-RPC result back to agent " classDef box fill: f7f5f0,stroke: 2a2a2a,color: 2a2a2a,stroke-width:1px classDef decision fill: eef6f4,stroke: 2a2a2a,color: 2a2a2a,stroke-width:1px class A,B,D,E,F,G box class C decision If you return raw rows, the model wastes tokens on junk columns. Map to a tiny DTO. Wire samples analytics Shapes only. Not production data. Keep it under a few KB. Big payloads torch the context window /glossary/context-window and the model gets worse, not better. Schema first, then code Vague schemas = invented arguments. Write the contract before the if . Hotels, holidays, draft reply: same idea. Send mail only after a human yes. Approach 1: stdio loop This is how I started. No port. No nginx. Just a process the agent owns. Analytics handler, cache first: Thing that wasted an evening: PHP buffers stdout. Client hangs after tools/list . Flush every line fflush STDOUT , or turn buffering off. You will do this once and never forget. Approach 2: SSE when stdio gets small I moved to SSE when I wanted a LAN endpoint and tools as files, not a giant switch. Bind to localhost. Proxy only what you need. Do not put this on the public Bootstrap site with no auth. Proxies often drop idle SSE connections. Heartbeats fix most of that. Hook it to Cursor / Claude When later tools ship flights and friends , swap the tool name. The habit stays. Here is that flow in Claude /glossary/claude against the Fayyaz Travels MCP. Red boxes mask real counts and internals so this post does not leak business data. What almost went wrong I almost did all of these, so I am writing them down: - Ship run any sql “just for managers.” Feels powerful. Becomes a liability. - Return full analytics rows. Model drowns. You leak noise. - Infinite Redis TTL. Cached “live” numbers that are three days dead. - Node sidecar “for AI only.” Two codebases fighting over one MySQL. - Public MCP URL on day one. No. Localhost until auth is real. Rules I kept: one verb per tool, small outputs, read before write, approvals for side effects, no secrets in results. | Tool | Status | Notes | |---|---|---| get website analytics | Live | Read-only summary | check flights | Planned | No book-by-default | check hotels | Planned | Cap results | lookup holiday | Planned | Public fields only | draft ticket reply | Planned | Draft first | send ticket reply | Planned | Human approval + audit | Future add-ons examples, not live Analytics only today. The diagram below is where desk tools go once I add them. Flights planned Hotels planned Holidays planned Fetched beats RAG /glossary/rag -only when packages change weekly. Ticket / questionnaire email planned Flow I want: php flowchart TD A "get ticket context" -- B "draft ticket reply" B -- C{"Human approves?"} C -- |yes| D "send ticket reply" C -- |no| E "Edit draft or stop" D -- F "Audit log" classDef box fill: f7f5f0,stroke: 2a2a2a,color: 2a2a2a,stroke-width:1px classDef decision fill: eef6f4,stroke: 2a2a2a,color: 2a2a2a,stroke-width:1px class A,B,D,E,F box class C decision Architecture in one sketch flowchart TB A "Agent Cursor / Claude / chat " B "PHP MCP server" B1 "Analytics tools live " B2 "Desk tools planned " D "Redis + MySQL" E "HTML / Bootstrap / JS website" F "Custom PHP speed scripts" A <-- |"stdio / SSE request + response "| B B --- B1 B --- B2 B1 <-- |"get / set"| D B2 <-.- |"later"| D E <-- D F <-- |"warm cache / speed path"| D classDef box fill: f7f5f0,stroke: 2a2a2a,color: 2a2a2a,stroke-width:1px class A,B,B1,B2,D,E,F box I am not replacing their website or booking stack. I am giving the agent a way to read live site analytics from the same PHP table first. Desk tools can plug into that same MCP later. Checklist I still use Ship analytics - Stdio loop + flush - initialize / tools/list / tools/call - One analytics tool with a real schema - .cursor/mcp.json and a real call Harden - Redis where the site already caches - Cap payloads; validate args - Localhost bind; deliberate proxy - No secrets, no raw dumps Next tools - check flights - check hotels - lookup holiday - draft ticket reply → send ticket reply Closing Fayyaz Travels did not need a prettier chatbot. They needed last week’s traffic and leads from live data, without another round of dashboard copy-paste. Flights, hotels, packages, and careful email can come later on the same MCP, without rewriting the house in Node. MCP is just plumbing. PHP was already there. Analytics proved the integration works. If your stack looks like theirs, skip a second runtime. Ship one read-only tool, flush stdout, then grow. - Client: fayyaztravels.com https://fayyaztravels.com/?utm source=manish.sh&utm medium=blog&utm campaign=how-i-built-mcp-in-php-for-fayyaz-travels&ref=manish.sh - Built with: Inncelerator https://inncelerator.com/?utm source=manish.sh&utm medium=blog&utm campaign=how-i-built-mcp-in-php-for-fayyaz-travels&ref=manish.sh MCP /glossary/mcp · AI agent /glossary/ai-agent · modelcontextprotocol.io https://modelcontextprotocol.io/?utm source=manish.sh&utm medium=blog&utm campaign=how-i-built-mcp-in-php-for-fayyaz-travels&ref=manish.sh Pinch or double-tap to zoom · tap outside to close FAQ Frequently asked questions What does the Fayyaz Travels MCP do today? Website analytics tools only. Ask an agent for live site metrics instead of opening dashboards. Flights, hotels, holidays, and ticket email are planned on the same server. What is MCP? Model Context Protocol is a standard so AI clients can discover and call your tools over stdio or HTTP/SSE. You expose tools. The agent picks when to use them. Why build MCP in PHP for Fayyaz Travels? Their site already runs PHP, HTML, Bootstrap, JS, Redis, MySQL, plus custom PHP scripts for speed. I kept MCP in that stack so tools reuse the same logic. Stdio vs SSE: which should I start with? Stdio for a local proof. SSE/HTTP when you need a network endpoint or attribute-based tool discovery. What is next on the roadmap? Flight search, hotel availability, holiday packages, and email replies for questionnaire or ticket doubts. Each one is a scoped tool, with approvals on anything that sends or spends. What should you never expose through MCP? Blind write access, secrets, payment credentials, or mailers without a human yes. Start read-only. Research this topic further - Click a tool below ChatGPT, Perplexity, Claude, or Gemini . - We copy the full article + companion guide prompt to your clipboard. - A new chat tab opens — if the message box is empty or only shows a short note, press Ctrl+V Windows/Linux or Cmd+V Mac to paste. - Send the message. The model already has the article text — it does not need to open this website. Related posts Comments Share a thought on this post — keep it useful and kind. Comments are moderated before they appear. Loading comments…