Skip to content
Centriu
Centriu Dash

AI-Agent MCP Tool-Server Automation: 25 Ways to Read, Zero Ways to Write

Centriu Dash runs a real Model Context Protocol server at `/api/mcp/v1` — JSON-RPC 2.0, protocol version `2025-06-18`, one POST endpoint, one `switch` statement short enough to read end to end. It exposes 25 tools total (17 native to Dash — funnel, forecast, social, models, data health — plus 8 read-only Maestro ad-spend tools), and not one of them writes anything: the write-tool list is a confirmed empty array, and a dedicated test suite checks that no tool name even contains a write verb in English or Portuguese. An agent authenticates with a bearer key tied to one organization; that organization is resolved server-side from the key's hash, never accepted as an argument — the code deletes `organization_id`, `organizacao` and `tenant_id` from every tool call's arguments before execution, whether or not an agent tries to send them. The whole surface is off by default behind a feature flag, and when it's off the endpoint returns 404, not 403, because a locked door still confirms a door exists.
Org scope resolved from the key, not the call
Zero write tools, tested to stay that way
Person working on a laptop with notifications on screen
25 ways to read, zero ways to write.

Why letting an AI agent "just query the dashboard" is a real access-control problem

The appeal of pointing ChatGPT or another MCP-speaking agent at a company's own dashboard is obvious — ask a plain-language question, get a real number back, no login screen in between. The risk is exactly as obvious once you say it out loud: any door that answers a bearer key instead of a logged-in session is a door that has to get organization scope, write access and error messages right on its own, because there is no browser session, no CSRF token and no human double-checking a form before it submits.

How the underlying problem shows up before you fix it

An integration built for "read access" quietly grows a write path because one endpoint was reused for convenience instead of built narrow on purpose.

A tool schema accepts an `organization_id` or `tenant_id` argument, trusting the caller to send the right one instead of resolving it server-side from an authenticated identity.

A raw database error reaches an external caller and carries a table or column name that was never meant to leave the server.

A percentile or benchmark computed across "the market" is actually computed across so few companies that it identifies one of them under a different label.

A new integration surface ships enabled by default, so opening it to the outside world is an accident of a deploy rather than a deliberate decision someone can point to.

Why a key-authenticated, agent-facing API needs different defaults than a logged-in dashboard

A person using a dashboard through a browser gets scope enforcement, layout that hides what a role can't see, and a session that expires. An agent holding a bearer key gets none of that for free — whatever the server does inside the request handler IS the entire security model. That is exactly why an endpoint like this one earns a stricter default than the rest of the product: off unless someone deliberately turns it on, no write path even as an option, and every trust decision (organization scope, tool permission, anonymity) enforced in code that a test suite checks, not left to the convention of "the frontend wouldn't send that."

How Centriu Dash built a tool server an agent can query without ever writing to it

Every call to `/api/mcp/v1` is a single POST carrying a JSON-RPC 2.0 envelope; the entire operation surface is the file's own `switch` statement over `method` — `initialize`, `ping`, `tools/list`, `resources/list`, `prompts/list`, and `tools/call` — which means there is no separate URL per operation for anything to scan for a forgotten route. The server checks its own feature flag first, before even reading the request body: if `mcp` is off, it returns 404, deliberately not 403, so a probing request cannot even confirm the endpoint exists. Authentication reads a bearer token, hashes it, and resolves BOTH identity and organization from that hash inside a `security definer` database function — the client never supplies which organization it belongs to, and even if a hostile agent tried, the tool arguments have `organization_id`, `organizacao` and `tenant_id` deleted from them before the tool function ever runs, confirmed directly in the handler's own code with a comment explaining why: "the organization NEVER comes from the agent."

Because a `security definer` function does not inherit the calling user's own row-level security, the team could not simply rely on Postgres RLS the way the rest of the product does for one specific tool: `dash_social_benchmarks`, which returns market percentiles aggregated across multiple companies. Read alone, RLS would have let that function see every organization's data to compute the aggregate — so the k-anonymity check (only return a percentile computed across enough companies that no single one is identifiable from it) had to be re-implemented by hand inside the function itself, and the tool's own description states this in plain language: it "only returns rows that pass Dash's own anonymity criterion," and every row that does come back states how many companies it was computed over, specifically so a percentile from a tiny sample is never mistaken for a real market reference.

The write side is not merely unused — it is empty and tested. `WRITE_TOOLS` is a literal empty array, and a companion test scans every one of the 25 tool names for verbs that suggest changing the world (create, update, delete, pause, resume, publish, send, budget-related terms, and their Portuguese equivalents) and asserts the resulting list is empty. Anything the 8 Maestro tools expose is likewise read-only — pausing a campaign, changing a budget, publishing an ad all still require the Maestro app's own authenticated session, on purpose, so a compromised or over-broad agent key can look at ad performance but can never touch a live budget. When a tool call does fail, the raw error — which might carry a column name, a table name, or a value — is logged server-side only; the agent gets a generic, typed failure code. And a `key.readOnly` scope is enforced independently of what an agent asks for: a test confirms that even listing a hypothetical write tool by name in `allowedTools` does not make it appear for a read-only key.

What is actually built today

25 tools total over one JSON-RPC 2.0 endpoint: 17 native to Dash (funnel stages, bottlenecks, stale and aging deals, owner and source performance, loss reasons, the 3-scenario forecast, social overview, top creatives, fatigue, k-anonymized benchmarks, MMM models, incrementality experiments, data health, metric lineage) plus 8 read-only Maestro ad-spend tools.

