Skip to content

AI Assistants (Claude Code & GitHub Copilot) & Model Context Protocol ​

This document explains the Claude Code / GitHub Copilot instructions and Model Context Protocol (MCP) server configuration included in this dotfiles repository. Both tools' shareable instructions/skills content is canonically sourced from the tools repository — this dotfiles repo only stores genuinely personal preferences (.claude/settings.json, .vscode/settings.json, .vscode/mcp.json) and wires the rest up via setup-ai-agent.sh at setup time.

The setup runs setup-ai-agent.sh through its remote helper: the script is downloaded first and only run once the download succeeded, never piped into a shell. It is fetched from https://raw.githubusercontent.com/this-is-tobi/tools/${TOOLS_REF:-main}: it follows the main branch unless you set TOOLS_REF to a tag or a commit SHA (TOOLS_REF=<ref> ./setup/setup-osx.sh -d), and the installer fetches its own content from the same ref. To run the installer by hand, use the tools shell function, which downloads a script of the tools repository and then runs it (see Shell Customization): tools setup-ai-agent.sh <flags>. The installer keeps what it replaces: an instruction file or CLAUDE.md whose content differs is first saved next to the original as <name>.bak.<timestamp>.

Claude Code ​

Overview ​

Claude Code reads CLAUDE.md (repo) and ~/.claude/CLAUDE.md (personal, always active — no opt-in setting needed, unlike Copilot). Only tool-agnostic, always-relevant guidance goes in the personal file — the same "general" instructions used for Copilot, just without the applyTo frontmatter (a Copilot-only scoping mechanism).

Per-path scoping: Claude Code has its own mechanism for guidance that only matters for some files: .claude/rules/*.md (repo) or ~/.claude/rules/*.md (personal) with a paths: frontmatter. A rule loads when Claude works with a matching file; a rule without paths: loads at the start of every session.

markdown
---
paths:
  - "src/api/**/*.ts"
---

# API Development Rules

- All API endpoints must include input validation

Sharing one file with Copilot (AGENTS.md): when a repository is used with both Claude Code and Copilot, keep the shared instructions in AGENTS.md (Copilot reads it too) and give Claude Code a CLAUDE.md that imports it:

markdown
@AGENTS.md

## Claude Code

Claude-specific instructions go here.

Claude Code reads an AGENTS.md by itself (v2.1.277 and later) only when there is no CLAUDE.md in the working directory or above it, so the @AGENTS.md import is needed as soon as a CLAUDE.md exists.

Size: keep each CLAUDE.md under about 200 lines. Longer files use more context and reduce adherence; imports help to organize a file but do not reduce its context cost. Move guidance that only matters for part of the codebase into path-scoped rules.

Installation ​

Automatic (during setup): ./setup/setup-osx.sh -d (or setup-debian.sh -d) runs, through the remote helper described above:

sh
setup-ai-agent.sh -t claude -g -P

This creates ~/.claude/general-instructions.md, adds an @general-instructions.md import line to ~/.claude/CLAUDE.md (once), installs the skills code-review, repository-audit, commit-message, pull-request and systematic-debugging in ~/.claude/skills/, and installs the Claude Code plugins at user scope (idempotent — safe to re-run). -P runs after the settings merge described below, so the plugin registration is already in place.

Manual installation: same flags, run directly:

sh
tools setup-ai-agent.sh -t claude -g -P

Settings (.claude/settings.json) ​

Dotfiles-owned and merged into ~/.claude/settings.json during setup by setup/helpers/claude-settings.sh (this is genuinely personal preference, not shareable content, so it lives in this repo rather than the tools repo):

json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "if command -v rtk >/dev/null 2>&1; then rtk hook claude; fi"
          }
        ]
      }
    ]
  },
  "effortLevel": "high",
  "attribution": {
    "commit": "",
    "pr": ""
  },
  "extraKnownMarketplaces": {
    "claude-plugins-official": {
      "source": {
        "source": "github",
        "repo": "anthropics/claude-plugins-official"
      }
    }
  },
  "enabledPlugins": {
    "claude-security@claude-plugins-official": true,
    "superpowers@claude-plugins-official": true
  },
  "permissions": {
    "deny": [
      "Read(//**/.env)",
      "Read(//**/.env.*)",
      "Read(//**/*.pem)",
      "Read(//**/*.key)",
      "Read(//**/*.p12)",
      "Read(//**/*.pfx)",
      "Read(//**/id_rsa)",
      "Read(//**/id_dsa)",
      "Read(//**/id_ecdsa)",
      "Read(//**/id_ed25519)",
      "Read(//**/secrets/**)",
      "Read(~/.config/dotfiles/secrets.env)",
      "Read(~/.continue/.env)",
      "Read(~/.claude.json)",
      "Read(~/.netrc)",
      "Read(~/.npmrc)",
      "Read(~/.pypirc)",
      "Read(~/.git-credentials)",
      "Read(~/.vault-token)",
      "Read(~/.docker/config.json)",
      "Read(~/.config/gh/hosts.yml)",
      "Read(~/.config/argocd/**)",
      "Read(~/.config/rta/kv.identity)",
      "Read(~/.ssh/**)",
      "Read(~/.aws/**)",
      "Read(~/.gnupg/**)",
      "Read(~/.kube/**)"
    ]
  }
}

