MCP Integration

Implemented ยท basicDraft 0.1

Runs today. The Go server in this repository serves five MCP tools over streamable HTTP at /mcp. It implements no authentication and no signatures, so keep it on localhost or a trusted network. Resources and prompts are still proposals; they are marked below.

MCP and Techlog

The Model Context Protocol (MCP) is how an agent talks to a tool. Techlog defines what the events mean and how the lifecycle fits together. An agent that already speaks MCP needs no custom client code to read history or record progress.

Connect

Start the server (see Run the server), then register it with Claude Code:

claude mcp add --transport http techlog http://localhost:8080/mcp

Use localhost or 127.0.0.1 in the URL. The server rejects requests whose Host header names another host, as a guard against DNS rebinding. The techlog-record skill teaches Claude when to record an event, which type to use, and how to link it to earlier events; make skill installs it.

Tools

Tool names use underscores, because some MCP clients accept only letters, digits, underscores, and hyphens.

ToolPurpose
techlog_search_eventsSearch events by free text, system, type, and time range, newest first. The query is optional.
techlog_get_system_historyRead the latest events of one system in chronological order, optionally with links.
techlog_get_eventFetch one event by id, with the links that leave it and the links that point at it.
techlog_record_eventRecord a structured event. The server assigns the id and validates the event against the JSON Schema.
techlog_link_eventsLink two existing events. A caused_by link must state confirmed or hypothesis; other links default to confirmed.

These definitions are the server's own tools/list output, trimmed to the name, description, and input schema.

[
  {
    "name": "techlog_search_events",
    "description": "Search engineering events by free text, system, type and time range. Newest first. Use it before recording, to find the earlier events a new one should link to.",
    "inputSchema": {
      "additionalProperties": false,
      "properties": {
        "limit": {
          "description": "1 to 100, default 20",
          "type": "integer"
        },
        "query": {
          "description": "free text matched against summary, system, component and type; words are ANDed and punctuation is plain text",
          "type": "string"
        },
        "since": {
          "description": "RFC 3339 timestamp or YYYY-MM-DD; events at or after it",
          "type": "string"
        },
        "system": {
          "description": "only events about this system",
          "type": "string"
        },
        "types": {
          "description": "only these event types, for example incident.opened",
          "items": {
            "type": "string"
          },
          "type": [
            "null",
            "array"
          ]
        },
        "until": {
          "description": "RFC 3339 timestamp or YYYY-MM-DD; events at or before it",
          "type": "string"
        }
      },
      "type": "object"
    }
  },
  {
    "name": "techlog_get_system_history",
    "description": "Read the latest events of one system in chronological order, optionally with their links.",
    "inputSchema": {
      "additionalProperties": false,
      "properties": {
        "include_links": {
          "description": "include each event's links, default true",
          "type": [
            "null",
            "boolean"
          ]
        },
        "limit": {
          "description": "1 to 200, default 50; the latest events are returned",
          "type": "integer"
        },
        "since": {
          "description": "RFC 3339 timestamp or YYYY-MM-DD; events at or after it",
          "type": "string"
        },
        "system": {
          "description": "the system whose history to read",
          "type": "string"
        }
      },
      "required": [
        "system"
      ],
      "type": "object"
    }
  },
  {
    "name": "techlog_get_event",
    "description": "Fetch one event by id, with the links that leave it and the links that point at it.",
    "inputSchema": {
      "additionalProperties": false,
      "properties": {
        "id": {
          "description": "event id, for example evt_01J9K2A0P011",
          "type": "string"
        }
      },
      "required": [
        "id"
      ],
      "type": "object"
    }
  },
  {
    "name": "techlog_record_event",
    "description": "Record a structured Techlog event (draft 0.1). Do not supply an id: the server assigns it. idempotency_key is required and unique per actor: use one stable key per logical event and reuse it when retrying; a duplicate is rejected and names the existing event. Events are immutable; record a correction as a new event. On a validation error the result lists the field paths to fix.",
    "inputSchema": {
      "additionalProperties": false,
      "properties": {
        "event": {
          "additionalProperties": true,
          "description": "the event without an id: type, time, actor, subject, status, provenance, idempotency_key, and optionally summary, sources, evidence, links, extensions",
          "type": "object"
        }
      },
      "required": [
        "event"
      ],
      "type": "object"
    }
  },
  {
    "name": "techlog_link_events",
    "description": "Link two existing events. basis is confirmed or hypothesis; it is required for caused_by and defaults to confirmed for other relations.",
    "inputSchema": {
      "additionalProperties": false,
      "properties": {
        "basis": {
          "description": "confirmed or hypothesis; required for caused_by",
          "type": "string"
        },
        "rationale": {
          "description": "why the link holds",
          "type": "string"
        },
        "rel": {
          "description": "relation, for example caused_by, implements, relates_to",
          "type": "string"
        },
        "source": {
          "description": "id of the later event, the one the link leaves",
          "type": "string"
        },
        "target": {
          "description": "id of the earlier event, the one the link points at",
          "type": "string"
        }
      },
      "required": [
        "source",
        "target",
        "rel"
      ],
      "type": "object"
    }
  }
]

