From 15781bfcc09f0c5931fbc92097253f13baab60d9 Mon Sep 17 00:00:00 2001 From: Matt Van Horn <455140+mvanhorn@users.noreply.github.com> Date: Sun, 17 May 2026 20:42:43 -0700 Subject: [PATCH] feat(mcp): scaffold Go module under mcp/ for Claude Desktop bundle Add a new top-level mcp/ Go module that will host the Claude Desktop MCPB server. U1 of the Claude Desktop .mcpb bundle plan: scaffold only, no behavior yet. - mcp/go.mod targets Go 1.22+ with mark3labs/mcp-go as the planned dependency (added in U3 when the server wiring lands). - mcp/scripts/sync-engine.sh mirrors skills/last30days/scripts/ into mcp/vendored/ before each build, keeping the Code skill and Desktop bundle on the same engine source. - Root .gitignore excludes mcp/vendored/ and mcp/build/ so the engine mirror and cross-compiled binaries stay local. --- .gitignore | 5 +++++ mcp/.gitignore | 9 ++++++++ mcp/README.md | 36 +++++++++++++++++++++++++++++++ mcp/cmd/last30days-pp-mcp/main.go | 11 ++++++++++ mcp/go.mod | 3 +++ mcp/scripts/sync-engine.sh | 32 +++++++++++++++++++++++++++ 6 files changed, 96 insertions(+) create mode 100644 mcp/.gitignore create mode 100644 mcp/README.md create mode 100644 mcp/cmd/last30days-pp-mcp/main.go create mode 100644 mcp/go.mod create mode 100755 mcp/scripts/sync-engine.sh diff --git a/.gitignore b/.gitignore index 3ecb537..aa03da0 100644 --- a/.gitignore +++ b/.gitignore @@ -26,5 +26,10 @@ htmlcov/ # build artifact from scripts/build-skill.sh /dist/ +# Go MCP bundle build outputs - source of truth for vendored/ stays under +# skills/last30days/scripts/; build/ holds cross-compiled binaries + .mcpb files. +/mcp/vendored/ +/mcp/build/ + # Internal planning docs (ce:plan output) — keep local, don't publish docs/plans/ diff --git a/mcp/.gitignore b/mcp/.gitignore new file mode 100644 index 0000000..3852985 --- /dev/null +++ b/mcp/.gitignore @@ -0,0 +1,9 @@ +# Mirror of skills/last30days/scripts/, populated by scripts/sync-engine.sh. +# Source of truth lives in the Python skill; never commit the mirror. +vendored/ + +# Local build output: cross-compiled binaries and packaged .mcpb files. +build/ +# Anchor to the mcp/ root so the cmd/last30days-pp-mcp/ package directory +# is not also excluded (subdirs with the same name would otherwise match). +/last30days-pp-mcp diff --git a/mcp/README.md b/mcp/README.md new file mode 100644 index 0000000..72a5f48 --- /dev/null +++ b/mcp/README.md @@ -0,0 +1,36 @@ +# last30days-pp-mcp + +Go MCP server that wraps the last30days Python engine for Claude Desktop. Packaged as a `.mcpb` bundle (drag-drop install into Claude Desktop). + +The MCP server exposes a single `research` tool that mirrors the `/last30days ` slash command available in Claude Code. At runtime the binary extracts the vendored Python engine into a per-user cache and shells out to `python3` to produce the synthesis input Claude renders. + +## Architecture + +- `cmd/last30days-pp-mcp/` - server entry point +- `internal/engine/` - `embed.FS` of the Python engine + cache extractor + subprocess wrapper +- `internal/tools/` - MCP tool handlers (currently `research`) +- `vendored/` - mirror of `skills/last30days/scripts/`, generated by `scripts/sync-engine.sh` (gitignored) +- `manifest.json` - MCPB v0.3 manifest consumed by Claude Desktop and `printing-press bundle` + +## Local build + +```bash +# Mirror the Python engine into vendored/. +bash scripts/sync-engine.sh + +# Build for the current host. +go build -ldflags "-X main.Version=dev" -o build/last30days-pp-mcp ./cmd/last30days-pp-mcp + +# Package as a .mcpb (requires the printing-press binary on PATH). +printing-press bundle . --skip-build --binary build/last30days-pp-mcp +``` + +The output `.mcpb` lands at `build/last30days-pp-mcp--.mcpb`. Drag it into Claude Desktop's Extensions panel to install. + +## Runtime requirements + +End users need Python 3.12+ on PATH. The bundle ships the engine source but relies on the host interpreter. + +## Versioning + +The MCPB `manifest.json` version is hand-bumped in the same PR that ships engine changes worth releasing. Release CI stamps the Go binary's `main.Version` from the tag. diff --git a/mcp/cmd/last30days-pp-mcp/main.go b/mcp/cmd/last30days-pp-mcp/main.go new file mode 100644 index 0000000..f6158cd --- /dev/null +++ b/mcp/cmd/last30days-pp-mcp/main.go @@ -0,0 +1,11 @@ +// Package main is the entry point for the last30days MCP server bundled +// as a .mcpb for Claude Desktop. See mcp/README.md for build instructions. +package main + +// Version is stamped at build time via -ldflags "-X main.Version=". +// Defaults to "dev" for local builds. +var Version = "dev" + +func main() { + // Wired in U3. +} diff --git a/mcp/go.mod b/mcp/go.mod new file mode 100644 index 0000000..dd5b944 --- /dev/null +++ b/mcp/go.mod @@ -0,0 +1,3 @@ +module github.com/mvanhorn/last30days-skill/mcp + +go 1.22 diff --git a/mcp/scripts/sync-engine.sh b/mcp/scripts/sync-engine.sh new file mode 100755 index 0000000..d0b51d6 --- /dev/null +++ b/mcp/scripts/sync-engine.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash +# Mirrors skills/last30days/scripts/{last30days.py,lib/} into mcp/vendored/ +# so the Go binary's embed.FS captures the engine at build time. +# +# Source of truth: skills/last30days/scripts/. Never edit mcp/vendored/ directly. +# Run before `go build` locally and in CI before `printing-press bundle`. + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +MCP_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)" +REPO_ROOT="$(cd "${MCP_DIR}/.." && pwd)" +ENGINE_SRC="${REPO_ROOT}/skills/last30days/scripts" +VENDORED="${MCP_DIR}/vendored" + +if [ ! -f "${ENGINE_SRC}/last30days.py" ]; then + echo "sync-engine: ${ENGINE_SRC}/last30days.py not found" >&2 + exit 1 +fi + +rm -rf "${VENDORED}" +mkdir -p "${VENDORED}" + +# Copy the entry script and the lib/ tree (modules + lib/vendor/). +cp "${ENGINE_SRC}/last30days.py" "${VENDORED}/last30days.py" +cp -R "${ENGINE_SRC}/lib" "${VENDORED}/lib" + +# Strip caches so the embed.FS stays deterministic. +find "${VENDORED}" -type d -name "__pycache__" -prune -exec rm -rf {} + +find "${VENDORED}" -type f -name "*.pyc" -delete + +echo "sync-engine: vendored engine at ${VENDORED}"