How the merge works:

  • Without an existing ~/.claude/settings.json, the file is copied as is. Otherwise the two are merged, and the previous file is saved under ~/.dotfiles-backup/<timestamp>/ when the merge changes it.
  • A file that is not a valid JSON object (a stray comma after a hand edit, for instance) cannot be merged: the setup says so, saves it under the same backup directory and installs the repository file in its place, then goes on with the next step. Copy what you need back from the backup.
  • Policy keys come from the repository and win: hooks, attribution, extraKnownMarketplaces, enabledPlugins and permissions. The permissions.deny and hooks.PreToolUse lists are unioned with the local ones, so rules and hooks you added locally stay, and a rule added to the repository reaches a machine on its next run. The flip side: removing a rule from the repository does not remove it from a machine that already has it.
  • One exception: a PreToolUse hook whose command is the bare rtk hook claude (what rtk init -g and earlier versions of this file write) is dropped, as the repository's hook below replaces it. When the setup runs rtk init -g --auto-patch before the merge, rtk adds that hook back first, so each run saves a backup of the file; rtk init -g --no-patch installs rtk's other files without touching settings.json.
  • Every other key (here effortLevel) is only a default: it is applied when the file does not set it, so a personal choice made with /config or /model is never reset.

What the keys do:

  • The hook runs rtk hook claude, the auto-rewrite mechanism rtk init -g installs (token-optimized shell command output, no prompt-level instructions needed), only when rtk is on the PATH. Claude Code runs a hook command through a shell, and a bare rtk hook claude fails with "command not found" on every Bash call of a machine without rtk; the command -v test makes it a no-op there instead.

  • attribution.commit: "" removes the Co-Authored-By trailer from commits and attribution.pr: "" removes the attribution line from pull request descriptions (an empty value hides the line for both keys).

  • enabledPlugins + extraKnownMarketplaces register the Claude Code plugins below. claude plugin install writes the same keys into ~/.claude/settings.json; since the file is merged rather than overwritten, a plugin installed by hand survives a setup re-run, and the plugins declared here are always re-enabled. setup-ai-agent.sh -P (which runs after the merge) materializes the plugin files and updates them.

  • permissions.deny keeps Claude Code's file tools away from secret-shaped paths, on every machine this file gets deployed to:

    • anywhere on the machine: .env, .env.*, *.pem, *.key, *.p12, *.pfx, private keys named id_rsa, id_dsa, id_ecdsa or id_ed25519, and secrets/ directories
    • in your home directory: ~/.config/dotfiles/secrets.env, ~/.continue/.env, ~/.claude.json (it holds the MCP server definitions), the credential files ~/.netrc, ~/.npmrc, ~/.pypirc, ~/.git-credentials, ~/.vault-token, ~/.docker/config.json and ~/.config/gh/hosts.yml, the Argo CD CLI's ~/.config/argocd/, the key that opens the rta store (~/.config/rta/kv.identity), and the ~/.ssh, ~/.aws, ~/.gnupg and ~/.kube directories

    The per-project template in the tools repo's ai/claude/config/settings.json covers fewer paths, and differently: its .env, .env.*, *.pem, *.key and secrets/** rules are relative to the project (opt-in per repo) and come with Edit(...) copies, while the first group here is anchored at // so it applies anywhere, and the home-directory files are not in the template at all. The VS Code terminal approval rules ask before a command line that names any of these paths (see Tool Auto-Approval). See Managing Secrets for the files themselves.

