Developer guide · Model Context Protocol

Build an automated content pipeline with the Endata MCP server

Endata exposes its whole editorial workflow as MCP tools: questions, research, drafts, approvals, publishing and sharing. This guide wires an agent to it and builds a pipeline that turns a customer question into a published, sourced answer, with a person confirming the step that goes public.

By the Endata teamUpdated 10 min read

Endata has two MCP servers. Which one do you need?

Workspace serverNewsroom server

Most of this guide is about the workspace server. The newsroom server needs no setup at all: it exists the moment you publish, and is advertised at /.well-known/mcp.json.

Step 1 2 minutes

Create a scoped API key

In Settings → Developer → API Keys, create a key with only the scopes the pipeline needs.

Keys belong to a project and start with fsk_. An owner creates them, and each carries scopes. Tools outside a key's scopes are not listed to the agent at all, so a narrow key is also a shorter, clearer tool list.

ScopeWhat it allows

For this pipeline, grant editorial:read, editorial:write and publishing:manage. Leave administration:manage and organization:manage off unless the agent truly manages domains, integrations or team members.

Step 2 2 minutes

Connect your MCP client

Add the server to Claude Code, Codex, or any client that speaks Streamable HTTP.

Claude Code

json
{
  "mcpServers": {
    "endata": {
      "type": "http",
      "url": "https://app.endata.com/mcp",
      "headers": { "Authorization": "Bearer ${ENDATA_API_KEY}" }
    }
  }
}
In .mcp.json at the root of your project. Set ENDATA_API_KEY in your shell.

Codex

bash
[mcp_servers.endata]
url = "https://app.endata.com/mcp"
bearer_token_env_var = "ENDATA_API_KEY"
In ~/.codex/config.toml.

Your own code

The server is stateless Streamable HTTP and accepts POST only, so the official SDK connects with no session handling. The small call helper below is used in every later step.

javascript
// npm install @modelcontextprotocol/sdk
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StreamableHTTPClientTransport } from
  "@modelcontextprotocol/sdk/client/streamableHttp.js";

const publicationId = process.env.ENDATA_PUBLICATION_ID;
const client = new Client({ name: "answer-pipeline", version: "1.0.0" });
await client.connect(new StreamableHTTPClientTransport(
  new URL("https://app.endata.com/mcp"),
  { requestInit: { headers: {
    Authorization: `Bearer ${process.env.ENDATA_API_KEY}`,
  } } },
));

// Every tool takes { publicationId, input } and returns JSON as text.
async function call(name, input = {}, extra = {}) {
  const result = await client.callTool({
    name,
    arguments: { publicationId, input, ...extra },
  });
  const value = JSON.parse(result.content[0].text);
  if (result.isError) throw new Error(`${name}: ${value.message}`);
  return value;
}

Step 3 1 minute

Find the questions worth answering

Call questions_list with the to-answer filter, or add a question with question_create.

A question is the unit of work: something a customer asks, which the business should have the best answer to. Questions arrive from Endata's own suggestions, from the gaps in your AI Visibility report, or from you.

javascript
// Questions still waiting for an answer, highest priority first.
const { questions } = await call("questions_list", {
  filter: "to-answer",
  limit: 10,
});

// Or add one of your own: a gap from your AI Visibility report, say.
const added = await call("question_create", {
  text: "How much cyber insurance does a 40-person firm need?",
  customers: "Owners of professional services firms",
});

questions_list returns questions, counts by status and a summary. Filters include to-answer, needs-update (answered, but the news has moved or the answer is stale), open and all. discovery_read gives the wider view: what share of customer questions you have answered and what to answer next.

Step 4 A few minutes, unattended

Research and draft the answer

Call question_research to gather evidence and write a cited draft.

Research searches the last 30 days of news and web sources for that question, keeps up to eight pieces of evidence, and writes an answer citing them. It runs in the background and draws on the plan's monthly allowance of researched answers.

javascript
// Research the question and draft its answer. This runs in the
// background and counts against the plan's monthly answers.
const question = questions[0];
await call("question_research", { questionId: question.id });

// Optional: add what only the business knows, attributed to it.
await call("question_perspective", {
  questionId: question.id,
  text: "We size cover from the client's largest single contract, not headcount.",
});

question_perspective is the step most pipelines skip and should not. It adds the business's own view, attributed to a named author, which is what makes the answer more than a summary of other people's pages.

Step 5 2 minutes

Read the draft and its evidence

Call question_read and answer_read, and keep the draftRevision you reviewed.

javascript
// When research is done ("question.researched" webhook, or poll),
// read the draft and the evidence behind it.
const detail = await call("question_read", { questionId: question.id });
const answer = await call("answer_read", { questionId: question.id });

console.log(answer.content);        // the draft
console.log(answer.pending);        // true: readers do not see it yet
console.log(answer.draftRevision);  // the exact revision you reviewed

This is where an agent earns its place. Have it check the draft against the evidence returned by question_read, flag any claim without a source, and save an improved draft with answer_update, which takes the content and the expectedRevision it was based on so two editors cannot overwrite each other. answer_preview returns the page as it would be published.

Step 6 1 minute

Publish, with a person in the loop

Call prepare_action, show its summary to a person, then call answer_publish with the confirmation token.

Publishing, unpublishing, sharing and administrative changes are marked consequential. Endata will not let an unattended key perform them by default. There are two ways through, and you should choose deliberately:

Confirm each action

prepare_action returns a summary and a token bound to that exact input and the current state, valid for 10 minutes. Show the summary to a person; pass the token when they agree.

Grant unattended scopes

An owner can explicitly allow a key to act unattended. Use this for a pipeline you trust end to end, and keep the key's scopes narrow.

