# EchoRelay (remote · mcp.echorelay.dev)

Manage EchoRelay relay projects from any MCP client: lines, endpoints, keys, billing, logs, DLQ.

- Trust score: 80/100 (high trust)
- Change this week: +9
- Registry status: active
- Liveness: live
- Owner verified: no
- Last scored: 2026-08-03

## Components

- remote · `mcp.echorelay.dev`: 80/100 (this document), [markdown](https://verifymcp.io/servers/dev-echorelay-management/mcp.md), [page](https://verifymcp.io/servers/dev-echorelay-management/mcp)

## Channel facts

- Endpoint: `https://mcp.echorelay.dev`
- Transports: `streamable-http`
- Auth: `required`
- Version: `0.11.0`

## Trust breakdown

How this component scores in each security and reliability category. Every signal is checked automatically against the live server, and we only credit what we can confirm. Scores are 0–100 per category. Scoring method: https://verifymcp.io/docs/scoring (what has changed: https://verifymcp.io/docs/scoring/changelog)

Scored 2026-08-03.

- **Endpoint Security**: 91/100
  - The endpoint's TLS certificate is valid, in date, and uses a strong key.
  - Authorisation is enforced on tool calls, but the challenge carries no valid RFC 9728 metadata, so a client cannot discover where to get a token.
  - HTTPS is enforced; there's no plaintext access path.
  - The HSTS (Strict-Transport-Security) header is present.
  - DNSSEC is configured correctly; the domain's records validate against the full chain to the root.
- **Transport & Reachability**: 100/100
  - Verified streamable-http transport via a live MCP handshake.
- **Schema Quality & AI Usability**: 76/100
  - AI-judged instruction clarity (excellent).
  - Context-footprint check failed: tool/resource definitions use about 8094 tokens (~105/item across 77 items; 77 tools + 0 resources), over budget; trim descriptions and params.
  - Usage-examples check failed: none of the tools include examples.
- **Stability & Change Management**: 27/100
  - Stability observed for 8 of 30 days with no destabilising changes; credit accrues until the full window elapses.
- **Tool Coverage**: 95/100
  - 100% of tools have a non-trivial description (not blank, and not just the tool's name).
  - 85% of tool parameters carry a description.
- **Capabilities**: 100/100
  - Implements a supported MCP spec version (2025-11-25); the latest is 2026-07-28.

## Install

### Claude

```bash
claude mcp add --transport http dev-echorelay-management https://mcp.echorelay.dev/
```

### Codex

```toml
[mcp_servers.dev-echorelay-management]
url = "https://mcp.echorelay.dev/"
```

### opencode

```json
{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "dev-echorelay-management": {
      "type": "remote",
      "url": "https://mcp.echorelay.dev/",
      "enabled": true
    }
  }
}
```

### OpenClaw

```bash
openclaw mcp add dev-echorelay-management --url https://mcp.echorelay.dev/ --transport streamable-http
```

### Hermes

```yaml
mcp_servers:
  dev-echorelay-management:
    url: "https://mcp.echorelay.dev/"
```

### Other

```json
{
  "mcpServers": {
    "dev-echorelay-management": {
      "type": "http",
      "url": "https://mcp.echorelay.dev/"
    }
  }
}
```

The mcpServers block is a cross-client convention. Remote transports vary, so check your client's docs.

## Changelog

Every change recorded for this component, newest first. Days that predate change tracking, or that we cannot explain, say so: "we were watching and nothing happened" and "we were not watching" are different claims.

### 2026-08-02 (score 80, +1)

- [security] Tool “get_billing” rewrote its description, which is the text the model reads
- [functional] Schema quality: good → excellent

### 2026-08-01 (score 79, +1)

No change was recorded against any check on this day. Stability & Change Management went from 17 to 20. That category is still filling its 30-day observation window: 5 days of observed history at the previous scan, 6 at this one. The score rises as the window fills, whether or not the server changes.

### 2026-07-31 (score 78, +5)

- [functional] We updated how we score, so this day's move reflects our rubric, not a change to the server

### 2026-07-30 (score 73, 0)

- [functional] We updated how we score, so this day's move reflects our rubric, not a change to the server

### 2026-07-29 (score 73, +1)

No change was recorded against any check on this day. Stability & Change Management went from 7 to 10. That category is still filling its 30-day observation window: 2 days of observed history at the previous scan, 3 at this one. The score rises as the window fills, whether or not the server changes.

### 2026-07-28 (score 72, +1)

- [security] Tool “get_metrics” rewrote its description, which is the text the model reads

### 2026-07-27 (score 71, 0)

- [functional] We updated how we score, so this day's move reflects our rubric, not a change to the server

### 2026-07-26 (score 71)

First indexed and scored.

## MCP tools (77)

### `get_project` (~47 tokens)

Get the EchoRelay project this token is scoped to: id, slug, name, caller-facing API base URL, whether the token has edit access, and the request-log hot-tier retention window.

### `list_lines` (~31 tokens)

List the project's versioned API lines (e.g. v1, v2), each with its endpoint count.

### `list_endpoints` (~37 tokens)

List the endpoints under one line of the project.

Input parameters:

- `lineKey` (string, required): The line key, e.g. "v1".

### `get_endpoint` (~54 tokens)

Get the full JSON of one endpoint, including its targets, auth keys and attributes.

Input parameters:

- `endpointId` (string, required): The endpoint UUID.
- `lineKey` (string, required): The line key, e.g. "v1".

### `get_config` (~39 tokens)

Get the project's published relay configuration — the exact JSON the EchoRelay Framework consumes. Includes pendingPublish with its frozen, redacted config when one is scheduled.

### `config_diff` (~69 tokens)

Preview what Save & Publish (or the pending scheduled publish, if one exists) would change: added/removed/changed lines, and within each changed line the added/removed/changed endpoints and targets, plus any project-level setting change. Outbound auth secrets are redacted the same as get_config.

### `list_config_revisions` (~44 tokens)

List retained revisions for one line so a prior revision can be selected for rollback.

Input parameters:

- `lineKey` (string, required): The line key, e.g. "v1".

### `get_line_draft` (~42 tokens)

Read one line draft, its optimistic revision, preview, and schedule.

Input parameters:

- `lineKey` (string, required): The line key, e.g. "v1".

### `preview_line_draft` (~36 tokens)

Preview one line draft without publishing it.

Input parameters:

- `lineKey` (string, required): The line key, e.g. "v1".

### `publish_line_draft` (~37 tokens)

Publish one line draft now and clear only its schedule.

Input parameters:

- `draftRevision` (integer, required)
- `lineKey` (string, required)

### `discard_line_draft` (~37 tokens)

Discard every unpublished change and schedule for one line.

Input parameters:

- `draftRevision` (integer, required)
- `lineKey` (string, required)

### `discard_draft_endpoint` (~44 tokens)

Discard one endpoint change from a line draft.

Input parameters:

- `draftRevision` (integer, required)
- `endpointId` (string, required)
- `lineKey` (string, required)

### `schedule_line_publish` (~44 tokens)

Schedule one non-empty line draft. Active subscribers required.

Input parameters:

- `draftRevision` (integer, required)
- `lineKey` (string, required)
- `scheduledAt` (string, required)

### `cancel_line_publish` (~35 tokens)

Cancel one line schedule before its two-minute lock.

Input parameters:

- `draftRevision` (integer, required)
- `lineKey` (string, required)

### `rollback_config` (~35 tokens)

Publish a retained line revision immediately and clear any pending schedule.

Input parameters:

- `lineKey` (string, required)
- `revision` (integer, required)

### `get_billing` (~143 tokens)

Get the project's credit balances (paid + testing) and which ledger it settles on. `billingModel` is `prepaid` (metered against the credit balance) or `invoiceable` (an Enterprise account, settled on the contract — its credit fields read empty/zero by design, not because it is out of credits). A zero balance is not on its own a reason to stop: live traffic is never refused for an empty balance, and a free-tier project is exempt from live credit billing entirely. Only the testing pool refuses when exhausted (402 `testing_credits_exhausted`). Read this before concluding from any other field that a project cannot send.

### `set_burst_opt_in` (~133 tokens)

Enable or disable the auto-decaying burst window for live traffic and publish the change immediately. When enabled, brief spikes a little above your steady RPM cap are absorbed at the normal credit rate (no surcharge, no burst SKU) instead of being rejected. Sustained overage decays back to your steady cap; headroom returns once traffic cools below the cap or you add RPM. Burst usage is tracked so you can see when to upgrade. Default is off (steady cap is a hard 429). Requires edit access.

Input parameters:

- `enabled` (boolean, required): true to enable the burst window; false to disable.

### `set_rpm_ceiling` (~149 tokens)

Set or clear a self-set ceiling on this project's own effective live-traffic RPM (Traffic shaping) — for when your origin can't take the throughput your plan grants. Never plan-gated: the ceiling can only LOWER your effective RPM below your plan/add-on rate, never raise it, and never touches billing. Steady traffic is capped at the ceiling; if Burst Protect is on, its headroom is measured above the ceiling, not above the plan rate. The current value is on get_project as `rpmCeiling` (null = no ceiling). Requires edit access.

Input parameters:

- `rpmCeiling` (integer|null, required): A positive integer to set the ceiling, or null to clear it.

### `set_outbound_allowlist` (~177 tokens)

Narrow the project's outbound host allowlist — the hosts a target may point at. The current list is on get_project as `outboundHostAllowlist` (empty means any public host). This tool may only narrow it: `patterns` must be a strict subset of, or equal to, the current list, compared as exact strings — submitting a wildcard that would cover an existing exact host, or clearing to empty, is refused as a widen. Widening the list is web-only, in the panel. Refused when the project already has a target (inactive lines included) outside the submitted list. Requires edit access.

Input parameters:

- `patterns` (array, required): The full replacement list of allowed hosts, e.g. ["api.stripe.com", "*.internal.example.com"]. Must be a subset of the current outboundHostAllowlist.

### `create_line` (~103 tokens)

Create a new versioned line. Defaults to immediate publish; draft mode requires draftRevision 0. Requires edit access.

Input parameters:

- `commitMode` (string): Defaults to publish. Draft writes require draftRevision.
- `draftRevision` (integer): Current line draft revision, required for draft writes.
- `lineKey` (string, required): URL key, lowercase letters/digits/hyphens, e.g. "v1".
- `name` (string, required): Human-readable line name.

### `delete_line` (~78 tokens)

Delete a line and every endpoint under it, publishing the removal immediately. Requires edit access.

Input parameters:

- `commitMode` (string): Defaults to publish. Draft writes require draftRevision.
- `draftRevision` (integer): Current line draft revision, required for draft writes.
- `lineKey` (string, required): The line key, e.g. "v1".

### `activate_line` (~90 tokens)

Activate and immediately publish an inactive line so it serves traffic again. Blocked at the active-line cap — deactivate another line first. Requires edit access.

Input parameters:

- `commitMode` (string): Defaults to publish. Draft writes require draftRevision.
- `draftRevision` (integer): Current line draft revision, required for draft writes.
- `lineKey` (string, required): The line key, e.g. "v1".

### `deactivate_line` (~108 tokens)

Take a line off the data plane immediately (callers get 404) without deleting it — endpoints/targets/config are preserved and it can be reactivated later. A deliberate action; live traffic is never auto-disabled. Requires edit access.

Input parameters:

- `commitMode` (string): Defaults to publish. Draft writes require draftRevision.
- `draftRevision` (integer): Current line draft revision, required for draft writes.
- `lineKey` (string, required): The line key, e.g. "v1".

### `duplicate_line` (~149 tokens)

Copy a line and all its endpoints/targets/rules into a new line key — build v2 from v1. The copy is saved inactive and remains off the data plane; edit it, then activate_line when ready. Requires edit access.

Input parameters:

- `commitMode` (string): Defaults to publish. Draft writes require draftRevision.
- `draftRevision` (integer): Current line draft revision, required for draft writes.
- `lineKey` (string, required): The source line key to copy.
- `name` (string): Optional name for the copy (defaults to "<source name> (copy)").
- `newLineKey` (string, required): New line key for the copy, e.g. "v2".

### `set_docs_shared` (~153 tokens)

Turn the line's public OpenAPI docs on or off. When enabled, the line's spec is published at docs.{baseDomain}/{slug}/{lineKey} (and …/openapi.json); when disabled those URLs return 404. The `docsShared` field on each line read (list_lines) reflects the current state. Requires edit access.

Input parameters:

- `commitMode` (string): Defaults to publish. Draft writes require draftRevision.
- `draftRevision` (integer): Current line draft revision, required for draft writes.
- `enabled` (boolean, required): true to publish the line's public docs; false to take them down.
- `lineKey` (string, required): The line key, e.g. "v1".

### `create_endpoint` (~134 tokens)

Create an endpoint under a line. Defaults to immediate publish; draft mode requires draftRevision. scheduledAt stages the line draft then schedules it. Requires edit access.

Input parameters:

- `commitMode` (string): Defaults to publish. Draft writes require draftRevision.
- `draftRevision` (integer): Current line draft revision, required for draft writes.
- `endpoint` (object, required): An EchoRelay endpoint. Round-trips with the JSON returned by get_endpoint.
- `lineKey` (string, required): The line key to create the endpoint under.
- `scheduledAt` (string): Compatibility alias: stage this endpoint in its line draft then schedule that line.

### `update_endpoint` (~124 tokens)

Update an endpoint immediately or in its line draft. The patch is merged field-by-field. Requires edit access.

Input parameters:

- `commitMode` (string): Defaults to publish. Draft writes require draftRevision.
- `draftRevision` (integer): Current line draft revision, required for draft writes.
- `endpointId` (string, required): The endpoint UUID.
- `lineKey` (string, required): The line key.
- `patch` (object, required): Partial endpoint document — same shape as create_endpoint, every field optional.
- `scheduledAt` (string): Compatibility alias: stage this update then schedule its line.

### `delete_endpoint` (~87 tokens)

Delete an endpoint and its targets, publishing the removal immediately. Requires edit access.

Input parameters:

- `commitMode` (string): Defaults to publish. Draft writes require draftRevision.
- `draftRevision` (integer): Current line draft revision, required for draft writes.
- `endpointId` (string, required): The endpoint UUID.
- `lineKey` (string, required): The line key, e.g. "v1".

### `default_endpoint_template` (~60 tokens)

Return a canonical minimal-valid endpoint JSON for a fresh line. POST it verbatim to create_endpoint to land an immediately-working mock endpoint, then customise. Read-only.

Input parameters:

- `lineKey` (string, required): The line key, e.g. "v1".

### `dry_run_endpoint` (~127 tokens)

Validate an endpoint document without writing it. Returns either {ok: true, resolved: <Framework config slice>} or {ok: false, errors: {...}}. Pass endpointId to dry-run a PATCH against an existing endpoint; omit it to dry-run a create. No DB write, no queue entry, no billing.

Input parameters:

- `endpoint` (object, required): Endpoint document (for create dry-run) or PATCH body (when endpointId is supplied).
- `endpointId` (string): Optional: dry-run a PATCH against this endpoint UUID.
- `lineKey` (string, required): The line key.

### `list_members` (~34 tokens)

List accepted and pending members of this project. Shows name/email, role, and whether the invite has been accepted. Owner only.

### `invite_member` (~72 tokens)

Invite a person by email to collaborate on this project. Returns the new invitation record; the invitee receives an email with an accept link. Enforces the seat cap for the plan tier. Owner only.

Input parameters:

- `email` (string, required): Email address to invite.
- `role` (string, required): Role to assign on acceptance.

### `remove_member` (~64 tokens)

Remove a member (accepted or pending) from this project by their member ID or email. Owner only.

Input parameters:

- `email` (string): Email address (alternative to memberId — removes the first matching member).
- `memberId` (string): Member record UUID (from list_members).

### `set_member_role` (~99 tokens)

Change a member's role between editor, viewer, and billing. Identify the member by their member ID or email (from list_members); the project owner's own role cannot be changed. Returns the updated member record. Owner only.

Input parameters:

- `email` (string): Email address (alternative to memberId — targets the first matching member).
- `memberId` (string): Member record UUID (from list_members).
- `role` (string, required): The new role.

### `resend_invite` (~84 tokens)

Re-send the invitation email for a still-pending invite, by member ID or email (from list_members). The original accept link is reused. Already-accepted members are rejected. Owner only.

Input parameters:

- `email` (string): Email address (alternative to memberId — targets the first matching pending invite).
- `memberId` (string): Member record UUID (from list_members).

### `list_api_keys` (~26 tokens)

List the project's inbound API keys (metadata only — the secret plaintext is never returned).

### `create_api_key` (~174 tokens)

Mint an inbound relay (data-plane) API key for callers to send traffic to this project. Its `er_live_` or `er_test_` secret authenticates relay requests; it cannot call management MCP or REST. Returns the plaintext exactly once — store it now, it cannot be retrieved later. Editor + owner; subject to the project key policy (see get_key_policy).

Input parameters:

- `mode` (string, required): live → paid pool, test → testing pool (rate-limited to the plan's live rate).
- `name` (string, required): Human-readable label, up to 120 characters.
- `ttlDays` (integer|null): Lifetime in days. Omit to use the project's defaultKeyTtlDays from get_key_policy. Null = never expires (owner-only unless editorsMayCreateNonExpiringKeys is set).

### `rotate_api_key` (~66 tokens)

Mint a linked successor for an existing key. Returns the successor plaintext exactly once — store it now. The predecessor stays valid through its overlap window so callers can swap without downtime. Owner anywhere, editor on own keys.

Input parameters:

- `keyId` (string, required): UUID of the key to rotate.

### `reveal_rotation_successor` (~91 tokens)

One-shot retrieval of the plaintext for a successor minted by the auto-rotation scheduler (not by rotate_api_key — that returns its own plaintext directly). Decrypts the at-rest ciphertext, returns it once, then destroys it; a second call errors. Only available while the predecessor still has an unrevealed successor.

Input parameters:

- `keyId` (string, required): UUID of the predecessor key whose successor to reveal.

### `revoke_api_key` (~45 tokens)

Manual hard revoke. The key stops authenticating on the next request. Owner anywhere, editor on own keys.

Input parameters:

- `keyId` (string, required): UUID of the key to revoke.

### `get_key_policy` (~35 tokens)

Returns the project's key policy: defaultKeyTtlDays, maxKeyTtlDays, editorsMayCreateNonExpiringKeys.

### `set_key_policy` (~78 tokens)

Owner-only. Edit the project's key policy. Only fields present in the patch are touched. Editors calling this get an error — use get_key_policy to read.

Input parameters:

- `defaultKeyTtlDays` (integer|null)
- `editorsMayCreateNonExpiringKeys` (boolean)
- `maxKeyTtlDays` (integer|null)

### `list_credentials` (~57 tokens)

List the project's saved credentials — reusable target-auth secrets that can be referenced from any number of targets instead of entering auth inline per target. `auth` is redacted (secret fields never returned, even encrypted — see create_credential for why).

### `create_credential` (~290 tokens)

Save a new reusable credential, sealed with the project's encryption key at write time. Neither this call nor any later read ever returns the secret back — reference it from a target by id (see create_endpoint/update_endpoint's target.credentialVaultEntryId) instead of copying the secret around. `auth.type` selects which fields apply: bearer→token, basic→username+password, apiKeyHeader→headerName+key, apiKeyQuery→paramName+key, publicPrivateKey→secret+key. `destinationHost` is required and permanent: the credential is only ever sent to that host, over https, and no later call can re-aim it.

Input parameters:

- `auth` (object, required): The credential's auth shape.
- `destinationHost` (string, required): The only host this credential may be sent to, e.g. "api.stripe.com", or a single-label wildcard, e.g. "*.stripe.com" (matches api.stripe.com, not stripe.com and not a.b.stripe.com). Host only — no sc…
- `name` (string, required): Human-readable label, up to 120 characters.

### `update_credential` (~202 tokens)

Rename a credential and/or rotate its secret, publishing the change immediately so every target that uses this credential picks it up right away. Omit a secret field (or the whole `auth` object) to keep the existing value — the vault can never show a secret back to confirm it, so leaving it out means "unchanged," not "cleared." The destination host cannot be changed here: create a new credential for a new destination.

Input parameters:

- `auth` (object): Same shape as create_credential. Omit to keep the existing auth entirely.
- `credentialId` (string, required): UUID of the credential (from list_credentials).
- `destinationHost` (string): Permanent — may only restate the credential's current host (omit it, or send back what list_credentials reported). Any other value is refused; a credential is never re-aimed, it is replaced.
- `name` (string): New name. Omit to keep the existing one.

### `delete_credential` (~58 tokens)

Delete a saved credential. Blocked (not a permission or not-found error — a validation error) while any target still references it; repoint or remove those targets first.

Input parameters:

- `credentialId` (string, required): UUID of the credential to delete.

### `list_requests` (~295 tokens)

List inbound API requests the relay has processed for this project, newest first. Hot-tier window is the project's requestLogRetentionDays (see get_project). Optional filters narrow the result. Returns {total, limit, offset, rows[]} where each row has ts (unix ms), requestId, method, path, status, durationMs, targets, creditsDeducted, authType, errorMessage, bytesSent, configVersion (the published config that authorised the request; 0 means unstamped), and closeReason (for a streaming request: complete / abortedOrError / byteCap / noValue; empty for non-stream requests).

Input parameters:

- `limit` (integer): Page size. Default 50.
- `method` (string): HTTP method (POST, GET, …). Case-insensitive.
- `offset` (integer): Page offset. Default 0.
- `path` (string): Substring of the request path (e.g. "/webhook"). LIKE-matched.
- `since` (integer): Lower bound on ts (unix ms). Inclusive.
- `status` (integer): Exact HTTP status code (e.g. 404).
- `targetOutcome` (string): Narrow to requests that produced at least one per-target attempt with this outcome. Use "dlq" to find requests with at least one target that ended up dead-lettered.
- `until` (integer): Upper bound on ts (unix ms). Inclusive.

### `get_request` (~111 tokens)

Get one inbound request envelope + every per-target attempt the Consumer made on the way out (success / permanent_failure / retry_scheduled / dlq). The request and each attempt carry configVersion, the published config that authorised them; 0 means unstamped. Scoped to this project; returns null if no matching row in the hot tier (the project's requestLogRetentionDays window — see get_project).

Input parameters:

- `requestId` (string, required): UUID returned by the relay in 202 responses or visible in list_requests rows.

### `get_metrics` (~425 tokens)

Aggregate request metrics for this project over a time window — volume, error rate and p50/p95/p99 latency, bucketed for charting. By default the series cover inbound requests; pass `targetId` to slice over one outbound target's per-attempt rows instead (answers "which target is degraded?"); pass `endpointId` to slice over inbound rows for a single endpoint (answers "is this endpoint receiving traffic / erroring?"). Pass at most one of targetId / endpointId. Free plans see "hour" and "day"; "week" and "month" require an active paid subscription on the project owner and are silently downgraded to "day" otherwise (the response includes `planGated: true` when that happens). Returns `{window, requestedWindow, planGated, scope, targetId?, endpointId?, bucketSeconds, buckets[], volume[], errors[], totalVolume, totalErrors, errorRate}` plus latency series whose keys depend on scope: an inbound read (project-wide, or `endpointId`) carries `relayP50[]/relayP95[]/relayP99[]` for our own time and `targetP50[]/targetP95[]/targetP99[]` for the destination's, never blended; a `targetId` read carries `p50[]/p95[]/p99[]` for that target's attempts — series arrays are dense (one point per bucket, zero-filled when no traffic).

Input parameters:

- `endpointId` (string): Optional endpoint UUID (from list_endpoints / get_endpoint). When set, the series aggregate inbound request rows for that endpoint only. Mutually exclusive with targetId.
- `targetId` (string): Optional outbound target UUID (from get_endpoint targets). When set, the series aggregate the per-attempt rows for that target instead of the project-wide inbound rows.
- `window` (string): Time horizon. Defaults to "day". "week"/"month" require a paid plan; otherwise the server returns "day" with planGated=true.

### `list_dlq` (~319 tokens)

List entries currently in this project's dead-letter queue, newest first. One inbound request fans out per-target, so a single failed request may produce several DLQ entries with different `targetId`s. Returns `{total, limit, offset, rows[], evictedCount}` where each row has `id` (Redis stream id), `requestId`, `targetId`, `configVersion` (the published config that authorised the delivery; 0 means unstamped), `failureReason`, `failedAttempts`, `failedAt`, `payload` (the original Consumer queue entry JSON); `evictedCount` is the lifetime count of entries the queue's capacity cap discarded before they could be triaged. DLQ entries — including the original request body and headers — are kept for up to 30 days from the failure time or until cleared, then purged automatically (or discarded early past capacity — see `evictedCount`); they are never written to a database. get_request still answers what happened to a purged/evicted/discarded entry's inbound request for the project's requestLogRetentionDays window, independent of whether the DLQ row itself still exists. `requestId` is the durable handle across a retry: an entry's own `id` changes every time it is replayed and later dead-letters again, so get_dlq_entry accepts a `requestId` lookup as well as `id`.

Input parameters:

- `limit` (integer): Page size. Default 50.
- `offset` (integer): Page offset. Default 0.

### `get_dlq_entry` (~255 tokens)

Get DLQ entries by Redis stream id (`{ms}-{seq}`, as returned by list_dlq's `id`) or by `requestId`. `requestId` is the durable handle — stable across a retry, unlike `id`, which changes every time an entry is replayed and later dead-letters again — and returns every fanned-out target's entry for that inbound request (one request can fan out to N targets, and several may dead-letter); `id` returns at most one. Each entry includes configVersion (the published config that authorised the delivery; 0 means unstamped). Errors if nothing matches this project — purged, retried, or discarded entries age out the same as any other.

Input parameters:

- `id` (string): Redis stream id, e.g. "1717930000000-0" (as returned by list_dlq). Changes on every retry — prefer requestId to track an entry across replays.
- `requestId` (string): The UUID the relay returned in its 202, or from list_requests / list_dlq rows. Stable across retries. Returns every fanned-out target's DLQ entry for this request. Provide exactly one of id / request…

### `retry_dlq_entry` (~118 tokens)

Re-enqueue a DLQ entry to the main relay stream and delete it from the DLQ. A replay is billed like any relay — it charges the entry's original credit cost to the project before re-sending; if the balance is too low the entry stays in the DLQ and this returns an insufficient-credits error (top up and retry). Succeeds if the entry exists, errors if it's already gone (retried or discarded). Requires edit access.

Input parameters:

- `id` (string, required): Redis stream id from list_dlq.

### `discard_dlq_entry` (~39 tokens)

Permanently delete a DLQ entry without retrying. Requires edit access.

Input parameters:

- `id` (string, required): Redis stream id from list_dlq.

### `list_subscription_plans` (~47 tokens)

List the available subscription plans (Free, Pro, Scale) with monthly EUR price, credit allowance, per-project RPM cap, and how many active projects the plan includes. Read-only.

### `list_credit_packs` (~38 tokens)

List the prepaid credit packs the project owner can purchase. Read-only. Identify a pack to start_topup by its `eurCents`.

### `list_addons` (~69 tokens)

List the optional add-on subscriptions (RPM upgrades, extra team seats, extended log retention, extra file-delivery storage in +5 GB blocks, …). Each entry is monthly, renews every 30 days until cancelled, and carries `scope` (per_account or per_project). Read-only.

### `get_subscription` (~59 tokens)

Get the project owner's current subscription: plan, active flag, period start/end, cancel-at-period-end flag, external Paddle subscription id, and whether a payment method is on file at the provider. Owner-only; requires a token minted with the billing scope.

### `preview_plan_change` (~89 tokens)

Preview what would happen if change_plan were called with this plan: the immediate-charge amount (cents), the next-invoice amount, the effective date, and the kind (`applied` for in-cycle upgrade, `scheduled` for at-period-end downgrade / cancel). Owner-only; requires a token minted with the billing scope.

Input parameters:

- `plan` (string, required): Target plan to preview a switch to.

### `start_subscription` (~73 tokens)

Begin a new Pro or Scale subscription for the project owner. Returns `{checkoutUrl, plan}` — surface the URL to the human for approval. Errors if the owner is already on an active paid plan (use change_plan instead). Owner-only; requires a token minted with the billing scope.

Input parameters:

- `plan` (string, required)

### `change_plan` (~96 tokens)

Switch the project owner between Pro and Scale, or cancel the subscription (effectively dropping to Free at period end). Returns `{kind, newPlan, effectiveAt, applied}` — `applied=true` means Paddle was charged immediately, otherwise the change is queued for the current period end. Use preview_plan_change first to see the dollar impact. Owner-only; requires a token minted with the billing scope.

Input parameters:

- `plan` (string, required)

### `cancel_subscription` (~93 tokens)

Schedule the project owner's subscription for cancellation at the end of the current billing period. Returns `{kind: 'scheduled', newPlan: 'free', effectiveAt}`. Add-ons keep renewing while the plan is active; after it ends they stop renewing and each already-paid add-on cycle stays active until its own end date (use unsubscribe_addon to stop one sooner). Owner-only; requires a token minted with the billing scope.

### `start_topup` (~106 tokens)

Begin a credit-pack top-up for the project owner. Pass the pack`s `eurCents` (from list_credit_packs). Returns `{checkoutUrl, credits, eurCents, directCharge}` or `{transactionId, credits, eurCents, directCharge:true}` on subsequent MCP purchases. Owner-only; requires a token minted with the billing scope.

Input parameters:

- `eurCents` (integer, required): Pack price in EUR cents, matching a pack returned by list_credit_packs.

### `subscribe_addon` (~188 tokens)

Subscribe the project owner to a monthly add-on (RPM upgrade, team seat, extended log retention, extra file storage in +5 GB blocks, …). Charges the saved payment method immediately, then auto-renews every 30 days until `unsubscribe_addon` — or until the subscription ends, after which the already-paid cycle stays active to its own end date and stops without further charges. Requires an active Pro/Scale subscription and a saved PM. RPM upgrades (rpm_*) are per-project and apply to THIS project automatically. Returns `{addonKey, project, monthlyEurCents, transactionId, activatedAt, nextRenewalAt}`. Owner-only; requires a token minted with the billing scope.

Input parameters:

- `addonKey` (string, required): Stable key from list_addons (e.g. "team_member", "rpm_2000", "log_retention", "file_storage").

### `unsubscribe_addon` (~67 tokens)

Cancel an active add-on subscription. Stops future charges; the current cycle stays active until expiry. RPM upgrades are cancelled for THIS project. Returns `{addonKey, project, cancelledAt}`. Owner-only; requires a token minted with the billing scope.

Input parameters:

- `addonKey` (string, required)

### `resume_addon` (~123 tokens)

Resume (un-cancel) a pending-cancel add-on whose paid-through cycle has not yet ended. Clears the cancellation so the add-on renews normally at the next cycle boundary. No new charge — the cycle was already paid. Stackable add-ons cannot be resumed; buy a new unit instead. Returns {addonKey, project, nextRenewalAt, resumed}. Owner-only; requires a token minted with the billing scope.

Input parameters:

- `addonKey` (string, required): Add-on key to resume (e.g. "log_retention", "rpm_5000").

### `downgrade_addon` (~122 tokens)

Schedule a tier downgrade for a non-stackable add-on (RPM tier). The current tier stays active until `nextRenewalAt`; at that point the lower tier is charged and activated. No mid-cycle charge. Use list_addons to find available keys. Returns {addonKey, targetAddonKey, project, effectiveAt}. Owner-only; requires a token minted with the billing scope.

Input parameters:

- `addonKey` (string, required): Current active add-on key to downgrade from.
- `targetAddonKey` (string, required): Target (lower) add-on key to downgrade to.

### `list_active_addons` (~83 tokens)

List the project owner`s currently-active add-ons. Returns `[{addonKey, project, monthlyEurCents, activatedAt, nextRenewalAt, consecutiveFailures}, ...]`. RPM upgrades are per-project; `project` is the slug they apply to (null for account-wide add-ons). Owner-only; requires a token minted with the billing scope.

### `list_audit_events` (~275 tokens)

List project audit-log entries, newest first. Captures who changed what — lines, endpoints, targets, API keys. Outbound-target `auth.token` / `auth.password` are redacted in the diff per the same policy used for endpoint reads. Retention is the `auditRetentionDays` advertised on get_project (default 365 days); rows older than that are purged by the cleanup job. Returns `{total, limit, offset, retentionDays, rows[]}` where each row has `id`, `createdAt`, `actor` (email or null), `action`, `entityType`, `entityId`, `entityLabel`, `diff`.

Input parameters:

- `action` (string): Exact action match, e.g. "endpoint.updated", "target.created", "key.revoked".
- `entityId` (string): Exact entity id (UUID or stream id depending on entityType).
- `entityType` (string): Exact entity type, e.g. "endpoint", "target", "line", "key".
- `limit` (integer): Page size. Default 50.
- `offset` (integer): Page offset. Default 0.
- `since` (integer): Lower bound on createdAt (unix ms). Inclusive.
- `until` (integer): Upper bound on createdAt (unix ms). Inclusive.

### `list_projects` (~78 tokens)

List the projects on your account (archived included), each with `id`, `slug`, `name`, `apiBaseUrl` and `archived`. Requires an ACCOUNT-scoped token (one minted with no project) and the `read` scope. A project-scoped token cannot call this — use it on its own project's tools instead.

### `create_project` (~163 tokens)

Create a new project on your account so an agent can bootstrap from a fresh account. The slug is derived from the name and validated server-side (format, reserved words, uniqueness). Each plan includes a fixed number of active projects (free tiers one; paid plans more — see list_subscription_plans); at the limit this errors — if an existing project can host this integration, skip create_project and call create_project_token against it instead of adding another. Requires an ACCOUNT-scoped token and the `config` scope. Returns the created project ({id, slug, name, apiBaseUrl, archived}); call create_project_token next to mint a token for it.

Input parameters:

- `name` (string, required): Human-readable project name (1–100 chars). The slug is derived from it.

### `rename_project` (~90 tokens)

Rename a project you own (the display name only — the slug, which is the identity in URLs / Redis, never changes). Requires an ACCOUNT-scoped token and the `config` scope. 404-equivalent error when you do not own the slug.

Input parameters:

- `name` (string, required): The new display name (1–100 chars).
- `slug` (string, required): The slug of a project you own.

### `archive_project` (~99 tokens)

Archive a project you own. Reversible: the project + slug persist and credits are kept, but the data plane drops the tenant (callers get 404). Idempotent. Fails with an error if the project has archive protection enabled — that is a support-only unlock, not something this call can override. Requires an ACCOUNT-scoped token and the `config` scope.

Input parameters:

- `slug` (string, required): The slug of a project you own.

### `protect_project` (~93 tokens)

Turn on archive protection for a project you own: archive_project (on every surface — panel, REST, and this tool) then refuses until it is lifted. Idempotent. Permanent from here — there is no tool or API call to turn it back off; only contacting support can. Requires an ACCOUNT-scoped token and the `config` scope.

Input parameters:

- `slug` (string, required): The slug of a project you own.

### `unarchive_project` (~85 tokens)

Restore an archived project you own to the data plane. Restoring consumes one of your plan's active-project slots, so at the limit this returns an error telling you to archive another project or upgrade. Idempotent for an already-active project. Requires an ACCOUNT-scoped token and the `config` scope.

Input parameters:

- `slug` (string, required): The slug of a project you own.

### `create_project_token` (~283 tokens)

Mint a PROJECT-scoped management token (`er_mcp_`) for MCP and REST; it cannot authenticate relay traffic. Use it after create_project to configure a fresh project, or for any project you already own. Attenuated by design: the scopes must be a subset of THIS token's own grant (`read` is always included), expiry is mandatory (1–90 days, default 30, never "never"), and the minted token — being project-scoped — can never mint tokens itself. `spend` is human-granted only: no token, of any scope, can mint one carrying it — mint a spend-scoped key from the project's panel instead. Requires an ACCOUNT-scoped token and the `config` scope. Returns the plaintext exactly once; only its hash is stored.

Input parameters:

- `expiresInDays` (integer): Days until the minted token expires. Default 30. API-minted tokens always expire.
- `label` (string, required): Human-readable token label (1–100 chars).
- `scopes` (array): Scopes for the minted token — must be a subset of this token's own scopes. `read` is always granted. `spend` and the legacy `billing` scope are never mintable here — panel-only. Default: read + confi…
- `slug` (string, required): The slug of a project you own.

### `list_project_tokens` (~79 tokens)

List the API tokens scoped to a project you own (any holder): id, label, scopes, createdAt, lastUsedAt, expiresAt, revokedAt — never the secret (only its hash is stored). Requires an ACCOUNT-scoped token and the `read` scope.

Input parameters:

- `slug` (string, required): The slug of a project you own.

### `revoke_project_token` (~113 tokens)

Revoke a PROJECT-scoped API token on a project you own — any holder's, not just your own (the account-owner kill switch for a leaked or retired integration token). Soft-revoke: the row stays for traceability and the token is rejected immediately. Idempotent. Requires an ACCOUNT-scoped token and the `config` scope.

Input parameters:

- `slug` (string, required): The slug of a project you own.
- `tokenId` (string, required): The token UUID (from create_project_token or list_project_tokens).

## Diagnostics

Captured diagnostic sections: TLS, DNSSEC, Authorisation, Transports. The full working is on the page: https://verifymcp.io/servers/dev-echorelay-management/mcp#diagnostics

## Score history

- 2026-08-03: 80
- 2026-08-02: 80
- 2026-08-01: 79
- 2026-07-31: 78
- 2026-07-30: 73
- 2026-07-29: 73
- 2026-07-28: 72
- 2026-07-27: 71
- 2026-07-26: 71

## Links

- Remote endpoint: https://mcp.echorelay.dev/
- Authorisation metadata: https://mcp.echorelay.dev/.well-known/oauth-protected-resource
- Changelog RSS feed: https://verifymcp.io/servers/dev-echorelay-management/mcp/changelog.xml
- Changelog JSON feed: https://verifymcp.io/servers/dev-echorelay-management/mcp/changelog.json
- HTML version of this page: https://verifymcp.io/servers/dev-echorelay-management/mcp