How deny rules match:

  • Anchors. Rules follow gitignore-style patterns. A bare Read(.env) is the same as Read(**/.env) but only matches at or under the current directory. // starts an absolute path, so Read(//**/.env) matches a .env anywhere on the machine. ~/path is relative to your home directory. A single leading / anchors at the source of the settings file (in user settings, ~/.claude/), not at the filesystem root.
  • No Edit mirrors. A Read deny also blocks Edit and Write on the same path, so the file has no Edit(...) copies of the rules. NotebookEdit is not covered.
  • Bash is partly covered. Read/Edit denies apply to Claude's file tools and also to the Bash file commands Claude Code recognises (cat, head, tail, sed, tee) and to redirections. They do not apply to grep -r run in a directory, nor to scripts, interpreters or any other program that opens the file itself.
  • Bash(...) deny rules are literal text matches. They match the text of the command, so an absolute path or an sh -c '...' wrapper evades them: they are a speed bump, not a boundary. This file has none for that reason.
  • Unverified: the rtk hook. rtk hook claude rewrites cat .env into rtk read .env before permission rules are evaluated. Whether the Read deny still catches the rewritten command has not been verified in a live session, so do not rely on it for Bash.

Optional: enforce it at the OS level (sandbox). Permission rules are evaluated by Claude Code itself. For a boundary the operating system enforces on the commands Claude runs (and the processes they start), turn on the sandbox and deny reads there. It is off by default and not in the repository file, because it can break tools. Add it to your own ~/.claude/settings.json (the merge leaves it alone), or run /sandbox:

json
{
  "sandbox": {
    "enabled": true,
    "filesystem": {
      "denyRead": ["~/.config/dotfiles/secrets.env", "~/.claude.json", "~/.aws"]
    }
  }
}
  • Sandbox paths use the usual conventions (~/ is your home, /tmp/x is absolute), unlike permission rules where // is the absolute prefix.
  • The sandbox only covers shell commands. Claude's file tools follow the permission rules above, and MCP servers and hooks run outside the sandbox.
  • Every denied path is unreadable for the commands Claude runs: adding ~/.ssh stops git over SSH, ~/.kube stops kubectl, ~/.gnupg stops commit signing.
  • Some tools fail inside it: gh, gcloud and terraform may fail TLS verification on macOS (Seatbelt), docker is incompatible, watchman too. List them in sandbox.excludedCommands (for example "gh *", "docker *") to run them outside the sandbox.

Plugins ​

Declared in enabledPlugins of the settings and installed at user scope by setup-ai-agent.sh -P during setup, so they're available in every repo without per-project opt-in:

PluginPurpose
claude-security@claude-plugins-officialOn-demand multi-agent vulnerability scan (/claude-security), with adversarially-verified findings and reviewed patches
superpowers@claude-plugins-officialJesse Vincent's skills library (TDD, brainstorming, plan writing/execution, parallel-agent workflows, and more)

Both cheap to keep around — ~642 and ~688 tokens always-on respectively, and nothing runs until invoked (skills) or requested (/claude-security). claude-security requires a paid plan with dynamic workflows enabled and python3 ≥ 3.9.6; superpowers has no special requirements. Full rationale, usage, and the deliberate exclusion of the security-guidance plugin are in the tools repo's Plugins section.

Manual install / verify:

sh
tools setup-ai-agent.sh -t claude -g -P
claude plugin list

RTK (Rust Token Killer) ​

rtk init -g --auto-patch (run automatically by the setup scripts when rtk is installed, before .claude/settings.json is merged into place) generates ~/.claude/RTK.md and adds the @RTK.md import to ~/.claude/CLAUDE.md. See the tools repo's RTK section for details.

Custom Agents ​

Not installed by default. To add them to ~/.claude/agents/:

sh
tools setup-ai-agent.sh -t claude -g -a

GitHub Copilot Instructions ​

Overview ​

GitHub Copilot customization is file based: markdown files that guide Copilot's behavior are picked up from default locations, with no per-feature setting needed:

  • Instructions (*.instructions.md, with an optional applyTo glob) - always-on or file-scoped guidance
  • Skills (<name>/SKILL.md) - workflows loaded on demand
  • Agents (<name>.md) - specialist personas, selectable from the agent picker

The personal locations VS Code reads by default (Agent Host) are ~/.copilot/instructions, ~/.copilot/copilot-instructions.md, ~/.claude/rules for instructions, ~/.copilot/skills, ~/.claude/skills, ~/.agents/skills for skills and ~/.copilot/agents, ~/.claude/agents for agents. Repository-level files (.github/copilot-instructions.md, .github/instructions/, AGENTS.md, CLAUDE.md, .github/skills/, .github/agents/) apply on top.

