> ## Documentation Index
> Fetch the complete documentation index at: https://developer.firstsales.io/llms.txt
> Use this file to discover all available pages before exploring further.

# FirstSales MCP server

> Connect to FirstSales Product MCP and inspect its released, parity-generated tool catalog.

For public, read-only email and DNS diagnostics, use the
[FirstSales Public Diagnostics listing on Smithery](https://smithery.ai/servers/uditgoenka1/firstsales-public-mcp)
to connect to `https://firstsales.io/mcp/` without a FirstSales account.

Use this Product MCP resource URI:

```text theme={null}
https://api.app.firstsales.io/mcp
```

> [!NOTE]
>
> The synchronized release manifest has `release_ready: true`. The released
> catalog is read-only and bound to the manifest's authorization catalog epoch.
> Runtime reachability and the tools visible to one connection still depend on
> deployment attestation, tenant features, and its live OAuth Grant.

This page describes the modern `2026-07-28` contract and the isolated legacy
`2025-11-25` compatibility contract, which also accepts `2025-06-18` clients.

## Agent operating checklist

Configure an agent or MCP client in this order:

1. Confirm the endpoint is the canonical resource: `https://api.app.firstsales.io/mcp`.
2. Obtain an OAuth bearer token whose audience/resource is exactly that URI.
3. Call `server/discover` first, then treat `tools/list`, `resources/list`, and `prompts/list` as the authority for the current connection.
4. Expect only the tenant- and Grant-scoped subset of the Released-tool catalog below, not the whole parity artifact.
5. Prefer CLI or direct Developer API calls when your workflow needs a capability that is not present in discovery.

Product MCP is a release-gated projection of the OpenAPI and CLI catalogs. It does not
mirror either catalog.

## Modern endpoint

The modern runtime implements stateless Streamable HTTP. Each request or
notification is one UTF-8 JSON-RPC message in an `application/json` `POST`; batch arrays
are rejected. The client `Accept` header may list `application/json` and
`text/event-stream`, but either media type is sufficient. The response is one JSON
object or a request-scoped SSE stream that closes after its final response. Accepted
notifications return `202` without a response body. The legacy `GET` stream requires
`Accept: text/event-stream`.

There is no modern MCP session or `initialize` handshake. Every request must carry
`io.modelcontextprotocol/protocolVersion` and
`io.modelcontextprotocol/clientCapabilities` in `params._meta`; `clientInfo` is optional
and never authority. The `MCP-Protocol-Version` and `Mcp-Method` headers must agree with
the body, and targeted calls also require a matching `Mcp-Name`. Modern `GET` and
`DELETE` return `405`. CORS `OPTIONS` is handled outside both protocol codecs.

Aggregate discovery uses `server/discover`. After release attestation, clients should
use discovery to learn the admitted surface; do not assume the MCP registry is a mirror
of the OpenAPI or CLI command catalog.

## Authorization

When release-attested, the endpoint requires an OAuth access token for the canonical
resource URI, sent as a bearer token:

```http theme={null}
Authorization: Bearer <access-token>
MCP-Protocol-Version: 2026-07-28
```

Read the OAuth Protected Resource Metadata at either URL. Authorization challenges use
the root URL; the path-scoped URL describes the same MCP resource.

```text theme={null}
https://api.app.firstsales.io/.well-known/oauth-protected-resource
https://api.app.firstsales.io/.well-known/oauth-protected-resource/mcp
```

The authorization server is FirstSales at `https://app.firstsales.io`. Its public
OAuth metadata and endpoints are:

```text theme={null}
https://app.firstsales.io/.well-known/oauth-authorization-server
https://app.firstsales.io/api/auth/oauth2/register
https://app.firstsales.io/api/auth/oauth2/authorize
https://app.firstsales.io/api/auth/oauth2/token
https://app.firstsales.io/api/auth/oauth2/revoke
https://app.firstsales.io/api/auth/jwks
```

Authorization and token requests must both send
`resource=https://api.app.firstsales.io/mcp`. The authorization-code flow uses state,
PKCE `S256`, and RFC 9207 `iss`; clients validate the returned issuer before exchanging
the code.

### Register a public MCP client

Use a pre-registered client when FirstSales supplies one. Otherwise, prefer a Client
ID Metadata Document (CIMD). Use the HTTPS URL of the document as the `client_id`; the
document must repeat that exact URL, list the exact redirect URIs, and declare a public
authorization-code client with `token_endpoint_auth_method: none`,
`response_types: ["code"]`, and PKCE `S256`. FirstSales fetches and validates the
document again for each authorization request.

For clients without CIMD support, use Dynamic Client Registration (DCR) as a
compatibility fallback. Discover `registration_endpoint` from the selected FirstSales
issuer and keep the returned `client_id` bound to that issuer. DCR clients are also
public authorization-code clients and must use PKCE `S256`; they do not receive or send
a client secret. Public MCP clients cannot register or exchange tokens with the
`client_credentials` grant.

If authorization with a client metadata document fails with `invalid_client` and
`Unable to validate client metadata document.`, register with DCR instead. In Codex,
pass `--oauth-client-registration dcr` to `codex mcp login`; see
[Codex and Claude Code](/agents/codex-claude-code#connect-codex-to-product-mcp).

### Consent, authority, and token lifetime

If the user is not signed in, FirstSales preserves the signed OAuth continuation while
sending them through the normal login flow. The consent flow lets the user choose from
their currently accessible organizations and workspaces. It restores the saved server
selection on reload, records accepted OAuth tiers per organization and autonomy per
workspace, and issues authority only for the approved selection.

The Approve and Deny actions show an immediate live progress label, lock the consent
controls, and ignore duplicate submissions. If an authorization step fails, the
controls are restored and the current organization, workspace, tier, and autonomy
selection remains available for retry.

The token must satisfy the configured issuer, audience/resource, expiry, and scope and
must carry the delegated capability required by the requested tool. Dashboard session
cookies and Developer API keys are not accepted by product MCP.

OAuth tiers are `mcp:access`, `mcp:write`, `mcp:destructive`, and `mcp:financial`;
`mcp:access` is the default and is required before any optional tier. Per-workspace
autonomy modes are `observe` ("Observe only"), `draft` ("Draft changes"),
`execute_within_policy` ("Execute within policy"), and `supervised` ("Supervised");
`observe` is the default. These values bound authorization only and never release a
capability.

Access tokens expire after 10 minutes. A client receives no refresh token unless it
opts in with `offline_access` and pairs that scope with the `refresh_token` grant.
Refresh tokens rotate on use and expire within 30 days; reuse invalidates the token
family. Refresh and access tokens remain bound to the client, MCP resource, and current
tenant grant. Use the authorization server's revocation endpoint to revoke a token;
revoking an MCP connection or organization grant also makes its authority fail closed.

## Legacy compatibility

The `2025-11-25` compatibility path uses its own durable session lifecycle over
`POST`, `GET`, and `DELETE`. A legacy session begins with `initialize`. When
`params.protocolVersion` is exactly `2025-11-25` or `2025-06-18`, the server echoes that
version. For any other requested version it answers with `2025-11-25`, following the
MCP lifecycle negotiation rule, and the client decides whether to continue. Subsequent
requests must carry the issued legacy session identifier. Both legacy versions receive identical responses; the lane
emits no field newer than `2025-06-18`. Malformed or contradictory modern markers never fall
through to this codec. Keep the legacy handshake separate from the baseline for new
integrations.

## Discovery surface

* `server/discover` for aggregate modern discovery
* `tools/list`
* `resources/list`
* `resources/templates/list`
* `prompts/list`

The backend-generated `capabilities/capability-parity-report.v1.json` owns the complete
released catalog. Its `summary.mcp_eligible_count` and eligible entries under
`rows[].mcp` generate the visible Released-tool catalog below. After release
attestation, `tools/list` returns the narrower intersection allowed
by the current tenant features, OAuth Grant, and live authorization. The released
catalog is read-only; no product MCP write, destructive, financial, or approval-backed
action is released.

## Released-tool catalog

> [!NOTE]
>
> This generated catalog contains exactly 17 parity-eligible tools. A tenant sees only the subset allowed by its live OAuth Grant and feature access.

All entries currently require the OAuth tier shown below. The parity artifact does not publish a per-tool autonomy requirement, so this table does not invent one; authenticated `server/discover` or `tools/list` is authoritative for the current connection. Input and output JSON Schemas are returned by those discovery methods.

| Tool and purpose | Capability and family | Authority | Primary inputs | REST and Operation behavior |
| - | - | - | - | - |
| **`activities_list`** List CRM activities | `activity.list.inspect` crm-activity-read-workflow-lifecycle | `mcp:access` Tenant Grant required | `org`, `workspace` | `listActivities` `/api/v1/organizations/{orgId}/workspaces/{wsId}/activities` query.limit: 1–100 (default 20); response.activities: at most 100 items |
| **`billing_credit_balance_get`** Inspect billing credit balance | `billing.credit.balance` billing-credits-and-usage | `mcp:access` Tenant Grant required | `org` | `getBillingCreditBalance` `/api/v1/organizations/{orgId}/billing/credits` Synchronous bounded read |
| **`billing_credit_history_list`** Inspect billing credit history | `billing.credit.history.inspect` billing-credits-and-usage | `mcp:access` Tenant Grant required | `org` | `listBillingCreditHistory` `/api/v1/organizations/{orgId}/billing/credits/history` query.limit: 1–100 (default 20); query.offset: 0–9007199254740991 (default 0); response.logs: at most 100 items |
| **`billing_usage_get`** Inspect billing usage over a date range | `billing.usage.inspect` billing-credits-and-usage | `mcp:access` Tenant Grant required | `org` | `getBillingUsageSummary` `/api/v1/organizations/{orgId}/billing/credits/usage-summary` query.days: 1–90 (default 30); response.daily: at most 366 items; response.byAction: at most 22 items |
| **`campaign_analytics_get`** Get campaign analytics | `campaign.analytics.inspect` campaign-creation-configuration-workflow-lifecycle | `mcp:access` Tenant Grant required | `org`, `workspace`, `campaign` | `getCampaignAnalytics` `/api/v1/organizations/{orgId}/workspaces/{wsId}/campaigns/{campaignId}/analytics` Synchronous bounded read |
| **`campaign_events_list`** List campaign events | `campaign.event.inspect` campaign-creation-configuration-workflow-lifecycle | `mcp:access` Tenant Grant required | `org`, `workspace`, `campaign` | `getCampaignEvents` `/api/v1/organizations/{orgId}/workspaces/{wsId}/campaigns/{campaignId}/events` Synchronous bounded read |
| **`campaign_progress_get`** Get campaign progress | `campaign.progress.inspect` campaign-creation-configuration-workflow-lifecycle | `mcp:access` Tenant Grant required | `org`, `workspace`, `campaign` | `getCampaignProgress` `/api/v1/organizations/{orgId}/workspaces/{wsId}/campaigns/{campaignId}/progress` Synchronous bounded read |
| **`campaign_workflow_get`** Get campaign workflow | `campaign.workflow.inspect` campaign-creation-configuration-workflow-lifecycle | `mcp:access` Tenant Grant required | `org`, `workspace`, `campaign` | `getCampaignWorkflow` `/api/v1/organizations/{orgId}/workspaces/{wsId}/campaigns/{campaignId}/workflow` Synchronous bounded read |
| **`campaigns_get`** Get campaign | `campaign.get` campaign-creation-configuration-workflow-lifecycle | `mcp:access` Tenant Grant required | `org`, `workspace`, `campaign` | `getCampaign` `/api/v1/organizations/{orgId}/workspaces/{wsId}/campaigns/{campaignId}` Synchronous bounded read |
| **`campaigns_list`** List campaigns | `campaign.list` campaign-creation-configuration-workflow-lifecycle | `mcp:access` Tenant Grant required | `org`, `workspace` | `listCampaigns` `/api/v1/organizations/{orgId}/workspaces/{wsId}/campaigns` Synchronous bounded read |
| **`companies_get`** Get company | `company.get` company-read-and-dedup-workflow-lifecycle | `mcp:access` Tenant Grant required | `org`, `workspace`, `company` | `getCompany` `/api/v1/organizations/{orgId}/workspaces/{wsId}/companies/{companyId}` Synchronous bounded read |
| **`companies_list`** List companies | `company.list` company-read-and-dedup-workflow-lifecycle | `mcp:access` Tenant Grant required | `org`, `workspace` | `listCompanies` `/api/v1/organizations/{orgId}/workspaces/{wsId}/companies` query.page: 1–9007199254740991 (default 1); query.limit: 1–100 (default 20); response.companies: at most 100 items |
| **`connector_warmup_get`** Get connector warmup status | `connector.warmup.inspect` connector-warmup-and-sender-health | `mcp:access` Tenant Grant required | `org`, `workspace`, `connector` | `getConnectorWarmupStatus` `/api/v1/organizations/{orgId}/workspaces/{wsId}/connectors/{connectorId}/warmup` Synchronous bounded read |
| **`deals_get`** Get deal | `deal.get` deal-and-pipeline-read-workflow-lifecycle | `mcp:access` Tenant Grant required | `org`, `workspace`, `deal` | `getDeal` `/api/v1/organizations/{orgId}/workspaces/{wsId}/deals/{dealId}` Synchronous bounded read |
| **`deals_list`** List deals | `deal.list` deal-and-pipeline-read-workflow-lifecycle | `mcp:access` Tenant Grant required | `org`, `workspace` | `listDeals` `/api/v1/organizations/{orgId}/workspaces/{wsId}/deals` Synchronous bounded read |
| **`pipelines_get`** Get pipeline | `pipeline.get` deal-and-pipeline-read-workflow-lifecycle | `mcp:access` Tenant Grant required | `org`, `workspace`, `pipeline` | `getPipeline` `/api/v1/organizations/{orgId}/workspaces/{wsId}/pipelines/{pipelineId}` Synchronous bounded read |
| **`team_members_list`** Inspect team members | `team.member.inspect` identity-organizations-workspaces-teams-permissions | `mcp:access` Tenant Grant required | `org` | `listTeamMembers` `/api/v1/organizations/{orgId}/members` query.search: max\_length; query.page: 1–9007199254740991 (default 1); query.limit: 1–100 (default 50); response.members: at most 100 items |

### Machine authorities

* [Capability parity report](https://developer.firstsales.io/data/capability-parity-report.v1.json) — exact compiled/eligible tool set, tenant context, bounds, and proof.
* [Release manifest](https://developer.firstsales.io/openapi/firstsales-public-v1.release-manifest.json) — bundle, authorization catalog epoch, release state, and contract hashes.
* [Canonical OpenAPI](https://developer.firstsales.io/openapi/firstsales-public-v1.yaml) — REST operation schemas linked by the operation IDs above.

The runtime also implements the `capability_catalog` resource, the `operation_status`
resource template, and the `safe_campaign_review` prompt. Resources and prompts are
first-class discovery surfaces. They do not mirror the OpenAPI or CLI registries.

## Task and Operation foundation

Tasks are the optional `io.modelcontextprotocol/tasks` extension and activate only when
a modern client advertises that extension in its per-request capabilities. A tool that
crosses its synchronous limit may project a durable Operation as a Task only when Tasks
are negotiated. `tasks/get`, `tasks/update`, and `tasks/cancel` remain behind the same
release and authorization gates. The runtime also implements an
`operation_status` resource template, but no current product MCP tool starts
long-running or approval-backed work. These methods define the contract for future
actions; current tools do not use them.

## Write tools

No write tool is released today; the released catalog above is read-only.

These write tools are built and will be released one at a time. Each has its own
capability, and each call records an audit row with surface `mcp`.

| Tool | Tier | Operation |
| - | - | - |
| `campaign_workflow_update` | `mcp:write` | Change a campaign's lists and senders. |
| `list_membership_update` | `mcp:write` | Add or remove contacts in a list. |
| `blocked_domains_list` | `mcp:access` | List blocked domains. |
| `blocked_domains_add` | `mcp:write` | Block domains. |
| `blocked_domains_remove` | `mcp:destructive` | Unblock a domain. Not planned for release. |
| `email_auth_verify` | `mcp:write` | Run a live SPF, DKIM, and DMARC check. |
| `connector_create_cal_com` | `mcp:write` | Connect Cal.com. Requires `idempotencyKey`. |

Idempotency keys for MCP writes are scoped to the grant. Direct Email tools (draft,
send, schedule, cancel) are planned; no MCP tool will approve a Direct Email.

## Protocol notes

* Modern clients must use the per-request contract instead of the old
  `initialize`/`session` handshake.
* Modern `Mcp-Session-Id` and `Last-Event-ID` values are ignored; they never select
  state, authority, or replay.
* Invalid `Host`, `Origin`, protocol, media type, or authorization values fail closed.
* Tool execution errors are returned as MCP tool results with `isError: true` so
  an agent can recover without treating a business error as a transport failure.

### Transport and authorization errors

The HTTP boundary returns stable error codes before JSON-RPC dispatch:

| Status | Code | Meaning |
| - | - | - |
| `401` | `invalid_token` | The bearer token is missing, invalid, or expired. |
| `401` | `invalid_grant` | The saved MCP connection or tenant Grant is no longer current. |
| `403` | `insufficient_scope` | The token lacks a required MCP OAuth scope. |
| `403` | `invalid_host` or `invalid_origin` | The request host or origin is not allowed. |
| `405` | `method_not_allowed` | The selected protocol lane does not support the HTTP method. |
| `406` | `not_acceptable` | `Accept` permits neither JSON nor SSE. |
| `415` | `unsupported_media_type` | A `POST` does not contain JSON. |
| `503` | `authorization_unavailable` | FirstSales cannot verify the live tenant Grant. |
| `503` | `mcp_release_blocked` | Release admission, attestation, or withdrawal policy blocks tools. |

The response also includes the applicable `WWW-Authenticate` challenge for bearer-token
and scope failures. After dispatch, malformed JSON-RPC requests use JSON-RPC errors.
Tool-level API, input, and policy failures return a completed tool result with
`isError: true`.

### Global limits

The runtime applies these limits in addition to the per-tool bounds in the catalog:

| Boundary | Limit |
| - | - |
| Descriptors per discovery page | 25 |
| Encoded discovery page | 256 KiB |
| Tool name | 128 characters |
| Descriptor text field | 4,096 characters |
| Input or output JSON Schema | 64 KiB, depth 32, 4,096 nodes, and 1,024 enum values |
| Schema validation | 100 ms |
| Inline tool result | 128 KiB |
| Synchronous tool execution | 2 seconds |

Schemas use JSON Schema 2020-12 and local JSON Pointer references. The server rejects
network references and descriptors or results that exceed these limits.

## Campaign activation and account acquisition

The new CLI campaign activation commands do not imply corresponding Product MCP tools or consent scopes. Those activation tools remain pending; use [campaign activation](/docs/campaign-activation) for the public API deployment prerequisite. Account signup/login, Developer API keys, MCP OAuth grants and sender Connectors remain separate authorization surfaces.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.