Hosted MCP — 26 core tools + per-prompt synthesized tools
Every tool exposed at https://mcp.getpromethic.com/v1. All tools are
namespaced promethic_<name> on the wire (underscore separator —
Anthropic's Tool API name regex is ^[a-zA-Z0-9_-]{1,64}$). Connect with
your pmk_ key as a Bearer header (Claude Desktop, Cursor, Codex CLI) or
via OAuth (Claude.ai web/iOS, ChatGPT) — see /agents for
client-specific config.
Scopes:
discovery always allowed (no scope check) read needsread scope
execute needs execute scope
write needs write scope
Per-tool grants are opt-in hardening on top of scopes — see agent docs.
How Promethic works
The basic loop. A prompt is a reusable task definition — it packages a system prompt, a model, and parameters. Call run_prompt with user input and you get the model's output back. If the output isn't quite right, you have two options: revise — call revise_run with a correction instruction and the model generates a fresh output — or edit — call patch_record(output: "...") to record what you actually wanted the output to be. Finalize the run and the whole exchange becomes a permanent record.
Records store the work; deltas store the corrections. A record is the log of a run: input, final output, cost, and metadata. When you correct a run — by revising or by editing the output afterward — those corrections are stored as deltas attached to the record. Revision deltas (one per revise_run call) track each revision turn. An edit delta (from patch_record) captures the gap between what the model produced and what you wanted — the correction vector from raw model output to desired output.
Generate Prompt uses those correction vectors. Promethic's desktop "Generate Prompt" feature reads your records and their deltas, then rewrites the prompt template to produce better first-draft outputs going forward — the corrections get folded into a new prompt version. The more records with clear corrections, the better the result. This is why the data model explicitly distinguishes the raw model output (IntermediateOutput) from the desired output (FinalCopiedOutput).
The agent workflow. run_prompt → inspect output → finalize_run (or set autoFinalize: true) → patch_record(output: "corrected version") if needed → repeat. After ~10–50 records with corrections, the user runs Generate Prompt on the Promethic desktop app to incorporate the corrections into an improved prompt version. Use create_record(promptId, input, output) to seed examples without an LLM call — useful when you already know what the ideal (input, output) pair should look like.
Timestamps: all *AtUtc fields are UTC ISO 8601 strings (e.g. "2026-05-29T14:32:00Z"). Parse with new Date(str) or a date library.
Discovery 1 tool · always allowed
Fetch the model catalog. Two modes:
Without model_id (slim list): returns {"catalog": {"models": [...], "recommended_defaults": {...}}}. Each model entry includes model_id, display_name, capabilities, costs, and deprecated_at — everything you need to pick a model. recommended_defaults is a flat object ({model_id, reasoning_effort, …}) ready to paste into modelSettings; use catalog.recommended_defaults.model_id as a safe starting point. Response is ~10 KB. Cache this per session — catalog changes only on backend deploys.
With model_id (full options): returns {"catalog": {"model": {"model_id", "display_name", "options": [...]}}}. options is the fully-resolved parameter schema for that model — valid values, ranges, and provider defaults — exactly what modelSettingsJson accepts. Call this only when you need to build custom model settings beyond the defaults. Returns model_not_found if the ID is unknown.
If you encounter an invalid_model_settings error after a working session, refresh with a new get_catalog call — a backend deploy may have changed the catalog.
Prompts 5 tools
List the user's prompts (cursor-paginated, default 50, max 500). Returns { prompts: [...], nextCursor? } — nextCursor is absent on the final page (not null). Each item includes promptId, name, description ("" when not yet generated, non-empty string when generated — never null), outputModality, mcpToolName (null when not exposed as a per-prompt MCP tool; the synthesized tool name string when exposed — agents can call it directly by that name), exposeAsMcpTool (boolean — true if the prompt is toggled on as a per-prompt synthesized MCP tool; use POST /api/v2/prompts/{id}/mcp-toggle or the Promethic desktop/web app to change), updatedAtUtc. Identifier convention: all response objects use the typed resource name (promptId, versionId, recordId, attachmentId) not bare id. Grant-restricted keys see only granted prompts.
Fetch a prompt by promptId. Returns: promptId, name, abbreviation, currentVersionId, currentVersionStatus ('ok' | 'missing'), updatedAtUtc, and a currentVersion sub-object with promptText, modelSettingsJson, description, descriptionMode, and versionNumber. currentVersionStatus: 'missing' means the stored currentVersionId points to a deleted version — call list_versions + switch_current_version to repair, or create_version(setAsCurrent: true).
Create a new prompt with an initial version. Top-level fields: name, promptText, modelSettings (object: {model_id, parameters}). Returns {promptId, name, abbreviation, description, currentVersionId, updatedAtUtc} — description is always "" immediately after creation (the server-generated capability description arrives asynchronously; once the worker completes, description becomes a non-empty string — call get_version after a moment to read it). Call get_catalog for valid model_id values + parameter shapes. promptText must be a non-empty, non-whitespace string — empty strings and whitespace-only values are rejected with invalid_params. Optional: abbreviation, versionDescription (whitespace-only rejected with invalid_params), description (capability description for the MCP tool's description field — writing it sets descriptionMode to Manual; whitespace-only rejected with invalid_params; omit to let the server auto-generate from promptText), idempotencyKey (stable dedup string — same key returns the original result on retry instead of creating a duplicate prompt; scoped per user, 24-hour TTL). To seed a prompt's training data before writing the template body, create the prompt with a draft promptText and add records via create_record; update the template via update_version when ready.
Patch a prompt's metadata (name, abbreviation). RFC 7396 merge-patch semantics: omit a field to leave it unchanged. To clear abbreviation, pass JSON null (not the string "null" — empty string is stored literally). Serialization note: some MCP hosts coerce the string "null" to JSON null before sending, which clears the field. If you want to store the literal text "null" as an abbreviation, choose a different value. Note: capability descriptions are now per-version — use update_version to edit them.
Soft-delete a prompt. Cascades hide records/versions/attachments under it; sweep job hard-deletes later. Requires per-prompt grant if the API key is restricted. Idempotent on already-deleted: returns {promptId, status:"deleted"} rather than an error.
Versions 6 tools
List all versions of a prompt (cursor-paginated, default 25, max 100). Response includes currentVersionId at the top level plus each version's versionId, versionNumber, versionDescription (null if unset), description ("" if not yet generated, non-empty when generated — never null), descriptionMode (0 = Auto, 1 = Manual), and updatedAtUtc. Pagination: nextCursor is absent on the last page (field is omitted, not null). Stop paginating when nextCursor is not present in the response.
Fetch a single prompt version by id. Returns full promptText and modelSettingsJson. Fields are returned at the top level (no wrapper), consistent with get_prompt and other version tools.
Append a new version to an existing prompt. The new version's versionNumber auto-increments server-side. Pass setAsCurrent: true to also activate it (atomic create + switch in one transaction). Required: promptId, promptText, modelSettings (object: {model_id, parameters} — note: this is an object, unlike update_version's modelSettingsJson string). Optional: versionDescription, description (capability description — writing it sets descriptionMode to Manual; whitespace-only rejected with invalid_params; omit to let the server auto-generate), descriptionMode (0 = Auto, 1 = Manual — can be set independently without a description value), idempotencyKey (stable dedup string — same key returns the original result on retry instead of creating a duplicate version). Whitespace-only versionDescription is rejected with invalid_params. Response includes versionId, versionNumber, promptText, modelSettingsJson, outputModality, versionDescription, description, descriptionMode, isActive, and currentVersionId.
Patch a version's content and metadata. Accepts: promptText, modelSettingsJson, versionDescription (whitespace-only rejected with invalid_params), description, descriptionMode. RFC 7396 merge-patch — omit a field to leave it unchanged. promptText and modelSettings edit in place (Avalonia's autosave does this constantly); create a new version when you want an explicit audit checkpoint. When promptText changes and descriptionMode is Auto, the description regenerates asynchronously (typically under a minute, occasionally longer under load); the immediate response returns the pre-update description. Server-owned description: in Auto mode (the default), the server regenerates the capability description from promptText via gpt-5.4-nano on every promptText change. Writing a non-empty description flips the version to Manual mode — unless you also pass descriptionMode: 0 explicitly, in which case the explicit mode wins and the version stays Auto ("explicit signal beats implicit ownership rule"). Clearing description: pass "" or any whitespace-only string to clear the description and immediately revert to Auto — the worker fires and regenerates from promptText. Omit or pass null to leave the description unchanged. To reset to Auto without clearing the text, send { descriptionMode: 0 } without a description field (the existing description text persists until the next promptText change triggers the worker). modelSettingsJson: null is not allowed — a prompt without model settings is unrunnable. To supply defaults, call get_catalog and use recommended_defaults. Response includes versionId, versionNumber, versionDescription, promptText, modelSettingsJson, outputModality, description, descriptionMode, and isActive.
Set the prompt's currentVersionId. Used by an agent that just created a new version and wants to make it default.
Soft-delete a version. If the version is the prompt's current version, the server auto-switches currentVersionId to the lowest remaining versionNumber and returns the new newCurrentVersionId in the response — no manual switch_current_version call needed. When the deleted version was not the current version, newCurrentVersionId is absent from the response entirely (the field is omitted, not null — check for key presence, not a null check). Returns 409 cannot_delete_only_version if it's the sole remaining version.
Runs 5 tools · streaming
Run a prompt as a new conversation. Required: promptId. Optional: userInput, versionId (defaults to current), autoFinalize (default true). userInput semantics: omitting userInput and passing userInput: "" are equivalent — both mean "no user input" and the model responds from the system prompt alone. Whitespace-only values are also normalized to null. For no-input prompts, make sure the system prompt explicitly directs the model — without a user turn, open-ended instructions can produce unpredictable output. Returns a structured multi-block result: content[0].text = final model output text (empty string on failure); content[1].text = JSON metadata — all keys always present: {"runId","status","costMilliCents","imageCount","modelId","recordId"} where status is "Finalized" (autoFinalize fired, recordId populated) or "Active" (not yet finalized, recordId is null). Null values are explicit, not absent — parse without existence-checking each key. content[2].text = <reasoning>...</reasoning> block if the model used reasoning (omitted otherwise); then image content blocks (base64 PNG) for image-modality runs, followed by a promethic_image_urls text block containing signed 5-minute download URLs (mimeType, suggestedFilename, URL) — useful when the MCP host doesn't render inline images. costMilliCents may be null on the immediate streaming response — it settles by the time finalize_run returns or can be read from list_records afterwards. On multi-turn revisions, costMilliCents in each turn's run_completed event reflects that turn's cost; finalize_run returns the session total. The runId is required for follow-up finalize_run or revise_run; the recordId (when present) is all you need to call list_records, patch_record, or the record image endpoint. Run states: Active → Running → Active (after proxy) → Finalized (after finalize). A run can also enter Failed if the upstream model call fails — Failed runs return isError: true with reason_code: "run_failed"; retry with a fresh run_prompt. Idempotency: each call creates a new run. For retry-safe dedup, use _meta["com.getpromethic/idempotency-key"].
NOT a chat append. Re-renders the prompt's output with a revision instruction — the model produces a fresh full output that supersedes the prior one. Note on echo-style prompts: if the prompt's template passes user input through verbatim (e.g. "Repeat back what the user says"), revision context may appear in the output and pollute training data. Use a fresh run_prompt instead. For multi-turn dialog, start a NEW run instead. Required: userInput (the revision instruction — there is no separate "instruction" parameter) and runId. Terminal-state guard: Expired, Failed, and Abandoned runs return run_already_terminal before any LLM call. Finalized runs are NOT blocked — the server transparently reopens them (Finalized → Active) before the LLM call, so agents can always revise with the same runId after autoFinalize. Response structure: identical to run_prompt — content[0].text = revised model output; content[1].text = JSON metadata {"runId","status","costMilliCents","imageCount","modelId","recordId"} (same schema; status reflects the session state after this call — "Finalized" when autoFinalize fired, "Active" otherwise). Optional: intermediateOutput (overrides the prior-turn output used as the revision baseline — becomes that turn's intermediateOutput in get_record's turns[]; this means turns[N-1].output will reflect your override, not the raw model output from the prior turn. Only use this when you have genuinely edited the prior output and want the record to capture that edited state as the correction baseline. Omit it to use the stored model output unchanged. Pass null or omit to use the prior output; empty string or whitespace is rejected), fromTurn (drops session turns > N before applying the revision; defaults to latest; to rewind a finalized record first use patch_record(fromTurn=N), then start a fresh run_prompt), autoFinalize (override account setting). Same image-content-block delivery as run_prompt.
Promote an active run to a saved record. Required: runId. Optional: finalText (Copy-with-edits — producing a value that differs from the model output creates an EDIT delta; if provided, cannot be an empty string or whitespace-only value), tag (label for that EDIT delta — requires finalText to be present and to differ from the model output; returns tag_without_delta otherwise; whitespace-only values are trimmed before storing; use notes for record-level labels), notes (free-form record-level label — overwrites the existing value; pass the full desired string if you want to append), fromTurn. Behavior on already-Finalized sessions: (1) Passing notes alone is a notes amendment — it directly updates the record's notes field and returns the current record state. This is intentionally NOT idempotent: each call with a different notes value updates the record. (2) Passing finalText, tag, or fromTurn reopens + re-finalizes the session, rebuilding deltas. (3) Passing nothing (no mutations) returns the original finalize response unchanged. Calling on an already-Finalized session with new content (finalText/tag/fromTurn) reopens + re-finalizes. Validation errors (bad tag/fromTurn/finalText) reject without state mutation — no zombie sessions. Returns {recordId, turns, costMilliCents?} where turns is an integer total turn count: 1 (original run) + each revise_run call + 1 if finalText differed from the model output (creating an edit delta). Type note: finalize_run.turns is an integer count; get_record has a same-named field that is an array of turn objects — they are different types serving different purposes.
Fetch an image from an active (non-finalized) run by 0-based index. Required: runId, n. Returns a single MCP image content block (base64 PNG). Most agents won't need this — run_prompt already returns images inline as content blocks. Use when revisiting an active run after losing the original tool result, or to re-fetch a specific image. For finalized records, use GET /api/v2/records/{recordId}/image?index={n} instead.
Records 5 tools
List finalized records (cursor-paginated, default 25, max 100). Filter by promptId (returns 404 prompt_not_found if the prompt doesn't exist) and/or source (one of "API" (MCP run_prompt or REST API key), "App" (Avalonia desktop), "Manual" (create_record, no LLM), "Headless" (Expo mobile — not MCP) — case-insensitive; invalid value returns invalid_source). Returns { records: [...], nextCursor? } — wrapper key is records, not items; nextCursor is absent on the final page (not null). Slim DTOs include outputText, cost (costMilliCents — present for all API/App/Headless records; null only on Manual records, which have no associated LLM cost), image counts, source label, and timestamps. Most-recent-first. Optional maxOutputChars (default 4096, range 0–32768): pass 0 to suppress output text — returns outputText: "" + outputTruncated: true (not null; null is reserved for image records where there is no text output). Optional maxInputChars (same range, default 4096): same semantics for inputText — pass 0 to suppress, returns inputText: "" + inputTruncated: true. Useful for bulk metadata listing without pulling large text payloads. Note: the source filter is part of the cursor fingerprint — cursor pages from a source=Manual query cannot be replayed against a source=API query (returns cursor_filter_mismatch). Image records: outputText is always null (not absent — the model output is the image, not text); imageStoredPaths is a typed array of opaque blob keys — fetch bytes via GET /api/v2/public/records/{id}/image?index=N.
Fetch a single record by recordId. Returns full detail: recordId, promptId, versionId (null for Manual records — not associated with a specific version), versionStatus ("active" when the source version exists and is not deleted; "deleted" when the source version has been soft-deleted; null for Manual records — they have no associated version), source, promptName, inputText, outputText (null for image records), imageStoredPaths (array, null if no images), notes, modelId, token counts (inputTokens, outputTokens, reasoningTokens), costMilliCents, revisionCount, imageCount, isOneShot, createdAtUtc, and the complete turns[] array reconstructed from the delta chain. turns[] structure: turn kind: "run" (index 0) = the original conversion output; kind "revision" = each revise_run call (indices 1…N); kind "edit" = a user output correction from patch_record (always last, at most one). Each turn has index, kind, input (what went in), output (what came out), and an optional instruction (revision instruction or edit tag). Cost note: patch_record(fromTurn: N) subtracts the costs of discarded deltas from the record-level costMilliCents total — the total reflects only the surviving turns after a revert. Per-turn costs after a revert are re-derived from the surviving deltas; treat them as estimates if any discarded delta lacked cost data. Use list_records to browse; use get_record when you have a specific recordId and need the full delta chain or token-level cost breakdown.
Create a finalized record from agent-supplied input + output with no LLM call. Source = Manual. Use this to seed a prompt with curated training examples before running Generate Prompt on the desktop. Required: promptId, input, output. Optional: notes, idempotencyKey (stable dedup string — same key returns the original record on retry instead of creating a duplicate). Returns { recordId, source: "Manual", createdAtUtc }. Text-modality prompts only; image/JSON prompts return manual_record_modality_not_supported. Requires execute scope. Idempotency key scope: keys are scoped per-user-account with a 24-hour TTL. Use a UUID or a namespaced string to avoid collisions with prior runs on the same account (e.g. "myapp-session-abc123", not "key-001"). Reusing a short key from a prior session returns idempotency_key_reused if the request body differs. Hash scope: the dedup hash covers all fields in the request body, including optional ones like notes. Retrying with the same key but different notes returns idempotency_key_reused even if input and output are identical. For retry-safe dedup, keep the entire body byte-for-byte identical across retries.
Update mutable fields on a record the caller created. Same-source-same-principal ownership. Record type matters:
Record source taxonomy: "Manual" = created by create_record (seed data); "API" = created by MCP run_prompt or REST API key execution; "App" = Avalonia desktop client; "Headless" = Expo mobile client. versionId is always null on Manual records (they aren't associated with a specific prompt version). idempotencyKey: "" (empty string) is treated as absent — two calls with an empty key and different bodies each create a new record with no dedup. Use a non-empty stable string for retry protection. Manual records (source = "Manual"): notes, input, and output are freely editable with no delta created. tag and fromTurn are not applicable (invalid_request) — manual records are pure (input, output) pairs and never have delta chains.
User-content fields (input, output, tag, notes): whitespace is valid user content — values are trimmed before storing. Pass JSON null to clear tag; pass null to clear notes. Empty strings after trimming are stored as "" (not rejected).
All other records (App / API / Headless — records from run_prompt, Avalonia, or Expo): any output change automatically creates or updates the single edit delta. Delta semantics: IntermediateOutput = model's last returned output, set once on the first output edit and immutable thereafter. FinalCopiedOutput = desired output, mutable across patches. Reverting to model output: if output matches the existing edit delta's IntermediateOutput (= the model's last returned output), the edit delta is deleted and editCount returns to 0 — you are undoing the correction. Providing tag alongside a reverting output returns tag_would_be_lost_on_revert (omit tag to revert cleanly, or use a different output to keep the delta and anchor the tag). tag is an optional annotation on the delta — write it as a generalizable rule or vibe that would apply across other inputs (e.g. “Less formal”, “Remove em-dashes”), NOT a description of this specific diff (the before/after already shows what changed); it feeds the Refine Prompt function, so a high-level rule the prompt could adopt is far more useful than restating the change. When tag + output are provided together: if an edit delta already exists and output is unchanged, the tag is simply updated (you're relabeling an existing edit); if no delta exists yet and output is unchanged, tag_requires_distinct_output is returned. When tag is provided without output: if an edit delta already exists, the tag is updated; if no delta exists yet, tag_requires_output is returned — provide an output value to create a delta first. Use notes for record-level labels that need no edit correction. REVERT: fromTurn: N restores the record to the state it was in at turn N and soft-deletes all deltas from N onward. Turn 0 = state before the first surviving delta. Mutually exclusive with output and tag; can be combined with notes and input. After a revision: all prior deltas (including edit deltas) are replaced by the new revision delta set. The revision delta's IntermediateOutput captures whatever FinalCopiedOutput was before the revision — so fromTurn=0 restores the pre-revision desired output, not the original conversion output.
Soft-delete a record the caller created (same ownership rules as patch_record). Sweep job hard-deletes later. Records are execute-axis, not write. Idempotent on already-deleted: returns {status:"deleted"} rather than an error.
Attachments 4 tools
Upload a file as a prompt attachment (text or image). Bytes via bytes_base64 (inline base64 — works on hosted and self-hosted MCP). localPath (local file path) is only accepted on self-hosted MCP instances; the hosted MCP at mcp.getpromethic.com rejects localPath with invalid_params. Server caps: per-file 10MB image / 5MB text; 20 attachments per prompt; per-prompt 50MB total; per-user storage quota. Filename+hash dedup: if an attachment with the same filename and identical bytes already exists on this prompt, the server returns the existing attachment without creating a duplicate or consuming quota — safe to retry on network failure with no side effects. Pass an explicit idempotencyKey for stronger cross-session replay stability (same key returns the byte-identical original response for 24 hours regardless of content changes). Filename validation: a missing or empty filename returns invalid_params ("filename is required"); a whitespace-only value (e.g. " ") returns invalid_filename; path separators, null bytes, or dots-only names also return invalid_filename. Path traversal in filenames (WAF-level): filenames containing .. (e.g. ../../etc/passwd) are intercepted by the Cloudflare WAF with a 403 before the request reaches the app layer — the app would reject them with invalid_filename anyway, but the WAF fires first, so the error shape differs (raw 403 HTML, not a JSON error envelope). Kind detection: if attachmentType is omitted, the server auto-detects kind from contentType, then filename extension (.png/.jpg/.gif/.webp etc.), then PNG/JPEG/GIF/WebP magic bytes, then UTF-8 validity. Files that are neither a recognized image format nor valid UTF-8 are rejected with invalid_params — pass attachmentType: "image" explicitly for image files the detector doesn't recognise (e.g. AVIF, HEIC, SVG). Image format validation: even with explicit attachmentType: "image", the server validates that the bytes have a recognized image magic number (PNG, JPEG, or WebP). Unrecognized bytes are rejected with invalid_image_format. Images below 128×128 px are rejected with image_dimensions_too_small. TRUST: localPath is read by the MCP server with the user's permissions — only use paths the agent is authorized to read.
List a prompt's attachments. Returns slim DTOs with fields: attachmentId, filename, kind ("text" or "image"), sizeBytes, createdAtUtc. Use get_attachment to fetch the bytes.
Fetch an attachment's content. Reply has explicit kind: "text" returns text (inline UTF-8 string); "image" returns imageBase64 (inline base64) for files ≤ 16 MB, or imageMediaRef (tmpfile path) for larger files. The field name depends on file size — check for imageBase64 first, then imageMediaRef. Also returns attachmentId, filename, sizeBytes, and createdAtUtc.
Soft-delete an attachment + refund its bytes against the per-user storage quota. Returns 409 attachment_referenced_by_active_run if a non-terminal run still uses this blob — the response body includes stuckRunIds: [...]. Recovery options: (a) call finalize_run(runId) on each stuck run, then retry delete_attachment; (b) wait for the run to expire naturally (runs expire after session inactivity). Idempotent on already-deleted: calling again on a deleted attachment returns {attachmentId, status:"deleted"} rather than an error.
Feedback 1 tool
Report a bug or unexpected behavior, or suggest a feature or improvement, directly to the Promethic development team. One issue per call — if you have multiple bugs or feature requests, call report_issue once for each. Do not bundle multiple findings into a single report. Use this proactively whenever you encounter confusing tool responses, missing capabilities, inconsistent behavior, or anything that would make Promethic more useful for agents. You don't need to wait to be asked — if something seems wrong or could be better, report it. All reports are ingested into a triage queue reviewed by automated agents and the development team. Required: type ("bug" or "feature_request"), report (description — max 4000 characters). Describe issues in terms of tool names, parameter values, and expected vs. actual behavior — do not include prompt content, user inputs, or patient data. Specific reports with reproduction steps have the most impact. Returns { feedbackId }. Rate-limited at 10 reports per hour per user account. Requires execute scope.
Per-prompt synthesized tools opt-in · cap 50 per account
For prompts users explicitly toggle on, the MCP host synthesizes
an additional tool per prompt — named
promethic_{slug} (e.g.
promethic_clay_cuties). Agents invoke it
by name in one round-trip — no list_prompts +
get_prompt + run_prompt sequence. The
tool's description field carries the
version's capability description so the calling LLM can decide
whether to use it without an extra fetch.
Synthesized at tools/list emit time when the
prompt has ExposeAsMcpTool=true. Input schema
is a single optional userInput string. Dispatch
resolves the prompt by exact-match
(UserId, McpToolName), then runs the same
pipeline as run_prompt. If the user toggles
exposure off (or deletes the prompt) the tool name returns
unknown_tool with a hint to re-list. Tool name
is stable across prompt renames — an
agent with a hardcoded name keeps working when the user
tweaks the prompt's display name. If the user wants the
tool name re-derived from the new prompt name, they click
"Update tool name" in the settings panel (which calls
POST /api/v2/prompts/{id}/mcp-rename).
Discovery: these only appear when the user
has toggled them on (Settings panel on the prompt). MCP
hosts cache tools/list aggressively — after a
toggle, the user may need to start a new conversation or
reconnect the connector for the new tool to appear.
Validation reason codes
Several distinct reason_code values cover input validation — they are not interchangeable:
invalid_params— wrong type, missing required field, or bad UUID (e.g. negative fromTurn, non-UUID string). Also appears as a top-level reason on mutex violations. For integer-bounds violations, seeparam_out_of_rangebelow.invalid_request— malformed request body or conflicting fields (e.g.fromTurn+outputtogether onpatch_record). May include aninvalid_params[]array for per-field detail.invalid_model_settings— unknown or inactivemodel_id, or a parameter value outside the catalog-declared set or range (e.g.temperature: 5.0,max_output_tokens: 999999). Model-settings bounds always use this code, notparam_out_of_range— they're validated by the catalog layer, not the tool parameter layer. Action hint: callget_catalogfor valid values and ranges.invalid_input— empty or oversizedinput/outputoncreate_recordorpatch_record.invalid_cursor— tampered or cross-user pagination cursor.invalid_filename— filename contains path separators, null bytes, dots-only names, whitespace-only value, or leading/trailing whitespace. A missing/empty filename returnsinvalid_paramsinstead.field_too_large— a string field (e.g.name,versionDescription) exceeds its declared character limit. Message states the limit.instruction_required—revise_runcalled with an empty or whitespace-onlyuserInput. The revision instruction must be a non-empty, non-whitespace string.image_dimensions_too_small— uploaded image dimensions are below the minimum required (128×128 px). Resize or use a larger image.
from_turn_invalid — fromTurn is negative.from_turn_out_of_range — fromTurn exceeds the number of available turns or deltas. Message includes the valid range. Two noun forms depending on surface: fromTurn=99 but session has 1 turns (valid range 0..0) for run-keyed paths; fromTurn=99 but record has 0 delta(s) (valid range 0..0) for record-keyed paths (patch_record).param_out_of_range — integer parameter outside its declared bounds (e.g. limit=501 on list_prompts). More specific than invalid_params; the message always states the valid range.Switch on the specific code rather than a prefix match — they carry different recovery actions.
Lifecycle reason_codes
Codes emitted during run/record/prompt lifecycle operations — not all listed in the validation table above. Full reference at agent-api-v1 § Errors.
run_not_found—runIddoesn't exist, has expired, or belongs to a different user. Start a new run withrun_prompt.run_already_terminal— the run is in a non-interactable terminal state. Returned by:revise_run(runId)on an Expired, Failed, or Abandoned run (Finalized is NOT terminal forrevise_run— the server reopens it transparently);finalize_runandget_run_imageon an Abandoned run.tag_without_delta—finalize_runwithtagbut nofinalTextthat differs from model output.tag_requires_distinct_output—patch_recordon a non-manual record:tag+outputprovided, butoutputequals the current value AND no edit delta exists yet. Provide a differentoutputto create a delta.tag_requires_output—patch_recordon a non-manual record:tagprovided withoutoutputand no edit delta exists yet. Provide anoutputvalue to create an edit delta first, or usenotesfor a record-level label with no edit.tag_would_be_lost_on_revert—patch_recordwithoutputmatching the model's last returned output (removing the edit delta) ANDtagalso provided. The delta is being deleted so the tag has nothing to anchor. Omittagto revert cleanly, or use a differentoutputto keep the delta and anchor the tag.cannot_delete_only_version—delete_versionon the sole remaining version.manual_record_modality_not_supported—create_recordon an image- or JSON-modality prompt.image_index_out_of_range—get_run_image(n)where n ≥ image count on the run.idempotency_key_reused— same key with different request body; mint a new key.record_not_found,attachment_not_found,version_not_found,prompt_not_found— resource does not exist or is not visible to the caller. Messages echo the requested ID where available.attachment_referenced_by_active_run—delete_attachmentblocked because a non-terminal run still uses the blob. Response includesstuckRunIds. Finalize the listed run(s) first, then retry.
Error format
MCP tool errors are not RFC 7807 problem+json. They use JSON-RPC-shaped content with isError: true and a JSON payload in content[0].text:
{
"isError": true,
"content": [{ "type": "text", "text": "{\"reason_code\":\"prompt_not_found\",\"message\":\"Prompt … not found.\"}" }]
}
The inner JSON always has reason_code (machine-readable, same vocabulary as the REST API problem table) and message (human-readable). Switch on reason_code for programmatic error handling. For the full reason_code table see agent-api-v1 § Errors.
Looking for a tutorial? See Build an agent in 10 minutes for a worked example. For the full reference (request shapes, error codes, idempotency contract, recovery recipes), see api.getpromethic.com/agent-api-v1.