The chat.instructionsFilesLocations, chat.agentSkillsLocations and chat.agentFilesLocations settings in .vscode/settings.json are deprecated by VS Code and only read by the Local agent; they stay in the file so the Local agent also finds the personal folders. github.copilot.chat.codeGeneration.instructions is deprecated as well and is not used. See VS Code Configuration.

Three Copilot features have their own instruction setting, each pointing at a file: commit messages, pull request descriptions and code review of a selection.

Installation ​

The setup scripts install Copilot's personal instructions and skills via the tools repository's setup-ai-agent.sh:

Automatic (during setup): ./setup/setup-osx.sh -d (or setup-debian.sh -d) runs, through the remote helper described at the top of this page:

sh
setup-ai-agent.sh -t copilot -g -c

This installs ~/.copilot/instructions/general.instructions.md, the skills code-review, repository-audit, commit-message and pull-request in ~/.copilot/skills/, and (-c) the general instructions in ~/.copilot/copilot-instructions.md, the personal file of Copilot CLI. That file is not overwritten: the instructions go in a marked block (<!-- general-instructions --> ... <!-- /general-instructions -->) that is replaced on re-runs, and everything outside it (the rtk block, your own content) is left untouched. A copy of the general instructions without markers, left by an older run, triggers a warning: remove it by hand so it is not loaded twice.

Manual installation:

sh
tools setup-ai-agent.sh -t copilot -g -c

