---
title: Server and protocol
description: The Dashies MCP endpoint, its JSON-RPC transport, version negotiation, the methods it handles, and the shape every result arrives in.
updated: 2026-08-04
---

Everything on this page is server-level: true for every tool call, and mostly
handled by your MCP client rather than by you. Read it when you are scripting
against the server, debugging a client, or working out why a call did not arrive.

## Endpoint and transport

| Fact | Value |
|---|---|
| Endpoint | `https://mcp.dashies.ai/mcp` |
| Transport | JSON-RPC 2.0 over HTTP, streamable, stateless |
| Accepted paths | `/mcp` and `/mcp/` only |
| Accepted method | `POST` only |
| Server identity | `{ "name": "dashies", "version": "0.6.0" }` |

Anything else on the API route is refused before any tool runs:

| Condition | Response |
|---|---|
| Any other path | `404 Not Found` |
| Any method other than POST | `405 Method Not Allowed`, with an `allow: POST` header |
| A body that is not JSON | HTTP 400, with the JSON-RPC parse error below |
| No or invalid bearer token | `401`, with a `WWW-Authenticate` header pointing at the OAuth metadata |

```json
{"jsonrpc":"2.0","id":null,"error":{"code":-32700,"message":"Parse error"}}
```

The `401` is not a failure state. It is the first step of the sign-in handshake:
a client that sees it discovers the OAuth metadata, registers itself, and opens
your browser. See [Shared rules](/reference/mcp-tools/conventions#authorization).

## Protocol version negotiation

The server supports two revisions, newest first:

```json
["2025-06-18", "2024-11-05"]
```

`initialize` echoes the client's requested revision when it is one of those two,
and otherwise advertises `2025-06-18`. A client pinned to `2024-11-05` keeps
working; you do not have to configure anything.

`initialize` advertises three capabilities, each with `listChanged: false`:
`tools`, `prompts`, `resources`. It also returns the server's `instructions`
string, which is the standing guidance Dashies gives every connected AI tool.

## Methods

`handleRpc` accepts exactly these:

| Method | Purpose |
|---|---|
| `initialize` | Negotiate the protocol revision and read capabilities. |
| `notifications/initialized` | Client handshake completion. No reply. |
| `notifications/cancelled` | Client cancelled a request. No reply. |
| `ping` | Liveness. |
| `tools/list` | Every tool definition. |
| `tools/call` | Invoke one tool. |
| `prompts/list` | The named dashboard-authoring prompt templates. |
| `prompts/get` | One prompt template, filled in. |
| `resources/list` | Your dashboards as MCP resources. |
| `resources/templates/list` | The single `dashies://{handle}/{slug}` URI template. |
| `resources/read` | One dashboard body by URI. |

Anything else returns `-32601 Method not found: <method>`. Any exception inside a
handler becomes `-32000 Internal error: <message>`.

### Batches

An array body is still serviced, even though JSON-RPC batching was dropped from
MCP 2025-06-18. The members run concurrently, notifications are filtered out of
the reply, and a batch containing only notifications returns HTTP 204 with a
**null** body rather than an empty string.

## The shape of a tool result

Every tool result is a text envelope:

```json
{"content":[{"type":"text","text":"..."}]}
```

A failure is the same envelope plus `"isError": true`. There is no separate error
channel for tools: a refusal arrives as text your AI tool reads and acts on, which
is why the per-tool Errors sections quote the exact sentences.

Most tools put a machine-readable copy of their answer inside that text, fenced
between two marker lines:

```text
BEGIN_JSON
{"count":2,"connections":[...]}
END_JSON
```

Some tools additionally ride a proper `structuredContent` payload alongside the
text, for clients that read it: `list_dashboards`, `list_dashboard_versions`,
`introspect_schema`, `validate_cube_sql`, `list_connections`, `explore_data`,
`check_readiness`, `set_refresh_schedule`, and `publish_dashboard` on its body
path.

No count is given, because one stood here and went stale the moment a tool was
added. The list above is the answer; the `TOOLS` array in `mcp.ts` is where it
comes from.

No rule is given either, and that is deliberate rather than an omission. Two
attempts at one both turned out false against exceptions already written on
either side of this paragraph: `publish_dashboard` declares no `outputSchema` and
returns `structuredContent` anyway on its body path, and `list_dashboard_versions`
declares one and returns none on its empty state, which the note below says in as
many words. A set with two documented exceptions flanking it is a set to
enumerate, not to legislate over.

:::note{title="An empty result may carry no JSON block"}
`list_dashboard_versions` deliberately emits text only when a dashboard has no
snapshots: no `BEGIN_JSON`, no `structuredContent`. A parser that assumes the
block is always present will throw on the empty state rather than read it.
:::

## Tool names as your client shows them

`tools/list` returns the bare names used throughout this site. What you
see in a client depends on how the server was added:

| How it was added | Name shown |
|---|---|
| The Dashies plugin (recommended) | `mcp__plugin_dashies_dashies__<tool>` |
| A bare MCP server named `dashies` | `mcp__dashies__<tool>` |

## Resources and prompts

Beyond tools, the server exposes two smaller surfaces.

**Resources.** URI scheme `dashies://`, template `dashies://{handle}/{slug}`,
default mime type `text/html`. `resources/list` enumerates the dashboards the
current authorization can see, using the same cursor as `list_dashboards`; a
workspace-authorized connection lists that workspace's dashboards.
`resources/read` returns a body by URI, mapping its failures onto JSON-RPC codes:
an invalid URI or cursor is `-32602`, a missing resource is `-32002`, and an
infrastructure failure is `-32000`.

**Prompts.** Named authoring templates, each expanding into one user message:

| Prompt | Arguments |
|---|---|
| `refreshable_dashboard` | `connection`, `grain` (both optional) |
| `audit_my_dashboards` | none |

`refreshable_dashboard` walks the spec publish flow from `check_readiness`
through `validate_cube_sql` to `publish_dashboard` with a `spec`, and on until
the first refresh lands. A `publish_static_dashboard` template is retired:
publishing raw HTML bytes is not available, and every spec names a connection and
declares at least one dataset, so a dashboard carrying author-supplied data has no
spec that can express it.

## Other endpoints

`GET /healthz` returns `ok` as `text/plain`. Anything else on the default handler
is `404 Not Found`.

## Check it worked

`curl` the endpoint without a token. A healthy server refuses with a 401 that
tells your client where to authorize:

```bash
curl -sS -i -X POST https://mcp.dashies.ai/mcp \
  -H 'content-type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}' | head -n 20
```

Expect `HTTP/2 401` and a `www-authenticate` header. A `404` there means the URL
is wrong, and a `405` means the request was not a POST.
