44 documents
Architecture Decision Records (ADRs)Status: Accepted📅2026-07-30⏱️3 min read

ADR-0003: Capture leads through an MCP tool and an OpenAPI action, not a web form alone

If the thesis is that buyers increasingly research inside an LLM (ADR-0001), then a conventional contact form is the wrong last mile: it requires the user to leave the chat, find t...

ADR-0003: Capture leads through an MCP tool and an OpenAPI action, not a web form alone

  • Status: Accepted
  • Date: 2026-07-30 (recorded; implemented earlier)
  • Deciders: Junaid Siddiqui (owner)
  • Related: ../integrations/mcp-server.md, ../integrations/lead-capture.md, ADR-0001, ADR-0004

Context

If the thesis is that buyers increasingly research inside an LLM (ADR-0001), then a conventional contact form is the wrong last mile: it requires the user to leave the chat, find the site, and retype what they already told the assistant. Every step loses people.

Options considered:

  1. Web form only — simplest, but invisible to agents and unusable mid-conversation.
  2. MCP tool only — native for MCP-capable hosts, but excludes ChatGPT Custom GPTs and Actions-style integrations that speak OpenAPI.
  3. OpenAPI action only — broad host coverage, but not the native path for MCP clients.
  4. Both agent surfaces, plus keep the web form for humans arriving via the site.

Option 4 costs little extra because validation and delivery can be shared.

Decision

Expose lead capture on three surfaces, with the two agent-facing ones sharing a single implementation:

Surface Path Recorded source
MCP tool request_dev_consultation /api/mcp/sse, /api/mcp/mcp "mcp"
OpenAPI action submitDevConsultationLead POST /api/leads/submit "openapi"
On-site contact form POST /api/contact — (separate payload and path)

Both agent surfaces converge on submitConsultationLead() in lib/leads.ts, sharing one Zod schema, one error taxonomy, and one delivery path. The MCP endpoint runs stateless Streamable HTTP (sessionIdGenerator: undefined) so it works on serverless and multi-instance deployments with no session store.

Both surfaces are public, unauthenticated, and CORS-open, because the point is that arbitrary AI hosts can reach them.

Consequences

  • Positive:
    • Covers MCP-native clients and OpenAPI/Actions hosts with one validation contract, so the two cannot drift apart.
    • Statelessness removes Redis and any session infrastructure.
    • Structured error responses (validation_error with per-field issues) let an agent re-prompt the user instead of failing opaquely.
    • source on every lead shows which channel actually produces business.
    • Doubles as the live demo (ADR-0007) and as proof-of-capability for prospects.
  • Trade-offs / costs:
    • Public, unauthenticated, no rate limiting — the endpoint can be flooded, writing junk into the destination.
    • Two specs to maintain: the MCP tool schema in lib/mcp-server.ts and lib/openapi-spec.ts (served with CORS at /docs/openapi.json and /api/openapi.json). A field change means editing both.
    • Per-request server construction adds a little latency.
    • Stateless mode forecloses features needing cross-call state (progress notifications, subscriptions).
    • The success message promises human follow-up within one hour, made unattended by an agent — an operational commitment with no notification behind it (../operations/client-engagement-sop.md).
  • Follow-ups:
    • Add rate limiting or abuse protection to both public endpoints.
    • Add notification on lead arrival to make the 1-hour promise credible.
    • Tier 3 sells "multi-endpoint MCP tools"; exactly one tool exists. Build or reword (../sales/pricing-tiers.md).