Skip to main content

Local MCP clients

Tickr ships an opt-in Model Context Protocol (MCP) facade for local stdio clients. An MCP client launches tickr-cli mcp; normal Tickr startup does not launch or supervise the process, and the MCP process does not start or stop a Data plane formation. It connects to an API component that is already available.

The facade exposes exactly twenty-five curated MCP tools. It is not an automatic projection of the HTTP API. The API component remains the canonical operation authority: every accepted tool call maps to one documented HTTP operation, and the facade does not read repositories, coordination subjects, or tickr-ctx directly.

Configure a local client

MCP clients use different configuration-file locations, but the local stdio server entry has the same command, arguments, and environment shape. To use the default API endpoint:

{
"mcpServers": {
"tickr": {
"command": "<PATH_TO_TICKR_CLI>",
"args": ["mcp"]
}
}
}

The endpoint resolves in this order:

  1. tickr-cli mcp --api-endpoint <URL>;
  2. TICKR_MCP_API_ENDPOINT;
  3. http://127.0.0.1:6000.

For an HTTPS endpoint with optional bearer authentication:

{
"mcpServers": {
"tickr": {
"command": "<PATH_TO_TICKR_CLI>",
"args": [
"mcp",
"--api-endpoint",
"https://<API_HOST>:<API_PORT>"
],
"env": {
"TICKR_MCP_API_BEARER_TOKEN": "<BEARER_TOKEN>"
}
}
}
}

These examples contain placeholders only. TICKR_MCP_API_BEARER_TOKEN is the only bearer-token input. It is environment-only: there is no bearer CLI option and no secret-bearing MCP tool argument. Supply it through the MCP client's secret-aware environment mechanism, not in args or a shared configuration file.

One MCP process binds one API component endpoint and therefore one Tenant boundary. It does not select or multiplex Tenants. Launch separate, explicitly configured processes for different Tenant endpoints.

Endpoint and transport safety

  • Plain http:// is accepted only for localhost or a loopback IP address. Every non-loopback endpoint requires HTTPS.
  • HTTPS uses normal platform certificate verification. There is no insecure-verification bypass.
  • HTTP redirects are prohibited. The facade does not follow a redirect with credentials or a mutation request.
  • Endpoint user information such as https://<USER>:<PASSWORD>@<API_HOST> is rejected.
  • Endpoint URLs, bearer values, request bodies, response headers, and internal diagnostics are excluded from protocol errors and normal diagnostics.
  • MCP transport is local stdio. Stdout contains only newline-delimited JSON-RPC frames; diagnostics use stderr. Do not merge stderr into stdout.

Protocol-wide contracts

tools/list returns the exact ordered inventory documented below. Every input schema is a closed JSON object: additional properties and explicit null values are rejected. Required identity strings must be UUIDs. Optional fields must be omitted when unused.

All tools set openWorldHint: true because they call the configured API component. The other protocol-visible annotations are:

Tool classreadOnlyHintdestructiveHintidempotentHint
Every read-only tooltruefalsetrue
register_workflow, patch_workflow_instancefalsetruefalse
trigger_workflow, cancel_workflow_instance, cancel_task, replay_workflow_instance, redrive_compaction_quarantinefalsetruetrue
activate_workflow, deactivate_workflowfalsefalsetrue

The first argument-valid tools/call in a process fetches /api-docs/openapi.json and proves compatibility with API version 0.2 and all twenty-five canonical operations. initialize and tools/list do not contact the API component. A compatible result is cached for that MCP process; operation data is not cached.

Every accepted read performs exactly one fresh HTTP GET. The facade does not poll, traverse cursors, enumerate all pages, perform follow-up reads, or retry. Every accepted mutation performs exactly one HTTP POST. The facade never retries a mutation automatically, including after a timeout or another ambiguous transport outcome.

Every operation returns a structured JSON envelope with:

  • ok and UTC observed_at on every result;
  • optional observed http_status;
  • complete data and optional meta on success; or
  • a bounded error on failure.

observed_at is the facade observation time, not an entity transition or persistence time. Public error messages are limited to 4,096 UTF-8 bytes. The complete compact envelope is limited to 524,288 encoded bytes. An oversized result is replaced atomically by response_too_large, with observed_bytes and limit_bytes; the facade never clips fields or returns partial data.

