MCP ยท JSON-RPC 2.0

Dock Optimizer MCP

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).

POST https://dockoptimizer.com/api/mcp/v1/message

๐Ÿ“– Overview

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:

For partner TMS/ERP polling without JSON-RPC, see the REST surface instead. Both share one credential, one rate-limit bucket, one audit trail.

๐Ÿ” Authentication

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}'
๐Ÿ”’
Security: API keys are SHA-256 hashed before storage. The raw token is shown exactly once during generation โ€” store it securely. Tokens begin with do_live_.

Key Scopes

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.

ScopeAllows
readresources/read on tenant URIs and all GET /api/v1/* endpoints. Discovery methods (tools/list, resources/list) do not require any scope.
writetools/call (auto-confirm and HITL tools) plus POST /api/mcp/v1/files/{eventId} multipart uploads. Implies read.
inventoryRequired in addition to write for receive_inventory, issue_inventory, adjust_inventory. Combine as read,write,inventory.
โ„น๏ธ
Keys with no scopes set have full access for backward compatibility. All new keys should be created with explicit scopes โ€” use read only for monitoring/observability agents, and read,write for agents that need to create or modify records.

Capability Gating (Fine-Grained Access)

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.

CapabilityGates
inventoryreceive_inventory, issue_inventory, adjust_inventory
appointmentsReserved โ€” 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"
}
โ„น๏ธ
A missing or empty capabilities array means all capability-gated tools are allowed (backward compatible). Explicit arrays are the recommended default going forward.

Environment Tagging

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.

โš ๏ธ
Generate each key in the environment you plan to use it. The Integration Hub only lets you mint keys for the env it itself is running in โ€” a prod key must be generated in prod, a staging key in staging.

Key Identity & Metadata

Every key carries identifying metadata so admins can distinguish one agent from another on the dashboard and in audit logs:

FieldPurpose
agentNameShort identifier โ€” e.g. zo-booking-prod, claude-desktop-akash
contactEmailOwner email; receives alerts on suspicious use or quota exhaustion
descriptionFree-text purpose โ€” what the agent is for, where it runs
expiresAtOptional absolute expiry (UTC). Recommended for experimental or user-BYO keys
writeRateLimitPer-hour ceiling on write-tool calls (null = inherit the daily quota only)

Rotation & Revocation

๐Ÿ“ก Endpoint

The MCP surface uses JSON-RPC 2.0 over a single HTTP endpoint. All requests are POST with a JSON body.

POST /api/mcp/v1/message

Send a JSON-RPC 2.0 request. The method field determines the operation.

Request Format

{
  "jsonrpc": "2.0",
  "method": "tools/list",
  "params": {},
  "id": 1
}

Handshake

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.

Idempotency

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.

Available Methods

MethodScopeDescription
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

Tools represent write operations that modify system state. Tools are split into two categories:

๐Ÿ› ๏ธ
Configuration tools (coming soon): A roadmap is in flight to expose org-config writes โ€” appointment types, questions, facility hours, door rules, carrier records โ€” as propose_* tools gated by the same HITL approval flow. Track progress in MCP_ALIGNMENT.md ยง3.

Auto-Confirmed Tools

These tools execute immediately and return the result inline.

propose_appointment auto-confirm

Create a new appointment. Auto-confirmed and immediately live. If eventTypeId is omitted, auto-selects the first active type for the facility.

ParameterTypeRequiredDescription
facilityIdintegerโœ…Facility ID
scheduledDatestringโœ…Date in YYYY-MM-DD
scheduledTimestringโœ…Time in HH:MM (24h)
eventTypeIdintegerAppointment type ID (from dock://event-types)
carrierNamestringCarrier name (defaults to "Walk-in")
bolNumberstringBill of Lading number
poNumberstringPurchase Order number (single)
poNumbersstring[]Purchase Order numbers (multiple)
notesstringFree-text notes (BOL details, weight, etc.)
driverCheckInNamestringDriver name
driverCheckInPhonestringDriver phone
driverCheckInEmailstringDriver email
shipperNamestringShipper name
consigneeNamestringConsignee name
totalWeightnumberShipment weight
totalPiecesintegerNumber of pieces
appointmentTypestringpickup, dropoff, or both

confirm_appointment auto-confirm

Confirm and finalize a previously proposed appointment.

ParameterTypeRequiredDescription
proposedAppointmentIdstring (GUID)โœ…The GUID returned from propose_appointment

update_appointment auto-confirm

Update fields on an existing appointment. Only include the fields you want to change.

ParameterTypeRequiredDescription
eventIdintegerโœ…Appointment ID to update
scheduledDatestringNew date (YYYY-MM-DD)
scheduledTimestringNew time (HH:MM, 24h)
carrierNamestringUpdated carrier name
bolNumberstringUpdated BOL number
poNumbersstring[]Updated PO numbers
notesstringUpdated notes
driverCheckInNamestringUpdated driver name
driverCheckInEmailstringUpdated driver email
driverCheckInPhonestringUpdated driver phone

cancel_appointment auto-confirm

Cancel an existing appointment.

ParameterTypeRequiredDescription
eventIdintegerโœ…Appointment ID to cancel
reasonstringCancellation reason

mark_noshow auto-confirm

Mark a past appointment as no-show.

ParameterTypeRequiredDescription
eventIdintegerโœ…Appointment ID to mark as no-show

Inventory Tools

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-confirm

Record receipt of goods โ€” increases QuantityOnHand. Creates an audited Receipt transaction.

ParameterTypeRequiredDescription
skustring*SKU of the inventory item
itemIdstring (GUID)*Item ID (alternative to sku)
quantitynumberโœ…Quantity to receive (must be positive)
referencestringExternal reference (BOL#, PO#, Receipt#)
eventIdintegerAppointment to link this receipt to

issue_inventory auto-confirm

Record issue / shipment of goods โ€” decreases QuantityOnHand. Validates available stock before issuing.

ParameterTypeRequiredDescription
skustring*SKU of the inventory item
itemIdstring (GUID)*Item ID (alternative to sku)
quantitynumberโœ…Quantity to issue (must be positive)
eventIdintegerAppointment to link this issue to

adjust_inventory auto-confirm

Set inventory to an absolute quantity (cycle count corrections, damage write-offs). Captures before/after quantities.

ParameterTypeRequiredDescription
skustring*SKU of the inventory item
itemIdstring (GUID)*Item ID (alternative to sku)
quantitynumberโœ…New absolute quantity
reasonstringReason for adjustment

* = either sku or itemId is required; if both are provided, itemId wins.

File Upload

File uploads use a dedicated multipart endpoint rather than tools/call. The call is audited under method upload_file.

POST /api/mcp/v1/files/{eventId} write

Attach a file (BOL, photo, PDF) to an existing appointment. Requires a tenant key with write scope.

FieldTypeRequiredDescription
eventIdinteger (path)โœ…Appointment to attach to
filefile (multipart)โœ…Binary upload; validated server-side

Human-in-the-Loop (HITL) Tools

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 HITL

Propose marking a facility door as blocked or full load.

ParameterTypeRequiredDescription
doorIdintegerโœ…Door ID to block
reasonstringReason for blocking
isFullLoadbooleanWhether the door is full load

propose_update_appointment HITL

Propose updating appointment fields (carrier, PO, driver info, notes).

ParameterTypeRequiredDescription
appointmentIdintegerโœ…Appointment ID
carrierNamestringCarrier name
poNumberstringPO or reference number
driverCheckInEmailstringDriver email
driverCheckInPhonestringDriver phone
notesstringNotes to append

propose_flag_appointment HITL

Flag an appointment for human review (data quality issues, mismatches, etc.).

ParameterTypeRequiredDescription
appointmentIdintegerโœ…Appointment ID
flagReasonstringโœ…Reason for flagging
severitystringlow, medium, or high

propose_run_report HITL

Trigger a scheduled report to run on demand.

ParameterTypeRequiredDescription
reportIdintegerโœ…Scheduled report ID (from dock://reports)

Example: Call a Tool

{
  "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
}

Response (Auto-Confirmed)

{
  "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
}

Response (HITL Queued)

{
  "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

Resources represent read-only data about your facility's current state. Use resources/list to discover all available URIs at runtime.

Appointments

URIDescription
dock://appointmentsQuery 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}/filesFiles attached to an appointment (BOLs, photos)
dock://appointments/{id}/ocr_outputOCR-extracted data from uploaded documents
dock://appointments/{id}/notesTimestamped free-text notes on an appointment
dock://appointments/stalePast-due appointments still in "scheduled" status. Optional: ?facilityId=&limit=
dock://appointments/checkConflict check. Required: ?date=YYYY-MM-DD&facilityId=X. Optional: &carrierName=
dock://pending-appointmentsAppointment proposals awaiting confirmation

Doors & Facilities

URIDescription
dock://doors/statusAll doors with computed status (available/occupied/blocked), current booking, next appointment
dock://doors/configFull door registry: sensor IDs, auto-block settings, active/inactive, facility assignment
dock://doors/historyHistorical door bookings with dwell time. Optional: ?doorId=&facilityId=&start=&end=&limit=
dock://doors/availableAvailable door time slots. Required: ?date=YYYY-MM-DD. Optional: &facilityId=
dock://door-availabilityOpen slots โ‰ฅ 30 min for a facility. Required: ?facilityId=X&date=YYYY-MM-DD
dock://slots/availableReal-time slot availability per appointment type. Required: ?date=YYYY-MM-DD
dock://facilitiesAll facilities with door list, timezone, address, sensor config

Organization & Operations

URIDescription
dock://operations/todayOperations dashboard per facility (check-ins, pending, no-shows, etc.). Supports ?date=
dock://organizationOrganization info: name, address, facilities, door counts, configuration
dock://usersAll users with recent login activity (last 5 logins per user)
dock://reportsScheduled email reports with frequency and last run status
dock://categoriesAppointment categories with door restrictions and question config
dock://event-typesAppointment types with duration, facility, and category info

Carriers & Inventory

URIDescription
dock://carriersAll active carriers with FMCSA data (MC#, DOT#), contact info, appointment counts
dock://carriers/searchFuzzy search by name or MC#. Optional: ?name=X&mc=Y
dock://inventory/summaryStock health: totals, low-stock alerts, quarantined, expiring, category breakdown
dock://inventory/itemsItemized inventory with filters: ?search=&category=&facilityId=&lowStockOnly=&owner=&limit=&offset=
dock://assetsFixed assets (equipment, machinery): ?search=&category=&status=Active|Retired|InRepair
dock://data-quality/summaryOrg-wide data health score: missing carriers, stale appointments, duplicates

Example: Read a Resource

{
  "jsonrpc": "2.0",
  "method": "resources/read",
  "params": {
    "uri": "dock://operations/today"
  },
  "id": 3
}

๐Ÿงญ Discovery Manifest

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.

GET /api/mcp/manifest.json

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.

โ„น๏ธ
The manifest advertises the full tenant surface. The authoritative runtime contract for a specific key is whatever tools/list and resources/list return when that key calls them.

โŒ Error Handling

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.

RFC 7807 Problem Details (REST + transport-level MCP errors)

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"
}

JSON-RPC error codes (MCP)

HTTPJSON-RPC CodeMeaning
400-32700Parse error โ€” invalid JSON in request body
401โ€”Missing, invalid, or expired API key
403-32600Insufficient scope for this operation
400-32601Method not found
400-32602Invalid params โ€” missing required fields
404-32001Invalid resource URI format
404-32002Resource not found or empty result
429โ€”Rate limit exceeded โ€” implement exponential backoff
500-32603Internal server error

Recovery Hints

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."
    }
  }
}

โšก Rate Limits

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.

LimitWindowScopeConfigurable
60 requestsPer minutePer API key (burst)Global โ€” rejection is immediate (no queue)
1,000 requestsPer UTC dayPer API key (quota)Server-wide via MCP:DailyCallLimit
Write-tool ceilingPer hourPer API key (optional)Per-key via update-metadata (null = unlimited)

429 Response Headers

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.

โš ๏ธ
Backoff strategy for agents: on 429, wait 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.

Right-sizing Per-Key Limits

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.

๐Ÿ”‘ Key Management

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.

GET /api/agent-keys/list

List all active keys for the calling admin's organization with agent name, prefix, scopes, lastUsedAt, daily/total call counts, and expiration status.

GET /api/agent-keys/dashboard

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.

POST /api/agent-keys/generate

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.

POST /api/agent-keys/{id}/regenerate

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.

POST /api/agent-keys/{id}/revoke

Revoke an API key immediately โ€” all future requests with this key will return 401. Irreversible.

POST /api/agent-keys/{id}/update-metadata

Update owner-visible metadata. Body accepts contactEmail, description, agentConfig (validated JSON), and writeRateLimit. Scopes and expiration are immutable โ€” rotate the key to change them.

GET /api/agent-keys/activity/{id}?limit=20

Return the most recent audit-log entries for a single key: method, target, response code, duration, timestamp. Capped at 100 per request.

๐Ÿ›ก๏ธ Multi-Agent & RBAC Best Practices

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.

One Key Per Agent, Per Environment

Least-Privilege by Default

  1. Start every new key with scopes: "read". Only add write when the agent demonstrably needs to mutate state.
  2. Restrict the agentConfig.capabilities array to the specific tool families the agent uses (["appointments"], ["inventory"], or both).
  3. For agents that only perform occasional writes (daily rollup, nightly flag cleanup), set an explicit writeRateLimit so a runaway loop is capped.

Customer (BYO) Agents

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:

Platform (Zo / Conmitto-operated) Agents

Agents that Conmitto operates on behalf of a tenant (Zo Computer, internal automations) should:

Incident Response

SignalAction
Key appears in a public repo, log, or screenshotRevoke immediately. Do not regenerate from the same ID โ€” generate a fresh one and update the agent.
Audit log shows unexpected tools/call trafficRevoke, then review dashboard for correlated keys. Email the contactEmail owner.
Daily quota exhausted repeatedlyDon't raise the limit reflexively โ€” inspect the activity feed for loops, then scale deliberately.
Employee leaves the teamRevoke every key owned by their contactEmail. Regenerate shared agent keys so the departed user's local copy is invalidated.

๐Ÿ“’ Audit & Observability

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.

FieldMeaning
companyIdTenant the call was attributed to
mcpKeyIdWhich key made the call
agentNameAgent identifier at call time (denormalized for immutable history)
methodtools/list, tools/call, resources/list, resources/read, or REST_GET for REST traffic
targetTool name, resource URI, or REST path
requestPayloadRequest arguments (truncated at 4 KB)
responseCodeHTTP status (200, 401, 403, 404, 429, 500)
responseSummaryResult or error text (truncated)
durationMsServer-side latency
createdUtcTimestamp

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.

๐Ÿ’ก
For agents: emit a stable 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.

๐Ÿš€ Getting Started

Follow these steps to connect your first AI agent with least-privilege defaults:

  1. Generate an API Key โ€” Navigate to Integration Hub โ†’ Agent Keys. Use a descriptive 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.
  2. Discover the Server โ€” Fetch 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.
  3. Read Facility Data โ€” Call resources/read with dock://facilities to get your facility IDs, then dock://operations/today for today's dashboard.
  4. Promote to Write โ€” Once your agent reliably reads the right data, rotate the key with scopes: "read,write" and the minimum capabilities array it needs. Test auto-confirm tools (e.g. propose_appointment) before enabling HITL flows.
  5. Monitor & Iterate โ€” Watch /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.
๐Ÿ’ก
Tip for Agent Orchestrators (Zo, Claude Desktop, LangChain, Cursor): The API is self-describing. Feed the live output of 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).