Example: record an event

The agent supplies the event without an id. The response carries a human-readable content block for the model and a structuredContent object for code, which includes the stored event. isError is false on success. The server assigns the id; the id shown here is the one this story uses, and a live server assigns a new one. The event carries no signature, because the server does not implement signing; see signed events.

{
  "request": {
    "jsonrpc": "2.0",
    "id": 7,
    "method": "tools/call",
    "params": {
      "name": "techlog_record_event",
      "arguments": {
        "event": {
          "schema_version": "0.1-draft",
          "idempotency_key": "seed-spring-spin-004",
          "type": "proposal.recorded",
          "time": "2026-03-10T11:30:00Z",
          "actor": {
            "kind": "ai_agent",
            "id": "agent:code-assistant",
            "on_behalf_of": "user:a.novak"
          },
          "subject": { "system": "promo-api", "component": "spin-service" },
          "status": "proposed",
          "summary": "Spin-to-win: one spin per new account, server-side draw, discount capped at 20%, never stacked with other promotions",
          "links": [
            {
              "rel": "motivated_by",
              "target": "evt_01J9K2A0P002",
              "basis": "confirmed"
            },
            {
              "rel": "relates_to",
              "target": "evt_01J9K2A0P003",
              "basis": "confirmed"
            }
          ],
          "provenance": {
            "recorded_by": "agent:code-assistant",
            "method": "agent_submitted",
            "confidence": "asserted"
          },
          "extensions": {
            "org.techlog.proposal": {
              "alternatives_considered": [
                {
                  "option": "Client-side draw",
                  "rejected_because": "the result could be tampered with"
                },
                {
                  "option": "Unlimited spins",
                  "rejected_because": "invites abuse and deep discounts"
                }
              ]
            }
          }
        }
      }
    }
  },
  "response": {
    "jsonrpc": "2.0",
    "id": 7,
    "result": {
      "content": [{ "type": "text", "text": "Recorded evt_01J9K2A0P004 (proposed)" }],
      "structuredContent": {
        "id": "evt_01J9K2A0P004",
        "status": "proposed",
        "event": {
          "schema_version": "0.1-draft",
          "id": "evt_01J9K2A0P004",
          "idempotency_key": "seed-spring-spin-004",
          "type": "proposal.recorded",
          "time": "2026-03-10T11:30:00Z",
          "actor": {
            "kind": "ai_agent",
            "id": "agent:code-assistant",
            "on_behalf_of": "user:a.novak"
          },
          "subject": { "system": "promo-api", "component": "spin-service" },
          "status": "proposed",
          "summary": "Spin-to-win: one spin per new account, server-side draw, discount capped at 20%, never stacked with other promotions",
          "links": [
            {
              "rel": "motivated_by",
              "target": "evt_01J9K2A0P002",
              "basis": "confirmed"
            },
            {
              "rel": "relates_to",
              "target": "evt_01J9K2A0P003",
              "basis": "confirmed"
            }
          ],
          "provenance": {
            "recorded_by": "agent:code-assistant",
            "method": "agent_submitted",
            "confidence": "asserted"
          },
          "extensions": {
            "org.techlog.proposal": {
              "alternatives_considered": [
                {
                  "option": "Client-side draw",
                  "rejected_because": "the result could be tampered with"
                },
                {
                  "option": "Unlimited spins",
                  "rejected_because": "invites abuse and deep discounts"
                }
              ]
            }
          }
        }
      },
      "isError": false
    }
  }
}

Example: record verification evidence

Evidence travels inside the event, as a reference. The digest lets a reader later check that the referenced artifact is the one that was collected. There is no separate evidence tool today.

