# desk > A desk that reads a team's Slack channel, keeps a record of what was reported, asks when a report does not say enough to act on, and hands the work to a coding agent over MCP. Items are numbered D-n. The MCP endpoint is https://desk.miranet.work/mcp — streamable HTTP, stateless, POST only. If you are an agent about to call these tools, read the silent failure below before your first call. It returns a successful result, so it will not tell you it went wrong. ## One silent failure - **`check_desk` reports failure as a success.** `status: 'working'` means the turn is still running: wait and ask again, do not assume nothing happened. `status: 'failed'` means the turn died, and it deliberately carries no `actions` key at all — a throw may have committed changes before dying and nothing recorded which. Branch on `status`, not on `isError`. That is the `requestId` form; the no-id form carries no `status` at all. ## Tools Eight, all of them `read`-scoped — nothing here takes a `write` scope, and nothing here changes the backlog directly. - `search_items(status?, kind?, product?, priority?, query?, limit?)` — `query` is a case-insensitive substring match against title or body. `status`, `kind` and `product` take ONE value each, not a list. Each row carries `id`, `display` (`D-n`) and `product`. `limit` is 1–200, default 50, sorted by most recent activity — there is no offset and no cursor, so 200 is a hard per-call ceiling. - `get_item(itemId)` — `itemId` is `D-23`, the old `ND-23` (any case), or the uuid. One item with its `product`, full event log, both message visibilities, every linked file, and `version`. Nothing at the reference returns `item: null` with the text `no item at `. - `get_signals()` — no arguments. Open signals only. Each names a subject: an item, or a knowledge entry whose sources are stale. - `get_knowledge(mode, products?, keys?, query?)` — `mode` is required and there is no call that returns everything. **Start with `mode: "index"` and `products: [...]`** (names from the product list in the server's instructions, also in `get_knowledge`'s own description): key, title, area, product and `bodyChars` per entry, no bodies, plus a total per product. Then `mode: "keys"` with `keys: ["..."]`; a key that names nothing comes back in `missingKeys`. `mode: "query"` takes `products` and `query`, a literal word match. A mode without its values, or with another mode's, fails with `invalid_arguments` naming the field. The repository you work in hints at a product; it does not limit which products you may read. - `answer(question)` — returns a `shape` field naming one of three shapes, and `shape` is what you branch on: `from_record` (text, itemNumbers, facts), `from_knowledge` (text, knowledgeKeys, staleKeys, knowledge) or `not_known` (text, missing). "Not known" is a real answer, not a failure. - `tell_desk(text, files?)` — see below. - `check_desk(requestId?)` — see below. - `request_file_upload(mimetype, originalName?)` — see below. Statuses: `new`, `needs_info`, `open`, `in_progress`, `blocked`, `dev_complete`, `done`, `declined`, `duplicate`. Terminal: `done`, `declined`, `duplicate`. `blocked`, `in_progress` and `dev_complete` are live states. `dev_complete` means finished and committed, not yet in users' hands. Kinds: `unclassified`, `bug`, `issue`, `idea`, `question`, `task`. Products: the names in the product list the server's instructions give when you connect (`get_knowledge`'s description carries the same list), plus `unknown` — carried on items and on knowledge entries. `kind` says what shape a report is; `product` says whose. `unknown` on an item means nobody has placed it yet. `search_items` filters on it, and both a search row and `get_item` return it. `priority` is free text a person sets in the portal — there is no enum for it anywhere, the desk never sets it, and most items have none. `search_items` and `get_item` refuse an argument they do not know. The result is `isError: true` with text like `MCP error -32602: Input validation error: Invalid arguments for tool search_items: [ { "code": "unrecognized_keys", "keys": [ "text" ], … } ]`. Until 2026-10-17 the free-text filter was `text`, and an unknown key was dropped silently. ## Resources - `knowledge://` — one curated entry as markdown. Enumerated in `resources/list`. - `file://` — what a file SAYS: a document as markdown, an image as a generated description, text as itself. A description is an interpretation, not the picture. Before the desk has been told about the file, this returns a status sentence in place of content — reserved, bytes never uploaded, extraction still running — never an empty string. - `file:///blob` — the bytes inline, up to an 8 MiB ceiling; past it you get a download URL instead and the answer says so. - `file:///url` — a short-lived presigned download link. ## You do not write. You tell the desk. `tell_desk(text)` is your whole write surface. Say what happened in your own words and the desk decides what it means for the backlog — what to change, whether to ask the reporter, whether to move a ticket on. It returns a `requestId` and a `conversationId` immediately and never an answer: the turn runs on a queue. Collect the reply with `check_desk(requestId)`, which on `status: 'done'` gives you the reply plus a list of what the desk actually DID, so you can tell "closed it" from "decided not to". `check_desk()` with no id hands over anything the desk has said to you unprompted — ONCE. Those messages are marked read as they are handed over, so a second call does not return them. Keep what it gives you; there is no way to ask again. That form answers `{unread: [...]}`, with no `status` key in it. Consequences of that: - You cannot move a status, set a priority, merge a duplicate, write an internal note, link a branch or address the reporter directly. Name what you want in your `tell_desk` text; the desk decides. - A status change still moves the status emoji on the report message, and in the same turn the desk may post to the reporter's Slack thread — in its own words, after reading you. - Finished work normally lands at `dev_complete`, and the release that ships it is what closes it to `done`. You do not tell it which. - Nothing you send takes an `expectedVersion`. Optimistic concurrency still guards the desk's own writes; you never supply a version. `files` on `tell_desk` is for text documents you already hold — `[{name, mimetype, text}]`, the CONTENTS as text, because the desk cannot fetch them. Total text across them is capped at 60,000 characters and an overrun is refused with `files_too_large`, never truncated. ## Sending bytes 1. `request_file_upload(mimetype)` returns a `name` like `df_7k3x9m2p8qvw.png`, a short-lived `uploadUrl` and `expiresAt`. The URL lives 15 minutes, and a PUT that failed part-way can be retried against the same URL until then. A stored file may be at most 25 MiB — that ceiling is checked when `tell_desk` names the file, and an over-size file is deleted rather than kept. 2. PUT the bytes to that URL with **exactly** the mimetype you asked for — it is signed into the URL and R2 refuses anything else. The type is lowercased and any `; charset=…` is stripped before signing, so ask for `image/PNG` and PUT `image/png`. 3. Mention the name in your next `tell_desk` text and say what it shows: "df_7k3x9m2p8qvw.png is the settings screen after the fix". There is no file argument for it. The desk finds the name in your sentence and works out from the surrounding words which ticket it belongs to. A name with nothing said about it is a file nobody can file. Accepted types, and nothing else: `image/png`, `image/jpeg`, `image/gif`, `image/webp`, `image/heic`, `application/pdf`, `text/plain`, `text/markdown`, `text/csv`, `text/html`, `application/json`. `get_item` returns files as `{name, originalName, messageId, visibility, contentType, bytes, textStatus}` — never bytes and never a URL. The `name` is the whole identifier and is how you read it as a resource. `textStatus` is `pending` | `running` | `ready` | `failed` | `skipped`: `pending`/`running` means wait, `skipped` means nothing here reads that kind of file (settled), `failed` means the read failed and will not be retried on its own. ## Rules that will refuse you - `insufficient_scope` means your credential lacks `read`, which every tool here requires. Stop and say so. - `write` gates nothing you call. It decides whether the DESK may change anything on your behalf after a `tell_desk`. With a read-only grant the desk will answer you and move nothing. It is the most consequential thing on the consent screen. - OAuth is the only credential that reaches `/mcp`. A pre-minted `ndsk_…` key is rejected by the OAuth layer with 401 `invalid_token` — "External access token is not bound to the configured resource" — before any tool runs, so it cannot read either. The `no_person` refusal the tools still carry, for a credential with nobody behind it, is not something you will meet here. - `tell_desk` file refusals, each meaning something different: `unknown_file` is a typo, `never_uploaded` means run the PUT, `upload_window_closed` means reserve again, `not_your_file` and `file_too_large` mean stop. `request_file_upload` refuses with `unsupported_type` or `token_exhausted`; `check_desk` with `unknown_request`, `not_yours` (another person's request) or `no_person` (a key not tied to a person). - `internal_error` is anything unexpected. The real exception is logged server-side and never shown to you; do not try to parse it. - An **authenticated** GET on the MCP endpoint returns 405 with `Allow: POST`. Without a token the OAuth layer answers 401 with `WWW-Authenticate: Bearer` before the server is reached. Either way there is no standalone SSE stream and no session — the server is stateless. - Authentication is OAuth 2.1 — connecting runs a browser login, and there is no key, header or environment variable to set. ## Docs - [Docs](https://desk.miranet.work/docs): connecting, scopes, the tool reference, and what reaches a person - [Home](https://desk.miranet.work/): what the desk does with a report - [Dashboard](https://desk.miranet.work/ops): the human surface, behind a login. Not an API — do not scrape it. Items, signals, knowledge and answers are available through the tools above; releases, people, keys, connected servers and failed jobs are staff-only and have no MCP equivalent. ## Optional - Read an item's internal notes before starting work on it. `get_item` returns both message visibilities deliberately. That is where the desk records what it searched, what it could not place, and what it is unsure about — an item with an unresolved question in its notes is usually not ready to start.