javascript
// Publishing is consequential. Prepare the exact action first:
const prepared = await client.callTool({
  name: "prepare_action",
  arguments: {
    publicationId,
    operation: "answer_publish",
    input: { questionId: question.id, revisionId: answer.draftRevision },
  },
});
const { confirmationToken } = JSON.parse(prepared.content[0].text);

// Show the summary to a person. Once they agree, within 10 minutes:
const live = await call(
  "answer_publish",
  { questionId: question.id, revisionId: answer.draftRevision },
  { confirmationToken, requestKey: `publish-${answer.draftRevision}` },
);

requestKey makes the call idempotent: a retry with the same key and input returns the first result and does not publish twice. The answer goes live at once at /questions/<slug> on the newsroom, and answer.url in the result is its address.

Step 7 Optional

Share articles and listen for events

Use article_share to post articles to connected networks, and webhooks to react when things go live.

Sharing works on articles, the news pieces Endata writes on your topics, not on answers. connections_list returns the connected accounts; article_share takes an articleId, a connectionId, your commentary and when to send it (now, at a time, or when published). It is consequential, so the same confirmation rule applies.

Webhooks close the loop. Up to 10 HTTPS endpoints per project receive signed events such as question.answered, question.updated, article.approved and share.sent. Delivery is at least once, with eight attempts over about 22 hours, so make the receiver idempotent and verify the signature:

javascript
import { createHmac, timingSafeEqual } from "node:crypto";

// Pass the exact, unmodified UTF-8 request body and request headers.
function verifyWebhook(rawBody, headers, secret) {
  const timestamp = headers.get("Webhook-Timestamp");
  const signature = headers.get("Webhook-Signature");
  if (!timestamp || !/^\d+$/.test(timestamp)) return false;
  if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;
  if (!/^v1=[a-f0-9]{64}$/.test(signature || "")) return false;

  const expected = createHmac("sha256", secret)
    .update(timestamp + "." + rawBody, "utf8")
    .digest();
  const received = Buffer.from(signature.slice(3), "hex");
  return timingSafeEqual(expected, received);
}
// After verification, parse and validate the event.
// Atomically persist it with a unique constraint on Webhook-Id.
// Return 2xx after persistence; duplicate events should also return 2xx.
// Process asynchronously and reconcile current state through the API.

Let other agents read your answers

Everything you publish is immediately available to any agent, with no key and no integration work. Each newsroom serves a read-only MCP server with four tools: search_answers, get_answer, list_questions and list_changes.

javascript
// No key. Any newsroom: yours, or one you want your agent to cite.
const newsroom = new Client({ name: "reader", version: "1.0.0" });
await newsroom.connect(new StreamableHTTPClientTransport(
  new URL("https://news.example.com/mcp"),
));
const hits = await newsroom.callTool({
  name: "search_answers",
  arguments: { q: "cyber insurance for small firms" },
});

The same content is also served as llms.txt, llms-full.txt, a JSON Feed at /answers.json, and Markdown for any answer by adding .md to its address or sending Accept: text/markdown. This is the practical side of being visible to AI: an assistant that goes looking finds a clean, sourced answer in the format it reads best.

Prefer REST? The same operations are there

MCP tools and the REST API are generated from one registry, so anything above can be done with POST https://app.endata.com/api/v1/publications/:id/operations/:operationId. Writes take an Idempotency-Key header, and confirmations are prepared at /publications/:id/confirmations.

  • GET /api/v1/operations lists every operation with its JSON Schema, with no key.
  • GET /api/v1/openapi.json is an OpenAPI 3.1 document generated from the same registry.
  • Limits: 120 requests a minute per key, 1 MB JSON bodies (14 MB for image uploads).

There is no Endata SDK to install and none is needed: use the MCP SDK, or plain fetch. Full reference on the developers page.

What the MCP server does not do

  • It does not expose the AI Visibility report. There is no tool to run one or read its standings. Bring the gaps in as questions with question_create.
  • It does not use personal OAuth. Only project API keys are accepted today.
  • It is not on Solo. API, MCP and webhooks are part of Business and Agency.
  • It does not bypass plan limits. Research and drafting count against the same monthly allowance as the app.

Frequently asked questions

What is the Endata MCP server URL?

https://app.endata.com/mcp. It is stateless Streamable HTTP over POST, authenticated with a project API key sent as Authorization: Bearer fsk_….

Which MCP clients work with Endata?

Any client that supports Streamable HTTP with a bearer header, including Claude Code, Codex and the official Model Context Protocol SDKs.

Which plan includes MCP and the API?

Business ($199 a month) and Agency. Solo does not include the API, MCP or webhooks. See pricing.

Can an agent publish without a human?

Only if a workspace owner has granted that key unattended scopes. Otherwise consequential tools require a confirmation token from prepare_action, which your client should obtain only after a person agrees.

How many tools does the server expose?

77 operations plus prepare_action, covering published content, articles, publishing, research, questions, answers, media, settings, webhooks, team and widgets. A key sees only the tools its scopes allow.

Can I read my AI Visibility report over MCP or the API?

Not today. No operation exposes the report. discovery_read and search_ai_read cover answered-question share, AI referral visits and retrieval requests.

Is there a public MCP server for published content?

Yes. Every Endata newsroom serves a read-only, keyless MCP server at /mcp, with search_answers, get_answer, list_questions and list_changes, discoverable at /.well-known/mcp.json.

Is there an SDK or an OpenAPI spec?

There is no Endata-specific SDK. There is a live OpenAPI 3.1 document at https://app.endata.com/api/v1/openapi.json, and the operation catalog with JSON Schemas at /api/v1/operations.