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.
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
resolve_zip_counties: find the county for the user's ZIP.quote_carriers: get plans and prices for the household.get_eligibility_questionnaires: ask the carrier's questions, when the chosen carrier requires them.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
ReadTurns 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
| Field | Type | Notes |
|---|---|---|
zipCode | string, required | 5 digits, for example "64063" |
Output
{
"counties": [{ "fipsCode": "29095", "zipCode": "64063", "countyName": "Jackson County", "state": "MO" }],
"hasMultipleCounties": false,
"correlationId": "…"
}
location: { fipsCode, zipCode, state, county }, where county is the row's countyName.quote_carriers
ReadRequests 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
| Field | Type | Notes |
|---|---|---|
primary | object, required | gender (Male/Female), dateOfBirth (YYYY-MM-DD), optional usesTobacco |
location | object, required | From resolve_zip_counties. Street address is optional and ignored when quoting. |
spouse, dependents | object / array | Same shape as primary. Up to 20 dependents. |
household | object | { income, size }. Only subsidy-aware carriers use it. |
effectiveDate | string | Coverage start, YYYY-MM-DD. Only some carriers honor it. |
carriers | array | Narrow to specific carriers. Omit for all you're granted. |
categories | array | SHORT_TERM, DENTAL, VISION, ANCILLARY, HEALTH_SHARE, GAP, BOOST, OTHER |
maxPlansPerCarrier | integer | Return only the cheapest N plans per carrier. |
subsidyEligible, childOnlyPlans | boolean | Optional 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: truemeans you must callget_eligibility_questionnairesbefore applying for that carrier's plans.- Carrier errors
TIMEOUT,CIRCUIT_OPENandUNAVAILABLEare worth retrying;CARRIER_ERRORis not. - Naming a carrier or category you aren't granted fails the whole request with a 403. Retry without that filter.
get_eligibility_questionnaires
ReadFetches 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
| Field | Type | Notes |
|---|---|---|
selectedPlans | array, 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
expectedAnsweris a knockout. Any other answer means the carrier will decline, so don't apply for that carrier's plans. - Show the user
questionTextPlainwhen present (questionTextcan contain HTML). - Send
questionTextandanswerTextback verbatim insubmit_enrollment'sanswers, together withquestionsId.
submit_enrollment
WriteFiles 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
| Field | Type | Notes |
|---|---|---|
explicitUserConsent | boolean, required | Must be true |
consentStatement | string, required | The user's own message consenting, quoted verbatim. Stored as the consent audit trail. |
applicants | object, required | { primary, spouse?, dependents? }. First and last name are required on every applicant. |
contactInfo | object, required | { email, phone? } |
location | object, required | Same as the quote. Include addressLine and city so the agent has a mailing address. |
effectiveDate | string, required | Same as the quote |
selectedPlans | array, required | [{ carrier, planId }] from quote_carriers |
household | object | Same as the quote |
answers | object | Questionnaire 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."
}
| status | Meaning | What the agent should do |
|---|---|---|
SYNCED | Filed with the enrollment workflow | Give the user the submissionId and relay nextStep verbatim |
SKIPPED | Filed (delivery disabled in this environment) | Same as SYNCED |
BACKUP_FILED | The primary handoff was unavailable, so the application went straight to a licensed agent | Same as SYNCED; don't resubmit |
RECEIVED, SYNC_FAILED | Not 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: truemeans 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
submissionIdas their reference and relaynextStepword for word. PLAN_NOT_FOUNDmeans 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
}
| Code | Retry? | Meaning |
|---|---|---|
VALIDATION_FAILED (400) | No | Fix the inputs listed in fields |
CARRIER_NOT_ALLOWED / CATEGORY_NOT_ALLOWED (403) | No | A carrier or category you aren't granted. Nothing was called or recorded. |
NOT_FOUND (404) | No | For example, no counties for that ZIP |
RATE_LIMITED (429) | Yes | Wait retryAfterSeconds |
UPSTREAM_ERROR (502) | No | The upstream service returned an error |
UPSTREAM_UNAVAILABLE (503) | Yes | Temporarily unavailable; retry with identical arguments |
UNAUTHORIZED (401) | No | Missing 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_FOUNDif the plan is gone. - Up to 20 dependents per household and 50 selected plans per application.