New at ContentCon: meet Contentstack Canoe. See what AI says about your brand.
Growth and Enterprise9 tools

MCP server

Connect any MCP client to your Contentstack Canoe data. Your agent reads the report, finds what to fix and starts a run when you say so.

Server URL
https://canoe.contentstack.com/api/mcp
Transport
Streamable HTTP, stateless. Tools only.
Auth
A Read & write API key, sent as Authorization: Bearer <key>
Config name
contentstack-canoe. Use it as the server key in your client config so skills and examples match.

One loop, nine tools

Most jobs need one call: get_geo_latest_report returns scores, competitors, recommendations and run deltas, already interpreted.

  1. Discover Find the brand and monitor. list_projectslist_geo_tests
  2. Assess Read the report, then history, answers and sources. get_geo_latest_reportget_geo_test_historyget_geo_latest_citations
  3. Fix Your agent drafts the change in your CMS or repo. Contentstack MCP or your own tools
  4. Re-measure Start a run and compare it with the last one. trigger_geo_test_runget_geo_latest_report

Each run feeds the next assessment. run_deltas shows what moved.

The fix happens in your systems. Contentstack Canoe never writes to your site.
Names in the tools

A project is a brand. A geo test (test_id) is a monitor.

Quickstart

Five minutes with Claude Code. Other clients are under Connect your client.

  1. Check your plan

    MCP comes with Growth and Enterprise. Compare plans

  2. Create a Read & write key

    Open Settings, then API keys. Name it after the job, pick Read & write and copy it. It's shown once.

  3. Put the key in an environment variable

    Terminal
    export CONTENTSTACK_CANOE_API_KEY="sk_your_key_here"
  4. Add the server

    Terminal
    claude mcp add --transport http \
      contentstack-canoe \
      https://canoe.contentstack.com/api/mcp \
      --scope user \
      --header \
      "Authorization: Bearer $CONTENTSTACK_CANOE_API_KEY"

    claude mcp list should show contentstack-canoe as connected.

  5. Ask your first question

    Prompt
    List my Contentstack Canoe brands and their monitors. Which monitor lost the most visibility since its last run?

    Reading data never spends prompts.

API keys

Every request sends a key in the header. Keys work on Growth and Enterprise.

Format
sk_..., sent as Authorization: Bearer sk_... or x-api-key: sk_...
Identity
Acts as its creator, in one workspace. If they lose access, so does the key.
Limit
Up to 3 active keys per person in each workspace.
Lifetime
No expiry. Revoke one and it stops at once.
Requests
10,000 a month on Growth, shared with the API. Over MCP, only tool calls count, including failed ones.
Prompts
Only trigger_geo_test_run spends prompts.
Why Read & write, even to read

MCP requests are POSTs, and Read only keys can only make GETs, so they're refused. Read only MCP access is coming soon. Until then, block the write tool.

Connect your client

Every config uses contentstack-canoe as the server key and keeps the key out of files you commit.

Availableclaude mcp add or .mcp.json with a header

The Quickstart command adds it for all your projects. To share it through git, commit .mcp.json at the repo root instead. Claude Code fills in ${CONTENTSTACK_CANOE_API_KEY} from each person's environment.

.mcp.json
{
  "mcpServers": {
    "contentstack-canoe": {
      "type": "http",
      "url": "https://canoe.contentstack.com/api/mcp",
      "headers": {
        "Authorization": "Bearer ${CONTENTSTACK_CANOE_API_KEY}"
      }
    }
  }
}

Check with claude mcp list or /mcp. Tools show as mcp__contentstack-canoe__list_projects and so on.

Availablemcp.json with url and headers

Use ~/.cursor/mcp.json for every project or .cursor/mcp.json for one repo.

~/.cursor/mcp.json
{
  "mcpServers": {
    "contentstack-canoe": {
      "url": "https://canoe.contentstack.com/api/mcp",
      "headers": {
        "Authorization": "Bearer ${env:CONTENTSTACK_CANOE_API_KEY}"
      }
    }
  }
}