Facade validation happens before compatibility or operation HTTP. Malformed successful API responses fail as invalid_api_response. API absence, not-found, conflict, and other canonical public failures remain distinct; the facade does not reinterpret an unavailable projection as an empty successful result.

Read-only tools

The tools in this section have readOnlyHint: true, destructiveHint: false, idempotentHint: true, and openWorldHint: true.

Workflow and instance reads

list_workflows

  • Inputs: optional limit, integer 1..=200, default 50.
  • HTTP: one GET /api/workflows?limit=<limit>.
  • Result: the complete canonically ordered Workflow array under data; meta.truncated preserves X-Tickr-Result-Truncated. This is a bounded top projection, not a complete-enumeration or snapshot-consistency claim.

get_workflow

  • Inputs: required workflow_id; optional version, integer >= 1. Omitting version delegates Default-version selection to the API component.
  • HTTP: one GET /api/workflows/{workflow_id}, with version only when supplied.
  • Result: the complete Workflow detail under data, including selected version and status, complete workflow_definition, exact authored nickel_source, available versions, aggregate run fields, and future public fields. Unknown Workflow or version is not_found.

get_workflow_calendar

  • Inputs: required workflow_id and integer year; optional canonical IANA tz. year must identify a representable calendar year whose following year is also representable. Omitting tz preserves the API component's UTC default.
  • HTTP: one GET /api/workflows/{workflow_id}/calendar?year=<year>, adding tz only when supplied.
  • Result: the complete requested-year projection under data, including timezone, ordered day counts, and live_data_available. The facade does not reconstruct calendars or infer future Workflow instances.

list_workflow_instances

  • Inputs: required workflow_id; optional date in YYYY-MM-DD; optional canonical IANA tz; optional limit, integer 1..=200, default 50. Omitting tz preserves the API component's UTC default.
  • HTTP: one GET /api/workflows/{workflow_id}/instances, forwarding supplied date/timezone filters and the effective limit.
  • Result: the complete newest-first Workflow-instance array under data; meta.truncated and meta.live_data_available preserve the canonical headers. live_data_available: false is a degraded archive-backed view, not a complete current view.

list_terminal_workflow_instances

  • Inputs: optional workflow_id; optional terminal state in Completed | Failed | Cancelled; optional inclusive RFC 3339 scheduled_from and scheduled_to with lower bound not after upper bound; optional non-empty opaque cursor; optional limit, integer 1..=100, default 50.
  • HTTP: one GET /api/runs, forwarding only supplied filters and cursor plus the effective limit.
  • Result: the complete canonical page under data, including ordered items and nullable next_cursor. Cursors are API-owned and opaque; a page does not claim snapshot consistency or complete history.

get_workflow_instance

  • Inputs: required workflow_instance_id.
  • HTTP: one GET /api/workflows/instances/{workflow_instance_id}.
  • Result: one complete canonical Instance snapshot under data, including canonical live or archive storage. Identity mismatch or malformed success is invalid_api_response; not-found and live-data-unavailable remain distinct failures.

list_task_instances

  • Inputs: required workflow_instance_id; optional limit, integer 1..=200, default 50.
  • HTTP: one GET /api/workflows/instances/{workflow_instance_id}/tasks?limit=<limit>.
  • Result: the complete deterministic Task-instance array under data, ordered by Task identity, Attempt, then Task-instance identity. meta.truncated and meta.live_data_available preserve the canonical headers independently.

get_task_logs

  • Inputs: required workflow_id, workflow_instance_id, and task_instance_id; optional tail_batches, integer 1..=200, default 50; optional before_seq, non-negative integer naming an existing Log sequence boundary.
  • HTTP: one GET /api/workflows/{workflow_id}/instances/{workflow_instance_id}/tasks/{task_instance_id}/logs, forwarding the effective tail bound and optional sequence boundary.
  • Result: one canonical bounded Log page under data, preserving batches, sequence metadata, terminal status and reason, earlier-page indication, and terminal-marker presence. The facade does not download complete Logs or follow a Log as it grows.

Signal, context, and Server event reads

get_signal

  • Inputs: required signal_id.
  • HTTP: one GET /api/signals/{signal_id}.
  • Result: the complete canonical Signal status under data: signal_id, signal_code, status (pending, materialized, or terminal), and applicable workflow_id, workflow_instance_id, workflow_instance_code, captures_summary, applied_count, name, and matched_workflows. Trigger, Wakeup, and Cancel audit shapes are preserved. The facade does not infer materialization or make a follow-up read.

