Anchored Growth MCP

Anchored Growth — API Guide

Anchored Growth gives AI sales agents calibrated outcome predictions: Bayesian per-channel posteriors, drift detection, per-contact decision history, recommendation profiles, and a tamper-evident audit trail.

You send us your outbound outcomes through a REST ingestion endpoint; you read calibrated decisions back through a set of Model Context Protocol (MCP) tools that any MCP-capable agent (Claude Desktop, Claude Code, your own client) can call.

This guide takes you from "I have an API key" to "I just made a calibrated tool call" in about five minutes.


What you get

Seven read-only MCP tools:

Tool What it answers Required args
get_campaign_health "How is this campaign performing?" tenant_id, campaign_id
get_campaign_recommendations "What does AG recommend?" (tone, day-of-week, word count, …) tenant_id
get_bandit_state "What's the Bayesian state behind those recommendations?" (Beta posteriors per channel/position) tenant_id
get_drift_state "Is the model still calibrated?" (CUSUM drift) tenant_id
get_workflow_decisions "Show me the actual per-contact decisions." tenant_id
get_recipient_intelligence "Tell me about this contact — decisions + position guidance." tenant_id, contact_id
get_action_audit_log "Prove every prior tool call was logged." tenant_id

Every response is locked to schema_version: "1.0.0". New fields may be added (non-breaking); existing fields are never renamed or removed before a major version bump.


Endpoints

Surface URL
MCP server (streamable-http) https://mcp.anchoredgrowth.io/mcp
MCP health check (public, no auth) https://mcp.anchoredgrowth.io/healthz
Outcome ingestion — JSON https://c3ltly5r5j.execute-api.us-east-2.amazonaws.com/prod/ingest/events
Outcome ingestion — CSV https://c3ltly5r5j.execute-api.us-east-2.amazonaws.com/prod/ingest/csv

All calls authenticate with the same key:

Authorization: Bearer ag_YOUR_KEY_HERE

Authentication

You authenticate with an Anchored Growth API key in the format ag_<48 chars>_<8-char checksum>. The same key works for both the MCP tools and the ingestion endpoints, scoped to a single tenant_id (your company ID).

Getting a key: mint it yourself at https://app.anchoredgrowth.io → AI Agent Access → Generate API key (requires the manage_billing permission). The screen also shows your tenant_id and config snippets. The key is shown exactly once — store it securely.


Step 1 — Confirm the server is reachable

curl https://mcp.anchoredgrowth.io/healthz

Expected:

{"status":"ok","transport":"streamable-http"}

If you don't get 200 OK, your network is blocking the host — stop here and contact us.


Step 2 — Connect your client

Option A — Claude Desktop

Claude Desktop launches MCP servers over stdio, so a remote streamable-http server needs the mcp-remote bridge (Node.js, run via npx).

Edit ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or %APPDATA%\Claude\claude_desktop_config.json (Windows):

{
  "mcpServers": {
    "anchored-growth": {
      "command": "npx",
      "args": [
        "-y",
        "mcp-remote",
        "https://mcp.anchoredgrowth.io/mcp",
        "--header",
        "Authorization:Bearer ag_YOUR_KEY_HERE",
        "--transport",
        "http-only"
      ]
    }
  }
}

Restart Claude Desktop. The seven Anchored Growth tools appear in the MCP tool menu.

Option B — Python (official MCP SDK)

pip install mcp
import asyncio
from mcp import ClientSession
from mcp.client.streamable_http import streamablehttp_client

MCP_URL = "https://mcp.anchoredgrowth.io/mcp"
API_KEY = "ag_YOUR_KEY_HERE"

async def main():
    headers = {"Authorization": f"Bearer {API_KEY}"}
    async with streamablehttp_client(MCP_URL, headers=headers) as (read, write, _):
        async with ClientSession(read, write) as session:
            await session.initialize()

            tools = await session.list_tools()
            print([t.name for t in tools.tools])

            result = await session.call_tool(
                "get_action_audit_log",
                {"tenant_id": "YOUR_TENANT_ID", "limit": 5},
            )
            print(result.content[0].text)

asyncio.run(main())

Option C — curl (protocol-level debugging)

Every MCP request is JSON-RPC 2.0. The streamable-http transport replies with Server-Sent Events, so the Accept header must include both content types:

KEY="ag_YOUR_KEY_HERE"
MCP_URL="https://mcp.anchoredgrowth.io/mcp"

curl -X POST $MCP_URL \
  -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{
    "jsonrpc":"2.0","id":1,"method":"initialize",
    "params":{
      "protocolVersion":"2025-11-25",
      "capabilities":{},
      "clientInfo":{"name":"curl","version":"0.0.1"}
    }
  }'

The JSON-RPC payload comes back on the SSE data: line.


Step 3 — Make your first tool call

get_action_audit_log is the fastest tool to try: it returns immediately whether or not you've sent us any data, and the shape is identical on a fresh tenant.

curl -X POST $MCP_URL \
  -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{
    "jsonrpc":"2.0","id":2,"method":"tools/call",
    "params":{
      "name":"get_action_audit_log",
      "arguments":{"tenant_id":"YOUR_TENANT_ID","limit":5}
    }
  }'

Expected (excerpt — text is a JSON-encoded string per the MCP content-block contract):

