Anchored Growth MCP

Anchored Growth MCP — Getting Started

Anchored Growth is a Model Context Protocol (MCP) server that gives AI sales agents calibrated outcome predictions: Bayesian per-channel posteriors, CUSUM drift state, per-contact decision history, recommendation profiles, and a tamper-evident audit trail. You ingest outbound outcomes through a sibling REST endpoint; the MCP tools return calibrated views your agent can call.

This doc gets you from "I have an API key" to "I just made a calibrated tool call" in 5 minutes.

Status: live in production at mcp.anchoredgrowth.io. Every URL below points at production. MCP access is currently included with any active subscription — 3 keys per company, 1,000 tool calls per month.

What you get in V1

Seven read-only MCP tools:

Tool Purpose Required args
get_campaign_health Composite health score for one campaign tenant_id, campaign_id
get_campaign_recommendations Pre-baked recommendation profiles (tone, day-of-week, body word count, etc.) tenant_id
get_bandit_state Thompson-sampler Beta posteriors per (channel, position) arm tenant_id
get_drift_state CUSUM drift detection — is the model still calibrated? tenant_id
get_workflow_decisions Per-contact decision history (what we recommended and why) tenant_id
get_recipient_intelligence Composite per-contact view (decisions joined with position guidance) tenant_id, contact_id
get_action_audit_log Self-referential audit trail of every prior tool call tenant_id

Every tool response is locked to schema_version: "1.0.0". Adding fields is non-breaking; renames or removals require a major version bump.

Beyond tools: resources and prompts

The server also serves three resources — static reference documents your agent can read in-band, at no quota cost:

URI What it is
ag://reference/alerts Every alert code, its severity, what it means, what to do
ag://reference/freshness How current the data is; what as_of vs data_as_of mean
ag://reference/trust-model Which fields tell you whether a number is backed by your own data

Read ag://reference/alerts when you hit an alert you do not recognise. The alerts array is how this service discloses small samples, borrowed priors, placeholder maths and stale data — an unfamiliar code is a reason to look it up, not a reason to ignore it.

And three prompts, which encode the tool ORDER that produces a defensible answer (the tools have non-obvious interdependencies — drift calibration governs whether the bandit posteriors mean anything at all):

Prompt Arguments Use for
diagnose_campaign campaign_id "Why is this campaign underperforming?"
assess_data_trust — "How much of this company's intelligence is real?"
brief_on_contact contact_id "Tell me about this prospect"

