{"slug": "how-i-built-an-mcp-server-in-php-for-fayyaz-travels", "title": "How I Built an MCP Server in PHP for Fayyaz Travels", "summary": "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.", "body_md": "[← Tips & Tricks](/tips)\n\n# How I Built an MCP Server in PHP for Fayyaz Travels\n\nBy[Manish Shahi](/about)Software Engineer • AI Developer\n\n## Table of contents(22)\n\nI 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.\n\nThat 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.\n\nSo I built an [MCP](/glossary/mcp) server in PHP.\n\n**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.\n\nMost people still assume MCP means Node. I stayed in PHP.\n\n## Why I started with analytics (not flights)\n\nIt is tempting to jump straight to “check flights” or “book Dubai.” That is also how you ship a mess.\n\nAnalytics is a safer first tool: read-only, clear inputs, easy to cache. If something is wrong, you find out without touching customers or bookings.\n\nSo day one for Fayyaz Travels looked like this:\n\n| Live now | Later (same MCP) |\n|---|---|\n| Website analytics tools | `check_flights` , `check_hotels` , `lookup_holiday` |\n| “How did the site do last 30 days?” | Draft / send ticket replies (with approval) |\n| PHP + Redis + MySQL | Still PHP + Redis + MySQL, just more tools |\n\n## The problem I kept hitting\n\nFor Fayyaz Travels the day-one ask was simple: “How did the site do last week? Traffic? Leads?”\n\nThe 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.\n\nThat is the same gap as those flashy travel demos, just less glamorous: the model talks, the system stays locked.\n\n| Shortcut | Why it sucks |\n|---|---|\n| Paste dashboard screenshots into the\n|\n\n[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.\n\n## MCP in plain words\n\n**MCP** is a plug between the chat app and your real systems (here: the Fayyaz Travels PHP site).\n\n- The chat asks: “What jobs can you do?”\n- Your server answers with a short menu of tools (for now: website analytics).\n- Someone asks in plain English: “How did traffic look last 7 days?”\n- The chat picks the right tool, your PHP code runs, live numbers come back.\n\nNo dumping the whole database into the chat. No guessing. Just: ask → tool → answer.\n\n- Client asks what tools exist →\n`tools/list`\n\n- Client runs one →\n`tools/call`\n\n- Transport:\n`stdio`\n\nlocally, or HTTP + SSE on a network - Messages: JSON-RPC\n\nMCP 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.\n\nFor this project, the live tool surface is analytics. Everything else in the table is roadmap, same server later:\n\n| Job | Without MCP | Status |\n|---|---|---|\n| Site analytics (traffic, leads) | Open dashboards, paste into chat | Live |\n| Flights | Tabs and copy-paste | Planned (`check_flights` ) |\n| Hotels | Supplier soup | Planned (`check_hotels` ) |\n| Holidays | “I’ll forward the PDF” | Planned (`lookup_holiday` ) |\n| Ticket doubts | Inbox ↔ CRM ping-pong | Planned (draft, then send with approval) |\n\n## Why PHP? Because that is the website\n\nMost MCP tutorials are Node or Python. Cool. Their site is not.\n\n[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:\n\n| Layer | Stack |\n|---|---|\n| Server | PHP |\n| Front | HTML, Bootstrap, JS |\n| Cache | Redis |\n| DB | MySQL |\n| Speed | Custom PHP scripts that keep pages snappy |\n\nPutting MCP in a second language meant copying MySQL access, Redis keys, and those speed scripts. I refused. MCP lives where the site lives.\n\nWhat I aimed for:\n\n- Reuse PHP + MySQL + Redis\n- Keep agent calls as fast as the site’s own scripts\n- Run MCP from CLI, off the public request path\n- Stdio first, SSE when I needed a network face\n\nPieces that mattered: PHP CLI, `php://stdin`\n\n/ `php://stdout`\n\n, a PHP MCP library for discovery + SSE, and the same Redis/MySQL the Bootstrap front already leans on.\n\n## How a call actually moves\n\n```\nsequenceDiagram\n  participant Agent as Agent client\n  participant MCP as PHP MCP server\n  participant Data as Redis / MySQL\n\n  Agent->>MCP: 1. tools/list\n  MCP-->>Agent: 2. tool names + schemas\n  Agent->>MCP: 3. tools/call\n  MCP->>Data: 4. read cache, then MySQL if needed\n  Data-->>MCP: 5. slim payload\n  MCP-->>Agent: 6. JSON-RPC result (stdio / SSE)\n  Note over Agent: 7. Model answers with real numbers\n```\n\nYou 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.\n\n### Analytics path (live)\n\n``` php\nflowchart TD\n  A[\"tools/call (get_website_analytics)\"] --> B[\"Validate range\"]\n  B --> C{\"Redis GET (analytics summary)\"}\n  C -->|hit| D[\"Slim JSON\"]\n  C -->|miss| E[\"MySQL aggregate\"]\n  E --> F[\"Redis SETEX (short TTL)\"]\n  F --> D\n  D --> G[\"JSON-RPC result (back to agent)\"]\n\n  classDef box fill:#f7f5f0,stroke:#2a2a2a,color:#2a2a2a,stroke-width:1px\n  classDef decision fill:#eef6f4,stroke:#2a2a2a,color:#2a2a2a,stroke-width:1px\n  class A,B,D,E,F,G box\n  class C decision\n```\n\nFake numbers in the samples below. Real payloads stay small on purpose: totals and trends, not event dumps.\n\n### Same pattern later for flights (not live)\n\n``` php\nflowchart TD\n  A[\"tools/call (check_flights, planned)\"] --> B[\"Validate args\"]\n  B --> C{\"Redis GET (flight search key)\"}\n  C -->|hit| D[\"Top N options\"]\n  C -->|miss| E[\"Search / MySQL\"]\n  E --> F[\"Redis SETEX (short TTL)\"]\n  F --> D\n  D --> G[\"JSON-RPC result (back to agent)\"]\n\n  classDef box fill:#f7f5f0,stroke:#2a2a2a,color:#2a2a2a,stroke-width:1px\n  classDef decision fill:#eef6f4,stroke:#2a2a2a,color:#2a2a2a,stroke-width:1px\n  class A,B,D,E,F,G box\n  class C decision\n```\n\nIf you return raw rows, the model wastes tokens on junk columns. Map to a tiny DTO.\n\n## Wire samples (analytics)\n\nShapes only. Not production data.\n\nKeep it under a few KB. Big payloads torch the [context window](/glossary/context-window) and the model gets worse, not better.\n\n## Schema first, then code\n\nVague schemas = invented arguments. Write the contract before the `if`\n\n.\n\nHotels, holidays, draft reply: same idea. Send mail only after a human yes.\n\n## Approach 1: stdio loop\n\nThis is how I started. No port. No nginx. Just a process the agent owns.\n\nAnalytics handler, cache first:\n\nThing that wasted an evening: PHP buffers stdout. Client hangs after `tools/list`\n\n. Flush every line (`fflush(STDOUT)`\n\n), or turn buffering off. You will do this once and never forget.\n\n## Approach 2: SSE when stdio gets small\n\nI moved to SSE when I wanted a LAN endpoint and tools as files, not a giant switch.\n\nBind to localhost. Proxy only what you need. Do not put this on the public Bootstrap site with no auth.\n\nProxies often drop idle SSE connections. Heartbeats fix most of that.\n\n## Hook it to Cursor / Claude\n\nWhen later tools ship (flights and friends), swap the tool name. The habit stays.\n\nHere 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.\n\n## What almost went wrong\n\nI almost did all of these, so I am writing them down:\n\n- Ship\n`run_any_sql`\n\n“just for managers.” Feels powerful. Becomes a liability. - Return full analytics rows. Model drowns. You leak noise.\n- Infinite Redis TTL. Cached “live” numbers that are three days dead.\n- Node sidecar “for AI only.” Two codebases fighting over one MySQL.\n- Public MCP URL on day one. No. Localhost until auth is real.\n\nRules I kept: one verb per tool, small outputs, read before write, approvals for side effects, no secrets in results.\n\n| Tool | Status | Notes |\n|---|---|---|\n`get_website_analytics` |\nLive | Read-only summary |\n`check_flights` |\nPlanned | No book-by-default |\n`check_hotels` |\nPlanned | Cap results |\n`lookup_holiday` |\nPlanned | Public fields only |\n`draft_ticket_reply` |\nPlanned | Draft first |\n`send_ticket_reply` |\nPlanned | Human approval + audit |\n\n## Future add-ons (examples, not live)\n\n**Analytics only today.** The diagram below is where desk tools go once I add them.\n\n### Flights (planned)\n\n### Hotels (planned)\n\n### Holidays (planned)\n\nFetched beats [RAG](/glossary/rag)-only when packages change weekly.\n\n### Ticket / questionnaire email (planned)\n\nFlow I want:\n\n``` php\nflowchart TD\n  A[\"get_ticket_context\"] --> B[\"draft_ticket_reply\"]\n  B --> C{\"Human approves?\"}\n  C -->|yes| D[\"send_ticket_reply\"]\n  C -->|no| E[\"Edit draft or stop\"]\n  D --> F[\"Audit log\"]\n\n  classDef box fill:#f7f5f0,stroke:#2a2a2a,color:#2a2a2a,stroke-width:1px\n  classDef decision fill:#eef6f4,stroke:#2a2a2a,color:#2a2a2a,stroke-width:1px\n  class A,B,D,E,F box\n  class C decision\n```\n\n## Architecture in one sketch\n\n```\nflowchart TB\n  A[\"Agent (Cursor / Claude / chat)\"]\n  B[\"PHP MCP server\"]\n  B1[\"Analytics tools (live)\"]\n  B2[\"Desk tools (planned)\"]\n  D[\"Redis + MySQL\"]\n  E[\"HTML / Bootstrap / JS website\"]\n  F[\"Custom PHP speed scripts\"]\n\n  A <-->|\"stdio / SSE (request + response)\"| B\n  B --- B1\n  B --- B2\n  B1 <-->|\"get / set\"| D\n  B2 <-.->|\"later\"| D\n  E <--> D\n  F <-->|\"warm cache / speed path\"| D\n\n  classDef box fill:#f7f5f0,stroke:#2a2a2a,color:#2a2a2a,stroke-width:1px\n  class A,B,B1,B2,D,E,F box\n```\n\nI 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.\n\n## Checklist I still use\n\n**Ship analytics**\n\n- Stdio loop + flush\n-\n`initialize`\n\n/`tools/list`\n\n/`tools/call`\n\n- One analytics tool with a real schema\n-\n`.cursor/mcp.json`\n\nand a real call\n\n**Harden**\n\n- Redis where the site already caches\n- Cap payloads; validate args\n- Localhost bind; deliberate proxy\n- No secrets, no raw dumps\n\n**Next tools**\n\n-\n`check_flights`\n\n-\n`check_hotels`\n\n-\n`lookup_holiday`\n\n-\n`draft_ticket_reply`\n\n→`send_ticket_reply`\n\n## Closing\n\nFayyaz Travels did not need a prettier chatbot.\n\nThey 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.\n\nMCP is just plumbing. PHP was already there. Analytics proved the integration works.\n\nIf your stack looks like theirs, skip a second runtime. Ship one read-only tool, flush stdout, then grow.\n\n- Client:\n[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:\n[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)\n\nPinch or double-tap to zoom · tap outside to close\n\nFAQ\n\n## Frequently asked questions\n\n## What does the Fayyaz Travels MCP do today?\n\nWebsite 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.\n\n## What is MCP?\n\nModel 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.\n\n## Why build MCP in PHP for Fayyaz Travels?\n\nTheir 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.\n\n## Stdio vs SSE: which should I start with?\n\nStdio for a local proof. SSE/HTTP when you need a network endpoint or attribute-based tool discovery.\n\n## What is next on the roadmap?\n\nFlight 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.\n\n## What should you never expose through MCP?\n\nBlind write access, secrets, payment credentials, or mailers without a human yes. Start read-only.\n\n### Research this topic further\n\n- Click a tool below (ChatGPT, Perplexity, Claude, or Gemini).\n- We copy the\n**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\n`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.\n\n### Related posts\n\n### Comments\n\nShare a thought on this post — keep it useful and kind. Comments are moderated before they appear.\n\nLoading comments…", "url": "https://wpnews.pro/news/how-i-built-an-mcp-server-in-php-for-fayyaz-travels", "canonical_source": "https://manish.sh/writings/tips/how-i-built-mcp-in-php-for-fayyaz-travels/", "published_at": "2026-07-19 12:00:00+00:00", "updated_at": "2026-07-22 21:52:14.568405+00:00", "lang": "en", "topics": ["ai-agents", "ai-tools", "ai-infrastructure", "developer-tools"], "entities": ["Manish Shahi", "Fayyaz Travels", "Inncelerator", "PHP", "MCP", "MySQL", "Redis", "Claude"], "alternates": {"html": "https://wpnews.pro/news/how-i-built-an-mcp-server-in-php-for-fayyaz-travels", "markdown": "https://wpnews.pro/news/how-i-built-an-mcp-server-in-php-for-fayyaz-travels.md", "text": "https://wpnews.pro/news/how-i-built-an-mcp-server-in-php-for-fayyaz-travels.txt", "jsonld": "https://wpnews.pro/news/how-i-built-an-mcp-server-in-php-for-fayyaz-travels.jsonld"}}