{
  "schema_version": "1.0.0",
  "tenant_id": "YOUR_TENANT_ID",
  "scope": "company",
  "entry_count": 0,
  "entries": [],
  "summary": "No audit entries for this tenant.",
  "alerts": ["no_audit_entries_available", "payloads_not_stored_only_sha256_hashes"],
  "as_of": "2026-06-09T00:00:00Z"
}

Call it a second time and entry_count increments — the audit trail is self-referential by design.


Step 4 — Send us your outcome data

The tools become meaningful once they have outcomes to reason over. POST outbound outcomes to the JSON ingestion endpoint:

curl -X POST https://c3ltly5r5j.execute-api.us-east-2.amazonaws.com/prod/ingest/events \
  -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "events": [{
      "channel": "email",
      "sequence_step": 0,
      "recipient_id": "prospect-001",
      "recipient_email": "demo@example.com",
      "recipient_position": "VP of Engineering",
      "sent_at": "2026-06-01T15:30:00Z",
      "outcome_at": "2026-06-02T09:12:00Z",
      "outcome": "responded",
      "tone": "direct",
      "word_count": 87
    }]
  }'

Expected: HTTP 202 with body {"accepted":1,"rejected":0}.

Required fields:

Field Type Notes
channel string email or linkedin
sequence_step int 0-indexed step (0–50). This is the step number, not a job title.
recipient_id string Your opaque prospect identifier
sent_at ISO8601 When the message was sent
outcome_at ISO8601 When the outcome was observed
outcome string One of responded, no_response, bounced, unsubscribed

Useful optional fields:

Field Notes
event_id UUID for client-side idempotency
recipient_email RFC 5321 address; used by reply-detection
recipient_position Job title (e.g. "VP of Engineering") — feeds position bucketing
thread_id If omitted, derived deterministically from (tenant, recipient, campaign)
tone One of direct, conversational, professional, empathetic, questioning. casual is not accepted — use conversational.
word_count, subject, body_excerpt, body, industry Content features. body is opt-in, capped at 10 000 chars.
metadata Arbitrary JSON, ≤2 KB serialized. metadata.campaign_id / metadata.sequence_id contributes to thread derivation.

Limits: max 500 events per request. Use {"events":[...]} for batches, or POST a single event object (no wrapper) for one-offs.

CSV alternative: POST /ingest/csv accepts a header row + up to 500 data rows (5 MB body cap, same field names as columns).


Step 5 — Get a calibrated read

After your first batch lands, the analytics pipeline starts populating per-tenant state. get_bandit_state exposes the Bayesian per-(channel, position) posteriors that drive AG's decisions:

curl -X POST $MCP_URL \
  -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{
    "jsonrpc":"2.0","id":3,"method":"tools/call",
    "params":{
      "name":"get_bandit_state",
      "arguments":{"tenant_id":"YOUR_TENANT_ID","top_n":5}
    }
  }'

The intelligence pipeline runs once daily; arms populate after the first batch is ingested and processed. Each populated arm carries alpha, beta, posterior_mean, posterior_variance, and a data_source flag (migration = bootstrapped from history, live = streamed).


Deleting data

# Delete a single event
curl -X DELETE \
  "https://c3ltly5r5j.execute-api.us-east-2.amazonaws.com/prod/ingest/events/{event_id}" \
  -H "Authorization: Bearer $KEY"

# Delete every event for a recipient
curl -X DELETE \
  "https://c3ltly5r5j.execute-api.us-east-2.amazonaws.com/prod/ingest/events?recipient_id=prospect-001" \
  -H "Authorization: Bearer $KEY"

Both are tenant-scoped — there is no path that deletes events outside your company. Ingested events also auto-delete via TTL 24 months after ingestion.


Troubleshooting

Status Meaning Fix
401 missing_token Authorization header absent/empty Send exactly Authorization: Bearer ag_...
401 invalid_token / self_serve_auth_failed Token doesn't match a credential. Body's reason says why: INVALID_FORMAT (truncation/whitespace), TOKEN_NOT_FOUND, TOKEN_INACTIVE (revoked), TOKEN_EXPIRED / TOKEN_LOCKED, TOKEN_INVALID (typo) Re-check the key; contact us if revoked/expired
429 QUOTA_EXCEEDED Monthly tool-call quota exhausted Body has quotaLimit, currentUsage, resetAt. Contact us for a higher limit
429 TOO_MANY_FAILED_ATTEMPTS 5 bad auth attempts (same IP+key) in 1 hour Wait (Retry-After: 3600) or use a different IP
406 Not Acceptable Accept header missing a type Use Accept: application/json, text/event-stream
200 but body looks like an SSE stream Expected — streamable-http frames JSON-RPC inside SSE The JSON is on the data: line; SDKs parse it transparently

Privacy & audit

Every tool call writes one row to our audit log, and the response blocks on that write (the audit is the canonical record, not a nice-to-have). Each row contains:

We do not store message bodies, recipient emails, or other payload data beyond what's needed to compute a response. Hashed audit rows are the only persistent record of a call — sufficient to prove a call happened and verify byte-equality, insufficient to reconstruct content.


Schema versioning

Every response includes schema_version: "1.0.0". We increment the major version for any breaking change. Until 2.0, all responses are forward-compatible: new fields may appear, but existing fields are never renamed or removed.


Support

Keys are self-serve at https://app.anchoredgrowth.io → AI Agent Access. Bug reports, product feedback and quota increases: jchasecrafton@anchoredgrowth.io.

When reporting an issue, include the as_of timestamp from any tool response, the HTTP status code, and the full error body with the API key redacted.