MCP server — the Agentic Action Layer
Verified against this repository on 2026-07-30. Variable **names** only — never values.
MCP server — the Agentic Action Layer
Verified against this repository on 2026-07-30. Variable names only — never values.
This is the action layer that ../strategy/positioning.md calls the core differentiator, the MCP half of Tier 2 in ../sales/pricing-tiers.md, and Step 2 of the live demo (../sales/demo-framework.md). It is live on JSGlobtech's own site, which is what makes the demo real.
Decision record: ../decisions/0003-lead-capture-via-mcp-and-openapi.md.
Endpoint
| Item | Detail |
|---|---|
| Route file | app/api/mcp/[transport]/route.ts |
| Handler | handleMcpHttpRequest in lib/mcp-server.ts |
Accepted transport values |
sse, mcp — anything else returns 404 not_found with a message naming the valid paths |
| Canonical agent URL | https://jsglobtech.com/api/mcp/sse |
| Alternate path | https://jsglobtech.com/api/mcp/mcp |
| Methods | GET, POST, DELETE, OPTIONS |
| Runtime | nodejs, maxDuration = 60 |
| SDK | @modelcontextprotocol/sdk ^1.29.0 |
| Transport class | WebStandardStreamableHTTPServerTransport |
| Session mode | Stateless — sessionIdGenerator: undefined, enableJsonResponse: true |
Despite the sse path name, both paths run the same Streamable HTTP handler. sse is kept because it is the URL commonly advertised to and expected by MCP clients; there is no separate legacy SSE implementation.
Statelessness
A fresh McpServer and transport are constructed per request, connected, used, then closed in a finally block. No Redis, no session store, no shared memory. This is deliberate: it works on serverless and multi-instance deployments where consecutive requests may land on different instances. Modern MCP clients POST initialize to the advertised URL and do not require session continuity.
Trade-off: per-request construction costs a little latency, and any future feature needing cross-call state (progress notifications, long-running subscriptions) would require revisiting this.
CORS
Set in lib/mcp-server.ts and applied to every response via withCors:
Access-Control-Allow-Origin: *Access-Control-Allow-Methods: GET, POST, DELETE, OPTIONSAccess-Control-Allow-Headers: Content-Type, Accept, Authorization, Mcp-Session-Id, Last-Event-IDAccess-Control-Expose-Headers: Mcp-Session-Id
OPTIONS short-circuits to 204 before any server is constructed. The endpoint is intentionally unauthenticated and public — it exists so arbitrary AI hosts can reach it.
Server identity
createJsGlobtechMcpServer() registers an McpServer named jsglobtech-mcp, version 1.0.0.
Tool: request_dev_consultation
The only registered tool. Title: "Request JSGlobtech Dev Consultation".
Its description is written for model consumption and instructs the agent to collect client_name and client_email_or_phone before calling, and to prefer WhatsApp-ready UAE numbers. That prompt engineering is part of the product — it is what makes hosts behave well.
Input schema (Zod)
| Field | Type | Required | Notes |
|---|---|---|---|
client_id |
string | No | Tenant key for webhook routing; defaults to default. Demo dogfood: jsglobtech. |
client_name |
string, min 2 | Yes | Full name of the client or decision-maker |
client_email_or_phone |
string, min 5 | Yes | Email or phone/WhatsApp; +971 encouraged for UAE |
project_type |
enum | Yes | flutter_mobile_app | web_application | geo_ai_integration |
budget_range_usd |
positive number or non-empty string | Yes | e.g. 15000, "10000-25000", "Not sure yet" |
requirements_notes |
string | No | Goals, platforms, timeline, compliance, integrations |
Result
On success the tool returns both content (pretty-printed JSON text) and structuredContent:
{ "ok": true, "lead_id": "jsg-<uuid>", "client_id": "…", "message": "…", "project_type": "…", "received_at": "<ISO-8601>" }
Error contract
Errors return isError: true with a JSON body rather than throwing, so the host can relay something useful to the user:
| Cause | error |
Behaviour |
|---|---|---|
LeadValidationError |
validation_error |
Includes per-issue path and message so the agent can re-prompt for the missing field |
LeadWebhookError |
webhook_error |
Tells the user the lead was validated but delivery failed, and offers the fallback email |
| Anything else | internal_error |
Logged server-side as [mcp] request_dev_consultation failed |
OpenAPI mirror (ChatGPT Actions / Claude webhooks)
| Item | Detail |
|---|---|
| Spec | lib/openapi-spec.ts, served at /docs/openapi.json and /api/openapi.json (CORS Access-Control-Allow-Origin: *) |
| OpenAPI version | 3.0.3 |
| Title | JSGlobtech Lead Capture API, version 1.0.0 |
| Server | https://jsglobtech.com (Production) |
| Path | POST /api/leads/submit — the only path |
operationId |
submitDevConsultationLead |
| Documented responses | 200, 400, 500, 502 |
| Component schemas | ProjectType, ConsultationLeadRequest, ConsultationLeadSuccess, LeadError |
The spec's info.description also carries agent instructions ("ask the user for any missing required fields. Do not invent contact details."), mirroring the MCP tool description.
Both surfaces converge on submitConsultationLead() in lib/leads.ts, differing only in the recorded source ("mcp" vs "openapi"). See ../integrations/lead-capture.md.
Discoverability
An agent can find all of this without documentation, which is the point of the GEO layer:
public/.well-known/llms.txt/public/llms.txtlist the MCP endpoints, the OpenAPI URL, the REST endpoint, and the tool's full field contract.components/GeoSchema.tsxpublishes the MCP endpoint and OpenAPI definition asadditionalPropertyvalues onProfessionalService, and a#software-mcpSoftwareApplicationnode whoseurlis the SSE endpoint anddocumentationis the OpenAPI URL.
Playbook claims not implemented here
The playbook attributes more to this layer than the code delivers. Recorded so nobody sells the gap:
| Claim | Source | Reality |
|---|---|---|
| "book consultations" | §1 | Captures a lead. No calendar or scheduling integration. |
| "process reservations directly inside the chat" | §1 | No booking, reservation, or payment capability. |
| "Multi-endpoint MCP tools" | §3 Tier 3 | Exactly one tool; OpenAPI exposes exactly one path. |
| "Live Inventory/Property sync" | §3 Tier 3 | No database, ORM, or external data source in the repo. |
| "24/7 Hosting & Monitoring" | §3 Tier 2 | No monitoring, alerting, or uptime tooling present. |
| "Confirm / Submit button appears in the chat" | §6 Step 2 | Host-client UI, not JSGlobtech code. The tool returns JSON; approval UI varies by client. |
Tier 3 items are buildable — they are ordinary integration work — but they are unbuilt, so quote them as engineering rather than as retainer inclusions.
Not used
- No auth, API key, or rate limiting on the MCP endpoint (REST
/api/leads/submitdoes rate-limit; MCP does not). X-API-Keyon REST is treated as aclient_id, not real authentication — see../operations/production-security-audit.md.- No Redis or session store (stateless by design).
- No MCP resources or prompts registered — tools only.
Production security implications of the public MCP surface: ../operations/production-security-audit.md (findings #4, #5, #6 and related).