Skip to main content
Glama

BriefGate

Listed on mcpservers.org

Client intake — from your browser, or from your AI coding agent.

Your agent can build the website. BriefGate gets the missing things from the client.

No agent? You don't need one. Everything below can be done by hand at app.briefgate.dev: build the request, send the link, watch answers arrive, download the results as one ZIP — no code, no API key, no MCP client. This package is for the other way of working, where an agent does it for you. See the dashboard quickstart.

Claude Code / Cursor / Codex → BriefGate → Client portal
  → Files · copy · credentials · structured data → Agent continues building

BriefGate demo: an intake being defined, the client filling the portal, results coming back

Watch as MP4 (25 s) · Full 47 s walkthrough

Website · MCP reference · llms.txt · Guides and checklists

The problem

Agents are fast. The bottleneck is the human on the other side of the project.

Somewhere in the middle of building, the agent needs something only the client has: a logo, homepage copy, brand colors, opening hours, hosting credentials, an API key, a piece of structured data like a price list. None of that exists in the chat, and none of it can be guessed.

The usual move is to stop and ask the developer to go chase the client by email. Instead, the agent creates a BriefGate intake. BriefGate emails the client, collects what comes back, chases automatically when it doesn't, and returns typed results the agent can use directly. The agent keeps building in the meantime.

Related MCP server: Business Bridge MCP Server

Quickstart

Claude Code — hosted, no key to manage:

claude mcp add --transport http briefgate https://mcp.briefgate.dev/mcp

Then run /mcp in Claude Code, pick briefgate, and choose Authenticate.

Claude Code — local package:

claude mcp add briefgate -- npx -y @briefgate/mcp
npx -y @briefgate/mcp login

Prefer to skip sign-in entirely? Get a key at briefgate.dev (free tier, no card) and pass it as BRIEFGATE_API_KEY.

Cursor — add to .cursor/mcp.json:

{
  "mcpServers": {
    "briefgate": {
      "command": "npx",
      "args": ["-y", "@briefgate/mcp"]
    }
  }
}

Then run npx -y @briefgate/mcp login, or ask the agent to call the login tool.

Codex:

codex mcp add briefgate --env BRIEFGATE_API_KEY=bg_live_xxxxx -- npx -y @briefgate/mcp

Gemini CLI — installs as an extension from this repo's gemini-extension.json, pointed at the hosted endpoint:

gemini extensions install https://github.com/sekera-radim/briefgate-mcp

It authenticates the same way as the other hosted clients above — via OAuth, on first use. The extension also ships GEMINI.md, a context file that tells the model what BriefGate is and when to reach for it.

By default gemini extensions install fetches the repo's latest GitHub Release rather than the main branch; if that release predates the current gemini-extension.json and the command reports a missing configuration file, install from main directly instead: gemini extensions install --ref main https://github.com/sekera-radim/briefgate-mcp.

Claude Desktop — one-click install as a Desktop Extension (.mcpb), running the local @briefgate/mcp package instead of the hosted endpoint. Build details: see MCPB bundle below.

Cursor — as a plugin from this repo's .cursor-plugin/plugin.json, which bundles the same MCP config together with the collect-from-client skill below.

Any Agent Skills-compatible tool (OpenAI Codex, GitHub Copilot / VS Code, Cursor, Gemini CLI, and other clients on that standard) — install the collect-from-client skill with the skills CLI:

npx skills add https://github.com/sekera-radim/briefgate-mcp/tree/main/skills/collect-from-client

The bare owner/repo shorthand the CLI's own docs show does not work against this layout as of skills@1.5.26 — it either 404s against its registry or, given the plain repo URL, clones the whole repo and reports "No SKILL.md found" because ours lives a directory deeper than the root. The full /tree/main/skills/<name> URL above is what actually installs it; verified by running it in an empty scratch directory before documenting it. This copies SKILL.md into that tool's local skills directory; the skill still assumes the briefgate MCP server (see the client-specific instructions above) is configured separately.

Full setup details, manual config, and API-key precedence: see Reference below.

For agents reading project instructions (AGENTS.md, Copilot, Cursor rules)

If your agent reads a project instructions file instead of (or in addition to) MCP tool descriptions or Agent Skills, add this block to your AGENTS.md, .github/copilot-instructions.md, or .cursor/rules:

## BriefGate

BriefGate (MCP server `briefgate`, or `npx @briefgate/mcp`) collects files,
text, choices, or credentials from a human who is not in this conversation
(a client, colleague, or customer) — it emails them a no-account portal link
and chases them automatically until everything is submitted.

Use it when a task is blocked on input only that outside person can supply,
especially if they may take days and need reminders. Do not use it when the
information is already available, or when the person who should answer is
the user you're talking to.

Flow: `define_intake` (with items) -> tell the user it was sent ->
`get_intake_status` later ("not ready" is normal, not an error) ->
`get_intake_results` once completed. `request_revision` for a bad item.
Secret items are revealed in plaintext exactly once.

Example: building a client's website

An agent is building a website for a restaurant. It has the layout and the booking flow, but it still needs the logo, a hero photo, the opening hours, a short description of the restaurant, the social media links, and admin access to the client's WordPress install. It calls define_intake:

{
  "project_name": "Website for Trattoria Bella",
  "client": { "email": "owner@trattoriabella.example", "name": "Marco", "language": "en" },
  "items": [
    { "key": "logo", "type": "image", "label": "Restaurant logo",
      "constraints": { "formats": ["svg", "png"], "min_width": 512 } },
    { "key": "hero_image", "type": "image", "label": "Hero photo for the homepage" },
    { "key": "opening_hours", "type": "structured", "label": "Opening hours",
      "schema": { "type": "object", "properties": { "mon_fri": { "type": "string" }, "sat": { "type": "string" }, "sun": { "type": "string" } } } },
    { "key": "about_copy", "type": "longtext", "label": "Short description of the restaurant" },
    { "key": "social_links", "type": "structured", "label": "Social media links" },
    { "key": "wp_admin", "type": "secret", "label": "WordPress admin credentials" }
  ]
}

From there, BriefGate (1) creates a branded portal, (2) emails the client, (3) validates each asset as it comes in, (4) chases the client automatically until everything is submitted, and (5) notifies the agent when it's done.

The agent keeps building the layout, the booking flow, and everything else that doesn't depend on this — then calls get_intake_results(intake_id) and gets back typed data and signed URLs for the files, plus a one-time reveal of the WordPress credentials. It stores the secret and continues.

Why not a form?

Generic form

BriefGate

Human creates the form

Agent declares what it needs

Human reads results

Agent consumes typed results

Generic answers

Typed items

Manual follow-up

Automatic chasing

Spreadsheet mindset

API / MCP workflow

Credentials are awkward

Secret item + controlled reveal

Human workflow

Agent workflow

BriefGate is not trying to replace every form builder. It is designed for the point where an AI agent needs information from a human.

Free tier, no card required. BriefGate is a hosted service — this repository is the open-source MCP client, MIT licensed. Sign up at briefgate.dev.

Reference

Everything below is unchanged technical detail: manual setup, environment variables, HTTP/OAuth mode, the full tool reference, webhooks, pricing, and legal.

Claude Code: manual setup and API keys