{
  "request": {
    "jsonrpc": "2.0",
    "id": 12,
    "method": "tools/call",
    "params": {
      "name": "techlog_record_event",
      "arguments": {
        "event": {
          "schema_version": "0.1-draft",
          "idempotency_key": "seed-spring-spin-007",
          "type": "verification.completed",
          "time": "2026-03-11T11:05:00Z",
          "actor": { "kind": "system", "id": "ci:pipeline" },
          "subject": { "system": "promo-api", "component": "spin-service" },
          "status": "verified",
          "summary": "Unit, integration, and abuse tests passed: repeat spins are rejected and the cap is enforced",
          "evidence": [
            {
              "kind": "test.report",
              "ref": "ci://run/90412",
              "digest": "sha256:7b1e4d9a2c6f08e35a9d1b7c4e2f6a8091d3c5b7e9f0a2c4d6e8b0a1c3e5f7d9"
            }
          ],
          "links": [
            {
              "rel": "verifies",
              "target": "evt_01J9K2A0P006",
              "basis": "confirmed"
            }
          ],
          "provenance": { "recorded_by": "collector:ci", "method": "collected" }
        }
      }
    }
  },
  "response": {
    "jsonrpc": "2.0",
    "id": 12,
    "result": {
      "content": [{ "type": "text", "text": "Recorded evt_01J9K2A0P007 (verified)" }],
      "structuredContent": {
        "id": "evt_01J9K2A0P007",
        "status": "verified",
        "event": {
          "schema_version": "0.1-draft",
          "id": "evt_01J9K2A0P007",
          "idempotency_key": "seed-spring-spin-007",
          "type": "verification.completed",
          "time": "2026-03-11T11:05:00Z",
          "actor": { "kind": "system", "id": "ci:pipeline" },
          "subject": { "system": "promo-api", "component": "spin-service" },
          "status": "verified",
          "summary": "Unit, integration, and abuse tests passed: repeat spins are rejected and the cap is enforced",
          "evidence": [
            {
              "kind": "test.report",
              "ref": "ci://run/90412",
              "digest": "sha256:7b1e4d9a2c6f08e35a9d1b7c4e2f6a8091d3c5b7e9f0a2c4d6e8b0a1c3e5f7d9"
            }
          ],
          "links": [
            {
              "rel": "verifies",
              "target": "evt_01J9K2A0P006",
              "basis": "confirmed"
            }
          ],
          "provenance": { "recorded_by": "collector:ci", "method": "collected" }
        }
      },
      "isError": false
    }
  }
}

When the server rejects an event

A failed call returns isError: true and a text block that lists each problem as a JSON Pointer path and a message, for example /actor/kind: value must be one of 'human', 'ai_agent', 'system'. The agent can fix exactly those fields and call the tool again. The HTTP API reports the same problems in error.details with status 422, because both go through the same validation. See what the server adds to the schema.

Proposed, not implemented

PartIdea
techlog_submit_evidenceAttach an evidence reference, optionally with a digest, to an event that already exists. Today, evidence is part of the event when it is recorded.
Resourcestechlog://systems/{system}/history, techlog://events/{id}
Prompt investigate_incident(incident_id)Gathers the incident, its release, and the linked changes, and asks for a hypothesis.
Prompt record_decision(event_id)Asks for the decision, its approver, and the terms, and records them against a proposal.

Agent walkthrough

  1. Retrieve history. techlog_get_system_history for promo-api, to see what shipped recently.
  2. Inspect the incident. techlog_search_events for incident.opened returns evt_01J9K2A0P011, linked to the 3.8.0 canary.
  3. Read the neighbors. techlog_get_event for the incident shows the change it is suspected to be caused by, and the rollback that mitigated it.
  4. Record the finding. techlog_record_event with an investigation.completed event that links back to the incident.
  5. Corroborate the hypothesis. techlog_link_events adds a caused_by link with basis confirmed from the investigation to the change. The earlier hypothesis link stays; nothing is edited.

Agent safety

An event submitted through MCP carries provenance.method: agent_submitted unless the agent states otherwise, and the techlog-record skill tells the agent to set actor.kind: ai_agent, so a reader can tell it apart from a collected or human-entered event. An agent never grants an approval for its own submission. See security and trust.

Status

PartStatus
Five tools over streamable HTTP, with schema validationImplemented (basic)
Claude skill techlog-recordImplemented
Authentication, event signaturesNot implemented
Rollback-plan and evidence tools, resources, promptsProposed