Documentation

ApolloTech MCP tools

ApolloTech MCP is a remote Model Context Protocol server. It gives an AI assistant four tools that take a user from a ZIP code to a filed insurance application. Each tool's description tells the model what to pass next, so most agents follow the flow without extra prompting.

Applications submitted through ApolloTech MCP are completed by a licensed agent. A filed application is not an active policy, and the agent should never tell the user they are enrolled.

Connect

You receive an endpoint URL and an API key when your access request is approved. The server speaks Streamable HTTP at /mcp (recommended) and legacy SSE at /sse.

Authentication

Send your key in a header on every request. Either form works:

x-api-key: <your-key>
Authorization: Bearer <your-key>

Keys in the query string are rejected. Each key identifies one integration and has its own rate limit.

Claude Code

Add the server to a project's .mcp.json, with the key read from an environment variable:

{
  "mcpServers": {
    "ApolloTech": {
      "type": "http",
      "url": "https://<your-endpoint>/mcp",
      "headers": { "x-api-key": "${ApolloTech_MCP_KEY}" }
    }
  }
}

Claude custom connector

An organization owner adds a custom connector with the URL https://<your-endpoint>/mcp, sets authentication to No sign-in, and adds a request header x-api-key with your key.

Any MCP client (TypeScript SDK)

import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";

const client = new Client({ name: "my-agent", version: "1.0.0" });
await client.connect(new StreamableHTTPClientTransport(
  new URL("https://<your-endpoint>/mcp"),
  { requestInit: { headers: { "x-api-key": process.env.ApolloTech_MCP_KEY } } }
));
const { tools } = await client.listTools(); // pass these to your model

Call order

  1. resolve_zip_counties: find the county for the user's ZIP.
  2. quote_carriers: get plans and prices for the household.
  3. get_eligibility_questionnaires: ask the carrier's questions, when the chosen carrier requires them.
  4. submit_enrollment: file the application, only after the user explicitly agrees.

Later tools refer to quoted plans by {carrier, planId}, not by copying plan details. Pass the same applicants, location, household and effective date you quoted with, so the server can find the exact plans it quoted.

resolve_zip_counties

Read

Turns a 5-digit ZIP code into the county rows carriers price on. Call it first. Most ZIPs return one county. A ZIP that straddles a county line returns several, and the user must choose, because premiums differ by county.

Input

FieldTypeNotes
zipCodestring, required5 digits, for example "64063"

Output

{
  "counties": [{ "fipsCode": "29095", "zipCode": "64063", "countyName": "Jackson County", "state": "MO" }],
  "hasMultipleCounties": false,
  "correlationId": "…"
}
On every later call, send the chosen row as location: { fipsCode, zipCode, state, county }, where county is the row's countyName.

quote_carriers

Read

Requests quotes from every carrier you're granted, all at once, and returns compact plan summaries. If some carriers fail, the others still come back, with the failures listed in errors. A partial result is normal, not a failure.

Input

FieldTypeNotes
primaryobject, requiredgender (Male/Female), dateOfBirth (YYYY-MM-DD), optional usesTobacco
locationobject, requiredFrom resolve_zip_counties. Street address is optional and ignored when quoting.
spouse, dependentsobject / arraySame shape as primary. Up to 20 dependents.
householdobject{ income, size }. Only subsidy-aware carriers use it.
effectiveDatestringCoverage start, YYYY-MM-DD. Only some carriers honor it.
carriersarrayNarrow to specific carriers. Omit for all you're granted.
categoriesarraySHORT_TERM, DENTAL, VISION, ANCILLARY, HEALTH_SHARE, GAP, BOOST, OTHER
maxPlansPerCarrierintegerReturn only the cheapest N plans per carrier.
subsidyEligible, childOnlyPlansbooleanOptional flags. Send the same values on later calls.

Output

{
  "carriers": [{
    "carrier": "ApolloTech_DENTAL",
    "carrierDisplayName": "ApolloTech Dental Choice",
    "questionsRequired": true,
    "plans": [{
      "planId": "…", "planName": "Premium Plus", "category": "Dental",
      "premium": { "value": 42.1, "label": "Monthly" },
      "monthlyFees": 2, "oneTimeFees": 20,
      "deductible": { "value": 50, "label": "Annual Deductible" },
      "benefits": [{ "name": "Annual maximum", "details": "…" }],
      "brochureLink": "…"
    }],
    "plansOmitted": 3
  }],
  "errors": [{ "carrier": "PIVOT", "code": "TIMEOUT" }],
  "correlationId": "…"
}
  • questionsRequired: true means you must call get_eligibility_questionnaires before applying for that carrier's plans.
  • Carrier errors TIMEOUT, CIRCUIT_OPEN and UNAVAILABLE are worth retrying; CARRIER_ERROR is not.
  • Naming a carrier or category you aren't granted fails the whole request with a 403. Retry without that filter.

