Settings Server (agency)
A hosted MCP server exposing the full agency product surface — 173 tools across data, sessions, replays, analysis, settings, links, Airbyte, segments, insights, dashboards, canvases, destinations, contracts, tenant config, and agency-grain rollups — with a select-a-client session flow.
Overview
The Settings Server is a hosted MCP server exposing the full agency product
surface — 176 tools across 23 scope families — from any MCP-compatible AI
client. It is separate from the
Gateway (which proxies upstream tools) but authenticates with
the same mcp_ API keys and scopes.
Most tools are thin proxies of the documented REST endpoints on
/client/v1 and /builder: the single implementation of each operation lives
in the worker, so a tool's parameters, semantics, and error codes are those of
the underlying route. The per-surface REST docs are therefore the reference
for what each tool does — see the Data API,
Client Links,
Destinations,
Constraint Finder,
Correlation,
Event Contracts,
Heatmaps,
Incrementality,
Airbyte Sync,
Client Config,
Agency API, and
Builder sections.
Its defining feature: an agency connects once with an agency-wide key, calls
list_clients to see its clients, select_client to pick one, and every
subsequent tool operates on that client for the session.
Base URL
https://mythic-mcp-server-production.up.railway.app/mcp
POST /mcp speaks JSON-RPC 2.0 (Streamable HTTP) and serves two protocol eras
on the same endpoint:
| Your client speaks | What you get |
|---|---|
The initialize handshake (2025-11-25 and earlier) | A session: the response carries an Mcp-Session-Id, and select_client pins the active client to it. |
2026-07-28 (per-request _meta) | Stateless serving. There is no session to pin, so agency keys pass the client id as an argument on every call (see Stateless clients). |
Requests are routed by what they carry, not by a setting — you do not have to opt in to either era.
Pinning a client in the URL
https://mythic-mcp-server-production.up.railway.app/mcp/c/<location_id>
An agency-wide key connected to /mcp/c/<location_id> behaves exactly like a
key bound to that client: every tool operates on it, select_client is not
needed, and stateless connections need no per-call location_id. Agents cannot
change a header or URL mid-session, so this is the right place for a choice that
should never change. A location outside the key's agency answers 403. Configure
one server entry per client you want the assistant to work on.
Connect
claude mcp add --transport http mythic \
https://mythic-mcp-server-production.up.railway.app/mcp \
--header "Authorization: Bearer mcp_your_api_key"
MCP API key as bearer token. Format: Bearer mcp_....
MCP API key. Alternative to the Authorization header.
A missing, malformed, or deactivated key returns HTTP 401 with a JSON-RPC
error (code -32600).
Key types
| Key type | location_id | Behavior |
|---|---|---|
| Client-scoped | set | Fixed to one client. Tools operate on it; navigation tools are hidden. |
| Agency-wide | null | Not bound to a client. Gets list_clients / select_client; a client must be selected before client-scoped tools work — or connect to /mcp/c/<location_id>. |
Mint keys of either type in the agency Builder or the platform admin UI. Every
key also gets whoami (key type, pinned client, scopes, and per family whether
it can read, write or nothing) and search_docs (this documentation, searchable
in-session). The server's instructions at connect time say the same, so the
assistant knows what the key can do before its first call.
The select-a-client flow
// 1. Discover clients this key can operate on (agency-wide keys only)
{ "method": "tools/call", "params": { "name": "list_clients", "arguments": {} } }
// → { "clients": [ { "id": "loc_abc", "name": "Acme", "enabled": true } ] }
// 2. Pin one for the session
{ "method": "tools/call", "params": { "name": "select_client", "arguments": { "client": "loc_abc" } } }
// 3. Subsequent tools operate on loc_abc — no location_id needed
{ "method": "tools/call", "params": { "name": "create_insight", "arguments": { "name": "Top Pages", "insight_type": "table" } } }
Client-scoped keys skip steps 1–2 — they always operate on their bound client.
Calling a client-scoped tool on an agency-wide key before select_client
returns an error telling you to select a client first.
Stateless clients
On a 2026-07-28 connection there is no session, so select_client has nothing
to pin to and returns an error saying so. Use list_clients to discover ids and
pass the one you want on each call — every client-scoped tool takes it as
location_id (or tenant_key for tag/snippet/transformer tools):
{ "method": "tools/call", "params": { "name": "create_insight",
"arguments": { "location_id": "loc_abc", "name": "Top Pages", "insight_type": "table" } } }
The argument works on handshake connections too, where it overrides the pinned client for that one call — so passing it always is the portable choice. Or pin the client in the URL and never pass it.
Several clients in one call
get_event_volume_daily, get_event_trend, get_event_breakdown and
get_sessions_breakdown accept location_ids (up to 25) on agency-wide keys and
answer { "results": [{ "location_id": …, … }] }, one entry per client, a failing
client reported as { "location_id", "error" } instead of failing the call. For
whole-agency rollups use the get_agency_* tools.
Tool families & scopes
Every tool is gated by a scope on the key (family:read / family:write,
family:*, or *). An empty scopes array grants nothing — unrestricted
is the explicit ["*"]. A :write scope implies the matching :read.
list_clients, select_client, whoami and search_docs need no scope.
Only the tools the key's scopes cover are listed, so a narrow key gives the
assistant a short tool list. Calling any other Mythic tool by name answers a tool
error with code: "insufficient_scope", the required scope and the scopes
held, so the assistant can tell you exactly what to add instead of reporting
an unknown tool.
The agency rollup tools (get_agency_stats, get_agency_destination_health,
get_agency_contracts_summary, get_agency_usage, get_agency_quota) are
registered only for agency-wide keys and take agency:read. They are thin
proxies of the Agency API (/client/v1/agency), so
parameters, semantics, error codes, and the 100-location location_ids batching
are those of the REST routes. No select_client needed: the agency itself is the
scope.
Twenty-three scope families gate the tools — the full table, with what each one grants, is on the Authentication page:
people, events, sessions, replays, exceptions, heatmaps,
bigquery_export, insights, dashboards, canvases, segments,
constraints, correlation, incrementality, contracts, destinations,
settings, links, tags, snippets, transformers, airbyte, agency.
See the Tool Reference for every tool by name, the scope it needs, what it does, and the API route it proxies. That page is generated from the server's own registry, so it is never out of step with what the server serves. The same scope authorizes the proxied route when the key is used directly over HTTP.
Destination writes are validated per type (webhook, meta_capi, google_ads);
runtime secrets are never exposed. Tag/snippet/transformer writes sync to the
edge automatically — the same operations are available over REST on the
Client Config API, whose responses carry an explicit
kv_sync field.
Discovering events and enforcing their shape
An agent must never guess a client's event names — they are client-specific.
The discovery pair answers that: list_events returns every event the client
actually sends with volumes, and list_event_properties returns the property
catalog for one of them (keys, inferred types, masked samples, first/last seen).
Event contracts turn that into something enforceable. The intended order:
Discover
list_events → pick an event → suggest_event_contract infers a contract
for it from live traffic. The result is set_event_contract-ready.
Store
set_event_contract replaces that event's property set. A bare type string
means required; { type, required: false } is optional.
Check
check_event_contracts reports violations worst-first — missing_required,
null_value, type_mismatch, kept separate because they have three
different fixes. Undeclared keys are reported but are never violations, and
an event with no traffic reads no_data rather than "all fields missing".
check_event_contracts and suggest_event_contract read raw events to get their
denominator, so they default to a 1-day window and accept sample (keep
1/N) for wider ones. When a window holds too much to scan, the error names the
fix — narrow the window, pass event, or add sample.
Storing a contract only enables reporting. Nothing is ever rejected, quarantined, or dropped at ingestion because of one.
Summarizing a session with get_session_timeline
get_session_timeline returns a compact, chronological timeline of one
recorded visitor session (page journey, dwell time, form submissions, field
inputs, identify, custom events) — the LLM-ready view of a session, not the
raw rrweb replay. The connected model does the summarizing; the server runs no
LLM of its own. No PII: form/input values are excluded, only field metadata.
Its companion list_replays (a proxy of GET /client/v1/data/replays — see
the Data API) lists recorded sessions newest first, with
duration and activity counters; pass a row's session_id (a UUIDv7) to the
timeline tool. A typical agent flow: pick sessions from list_replays (e.g.
high console_errors or long duration), call get_session_timeline for each,
and summarize behavior, drop-off, and friction.
Prompts
Three prompts encode the tool sequences agencies run most, so the assistant does not rediscover them each session:
| Prompt | Arguments | What it drives |
|---|---|---|
weekly-client-report | client?, days? (7) | Trend, channels, top pages, conversion events, errors, coverage, contracts — with the prior window for comparison. |
tracking-health-check | client? | PASS / WARN / FAIL per area: ingestion, volume, coverage, contracts, web vitals, errors, settings. |
compare-clients | clients?, days? (30) | Agency-wide side-by-side from the rollups plus a one-call location_ids channel comparison. |
Rate limiting
Each key's rate_limit_per_minute (default 60) is enforced with a shared
fixed-window counter. Over the limit returns a JSON-RPC 429.
Errors
| Status | Meaning |
|---|---|
401 | Missing or invalid mcp_ key |
402 | The plan does not include MCP access |
403 | Session doesn't belong to this key, or /mcp/c/<location_id> names a location outside the key's agency |
429 | Rate limit exceeded |
Tool-level failures return { "isError": true, "content": [...] } in the result.
A call outside the key's scopes is one of them: the text is JSON with
error.code = "insufficient_scope", error.required and error.held.
Never expose mcp_ API keys in client-side JavaScript, public repositories, or
logs — they are server-side credentials. The raw key is shown only once, at creation.