OverviewSettings Server (agency)

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 speaksWhat 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"
header
Authorizationstring

MCP API key as bearer token. Format: Bearer mcp_....

header
X-Api-Keystring

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 typelocation_idBehavior
Client-scopedsetFixed to one client. Tools operate on it; navigation tools are hidden.
Agency-widenullNot 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:

PromptArgumentsWhat it drives
weekly-client-reportclient?, days? (7)Trend, channels, top pages, conversion events, errors, coverage, contracts — with the prior window for comparison.
tracking-health-checkclient?PASS / WARN / FAIL per area: ingestion, volume, coverage, contracts, web vitals, errors, settings.
compare-clientsclients?, 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

StatusMeaning
401Missing or invalid mcp_ key
402The plan does not include MCP access
403Session doesn't belong to this key, or /mcp/c/<location_id> names a location outside the key's agency
429Rate 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.