Full per-tool schemas: tool-reference.md (generated from the server's own tools/list, so it cannot drift).

Limits and headers

Limit Value
Monthly quota 1,000 tool calls per key
Burst 200 tool calls per 15 minutes per key
Active keys 3 per company
Key lifetime 90 days

Only tools/call counts against quota and burst. initialize, tools/list, resources/* and prompts/* are free, so an agent that is over its limits can still connect and discover — it just cannot call tools.

Successful responses carry your remaining headroom:

RateLimit-Limit: 200
RateLimit-Remaining: 199
RateLimit-Reset: 209

A 429 carries Retry-After. Burst rejections do not consume monthly quota.

You can omit tenant_id

Every tool accepts tenant_id, but your API key is already bound to exactly one company, so omit it and the server resolves it for you. Pass it explicitly only if you are using a multi-tenant bootstrap credential. Passing a company other than the one your key is bound to returns permission_denied — and the attempt is recorded in that company's audit log.

Endpoints

Surface URL
MCP server (streamable-http) https://mcp.anchoredgrowth.io/mcp
Health check (public) https://mcp.anchoredgrowth.io/healthz
Outcome ingestion (REST) https://c3ltly5r5j.execute-api.us-east-2.amazonaws.com/prod/ingest/events

mcp.anchoredgrowth.io is the stable hostname; use it rather than the raw execute-api address, which can change.

Prerequisites

Step 1: Confirm the server is reachable

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

Expected response:

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

If you do not get 200 OK, stop here and contact us — your network is blocking the host.

Step 2: Configure 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 (get_campaign_health, get_bandit_state, etc.) will appear in the MCP tool menu.

Option B: Python

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()

            # List the tools the server exposes
            tools = await session.list_tools()
            print([t.name for t in tools.tools])

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

asyncio.run(main())

The tenant_id is whatever AG company ID was associated with your key when it was issued (we tell you when we send the key).

Option C: curl

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"

# initialize
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"}
    }
  }'

Expected (the response is SSE-framed; the JSON-RPC payload is on the data: line):

event: message
data: {"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"2025-11-25","capabilities":{...},"serverInfo":{"name":"ag-mcp","version":"1.28.1"}}}

Step 3: Make your first tool call

get_action_audit_log is the fastest tool to try because it returns immediately whether or not you've sent us any data — every prior tool call (including this one) is recorded, and the response is the same shape 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 — the text field 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.",
  "tool_breakdown": {},
  "error_rate": 0.0,
  "alerts": [
    "no_audit_entries_available",
    "payloads_not_stored_only_sha256_hashes"
  ],
  "as_of": "2026-05-25T22:08:06.044096Z"
}

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

Step 4: Send us your outcome data

The MCP tools become meaningful once they have outcomes to reason over. POST outbound outcomes to the public-API ingest 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-05-20T15:30:00Z",
      "outcome_at": "2026-05-21T09:12:00Z",
      "outcome": "responded",
      "tone": "direct",
      "word_count": 87
    }]
  }'

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

Required event fields:

Field Type Notes
channel string email or linkedin
sequence_step int 0-indexed step (0..50). 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 the pipeline's reply-detection
recipient_position Job title string (e.g. "VP of Engineering") — feeds position bucketing
thread_id If omitted, derived deterministically from (companyId, recipient, campaign)
tone One of direct, conversational, professional, empathetic, questioning. The string casual is NOT accepted — use conversational.
word_count, subject, body_excerpt, body, industry Content features. body is opt-in and capped at 10000 chars.
metadata Arbitrary JSON, max 2 KB serialized. metadata.campaign_id or metadata.sequence_id contributes to thread derivation.

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

A CSV variant is also live at POST /ingest/csv (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 of outcomes lands, the analytics pipeline will start populating per-tenant state. get_bandit_state exposes the Bayesian per-(channel, position) arm 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}
    }
  }'

On a brand-new tenant you'll see:

{
  "schema_version": "1.0.0",
  "tenant_id": "YOUR_TENANT_ID",
  "scope": "company",
  "arm_count": 0,
  "arms": [],
  "summary": "No bandit state available for this tenant.",
  "alerts": [
    "no_bandit_state_available",
    "recommendations_are_company_scoped_not_per_campaign"
  ],
  "as_of": "..."
}

The intelligence pipeline runs 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).

Troubleshooting

HTTP 401 — missing_token

The Authorization header was absent or empty. Make sure the header is sent exactly as Authorization: Bearer ag_....

HTTP 401 — invalid_token

The token doesn't match any configured credential. The bootstrap-key path returns this code; the self-serve key path returns the JSON below instead.

HTTP 401 — self_serve_auth_failed

The response body includes a reason field that tells you what's wrong:

HTTP 429 — QUOTA_EXCEEDED

Monthly tool-call quota exhausted for this key. The response body includes quotaLimit, currentUsage, and a resetAt ISO8601 timestamp for the start of the next UTC month. Contact us for a higher limit.

HTTP 429 — TOO_MANY_FAILED_ATTEMPTS

Five bad authentication attempts from the same source IP for the same key within one hour. The response also returns Retry-After: 3600. Wait an hour or call from a different IP.

HTTP 406 — Not Acceptable

Your Accept header doesn't include both application/json AND text/event-stream. Streamable-http always responds in SSE-framed form. Use:

Accept: application/json, text/event-stream

HTTP 200 but the response body looks like an SSE stream

That's expected — streamable-http frames the JSON-RPC payload inside an SSE data: line. The Python SDK and mcp-remote parse this transparently. For raw curl debugging, the JSON of interest is the line starting with data:.

Audit and privacy

Every tool call writes one row to our audit log, blocking the response on the audit write. Each row contains:

We do not store message bodies, recipient emails, or other payload data for analytical purposes beyond what's needed to compute responses. Hashed audit rows are the only persistent record of a tool call.

Data retention and deletion

Ingested events are auto-deleted via DynamoDB TTL 24 months after ingestion.

To 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"

To 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.

What's V1 vs what's coming

V1 (today, test-preview):

Coming soon:

Getting a key

Mint your own key in the app: sign in at https://app.anchoredgrowth.io, open AI Agent Access in the sidebar, and click Generate API key. You need the manage_billing permission (Company Owner or Organization Admin). The key is shown ONCE — copy it before closing the dialog. The same screen shows your tenant_id and ready-made config snippets. doc. Keys are issued in the format ag_<48 chars>_<8-char checksum> and shown exactly once.

Schema versioning

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

Reporting issues

Bug reports and product feedback: jchasecrafton@anchoredgrowth.io.

Please include the value of the as_of timestamp from any tool response, the HTTP status code, and the full error body (with the API key redacted).