Integrate AI agents with your facility operations through the Model Context Protocol โ a single JSON-RPC endpoint exposing read-only resources and write-capable tools (auto-confirm and human-in-the-loop).
The MCP surface is a single JSON-RPC 2.0 endpoint that AI agents (Zo, Claude Desktop, LangChain, Cursor) use to read state and propose actions. It exposes:
tools/list and resources/list, plus a static manifest at /api/mcp/manifest.jsonFor partner TMS/ERP polling without JSON-RPC, see the REST surface instead. Both share one credential, one rate-limit bucket, one audit trail.
All API requests require a Bearer token in the Authorization header. API keys are organization-specific โ an admin in your organization generates them from the Integration Hub โ Agent Keys tab. Each key is scoped to that organization's data.
curl -X POST https://dockoptimizer.com/api/mcp/v1/message \ -H "Authorization: Bearer do_live_YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","method":"tools/list","id":1}'
do_live_.
Scopes gate the write-capable RPC methods. Discovery (tools/list and resources/list) is always allowed so a client can introspect the server regardless of what it's permitted to do.
| Scope | Allows |
|---|---|
| read | resources/read on tenant URIs and all GET /api/v1/* endpoints. Discovery methods (tools/list, resources/list) do not require any scope. |
| write | tools/call (auto-confirm and HITL tools) plus POST /api/mcp/v1/files/{eventId} multipart uploads. Implies read. |
| inventory | Required in addition to write for receive_inventory, issue_inventory, adjust_inventory. Combine as read,write,inventory. |
read only for monitoring/observability agents, and read,write for agents that need to create or modify records.Scopes gate methods; capabilities gate individual tools. Sensitive tool families check the key's agentConfig.capabilities[] array and return SCOPE_DENIED if the capability is missing. Use this to keep a booking agent from touching stock, or an inventory agent from moving appointments.
| Capability | Gates |
|---|---|
inventory | receive_inventory, issue_inventory, adjust_inventory |
appointments | Reserved โ future appointment tool gating |
Configure per key via POST /api/agent-keys/{id}/update-metadata with an agentConfig payload:
{
"version": 1,
"capabilities": ["appointments", "inventory"],
"defaultFacilityId": 19,
"notes": "Zo booking agent, prod"
}
capabilities array means all capability-gated tools are allowed (backward compatible). Explicit arrays are the recommended default going forward.Keys may carry an environment tag of staging or production. The auth filter compares that tag against the server's ASPNETCORE_ENVIRONMENT on every request โ a tagged key whose environment doesn't match the server is rejected with 401. Tenant keys may leave the tag blank for legacy compatibility.
Every key carries identifying metadata so admins can distinguish one agent from another on the dashboard and in audit logs:
| Field | Purpose |
|---|---|
agentName | Short identifier โ e.g. zo-booking-prod, claude-desktop-akash |
contactEmail | Owner email; receives alerts on suspicious use or quota exhaustion |
description | Free-text purpose โ what the agent is for, where it runs |
expiresAt | Optional absolute expiry (UTC). Recommended for experimental or user-BYO keys |
writeRateLimit | Per-hour ceiling on write-tool calls (null = inherit the daily quota only) |
POST /api/agent-keys/{id}/regenerate to atomically revoke the old token and issue a new one with the same metadata. Recommended cadence: 90 days for production agents, 30 days for shared developer keys.POST /api/agent-keys/{id}/revoke takes effect on the next request.The MCP surface uses JSON-RPC 2.0 over a single HTTP endpoint. All requests are POST with a JSON body.
Send a JSON-RPC 2.0 request. The method field determines the operation.
{
"jsonrpc": "2.0",
"method": "tools/list",
"params": {},
"id": 1
}
Strict MCP clients (Claude Desktop, MCP Inspector) must send initialize first and won't proceed until the server replies with protocolVersion, capabilities, and serverInfo. DockOptimizer answers this call and acknowledges notifications/initialized with an empty result.
{
"jsonrpc": "2.0",
"method": "initialize",
"params": { "protocolVersion": "2025-06-18" },
"id": 1
}
Only wired-up capabilities are advertised โ resources/subscribe, sampling/*, logging/*, and prompts/* are deliberately omitted so compliant clients don't call them.
Tools that aren't naturally idempotent (propose_appointment) accept an Idempotency-Key HTTP header. A retry with the same key within 24h replays the original response (no duplicate booking). Reuse with different arguments returns JSON-RPC error -32002 so the client fails loudly instead of silently submitting a changed payload.
| Method | Scope | Description |
|---|---|---|
initialize |
โ | MCP handshake. Returns protocolVersion, capabilities, serverInfo. |
notifications/initialized |
โ | Client-to-server ack after initialize. Server replies with empty result. |
tools/list |
โ | Discover available tools. Always allowed (no scope required). |
tools/call |
write | Execute a tool (some auto-confirm, some queue for HITL approval) |
resources/list |
read | Discover available data resources |
resources/read |
read | Read the current state of a resource |
Tools represent write operations that modify system state. Tools are split into two categories:
propose_* tools gated by the same HITL approval flow. Track progress in MCP_ALIGNMENT.md ยง3.These tools execute immediately and return the result inline.
propose_appointment auto-confirmCreate a new appointment. Auto-confirmed and immediately live. If eventTypeId is omitted, auto-selects the first active type for the facility.
| Parameter | Type | Required | Description |
|---|---|---|---|
facilityId | integer | โ | Facility ID |
scheduledDate | string | โ | Date in YYYY-MM-DD |
scheduledTime | string | โ | Time in HH:MM (24h) |
eventTypeId | integer | Appointment type ID (from dock://event-types) | |
carrierName | string | Carrier name (defaults to "Walk-in") | |
bolNumber | string | Bill of Lading number | |
poNumber | string | Purchase Order number (single) | |
poNumbers | string[] | Purchase Order numbers (multiple) | |
notes | string | Free-text notes (BOL details, weight, etc.) | |
driverCheckInName | string | Driver name | |
driverCheckInPhone | string | Driver phone | |
driverCheckInEmail | string | Driver email | |
shipperName | string | Shipper name | |
consigneeName | string | Consignee name | |
totalWeight | number | Shipment weight | |
totalPieces | integer | Number of pieces | |
appointmentType | string | pickup, dropoff, or both |
confirm_appointment auto-confirmConfirm and finalize a previously proposed appointment.
| Parameter | Type | Required | Description |
|---|---|---|---|
proposedAppointmentId | string (GUID) | โ | The GUID returned from propose_appointment |
update_appointment auto-confirmUpdate fields on an existing appointment. Only include the fields you want to change.
| Parameter | Type | Required | Description |
|---|---|---|---|
eventId | integer | โ | Appointment ID to update |
scheduledDate | string | New date (YYYY-MM-DD) | |
scheduledTime | string | New time (HH:MM, 24h) | |
carrierName | string | Updated carrier name | |
bolNumber | string | Updated BOL number | |
poNumbers | string[] | Updated PO numbers | |
notes | string | Updated notes | |
driverCheckInName | string | Updated driver name | |
driverCheckInEmail | string | Updated driver email | |
driverCheckInPhone | string | Updated driver phone |
cancel_appointment auto-confirmCancel an existing appointment.
| Parameter | Type | Required | Description |
|---|---|---|---|
eventId | integer | โ | Appointment ID to cancel |
reason | string | Cancellation reason |
mark_noshow auto-confirmMark a past appointment as no-show.
| Parameter | Type | Required | Description |
|---|---|---|---|
eventId | integer | โ | Appointment ID to mark as no-show |
Require the inventory capability on the key (combine with write, e.g. read,write,inventory). Every transaction is audited with agent attribution and broadcasts a SignalR toast to users in the org.
receive_inventory auto-confirmRecord receipt of goods โ increases QuantityOnHand. Creates an audited Receipt transaction.
| Parameter | Type | Required | Description |
|---|---|---|---|
sku | string | * | SKU of the inventory item |
itemId | string (GUID) | * | Item ID (alternative to sku) |
quantity | number | โ | Quantity to receive (must be positive) |
reference | string | External reference (BOL#, PO#, Receipt#) | |
eventId | integer | Appointment to link this receipt to |
issue_inventory auto-confirmRecord issue / shipment of goods โ decreases QuantityOnHand. Validates available stock before issuing.
| Parameter | Type | Required | Description |
|---|---|---|---|
sku | string | * | SKU of the inventory item |
itemId | string (GUID) | * | Item ID (alternative to sku) |
quantity | number | โ | Quantity to issue (must be positive) |
eventId | integer | Appointment to link this issue to |
adjust_inventory auto-confirmSet inventory to an absolute quantity (cycle count corrections, damage write-offs). Captures before/after quantities.
| Parameter | Type | Required | Description |
|---|---|---|---|
sku | string | * | SKU of the inventory item |
itemId | string (GUID) | * | Item ID (alternative to sku) |
quantity | number | โ | New absolute quantity |
reason | string | Reason for adjustment |
* = either sku or itemId is required; if both are provided, itemId wins.
File uploads use a dedicated multipart endpoint rather than tools/call. The call is audited under method upload_file.
Attach a file (BOL, photo, PDF) to an existing appointment. Requires a tenant key with write scope.
| Field | Type | Required | Description |
|---|---|---|---|
eventId | integer (path) | โ | Appointment to attach to |
file | file (multipart) | โ | Binary upload; validated server-side |
These tools queue the action for admin approval. Admins receive a real-time push notification and can approve or reject from the dashboard.
propose_block_door HITLPropose marking a facility door as blocked or full load.
| Parameter | Type | Required | Description |
|---|---|---|---|
doorId | integer | โ | Door ID to block |
reason | string | Reason for blocking | |
isFullLoad | boolean | Whether the door is full load |
propose_update_appointment HITLPropose updating appointment fields (carrier, PO, driver info, notes).
| Parameter | Type | Required | Description |
|---|---|---|---|
appointmentId | integer | โ | Appointment ID |
carrierName | string | Carrier name | |
poNumber | string | PO or reference number | |
driverCheckInEmail | string | Driver email | |
driverCheckInPhone | string | Driver phone | |
notes | string | Notes to append |
propose_flag_appointment HITLFlag an appointment for human review (data quality issues, mismatches, etc.).
| Parameter | Type | Required | Description |
|---|---|---|---|
appointmentId | integer | โ | Appointment ID |
flagReason | string | โ | Reason for flagging |
severity | string | low, medium, or high |
propose_run_report HITLTrigger a scheduled report to run on demand.
| Parameter | Type | Required | Description |
|---|---|---|---|
reportId | integer | โ | Scheduled report ID (from dock://reports) |
{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "propose_appointment",
"arguments": {
"facilityId": 19,
"scheduledDate": "2026-03-18",
"scheduledTime": "14:00",
"carrierName": "FedEx Freight",
"bolNumber": "BOL-2026-0318",
"notes": "Palletized shipment, 2 skids"
}
},
"id": 2
}
{
"jsonrpc": "2.0",
"result": {
"content": [{
"type": "text",
"text": "Appointment created and confirmed.\n{\"eventId\":1234,\"facilityId\":19,\"scheduledDate\":\"2026-03-18\",\"scheduledTime\":\"14:00\"}"
}]
},
"id": 2
}
{
"jsonrpc": "2.0",
"result": {
"content": [{
"type": "text",
"text": "Action 'propose_block_door' successfully queued for Human-in-the-Loop review. Action ID: 42."
}]
},
"id": 2
}
Resources represent read-only data about your facility's current state. Use resources/list to discover all available URIs at runtime.
| URI | Description |
|---|---|
dock://appointments | Query appointments with filters: ?start=&end=&date=&status=&flagged=&facilityId=&carrier=&search=&hasFiles=&limit=&offset=&sortBy= (legacy sort= still accepted) |
dock://appointments/{id} | Full detail for a single appointment (files, questions, door, flag status) |
dock://appointments/{id}/files | Files attached to an appointment (BOLs, photos) |
dock://appointments/{id}/ocr_output | OCR-extracted data from uploaded documents |
dock://appointments/{id}/notes | Timestamped free-text notes on an appointment |
dock://appointments/stale | Past-due appointments still in "scheduled" status. Optional: ?facilityId=&limit= |
dock://appointments/check | Conflict check. Required: ?date=YYYY-MM-DD&facilityId=X. Optional: &carrierName= |
dock://pending-appointments | Appointment proposals awaiting confirmation |
| URI | Description |
|---|---|
dock://doors/status | All doors with computed status (available/occupied/blocked), current booking, next appointment |
dock://doors/config | Full door registry: sensor IDs, auto-block settings, active/inactive, facility assignment |
dock://doors/history | Historical door bookings with dwell time. Optional: ?doorId=&facilityId=&start=&end=&limit= |
dock://doors/available | Available door time slots. Required: ?date=YYYY-MM-DD. Optional: &facilityId= |
dock://door-availability | Open slots โฅ 30 min for a facility. Required: ?facilityId=X&date=YYYY-MM-DD |
dock://slots/available | Real-time slot availability per appointment type. Required: ?date=YYYY-MM-DD |
dock://facilities | All facilities with door list, timezone, address, sensor config |
| URI | Description |
|---|---|
dock://operations/today | Operations dashboard per facility (check-ins, pending, no-shows, etc.). Supports ?date= |
dock://organization | Organization info: name, address, facilities, door counts, configuration |
dock://users | All users with recent login activity (last 5 logins per user) |
dock://reports | Scheduled email reports with frequency and last run status |
dock://categories | Appointment categories with door restrictions and question config |
dock://event-types | Appointment types with duration, facility, and category info |
| URI | Description |
|---|---|
dock://carriers | All active carriers with FMCSA data (MC#, DOT#), contact info, appointment counts |
dock://carriers/search | Fuzzy search by name or MC#. Optional: ?name=X&mc=Y |
dock://inventory/summary | Stock health: totals, low-stock alerts, quarantined, expiring, category breakdown |
dock://inventory/items | Itemized inventory with filters: ?search=&category=&facilityId=&lowStockOnly=&owner=&limit=&offset= |
dock://assets | Fixed assets (equipment, machinery): ?search=&category=&status=Active|Retired|InRepair |
dock://data-quality/summary | Org-wide data health score: missing carriers, stale appointments, duplicates |
{
"jsonrpc": "2.0",
"method": "resources/read",
"params": {
"uri": "dock://operations/today"
},
"id": 3
}
MCP-compatible clients (Zo, Claude Desktop, LangChain, Cursor) can auto-discover the server's capabilities via a public manifest. Use it to bootstrap your agent's tool/resource registry without hardcoding URIs.
Unauthenticated. Returns schemaVersion, server identity, auth instructions, keyTypes (tenant vs platform), environmentTagging, a surfaces map covering all three entry points (MCP JSON-RPC, file upload, REST /api/v1), and the full tool and resource listings with JSON-Schema input definitions.
The tools and resources arrays are sourced from the same helpers that power the live tools/list and resources/list responses, so the manifest cannot drift from what your key actually sees. The counts block is there for dashboards that want to show "X tools, Y resources" at a glance.
tools/list and resources/list return when that key calls them.The API uses standard JSON-RPC 2.0 error codes alongside HTTP status codes. Error responses include an optional data.hint field with recovery guidance for agents.
Unhandled errors on /api/v1/* and transport-level errors on /api/mcp/* come back as application/problem+json:
{
"type": "https://dockoptimizer.com/errors/internal",
"title": "Internal server error",
"status": 500,
"detail": "An unexpected error occurred. Our team has been notified.",
"instance": "/api/v1/appointments",
"requestId": "8f4d2c1a7b6e4e3f9a0c1b8d2e7f4a9c"
}
| HTTP | JSON-RPC Code | Meaning |
|---|---|---|
| 400 | -32700 | Parse error โ invalid JSON in request body |
| 401 | โ | Missing, invalid, or expired API key |
| 403 | -32600 | Insufficient scope for this operation |
| 400 | -32601 | Method not found |
| 400 | -32602 | Invalid params โ missing required fields |
| 404 | -32001 | Invalid resource URI format |
| 404 | -32002 | Resource not found or empty result |
| 429 | โ | Rate limit exceeded โ implement exponential backoff |
| 500 | -32603 | Internal server error |
Error responses may include a data.hint field with actionable guidance:
{
"error": {
"code": -32002,
"message": "Resource not found or empty result for URI: dock://appointments/99999",
"data": {
"hint": "The requested resource returned no data. Check the appointment ID or filters."
}
}
}
API requests are rate-limited per API key across three windows. The three layers protect against burst traffic, runaway loops, and accidental write storms from a single agent. REST and MCP traffic share the same bucket โ the limits below are per key, not per surface.
| Limit | Window | Scope | Configurable |
|---|---|---|---|
| 60 requests | Per minute | Per API key (burst) | Global โ rejection is immediate (no queue) |
| 1,000 requests | Per UTC day | Per API key (quota) | Server-wide via MCP:DailyCallLimit |
| Write-tool ceiling | Per hour | Per API key (optional) | Per-key via update-metadata (null = unlimited) |
When rate-limited, the API returns HTTP 429 Too Many Requests. The minute-window limiter is a token bucket โ the bucket refills continuously. The daily window resets at 00:00 UTC.
1s, 2s, 4s, 8s, 16sโฆ with jitter, capped at 60s. Don't retry the same tool call on 429 โ re-read the relevant resource first in case state has changed.Use writeRateLimit on keys that should only make occasional changes (a data-quality agent, a report runner). Leave it null for keys that may legitimately run bursts (a bulk backfill import). The per-minute burst limit still applies either way.
Manage API keys programmatically via the admin endpoints (requires an authenticated admin session cookie โ these endpoints are not callable from an agent using its own API key). The same keys work for both REST and MCP traffic.
List all active keys for the calling admin's organization with agent name, prefix, scopes, lastUsedAt, daily/total call counts, and expiration status.
Everything /list returns, plus per-key recent activity samples and aggregate stats (total keys, calls today, all-time calls, effective rate limits). Use this for the admin dashboard.
Generate a new API key. Body: { "agentName": "โฆ", "scopes": "read,write", "expiresAt": "2026-07-01T00:00:00Z" }. Returns the raw token exactly once โ store it immediately; it is not recoverable.
Atomically revoke an existing key and issue a new one carrying the same agentName, scopes, expiresAt, contactEmail, description, agentConfig, environment, and writeRateLimit. Use this for rotation without reconfiguring agent metadata.
Revoke an API key immediately โ all future requests with this key will return 401. Irreversible.
Update owner-visible metadata. Body accepts contactEmail, description, agentConfig (validated JSON), and writeRateLimit. Scopes and expiration are immutable โ rotate the key to change them.
Return the most recent audit-log entries for a single key: method, target, response code, duration, timestamp. Capped at 100 per request.
Dock Optimizer is designed for organizations running multiple agents at once โ Conmitto-operated agents (Zo Computer), third-party orchestrators, and customer-owned agents built by end users. Use the checklist below to keep the permission model honest as that fan-out grows.
zo, give each its own key so rate limits and audit logs are separable.<tenant>-<agent>-<env> โ e.g. kobelco-zo-booking-prod, akash-claude-desktop-dev.scopes: "read". Only add write when the agent demonstrably needs to mutate state.agentConfig.capabilities array to the specific tool families the agent uses (["appointments"], ["inventory"], or both).writeRateLimit so a runaway loop is capped.End users in an organization can generate their own API keys from Integration Hub โ Agent Keys. Customer agents inherit the organization's data boundary automatically โ they cannot read or modify any other tenant's data. What you do control as an admin:
contactEmail and description on every key so you can page the owner when something misbehaves.expiresAt values (30โ90 days) on user-generated keys; rotations become a natural checkpoint.Agents that Conmitto operates on behalf of a tenant (Zo Computer, internal automations) should:
environment = staging or production for visibility.agentName prefix (e.g. zo-โฆ) so audit logs make first-party traffic easy to spot.propose_*) for anything a human would want to review โ door blocks, flagged appointments, ad-hoc report runs. Reserve auto-confirm tools for well-bounded user intents.| Signal | Action |
|---|---|
| Key appears in a public repo, log, or screenshot | Revoke immediately. Do not regenerate from the same ID โ generate a fresh one and update the agent. |
Audit log shows unexpected tools/call traffic | Revoke, then review dashboard for correlated keys. Email the contactEmail owner. |
| Daily quota exhausted repeatedly | Don't raise the limit reflexively โ inspect the activity feed for loops, then scale deliberately. |
| Employee leaves the team | Revoke every key owned by their contactEmail. Regenerate shared agent keys so the departed user's local copy is invalidated. |
Every authenticated MCP and REST request is written to the AgentAuditLogs table with the fields below. Logs are always scoped by CompanyId, so your organization only ever sees its own traffic.
| Field | Meaning |
|---|---|
companyId | Tenant the call was attributed to |
mcpKeyId | Which key made the call |
agentName | Agent identifier at call time (denormalized for immutable history) |
method | tools/list, tools/call, resources/list, resources/read, or REST_GET for REST traffic |
target | Tool name, resource URI, or REST path |
requestPayload | Request arguments (truncated at 4 KB) |
responseCode | HTTP status (200, 401, 403, 404, 429, 500) |
responseSummary | Result or error text (truncated) |
durationMs | Server-side latency |
createdUtc | Timestamp |
Query per-key activity via GET /api/agent-keys/activity/{id}. Aggregate dashboard data โ including recent activity samples per key โ is available at GET /api/agent-keys/dashboard.
id field on every JSON-RPC request (a ULID or UUID is ideal). This lets you correlate your own client-side trace with the AgentAuditLogs entry when debugging.Follow these steps to connect your first AI agent with least-privilege defaults:
agentName (e.g. zo-booking-prod), set contactEmail, start with scopes: "read", and set an expiresAt 90 days out. Copy the do_live_... token immediately โ it is shown only once.GET /api/mcp/manifest.json (unauthenticated) to bootstrap your client, then call tools/list and resources/list for the authoritative schema filtered to your key's scopes and capabilities.resources/read with dock://facilities to get your facility IDs, then dock://operations/today for today's dashboard.scopes: "read,write" and the minimum capabilities array it needs. Test auto-confirm tools (e.g. propose_appointment) before enabling HITL flows./api/agent-keys/activity/{id} during rollout, and set up a weekly review of /api/agent-keys/dashboard to catch stale or over-privileged keys.tools/list + resources/list into your agent context on each session โ it will always reflect the exact permissions of the key in use (scopes, capabilities, HITL gating).