# Build an automated content pipeline with the Endata MCP server

> Point any MCP client at `https://app.endata.com/mcp` with a project API key as a bearer token. You get 77 operations as tools, the same ones as the REST API. A pipeline is five calls: `questions_list`, `question_research`, `answer_read`, `prepare_action`, `answer_publish`. Publishing needs either a person's confirmation or an owner's unattended grant on the key. Separately, every Endata newsroom is itself a public, keyless MCP server that agents can search.

Updated 2026-10-12

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.

- A workspace on Business or Agency
- A project API key (`fsk_…`)
- An MCP client: Claude Code, Codex, or Node.js 20+

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

|   | Workspace server | Newsroom server |
| --- | --- | --- |
| Address | `https://app.endata.com/mcp` | `https://<your-newsroom>/mcp` |
| Auth | Bearer project API key | None |
| Can it write? | Yes, within the key's scopes | No, read-only |
| Tools | 77 operations plus `prepare_action` | `search_answers`, `get_answer`, `list_questions`, `list_changes` |
| Rate limit | 120 requests a minute per key | 60 a minute per address |
| Use it to | Run your editorial workflow from an agent | Let any agent read what you published |

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

| Scope | What it allows |
| --- | --- |
| `published:read` | Read published articles and answers |
| `editorial:read` | Read questions, drafts, research and analytics |
| `editorial:write` | Add questions, start research, save drafts |
| `editorial:approve` | Approve an article revision |
| `publishing:manage` | Publish, unpublish and share |

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.

> **Keep the key on the server** A key acts for the whole project. Put it in an environment variable, never in browser code or a repository. Keys can be given an expiry and are checked for revocation on every request.

## Step 2: 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;
}
```

> **Try it before you write anything** The [developers page](https://endata.com/developers#mcp-playground) has a playground that shows the tool calls and responses without a key.

## Step 3: 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: 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: 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.

> **Don't poll if you can listen** Add a webhook endpoint and subscribe to `question.researched`. Payloads carry `questionId` and `revisionId`, not the text, so fetch the draft when the event arrives.

## Step 6: 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.

> **A token is not consent** `prepare_action` proves which action was prepared, not that a human agreed to it. Your client is responsible for actually asking. Do not have the agent prepare and confirm in one breath.

## Step 7: 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](https://endata.com/developers).

## 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](https://endata.com/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`.

## Keep reading

- [API, MCP and webhook reference](https://endata.com/developers): Every operation, scope and example, with a live playground.
- [Fix an AI visibility gap in 10 minutes](https://endata.com/guides/fix-ai-visibility-gap): The same workflow, done by hand in the app.
- [Endata for agencies](https://endata.com/agencies): One workspace per client, under your brand, with the API.