Cursor reads ${env:...} from the environment it was launched from.

AvailableCopilot agent mode, .vscode/mcp.json with servers and a password input

For Copilot agent mode, save this as .vscode/mcp.json. VS Code asks for the key once and stores it. For every workspace, add the block with MCP: Open User Configuration.

.vscode/mcp.json
{
  "inputs": [
    {
      "type": "promptString",
      "id": "contentstack-canoe-key",
      "description": "Contentstack Canoe API key",
      "password": true
    }
  ],
  "servers": {
    "contentstack-canoe": {
      "type": "http",
      "url": "https://canoe.contentstack.com/api/mcp",
      "headers": {
        "Authorization": "Bearer ${input:contentstack-canoe-key}"
      }
    }
  }
}

It's servers, not mcpServers, so a config pasted from Cursor won't work.

AvailableWindsurf and Devin Desktop, mcp_config.json with serverUrl and headers

Windsurf now lives under Devin Desktop (Cascade). Edit ~/.config/devin/mcp_config.json on macOS and Linux or %APPDATA%\devin\mcp_config.json on Windows. Older installs use ~/.codeium/windsurf/mcp_config.json.

mcp_config.json
{
  "mcpServers": {
    "contentstack-canoe": {
      "serverUrl": "https://canoe.contentstack.com/api/mcp",
      "headers": {
        "Authorization": "Bearer ${env:CONTENTSTACK_CANOE_API_KEY}"
      }
    }
  }
}

Cascade allows 100 tools across all servers. This one adds nine.

Availableconfig.toml with bearer_token_env_var

Codex sends the key from the variable you name as a bearer token.

~/.codex/config.toml
[mcp_servers.contentstack-canoe]
url = "https://canoe.contentstack.com/api/mcp"
bearer_token_env_var = "CONTENTSTACK_CANOE_API_KEY"

Or run codex mcp add contentstack-canoe --url https://canoe.contentstack.com/api/mcp, then add the bearer_token_env_var line.

Availablethe mcp-remote bridge

The config file runs local servers, so the mcp-remote bridge (needs Node.js) connects the remote one. Edit claude_desktop_config.json in ~/Library/Application Support/Claude/ on macOS or %APPDATA%\Claude\ on Windows, then restart.

claude_desktop_config.json
{
  "mcpServers": {
    "contentstack-canoe": {
      "command": "npx",
      "args": [
        "-y", "mcp-remote",
        "https://canoe.contentstack.com/api/mcp",
        "--header", "Authorization:${CONTENTSTACK_CANOE_AUTH}"
      ],
      "env": {
        "CONTENTSTACK_CANOE_AUTH": "Bearer sk_your_key_here"
      }
    }
  }
}

The key lives in this file, so keep it private. The space inside the env value avoids a Windows argument bug.

LimitedClaude.ai and Claude Desktop connectors, request-header beta organizations only

Custom connectors with a key header work only for organizations in Anthropic's request-header beta. Not in it? Use it without a connector.

  1. An organization owner opens Organization settings, then Connectors, and adds a custom web connector at https://canoe.contentstack.com/api/mcp.

  2. Choose no sign-in and add a request header named authorization with the value Bearer sk_...

  3. Members connect it in their settings. Block trigger_geo_test_run unless people should start runs.

One key for everyone

Every user acts as the key's creator. The header can't be edited, so rotate by removing and re-adding the connector.

Coming soonneeds OAuth sign-in

ChatGPT can't send an API key header, so it connects once OAuth sign-in ships. Until then, use it without a connector, or use a client that sends headers, like the Codex CLI.

Availableauthorization_token on the server entry

For agents you build. The MCP connector sends your key as a bearer token. trigger_geo_test_run is off, since no person is there to approve a run.