Zero write tools — a literal empty `WRITE_TOOLS` array, independently checked by a test that scans every tool name for a write verb in English or Portuguese.

Organization scope resolved server-side from a hashed bearer key inside a `security definer` function — never accepted as a client-supplied argument, and stripped from tool arguments even if sent.

A hand-implemented k-anonymity check inside `dash_social_benchmarks` specifically because `security definer` does not inherit the caller's row-level security — every surviving row states how many companies it was computed over.

Off by default behind a feature flag, returning 404 (not 403) when disabled, so a probing request cannot distinguish "disabled" from "does not exist."

Raw errors redacted before they leave the server — logged internally with full detail, returned to the agent as a generic, typed failure code.

A benchmark query that almost identified one company (illustrative scenario, not a real client)

An agent asks for social-media engagement benchmarks in a niche segment where, this quarter, only two companies in the whole customer base have enough data to compute a percentile. Rather than returning that percentile — which would effectively be describing those two companies' own numbers relabeled as "the market" — the k-anonymity check inside the tool itself withholds that row and the response states, in the same envelope, why: too few companies pass the anonymity threshold this period.

What changes operationally

An organization can hand an AI agent real, current numbers about its own funnel, forecast, social performance and model quality without opening a write path, without the agent ever seeing another organization's data, and without a benchmark accidentally exposing one specific company's numbers under a market label. Because the surface is off by default and 404s when disabled, turning it on is a deliberate, visible decision rather than something that happens as a side effect of a deploy.

When this is not the right fit

A team hoping to let an agent pause a campaign, change a budget, or update a deal directly through this server will not find that here — every one of the 25 tools is read-only by design, and operating on Maestro or Orbit data still requires their own authenticated sessions. A team expecting the k-anonymity threshold to be configurable per organization will find a fixed, code-level rule instead — it is not a setting.

A generic API key vs. a scoped, read-only agent protocol

Handing an AI agent a generic API key that accepts an `organization_id` parameter trusts the agent, and every piece of code the agent talks to, to always send the right one — one bug, one prompt-injected argument, and that trust boundary is gone. Centriu Dash instead resolves organization scope on the server from the key itself, deletes any organization claim an agent tries to send anyway, and keeps the entire write surface at zero — so the worst a leaked key can do is read that one organization's own numbers, never write to them and never read anyone else's.

Related systems

Main system: Centriu Dash. Complementary when relevant: Centriu Maestro, Centriu Orbit.

What it does NOT do

  • Does not expose a single write tool — pausing a campaign, changing a budget, publishing a post, updating a deal all still require their owning app's own authenticated session.
  • Does not accept `organization_id`, `organizacao` or `tenant_id` as a tool argument — scope is resolved server-side from the bearer key's hash, and any such field an agent sends is deleted before the tool runs.
  • Does not return a market benchmark computed across too few companies to be anonymous — the k-anonymity check withholds that row and states why.
  • Does not confirm the MCP endpoint even exists when the feature flag is off — the response is a plain 404, not a 403 that would reveal a locked-but-present door.
  • Does not return a raw database error to an agent — the full error is logged server-side only, the caller gets a generic, typed failure code.
  • Does not let a read-only key gain a write tool by listing one in its allowed-tools set — a test confirms the tool still does not appear.

Security and governance

Every MCP call is logged with method, tool name, success, error code and duration to `dash_mcp_server_log`, scoped per organization and visible on the Agentes settings screen. Keys are stored hashed, shown once at creation and identified afterward only by a non-reversible prefix. Any personal data reachable through a tool call remains subject to Brazil's LGPD (Law No. 13,709/2018). Full detail on access control lives at /governanca and /iso.

Pricing and contracting

Available by monthly subscription, with tiered plans. Values and terms come from the official pricing table at /precos (Centriu's central source — never restated here).

Frequently asked questions

How many tools does the Dash MCP server actually expose?

25 total: 17 native to Dash (funnel, forecast, social, models, data health) and 8 read-only Maestro ad-spend tools — confirmed directly against the tool registry and its own test suite.

Can an AI agent change anything through this server?

No — the write-tool list is a confirmed empty array, and a test independently scans every tool name for a write verb to guarantee none exists.

Can an agent request another organization's data by passing a different organization ID?

No — organization scope is resolved server-side from the bearer key's own hash, and any organization-shaped field an agent sends is deleted before the tool call runs.

Is the MCP server on by default?

No — it sits behind a feature flag that is off by default, and returns a plain 404 (not 403) when disabled, so a probing request cannot tell "off" from "doesn't exist."

How does a market benchmark avoid identifying one specific company?

A k-anonymity check inside the benchmark tool withholds any percentile computed across too few companies, and every row that is returned states how many companies it was computed over.

What does Centriu Dash cost?

It is sold by subscription with a published starting price — exact current values are on the central pricing page.

See how Centriu Dash's AI-agent tool server works

Reach our commercial team directly, or leave your details below — we'll follow up with guidance for your case.

Sources

  1. Centriu Dash — public product page — Centriu, 2026-07-20 · link(primária)
  2. Centriu Dash — public factsheet (API, JSON) — Centriu, 2026-07-21 · link
  3. Law No. 13,709/2018 — Brazil’s General Data Protection Law (LGPD) — Presidência da República (Brazil), 2018-08-14 · link

Last material update on .

By · AI-assisted production, with human review