Paste an API key (for CI, scripts, or if you'd rather manage the key yourself). Get one at briefgate.dev (free tier available, no card required):

claude mcp add briefgate \
  -e BRIEFGATE_API_KEY=bg_live_... \
  -- npx -y @briefgate/mcp

Or add manually to ~/.claude/settings.json:

{
  "mcpServers": {
    "briefgate": {
      "command": "npx",
      "args": ["-y", "@briefgate/mcp"],
      "env": {
        "BRIEFGATE_API_KEY": "bg_live_..."
      }
    }
  }
}

BRIEFGATE_API_KEY (or --api-key on the command line), if set, always takes precedence over a key login stored locally — running login while one is configured just says so instead of doing anything.

Verify it loaded — run /mcp in Claude Code and look for briefgate with 15 tools.

The same local-package and API-key setup works for any MCP client that runs the package locally (Cursor, Codex, others) — register it with no key at all and run login, or paste BRIEFGATE_API_KEY into that client's own MCP config the same way.

Sign in without an API key

Two ways to get a key onto this machine without pasting one — both run the same device-authorization flow (RFC 8628) against the same credential file, so pick whichever fits how you're using the package.

From a terminal — the login / logout subcommands:

npx -y @briefgate/mcp login     # prints a code + URL, waits for approval, saves the key
npx -y @briefgate/mcp logout    # removes the local key, best-effort revokes it remotely

login blocks until you approve it (or it times out at 10 minutes), then prints Signed in as <account_name> and exits 0 — or prints why it didn't work (denied, expired, an error) and exits 1. logout always removes the local copy; it also sends DELETE /v1/keys/current using that same key to revoke it server-side, and if that call fails (no network, API unreachable) it says so and points at the BriefGate dashboard instead of leaving you unsure whether the key is still live.

From an agent — the login / logout tools (see Tools):

Same flow, for a client that can't block a terminal on your click. login is two-phase because a tool call can't sit open for minutes:

  1. The first call starts the flow and returns immediately with the code and URL. A browser is opened automatically where possible.

  2. Call login again — any time, or once you've approved it — to check progress. While it's still waiting, it says so; once approved, that same call reports success and the key is saved. No restart needed: the very next tool call is signed in.

logout as a tool does exactly what the subcommand does, including the best-effort remote revoke.

Either way, the key lands in ~/.briefgate/credentials.json (directory mode 0700, file mode 0600; override the path with BRIEFGATE_CREDENTIALS_FILE), keyed by which BriefGate server it's for so a staging BRIEFGATE_BASE_URL and production never collide. An explicit key always wins over a stored one — --api-key, then BRIEFGATE_API_KEY, then whatever login last saved — and login says so instead of running the flow when one of those is already set. Neither the subcommands nor the tools apply to the shared hosted endpoint (mcp.briefgate.dev) — see Hosted endpoint + OAuth, where connecting a client triggers real OAuth instead.

Environment variables

Variable

Required

Default

Description

BRIEFGATE_API_KEY

No

API key (bg_live_... or bg_test_...). Takes precedence over a credential stored by login. If nothing is configured, tool calls fail with a message pointing at login.

BRIEFGATE_BASE_URL

No

https://api.briefgate.dev

Override for staging or local development.

BRIEFGATE_CREDENTIALS_FILE

No

~/.briefgate/credentials.json

Where login/logout store the key. Mainly for tests and unusual setups.

BRIEFGATE_NO_BROWSER

No

unset

Set to 1 to stop login from opening a browser (headless servers, CI); the URL is printed either way.

BRIEFGATE_MCP_HTTP

No

Set to 1 to start Streamable HTTP instead of stdio.

BRIEFGATE_MCP_PORT

No

3000

Port for HTTP mode.

BRIEFGATE_MCP_PUBLIC_HOST

No

Publishes the server as a shared, multi-customer OAuth endpoint. See Hosted endpoint + OAuth.

BRIEFGATE_MCP_AUTH_SERVER

No

BRIEFGATE_BASE_URL

The OAuth authorization server advertised to clients in published mode. Defaults to BRIEFGATE_BASE_URL for local dev, where they're usually the same address; a real deployment behind a container network sets this explicitly (see below).

--api-key bg_live_... is also accepted on the command line, ahead of BRIEFGATE_API_KEY in priority. login and logout are also accepted as the first command-line argument (npx @briefgate/mcp login), instead of --http/no flag.

HTTP (Streamable HTTP) mode

For remote or multi-session deployments, start the server in HTTP mode:

BRIEFGATE_API_KEY=bg_live_... npx @briefgate/mcp --http --port 3000

The server binds to 127.0.0.1 only and includes DNS-rebinding protection. Behind a reverse proxy, terminate TLS there and forward to the local port — do not expose the port directly.

Hosted endpoint + OAuth

Set BRIEFGATE_MCP_PUBLIC_HOST to the hostname the server is published under and it becomes a shared, multi-customer endpoint: each caller sends its own key as Authorization: Bearer bg_live_... (an OAuth access token, for this API, is that same key — see below), and the server speaks to the BriefGate API as that caller. The public instance is https://mcp.briefgate.dev/mcp.

BRIEFGATE_MCP_PUBLIC_HOST=mcp.example.com npx @briefgate/mcp --http --port 3000

Several things change, on purpose:

  • the listener binds 0.0.0.0 and the Host guard accepts that name, because a server behind a reverse proxy is reached by its public name;

  • the BRIEFGATE_API_KEY fallback and the local login credential are both switched off. Leaving either on would let an anonymous caller spend the operator's key, or read whatever the machine's own login last stored;

  • login/logout, tools and subcommands alike, are unavailable — connecting a client triggers real OAuth instead, described below;

  • the server becomes an OAuth 2.1 resource server, per the MCP authorization spec, so an OAuth-aware client can add it with nothing but the URL. This package never runs the authorization flow itself — it only advertises where to find it and enforces that a request carries a token:

    • it serves GET /.well-known/oauth-protected-resource (RFC 9728), and the same content again under /.well-known/oauth-protected-resource/mcp (the resource-scoped path the MCP spec also has clients try), both with open CORS and naming the BriefGate API as the authorization server — see BRIEFGATE_MCP_AUTH_SERVER above;

    • every MCP request now needs a Bearer token — including initialize and tools/list, which used to work without one so a registry could introspect the tool list. One with no token gets HTTP 401 and a WWW-Authenticate: Bearer resource_metadata="https://<host>/.well-known/oauth-protected-resource" header, which is the signal an OAuth client uses to start signing in;

    • if a tool call's key turns out to be expired or revoked (the API answers 401), the response is rewritten into a real HTTP 401 with the same header plus error="invalid_token", rather than an ordinary tool error — so the client knows to refresh rather than just reporting the call failed.

What a connecting client actually does, against the authorization server named in that metadata: standard OAuth 2.1 discovery (GET /.well-known/oauth-authorization-server), dynamic client registration (POST /v1/oauth/register), then an authorization-code exchange with PKCE (S256) at POST /v1/oauth/token — no client secret, since MCP clients are public clients — and POST /v1/oauth/revoke to end a session. None of that is this package's concern; it only has to be a correct resource server pointing at it. The access token that comes out the other end is a bg_live_... key like any other, with a one-hour expiry the API enforces.

None of this applies without BRIEFGATE_MCP_PUBLIC_HOST: a local --http run keeps behaving exactly as before, including an absent key reaching initialize/tools/list and a plain Authorization: Bearer ... header working with no OAuth involved.

Tools

define_intake

Create a new client intake — a branded portal where the client submits the assets you need. BriefGate sends the invite email and chases the client automatically until everything is collected.

project_name: "Website for John Finance"
client: { email: "john@example.com", name: "John", language: "cs" }
// also_notify: [{ email: "jane@example.com", name: "Jane" }]
//   Others at the client who get the same link and the same reminders — either of
//   them can supply the material. Each gets their own email; nobody sees the rest.
due_date: "2026-08-15"
branding: { accent_color: "#1B2A4A", sender_name: "Radim" }
chase_schedule: "default"   // default | gentle | aggressive | custom | off
// chase_interval: 5, chase_interval_unit: "minutes"   // only with "custom"; omit for every 3 days
// respect_quiet_hours: false, max_reminders: 12       // for a deliberately rapid cadence
items:
  - { key: "logo",       type: "image",    label: "Company logo",
      constraints: { formats: ["svg","png"], min_width: 512 } }
  - { key: "hero_copy",  type: "longtext", label: "Homepage headline",
      constraints: { max_chars: 400 } }
  - { key: "brand_colors", type: "color_list", label: "Brand colors", required: false }
  - { key: "ga4_id",    type: "text",     label: "Google Analytics ID",
      pattern: "^G-[A-Z0-9]+$", required: false }
  - { key: "wp_admin",  type: "secret",   label: "WordPress admin credentials" }
  - { key: "photos",    type: "file_list", label: "Photos (5–10 images)",
      constraints: { formats: ["jpg","png","heic"], min_count: 5, max_count: 15 } }
  - { key: "opening_hours", type: "structured", label: "Opening hours",
      schema: { type: "object", properties: { mon_fri: { type: "string" }, sat: { type: "string" } } } }
  - { key: "has_existing_site", type: "boolean", label: "Does the client have an existing website?" }
  - { key: "website_url", type: "url", label: "Current website URL", required: false }
  - { key: "service_tier", type: "select", label: "Service package",
      options: [{ value: "basic", label: "Basic" }, { value: "pro", label: "Pro" }] }
// folder_id: "fld_1"
//   Put the intake straight into an existing folder from list_folders instead
//   of leaving it unfiled.
// client_brief: "Here's the offer we agreed on, plus a few notes on scope..."
//   Free text shown to the client above the requested items — information from
//   you to them, not another thing you're asking them for. Up to 5000 characters.
//   Documents go through POST /v1/intakes/:id/brief/files (dashboard or REST,
//   not through MCP).

Item key rules: must be snake_case (e.g. logo, hero_copy, ga4_id). Keys become property names in get_intake_results — no uppercase, no spaces, no hyphens.

Returns { intake_id, portal_url, status }. Save intake_id for all follow-up calls.

get_intake_status

Check which items are submitted, pending, or need revision. Includes the history of automated chase emails and when the client last opened the portal.

intake_id: "in_8f3k"

Returns per-item status and a full chase history.

get_intake_results

Retrieve typed submitted values. Files are signed URLs (valid 24 hours). Secrets are one-time — decrypted and returned on the first call only; store them before moving on.

intake_id: "in_8f3k"
only_new: true          // only items new since last call
include_pending: false  // omit unsubmitted items

Returns { results: { logo: "https://signed...", hero_copy: "text...", wp_admin: "s3cr3t" }, meta: { ... } }.

request_revision

Ask the client to resubmit an item with a note explaining what is wrong.

intake_id: "in_8f3k"
item_key: "logo"
note: "Logo is blurry — we need at least 512 px wide in SVG or PNG with a transparent background"

Returns { status: "revision_requested", item_key }.

send_chase

Send a manual reminder outside the automatic schedule. Use when a deadline is approaching or email attempts have failed.

intake_id: "in_8f3k"

Returns { sent: true }.

list_intakes

List all intakes across projects, optionally filtered by status, client email, folder, or a text search.

status: "in_progress"   // draft | sent | in_progress | completed | archived
client_email: "john@example.com"
folder_id: "fld_1"      // or "none" for intakes not in any folder
q: "Finance"             // substring match on project name, client name, or client email
limit: 20
offset: 0

Returns { intakes: [...], total }.

add_items

Add new items to an already-sent intake — for example a favicon you forgot, or additional credentials needed mid-project.

intake_id: "in_8f3k"
items:
  - { key: "favicon", type: "image", label: "Favicon (32×32 PNG or ICO)" }

Returns the updated intake.

update_item

Change an item's definition after the intake was sent — the type, label, help text or constraints. Use this when you asked for the wrong thing, e.g. you requested an image but the client has a PDF.

intake_id: "in_8f3k"
item_key: "logo"
type: "file"                        // was "image"
constraints: { formats: ["pdf","ai","svg"] }
discard_submitted_value: false      // true is required if the change invalidates what the client already sent

Returns the updated item. If the client already submitted a value that the new definition would reject, the call fails with item_answer_would_be_discarded until you pass discard_submitted_value: true.

update_intake

Change settings on an already-sent intake — project name, due date, reminder cadence, quiet hours, the client brief, or the client's name, phone, language, and timezone. Use this instead of deleting and recreating the intake, which would re-send the invite.

intake_id: "in_8f3k"
due_date: "2026-12-01"
chase_schedule: "gentle"            // was "default"
max_reminders: "unlimited"          // reactivates a stalled intake if it had hit its cap
// folder_id: "fld_1"                // move it into a folder; null removes it from any folder
// client_brief: "Updated offer..."  // replaces the brief shown above the items; null clears it

If any chase-related field changes (chase_schedule, chase_interval, chase_interval_unit, chase_at_time, max_reminders, respect_quiet_hours, due_date, client.timezone) on a sent intake, every pending reminder is cancelled and re-planned from now — reminders already sent still count toward max_reminders. folder_id never touches the chase schedule.

The client's e-mail address cannot be changed here — the portal link and login are bound to it. Use manage_recipients for that. Fails if the intake is archived. Returns the full, updated intake object.

manage_recipients

Add, remove, or reinstate a person who receives an intake's invite and reminders, alongside or instead of the primary client.

intake_id: "in_8f3k"
action: "reinstate"                 // add | remove | reinstate
email: "extra@example.com"
name: "Petr"                        // only used with action="add"

action="add" invites another address the same way also_notify does at define_intake time. action="remove" stops future reminders to that address. action="reinstate" is for a bounce that was wrong — the person did get the e-mail — it clears the bounce flag so reminders resume, and re-plans the chase schedule from now if that address was the only one still being chased.

manage_webhook

Register, list or remove a webhook endpoint so events are pushed to your service instead of you polling.

action: "create"                    // create | list | delete
url: "https://your.service/hooks/briefgate"
events: ["intake.completed", "intake.overdue"]
format: "raw"                       // raw | slack | discord

action: "create" returns a secret once — store it, it verifies every delivery signature and cannot be retrieved again. Remove with action: "delete" and webhook_id.

Because an agent receives the secret in a tool result, it can come to rest wherever that conversation is stored. There is no rotation endpoint: if a transcript leaks, delete the endpoint and create a new one to get a fresh secret.

Only register an endpoint you can actually receive on. An agent running in a terminal has no public HTTPS address; for that case register nothing and check on a schedule instead (see below).

list_folders

List the folders in your account, used to group intakes by client or project. Takes no arguments.

Call this before create_folder or before setting folder_id on define_intake, update_intake, or list_intakes — reuse an existing folder for a returning client instead of creating a duplicate.

Returns { folders: [{ id, name, sort_order, intake_count, created_at }] }.

create_folder

Create a new folder to group intakes, e.g. one per client.

name: "Acme Inc"

Call list_folders first and reuse a matching folder — only create one when none of the existing folders fits. Fails with folder_exists if a folder with this name already exists. Returns the created folder.

login

Sign in without an API key — see Sign in without an API key. Takes no arguments.

Call it whenever another tool reports "Not signed in" or that the stored key was revoked or expired. The first call starts a device-authorization flow and returns a URL and a short code immediately; call it again (any time) to check whether it's been approved yet. Has no effect — it says so instead — if --api-key or BRIEFGATE_API_KEY already supplies a key. Not available on the hosted endpoint. Same flow as running npx @briefgate/mcp login from a terminal (which blocks until approved instead of needing a second call) — see Sign in without an API key.

logout

Removes the API key login stored locally for this BriefGate server, and best-effort revokes it on the server too. Takes no arguments.

If the revoke call fails — no network, the API unreachable — the local copy is still removed; the response says so and points at the BriefGate dashboard to revoke it there instead. Not available on the hosted endpoint. Same effect as running npx @briefgate/mcp logout from a terminal — see Sign in without an API key.

Decisions — questions for the developer

An agent building something hits things only the account holder can settle: does the discounted plan cost $19 or $29? Stopping to wait wastes the run; picking silently buries the assumption. A decision is the third option — pose the question, record the answer you are proceeding on, keep building.

{ "key": "discount_price", "type": "select", "assignee": "owner",
  "label": "What does the discounted subscription cost?",
  "options": [ { "value": "19", "label": "$19/month" },
               { "value": "29", "label": "$29/month" } ],
  "proposed": { "value": "19", "rationale": "matches the competitor we benchmarked" } }

type: "multiselect" takes several answers, bounded by constraints.min_count / max_count.

The proposal is stored apart from the real answer, so it can never be mistaken for one the developer gave — and it survives being overruled, which is the point: in three months you can still see that $19 was assumed, not agreed. Read it back from get_intake_results:

"results": { "discount_price": "19" },
"meta": { "discount_price": { "decided_by": "agent_proposal", "proposed_value": "19" } }

decided_by is "owner" once a person has settled it and "agent_proposal" while it is still your own pick. A proposed decision comes back even without include_pending — you need the assumption you are building on. It does not bump revision, so an only_new read surfaces exactly the decisions someone has since answered.

You cannot answer your own question. The answer endpoint takes a dashboard session, not an API key: if the agent could confirm its own proposal and have it recorded as the developer's, the distinction would be worth nothing. Decisions are answered in the BriefGate dashboard.

Owner items never reach the client portal, never appear in a reminder, and never hold up completion — the intake is finished when the client is finished.

Knowing when the client is done

Nothing pushes to an MCP client on its own — MCP is request/response, so the server cannot wake your agent when the client finishes. define_intake therefore returns a follow_up block naming the mechanism that fits your setup:

"follow_up": {
  "recommended": "schedule",        // or "webhook" when an endpoint already exists
  "webhook": { "active_endpoints": 0, "events": ["intake.completed", "item.submitted"],
               "register_with": "manage_webhook" },
  "schedule": { "check_with": "get_intake_status", "every_hours": 24,
                "until": "2026-10-01T08:00:00.000Z" }
}
  • You run a service → register a webhook with manage_webhook and act on intake.completed.

  • You are an agent in a terminal → set up a recurring check that calls get_intake_status every every_hours hours until until. A cron entry, a systemd timer, or your agent host's own scheduler all work.

Events worth acting on: intake.completed (everything is in) and intake.overdue (the deadline passed with required items missing — the project is blocked and the client needs a human, not another reminder).

The cadence tightens near the deadline (24h normally, 12h inside a week, 6h inside two days) and is not tied to the reminder schedule: a client can submit everything at 2am having never opened a reminder.

End-to-end example

# System prompt excerpt
You are a web development agent. When you need client assets:

1. Call define_intake with all assets needed for this project.
   Use type=secret for passwords/credentials.
   The chase engine runs automatically — do not poll more often than once per day.

2. Read follow_up in the response and set up how you will hear back:
   register a webhook with manage_webhook if you have an HTTPS endpoint,
   otherwise schedule a get_intake_status check at follow_up.schedule.every_hours.

3. When intake.completed arrives (or the scheduled check reports "completed"),
   call get_intake_results. Download file URLs within 24 hours.
   Secrets are shown only on the first retrieval.

4. If a submitted asset does not meet requirements (blurry logo, broken URL),
   call request_revision with a clear note for the client.
   If the client has the asset in another form, call update_item to change the type.

5. If the client is still unresponsive after 9 days, call send_chase for an
   extra nudge outside the automatic schedule, or tell the developer the intake
   is stuck and let them pick up the phone.

Verifying webhooks

BriefGate signs every webhook with HMAC-SHA256 to prevent forgery and replay attacks. The @briefgate/mcp package exports a ready-made helper:

import { verifyWebhookSignature, parseWebhookEvent } from "@briefgate/mcp/webhook";

The signature lives in the X-BriefGate-Signature header as t=<unix>,v1=<hex>:

import Fastify from "fastify";
import { verifyWebhookSignature, parseWebhookEvent } from "@briefgate/mcp/webhook";

const app = Fastify();

// Parse body as raw string — JSON-parsing before verification breaks the HMAC.
app.addContentTypeParser("application/json", { parseAs: "string" }, (req, body, done) => {
  done(null, body);
});

app.post("/briefgate/webhook", (request, reply) => {
  const rawBody = request.body as string;

  const ok = verifyWebhookSignature(
    process.env.BRIEFGATE_WEBHOOK_SECRET!,
    request.headers["x-briefgate-signature"] as string,
    rawBody,
    // { toleranceSec: 300 }  ← default; increase for slow networks
  );

  if (!ok) {
    return reply.status(401).send({ error: "Invalid signature" });
  }

  const event = parseWebhookEvent(rawBody);
  console.log("BriefGate event:", event.event, event.intake_id);
  reply.send({ ok: true });
});

Express

import express from "express";
import { verifyWebhookSignature, parseWebhookEvent } from "@briefgate/mcp/webhook";

const app = express();

// raw body parser — must come before express.json()
app.post(
  "/briefgate/webhook",
  express.raw({ type: "application/json" }),
  (req, res) => {
    const rawBody = Buffer.isBuffer(req.body)
      ? req.body.toString("utf8")
      : String(req.body);

    const ok = verifyWebhookSignature(
      process.env.BRIEFGATE_WEBHOOK_SECRET!,
      req.headers["x-briefgate-signature"] as string,
      rawBody,
    );

    if (!ok) return res.status(401).json({ error: "Invalid signature" });

    const event = parseWebhookEvent(rawBody);
    console.log("BriefGate event:", event.event, event.intake_id);
    res.sendStatus(200);
  },
);

Webhook events

Event

When

Key fields

item.submitted

Client submits an item

item_key, item_status

intake.completed

All required items approved

client.viewed

Client opens the portal

client_email

chase.bounced

A reminder bounced

channel, reason, recipient, still_chasing

intake.stalled

3 reminders sent, no response

attempts

Pricing

Launch offer: code LAUNCH20 gives 20% off Solo and Agency for the lifetime of the subscription, valid until 4 October 2026 (new customers, plans only).

Free

Solo — $29/mo

Agency — $79/mo

Active intakes

1

15

60

Items per intake

10

unlimited

unlimited

Storage

1 GB

25 GB

100 GB

Branding

"powered by"

custom logo + colors

+ custom sending domain

Chase

email, default

email, all schedules

email, all schedules

Secrets vault

yes

yes

Webhooks + REST + MCP

yes

yes

yes

Full pricing at GET https://api.briefgate.dev/pricing.json (no auth required — agents can read it directly).

Data residency

BriefGate is hosted in the EU: application servers at netcup GmbH in Nuremberg, Germany; files in Cloudflare R2 under EU jurisdiction. See the GDPR notes and the DPA.

Privacy Policy

This package is a thin client: it holds no data of its own and sends nothing anywhere except to the BriefGate API at api.briefgate.dev, using the API key you configure. It writes no telemetry and no analytics.

What BriefGate itself collects, how long it keeps it, who it is shared with and how to have it deleted is covered in full here:

Contact for privacy requests: privacy@briefgate.dev

MCPB bundle (Claude Desktop Extension)

manifest.json at the repo root packages the local @briefgate/mcp package as a one-click Claude Desktop install (MCPB spec). It runs dist/index.js locally and prompts for an optional API key at install time — the same login/BRIEFGATE_API_KEY setup documented above, not the hosted endpoint's OAuth flow.

Build the bundle (production dependencies only, packed in a throwaway staging directory so it never touches this repo's own node_modules):

npm run package:mcpb

This produces briefgate.mcpb at the repo root (gitignored — install it locally to test, don't commit it). Not yet submitted anywhere; see scripts/build-mcpb.mjs for what the command does.

Contributing

This repository is the BriefGate MCP client only — a thin wrapper over the public BriefGate REST API. The BriefGate service itself is closed source.

npm install
npm run typecheck   # TypeScript check
npm run lint        # ESLint
npm run test        # Vitest
npm run check       # all three
npm run build       # compile to dist/

License

MIT — use freely in commercial projects.


Made by Radim Sekera. Related project: impri.dev — human-in-the-loop approval inbox for AI agents.

Available Tools

15 tools
add_itemsAdd items to an intakeA

Add new items to an already-sent intake — for example, when you realise mid-project that you also need a favicon, social media assets, or additional credentials.

The client is notified about the new items. Existing items and their submitted values are not affected. Returns the updated intake object.

Items must follow the same key/type/label rules as define_intake (snake_case keys, type-specific constraints).

ParametersJSON Schema
NameRequiredDescriptionDefault
itemsYesNew items to add. Same schema as define_intake items.
intake_idYesIntake ID returned by define_intake.

Output Schema

ParametersJSON Schema
NameRequiredDescription
itemsNo
statusNo
folder_idNo
follow_upNoHow to learn this intake is done — present unless a webhook already covers it. Mirrors FollowUpAdvice in client.ts.
intake_idNo
portal_urlNo

TDQS

A4.4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations indicate readOnlyHint=false (mutation), destructiveHint=false (not destructive), and idempotentHint=false. The description adds important behavioral details: the client is notified, existing items are untouched, and returns the updated intake. It doesn't mention potential side effects like rate limits, but the client notification is a key behavioral trait not captured in annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is three short paragraphs: one for purpose with examples, one for behavioral notes, and one for validation rules. It is front-loaded with the core purpose, and every sentence adds value without unnecessary fluff.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a mutation tool with 2 parameters and an output schema, the description is mostly complete. It specifies the items schema references another tool, mentions notification and non-impact on existing items, and returns the updated intake. The only minor gap is not detailing validation failure behavior (e.g., partial additions), but the output schema and schema coverage mitigate this.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the schema fully documents the parameters and their properties. The description adds minimal extra meaning beyond referencing 'same rules as define_intake' and the snake_case key requirement, but since the schema covers it, a baseline 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the verb ('Add') and resource ('items to an intake'), and specifies the exact use case: adding items after an intake has been sent, with examples. It distinguishes itself from siblings like update_intake and define_intake by focusing on post-send additions.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description explicitly says when to use this tool: when you need to add items to an already-sent intake, and gives concrete examples. It also implicitly differentiates from define_intake (for initial definition) and update_item (for modifying existing items) by stating existing items are not affected.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

create_folderCreate a folderA

Create a new folder to group intakes, e.g. one per client.

Call list_folders first and reuse a matching folder — only create one when none of the existing folders fits. Fails with folder_exists if a folder with this name already exists; use list_folders to find it instead.

Returns the created folder { id, name, sort_order, intake_count, created_at }.

ParametersJSON Schema
NameRequiredDescriptionDefault
nameYesFolder name, e.g. the client's or project's name. Must be unique in your account.

Output Schema

ParametersJSON Schema
NameRequiredDescription
idNo
nameNo
created_atNo
sort_orderNo
intake_countNo

TDQS

A4.7/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Beyond the annotations, the description discloses the duplicate-name error condition, the uniqueness requirement, and the exact return payload. This gives the agent clear expectations about side effects and failure behavior.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Every sentence earns its place: purpose, usage guidance, error condition, and return value. The most important routing instruction is front-loaded, and the description is compact without being vague.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a single-parameter creation tool with annotations and an output schema, the description covers the essential context: when to create, what happens on duplicates, and what the response contains. Nothing needed to invoke it correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The schema already documents the 'name' parameter with uniqueness and examples, so coverage is 100%. The description adds the grouping use case but does not materially improve parameter understanding beyond the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool creates a new folder to group intakes, with a concrete example ('one per client'). It distinguishes this tool from list_folders by framing creation as the fallback when no existing folder fits.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly instructs the agent to call list_folders first, reuse a matching folder, and only create when none matches. It also names the duplicate error and directs the agent back to list_folders, leaving no ambiguity about when to use this tool.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

define_intakeCreate client intakeA
Idempotent

Create a new client intake request — a branded portal where the client submits logos, copy, files, credentials, and other assets. BriefGate sends the invite email and chases the client automatically until all items are collected.

Call this once at the start of a project, after you know what assets you need. Returns { intake_id, portal_url, status, follow_up }. Save intake_id — you need it for all follow-up calls.

AFTER CREATING AN INTAKE, SET UP HOW YOU WILL LEARN IT IS DONE. Nothing pushes to you on its own: MCP is request/response, so the server cannot wake you when the client finishes. Creating the intake and never checking again is the common failure — the completed work then sits in the portal until a human happens to look. The returned follow_up block tells you which of the two mechanisms applies:

  • follow_up.recommended = "webhook" — the account already has an endpoint; deliveries will arrive there and you need do nothing further.

  • follow_up.recommended = "schedule" — no endpoint is registered. If you control a service that can receive public HTTPS, register one with manage_webhook. Otherwise tell the user to set up a recurring check (cron, a systemd timer, a scheduled task in their agent host) that calls get_intake_status every follow_up.schedule.every_hours hours until follow_up.schedule.until, and offer to configure it for them.

Example: { "project_name": "Website for John Finance", "client": { "email": "john@example.com", "name": "John", "language": "cs" }, "due_date": "2026-08-15", "branding": { "accent_color": "#1B2A4A", "sender_name": "Radim" }, "items": [ { "key": "logo", "type": "image", "label": "Company logo", "constraints": { "formats": ["svg","png"], "min_width": 512 } }, { "key": "hero_copy", "type": "longtext", "label": "Homepage headline (2–3 sentences)", "constraints": { "max_chars": 400 } }, { "key": "wp_admin", "type": "secret", "label": "WordPress admin credentials" }, { "key": "photos", "type": "file_list", "label": "Photos (5–10 images)", "constraints": { "formats": ["jpg","png","heic"], "min_count": 5, "max_count": 15 } } ] }

Item types: text, longtext, file, file_list, image, color_list, select (one of options[]), multiselect (several of options[]; min_count/max_count in constraints), boolean, url, secret (encrypted; the value is shown only on the first retrieval), structured (requires schema with JSON Schema).

DECISIONS — questions for the account holder, not the client. An item with assignee="owner" and type select/multiselect is a question only the account holder can answer ("does the discounted plan cost 19 or 29?"). It can carry an optional "proposed" answer, e.g. proposed = { value: "19", rationale: "matches the competitor we benchmarked" }, which is stored separately from the account holder's answer and clearly labelled as a proposal.

get_intake_results returns the current answer with meta..decided_by: "owner" when the account holder answered in the dashboard, "agent_proposal" while only the proposal exists. A proposal cannot be confirmed through this API; only the account holder answers it. Item keys must be snake_case (e.g. "logo", "hero_copy", "ga4_id") — they become property names in get_intake_results.

ParametersJSON Schema
NameRequiredDescriptionDefault
sendNoWhether to send the invite email immediately. Default: true. Set to false to create a draft and call /v1/intakes/:id/send later.
itemsYesList of assets to collect. Each item has key, type, label, and optional constraints.
clientYesClient contact details.
brandingNoOverride account-level branding for this intake.
due_dateNoDeadline in YYYY-MM-DD format. Shown in the portal and used to escalate chase cadence.
templateNoTemplate slug to pre-populate items (e.g. "restaurant-website", "consulting-firm").
folder_idNoPut this intake in an existing folder from list_folders instead of leaving it unfiled. Folders group intakes by client or project — reuse one for a returning client rather than creating a duplicate with create_folder.
retentionNoHow long BriefGate keeps this intake after it is finished. Default: purged 90 days after the client completes. Use mode "on_delivery" when the intake holds anything sensitive (credentials, personal photos): the contents are then removed shortly after YOU collect them with get_intake_results, because at that point you already have the files and there is no reason for a copy to sit on our server. An intake you never collect still expires on the day count, so this can only ever delete data earlier, never later. Example: { "mode": "on_delivery" } — or { "mode": "days", "days": 7 } to just shorten the window.
email_copyNoYour own subject and intro lines, overriding the built-in translation for this intake. Placeholders: {sender}, {project}, {client}, {count}, {minutes}, {due}. An unknown placeholder is rejected rather than rendered literally to the client. Layout, button and footer stay as they are.
client_briefNoFree-text brief shown to the client at the top of the portal, above the requested items — information from you to them: an offer, instructions, or context for why you are asking for these items. Up to 5000 characters. Documents attached to the brief go through the REST endpoint POST /v1/intakes/:id/brief/files (dashboard or REST — not available through this MCP tool set).
project_nameYesHuman-readable project name shown in the invite email and portal heading.
chase_at_timeNoLocal time of day to send reminders at, "HH:MM" in the client's timezone (e.g. "07:00"). Anchors the cadence to a clock time instead of counting from the invite, and needs an interval measured in whole days. Naming a time deliberately overrides quiet hours, so "07:00" stays 07:00.
max_remindersNoReminders to send before the intake is marked stalled and handed back to you (default 3). An integer from 1 to 1000, or the string "unlimited" to keep reminding until the client finishes. Raise it for a rapid cadence, which would otherwise exhaust three attempts in minutes. A bounce or spam complaint always cancels the remaining reminders, whatever this is set to.
chase_intervalNoHow often to remind, only with chase_schedule="custom". Pair with chase_interval_unit. Defaults to every 3 days when omitted. The interval must work out to at least 5 minutes and at most 90 days.
chase_scheduleNoAutomated reminder cadence. default=T+2d,T+5d,T+9d,weekly. gentle=T+3d,T+8d,biweekly. aggressive=T+1d,T+3d,T+5d,every-other-day. custom=every chase_interval chase_interval_unit. off=no auto reminders.
auto_approve_hoursNoHours after submission before an item is auto-approved without agent review. Default: 72. Set to 0 to require explicit approval.
chase_interval_unitNoUnit for chase_interval. Defaults to "days".
respect_quiet_hoursNoHold reminders to the client's 08:00-19:00 local window (default true). A cadence of minutes or hours pauses overnight and resumes in the morning; set false to send around the clock.

Output Schema

ParametersJSON Schema
NameRequiredDescription
statusNo
noticesNoCadence caveats, present only when chase_schedule="custom" makes them relevant.
follow_upNoHow to learn this intake is done — present unless a webhook already covers it. Mirrors FollowUpAdvice in client.ts.
intake_idNo
portal_urlNo

TDQS

A4.7/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Beyond annotations, the description reveals important side effects: the invite email is sent, BriefGate chases the client automatically, and nothing pushes back because MCP is request/response. It also warns about the common failure of never checking, explains the follow_up block, and even flags the blank-name pitfall ('Hello,' to someone being asked for their admin password).

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is long, but the length is largely justified by 18 parameters, nested objects, and complex lifecycle behavior. It is front-loaded with the core purpose, then uses titled blocks and a concrete example; some content slightly repeats schema details, so it is not perfectly lean.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Covers creation, the returned contract, follow-up setup, item and assignee semantics, naming constraints, and failure modes. An output schema exists, so the description does not need to enumerate return fields beyond the key { intake_id, portal_url, status, follow_up } shape. Nothing an agent needs to decide whether, when, or how to call it is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The schema already covers 100% of parameters, but the description adds substantial meaning beyond it: a full example intake, snake_case key rule, semantics for each item type, assignee='owner' behavior, proposal/decided_by flow, retention nuances, and chase cadence details. This materially improves an agent's ability to construct correct requests.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States 'Create a new client intake request — a branded portal where the client submits logos, copy, files, credentials, and other assets' with a specific verb, resource, and outcome. The create-vs-manage distinction is clear from 'Call this once at the start of a project' and from the sibling tool names like update_intake and add_items.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly says to call this once at the project start after the needed assets are known, and gives detailed post-create guidance on setting up follow-up via webhook or schedule. It does not explicitly name alternatives like update_intake for modifying an existing intake, so exclusion guidance is implied rather than fully stated.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_intake_resultsCollect intake resultsA
Destructive

Retrieve the typed submitted values from a client intake.

Files are returned as signed download URLs that expire after 24 hours.

Secrets (type=secret, e.g. passwords, API keys) are decrypted and included only in the first retrieval. Later calls return first_reveal: false in meta and omit the value, so the user should be ready to receive a secret before this tool is called on an intake that contains one.

Use only_new=true to get only items submitted since the last call (useful in webhook-driven workflows). Use include_pending=true to also return partially filled items.

Returns { results: { : }, meta: { : { type, status, submitted_at, first_reveal? } } }.

For a DECISION item (assignee=owner, type select/multiselect), results holds the current answer and meta..decided_by is "owner" (answered by the account holder) or "agent_proposal" (only a proposal exists). Proposals are returned even without include_pending. A proposal does not bump revision, so only_new returns decisions the account holder has answered or changed since the last call.

ParametersJSON Schema
NameRequiredDescriptionDefault
only_newNoReturn only items submitted or updated since the previous get_intake_results call. Default: false.
intake_idYesIntake ID returned by define_intake.
include_pendingNoInclude items not yet submitted (useful for partial progress checks). Default: false.

Output Schema

ParametersJSON Schema
NameRequiredDescription
metaYesKeyed the same as results.
statusYes
resultsYesKeyed by this intake's own item keys. A value's shape depends on that item's type.
intake_idYes

TDQS

A4.6/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations flag destructiveHint=true, readOnlyHint=false, and idempotentHint=false, and the description explains exactly why: secrets are decrypted and included only on the first call, later calls omit the value with first_reveal: false; files expire after 24 hours; and proposals are returned without include_pending yet don't bump revision, so they don't trigger only_new. This is rich behavioral disclosure fully consistent with the annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is lengthy but well-structured: the purpose is front-loaded, and each paragraph addresses one distinct concern (files, secrets, flags, return shape, decision items). The explicit 'Returns { results ... }' block is partially redundant given the stated output schema, but every sentence carries distinct, non-fluff information that an agent needs.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a tool with this much behavioral nuance — one-time secret reveal, signed URL expiry, and the interplay of proposals with only_new — the description covers the full calling surface: return shape, parameter effects, side effects, and edge cases like decided_by being 'owner' versus 'agent_proposal.' Nothing an agent needs to call it correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so a baseline of 3 applies. The description adds real meaning beyond the schema: it explains only_new's 'since the last call' semantics and the revision/proposal nuance that affects it, and clarifies include_pending as partial-progress inclusion. intake_id is already self-explanatory from the schema, so the added value is concentrated but meaningful.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The opening line 'Retrieve the typed submitted values from a client intake' uses a specific verb and resource, and it clearly distinguishes the tool from siblings like get_intake_status (status vs. values) and list_intakes. The remainder of the description reinforces exactly what data the tool returns (results plus meta), leaving no ambiguity about its core function.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives concrete usage context: only_new is positioned 'useful in webhook-driven workflows,' include_pending is for 'partial progress checks,' and a precondition warns the user to be ready to receive a secret. However, it never names an alternative sibling or states when not to use this tool (e.g., versus get_intake_status), so explicit tool-routing guidance is missing.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_intake_statusCheck intake progressA
Read-onlyIdempotent

Check the completion status of a client intake — which items are submitted, pending, or need revision; the history of automated chase emails sent; and when the client last opened the portal.

Use this to decide whether to send a manual reminder (send_chase), request a revision (request_revision), or fetch results (get_intake_results). Returns per-item status and chase history.

This is also the call a scheduled check should make when no webhook is registered — see follow_up in the define_intake response for the cadence. When status becomes "completed", fetch the results with get_intake_results and carry on with the work that was waiting on them.

ParametersJSON Schema
NameRequiredDescriptionDefault
intake_idYesIntake ID returned by define_intake (e.g. "in_8f3k").

Output Schema

ParametersJSON Schema
NameRequiredDescription
itemsYes
chasesYes
statusYes
due_dateNo
progressYes
intake_idNo
client_briefNoThe brief shown to the client above the requested items, if one is set.
client_last_seenNo

TDQS

A4.3/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint=true, idempotentHint=true, and destructiveHint=false, so the safety profile is covered. The description adds meaningful context beyond that: it mentions the tool returns chase history and last-open time, and it specifies the scheduled-check behavior ('This is also the call a scheduled check should make when no webhook is registered'). This enriches the agent's understanding without contradicting annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is efficiently written, with the core purpose in the first sentence and usage guidance following. It is front-loaded and each sentence earns its place, though there is slight redundancy in restating 'Returns per-item status and chase history' after already listing those items. Overall, it is compact and well-organized.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a tool with a single parameter and an output schema, the description covers purpose, usage scenarios, and the scheduled-check exception. It references the defining tool (define_intake) and the result tool (get_intake_results), giving the agent a clear workflow. The only minor gap is that it doesn't describe pagination or volume limits, but that is likely covered by the output schema and the tool's simplicity.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, and the description does not add new parameter information. It only restates that intake_id comes from define_intake, which is already in the schema. The baseline of 3 for high coverage applies, as the description does not need to compensate for missing schema documentation.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a specific verb ('Check the completion status of a client intake') and enumerates the exact information returned (submitted/pending/revision status, chase email history, last portal open). It also names sibling tools to route selection, distinguishing it from get_intake_results, send_chase, and request_revision.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly states when to use this tool versus alternatives: 'Use this to decide whether to send a manual reminder (send_chase), request a revision (request_revision), or fetch results (get_intake_results).' It also provides a specific scheduled-check scenario with the cadence source ('follow_up in the define_intake response') and the follow-up action when status completes. No ambiguity.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

list_foldersList foldersA
Read-onlyIdempotent

List the folders in your account, used to group intakes by client or project.

Call this before create_folder or before setting folder_id on define_intake, update_intake, or list_intakes — reuse an existing folder for a returning client instead of creating a duplicate.

Returns { folders: [{ id, name, sort_order, intake_count, created_at }] }.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

Output Schema

ParametersJSON Schema
NameRequiredDescription
foldersYes

TDQS

A4.7/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint, idempotentHint, and destructiveHint, so the safety profile is covered. The description adds the return structure (folders array with fields) and the reuse context, which goes beyond annotations. No contradictions. A 4 is appropriate because the description contributes useful behavioral context on top of annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is three sentences, front-loaded with the purpose and immediately followed by usage guidance and return format. No fluff, every sentence earns its place. The structure is efficient and easy to scan.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a simple, parameterless read-only tool with an output schema, the description covers the purpose, usage timing, return structure, and even provides a reason to call it. Nothing an agent needs to invoke it correctly is missing. The description is complete and self-sufficient.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool has zero parameters, so there is nothing to describe. Schema coverage is trivially 100%. The baseline for 0 params is 4, and the description does not need to add parameter information since none exist. It correctly omits any param details.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states it lists folders used for grouping intakes by client/project, with a specific verb and resource. It distinguishes itself from siblings by naming when to call it (before create_folder, etc.) and why (reuse existing folder). This fully clarifies the tool's purpose and differentiates it from alternatives.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description explicitly instructs when to use the tool: 'Call this before create_folder or before setting folder_id on define_intake, update_intake, or list_intakes.' It also provides an anti-pattern (avoid creating duplicates) and names the alternative action (reuse existing folder). This is exemplary usage guidance.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

list_intakesList intakesA
Read-onlyIdempotent

List all intakes in your account, optionally filtered by status, client email, folder, or a text search.

Use this to get an overview of active projects, find a specific intake by the client's email when you have lost the intake_id, check how many intakes are currently in progress, or see what's in a folder from list_folders.

Returns { intakes: [...], total } where each intake includes intake_id, project_name, status, created_at, due_date, folder_id, and portal_url.

ParametersJSON Schema
NameRequiredDescriptionDefault
qNoFree-text search: matches a substring of project name, client name, or client email.
limitNoMaximum number of results (1–100). Default: 20.
offsetNoPagination offset. Default: 0.
statusNoFilter by intake status. Omit to return all.
folder_idNoFilter by folder, using an id from list_folders. Pass the literal string "none" to see only intakes that are not in any folder.
client_emailNoFilter by client email address.

Output Schema

ParametersJSON Schema
NameRequiredDescription
totalYes
intakesYes

TDQS

A4.2/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already carry readOnlyHint, openWorldHint, idempotentHint, and destructiveHint, so the description does not need to repeat those traits. It adds useful behavioral context by specifying account scope, optional filtering, and the exact response envelope with the fields included in each intake.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is well structured and front-loaded: operation and filters first, then use cases, then return shape. The use-case sentence is slightly longer than strictly necessary, but each listed scenario earns its place by clarifying when an agent should call this tool.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a list tool with six optional filters and zero required parameters, this description is complete: it states the operation, gives decision triggers, names the related list_folders tool, and documents the response shape. Annotations cover the safety and idempotency profile, so the agent has enough to call it correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The input schema has 100% parameter description coverage, including details like q substring matching, limit range, folder_id 'none' semantics, and default values. The description only summarizes the filter categories and adds no new parameter-level meaning, so the baseline of 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The first sentence clearly states the operation ('List all intakes in your account') and the filter dimensions available ('status, client email, folder, or a text search'). The return-shape sentence reinforces that this is a list-level tool, distinguishing it from single-intake siblings like get_intake_status and get_intake_results.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The second paragraph gives concrete use cases: overviewing active projects, finding an intake by client email when intake_id is lost, counting in-progress intakes, and inspecting a folder from list_folders. It does not explicitly tell the agent when not to use this tool or route to single-intake alternatives, but the context is clear enough to guide selection.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

loginSign in to BriefGateA

Sign in without pasting an API key, the same way snyk_auth works: this opens a browser page where a human approves this device, then stores the issued key locally.

Call this whenever a tool reports "Not signed in" or that the stored key was revoked or expired.

This is a TWO-PHASE tool because approval can take minutes — longer than a single tool call should block for:

  1. The first call starts the sign-in and returns immediately with a URL and a short code (e.g. "WDJB-MJHT"). Tell the user to open the URL and confirm the code; a browser is also opened automatically when possible.

  2. Call login again (no arguments change) to check progress. While the human hasn't approved yet, it replies that it's still waiting. Once approved, the same call reports success and the key is saved — no further action needed, other tools start working immediately.

Do not wait silently for minutes on one call — call this tool again after telling the user to approve, and again if they say they've clicked Allow.

Has no effect if a key is already supplied via the --api-key flag or the BRIEFGATE_API_KEY environment variable — those always take priority over a locally stored one, so this tool says so instead of running the flow. Not available when this server is running as the shared hosted endpoint (mcp.briefgate.dev): there, connecting a client already triggers OAuth automatically.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

TDQS

A4.9/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Beyond annotations, the description explains that this is a two-phase flow, that the first call returns a URL and code, that re-calling checks approval status, and that the key is stored locally once approved. It also discloses that it has no effect when env/flag auth exists and that the hosted endpoint auto-triggers OAuth, giving a very complete behavioral picture.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Although the description is long, it is necessary for a non-trivial two-phase flow. It is well-structured with a summary, trigger conditions, numbered phases, and explicit exclusions. Every sentence carries operational information, and the key trigger condition is front-loaded.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a multi-phase tool with no output schema, the description covers the full flow: what happens on first call, what the agent should tell the user, what happens on subsequent calls, and when the tool is not applicable. An agent has enough information to call it correctly and to guide the user through approval.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool has zero parameters, so the input schema leaves nothing to document; the baseline for no parameters is 4. The description reinforces this by saying repeated calls use 'no arguments change,' which is helpful for agents deciding how to invoke the second phase.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states the specific action ('Sign in without pasting an API key') and the mechanism (browser-based human approval, then local key storage). It is clearly distinct from siblings like logout and the rest of the toolset, and even references a comparable flow (snyk_auth) to anchor meaning.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description explicitly says when to call this tool: 'whenever a tool reports “Not signed in” or that the stored key was revoked or expired.' It also gives exclusions: if an API key flag or environment variable is present, and if running on the shared hosted endpoint, the tool should not run the flow.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

logoutSign out of BriefGateA
DestructiveIdempotent

Remove the API key login stored locally for this BriefGate server, and best-effort revoke it on the server too (a DELETE /v1/keys/current call using that same key). If the revoke call fails — no network, the API is unreachable — the local copy is still removed; the response says so and points at the BriefGate dashboard to revoke it there instead. Not available when this server is running as the shared hosted endpoint.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

TDQS

A4.7/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The description goes well beyond the annotations: it explains the local removal, the server-side DELETE call, the best-effort revocation behavior, what happens when the revoke fails, and where to revoke manually if needed. This aligns with and enriches destructiveHint and readOnlyHint rather than merely repeating them.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three compact sentences pack in the action, the HTTP detail, the failure mode, the fallback guidance, and the availability constraint. Every sentence earns its place and there is no redundant wording.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a zero-parameter, no-output-schema tool, the description is complete: it covers what happens, how it happens, what happens on failure, and when it cannot be used. No critical information is missing for an agent to invoke it correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool has zero parameters, so there is nothing for the description to add beyond the schema. Baseline 4 is appropriate because the description correctly focuses on behavior instead of inventing parameter details.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific action: remove the locally stored API key and best-effort revoke it on the server. It also distinguishes itself by explaining exactly what is modified, so an agent can clearly tell logout from the sibling login tool.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides clear behavioral context and an explicit when-not: the tool is unavailable on the shared hosted endpoint. It does not explicitly contrast with alternatives, but for a logout operation the purpose is unambiguous and the failure mode is fully explained.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

manage_recipientsManage intake recipientsA
Destructive

Add, remove, or reinstate a person who receives an intake's invite and reminders, alongside or instead of the primary client.

action="add" invites another address the same way also_notify does at define_intake time — its own message, its own bounce state; pass name to address it by name. action="remove" stops future reminders to that address. action="reinstate" is for a bounce that was wrong — the person did get the e-mail — and clears the bounce flag so reminders resume; if that address was the only one still being chased, the schedule is re-planned from now.

Fails if the address is not on the intake, or — for reinstate — if it never bounced in the first place.

ParametersJSON Schema
NameRequiredDescriptionDefault
nameNoTheir name, used to address their copy. Only used with action="add".
emailYesThe recipient's e-mail address.
actionYesWhat to do with the address.
intake_idYesIntake ID returned by define_intake.

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A4.7/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The description reveals important side effects beyond the annotations: it stops future reminders, clears the bounce flag, re-plans the schedule if the reinstated address was the only one being chased, and fails under specific conditions. This gives the agent a much clearer picture of what mutating the recipient list entails.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is front-loaded with the overall purpose, then unpacks each action in turn, and ends with failure conditions. There is no filler or repetition of schema text; every sentence contributes a distinct fact needed to choose and invoke an action.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With an output schema present and annotations already signaling mutation, destructiveness, and idempotency, the description covers all operational essentials: action semantics, parameter roles, error conditions, and schedule re-planning. An agent has enough to call the tool correctly without additional inference.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so parameters are already documented. The description adds useful semantic detail by explaining what each action actually does to the recipient and when name is relevant, which goes beyond the schema's brief field descriptions.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The opening sentence names the resource (intake recipients) and the three operations (add, remove, reinstate), making the tool's purpose specific. It also distinguishes this from creation-time recipient setup by referencing 'also_notify does at define_intake time', so an agent understands this manages recipients after intake creation.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Each action is tied to a concrete scenario: add invites, remove stops reminders, and reinstate clears a wrong bounce. The reference to define_intake's also_notify gives context for when this tool is the right post-creation choice, though it stops short of explicitly saying 'do not use for initial recipient setup'.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

manage_webhookManage webhook endpointsA
Destructive

Register, list, or remove a webhook endpoint so BriefGate pushes intake events to your service instead of you polling for them.

Use this ONLY if you control a service that can receive public HTTPS requests. An agent running in a terminal cannot — for that case do not register anything and check on a schedule with get_intake_status instead. A registered endpoint that cannot receive produces failing deliveries and a false impression that the work is being watched.

action="create" returns a signing "secret" exactly once. The receiving service needs it to verify the signature on every delivery (verifyWebhookSignature from @briefgate/mcp/webhook), and it cannot be shown again. If it is ever exposed, there is no rotation in place — delete the endpoint and create a new one, which issues a fresh secret.

Events: intake.completed (all required items in — the one to act on), item.submitted (a single item arrived), client.viewed (the client opened the portal), chase.bounced (a reminder failed to deliver), intake.overdue (the due date passed with required items outstanding — the one to act on when work is blocked), intake.stalled (fires only when the intake sets max_reminders; without it this event never arrives).

ParametersJSON Schema
NameRequiredDescriptionDefault
urlNoHTTPS endpoint to deliver to. Required for action="create".
actionYesWhat to do. "list" needs no other argument.
eventsNoEvents to receive. Required for action="create". For "tell me when the client is done", this is ["intake.completed"].
formatNoPayload shape. "raw" (default) is the signed BriefGate envelope; "slack" and "discord" post a message those services render directly.
webhook_idNoEndpoint to remove. Required for action="delete".

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The description goes well beyond the annotations. It discloses that the signing secret is returned exactly once and cannot be shown again, that there is no rotation, and that the remedy is to delete and recreate. It also explains event semantics, including that intake.stalled only fires when max_reminders is set. This is rich behavioral context that annotations alone do not provide.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is well-structured with clear paragraphs for usage, secret handling, and events. It is longer than average, but every sentence carries important information. The front-loading of the core purpose and the explicit usage condition is effective. Slight deduction for the event list being somewhat dense, but it earns its place.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's complexity (5 params, 3 actions, 6 events, security implications), the description covers all critical aspects: when to use, what the secret is, event semantics, and action-specific parameter requirements. The output schema exists, so return values don't need explanation. Nothing essential is missing for an agent to invoke this tool correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3. The description adds value by explaining the action-specific requirements (url required for create, webhook_id for delete), the meaning of the events array with a concrete example (["intake.completed"]), and the format options. It doesn't fully document every parameter's edge cases, but it meaningfully supplements the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a specific verb and resource ('Register, list, or remove a webhook endpoint') and immediately states the purpose: pushing intake events instead of polling. It clearly distinguishes itself from sibling tools like get_intake_status by naming the alternative and the condition that selects it.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description explicitly says when to use this tool ('ONLY if you control a service that can receive public HTTPS requests') and when not to ('An agent running in a terminal cannot — for that case do not register anything and check on a schedule with get_intake_status instead'). It also warns about the consequence of misuse (failing deliveries and false impression). This is exemplary usage guidance.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

request_revisionRequest a revisionA

Ask the client to resubmit a specific item with a note explaining what is wrong.

Use this after reviewing get_intake_results and finding an item that does not meet requirements — for example a blurry logo, copy that is too long, or a broken URL. The client is notified automatically and the item status moves to needs_revision.

Returns { status: "revision_requested", item_key }.

ParametersJSON Schema
NameRequiredDescriptionDefault
noteYesPlain-language explanation shown to the client (e.g. "Logo is blurry — we need at least 512 px wide in SVG or PNG with a transparent background").
item_keyYesThe key of the item to revise (e.g. "logo", "hero_copy").
intake_idYesIntake ID returned by define_intake.

Output Schema

ParametersJSON Schema
NameRequiredDescription
statusNo
item_keyNo

TDQS

A4.1/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The description discloses the key behavioral side effects: the client is notified automatically and the item status moves to needs_revision, and it returns a specific object { status: 'revision_requested', item_key }. These details go beyond the annotations, which only indicate non-read-only, non-idempotent, non-destructive, and open-world behavior. The description also implies a write operation that cannot be idempotent, which aligns with annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is brief and front-loaded: the first sentence states the core action, the second gives usage context with examples, and the third reports the return value. No extraneous content; every sentence carries essential information.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The description covers purpose, when to use, side effects, and return value. With the output schema already available and all parameters documented, this is sufficient for an agent to correctly invoke the tool in the expected workflow.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The input schema already describes all three required parameters (intake_id, item_key, note) with explanations, achieving 100% coverage. The description's examples (e.g., blurry logo, copy too long) illustrate usage but do not add new semantic constraints beyond what the schema provides, so it earns the baseline score.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's action: asking the client to resubmit a specific item with an explanatory note. It provides concrete examples of what constitutes a revision-worthy item (blurry logo, overlong copy, broken URL), which helps the agent understand the intent. However, it does not explicitly differentiate from sibling tools like update_item, relying on the distinct verb 'request revision' rather than naming alternatives.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description explicitly states when to use the tool: after reviewing get_intake_results and finding an item that does not meet requirements. It also notes the automatic client notification and status change to needs_revision. This gives clear usage context, but it doesn't provide when-not conditions or mention alternative tools such as update_item for direct edits.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

send_chaseSend a reminderA

Send a manual reminder to the client outside the automatic schedule.

Use when a deadline is approaching and the client has not responded to automatic reminders, or when you want to send an SMS after email attempts have failed. The automatic chase schedule continues after this call — this is an extra nudge, not a replacement.

Returns { sent: true }.

ParametersJSON Schema
NameRequiredDescriptionDefault
channelNoDelivery channel. Email is the only one offered.
intake_idYesIntake ID returned by define_intake.

Output Schema

ParametersJSON Schema
NameRequiredDescription
sentNo

TDQS

A4.4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With annotations indicating it is not read-only (readOnlyHint=false), not idempotent, and not destructive, the description adds value by noting that the automatic schedule continues after this call, preventing the agent from thinking it disables automation. It also discloses the return value, though the output schema already provides that. The description doesn't mention potential side effects like duplicate reminders, but with the annotations, the bar is lower, and this is adequate.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is concise with three sentences, front-loading the core purpose and usage context. It includes the return value in the last sentence, which is efficiently placed. No redundant information; every sentence contributes to understanding.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's moderate complexity, the description covers purpose, usage, behavioral notes, and return value. The output schema already documents parameters and return type, so no further detail is needed. It could mention that channel is always 'email' but that's in the schema; the description is complete enough.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The schema already documents both parameters: channel is an enum limited to 'email', and intake_id references define_intake. The description does not add additional meaning beyond that, but since schema coverage is 100%, the baseline of 3 is appropriate. No further explanation is needed.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the verb (send), the resource (reminder), and the context (manual nudge outside automatic schedule). It distinguishes itself from automatic reminders and from sibling tools like update_intake or get_intake_status, so an agent can confidently select it for this specific action.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It explicitly specifies when to use: when a deadline is approaching and the client has not responded, or when SMS is needed after email failures. It also clarifies that the automatic schedule continues, so the agent knows this is an extra nudge, not a replacement. This directly guides decision-making among alternatives.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

update_intakeEdit intake settingsA

Change settings on an intake that has already been sent — project name, due date, reminder cadence, quiet hours, which folder it's in, the client brief, or the client's name, phone, language, and timezone.

Use this instead of deleting and recreating the intake when a deadline moves or the chase cadence needs to change. If any of chase_schedule, chase_interval, chase_interval_unit, chase_at_time, max_reminders, respect_quiet_hours, due_date, or client.timezone is included, every pending reminder is cancelled and the schedule is re-planned from now — reminders already sent still count toward max_reminders. Raising max_reminders (or setting it to "unlimited") past the number already sent on a stalled intake reactivates it and resumes chasing.

The client's e-mail address cannot be changed here — the portal link and login are bound to it. Use manage_recipients to add, remove, or reinstate an address.

folder_id moves the intake to a different folder (an id from list_folders); set it to null to remove the intake from any folder. It never touches the chase schedule.

client_brief replaces the free-text brief shown to the client above the requested items; set it to null to clear it. Documents attached to the brief are managed via the dashboard or the REST endpoint POST /v1/intakes/:id/brief/files, not through this tool.

Fails if the intake is archived. At least one field must be given. Returns the full, updated intake object.

ParametersJSON Schema
NameRequiredDescriptionDefault
clientNoClient fields to change. Email cannot be changed here — use manage_recipients.
due_dateNoDeadline in YYYY-MM-DD format. null clears it.
folder_idNoMove this intake to a different folder, using an id from list_folders. null removes it from any folder.
intake_idYesIntake ID returned by define_intake.
owner_noteNoPrivate note, never shown to the client. null clears it.
client_briefNoFree-text brief shown to the client at the top of the portal, above the requested items — information from you to them: an offer, instructions, or context. Up to 5000 characters. null clears it. Documents attached to the brief go through the REST endpoint POST /v1/intakes/:id/brief/files (dashboard or REST — not available through this MCP tool set).
project_nameNoHuman-readable project name shown to the client.
chase_at_timeNoAnchor reminders to this 24-hour local time in the client's timezone (e.g. "07:00"), overriding quiet hours. null clears it.
max_remindersNoCap on reminder attempts (1-1000), or "unlimited". Raising this above the number already sent reactivates a stalled intake.
chase_intervalNoHow often to remind, only meaningful with chase_schedule="custom". Pair with chase_interval_unit.
chase_scheduleNoAutomated reminder cadence. default=T+2d,T+5d,T+9d,weekly. gentle=T+3d,T+8d,biweekly. aggressive=T+1d,T+3d,T+5d,every-other-day. custom=every chase_interval chase_interval_unit. off=no auto reminders.
chase_interval_unitNoUnit for chase_interval.
respect_quiet_hoursNoWhether reminders pause outside 08:00-19:00 in the client's timezone.

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A4.9/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Beyond the annotations, it discloses critical side effects: including certain fields cancels all pending reminders and re-plans the schedule, raising max_reminders reactivates a stalled intake, and folder_id never touches the chase schedule. It also explains the email limitation and document attachment route, giving an agent accurate expectations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is long but every sentence carries operational value, and the main purpose is front-loaded before the caveats. It efficiently packs usage guidance, exclusions, side effects, and failure conditions without repetition.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a tool with 13 parameters, nested objects, and significant side effects, the description covers input requirements, when to use alternatives, error conditions, and special cases like archived intakes and stalled reminders. The output schema exists, so not describing the return value is acceptable.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the baseline is 3, but the description adds cross-parameter behavior not visible in the schema: the reminder re-planning trigger, max_reminders reactivation semantics, and null-clearing behavior for folder_id and client_brief. This adds genuine meaning beyond the individual parameter descriptions.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description uses a specific verb and resource ('Change settings on an intake that has already been sent') and enumerates exactly which fields can be modified. It clearly distinguishes itself from siblings like define_intake and manage_recipients by stating what this tool can and cannot do.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It explicitly says to use this instead of deleting and recreating an intake when deadlines or chase cadence change, and it names manage_recipients as the alternative for changing the client email. It also gives constraints: fails if archived and at least one field must be provided.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

update_itemEdit an itemA
Destructive

Change one item on an intake that is already with the client — its type, label, hint, whether it is required, and which file formats it accepts.

Reach for this when the field turns out to be the wrong shape: you asked for an image and the client only has their logo as a PDF, or what you asked for as a line of text is really a file. Widening the accepted formats or switching the type unblocks them without adding a duplicate item and waiving the original.

The item key cannot be changed — results come back under it, so renaming would break whatever reads them. Add a new item instead.

If the client has already answered and the change would make their answer invalid, the call fails and nothing is touched. Repeat it with discard_submitted_value: true to clear the answer and ask them again. A change that leaves their answer valid (a new label, a wider limit) never discards anything.

ParametersJSON Schema
NameRequiredDescriptionDefault
helpNoHint under the label. null clears it.
typeNo
labelNoHuman-readable label shown to the client.
optionsNo
patternNo
item_keyYesKey of the item to change.
requiredNo
intake_idYesIntake ID returned by define_intake.
constraintsNoSame shape as define_intake, e.g. { "formats": ["svg","png","pdf"] }. null clears all constraints.
discard_submitted_valueNoGo ahead even though it throws away what the client already sent. Only set this after the call has failed once for that reason.

Output Schema

ParametersJSON Schema
NameRequiredDescription
itemNo
discarded_submitted_valueNo

TDQS

A4.9/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Beyond the annotations' destructiveHint, the description discloses critical behavior: item keys are immutable because results are keyed by them, invalidating a submitted answer fails the call without side effects, and only discard_submitted_value: true clears the answer. This gives the agent a clear mental model of failure modes and idempotency.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is front-loaded with the core purpose, then builds context through usage scenarios, a hard constraint, and failure semantics. Every paragraph serves a distinct function with no filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a destructive 10-parameter mutation tool, the description covers the essential operational context: when to use it, what cannot change, how invalidations behave, and how to force a resubmission. The output schema covers return values, so nothing critical is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

With 60% schema coverage, the description adds meaningful semantics for type switching, required flags, accepted formats, and the discard_submitted_value escape hatch. It does not elaborate on options or pattern, but the description significantly strengthens understanding of the key mutation parameters.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a specific verb and resource: 'Change one item on an intake that is already with the client' and enumerates exactly which attributes can change (type, label, hint, required, accepted formats). This clearly distinguishes it from siblings like add_items and update_intake.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It gives concrete when-to-use guidance ('Reach for this when the field turns out to be the wrong shape'), explains the alternative approach ('Add a new item instead' when the key must change), and details the discard_submitted_value workflow after a failed call. No ambiguity remains about when this tool is appropriate.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 13 tool updatesv0.10.5
    • Changedadd_items1 field changed
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "properties": {
        +    "folder_id": {
        +      "type": [
        +        "string",
        +        "null"
        +      ]
        +    },
        +    "follow_up": {
        +      "description": "How to learn this intake is done — present unless a webhook already covers it. Mirrors FollowUpAdvice in client.ts.",
        +      "properties": {
        +        "reason": {
        +          "type": "string"
        +        },
        +        "recommended": {
        +          "enum": [
        +            "webhook",
        +            "schedule"
        +          ],
        +          "type": "string"
        +        },
        +        "schedule": {
        +          "properties": {
        +            "check_with": {
        +              "type": "string"
        +            },
        +            "every_hours": {
        +              "type": "number"
        +            },
        +            "until": {
        +              "type": "string"
        +            }
        +          },
        +          "type": "object"
        +        },
        +        "webhook": {
        +          "properties": {
        +            "active_endpoints": {
        +              "type": "number"
        +            },
        +            "events": {
        +              "items": {
        +                "type": "string"
        +              },
        +              "type": "array"
        +            },
        +            "register_with": {
        +              "type": "string"
        +            }
        +          },
        +          "type": "object"
        +        }
        +      },
        +      "type": "object"
        +    },
        +    "intake_id": {
        +      "type": "string"
        +    },
        +    "items": {
        +      "items": {
        +        "properties": {
        +          "key": {
        +            "type": "string"
        +          },
        +          "status": {
        +            "enum": [
        +              "pending",
        +              "submitted",
        +              "needs_revision",
        +              "approved"
        +            ],
        +            "type": "string"
        +          }
        +        },
        +        "type": "object"
        +      },
        +      "type": "array"
        +    },
        +    "portal_url": {
        +      "type": "string"
        +    },
        +    "status": {
        +      "enum": [
        +        "draft",
        +        "sent",
        +        "in_progress",
        +        "completed",
        +        "archived"
        +      ],
        +      "type": "string"
        +    }
        +  },
        +  "type": "object"
        +}
    • Changedcreate_folder1 field changed
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "properties": {
        +    "created_at": {
        +      "type": [
        +        "string",
        +        "null"
        +      ]
        +    },
        +    "id": {
        +      "type": "string"
        +    },
        +    "intake_count": {
        +      "type": "number"
        +    },
        +    "name": {
        +      "type": "string"
        +    },
        +    "sort_order": {
        +      "type": "number"
        +    }
        +  },
        +  "type": "object"
        +}
    • Changeddefine_intake1 field changed
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "properties": {
        +    "follow_up": {
        +      "description": "How to learn this intake is done — present unless a webhook already covers it. Mirrors FollowUpAdvice in client.ts.",
        +      "properties": {
        +        "reason": {
        +          "type": "string"
        +        },
        +        "recommended": {
        +          "enum": [
        +            "webhook",
        +            "schedule"
        +          ],
        +          "type": "string"
        +        },
        +        "schedule": {
        +          "properties": {
        +            "check_with": {
        +              "type": "string"
        +            },
        +            "every_hours": {
        +              "type": "number"
        +            },
        +            "until": {
        +              "type": "string"
        +            }
        +          },
        +          "type": "object"
        +        },
        +        "webhook": {
        +          "properties": {
        +            "active_endpoints": {
        +              "type": "number"
        +            },
        +            "events": {
        +              "items": {
        +                "type": "string"
        +              },
        +              "type": "array"
        +            },
        +            "register_with": {
        +              "type": "string"
        +            }
        +          },
        +          "type": "object"
        +        }
        +      },
        +      "type": "object"
        +    },
        +    "intake_id": {
        +      "type": "string"
        +    },
        +    "notices": {
        +      "description": "Cadence caveats, present only when chase_schedule=\"custom\" makes them relevant.",
        +      "items": {
        +        "type": "string"
        +      },
        +      "type": "array"
        +    },
        +    "portal_url": {
        +      "type": "string"
        +    },
        +    "status": {
        +      "enum": [
        +        "draft",
        +        "sent",
        +        "in_progress",
        +        "completed",
        +        "archived"
        +      ],
        +      "type": "string"
        +    }
        +  },
        +  "type": "object"
        +}
    • Changedget_intake_results1 field changed
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "properties": {
        +    "intake_id": {
        +      "type": "string"
        +    },
        +    "meta": {
        +      "additionalProperties": {
        +        "additionalProperties": true,
        +        "properties": {
        +          "decided_by": {
        +            "description": "Only present for an assignee=owner decision item.",
        +            "enum": [
        +              "owner",
        +              "agent_proposal"
        +            ],
        +            "type": "string"
        +          },
        +          "first_reveal": {
        +            "description": "Only present for type=secret: true on the call that reveals the plaintext value, false after.",
        +            "type": "boolean"
        +          },
        +          "status": {
        +            "enum": [
        +              "pending",
        +              "submitted",
        +              "needs_revision",
        +              "approved"
        +            ],
        +            "type": "string"
        +          },
        +          "submitted_at": {
        +            "type": [
        +              "string",
        +              "null"
        +            ]
        +          },
        +          "type": {
        +            "enum": [
        +              "text",
        +              "longtext",
        +              "file",
        +              "file_list",
        +              "image",
        +              "color_list",
        +              "select",
        +              "multiselect",
        +              "boolean",
        +              "url",
        +              "secret",
        +              "structured"
        +            ],
        +            "type": "string"
        +          }
        +        },
        +        "required": [
        +          "type",
        +          "status"
        +        ],
        +        "type": "object"
        +      },
        +      "description": "Keyed the same as results.",
        +      "type": "object"
        +    },
        +    "results": {
        +      "additionalProperties": true,
        +      "description": "Keyed by this intake's own item keys. A value's shape depends on that item's type.",
        +      "type": "object"
        +    },
        +    "status": {
        +      "enum": [
        +        "draft",
        +        "sent",
        +        "in_progress",
        +        "completed",
        +        "archived"
        +      ],
        +      "type": "string"
        +    }
        +  },
        +  "required": [
        +    "intake_id",
        +    "status",
        +    "results",
        +    "meta"
        +  ],
        +  "type": "object"
        +}
    • Changedget_intake_status1 field changed
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "properties": {
        +    "chases": {
        +      "items": {
        +        "properties": {
        +          "attempt_no": {
        +            "type": "number"
        +          },
        +          "channel": {
        +            "type": "string"
        +          },
        +          "sent_at": {
        +            "type": [
        +              "string",
        +              "null"
        +            ]
        +          },
        +          "status": {
        +            "type": "string"
        +          }
        +        },
        +        "required": [
        +          "channel",
        +          "status",
        +          "attempt_no"
        +        ],
        +        "type": "object"
        +      },
        +      "type": "array"
        +    },
        +    "client_brief": {
        +      "description": "The brief shown to the client above the requested items, if one is set.",
        +      "type": "string"
        +    },
        +    "client_last_seen": {
        +      "type": [
        +        "string",
        +        "null"
        +      ]
        +    },
        +    "due_date": {
        +      "type": [
        +        "string",
        +        "null"
        +      ]
        +    },
        +    "intake_id": {
        +      "type": "string"
        +    },
        +    "items": {
        +      "items": {
        +        "properties": {
        +          "key": {
        +            "type": "string"
        +          },
        +          "label": {
        +            "type": "string"
        +          },
        +          "status": {
        +            "enum": [
        +              "pending",
        +              "submitted",
        +              "needs_revision",
        +              "approved"
        +            ],
        +            "type": "string"
        +          },
        +          "submitted_at": {
        +            "type": [
        +              "string",
        +              "null"
        +            ]
        +          }
        +        },
        +        "required": [
        +          "key",
        +          "status",
        +          "label"
        +        ],
        +        "type": "object"
        +      },
        +      "type": "array"
        +    },
        +    "progress": {
        +      "properties": {
        +        "submitted": {
        +          "type": "number"
        +        },
        +        "total": {
        +          "type": "number"
        +        }
        +      },
        +      "required": [
        +        "submitted",
        +        "total"
        +      ],
        +      "type": "object"
        +    },
        +    "status": {
        +      "enum": [
        +        "draft",
        +        "sent",
        +        "in_progress",
        +        "completed",
        +        "archived"
        +      ],
        +      "type": "string"
        +    }
        +  },
        +  "required": [
        +    "status",
        +    "progress",
        +    "items",
        +    "chases"
        +  ],
        +  "type": "object"
        +}
    • Changedlist_folders1 field changed
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "properties": {
        +    "folders": {
        +      "items": {
        +        "properties": {
        +          "created_at": {
        +            "type": [
        +              "string",
        +              "null"
        +            ]
        +          },
        +          "id": {
        +            "type": "string"
        +          },
        +          "intake_count": {
        +            "type": "number"
        +          },
        +          "name": {
        +            "type": "string"
        +          },
        +          "sort_order": {
        +            "type": "number"
        +          }
        +        },
        +        "required": [
        +          "id",
        +          "name",
        +          "sort_order",
        +          "intake_count",
        +          "created_at"
        +        ],
        +        "type": "object"
        +      },
        +      "type": "array"
        +    }
        +  },
        +  "required": [
        +    "folders"
        +  ],
        +  "type": "object"
        +}
    • Changedlist_intakes1 field changed
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "properties": {
        +    "intakes": {
        +      "items": {
        +        "properties": {
        +          "client_email": {
        +            "type": "string"
        +          },
        +          "created_at": {
        +            "type": [
        +              "string",
        +              "null"
        +            ]
        +          },
        +          "due_date": {
        +            "type": [
        +              "string",
        +              "null"
        +            ]
        +          },
        +          "folder_id": {
        +            "type": [
        +              "string",
        +              "null"
        +            ]
        +          },
        +          "intake_id": {
        +            "type": "string"
        +          },
        +          "portal_url": {
        +            "type": "string"
        +          },
        +          "project_name": {
        +            "type": "string"
        +          },
        +          "status": {
        +            "enum": [
        +              "draft",
        +              "sent",
        +              "in_progress",
        +              "completed",
        +              "archived"
        +            ],
        +            "type": "string"
        +          }
        +        },
        +        "required": [
        +          "intake_id",
        +          "project_name",
        +          "status",
        +          "created_at",
        +          "portal_url"
        +        ],
        +        "type": "object"
        +      },
        +      "type": "array"
        +    },
        +    "total": {
        +      "type": "number"
        +    }
        +  },
        +  "required": [
        +    "intakes",
        +    "total"
        +  ],
        +  "type": "object"
        +}
    • Changedmanage_recipients1 field changed
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "anyOf": [
        +    {
        +      "additionalProperties": true,
        +      "type": "object"
        +    },
        +    {
        +      "properties": {
        +        "bounced_at": {
        +          "type": "null"
        +        },
        +        "email": {
        +          "type": "string"
        +        },
        +        "still_chasing": {
        +          "type": "boolean"
        +        }
        +      },
        +      "type": "object"
        +    }
        +  ],
        +  "type": "object"
        +}
    • Changedmanage_webhook1 field changed
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "anyOf": [
        +    {
        +      "properties": {
        +        "active": {
        +          "type": "boolean"
        +        },
        +        "created_at": {
        +          "type": "string"
        +        },
        +        "events": {
        +          "items": {
        +            "type": "string"
        +          },
        +          "type": "array"
        +        },
        +        "format": {
        +          "type": "string"
        +        },
        +        "id": {
        +          "type": "string"
        +        },
        +        "note": {
        +          "type": "string"
        +        },
        +        "secret": {
        +          "type": "string"
        +        },
        +        "url": {
        +          "type": "string"
        +        }
        +      },
        +      "type": "object"
        +    },
        +    {
        +      "properties": {
        +        "webhooks": {
        +          "items": {
        +            "properties": {
        +              "active": {
        +                "type": "boolean"
        +              },
        +              "created_at": {
        +                "type": "string"
        +              },
        +              "events": {
        +                "items": {
        +                  "type": "string"
        +                },
        +                "type": "array"
        +              },
        +              "format": {
        +                "type": "string"
        +              },
        +              "id": {
        +                "type": "string"
        +              },
        +              "url": {
        +                "type": "string"
        +              }
        +            },
        +            "type": "object"
        +          },
        +          "type": "array"
        +        }
        +      },
        +      "type": "object"
        +    },
        +    {
        +      "properties": {
        +        "deleted": {
        +          "enum": [
        +            true
        +          ],
        +          "type": "boolean"
        +        }
        +      },
        +      "type": "object"
        +    }
        +  ],
        +  "type": "object"
        +}
    • Changedrequest_revision1 field changed
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "properties": {
        +    "item_key": {
        +      "type": "string"
        +    },
        +    "status": {
        +      "enum": [
        +        "revision_requested"
        +      ],
        +      "type": "string"
        +    }
        +  },
        +  "type": "object"
        +}
    • Changedsend_chase1 field changed
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "properties": {
        +    "sent": {
        +      "enum": [
        +        true
        +      ],
        +      "type": "boolean"
        +    }
        +  },
        +  "type": "object"
        +}
    • Changedupdate_intake1 field changed
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "additionalProperties": true,
        +  "minProperties": 1,
        +  "type": "object"
        +}
    • Changedupdate_item1 field changed
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "properties": {
        +    "discarded_submitted_value": {
        +      "type": "boolean"
        +    },
        +    "item": {
        +      "additionalProperties": true,
        +      "type": "object"
        +    }
        +  },
        +  "type": "object"
        +}
  2. 12 tool updatesv0.9.0
    • Changedadd_items1 field changed
      • changedInput schema / properties / items / items / properties / type / enum
        Previous value: -[
        -  "text",
        -  "longtext",
        -  "file",
        -  "file_list",
        -  "image",
        -  "color_list",
        -  "select",
        -  "boolean",
        -  "url",
        -  "secret",
        -  "structured"
        -]New value: +[
        +  "text",
        +  "longtext",
        +  "file",
        +  "file_list",
        +  "image",
        +  "color_list",
        +  "select",
        +  "multiselect",
        +  "boolean",
        +  "url",
        +  "secret",
        +  "structured"
        +]
    • Addedcreate_folder
    • Changeddefine_intake7 fields changed
      • addedInput schema / properties / client / properties / also_notify
        Added value: +{
        +  "description": "Other people who should receive the same invitation and the same reminders, through the same portal link — two directors of one company, say, where it does not matter which of them supplies the material. Each address gets its own message (nobody sees the others) and its own bounce state, so one dead address does not stop the rest being chased. At most 4, on top of the primary client.",
        +  "items": {
        +    "properties": {
        +      "email": {
        +        "description": "Their email address.",
        +        "type": "string"
        +      },
        +      "name": {
        +        "description": "Their name, used to address their copy.",
        +        "type": "string"
        +      }
        +    },
        +    "required": [
        +      "email"
        +    ],
        +    "type": "object"
        +  },
        +  "type": "array"
        +}
      • changedInput schema / properties / client / properties / name / description
        Previous value: -"Client name for personalised emails."New value: +"Client name (required). Every email opens by addressing them by name, so a blank one sends \"Hello,\" to someone being asked for their admin password."
      • changedInput schema / properties / client / required
        Previous value: -[
        -  "email"
        -]New value: +[
        +  "email",
        +  "name"
        +]
      • addedInput schema / properties / client_brief
        Added value: +{
        +  "description": "Free-text brief shown to the client at the top of the portal, above the requested items — information from you to them: an offer, instructions, or context for why you are asking for these items. Up to 5000 characters. Documents attached to the brief go through the REST endpoint POST /v1/intakes/:id/brief/files (dashboard or REST — not available through this MCP tool set).",
        +  "maxLength": 5000,
        +  "type": "string"
        +}
      • addedInput schema / properties / folder_id
        Added value: +{
        +  "description": "Put this intake in an existing folder from list_folders instead of leaving it unfiled. Folders group intakes by client or project — reuse one for a returning client rather than creating a duplicate with create_folder.",
        +  "type": "string"
        +}
      • addedInput schema / properties / items / items / properties / assignee
        Added value: +{
        +  "description": "Who owes this. \"client\" (the default) is something the client fills in through the portal. \"owner\" is a private to-do for the account holder, e.g. \"call the client\": it never appears in the client portal, is never mentioned in a reminder, and never holds up completion of the intake. Use type \"boolean\" for a plain tick-off task. Owner items cannot use type file, file_list, image or secret.",
        +  "enum": [
        +    "client",
        +    "owner"
        +  ],
        +  "type": "string"
        +}
      • changedInput schema / properties / items / items / properties / type / enum
        Previous value: -[
        -  "text",
        -  "longtext",
        -  "file",
        -  "file_list",
        -  "image",
        -  "color_list",
        -  "select",
        -  "boolean",
        -  "url",
        -  "secret",
        -  "structured"
        -]New value: +[
        +  "text",
        +  "longtext",
        +  "file",
        +  "file_list",
        +  "image",
        +  "color_list",
        +  "select",
        +  "multiselect",
        +  "boolean",
        +  "url",
        +  "secret",
        +  "structured"
        +]
    • Addedlist_folders
    • Changedlist_intakes2 fields changed
      • addedInput schema / properties / folder_id
        Added value: +{
        +  "description": "Filter by folder, using an id from list_folders. Pass the literal string \"none\" to see only intakes that are not in any folder.",
        +  "type": "string"
        +}
      • addedInput schema / properties / q
        Added value: +{
        +  "description": "Free-text search: matches a substring of project name, client name, or client email.",
        +  "type": "string"
        +}
    • Addedlogin
    • Addedlogout
    • Addedmanage_recipients
    • Addedmanage_webhook
    • Changedsend_chase2 fields changed
      • changedInput schema / properties / channel / description
        Previous value: -"Delivery channel. Default: email. Use \"sms\" only if the client provided a phone number and has not responded to emails."New value: +"Delivery channel. Email is the only one offered."
      • changedInput schema / properties / channel / enum
        Previous value: -[
        -  "email",
        -  "sms"
        -]New value: +[
        +  "email"
        +]
    • Addedupdate_intake
    • Addedupdate_item
  3. 7 tool updatesv0.2.1
    • First observedadd_items
    • First observeddefine_intake
    • First observedget_intake_results
    • First observedget_intake_status
    • First observedlist_intakes
    • First observedrequest_revision
    • First observedsend_chase

TDQS

A4.5/5.0

Scored across 15 tools

Disambiguation5/5

Each tool targets a distinct resource and action: intake creation, status, results, revisions, reminders, item management, recipients, webhooks, folders, and auth are all clearly separated. The only close neighbors—update_intake, update_item, and add_items—are disambiguated by operating on different objects and having different effects. No two tools appear to do the same thing.

Naming Consistency5/5

All tools use lowercase snake_case verb_noun naming, with get_, list_, create_, update_, manage_, and send_ prefixes applied predictably. The pattern is consistent across intake, item, folder, recipient, webhook, and auth operations. This makes the toolset easy for an agent to guess and select from.

Tool Count5/5

Fifteen tools is at the upper end of the ideal range but is justified by the server's scope: intake lifecycle, item management, revisions, reminders, recipients, webhooks, folders, and authentication each need dedicated tooling. None of the tools feel redundant, and the set is not bloated for the domain it covers.

Completeness4/5

The core intake workflow is well covered: create an intake, add and update items, monitor status, fetch results, request revisions, send manual chases, manage recipients, and receive webhook events. Minor gaps exist—there is no archive/delete tool for finished or cancelled intakes, and folder management is limited to list/create—but agents can work around these reasonably well.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers