verae-mcp-timestamping-serv.../docs/superpowers/specs/2026-09-29-go-mcp-timestamping-server-design.md
George Lambert 6c20d87131 docs: embedded NATS, bearer require flag, host-token keeper
Amend design/plan: in-process NATS calling VeraeTime; MCP bearer
passthrough when present; VERAE_MCP_REQUIRE_BEARER=0 keeps a server
host token when none supplied; =1 requires caller bearer.
2026-09-29 11:49:43 -04:00

10 KiB
Raw Blame History

Go MCP timestamping server

Date: 2026-09-29
Status: Draft for review
Sibling of: verae-mcp-timestamping-server (Python, live on ns1)
Contract: Same digests-only MCP tools as https://mcp.verae.ai/mcp and Application Client OpenAPI

Problem

The live Verae MCP timestamping service is a Python process (verae-mcp-timestamping-server in a venv, Streamable HTTP on 127.0.0.1:8000). Operators want a Go executable with full tool and ledger parity, suitable for shadow deploy and eventual cutover, without changing the public MCP contract.

Goals

  1. Single static Go binary implementing the MCP timestamping server.
  2. Full tool parity: stamp, verify, hash_report, signup, login, session, create_user.
  3. Local/shared FileLedger (JSON file, mode 0600) and global VeraeTime HTTPS client.
  4. Embedded NATS server in-process (nats-server library); handlers call VeraeTime directly.
  5. Bearer handling (configurable):
    • VERAE_MCP_REQUIRE_BEARER=0 (default): if the MCP/NATS request supplies Authorization: Bearer, use it (passthrough). If none is supplied, the server obtains and keeps a current host token (login via auth.properties username/password or VERAE_GLOBAL_TOKEN, refresh before expiry) and uses that for VeraeTime.
    • VERAE_MCP_REQUIRE_BEARER=1: missing/invalid bearer is rejected; no host-token fallback for global VeraeTime calls.
  6. Streamable HTTP + stdio transports; CORS / allowed hosts/origins compatible with ns1.
  7. Shadow on ns1 beside Python; documented cutover checklist.
  8. Importable pkg/tsnats client for bulk workers connecting to the embedded (or external) NATS listen address.
  9. Job-complete remains core NATS publish/subscribe; no JetStream for job-complete.

Non-goals (this project)

  • Changing live MCP tool schemas or Application Client OpenAPI semantics.
  • Replacing Python on day one without a shadow period.
  • JetStream (or other persistent streams) for job-complete / jobId notification.
  • Accepting file bytes / content on stamp tools.

Current state (Python)

  • Package: verae-mcp-timestamping-server (Python ≥3.10, mcp[cli]).
  • Deploy: /opt/verae/verae-mcp-timestamping-server, user verae-mcp, systemd verae-mcp-timestamping.service.
  • Public: Caddy → 127.0.0.1:8000 for mcp.verae.ai, mcp.veraetime.net, mcp.georgelambert.org.
  • Global stamps: POST https://api.veraetime.net/api/timestamp with restamp: false.

Architecture

AI assistants
   │  MCP Streamable HTTP / stdio
   │  Authorization: Bearer <user JWT>
   ▼
verae-mcp-timestamping-server-go (single binary)
   ├── MCP tools (AI) ──► FileLedger (local|shared) / VeraeTime (global)
   │                         bearer from MCP request context (passthrough)
   ├── Embedded NATS server (nats-server, in-process)
   │      subscribe request/reply:
   │        verae.ts.request → VeraeTime or FileLedger
   │        verae.ts.verify  → VeraeTime or FileLedger
   │        verae.hash.report → VeraeTime or FileLedger
   │      publish core notify: verae.ts.completed (no JetStream)
   │      token: from NATS msg header/field OR from MCP-originated publish context
   └── Listen: MCP :8001 (shadow) + NATS client port (e.g. 4223) for pkg/tsnats

Bulk workers ──pkg/tsnats──► embedded NATS port

Split: MCP tools stay for AI. Embedded NATS is the high-concurrency messaging plane; both paths call the same VeraeTime client with per-request bearer.

SDK: official github.com/modelcontextprotocol/go-sdk/mcp.
Embedded NATS: github.com/nats-io/nats-server/v2 + nats.go.
Client lib: pkg/tsnats connects to the embedded listen URL (or an external NATS URL for tests).

Repository layout

Path: /Users/marchon/research/modelcontextprotocol/verae-mcp-timestamping-server-go
Remote (planned): ssh://git@git.georgelambert.org:2223/marchon/verae-mcp-timestamping-server-go.git

cmd/verae-mcp-timestamping-server/main.go
pkg/digest/               # NormalizeSHA256, AssertDigestOnly (done)
pkg/tsnats/               # importable client → embedded or external NATS
internal/config/
internal/ledger/          # FileLedger
internal/veraetime/       # GlobalClient; per-request Bearer override
internal/embednats/       # start embedded nats-server; register subject handlers
internal/authctx/         # extract MCP bearer → context; attach to VeraeTime + NATS
internal/tools/           # MCP tool handlers
internal/server/          # MCP HTTP/stdio wiring
docs/superpowers/specs/
docs/cutover.md
README.md

Binary name: verae-mcp-timestamping-server
Install dir on ns1 (shadow): /opt/verae/verae-mcp-timestamping-server-go/ so it does not overwrite the Python venv path.

Tool contract

Match Python docs/api.md / live tools/list:

Tool Required args Notes
stamp sha256 scope local|shared|global; optional cube_id, org_id
verify receipt_id optional want_scope; global or CID-like id → VeraeTime
hash_report sha256 optional org_id, want_scope
signup username, password VeraeTime /auth/signup
login username, password stores JWT in auth.properties; does not return JWT
session — /auth/validate
create_user username, password, role admin bearer

Digests only: reject content/bytes/file/data/path/body/payload/blob/archive.
Normalize SHA-256 (optional sha256: prefix; 64 hex).
Global stamp: restamp: false; poll /api/status/{jobId} when needed.

Auth / bearer policy

Mode Config Behavior
Host-token fallback (default) VERAE_MCP_REQUIRE_BEARER=0 or unset Resolve bearer: request Bearer → else in-memory host token (mint/refresh via VeraeTime /auth/login using username/password from auth.properties, or use VERAE_GLOBAL_TOKEN / stored access_token). Never log the token.
Require caller bearer VERAE_MCP_REQUIRE_BEARER=1 Request must include Bearer; validate with VeraeTime /auth/validate (or reject 401 from upstream). No host fallback for stamp/verify/hash_report global paths.

login tool still writes auth.properties for operators. Host-token keeper loads from that file and refreshes when expires_at is near (skew ~60s). MCP session may cache the resolved bearer for the session lifetime when require-bearer is off.

Embedded NATS handlers use the same resolver: message/header token if present, else host token when require-bearer is off.

pkg/tsnats (bulk app client)

Import path (planned): git.georgelambert.org/marchon/verae-mcp-timestamping-server-go/pkg/tsnats

API Behavior
Connect(url string, opts ...Option) (*Client, error) Core NATS connection
Stamp(ctx, sha256, opts) Request/reply on verae.ts.request; digests only
Verify(ctx, receiptID, opts) verae.ts.verify
HashReport(ctx, sha256, opts) verae.hash.report
SubscribeCompleted(ctx, handler) Core subscribe to verae.ts.completed (and documented receipt alias if any); not JetStream

Defaults: request timeout ~2s (configurable); concurrent Stamp calls are safe; empty/invalid digests fail client-side before publish.

The MCP server’s optional NATS upstream (internal/natsbridge) imports this package so bus encoding stays one implementation.

Bulk guidance: prefer many in-flight Stamp calls (or fire request + SubscribeCompleted) over serial MCP tools/call. For central global certificates when not on the bus, Application Client / VeraeTime HTTPS remains valid.

Config (env)

Variable Default
VERAE_MCP_TRANSPORT streamable-http (or stdio)
VERAE_MCP_HOST 127.0.0.1
VERAE_MCP_PORT 8000 (shadow uses 8001)
VERAE_GLOBAL_URL https://api.veraetime.net
VERAE_AUTH_PROPERTIES $HOME/.verae/auth.properties
VERAE_GLOBAL_TOKEN empty (optional static host bearer)
VERAE_MCP_REQUIRE_BEARER 0 (off = host-token fallback; 1 = require caller bearer)
VERAE_LEDGER_PATH $HOME/.verae/mcp-timestamp-local.json
VERAE_NATS_LISTEN 127.0.0.1:4223 (embedded NATS client port; empty disables embed)
VERAE_MCP_CORS unset / 1
VERAE_MCP_ALLOWED_HOSTS same list as Python verae-ai.conf
VERAE_MCP_ALLOWED_ORIGINS same list as Python

HOME for the service user remains /var/lib/verae-mcp (shared auth file with Python during shadow if desired, or separate path for isolation).

Shadow deploy

  1. Build linux amd64 (and optionally arm64) release binary.
  2. Install under /opt/verae/verae-mcp-timestamping-server-go/.
  3. Systemd unit verae-mcp-timestamping-go.service: VERAE_MCP_PORT=8001, same user verae-mcp, same or dedicated auth properties.
  4. Optional Caddy path or internal-only listen for smoke (do not steal public /mcp until cutover).
  5. Compare tools/list and stamp/verify/hash_report against Python on :8000.

Cutover checklist (docs/cutover.md)

  1. Shadow green for agreed soak (tools + global stamp/verify/hash_report + local ledger).
  2. Stop Python unit (or switch Caddy upstream).
  3. Point Go unit at VERAE_MCP_PORT=8000 (or Caddy to Go’s port).
  4. Confirm https://mcp.verae.ai/mcp tools/list and a known digest hash_report.
  5. Keep Python tree on disk for rollback for one release cycle.
  6. Rollback: restore Python ExecStart / Caddy upstream; restart.

Testing

  • Unit: digest normalize/guard; FileLedger idempotency; VeraeTime request shaping (restamp: false) with fake HTTP.
  • Integration: Streamable HTTP initialize → tools/call against an httptest test server.
  • Parity script: same digests against Python :8000 and Go :8001.
  • Optional live smoke behind flag (requires auth.properties).

Success criteria

  • go build produces a single runnable binary.
  • Full tool list matches Python.
  • Local/shared persist to ledger file; global uses VeraeTime with caller bearer or host-token fallback per VERAE_MCP_REQUIRE_BEARER.
  • Embedded NATS (when enabled) handles verae.ts.* and does not use JetStream for job-complete.
  • pkg/tsnats builds as an importable library with unit tests against embedded or test NATS.
  • Host-token keeper refreshes before expiry when require-bearer is off.
  • Shadow unit runs on ns1 without disturbing Python :8000.
  • Cutover doc is complete enough to swap without inventing steps on the day.

Open follow-ups

  • Whether shadow shares /var/lib/verae-mcp/auth.properties with Python or uses a separate file.
  • Whether public hostname gets a temporary Go-only path (e.g. /mcp-go) during soak.