get_workflow_instance_context

  • Inputs: required workflow_instance_id.
  • HTTP: one GET /api/workflows/instances/{workflow_instance_id}/context.
  • Result: the complete canonical tickr-ctx scope projection under data, including storage, run, trigger, every gate scope, ordered entries, and each scope's availability and complete fields. available with empty entries is distinct from incomplete not_retained. Required live-scope unavailability remains api_unavailable, not not-found or an empty scope.

Shared scoped Server event filters

list_workflow_instance_events and list_task_instance_events accept these optional filters:

  • inclusive RFC 3339 occurred_after and exclusive RFC 3339 occurred_before;
  • category, a unique array of 1..=3 values from Workflow | Task | Gate;
  • event_type, a unique array of 1..=21 values from CreateWorkflowInstance, CancelWorkflowInstance, WorkflowSubmitted, WorkflowInstanceCreated, WorkflowTriggered, WorkflowCompleted, WorkflowFailed, WorkflowCancelled, WorkflowInstancePatched, TaskInstanceCreated, TaskQueued, TaskDelivered, TaskAssigned, TaskStarted, TaskCompleted, TaskFailed, TaskParked, TaskCancelled, GateDispatched, GateOutcome, or GateTimeoutFired;
  • attention, a unique array of 1..=4 values from Success | Failure | Cancellation | Waiting;
  • optional exact workflow_id, edge_id, signal_id, and patch_id UUID filters; and
  • one optional non-empty opaque cursor, copied from older_cursor or newer_cursor of a prior scoped page.

Repeated filter values are ORed. The facade forwards filters exactly and does not decode cursors or post-filter results. The API component fixes page size; these tools expose no limit input.

list_workflow_instance_events

  • Inputs: required workflow_instance_id, the shared filters above, and optional exact task_instance_id UUID filter.
  • HTTP: one GET /api/workflows/instances/{workflow_instance_id}/events.
  • Result: one complete canonical Server event page under data, preserving ordered items, every event field, matching_count, snapshot_high_water, older_cursor, newer_cursor, and at_live_head. An empty page is successful; unavailable history is api_unavailable rather than an empty page.

list_task_instance_events

  • Inputs: required parent workflow_instance_id and required task_instance_id, plus the shared filters above.
  • HTTP: one parent-enforcing GET /api/workflows/instances/{workflow_instance_id}/tasks/{task_instance_id}/events.
  • Result: the same complete canonical Server event page contract as the Workflow-instance tool. The parent path prevents cross-Workflow-instance Task history access.

Replay, Patch, and Compaction reads

list_workflow_instance_replays

  • Inputs: required source_workflow_instance_id.
  • HTTP: one GET /api/workflows/instances/{source_workflow_instance_id}/replays.
  • Result: complete canonical newest-first reverse-link rows under data, including replay/source identities and codes, status, nullable name, resume_from Task references, names-only shadowed_keys, and RFC 3339 created_at. Both a known source with no replays and an unknown source are successful empty lists.

get_patch_status

  • Inputs: required patch_id.
  • HTTP: one GET /api/patches/{patch_id}.
  • Result: the complete canonical Patch status under data, including Patch identity, Workflow-instance identity, durable Patch key, lifecycle status, applied version, outcome, reason, update time, and future public fields. This is one observation, not a subscription or polling operation.

get_patch_source

  • Inputs: required patch_key, the durable UUID returned by accepted Patch submission or status.
  • HTTP: one GET /api/patches/{patch_key}/source.
  • Result: the complete retained source under data, including Patch key, Workflow-instance identity and code, applied version, exact authored source text, and source format. The 512 KiB result ceiling is atomic; no partial source is returned.

list_compaction_quarantine

  • Inputs: optional state in quarantined | redrive-pending | redriving | recovered | redrive-failed; optional non-empty opaque cursor; optional limit, integer 1..=100, default 50.
  • HTTP: one GET /api/compaction/quarantine, forwarding supplied state and cursor plus the effective limit.
  • Result: the complete canonical page under data, including ordered items, nullable next_cursor, Tenant identities, optional Workflow-instance identity and code, payload digest, formation source/reference, bounded failure evidence, attempt and observation fields, quarantine state, and redrive fields. Retained CompactionEnvelope bytes are intentionally not exposed.

