Official Primitive Node.js, Python, and Go SDKs for webhook handling, API access, email operations, and agent functions. Includes CLI (primitive) for deploying functions, sending mail, managing domains, and webhook operations.
This MCP server exposes 13 tools from the Primitive email/payment API via auto-generated Go bindings (oas_server_gen.go). All tool definitions appear to be inferred from OpenAPI schema generation rather than explicitly hand-crafted. While descriptions are present and moderately detailed (ranging 150 - 400 chars), critical deficiencies emerge: (1) No visible input parameter schemas in the provided source, the Makefile and Go files show code generation but do NOT display the actual JSON Schema definitions that would be marshaled to MCP clients. (2) Parameter documentation is embedded in natural language descriptions rather than in structured schema with explicit type definitions, enums, and constraints. (3) Tool names do NOT uniformly follow verb_noun convention: 'AddDomain', 'AwaitReply', 'CheckDomainDns', 'CancelSentEmail', and 'CliLogout' use PascalCase, whereas MCP convention expects snake_case verbs. (4) No evidence of output schemas, error handling guidance, or idempotency markers. (5) Because tool definitions are auto-generated from OpenAPI, they inherit the API's design rather than being optimized for LLM interaction (e.g., AwaitReply has 'wait' and 'wait_timeout_ms' as separate parameters with implicit coupling, rather than cleanly composed operations). The server DOES provide reasonable descriptions and appears functionally complete, but lacks the structural clarity and LLM-optimized parameter design that would earn a higher score.
Creates an unverified domain claim and returns the exact DNS records to publish in `dns_records`. Publish those records before calling the verify endpoint. To give users an importable DNS file, call `downloadDomainZoneFile` or run `primitive domains zone-file --id <domain-id>`.
Returns the first threaded inbound reply to a send, keyed by the inbound email's `reply_to_sent_email_id`. This is the canonical "did a reply arrive for this send?" call: after /send-mail, poll (or long-poll) here instead of hand-rolling an /emails/search loop. By default the call returns immediately with `reply: null` when nothing has arrived yet. Set `wait=true` to long-poll: the server holds the request up to `wait_timeout_ms` (default 10000, max 30000), returning as soon as the first reply lands. On a wait that elapses with no reply, the response has `reply: null` and `timed_out: true`. The reply object is compact (headers plus bodies); fetch `/emails/{id}` with `reply.id` for the fully parsed record, or `/threads/{thread_id}` for the whole back-and-forth.
Cancels a STILL-SCHEDULED send (status `scheduled`), moving it to the terminal `canceled` status. Nothing is dispatched and the row is kept for historical lookup with `canceled_at` set. Uses the same compare-and-swap guard as reschedule: once the scheduler has claimed the row for execution, or it was already canceled or executed, the call returns a 409 `not_scheduled` conflict naming the row's current status. Canceling can therefore never race an in-progress execution; a send that reports `canceled` was never handed to the delivery path. Returns the full updated sent-email record on success.
Re-checks the domain's DNS records and persists the result as the domain's current DNS health state. This is the on-demand counterpart of the scheduled background checker: the response mirrors what the checker records, broken down per scope (`ownership`, `inbound`, `outbound`) with the exact records inspected and each record's individual status. Unlike /domains/{id}/verify, this call never promotes an unverified domain; it only re-evaluates and records health for an existing claim. Managed (Primitive-operated) domains are rejected with a validation error because their DNS is not customer-published. Rate limited per organization; a `Retry-After` header accompanies 429 responses.
No visible input parameter schemas in source code. All 13 tools show 0 score for schema dimension because the provided files (Makefile, Go bindings, TypeScript smoke tests) do not expose the JSON Schema definitions that MCP would serialize. Code generation produces bindings but schemas are not displayed, making it impossible to verify type definitions, enums, constraints, or defaults.
Tool names use PascalCase instead of snake_case verb convention (AddDomain, AwaitReply, CancelSentEmail, CliLogout, CheckDomainDns, etc.). MCP tooling and LLM prompts expect lowercase action_noun patterns. This deviates from the 90% baseline of A+ tools that start with lowercase verbs.
| Scored | Grade | Overall | Spec posture | Rubric |
|---|---|---|---|---|
| 2026-09-23 | F | 32 | 2026-07-28+ | v2 |
Revokes the OAuth grant used to authenticate the request. API-key authenticated legacy logout requests succeed without deleting server API keys so old local CLI state can be cleared safely.
Report normalized handling evidence for the queue, attempt and lease. Required JSON fields: queue_id, delivery_id, lease_token, mode, duration_ms. For mode=exec include exit_code; stdout includes write_succeeded; http includes status_code and optional transport_error, error_code, confirmed. Supply this discriminated request with --raw-body in the generated CLI. The server classifies success and retry. Duplicate identical completion is idempotent; a stale lease cannot finish a newer attempt. Exec success means durable input acceptance, not completion of agent reasoning. Only successful HTTP handling with recognized confirmation evidence can confirm content discard. Never send arbitrary handler output or HTTP response bodies.
Creates an emailless agent account without authentication and returns a one-time API key (prefixed `prim_`) plus a provisioned managed inbox. The account is on the `agent` plan: reply-only (it can send only to addresses that have already sent it authenticated mail) with tight send limits. Use the returned `api_key` as a Bearer token on later calls. The account can be upgraded to a full developer account by confirming an email through the claim flow. This endpoint does not require an API key.
Mints an opaque, single-use link an agent can hand to a human to complete the email-confirmation upgrade in a browser. Authenticated by the agent's own API key. `claim_url` is null when the API host cannot resolve a web origin to build the link.
Create an x402 payment challenge (the payee side of a payment). The `pay_to` address is resolved server-side from your registered default payout address for the network, never from the request. The response carries the `nonce_binding` and `payment_requirements` the payer needs to sign; hand the whole challenge object to the payer (for example in an email reply). Amounts are in token base units (USDC has 6 decimals, so `"10000"` is 0.01 USDC).
Issue an x402 payment challenge over a real email thread (the payee side). Unlike `createChallenge` (which mints a synthetic challenge id), this sends the challenge as an email from `from` to `to` and binds the payment to that DKIM-authenticated thread. The `pay_to` address and the token asset are resolved server-side from your registered default payout address for the network, never from the request. The response carries the thread's `interaction_id` plus the `challenge` (the `payment_requirements`, the `nonce_binding`, and `expires_at`) the payer needs to sign; the payer replies with a signed `payment` interaction step. Amounts are in token base units (USDC has 6 decimals, so `"10000"` is 0.01 USDC).
Creates a new webhook endpoint. If a deactivated endpoint with the same URL and domain exists, it is reactivated instead. Subject to plan limits on the number of active endpoints. **Signing is account-scoped, not per-endpoint.** This call does not return any signing material; every endpoint on the account uses the same webhook secret, fetched via `GET /account/webhook-secret`. See the API-level "Webhook signing" section for the full wire format (header name, signed string, hash algo, secret format, tolerance) and a language-agnostic verification recipe. After creating the endpoint, fire a test delivery against it via `POST /endpoints/{id}/test` to confirm your verifier accepts the signature. For local receiving, use kind=pull and a stable name, without url, function_id or domain_id. Named creation resumes the same active destination; omitted filters preserve its selection, while conflicting supplied configuration returns 409. New unfiltered destinations receive all subsequently ready eligible event types. Pull destinations do not occupy HTTP routing slots. Signing-secret setup and a test HTTP delivery are not required for pull receiving.
Creates a new whitelist or blocklist filter. Per-domain filters require a Pro plan. Patterns are stored as lowercase.
Creates and deploys a new function. The handler must be a single ESM module whose default export is an object with an async `fetch(request, env)` method (Workers-style). Primitive signs each delivery and forwards the `Primitive-Signature` header to the handler. Verify the raw request body with `PRIMITIVE_WEBHOOK_SECRET` before parsing JSON; after verification the request body parses to a webhook event whose `event` field is `email.received` for normal inbound mail, or a machine-mail type (`email.bounced`, `email.tls_report`, `email.dmarc_report`, `email.dmarc_failure`) for bounces and reports.
Tool descriptions embed parameter constraints in natural language (e.g., AwaitReply mentions 'wait=true' and 'wait_timeout_ms' inline) rather than declaring them in structured schema with type, range, enum, or format. LLMs cannot parse these from descriptions, they rely on JSON Schema properties.
No error handling guidance in tool descriptions. Tools like CancelSentEmail and CheckDomainDns mention error states (409 conflict, rate limiting) but do not indicate whether errors are retryable, how to recover, or what the LLM should do next. This violates the recovery-guide pattern.
No idempotency or confirmation pattern for destructive operations. CreateFunction and CreateEndpoint have write/deployment side effects, and CancelSentEmail and CliLogout are irreversible, but descriptions do not indicate idempotency or suggest a dry-run / confirmation step.
CreateAgentAccount description states 'This endpoint does not require an API key', exposing authentication logic in the tool description. Credential handling should be opaque to the MCP client.
Output schemas are not documented. Tools like CreateAgentAccount, CreateChallenge, and CreateEndpoint return complex objects (agent account with api_key, challenge with nonce_binding and payment_requirements, endpoint with signing details), but no descriptions explain what fields are in the response or what an LLM should expect to extract.
Tool composition assumes knowledge of internal API structure. CompleteWebhookEvent requires 'queue_id', 'delivery_id', 'lease_token', and discriminated fields (exit_code, status_code, etc.) but does not explain where these come from or how they chain from prior calls.