Only the general instructions are installed. The commit message, pull request and code review instruction files are not installed on purpose: they declare applyTo: "**", so putting them in ~/.copilot/instructions/ would load about 830 more lines into every chat. The three per-feature settings in .vscode/settings.json point at ~/.copilot/instructions/*.instructions.md, so until you install those files they have nothing to read. To use them, keep them outside the auto-loaded folder and repoint the settings' file entries there:

sh
mkdir -p ~/.copilot/feature-instructions
for name in commit-message pull-request code-review; do
  curl -fsSL "https://raw.githubusercontent.com/this-is-tobi/tools/main/ai/common/instructions/${name}.instructions.md" \
    -o "$HOME/.copilot/feature-instructions/${name}.instructions.md"
done

Instruction Files ​

general.instructions.md ​

Purpose: Universal development guidelines

Location: ~/.copilot/instructions/general.instructions.md (installed by the setup)

Content:

  • Code quality principles, version control, documentation and testing practices
  • Security, performance, error handling and monitoring
  • Configuration, dependency and development environment management
  • Code review, deployment and operations
  • An "AI Agent Behaviour" section (read before writing, incremental changes, no unnecessary files, follow existing conventions)

Usage in VS Code: no setting is needed. The file carries an applyTo: "**" frontmatter, so it applies to every file, and ~/.copilot/instructions is one of the folders VS Code reads by default. For the Local agent, chat.instructionsFilesLocations in settings.json lists the folder.

commit-message.md ​

Purpose: Conventional commit message generation

Location: ~/.copilot/instructions/commit-message.instructions.md (optional, see Installation above)

Content:

  • Conventional Commits v1.0.0 specification
  • Commit type definitions (feat, fix, docs, etc.)
  • Scope guidelines
  • Breaking change handling
  • Commit message examples by type
  • Multi-commit scenarios
  • Best practices for atomic commits

Format enforced:

<type>[optional scope]: <description>

[optional body]

[optional footer(s)]

Usage in VS Code:

json
{
  "github.copilot.chat.commitMessageGeneration.instructions": [
    { "file": "~/.copilot/instructions/commit-message.instructions.md" }
  ]
}

Examples:

feat(api): add user authentication endpoint

fix: resolve memory leak in cache layer

docs(readme): add installation instructions

chore(deps): upgrade dependencies to latest versions

pull-request.md ​

Purpose: PR description best practices and template compliance

Location: ~/.copilot/instructions/pull-request.instructions.md (optional, see Installation above)

Content:

  • PR description structure
  • Check for the repository's own template first
  • Context extraction from commits
  • Issue reference formatting
  • Breaking change highlighting
  • Examples for different PR types (features, fixes, refactors)

Templates looked up before writing a custom description:

  1. .github/PULL_REQUEST_TEMPLATE.md or .github/pull_request_template.md
  2. .github/PULL_REQUEST_TEMPLATE/ (several templates)
  3. docs/PULL_REQUEST_TEMPLATE.md
  4. PULL_REQUEST_TEMPLATE.md (repository root)

The contributing guidelines (CONTRIBUTING.md) are checked as well. Without a template, the file's own structure is used.

Usage in VS Code:

json
{
  "github.copilot.chat.pullRequestDescriptionGeneration.instructions": [
    { "file": "~/.copilot/instructions/pull-request.instructions.md" }
  ]
}

code-review.md ​

Purpose: Lightweight code review guidelines (the detailed methodology lives in the code-review skill)

Location: ~/.copilot/instructions/code-review.instructions.md (optional, see Installation above)

Content:

  • Priority levels (Critical, High, Medium, Low)
  • Six core checks: correctness, security, performance, testing, quality, error handling
  • Output structure (summary, critical issues, important issues, suggestions, positive feedback, questions)

Usage in VS Code:

json
{
  "github.copilot.chat.reviewSelection.instructions": [
    { "file": "~/.copilot/instructions/code-review.instructions.md" }
  ]
}

Review priorities:

  • Critical - Must fix before merge (security vulnerabilities, bugs causing crashes or data loss)
  • High - Should fix (performance bottlenecks, missing error handling, missing tests on critical paths)
  • Medium - Consider fixing (style, naming, refactoring opportunities)
  • Low - Nice to have (extra test coverage, documentation, micro-optimizations)

Using Copilot Instructions ​

Generating Commit Messages ​

  1. Stage your changes: git add .
  2. In the Source Control view, use the Copilot button of the commit message box
  3. Copilot generates a conventional commit message, following commit-message.instructions.md through github.copilot.chat.commitMessageGeneration.instructions
  4. Review and accept or modify

Example:

feat(auth): add OAuth2 authentication support

Implements OAuth2 authentication flow with support for
Google and GitHub providers. Includes token refresh logic
and secure session management.

BREAKING CHANGE: replaces existing basic auth system

Generating PR Descriptions ​

  1. Create the PR in VS Code (GitHub Pull Requests extension)
  2. Use the Copilot action of the PR creation view to generate the description (github.copilot.chat.pullRequestDescriptionGeneration.instructions)
  3. Copilot checks for templates and formats appropriately

Example:

markdown
## Summary
Adds OAuth2 authentication support with Google and GitHub providers.

## Changes
- Implement OAuth2 flow
- Add token refresh mechanism
- Update authentication middleware

## Breaking Changes
⚠️ This replaces the existing basic authentication system

## Related Issues
Closes #123

Code Review ​

  1. Select code in editor
  2. Ask Copilot to review the selection (github.copilot.chat.reviewSelection.instructions shapes the review)
  3. Copilot provides a structured review with priorities

Example:

Critical:
- Missing input validation on user email (security)

High:
- Database query in loop (performance)
- No error handling for API call

Medium:
- Consider extracting magic number to constant

Low:
- Add JSDoc comment
- Variable name could be more descriptive

Model Context Protocol (MCP) ​

Overview ​

MCP provides standardized access to external tools and data sources for AI assistants. This dotfiles repo configures the same set of servers for both Claude Code (registered at user scope by setup/helpers/claude-mcp.sh, which the setup scripts run) and VS Code Copilot (.vscode/mcp.json) — github is the one exception, since it authenticates via the VS Code Copilot session and has no equivalent for Claude Code. The Claude Code helper is idempotent (claude mcp add fails when the name already exists): the servers it defines (context7, kubernetes, playwright and argocd) are removed and registered again on every run, so each entry always runs the version pinned in the script and an entry where a value was once stored in plain text is repaired. Servers registered under other names are never touched, so register your own variant of one of these under another name (a re-run would replace a kubernetes entry you edited).

The npx servers (kubernetes, argocd, playwright) run with your kube credentials or an API token, so they are pinned to an exact version in both setup/helpers/claude-mcp.sh and .vscode/mcp.json (shown as <version> below) rather than @latest, which would run whatever was published last. renovate.json, at the root of the repository, lets Renovate propose the updates: a release is proposed once it is three days old, both files move in the same pull request, and nothing is merged automatically (details in mcp.json). After merging one, re-run setup/helpers/claude-mcp.sh (or the setup with -d) to move Claude Code to the new version.

Managing Secrets ​

Secrets live in one local file, ~/.config/dotfiles/secrets.env, and need no CLI, secret manager or wrapper. The first time the setup copies the dotfiles it creates that file from secrets.env.example with mode 600, and it never overwrites it afterwards. The file holds plain KEY=value lines; env.sh (managed by the setup, don't edit it) loads it into every zsh session.

Each tool then picks the secrets up through its own standard mechanism:

ConsumerHow it gets the secret
Shell and CLIs (vault, argocd, mise)exported from secrets.env by env.sh
Claude Code MCP servers${VAR} references stored in ~/.claude.json, expanded from the environment when Claude Code starts
VS Code MCP serversinputs in mcp.json: VS Code asks once per value (password: true hides it) and stores it securely
Continue${{ secrets.NAME }} in config.yaml, resolved from ~/.continue/.env
A single projectdirenv: dotenv_if_exists .env.local in the project's .envrc, with .env.local git-ignored

Keep in secrets.env only what you want in every shell: every program you start, AI agents included, inherits it. Project-specific keys belong in that project's .env.local, loaded by direnv only inside the project. The file is sourced by the shell, so a secret manager stays optional: a line like CONTEXT7_API_KEY=$(your-tool get context7) works if you prefer fetching values from one.

Deny rules in .claude/settings.json keep Claude Code's file tools away from secrets.env and ~/.claude.json; what else they cover (and what they do not) is detailed under Settings.

Keeping secrets in rta (optional) ​

If you would rather not have tokens in a plain file, rta's kv store keeps them encrypted (Secrets); nothing in this repository needs it. Keep the non-secret lines (ARGOCD_BASE_URL, VAULT_ADDR) in secrets.env and move the tokens:

sh
bash <<'EOF'
set -euo pipefail
set -a; . ~/.config/dotfiles/secrets.env; set +a
for k in CONTEXT7_API_KEY ARGOCD_API_TOKEN ARGOCD_AUTH_TOKEN VAULT_TOKEN; do
  printf '%s' "${!k}" | rta kv set "$k" --file /dev/stdin --description "dotfiles shell env"
done
EOF

The value goes through a pipe, so it is in no argument list or shell history. Check each one ([ "$(rta kv get "$k")" = "${!k}" ] prints nothing but the verdict), then delete the token lines from secrets.env and list the names, without values, in the same file:

sh
DOTFILES_RTA_KEYS="CONTEXT7_API_KEY ARGOCD_API_TOKEN ARGOCD_AUTH_TOKEN VAULT_TOKEN"

The secrets command (see secrets -h) then exports those keys into the current shell. Name the keys: a bare rta kv env would also export every other entry of the store. Run secrets before starting Claude Code from that terminal, since it reads ${VAR} from the environment at launch. To load them in every new shell instead, put secrets in ~/.zshrc.local, which only suits a store that opens without a prompt.

Passphrase. rta kv init --generate locks the store to a key file that opens without asking, so anything running as you can read it. To ask for a passphrase instead, lock the store to an SSH key that has one: rta kv rekey --only --recipient ~/.ssh/<key>.pub --identity ~/.ssh/<key> (add --dry-run first; the key must already be able to open the store, otherwise add it first with rta kv rekey --recipient ~/.ssh/<key>.pub). rta then asks for the key's passphrase on each command (ssh-agent cannot decrypt), RTA_KV_IDENTITY=~/.ssh/<key> names the key without a flag (set it in ~/.zshrc.local), and RTA_KV_PASSPHRASE supplies the passphrase to a script or an MCP server. Move ~/.config/rta/kv.identity away once it is verified: rta kv status keeps saying "unlocks here" while that file exists, even after it stopped being able to open the store. Copies of kv.age made earlier stay readable by the old key. A store that is a plain passphrase (no key) is created by the first rta kv set on a machine with no store; rta's docs describe no way to convert a key-locked store into one, and a store cannot take both a passphrase and keys.

A daily key and a backup key. Any number of keys can open the store, each with its own passphrase. A dedicated key for daily use, with your usual SSH key kept as the way back in:

sh
ssh-keygen -t ed25519 -C "rta kv" -f ~/.ssh/rta_kv     # asks for its passphrase
rta kv rekey --only --recipient ~/.ssh/rta_kv.pub --recipient ~/.ssh/id_rsa.pub --identity ~/.ssh/id_rsa --dry-run
rta kv rekey --only --recipient ~/.ssh/rta_kv.pub --recipient ~/.ssh/id_rsa.pub --identity ~/.ssh/id_rsa
RTA_KV_IDENTITY=~/.ssh/rta_kv rta kv list              # daily key
RTA_KV_IDENTITY=~/.ssh/id_rsa rta kv list              # backup key
echo 'export RTA_KV_IDENTITY="$HOME/.ssh/rta_kv"' >> ~/.zshrc.local

--identity names the key that opens the store now. A wrong passphrase reports kv.identity.locked: it is the key's passphrase, not the store's.

The backup scripts copy kv.age and kv.recipients (see Backup & Restore).

Configured Servers ​

GitHub MCP Server ​

Type: HTTP

Purpose: GitHub API integration (repos, PRs, issues, code search, Actions)

Configuration (.vscode/mcp.json):

json
{
  "github": {
    "type": "http",
    "url": "https://api.githubcopilot.com/mcp/"
  }
}

No setup required - uses your VS Code GitHub authentication. Copilot (VS Code) only — Claude Code has its own native GitHub integration via the gh CLI instead.

Context7 MCP Server ​

Type: HTTP

Purpose: Up-to-date library and framework documentation

Configuration (.vscode/mcp.json):

json
{
  "context7": {
    "type": "http",
    "url": "https://mcp.context7.com/mcp",
    "headers": {
      "CONTEXT7_API_KEY": "${input:context7-api-key}"
    }
  }
}

context7-api-key is a promptString input (password: true) declared in the same file: VS Code asks for the key the first time the server starts and stores it securely.

Claude Code setup (done automatically by the setup scripts):

sh
claude mcp add --transport http context7 https://mcp.context7.com/mcp -H 'CONTEXT7_API_KEY: ${CONTEXT7_API_KEY}' -s user

WARNING

The single quotes around ${CONTEXT7_API_KEY} are load-bearing — they stop the shell from expanding the variable before claude mcp add sees it. Double-quoting would bake the current secret value into ~/.claude.json as plaintext instead of storing the ${VAR} reference for Claude Code to expand at runtime.

Setup required:

  1. Get an API key at context7.com
  2. Add to ~/.config/dotfiles/secrets.env (Claude Code reads it from the environment):
sh
CONTEXT7_API_KEY=your_api_key_here
  1. Restart the terminal and any running Claude Code sessions. VS Code asks for the key the first time the server starts.

Kubernetes MCP Server ​

Type: stdio (npx)

Purpose: Kubernetes cluster management (pods, services, deployments, logs, events)

Configuration (.vscode/mcp.json):

json
{
  "kubernetes": {
    "type": "stdio",
    "command": "npx",
    "args": ["-y", "kubernetes-mcp-server@<version>"]
  }
}

Claude Code setup:

sh
claude mcp add kubernetes -s user -- npx -y kubernetes-mcp-server@<version>

Setup required: ~/.kube/config must exist, be valid, and point at a reachable cluster.

ArgoCD MCP Server ​

Type: stdio (npx)

Purpose: List/sync/inspect ArgoCD applications

Configuration (.vscode/mcp.json):

json
{
  "argocd": {
    "type": "stdio",
    "command": "npx",
    "args": ["-y", "argocd-mcp@<version>", "stdio"],
    "env": {
      "ARGOCD_BASE_URL": "${input:argocd-base-url}",
      "ARGOCD_API_TOKEN": "${input:argocd-api-token}"
    }
  }
}

Claude Code setup (registered only when ARGOCD_BASE_URL is set in secrets.env):

sh
claude mcp add argocd -s user -e ARGOCD_BASE_URL='${ARGOCD_BASE_URL}' -e ARGOCD_API_TOKEN='${ARGOCD_API_TOKEN}' -- npx -y argocd-mcp@<version> stdio

WARNING

Same reason as the Context7 command above — single-quote the ${...} references so the literal reference is stored, not the expanded secret.

Setup required: the server URL and an ArgoCD API token (Settings > Accounts > Generate Token). For Claude Code, set ARGOCD_BASE_URL and ARGOCD_API_TOKEN in ~/.config/dotfiles/secrets.env, then re-run setup/helpers/claude-mcp.sh. VS Code asks for both the first time the server starts. Remove the server from mcp.json if you don't use ArgoCD.

Playwright MCP Server ​

Type: stdio (npx)

Purpose: Browser automation (navigate, click, screenshot, fill forms)

Configuration (.vscode/mcp.json, registered as microsoft/playwright-mcp):

json
{
  "microsoft/playwright-mcp": {
    "type": "stdio",
    "command": "npx",
    "args": ["-y", "@playwright/mcp@<version>"]
  }
}

Claude Code setup (registered as playwright):

sh
claude mcp add playwright -s user -- npx -y @playwright/mcp@<version>

Setup required: none beyond Node/npx being available.

Adding Custom MCP Servers ​

Finding MCP Servers ​

Browse available servers:

Adding a Server ​

  1. Edit the VS Code user mcp.json (Command Palette: MCP: Open User Configuration; the repository copy is dotfiles/.vscode/mcp.json, which setup -d installs). Add the server to the servers object:
json
{
  "my-server": {
    "type": "stdio",
    "command": "node",
    "args": ["/path/to/server.js"]
  }
}
  1. Server types: stdio, http and sse.

stdio (local command, standard input/output):

json
{
  "type": "stdio",
  "command": "node",
  "args": ["server.js"],
  "env": {
    "API_BASE_URL": "https://api.example.com"
  }
}

http (HTTP endpoint, field url):

json
{
  "type": "http",
  "url": "https://api.example.com/mcp"
}

sse (Server-Sent Events, field url):

json
{
  "type": "sse",
  "url": "https://api.example.com/sse",
  "headers": {
    "Authorization": "Bearer ${input:my-api-token}"
  }
}
  1. Keep secrets out of the file. Declare an inputs entry next to servers and reference it with ${input:<id>} (works in headers and env): VS Code asks for the value once and stores it securely.
json
{
  "inputs": [
    {
      "type": "promptString",
      "id": "my-api-token",
      "description": "API token of my-server",
      "password": true
    }
  ]
}

To read a value from the environment instead, use ${env:NAME} (the variable must be in VS Code's own environment, so start VS Code from a shell that exports it). A stdio server can also load a file of variables with envFile; envFile does not exist for http and sse.

  1. Claude Code has its own registry, ~/.claude.json. Add the same server at user scope, single-quoting ${VAR} so the reference, not its value, is stored (see the warning under Context7):
sh
claude mcp add --transport http my-server https://api.example.com/mcp -H 'Authorization: Bearer ${MY_API_TOKEN}' -s user

claude mcp add exits with an error when the name already exists: run claude mcp remove my-server -s user first to change an entry. Do not reuse the names the setup registers (context7, kubernetes, playwright, argocd): a re-run replaces those entries. ${VAR} and ${VAR:-default} are expanded in url, headers, command, args and env; an unset variable is left as is and the server loads with a warning. --transport accepts http, sse and stdio.

  1. Start the server. VS Code starts new or changed servers on its own (chat.mcp.autostart is newAndOutdated); MCP: List Servers shows their state.

Troubleshooting ​

Copilot Instructions Not Applied ​

Check file paths:

sh
ls -la ~/.copilot/instructions/

Verify VS Code settings: Open the user settings (Command Palette: Preferences: Open User Settings (JSON); on macOS the file is ~/Library/Application Support/Code/User/settings.json, on Linux ~/.config/Code/User/settings.json) and check that the file entries point at existing files. Only general.instructions.md is installed by default, see the Installation part of the Copilot section above.

Reload instructions:

  1. Restart VS Code
  2. Or reload window: Cmd+Shift+P → "Reload Window"

MCP Server Connection Failed ​

Check server status:

  1. Cmd+Shift+P → "MCP: Show Server Status"
  2. Review error messages

Common issues:

Missing environment variables (Claude Code):

sh
# Verify in terminal
echo $CONTEXT7_API_KEY

# Add to secrets.env if missing
vim ~/.config/dotfiles/secrets.env

VS Code does not use the environment for these servers: it asks for the value through inputs.

Node.js missing: the Kubernetes, Argo CD and Playwright servers start through npx.

sh
npx --version

Invalid Kubernetes config:

sh
# Test cluster access
kubectl cluster-info

# Update config if needed

Copilot Not Using MCP Servers ​

Explicitly mention the server:

You: Using GitHub MCP, what's the status of PR #42?

Check Copilot settings:

  • Ensure Copilot is enabled
  • Verify MCP integration is active
  • Check network connectivity

Best Practices ​

Instruction Files ​

  • Keep instructions concise and actionable
  • Update instructions as practices evolve
  • Use project-specific instructions when needed
  • Share instructions with team

MCP Servers ​

  • Only enable servers you actively use
  • Rotate API keys regularly
  • Monitor server performance

Security ​

  • Never commit API keys to git
  • Keep secrets in secrets.env (or VS Code inputs), never in a committed file
  • Review MCP server permissions
  • Use minimal necessary scopes

Further Reading ​