State-changing tools

The MCP facade never retries any state-changing tool. “Explicit retry” below always means a new caller-directed tool call after the caller applies the named rule.

register_workflow

  • Annotations: state-changing, destructive, non-idempotent, open-world.
  • Inputs: required nickel_source, maximum 524,288 UTF-8 bytes; optional string namespace, with no additional facade character bound. There is no idempotency-key input.
  • HTTP and side effect: one POST /api/workflows/register with exactly the supplied body fields. The API component validates and registers the Nickel Workflow definition.
  • Result: HTTP 200 or 202 with the complete canonical registration response under data, preserving Inserted, Refreshed, NoOp, and BuildRequeued outcomes.
  • Retry rule: do not repeat after an ambiguous result. Reconcile through a canonical read before deciding whether a new registration request is appropriate; the facade cannot deduplicate it.

trigger_workflow

  • Annotations: state-changing, destructive, idempotent, open-world.
  • Inputs: required workflow_id; required non-empty idempotency_key that is a valid HTTP header value; optional RFC 3339 scheduled_at; optional JSON-object inputs with no per-key schema; optional name, at most 200 characters.
  • HTTP and side effect: one POST /api/workflows/{workflow_id}/trigger. The key is forwarded unchanged only as Idempotency-Key; supplied scheduling, input, and name fields form the body. The API component creates or deduplicates the Trigger Signal.
  • Result: HTTP 200 or 202 with canonical signal_id, deduplicated, and optional scheduled_at under data.
  • Retry rule: an explicit retry must reuse the same idempotency key for the same logical trigger. Never use a new key to resolve ambiguity.

cancel_workflow_instance

  • Annotations: state-changing, destructive, idempotent, open-world.
  • Inputs: required workflow_instance_id; required non-empty idempotency_key that is a valid HTTP header value; optional note, maximum 4,096 UTF-8 bytes.
  • HTTP and side effect: one POST /api/workflows/instances/{workflow_instance_id}/cancel. The key is forwarded unchanged only as Idempotency-Key; the body contains only the optional note. The API component requests cancellation of the complete Workflow instance.
  • Result: HTTP 200 or 202 with canonical Cancel signal_id and applicable applied, instances_matched, and deduplicated fields under data.
  • Retry rule: an explicit retry must reuse the same idempotency key for the same logical Cancel.

cancel_task

  • Annotations: state-changing, destructive, idempotent, open-world.
  • Inputs: required workflow_instance_id; required canonical task_id; required non-empty idempotency_key that is a valid HTTP header value; optional note, maximum 4,096 UTF-8 bytes.
  • HTTP and side effect: one POST /api/workflows/instances/{workflow_instance_id}/tasks/{task_id}/cancel. The key is forwarded unchanged only as Idempotency-Key; the body contains only the optional note. The API component targets that Task within the named Workflow instance.
  • Result: HTTP 200 or 202 with the canonical Cancel Signal identity and applicable applied, instances_matched, and deduplicated fields under data.
  • Retry rule: an explicit retry must reuse the same idempotency key for the same logical Task Cancel.

replay_workflow_instance

  • Annotations: state-changing, destructive, idempotent, open-world.
  • Inputs: required source_workflow_instance_id; required non-empty idempotency_key; optional resume_from, an array of canonical Task UUIDs with no schema item-count bound; optional name, at most 200 characters; optional JSON-object inputs containing fresh values for declared Event variables.
  • HTTP and side effect: one POST /api/workflows/instances/{source_workflow_instance_id}/replay. The idempotency key is sent exactly once in the JSON body, not as a header. The body may contain the replay frontier, name, and fresh Event-variable inputs. It cannot contain replay seed state, archived tickr-ctx scope, Task outputs, or arbitrary carried state. The API component and Conductor decide source terminality, chained-replay eligibility, Event-variable validity, and fireability.
  • Result: HTTP 200 for a same-key replay or 202 for accepted replay, preserving complete canonical replay identity, code, status, source, frontier, name, and deduplication fields under data.
  • Retry rule: an explicit retry must reuse the same idempotency key and the same logical replay body.

