Eight TDD tasks: digest module, tsnats client, VeraeTime, FileLedger, MCP HTTP server, NATS bridge/stdio, cutover docs, shadow unit. Spec already includes AI MCP vs bulk NATS split.
9.2 KiB
Go MCP Timestamping Server + pkg/tsnats Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Ship a Go MCP timestamping server (full tool parity with Python) plus importable pkg/tsnats for bulk NATS clients, with shadow deploy docs for ns1 :8001.
Architecture: Official go-sdk/mcp for AI-facing Streamable HTTP/stdio; FileLedger + VeraeTime HTTPS; optional NATS upstream via pkg/tsnats. Job-complete stays core NATS only (no JetStream).
Tech Stack: Go 1.22+, github.com/modelcontextprotocol/go-sdk, github.com/nats-io/nats.go, net/http, stdio.
Spec: docs/superpowers/specs/2026-09-29-go-mcp-timestamping-server-design.md
Global Constraints
- Digests only: refuse content/bytes/file/data/path/body/payload/blob/archive.
- Global stamp
restamp: falseby default; poll/api/status/{jobId}when needed. - Never log JWT or password values.
- No JetStream (or other persistent stream) for job-complete / jobId notification.
- MCP tools:
stamp,verify,hash_report,signup,login,session,create_user. pkg/tsnatssubjects:verae.ts.request,verae.ts.verify,verae.hash.report; subscribeverae.ts.completed.- Shadow default port
8001; Python keeps8000. - Work in
/Users/marchon/research/modelcontextprotocol/verae-mcp-timestamping-server-go. - READMEs follow Standard Readme when touched.
- Frequent commits; one logical change each.
File map
| Path | Responsibility |
|---|---|
go.mod / go.sum |
Module + deps |
cmd/verae-mcp-timestamping-server/main.go |
Flags, env, serve |
internal/digest/ |
Normalize SHA-256 + forbidden keys |
internal/config/ |
Env loading |
pkg/tsnats/ |
Importable NATS client |
internal/veraetime/ |
Global HTTPS + auth.properties |
internal/ledger/ |
FileLedger local/shared |
internal/natsbridge/ |
MCP upstream wrapping tsnats |
internal/tools/ |
MCP tool registration/handlers |
internal/server/ |
MCP server wiring (HTTP + stdio) |
docs/cutover.md |
Shadow + cutover checklist |
README.md |
Standard Readme |
Task 1: Module scaffold + digest package
Files:
- Create:
go.mod,internal/digest/digest.go,internal/digest/digest_test.go,.gitignore - Modify:
README.md(Install already present; keep Standard Readme)
Interfaces:
-
Produces:
NormalizeSHA256(s string) (string, error),AssertDigestOnly(args map[string]any) error,ForbiddenKeys -
Step 1: Write failing tests
// internal/digest/digest_test.go
func TestNormalizeSHA256_StripPrefix(t *testing.T) {
h := strings.Repeat("a", 64)
got, err := NormalizeSHA256("sha256:" + h)
if err != nil || got != h {
t.Fatalf("got %q err %v", got, err)
}
}
func TestAssertDigestOnly_RejectsContent(t *testing.T) {
err := AssertDigestOnly(map[string]any{"sha256": strings.Repeat("a", 64), "content": "x"})
if err == nil {
t.Fatal("expected error")
}
}
- Step 2: Run tests — expect fail
Run: go test ./internal/digest/ -count=1
Expected: build/package errors
- Step 3: Implement digest +
go mod init
go mod init git.georgelambert.org/marchon/verae-mcp-timestamping-server-go
Implement normalize (lower hex, strip sha256:) and forbidden-key check. Add .gitignore for bin/, *.exe, .venv/.
- Step 4: Tests pass
Run: go test ./internal/digest/ -count=1
- Step 5: Commit
git add go.mod internal/digest .gitignore
git commit -m "feat: module scaffold and digest normalize/guard"
Task 2: pkg/tsnats client
Files:
- Create:
pkg/tsnats/client.go,pkg/tsnats/client_test.go,pkg/tsnats/subjects.go
Interfaces:
- Consumes:
internal/digest(or duplicate thin normalize in pkg to avoid internal import — Ruling: put a smallNormalizeSHA256copy inpkg/tsnatsor export via a publicpkg/digest; preferpkg/digestmoved from internal if needed) - Produces:
type Client structConnect(url string, opts ...Option) (*Client, error)Stamp(ctx, sha256 string, opts StampOptions) ([]Receipt, error)→verae.ts.requestVerify(ctx, receiptID string, opts VerifyOptions) (VerifyResult, error)HashReport(ctx, sha256 string, opts ReportOptions) ([]Receipt, error)SubscribeCompleted(ctx, func(CompletedEvent)) (Subscription, error)→ core subscribeverae.ts.completed
Ruling for Task 2: Move digest helpers to pkg/digest so both pkg/tsnats and internal/ can import them (Go forbids internal/ import from pkg/). Update Task 1 packages in the same commit if still only local, or do a quick move commit first.
- Step 1: Write failing client tests with nats-server or mock Conn
Prefer natsserver.RunServer from github.com/nats-io/nats-server/v2/test or inject a Requester interface:
type Requester interface {
Request(ctx context.Context, subj string, data []byte) ([]byte, error)
}
Test: Stamp sends JSON {"hash":"...","scope":"global"} on verae.ts.request and parses receipt list.
-
Step 2: Run — expect fail
-
Step 3: Implement subjects + Client
Subjects constants exactly: verae.ts.request, verae.ts.verify, verae.hash.report, verae.ts.completed. Digests only. Timeout default 2s. No JetStream APIs in this package.
- Step 4: Tests pass
Run: go test ./pkg/tsnats/ -count=1
- Step 5: Commit
git commit -m "feat: pkg/tsnats digests-only NATS timestamp client"
Task 3: VeraeTime HTTPS client + auth.properties
Files:
- Create:
internal/veraetime/client.go,authprops.go,*_test.go
Interfaces:
-
Produces:
Stamp(digest),Verify(receiptID),HashReport(digest),Signup,Login,Session,CreateUser -
Token order: ctor →
VERAE_GLOBAL_TOKEN→ auth.properties -
Stamp body:
data,digest,hashAlg: SHA256,restamp: false -
Step 1: Fake HTTP round-tripper tests (existing certificate path + 401 → AuthError)
-
Step 2: Fail
-
Step 3: Implement
-
Step 4: Pass —
go test ./internal/veraetime/ -count=1 -
Step 5: Commit —
feat: VeraeTime client and auth.properties
Task 4: FileLedger (local/shared)
Files:
- Create:
internal/ledger/file.go,file_test.go
Interfaces:
-
Stamp(digest, scope, cubeID, orgID) ([]Receipt, error)— idempotent for same hash+org+scope -
Verify,HashReportagainst file -
Path mode
0600; parent dir0700 -
Step 1–5: TDD; commit
feat: FileLedger for local and shared scopes
Task 5: MCP tools + Streamable HTTP server
Files:
- Create:
internal/tools/register.go, handlers;internal/server/server.go;cmd/verae-mcp-timestamping-server/main.go - Depend: go-sdk
mcp.NewStreamableHTTPHandler,mcp.AddTool
Routing:
-
stampscope global → veraetime; local|shared → ledger -
verify/hash_reportwant_scope=global or CID-like → veraetime; else ledger -
auth tools → veraetime
-
Step 1: Integration test httptest initialize → tools/list → tools/call stamp local
-
Step 2: Fail
-
Step 3: Wire server + main flags (
-transport,-host,-port, env overrides); default shadow-friendly:8001in examples only — code default may match Python8000with README documenting8001for shadow -
Step 4:
go test ./...andgo build -o bin/verae-mcp-timestamping-server ./cmd/verae-mcp-timestamping-server -
Step 5: Commit —
feat: MCP Streamable HTTP server with full tool set
Task 6: internal/natsbridge + stdio transport
Files:
-
Create:
internal/natsbridge/bridge.gowrappingpkg/tsnats -
Modify: config + main to enable when
VERAE_NATS_URLset (ledger backend switch or dual) -
Add stdio serve path via go-sdk stdio transport
-
Step 1–5: TDD with fake tsnats; commit
feat: optional NATS upstream and stdio transport
Task 7: Docs — cutover, README Usage for tsnats, example
Files:
- Create:
docs/cutover.md,examples/tsnats-bulk/main.go(optional small example) - Modify:
README.md— Install/Usage for binary +pkg/tsnatsimport snippet; Standard Readme
client, err := tsnats.Connect(os.Getenv("NATS_URL"))
receipts, err := client.Stamp(ctx, digest, tsnats.StampOptions{Scope: "global"})
- Step 1–5: Write docs;
go test ./...; commitdocs: cutover checklist and tsnats usage
Task 8: Shadow unit file + local parity script
Files:
-
Create:
deploy/verae-mcp-timestamping-go.service(PORT=8001) -
Create:
scripts/parity_smoke.sh— tools/list against:8000and:8001when both up -
Do not install on ns1 unless user says publish/deploy.
-
Commit
chore: shadow systemd unit and parity smoke script
Self-review (plan vs spec)
| Spec item | Task |
|---|---|
| Go MCP binary full tools | Task 5–6 |
| FileLedger | Task 4 |
| VeraeTime + auth.properties | Task 3 |
| pkg/tsnats | Task 2 |
| natsbridge reuses tsnats | Task 6 |
| No JetStream job-complete | Tasks 2, 6, Global Constraints |
| Shadow :8001 + cutover | Tasks 7–8 |
| Digests only | Task 1 (+ enforced everywhere) |