Dev Tool Config Sync MCP Server
by akurinnoy
README.md
# Dev Tool Config Sync MCP Server
An MCP server that syncs dev tool configurations to/from a storage backend with snapshot history and rollback. Tool-agnostic — declarative YAML profiles define what to sync for each tool (Claude Code, Gemini CLI, opencode, etc.).
Designed to run as a sidecar container in Eclipse Che workspaces, but works standalone.
## Quick start
```bash
npm install
npm run build
npm start
```
The server listens on port **8089** (configurable via `CONFIG_SYNC_PORT`) and exposes MCP tools over streamable HTTP at `/mcp`. Health endpoint at `/healthz`.
### Environment variables
| Variable | Default | Description |
|----------|---------|-------------|
| `CONFIG_SYNC_PORT` | `8089` | HTTP port |
| `CONFIG_SYNC_STORAGE_DIR` | `~/.config-sync-storage` | File-based storage directory |
| `CONFIG_SYNC_PROFILES_DIR` | `./profiles` | Directory containing tool profile YAML files |
| `CONFIG_SYNC_USER_ID` | `CHE_USER_ID` or `default` | User identifier for storage key namespacing |
| `FILE_ACCESS_MODE` | `local` | File access mode: `local` (direct filesystem) or `remote` (exec into workspace pods) |
| `POD_NAMESPACE` | auto-detected | Kubernetes namespace for pod discovery (auto-detected from `/var/run/secrets/kubernetes.io/serviceaccount/namespace`) |
| `TARGET_WORKSPACE` | none | Optional default workspace name for remote mode |
| `WORKSPACE_HOME_DIR` | `~` | Home directory path inside workspace pods |
## MCP tools
| Tool | Description |
|------|-------------|
| `sync_from_storage` | Pull latest config from storage backend (all tools or a specific tool) |
| `sync_to_storage` | Push current workspace config to storage backend (all tools or a specific tool) |
| `list_config_versions` | List available config snapshots/versions for a tool |
| `rollback_config` | Restore config from a previous version for a tool |
| `diff_config` | Show differences between local and stored config for a tool |
| `get_sync_status` | Check last sync time and pending changes (all tools or a specific tool) |
| `list_tools` | List all registered tool profiles and their sync status |
## Tool profiles
Each supported tool has a YAML profile in `profiles/` defining its sync rules:
```yaml
tool: claude-code
name: Claude Code
paths:
sync:
- "~/.claude/settings.json"
- "~/.claude/settings.local.json"
- "~/.claude/CLAUDE.md"
- "~/.claude/agents/**"
- "~/.claude/hooks/**"
- "~/.claude/plugins/**"
- "~/.claude.json"
skip:
- "~/.claude/plugins/cache/**"
sensitive:
- "**/*credentials*"
- "**/*token*"
- "**/*.key"
```
**Built-in profiles:** `claude-code`, `gemini-cli`, `opencode`. Add new tools by dropping a YAML file in the profiles directory — no code changes needed.
### Profile schema
| Field | Required | Description |
|-------|----------|-------------|
| `tool` | yes | Unique identifier (`^[a-z][a-z0-9-]*$`), used as storage key |
| `name` | yes | Human-readable display name |
| `paths.sync` | yes (≥1) | Glob patterns relative to `$HOME` to sync |
| `paths.skip` | no | Glob patterns to exclude from sync |
| `paths.sensitive` | no | Glob patterns for files that must never be synced (credentials, tokens) |
All paths must start with `~/`. Symlinks pointing outside `$HOME` are rejected. Max file size: 1 MB (configurable).
## Architecture
```
┌──────────────────────────────────────────────────────┐
│ Workspace Pod │
│ │
│ ┌──────────────┐ │
│ │ Claude Code │─MCP─┐ │
│ └──────────────┘ │ ┌───────────────────────┐ │
│ ┌──────────────┐ ├──│ Config Sync Server │ │
│ │ Gemini CLI │─MCP─┤ │ (sidecar, port 8089) │ │
│ └──────────────┘ │ │ │ │
│ ┌──────────────┐ │ │ profiles/ │ │
│ │ other tools │─MCP─┘ │ claude-code.yaml │ │
│ └──────────────┘ │ gemini-cli.yaml │ │
│ │ opencode.yaml │ │
│ └──────────┬────────────┘ │
│ │ │
└──────────────────────────────────────┼───────────────┘
│
┌─────────▼─────────┐
│ Storage Backend │
│ (file-based) │
└───────────────────┘
```
### Storage
Each push creates an immutable snapshot at `{storageDir}/{userId}/{tool}/{version}/`, containing a `manifest.json` (checksums, sizes, permissions) and the raw files. Snapshots are append-only. Storage key uses `userId` + `tool` — configs follow the user across workspaces, stored within the user namespace on the cluster.
**Conflict strategy:** Last-write-wins. Use `diff_config` to inspect drift before overwriting.
### What gets synced vs skipped
- **Sync**: user-edited configuration, preferences, custom extensions (agents, hooks, plugins)
- **Skip**: session state, history/transcripts, caches, analytics, anything regeneratable or workspace-local
## Docker
```bash
npm run build
docker build -t config-sync-mcp .
docker run -p 8089:8089 config-sync-mcp
```
Base image: `registry.access.redhat.com/ubi10/nodejs-24-minimal`
## Cluster deployment
Deploy the centralized config-sync-mcp server to a Kubernetes/OpenShift namespace where DevWorkspaces run.
### Prerequisites
- `oc` or `kubectl` configured for the target cluster
- Namespace where DevWorkspaces run (e.g., `kubeadmin-devspaces`)
### Apply manifests
```bash
oc project <namespace>
oc apply -f deploy/
```
### Connect Claude Code (external access via K8s API proxy)
Access the MCP server from outside the cluster without port-forwarding. The K8s API server authenticates requests using your kubeconfig credentials:
```bash
API_SERVER=$(kubectl config view --minify -o jsonpath='{.clusters[0].cluster.server}')
NAMESPACE=<your-namespace>
claude mcp add --transport http \
--header "Authorization: Bearer $(oc whoami -t)" \
config-sync "$API_SERVER/api/v1/namespaces/$NAMESPACE/services/config-sync-mcp:8089/proxy/mcp"
```
No ClusterRole or Route required — the API server handles authentication and proxies to the ClusterIP service. The caller needs `services/proxy` RBAC permission in the target namespace.
This creates:
- **ServiceAccount** `config-sync-mcp` — identity for the server pod
- **Role** — permissions to list pods, exec into workspace pods, and watch DevWorkspaces
- **RoleBinding** — binds the role to the service account
- **Deployment** — the server running in remote mode (`FILE_ACCESS_MODE=remote`)
- **Service** — exposes the MCP endpoint at `config-sync-mcp:8089`
### Verify
```bash
# Check pod is running
oc get pods -l app=config-sync-mcp
# Check health
oc exec deployment/config-sync-mcp -- curl -s http://localhost:8089/healthz
# Test MCP handshake
oc exec deployment/config-sync-mcp -- curl -s -X POST http://localhost:8089/mcp \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"test","version":"0.1"}},"id":1}'
```
## Development
```bash
npm install
npm run build # compile TypeScript + copy profiles to dist/
npm test # run all tests (vitest)
npm run test:watch # watch mode
```
### Project structure
```
src/
index.ts entrypoint
server.ts HTTP server + MCP transport
types.ts shared types and interfaces
tools/ MCP tool handlers (one per tool)
storage/ storage backend interface + file implementation
profiles/ YAML profile loader + glob resolver
sync/ sync engine + SHA-256 checksums
profiles/ built-in tool profile YAML files
tests/ unit + integration tests
```
## Next steps
- [ ] Auto-sync on workspace start/stop via lifecycle hooks
- [ ] Test concurrent workspace scenarios
- [ ] Admin-pushed baseline profiles
- [ ] Add opencode built-in profile
This server cannot be deployed
Maintenance
ActivityStale
ResponsivenessNo issues