patch_workflow_instance

  • Annotations: state-changing, destructive, non-idempotent, open-world.
  • Inputs: required workflow_instance_id; required non-empty authored nickel_source, maximum 524,288 UTF-8 bytes. The schema deliberately has no idempotency-key input.
  • HTTP and side effect: one POST /api/workflows/instances/{workflow_instance_id}/patch with exact source in the body. The API component submits it to the asynchronous Patch pipeline; the facade performs no preflight or follow-up read.
  • Result: HTTP 202 with valid Patch identity, durable Patch key, and accepted status under data. Canonical HTTP 409 is a conflict, not successful data.
  • Retry rule: Patch is non-idempotent. Never resubmit automatically or blindly after ambiguity. If an identity or key was observed, reconcile with get_patch_status or get_patch_source; otherwise leave recovery to deliberate operator action.

redrive_compaction_quarantine

  • Annotations: state-changing, destructive, idempotent, open-world.
  • Inputs: required quarantine_id; required idempotency_key of 1..=200 characters that is also a valid HTTP header value.
  • HTTP and side effect: one POST /api/compaction/quarantine/{quarantine_id}/redrive, with the key only in Idempotency-Key and no request body. The API component re-stages work through the formation-selected Compaction path; the MCP facade receives no quarantine repository or formation-staging handle.
  • Result: HTTP 202 for requested redrive or 200 for a same-key deduplicated result, preserving canonical redrive identity and lifecycle under data. Competing-key conflict, not-found, and unavailable outcomes remain distinct.
  • Retry rule: an explicit retry must reuse the same idempotency key for the same quarantine record.

activate_workflow and deactivate_workflow

  • Annotations: state-changing, non-destructive, idempotent, open-world.
  • Inputs: each accepts only required workflow_id. Neither accepts or invents an idempotency key.
  • HTTP and side effect: activate_workflow sends one POST /api/workflows/{workflow_id}/activate; deactivate_workflow sends one POST /api/workflows/{workflow_id}/deactivate. Activation makes a Ready Workflow eligible for later admission. Deactivation prevents later Workflow-instance admission but does not cancel existing Workflow instances.
  • Result: HTTP 200 with the complete canonical acknowledged status-version response under data: application acknowledgement Applied | Superseded, created, effect identity, target status (Active or Inactive), Workflow identity, positive Workflow version, and current version.
  • Retry rule: the facade never retries after ambiguity. Because status changes carry no idempotency key, read current Workflow status with get_workflow before making another explicit status request.

Capability boundary

The operational surface has three distinct categories.

Canonical API operations

The API component's canonical HTTP operations remain authoritative. The MCP facade supplies typed, bounded access only to the twenty-five operations above; it does not turn every existing route into a tool or become a generic HTTP executor. Existing API operations outside this allowlist remain HTTP operations, not hidden MCP tools.

Wakeup submission and ByTag Cancel are intentionally unavailable. Wakeup needs a formation-neutral same-key idempotency law before it is safe across Tickr Lite and distributed formations. The current ByTag resolver can span Tenants; it needs a proven single-Tenant resolution boundary before it is safe through this MCP facade.

Local operator orchestration

Formation selection, setup, installation and migration work, startup and shutdown, fresh reset, endpoint and credential provisioning, Nickel preflight validation, mutation policy, identifier selection, and multi-call sequencing remain local operator responsibilities. The facade neither controls formations nor treats local orchestration as an agent-native fallback.

Unavailable agent-native families

Unsettled or unimplemented agent-native capability families remain unavailable. The registry does not expose Console dashboards, Activity collections, Tenant-wide Server event history, Health, dynamic Tenant selection, authentication, complete Log download, arbitrary Signal construction, generic discovery, TaskMessage, direct tickr-ctx, checkpoints, ShadowTask, Piloted, Advisor, Healer, resources, prompts, subscriptions, or notifications. MCP clients must not infer these capabilities from HTTP routes or local operator commands.

Formation behavior

Tickr Lite (lite-local) and the distributed all-nats and all-redis formations expose identical MCP tool names, input schemas, annotations, envelopes, and retry rules. The facade is formation-neutral because it speaks only to the selected formation's API component. It does not expose formation substrate, credentials, or control.

HTTP Commands and formation-level external Event ingress are different capabilities. All three admitted formations provide HTTP Commands used by these MCP tools. Distributed formations also provide external Event ingress; Tickr Lite does not. An MCP tool call remains an HTTP Command and never becomes, emulates, or configures formation-level external Event ingress.