Design Specification: Go-Based Model Context Protocol (MCP) Server for GitHub Backup & Observatory System
Document Version: 1.0.0
Status: DRAFT / PROPOSED
Author: Antigravity AI Pair Programmer & MishraShardendu22
Target Clients: Google Antigravity, Claude Desktop, Claude Code, Cursor, Windsurf
Implementation Language: Go 1.25+
1. Executive Summary & Objectives
The GitHub Backup Automation & Observatory System currently features a rich ecosystem of tools distributed across its Go Backend API, Python AI Observatory, and Go Backup Worker:
- Real-time backup telemetry and historical analytics snapshots.
- Repository tracking, size trends, and commit verification.
- Detailed execution logs with multi-level filtering.
- Hybrid vector search (
pgvector+ PostgreSQL Full-Text Search). - Incident fix registration and tracking (
backup_fixes). - Archive management: git bundles and legacy tarballs.
Currently, these tools are consumed either via the Next.js Web Dashboard or internally by the LangChain agent in the Python Observatory.
Core Objective
This design document specifies the architecture, tool schemas, transport mechanisms, and integration patterns for building a dedicated, high-performance Go MCP Server (github-backup-mcp).
By exposing these capabilities via the open Model Context Protocol (MCP), external AI coding agents—most notably Google Antigravity and Anthropic Claude (Claude Desktop & Claude Code)—can directly connect to the backup infrastructure to:
- Inspect & Observe: Monitor real-time backup health, review run histories, and inspect archive growth without opening a browser.
- Diagnose & Correlate: Automatically triage clone failures, rate-limit lockouts, or network timeouts by aggregating run results, execution logs, and semantic past-incident memory in a single reasoning step.
- Fix & Remediate: Propose, apply, and permanently record incident fixes (
backup_fixes) with linked commit hashes and affected run IDs. - Operate & Restore: Verify archive checksums, inspect files within compressed
.tar.gzsnapshots without full extraction, and trigger on-demand backup runs for specific repositories.
2. Why Go for the MCP Server?
While MCP servers can be written in TypeScript or Python, building this server in Go provides decisive engineering advantages:
| Dimension | Go Implementation | Python / Node Alternatives |
|---|---|---|
| Binary Footprint | Single static executable (~15MB), zero external runtime dependencies | Requires Python 3.12 + venv or Node.js + node_modules |
| Startup Latency | Instantaneous cold start (<15ms) via stdio pipe | 300ms - 1500ms interpreter initialization overhead |
| Memory Footprint | ~12MB RSS idle / ~25MB peak | 80MB - 200MB RSS idle |
| Codebase Synergy | Direct reuse of existing Go structs (models.BackupRun, models.BackupFix) and pgxpool queries | Requires duplicate data contract definitions and serialization logic |
| Concurrency Model | Native lightweight goroutines for async log tailing, health checks, and concurrent tool calls | Event-loop / thread-pool management complexity |
| Transport Versatility | Effortlessly supports both stdio (local IDE pipe) and SSE/HTTP (remote cloud daemon) in a single binary | Frequently requires separate framework stacks |
3. System Architecture & Component Interactions
graph TD
subgraph AI Client Layer
Claude[Claude Desktop / Claude Code]
Antigravity[Google Antigravity IDE / Agent]
Cursor[Cursor / Other MCP Clients]
end
subgraph MCP Transport Boundary
StdioTransport[Stdio Transport - Local Process Pipe]
SSETransport[SSE / HTTP Transport - Port 8090]
end
Claude -->|JSON-RPC 2.0 via Stdio| StdioTransport
Antigravity -->|JSON-RPC 2.0 via Stdio or SSE| StdioTransport
Cursor -->|JSON-RPC 2.0| StdioTransport
subgraph Go MCP Server Subsystem [github-backup-mcp]
Router[MCP Dispatcher & Protocol Engine - mark3labs/mcp-go]
Sanitizer[Security Filter & Secret Redactor]
subgraph Handlers
ObsHandler[Observability & Telemetry Tools]
DiagHandler[Diagnostic & Failure Triaging Tools]
ArchiveHandler[Archive Inspection & Integrity Tools]
FixHandler[Remediation & Fix Recording Tools]
SearchHandler[Hybrid Search & Knowledge Tools]
end
Router --> Sanitizer
Sanitizer --> ObsHandler
Sanitizer --> DiagHandler
Sanitizer --> ArchiveHandler
Sanitizer --> FixHandler
Sanitizer --> SearchHandler
end
StdioTransport --> Router
SSETransport --> Router
subgraph Backend Infrastructure
NeonDB[(Neon PostgreSQL 16 + pgvector)]
GoBackend[Go Backend REST API :8080]
PythonObs[Python AI Observatory :8000]
LocalArchives[Local Archive Storage _Repos/ & Backups]
end
ObsHandler -->|Direct pgxpool Query| NeonDB
DiagHandler -->|Direct pgxpool Query| NeonDB
ArchiveHandler -->|Direct File I/O & SHA-256| LocalArchives
FixHandler -->|SQL Insert / Update| NeonDB
SearchHandler -->|HTTP POST /search| PythonObs
ObsHandler -.->|HTTP Fallback| GoBackend
Architectural Highlights:
- Dual-Mode Data Access:
- Direct PostgreSQL Access (
pgxpool): WhenDATABASE_URLis available, the MCP server queries Neon PostgreSQL directly with sub-10ms latency. - HTTP Client Fallback: When running in isolated or remote environments, the server falls back to calling the Go Backend REST API (
http://localhost:8080or Render URL). Every fallback call sendsINTERNAL_SECRETasX-Internal-Secret(required by the API whenAPI_REQUIRE_AUTH=true), and a non-2xx answer is a tool error.
- Direct PostgreSQL Access (
- Secret Redaction Layer: A tool handler middleware passes every tool result, errors included, through the sanitizer (see 8.3), for every transport.
- Transports (
--transportorMCP_TRANSPORT):stdio: Default mode when spawned directly by Antigravity, Claude Desktop or Claude Code. Logs go to stderr only.http: Streamable HTTP at/mcponMCP_PORTor--port(default 8090; it never readsPORT/SERVER_PORT, which belong to the API), stateless (no session ID, nothing kept in memory between requests), with a publicGET /health. Use this for containers, several replicas and remote access.sse: The legacy SSE transport (/sse+/message). Its sessions live in one process's memory, so it only works with a single instance; preferhttp.- The Vercel entry point (
api/index.go) serves Streamable HTTP at/mcp(with/rpcandPOST /as aliases) and still serves/sseand/messagefor existing clients.
- Logging: slog JSON through
src/internal/logging(servicegithub-backup-mcp,LOG_LEVEL), always on stderr, with one record per MCP request and per tool call (names, durations and outcomes, never arguments).
4. MCP Tools Specification
The server exposes 15 tools in 5 groups. The tool definitions in src/backend/github-backup-mcp/tools/*.go are the source of truth for names and parameters:
github-backup-mcp
├── 1. Observability & Telemetry (Read-only)
│ ├── get_system_health
│ ├── get_dashboard_summary
│ ├── get_backup_metrics
│ └── list_tracked_repositories
├── 2. Diagnostics & Failure Triaging (Read-only)
│ ├── list_backup_runs
│ ├── get_backup_run_details
│ ├── get_execution_logs
│ └── diagnose_backup_failure
├── 3. Semantic Search & Memory (Read-only)
│ └── search_backup_knowledge
├── 4. Archive & File Inspection (Read-only / Safe Sandbox)
│ ├── verify_archive_integrity
│ ├── inspect_archive_contents
│ └── extract_backup_file
└── 5. Remediation & Operations (State-Modifying / HITL-Gated)
├── trigger_backup_run
├── record_backup_fix
└── send_backup_report
Category 1: Observability & Telemetry
1. get_system_health
- Description: Verifies connectivity to Neon PostgreSQL, checks backup directory disk availability, reports active worker status, and returns API health indicators.
- Parameters: None.
- Output:
{"database": {"status": "connected", "latency_ms": 14, "pool_open_connections": 3},"storage": {"archive_path": "_Repos", "total_disk_bytes": 107374182400, "available_disk_bytes": 68719476736},"backend_api": {"status": "reachable", "url": "http://localhost:8080"},"status": "HEALTHY"}
2. get_dashboard_summary
- Description: Retrieves high-level aggregated metrics across all backup history: total runs, overall success rate, distinct repos, total archive size, and latest analytics snapshot.
- Parameters: None.
- Output:
{"total_runs": 150,"total_repos": 1250,"total_successful": 1180,"total_failed": 45,"success_rate": "94.40%","last_run_status": "completed","last_run_at": "2026-09-18T10:45:30Z","total_size_bytes": 536870912,"largest_repository": "MishraShardendu22/large-repo"}
3. get_backup_metrics
- Description: Returns historical time-series metrics over a specified number of days, including daily run counts, success rates, and average duration.
- Parameters:
days(integer, optional, default: 30): Days of history to analyze.
- Output:
{"period_days": 30,"average_run_duration_ms": 870000,"aggregated_success_rate": 0.944,"history": [{"date": "2026-09-17", "runs": 1, "successful": 42, "failed": 2, "avg_duration_ms": 850000}]}
4. list_tracked_repositories
- Description: Lists tracked GitHub repositories, indicating privacy status, last backup timestamp, archive size, and repository URL.
- Parameters:
page(integer, optional, default: 1)limit(integer, optional, default: 50, max: 200)
Category 2: Diagnostics & Failure Triaging
5. list_backup_runs
- Description: Retrieves paginated backup runs, optionally filtered by status (
completed,failed,running). - Parameters:
page(integer, optional, default: 1)limit(integer, optional, default: 20, max: 100)status(string, optional):completed,failedorrunning.
6. get_backup_run_details
- Description: Retrieves complete execution details of a specific backup run, including all individual repository outcomes, durations, commit hashes, and error messages.
- Parameters:
run_id(integer, required): The unique ID of the backup run.
7. get_execution_logs
- Description: Queries stored operational logs with filtering by severity level, backup run ID, or target repository.
- Parameters:
run_id(integer, optional): Filter by backup run.repository(string, optional): Filter by repository full name (e.g.owner/repo).level(string, optional): One ofINFO,WARN,ERROR,DEBUG(case-insensitive); omit it for every level.limit(integer, optional, default: 50, max: 200).
8. diagnose_backup_failure
- Description: Compound Diagnostic Tool. Given a
run_id(or defaulting to the latest failed run), this tool collects the failed repositories with their errors and error logs, attaches the three newest recorded fixes frombackup_fixes, and returns one diagnostic packet for the LLM. For similar past incidents, follow up withsearch_backup_knowledge. - Parameters:
run_id(integer, optional): Run ID to diagnose. If omitted, finds the most recent failed run.
- Output:
{"run_id": 149,"failed_at": "2026-09-17T02:15:00Z","failed_repos_count": 1,"failures": [{"repository": "MishraShardendu22/legacy-tool","error_message": "fatal: early EOF / index-pack failed","error_logs": ["2026-09-17T02:14:55Z [ERROR] fatal: read error: Connection reset by peer","2026-09-17T02:15:00Z [ERROR] Clone failed after 3 retries"],"last_successful_commit": "e5b8d4f","similar_past_incidents": [{"run_id": 112,"resolution": "Increased git http.postBuffer to 524288000 in backup worker config"}]}],"recommended_actions": ["Check network buffer limits in src/services/github-backup-worker/config","Verify GitHub API rate-limit headers"]}
Category 3: Semantic Search & AI Knowledge Base
9. search_backup_knowledge
- Description: Performs hybrid search (combining dense vector embeddings in
pgvectorand PostgreSQL full-text search with Reciprocal Rank Fusion) across historical logs, errors, past incident fixes, and chat investigations. - Parameters:
query(string, required): Search query (e.g. "clone timeout RPC failed", "SSH authentication failure").limit(integer, optional, default: 5, max: 20).
Category 4: Archive & File Inspection
Archives live in BACKUP_ARCHIVE_PATH (set it to the worker's _Repos directory) and are resolved by exact file name only, never by substring or outside that root. repo_name must be owner/name (GitHub naming rules) or, for legacy archives, a bare repository name. Candidates, in order:
worker_repo_state.archive_keyfor the repository (when the worker's table from migration 000008 exists; keys that leave the root are ignored),<owner>__<repo>.bundle, then<owner>__<repo>.tar.gz(lower case),- the legacy
<repo>.tar.gz(as given, then case-insensitively when exactly one file matches).
Files are opened through os.Root, so neither .. nor a symbolic link leaves the root. .bundle archives need git on the MCP host (a clear tool error otherwise); each git command has a 3 minute timeout, bundles over 2 GiB are not unpacked, and scratch clones are always removed.
10. verify_archive_integrity
- Description: Computes the archive's SHA-256 and compares it with
worker_repo_state.archive_sha256when recorded (a mismatch is an error). Git bundles are checked withgit bundle verify(header, refs, prerequisites) and unpacked into a scratch repository withgit bundle unbundle, which checks every object (verifyalone accepts a corrupted or truncated pack). tar.gz archives have every tar header read. - Parameters:
repo_name(string, required):owner/name, or a bare name for legacy archives.
- Output (bundle):
{"repository": "octo/repo","archive": "octo__repo.bundle","format": "bundle","resolved_by": "worker_repo_state","archive_size_bytes": 1048576,"sha256_checksum": "3a7b9c...","expected_sha256": "3a7b9c...","refs": ["refs/heads/main", "HEAD"],"pack_verified": true,"integrity_status": "VALID_UNCOMPROMISED"}
11. inspect_archive_contents
- Description: Lists files without extracting the archive: tar headers for tar.gz, the tree of the bundle's default branch (its HEAD, else
main,masteror the first branch) for bundles, read from a bounded scratch clone. - Parameters:
repo_name(string, required)prefix(string, optional): Filter paths starting with this prefix (e.g..github/,src/).limit(integer, optional, default: 50, max 200).
12. extract_backup_file
- Description: Returns the content of one file (at most 10 MB) from the tar.gz archive or from the bundle's default branch (
git cat-file). - Parameters:
repo_name(string, required)file_path(string, required): Relative path within the archive (e.g.README.md,package.json);.., absolute paths, backslashes and NUL are rejected.
Category 5: Remediation & Operational Actions
13. trigger_backup_run
- Description: Queues an on-demand backup run by inserting a row into
backup_run_requests(created by the worker's migration 000008) withrequested_by = 'mcp'. The worker claims pending requests at its next scheduled run; the tool does not start the worker. If a pending request for the same target exists, that request is returned withalready_queued: trueinstead of queuing a duplicate. NeedsDATABASE_URL; without it, or while the table does not exist, the tool returns an error. - Parameters:
target_repo(string, optional):owner/name, validated and stored in lower case; omit it for all tracked repositories.reason(string, optional): Stored with the request (at most 500 characters).
- Output:
{"request_id": 12,"status": "pending","target_repo": "octo/repo","requested_by": "mcp","reason": "config changed","created_at": "2026-09-24T07:00:00Z","already_queued": false,"message": "Backup run request queued; the worker picks it up on its next run."}
- Safety Guardrail: Marked as an operational action. Antigravity and Claude will surface user confirmation when invoked.
14. record_backup_fix
- Description: Registers a permanent remediation resolution in the
backup_fixesdatabase table, optionally linked to one affected run. This preserves institutional knowledge so the AI agent and future runs know how an issue was resolved. - Parameters:
title(string, required): Concise description of the fix (e.g. "Increased git clone postBuffer to 500MB").description(string, required): Detailed explanation of the root cause and applied fix.author(string, required): Author identifier (e.g. "Antigravity Agent", "Claude Code", or human user).commit_hash(string, optional): Git commit hash containing the code fix.affected_run_id(integer, optional): The backup run this fix addresses.
- Output: the stored fix with
"status": "RECORDED_IN_DATABASE".
15. send_backup_report
- Description: POSTs
{"subject", "content_markdown"}withX-Internal-Secretto<OBSERVATORY_URL>/reports/send. It succeeds only when the Observatory answers 2xx; a connection failure, any other status or an emptyOBSERVATORY_URLis a tool error naming the status and message. Note: the Observatory's current/reports/sendexpects{"report_id"}and a user JWT, so this tool reports that refusal until the Observatory accepts MCP requests. - Parameters:
subject(string, required)content_markdown(string, required)
5. MCP Resources & Prompts Specifications
In addition to tools, the MCP protocol supports Resources (data URIs readable by agents) and Prompts (pre-composed agent workflows):
5.1 MCP Resources
| URI Pattern | Name | MIME Type | Description |
|---|---|---|---|
backup://system/health | System Health State | application/json | Live connectivity, database latency, and storage stats |
backup://runs/latest | Latest Backup Run Summary | application/json | Summary of the most recent backup execution |
Repository and analytics resources are not implemented; use the list_tracked_repositories and get_backup_metrics tools.
5.2 MCP Prompts
Prompt 1: diagnose-latest-failure
- Description: Automatically loads the latest failed run, queries execution logs, searches semantic past resolutions, and guides the LLM to provide a step-by-step remediation plan.
- Arguments: None.
- Template Context: Pre-fetches
backup://runs/latestand injects system diagnostic instructions.
Prompt 2: audit-backup-health
- Description: Performs a complete health and storage audit of the backups: system health, tracked repositories, recent runs and archive storage.
A disaster-recovery prompt is not implemented; verify_archive_integrity, inspect_archive_contents and extract_backup_file cover a test restore.
6. Go Server Technical Architecture & Package Layout
6.1 Package Layout
The MCP server is part of the root Go module (github.com/MishraShardendu22/github-backup), not a separate module:
src/backend/github-backup-mcp/
├── cmd/github-backup-mcp/main.go # Entrypoint (--transport stdio|http|sse, --port)
├── internal/app/ # run.go (startup), http.go (Streamable HTTP), sse.go (legacy SSE)
├── config/config.go # Settings (DATABASE_URL, BACKEND_URL, OBSERVATORY_URL, MCP_AUTH_TOKEN, ...)
├── server/server.go # mcp-go server, tool/resource registration, result sanitizing middleware
├── transport/ # Bearer auth and the stateless Streamable HTTP handler
├── sanitizer/sanitizer.go # Token and secret redaction
├── tools/ # observability, diagnostics, search, archives (+ archive_bundle, archive_resolve), remediation, backend API fallback
├── resources/resources.go # backup:// resources and the two prompts
├── storage/db.go # pgxpool connection and queries
├── Dockerfile
└── README.md
The Vercel entry point is api/index.go at the repository root.
6.2 Recommended Go Libraries
- MCP SDK:
github.com/mark3labs/mcp-go- Most robust, standard-compliant Go implementation of Model Context Protocol (v2024-11-05).
- Built-in support for
server.NewStdioServer,server.NewStreamableHTTPServer(used in stateless mode) andserver.NewSSEServer. - Automatic JSON Schema generation from Go struct parameters via
mcp.NewTool.
- Database Driver:
github.com/jackc/pgx/v5/pgxpool- High-performance, native PostgreSQL driver with connection pooling.
- Matches the Go backend stack in
src/backend/github-backup-api/.
- Structured Logging:
log/slogthroughsrc/internal/logging(shared with the API).- Logs write strictly to
stderrso they do not corrupt the JSON-RPC pipe onstdoutinstdiomode.
- Logs write strictly to
- CLI Flags:
github.com/spf13/pflagor standardflagpackage.
6.3 Core Code Implementation Patterns
A. Entry Point & Transports (src/backend/github-backup-mcp/internal/app/run.go)
The entry point parses --transport (stdio, http or sse; anything else is an error) and --port, builds the logger with server.NewLogger (stderr) and the optional pgxpool pool, and calls server.NewServer, which registers the tools, resources and prompts, installs the result sanitizer and registers the configured secrets with it. For http it serves transport.NewStreamableHTTP (stateless Streamable HTTP, standalone GET stream disabled) at /mcp behind transport.RequireBearerToken, plus a public GET /health, and shuts down gracefully on SIGINT/SIGTERM. The HTTP transports refuse to start without MCP_AUTH_TOKEN.
B. Tool Registration Example: Compound Diagnostic Tool (tools/diagnostics.go)
package tools
import (
"context"
"fmt"
"log/slog"
"github.com/jackc/pgx/v5/pgxpool"
"github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server"
)
func registerDiagnosticTools(s *server.MCPServer, db *pgxpool.Pool, logger *slog.Logger) {
diagnoseTool := mcp.NewTool("diagnose_backup_failure",
mcp.WithDescription("Compound diagnostic tool that gathers failed repos, specific error logs, stack traces, and similar past incidents for a backup run."),
mcp.WithNumber("run_id",
mcp.Description("Optional backup run ID to diagnose. If omitted, triages the most recent failed run."),
),
)
s.AddTool(diagnoseTool, func(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
var runID int64
if val, ok := req.Params.Arguments["run_id"]; ok && val != nil {
if num, ok := val.(float64); ok {
runID = int64(num)
}
}
// If no runID provided, find latest failed run
if runID == 0 {
err := db.QueryRow(ctx, "SELECT id FROM backup_runs WHERE status = 'failed' ORDER BY id DESC LIMIT 1").Scan(&runID)
if err != nil {
return mcp.NewToolResultError("No failed backup runs found in history"), nil
}
}
// 1. Fetch failing repos
rows, err := db.Query(ctx,
"SELECT repo_full_name, COALESCE(error_message, 'Unknown error') FROM backup_results WHERE run_id = $1 AND status = 'failed'",
runID)
if err != nil {
return mcp.NewToolResultError(fmt.Sprintf("Failed to query run results: %v", err)), nil
}
defer rows.Close()
type FailureInfo struct {
Repo string `json:"repository"`
Error string `json:"error"`
Logs []string `json:"logs"`
}
var failures []FailureInfo
for rows.Next() {
var f FailureInfo
if err := rows.Scan(&f.Repo, &f.Error); err == nil {
failures = append(failures, f)
}
}
// 2. Fetch error logs for this run
logRows, err := db.Query(ctx,
"SELECT message FROM execution_logs WHERE run_id = $1 AND level = 'ERROR' ORDER BY id DESC LIMIT 10",
runID)
if err == nil {
defer logRows.Close()
for logRows.Next() {
var msg string
if err := logRows.Scan(&msg); err == nil && len(failures) > 0 {
failures[0].Logs = append(failures[0].Logs, msg)
}
}
}
resultJSON := fmt.Sprintf(`{"run_id": %d, "failures": %v}`, runID, failures)
return mcp.NewToolResultText(resultJSON), nil
})
}
7. Client Configuration & Integration Guide
7.1 Integration with Google Antigravity
Antigravity natively discovers and manages MCP servers via its global configuration file ~/.gemini/config/mcp_config.json or within workspace plugins.
Configuration (~/.gemini/config/mcp_config.json):
{
"mcpServers": {
"github-backup": {
"command": "<path-to-repo>/src/backend/github-backup-mcp/bin/github-backup-mcp",
"args": ["--transport", "stdio"],
"env": {
"DATABASE_URL": "postgresql://DB_USER:DB_PASSWORD@DB_HOST/DB_NAME?sslmode=require",
"BACKEND_URL": "http://localhost:8080",
"BACKUP_ARCHIVE_PATH": "<path-to-repo>/src/services/github-backup-worker/_Repos"
}
}
}
}
Note: Once configured, Antigravity instantly populates all 15 tools in its agent context, visible via the UI under Additional Options (...) > MCP Servers.
7.2 Integration with Claude Desktop
Claude Desktop starts local MCP servers over standard input/output (stdio); no token is needed because only the process that started the server can reach it.
Configuration:
- Linux:
~/.config/Claude/claude_desktop_config.json - macOS:
~/Library/Application Support/Claude/claude_desktop_config.json
{
"mcpServers": {
"github-backup": {
"command": "<path-to-repo>/src/backend/github-backup-mcp/bin/github-backup-mcp",
"args": ["--transport", "stdio"],
"env": {
"DATABASE_URL": "postgresql://DB_USER:DB_PASSWORD@DB_HOST/DB_NAME?sslmode=require",
"BACKEND_URL": "http://localhost:8080"
}
}
}
}
To use a remote server (the Vercel deployment or --transport http), bridge it through the mcp-remote package, which forwards stdio to a Streamable HTTP endpoint and adds the header. This follows mcp-remote's documented --header usage; keeping the token in env avoids problems with spaces in arguments:
{
"mcpServers": {
"github-backup-remote": {
"command": "npx",
"args": ["-y", "mcp-remote", "https://<your-deployment>/mcp", "--header", "Authorization:${MCP_AUTHORIZATION}"],
"env": { "MCP_AUTHORIZATION": "Bearer <MCP_AUTH_TOKEN>" }
}
}
}
7.3 Integration with Claude Code (CLI)
Local server over stdio:
claude mcp add github-backup \
--env DATABASE_URL="postgresql://DB_USER:DB_PASSWORD@DB_HOST/DB_NAME?sslmode=require" \
-- /path/to/github-backup-mcp --transport stdio
Remote server over Streamable HTTP (the Vercel deployment's /mcp, or a local --transport http server):
claude mcp add --transport http github-backup https://<your-deployment>/mcp \
--header "Authorization: Bearer $MCP_AUTH_TOKEN"
or in a project .mcp.json (Claude Code expands ${VAR} from the environment):
{
"mcpServers": {
"github-backup": {
"type": "http",
"url": "http://localhost:8090/mcp",
"headers": { "Authorization": "Bearer ${MCP_AUTH_TOKEN}" }
}
}
}
Verify connection:
claude mcp list
7.4 Integration with Cursor
In Cursor, configure MCP tools under .cursor/mcp.json in the workspace root:
{
"mcpServers": {
"github-backup": {
"command": "./src/backend/github-backup-mcp/bin/github-backup-mcp",
"args": ["--transport", "stdio"],
"env": {
"DATABASE_URL": "postgresql://...neon.tech/neondb?sslmode=require"
}
}
}
}
7.5 Remote Docker / HTTP Deployment (Multi-Agent & Team Access)
For teams or agents running in cloud environments, the server can run as a container serving Streamable HTTP at /mcp on port 8090. The transport is stateless, so it can run as several replicas behind a load balancer. The image needs git for the .bundle archive tools and BACKUP_ARCHIVE_PATH pointing at the archives:
The image (src/backend/github-backup-mcp/Dockerfile) starts the Streamable HTTP transport (--transport http) and is already part of docker-compose.yml:
services:
github-backup-mcp:
build:
context: .
dockerfile: src/backend/github-backup-mcp/Dockerfile
ports:
- "8090:8090"
environment:
DATABASE_URL: *database-url
BACKEND_URL: http://github-backup-api:8080
OBSERVATORY_URL: http://github-backup-observatory:8000
INTERNAL_SECRET: ${INTERNAL_SECRET:?...}
MCP_AUTH_TOKEN: ${MCP_AUTH_TOKEN:?...} # required; the server refuses to start without it
MCP_PORT: "8090"
# The worker's git sink keeps its archives in _Repos on the shared volume.
BACKUP_ARCHIVE_PATH: /data/archives/_Repos
volumes:
- backups_data:/data/archives:ro
GET /health is served without the token for container health checks; every other route requires Authorization: Bearer $MCP_AUTH_TOKEN.
Clients use http://<host>:8090/mcp with Authorization: Bearer <MCP_AUTH_TOKEN> (see 7.2 and 7.3); GET /health is public. Requests that arrive over a loopback connection must name a localhost Host (DNS rebinding protection), so a reverse proxy on the same host must rewrite Host. The legacy --transport sse (/sse, /message) still works for a single instance only, because its sessions live in memory.
Client configuration for the HTTP transport:
{
"mcpServers": {
"github-backup-remote": {
"type": "http",
"url": "http://localhost:8090/mcp",
"headers": { "Authorization": "Bearer <MCP_AUTH_TOKEN>" }
}
}
}
8. Security, Guardrails & Human-In-The-Loop (HITL)
8.1 Read-Only vs State-Modifying Segregation
Tools are explicitly classified into safety tiers:
- Safe / Read-Only (Unconditional):
get_system_health,get_dashboard_summary,get_backup_metrics,list_tracked_repositories,list_backup_runs,get_backup_run_details,get_execution_logs,diagnose_backup_failure,search_backup_knowledge,verify_archive_integrity,inspect_archive_contents. - Operational / Mutation (Requires Confirmation):
trigger_backup_run: Queues abackup_run_requestsrow; the worker runs the backup (clones and uploads) on its next run.record_backup_fix: Writes permanent data to PostgreSQL.send_backup_report: Dispatches emails via external SMTP.
8.2 HTTP Transport Authentication
Every network transport (the local --transport http and --transport sse servers and the serverless handler in api/index.go) requires Authorization: Bearer $MCP_AUTH_TOKEN on every request except GET /health.
- If
MCP_AUTH_TOKENis not set, the local HTTP servers refuse to start and the serverless handler answers MCP requests with503, so the tools are never exposed without a token. - The token is compared in constant time (
src/backend/github-backup-mcp/transport/auth.go). - No CORS headers are sent. MCP clients are not browsers, and a wildcard origin would let any web page call the tools.
- Generate a token with
openssl rand -hex 32and pass it to MCP clients as a request header. - Stdio mode is unaffected: it is only reachable by the local process that started it.
8.3 Sensitive Secret Redaction
A tool handler middleware passes the text of every tool result, errors included, through sanitizer.Sanitize, which redacts:
- GitHub tokens:
ghp_(classic PAT),gho_(OAuth),ghu_(App user-to-server),ghs_(App installation),ghr_(refresh) with 36 or more characters, and fine-grainedgithub_pat_tokens. - Passwords in PostgreSQL URLs, including driver suffixes such as
postgresql+asyncpg://. - OpenRouter API keys (
sk-or-v1-[a-f0-9]{64}) andBearertokens. - The configured secret values, raw and JSON-escaped:
INTERNAL_SECRET,MCP_AUTH_TOKEN, theDATABASE_URLpassword, andSMTP_PASSWORD/JWT_SECRETwhen they are set in the MCP environment (values under 8 characters are skipped).
8.4 Resource Bounds & Tar Bomb Protection
- Single-file extraction (
extract_backup_file) is restricted to files $\le 10\text{MB}$ to prevent memory exhaustion. - Path traversal protection enforces that extracted paths cannot escape the sandbox boundary (rejects
../sequences). - Archives are resolved by exact name inside
BACKUP_ARCHIVE_PATHthroughos.Root(see Category 4), sorepo_namecannot select files elsewhere on the host. - Bundle operations run git through
src/internal/gitexec(argv only, no prompts, process group killed on timeout) with a 3 minute timeout per command, skip unpacking bundles over 2 GiB and remove their scratch clones. - Database queries use strict context timeouts (15s limit) to avoid hanging client requests.
9. Phased Implementation Roadmap
┌───────────────────────────────────────────────────────────────────────────────┐
│ MCP SERVER IMPLEMENTATION PHASES │
├───────────────────────────────────────────────────────────────────────────────┤
│ Phase 1: Foundation & Stdio Transport │
│ • Initialize Go module `mcp-server` │
│ • Setup `mark3labs/mcp-go` and Stdio lifecycle │
│ • Establish pgxpool database connection & health check tool │
│ │
│ Phase 2: Observability & Diagnostics Tools │
│ • Port dashboard statistics, run history, and execution log queries │
│ • Implement compound `diagnose_backup_failure` triage tool │
│ • Connect `search_backup_knowledge` to Python Observatory / pgvector │
│ │
│ Phase 3: Archive Inspection & Fix Management │
│ • Implement tar header inspection and SHA-256 integrity verification │
│ • Implement `record_backup_fix` for logging permanent hotfixes │
│ • Add safe single-file extraction sandbox │
│ │
│ Phase 4: MCP Resources, Prompts & Client Wiring │
│ • Register `backup://` resources and diagnostic prompt templates │
│ • Generate client configs for Antigravity, Claude Desktop, and Claude Code │
│ • Add Makefile targets: `make mcp-build`, `make mcp-run` │
└───────────────────────────────────────────────────────────────────────────────┘
Next Steps & Action Items
- Review and approve the design document.
- Create feature branch:
MishraShardendu22/main/go-mcp-server. - Implement Phase 1 skeleton in
src/backend/github-backup-mcp/. - Register the MCP server in
~/.gemini/config/mcp_config.jsonto immediately test with Antigravity.