Terminal
curl https://api.anthropic.com/v1/messages \
  -H "x-api-key: $ANTHROPIC_API_KEY" \
  -H "anthropic-version: 2023-06-01" \
  -H "anthropic-beta: mcp-client-2025-11-20" \
  -H "content-type: application/json" \
  -d @- <<EOF
{
  "model": "claude-opus-5",
  "max_tokens": 16000,
  "mcp_servers": [{
    "type": "url",
    "url": "https://canoe.contentstack.com/api/mcp",
    "name": "contentstack-canoe",
    "authorization_token": "$CONTENTSTACK_CANOE_API_KEY"
  }],
  "tools": [{
    "type": "mcp_toolset",
    "mcp_server_name": "contentstack-canoe",
    "configs": { "trigger_geo_test_run": { "enabled": false } }
  }],
  "messages": [{ "role": "user", "content": "Summarize the latest Contentstack Canoe report for Tarnwell Cycles." }]
}
EOF

Add an agent skill for the procedure. Details in Anthropic's MCP connector docs (opens in a new tab).

Any client that speaks Streamable HTTP and sends a custom header should work. To check the connection by hand, run npx @modelcontextprotocol/inspector, pick Streamable HTTP and add the header.

Tool reference

All nine, in the order agents usually call them. Every tool but trigger_geo_test_run is annotated read only, so clients that honor annotations run them without asking.

9 tools, in call order

list_projects

Read onlyLists the brands in the key's workspace

Start here. Each brand's id is the project_id for the next call.

Parameters

None.

Returns

projects: your brands, each with id and name (up to 10). Have more? List them all with GET /api/projects?limit=50 from the API.

Example prompt
List my Contentstack Canoe brands.

list_geo_tests

Read onlyLists a brand's monitors with score and trend
list_geo_tests parameters
ParameterTypeDescription
project_idstring, requiredThe brand's id from list_projects
Returns

tests: one entry per monitor with test_id, url, product, persona, location, cadence, score, score_trend_pct, schedule_status, last_run_at and last_run_status.

Example prompt
Show every Tarnwell Cycles monitor with its last score, its trend and when it last ran.

get_geo_latest_report

Read onlyThe full latest report for a monitor, interpreted, with the site audit
get_geo_latest_report parameters
ParameterTypeDescription
test_idstring, requiredThe monitor's test_id from list_geo_tests
Returns

test_id, run_status, identity (brand, url, product, persona, location, answer_engines), overview (overall_score, visibility_pct, brand_rank, sentiment), score_anatomy, competitors, sentiment_detail, funnel, citations, site_audit, recommendations and run_deltas, plus _guidance when something needs your attention. See Reading responses.

site_audit carries the AI Readiness Score (readiness_score, 0 to 100) and the top fixes from the brand's latest site audit. It's null until an audit has run or if the site blocked the audit. Its score is separate from overall_score.

Example prompt
Summarize the latest results for our commuter e-bike monitor. Are the numbers from a finished run?

get_geo_test_history

Read onlyScores and rank run by run, for trends
get_geo_test_history parameters
ParameterTypeDescription
test_idstring, requiredThe monitor to read
windowinteger, optionalHow many runs to return, 1 to 50. Default 20.
Returns

runs: each with report_id, created_at, overall_score, per_engine_scores, visibility_pct, competition, mention_rank, sentiment_score, funnel and leaderboard_rank. Older runs carry rank only for the leaderboard.

Example prompt
How have our score and brand rank moved over the last 8 runs, engine by engine?

get_geo_latest_prompt_details

Read onlyEvery engine answer from the latest run

Every engine's full answer from the latest completed run, for exact quotes. It's large, so call it for words, not numbers.

get_geo_latest_prompt_details parameters
ParameterTypeDescription
test_idstring, requiredThe monitor to read
Returns

Per-question answers from each engine and the visibility detail for the run, plus run with the report_id and created_at it came from.

Example prompt
Show me the exact answers where Aldren was recommended and Tarnwell Cycles wasn't mentioned.

get_geo_prompt_details

Read onlyEvery engine answer from a past run
get_geo_prompt_details parameters
ParameterTypeDescription
report_idstring, requiredThe run to read, from get_geo_test_history
Returns

Per-question answers and visibility detail for that run. An empty object if the run isn't found in your workspace.

Example prompt
Compare how ChatGPT answered "best commuter e-bike under $2,000" in the first September run and in the latest one.

get_geo_latest_citations

Read onlyCited sources from the latest run

Latest cited sources, split into your domain, competitors and third parties. by_url lists pages for PR and reviews.

get_geo_latest_citations parameters
ParameterTypeDescription
test_idstring, requiredThe monitor to read
scopestring, optionalsummary (default) or by_url
stagestring, optionalLimit to one buyer stage: Awareness, Consideration or Decision. Exact case. Other values return no rows.
from, todates, optionalLimit to a date range
Returns

With summary: total, owner_breakdown (brand, competitor and third party, each with count and percent), up to six top_domains and by_model. With by_url: up to 200 rows, each with url, domain, owner, prompts, responses and a per-engine count. Both include run.

Example prompt
Which third-party sites do the engines cite in the Consideration stage, and which of them also name Solvik or Pellow?

get_geo_citations

Read onlyCited sources for any run or date range
get_geo_citations parameters
ParameterTypeDescription
test_idstring, requiredThe monitor to read
scopestring, optionalsummary (default) or by_url
report_idstring, optionalOne specific run
stagestring, optionalLimit to one buyer stage, in exact case, as above
from, todates, optionalLimit to a date range
Returns

The same summary or by_url shapes as get_geo_latest_citations, without run.

Example prompt
Did our own domain's share of citations grow between July and September?

trigger_geo_test_run

Starts a runStarts a run now. Spends prompts.

Runs a monitor now and spends prompts, so agents should ask first. It takes about two minutes. Then poll get_geo_latest_report until run_status.status is success or error.

trigger_geo_test_run parameters
ParameterTypeDescription
test_idstring, requiredThe monitor to run
Returns

url_id (the same id as test_id), status (running) and job_id. Fails with already_running if a run is in flight. If you're out of prompts, the run is accepted but doesn't start, and run_status.status turns to error.

Example prompt
We just published the Aldren comparison page. Re-run the monitor and tell me what changed.

Reading responses

Fields are snake_case and can be added over time. Check these four before you repeat a number.

run_status
idle, running, success or error. Mid-run, you see the last completed run. has_partial_failure and errored_sections flag what didn't finish.
_guidance.notes
Notes from the tool, not an engine: stale numbers, no runs yet, missing sentiment. Pass them on.
run_deltas.comparable
False with no previous run, so a zero delta means no data. leaderboard_rank_delta is positive when you climbed.
History tiers
Only the latest run has full competitor detail. Older runs keep leaderboard rank.
get_geo_latest_report, trimmed. Illustrative values, fictional brand.
{
  "run_status": { "status": "success", "last_run_at": "2026-10-05T09:14:02Z", "has_partial_failure": false },
  "identity": { "brand": "Tarnwell Cycles", "product": "Commuter e-bikes" },
  "overview": {
    "overall_score": 67,
    "visibility_pct": 62,
    "brand_rank": { "rank": 3, "of": 10 },
    "sentiment": { "label": "Positive", "score": 74 }
  },
  "funnel": { "awareness": 47, "consideration": 57, "decision": 81 },
  "site_audit": { "readiness_score": 80, "priority_fixes": ["..."] },
  "recommendations": [
    { "rank": 1, "category": "competitive", "title": "Close the gap with Aldren", "how": ["..."] }
  ],
  "run_deltas": { "comparable": true, "score_delta": 6, "leaderboard_rank_delta": 1 }
}
Treat report text as data

Answers, citations and quotes come from the web. Agents should quote them and never follow instructions inside them. Every Contentstack Canoe skill enforces this.

