MCP server

Streamable HTTP MCP endpoint at /api/mcp. JSON-RPC 2.0. Designed so an agent can provision, author, query, and summarize a survey end-to-end without a human in the loop.

Discovery

GET /api/mcp
# returns server info, protocol version, and tool names

POST /api/mcp
{ "jsonrpc": "2.0", "id": 1, "method": "tools/list" }
# returns full tool definitions, input schemas, and _meta.estimatedTokenCost

Auth

provisionProject is unauthenticated (anyone can sign up). Every other tool requires a Bearer token. We accept two kinds:

# Service key (sk_...) — for headless server-to-server
Authorization: Bearer sk_...

# OAuth access token (at_...) — for agents that can complete the consent flow
Authorization: Bearer at_...

OAuth 2.1 + PKCE + DCR

Agents that can open a browser (Claude Code, Cursor) can register dynamically and get tokens scoped to a single project chosen by the user — no admin pre-provisioning.

1. Discover endpoints:
   GET /.well-known/oauth-authorization-server

2. Register the client (RFC 7591):
   POST /oauth/register
   { "client_name": "Claude Code", "redirect_uris": ["http://localhost:51763/cb"] }
   → { "client_id": "client_..." }

3. Open the authorize URL in the user's browser:
   /oauth/authorize?response_type=code
                   &client_id=client_...
                   &redirect_uri=http://localhost:51763/cb
                   &code_challenge=<sha256(verifier)|base64url>
                   &code_challenge_method=S256
                   &state=<random>

4. User picks a project, clicks Allow → redirect lands at the local listener:
   http://localhost:51763/cb?code=code_...&state=...

5. Exchange the code:
   POST /oauth/token
   grant_type=authorization_code
   code=code_...
   client_id=client_...
   redirect_uri=http://localhost:51763/cb
   code_verifier=<the verifier you hashed in step 3>

   → { "access_token": "at_...", "token_type": "Bearer",
       "expires_in": 2592000, "scope": "project",
       "project_id": "proj_..." }

6. Use the access token against /api/mcp:
   Authorization: Bearer at_...
text

The service key is returned by provisionProject and rotatable via rotateKey. OAuth access tokens expire after 30 days; the agent re-runs the flow to refresh.

Tools

ToolPurposeAuthin/out tokens
provisionProjectBootstrap project + keys + integration snippetnone60 / 350
createSurveyCreate a draft surveybearer400 / 80
parseSurveyParse loose JSON / markdown / prose into validated Question[]bearer350 / 500
editSurveyReplace draft contentsbearer400 / 60
publishSurveyPromote draft to publishedbearer80 / 120
listSurveysEnumerate surveys in a projectbearer50 / 250
queryResponsesRaw paginated responsesbearer80 / 1500
summarizeResponsesToken-efficient analyticsbearer60 / 600
exportInsightsHeadline + bullets + summarybearer60 / 900
getUsageCurrent period response countbearer40 / 80
listMembersList humans on a projectbearer50 / 200
inviteMemberInvite a teammate by email (unlimited members)bearer80 / 150
removeMemberRemove a project memberbearer60 / 30
subscribeWebhookRegister an outbound webhookbearer80 / 100
rotateKeyRotate publishable or service keybearer50 / 80

End-to-end loop

provisionProject({ name }) → { projectId, publishableKey, serviceKey, integrationSnippet }
createSurvey({ projectId, draft }) → { surveyId, version: 1 }
publishSurvey({ projectId, surveyId }) → { publishedVersion, publicUrl }
   ... respondents submit ...
summarizeResponses({ projectId, surveyId }) → SurveySummary
exportInsights({ projectId, surveyId }) → { headline, bullets, summary }

Webhooks

Subscribe via subscribeWebhook to receive POSTs on response.submitted and survey.published. Signed with HMAC-SHA256 via the x-survey-signature header.

// HMAC verify (Node)
import { createHmac, timingSafeEqual } from "node:crypto";

function verify(req, secret) {
  const ts = req.headers["x-survey-timestamp"];
  const sig = req.headers["x-survey-signature"];
  const expected = createHmac("sha256", secret)
    .update(`${ts}.${req.rawBody}`)
    .digest("hex");
  return timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
}