get_eligibility_questionnaires

Read

Fetches each carrier's eligibility questions for the plans the user picked. Carriers without questions (for example UHC and OneShare) are simply absent from the result.

Input

FieldTypeNotes
selectedPlansarray, required[{ carrier, planId }], exactly as returned by quote_carriers
primary, spouse, dependents, household, location, effectiveDate—The same values used for the quote

Output

{
  "carrierQuestionnaires": [{
    "carrier": "ApolloTech_DENTAL",
    "questionsId": "…",
    "categories": [{
      "categoryId": 1, "categoryName": "Eligibility", "categoryType": "eligibility",
      "questions": [{
        "questionId": "q1",
        "questionText": "Are you currently hospitalized…?",
        "questionTextPlain": "Are you currently hospitalized…?",
        "possibleAnswers": [{ "answerId": "a1", "answerText": "Yes" }, { "answerId": "a2", "answerText": "No" }],
        "expectedAnswer": "No"
      }]
    }]
  }],
  "errors": [],
  "correlationId": "…"
}
  • A question with expectedAnswer is a knockout. Any other answer means the carrier will decline, so don't apply for that carrier's plans.
  • Show the user questionTextPlain when present (questionText can contain HTML).
  • Send questionText and answerText back verbatim in submit_enrollment's answers, together with questionsId.

submit_enrollment

Write

Files the application, with the applicants, contact details, chosen plans and questionnaire answers, for a licensed agent to complete. Only call it after the user has explicitly agreed to submit.

Input

FieldTypeNotes
explicitUserConsentboolean, requiredMust be true
consentStatementstring, requiredThe user's own message consenting, quoted verbatim. Stored as the consent audit trail.
applicantsobject, required{ primary, spouse?, dependents? }. First and last name are required on every applicant.
contactInfoobject, required{ email, phone? }
locationobject, requiredSame as the quote. Include addressLine and city so the agent has a mailing address.
effectiveDatestring, requiredSame as the quote
selectedPlansarray, required[{ carrier, planId }] from quote_carriers
householdobjectSame as the quote
answersobjectQuestionnaire answers, where the carrier has a questionnaire

Output

{
  "submissionId": "0bbd9186-…",
  "status": "SYNCED",
  "receivedAt": "2026-10-09T15:43:25Z",
  "filed": true,
  "filedVia": "api",
  "idempotentReplay": false,
  "correlationId": "…",
  "nextStep": "You will receive a confirmation email once the enrollment is complete, and a licensed agent might reach out to you."
}
statusMeaningWhat the agent should do
SYNCEDFiled with the enrollment workflowGive the user the submissionId and relay nextStep verbatim
SKIPPEDFiled (delivery disabled in this environment)Same as SYNCED
BACKUP_FILEDThe primary handoff was unavailable, so the application went straight to a licensed agentSame as SYNCED; don't resubmit
RECEIVED, SYNC_FAILEDNot filed yet (filed: false)Call again with identical arguments
  • Idempotent. The server derives the idempotency key from the application itself. An identical retry resumes the same submission and never files twice; idempotentReplay: true means you got the original receipt back.
  • Decide success from filed / status, not from whether the call returned.
  • Never tell the user they are enrolled. Give them the submissionId as their reference and relay nextStep word for word.
  • PLAN_NOT_FOUND means the plan is no longer offered for these details. Quote again.

Errors

A failed tool call returns isError: true with a JSON body your agent can branch on:

{
  "error": "UPSTREAM_UNAVAILABLE",
  "status": 503,
  "message": "…",
  "fields": null,
  "correlationId": "7ac45b90-…",
  "retryAfterSeconds": null,
  "retryable": true
}
CodeRetry?Meaning
VALIDATION_FAILED (400)NoFix the inputs listed in fields
CARRIER_NOT_ALLOWED / CATEGORY_NOT_ALLOWED (403)NoA carrier or category you aren't granted. Nothing was called or recorded.
NOT_FOUND (404)NoFor example, no counties for that ZIP
RATE_LIMITED (429)YesWait retryAfterSeconds
UPSTREAM_ERROR (502)NoThe upstream service returned an error
UPSTREAM_UNAVAILABLE (503)YesTemporarily unavailable; retry with identical arguments
UNAUTHORIZED (401)NoMissing or invalid API key

Two tool-level errors come back as text instead, with the code at the start of the message: PLAN_NOT_FOUND (quote again and let the user re-pick) and ALREADY_SUBMITTED (this exact application was already filed; don't resubmit).

Include the correlationId when contacting support. It identifies the exact request.

Limits

  • 120 requests per minute per API key by default. Need more? Mention it in your access request.
  • Quotes are kept on the server for 30 minutes. After that, later tools re-quote automatically, or return PLAN_NOT_FOUND if the plan is gone.
  • Up to 20 dependents per household and 50 selected plans per application.

Request access