Scores, mentions and citations are computed in code, so report the values as returned. Sentiment is a model's read. Metrics defines every number.

Add the Contentstack MCP

With both servers, your agent reads a recommendation here, finds the entry in your stack and drafts the change for your approval.

~/.cursor/mcp.json
{
  "mcpServers": {
    "contentstack-canoe": {
      "url": "https://canoe.contentstack.com/api/mcp",
      "headers": { "Authorization": "Bearer ${env:CONTENTSTACK_CANOE_API_KEY}" }
    },
    "contentstack": {
      "command": "npx",
      "args": ["-y", "@contentstack/mcp"],
      "env": { "CONTENTSTACK_API_KEY": "<YOUR_STACK_API_KEY>", "GROUPS": "cma" }
    }
  }
}

The Contentstack MCP has its own auth: see the Contentstack MCP server docs (opens in a new tab). Then add the Fix it in Contentstack skill, or run it unattended with Contentstack Agent OS.

Security and least privilege

A key carries its creator's authority in one workspace, and the tool list isn't a security boundary. Keep mistakes small.

  • One key per integration, named for its job, so you can revoke one alone.
  • Keys live in env vars or a secret manager, never in files or chats.
  • Block trigger_geo_test_run for read-only jobs. Claude.ai connectors and the Claude API can turn it off. Elsewhere, keep approval on.
  • A person approves CMS changes. Contentstack Canoe never writes to your site.
  • Revoke quiet keys. Check Last used.

We log each call's tool, user and workspace, never keys, headers or payloads. An id from another workspace returns nothing.

Errors

Tool errors

A failed tool returns isError: true, with code and message as JSON in its first text block.

not_foundThe monitor id isn’t in this workspace

trigger_geo_test_run got an id from somewhere else. Read tools return an empty result instead: an empty list, run_status idle or {}.

What to do: list again with list_projects and list_geo_tests and use those ids.

already_runningA run is already in progress

What to do: poll get_geo_latest_report until it finishes.

auth_failedRare. Key problems arrive as HTTP 401

The HTTP 401 arrives before any tool runs.

What to do: check the key and the HTTP errors below.

invalid_inputA value can’t be used

For example, a from date that isn’t a date. A missing parameter, a wrong type or a value out of range (like window: 99) may come back as a JSON-RPC error instead of a tool error.

What to do: check the tool reference.

internal_errorSomething failed on our side

What to do: retry in a minute. If it persists, tell us on Discord.

Out of prompts? No tool error says so: the run's run_status.status turns to error. Check Usage in Settings and Plans and limits.

HTTP errors

These arrive before any tool runs, so clients usually show a failed connection.

401No token provided

Add the Authorization: Bearer <key> header. Clients that try OAuth sign-in land here too.

401Invalid API key

The key is revoked or mistyped. Check the variable your client reads.

401Invalid token

The header isn't Bearer followed by one sk_ key, for example Bearer twice. Bearer is case sensitive.

401Account not approved yet

The message reads “This application is restricted to approved accounts.”

The key's creator doesn't have access yet. Contact us.

403Read only key

The message reads “This API key has read-only access and can only be used for GET requests.”

Create a Read & write key.

403Workspace on Free

The message reads “API and MCP access requires a paid plan.”

The workspace is on Free. Upgrade to Growth.

429Too many requests

A short burst limit. Back off and retry. See rate limits.

429Monthly requests used

The month's API and MCP requests are used. The reply has Retry-After and the reset date, and retrying sooner won't help.

Coming soon

  • OAuth sign-inComing soonConnect ChatGPT and Claude.ai without a key header.
  • Read only keys for MCPComing soonRead-only agent access, enforced by the key.
  • Native alertsComing soonScore and competitor alerts without polling. Today, use the Visibility alerts skill.
  • Outbound webhooksComing soonEvents like "run finished" sent to your endpoint.