Tool rules
The same rules hold for every Postwind tool. A tool is a thin wrapper over the use case of its API operation: validation, rights and errors are those of the API.
Names
- Names are
snake_case, verb first:create_list,launch_campaign. - One tool is one API operation, and the
operationIdof that operation equals the tool name.whoamiisGET /api/v1/whoami;get_server_infoisGET /api/v1/server. - Each tool page links to its operation, and each operation page links back.
Descriptions
Every description answers three questions, as sentences in this order:
- Call it … — when to call the tool.
- Needs … — what it takes: the kind of key, the scope, the state of the resources.
- Returns … — what comes back.
Each tool page shows these as three lines.
Arguments
- Arguments are strict: the input schema has
additionalProperties: false. An extra argument isvalidation_failed, like an unknown field in the API. - A tool without arguments takes
{}or no arguments at all. - Partial updates work like
PATCHin the API: an absent argument keeps the value,nullclears it.nullon an argument that cannot be cleared isvalidation_failed, like422with field codeinvalid_typein the API. The input schema of such an argument does not allownull. project_idfollows the rule of project context.
Output
structuredContentis exactly the body of the API operation’s answer, field for field. Every field is present; an empty one isnull.- A text block carries the same JSON, for clients that read only text.
- An API operation that answers
204without a body gives its tool an output ofoutcome,effectand the resource as itsGETreturns it after the call. See below.
Errors
A tool error is a result with isError: true, not a JSON-RPC error:
{
"isError": true,
"content": [{ "type": "text", "text": "not_found: …" }],
"structuredContent": { "code": "not_found", "detail": "…" }
}
codeanddetailare those the API puts in its problem+json. The codes are on the errors page.validation_failedandprecondition_failedadderrors: [{ "field", "code", "message" }], as422does in the API.- The text block is
"<code>: <detail>". - An argument of the wrong form or an extra argument is
validation_failedwith the parse message indetail. Unlike the API, it names the first violation of form only, withouterrors. Violations of values come back all at once, witherrors, as in the API. storage_timeoutmeans the change may still take effect. Check before you repeat a call that creates something.- JSON-RPC errors are left for an unknown tool and for transport failures.
- A bad key (
401), the rate limit (429) and the body limit (413) are answered at the HTTP level, byte for byte as the API.
Confirm and dry run
Ten tools change something that cannot be undone and take a confirm argument:
| Tool | outcome values |
|---|---|
launch_campaign |
would_launch, would_resume, launched, failed |
cancel_campaign |
would_cancel, cancelled, already_cancelled |
revoke_api_key |
would_revoke, revoked, already_revoked |
revoke_admin_key |
would_revoke, revoked, already_revoked |
delete_project |
would_delete, deleted, already_deleted |
delete_list |
would_delete, deleted, already_deleted |
delete_sending_domain |
would_delete, deleted |
erase_subscriber |
would_erase, erased |
add_suppression |
would_add, added, already_suppressed |
remove_suppression |
would_remove, removed |
confirmis a boolean and defaults tofalse.- Without
confirm: truethe call is a dry run. It checks everything the real call checks (the key, existence, preconditions) and writes nothing. It answers with thewould_…outcome,effectin words, and the resource as itsGETreturns it. - With
confirm: truethe call does the work and answers with the outcome and the resource after the call. - A deleted sending domain cannot be read, so
delete_sending_domainanswersdeletedwithdomain: null, and a repeat isnot_found. - A key has no
GETof its own.revoke_api_keyandrevoke_admin_keyreturn the key as the key list shows it after the call; their dry run also checks the rule of the last admin key. - Each tool page shows a dry-run example with
"confirm": false.
Limits
- List tools return at most 100 items per call:
limit1 to 100, default 50. A larger value isvalidation_failed, fieldlimit, codeout_of_range. The cursor is the same as in the API; see Pagination. import_subscriberstakes at most 1 000 records per call. More isvalidation_failedand nothing is written. The API takes up to 10 000 per request.- A request body is at most 10 MB, as in the API.
No idempotency key
MCP has no Idempotency-Key. When a call gets no answer, what a repeat does depends on the tool:
| Tool | A repeat |
|---|---|
create_project |
Fails with conflict: the slug is taken |
create_api_key, create_admin_key |
Creates a second key. Check list_api_keys or list_admin_keys and revoke the extra one |
create_list |
Creates a second list: list names need not be unique. Check list_lists and delete the extra one with delete_list |
create_campaign |
Creates a second campaign. Check list_campaigns first |
add_subscriber, import_subscribers |
Safe: the subscriber id comes from the list and the email address, so a repeat is the same subscriber |
Rate limit
Tools share the key’s budget with the API. Each JSON-RPC message counts: initialize, notifications/initialized, tools/list and each tools/call. See Rate limits.