Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/agents/cockroachdb-dba.agent.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ You are a CockroachDB database administration expert. You specialize in:
## Available MCP Tools

**Via MCP Toolbox** (self-hosted, any cluster):
- `cockroachdb-execute-sql`: Execute any SQL statement
- `cockroachdb-execute-sql`: Run SQL (read-only by default: SELECT, SHOW, EXPLAIN; writes and DDL need write mode enabled in the Toolbox config)
- `cockroachdb-list-schemas`: List database schemas
- `cockroachdb-list-tables`: List tables with column details

Expand Down
2 changes: 1 addition & 1 deletion .github/agents/cockroachdb-developer.agent.md
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ When bulk DML exceeds 250K-500K rows (or 1M+ without secondary indexes):
## Available MCP Tools

**Via MCP Toolbox** (self-hosted, any cluster):
- `cockroachdb-execute-sql`: Execute any SQL statement
- `cockroachdb-execute-sql`: Run SQL (read-only by default: SELECT, SHOW, EXPLAIN; writes and DDL need write mode enabled in the Toolbox config)
- `cockroachdb-list-schemas`: List database schemas
- `cockroachdb-list-tables`: List tables with column details

Expand Down
2 changes: 1 addition & 1 deletion .github/agents/cockroachdb-operator.agent.md
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ For batch DML exceeding 250K-500K rows:
## Available MCP Tools

**Via MCP Toolbox** (self-hosted, any cluster):
- `cockroachdb-execute-sql`: Execute any SQL statement (diagnostics, SHOW commands, DDL)
- `cockroachdb-execute-sql`: Run SQL for diagnostics and SHOW commands (read-only by default; DDL and other writes need write mode enabled in the Toolbox config)
- `cockroachdb-list-schemas`: List database schemas
- `cockroachdb-list-tables`: List tables with column details

Expand Down
8 changes: 4 additions & 4 deletions .github/hooks/cockroachdb.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,23 @@
"hooks": {
"PreToolUse": [
{
"matcher": "mcp__cockroachdb-toolbox__cockroachdb-execute-sql",
"matcher": "cockroachdb-toolbox-cockroachdb-execute-sql|mcp__plugin_cockroachdb_cockroachdb-toolbox__cockroachdb-execute-sql|mcp__cockroachdb-toolbox__cockroachdb-execute-sql",
"hooks": [
{
"type": "command",
"command": "python3 -c 'import sys, os, runpy; p = os.path.normpath(r\"${CLAUDE_PLUGIN_ROOT}/scripts/validate-sql.py\"); p = (\"\\\\?\\\\\" + p) if os.name == \"nt\" else p; runpy.run_path(p, run_name=\"__main__\")'; exit 0",
"command": "python3 -c 'import os, runpy; r = r\"${CLAUDE_PLUGIN_ROOT}\"; r = r if r and not r.startswith(\"$\") else next((os.environ[k] for k in (\"CLAUDE_PLUGIN_ROOT\", \"COPILOT_PLUGIN_ROOT\", \"PLUGIN_ROOT\") if os.environ.get(k)), \"\"); p = os.path.normpath(os.path.join(r, \"scripts\", \"validate-sql.py\")) if r else \"\"; p = chr(92) * 2 + \"?\" + chr(92) + p if p and os.name == \"nt\" else p; p and os.path.isfile(p) and runpy.run_path(p, run_name=\"__main__\")'; exit 0",
"timeout": 10
}
]
}
],
"PostToolUse": [
{
"matcher": "Write|Edit|MultiEdit",
"matcher": "Write|Edit|MultiEdit|create|edit|str_replace_editor|apply_patch",
"hooks": [
{
"type": "command",
"command": "python3 -c 'import sys, os, runpy; p = os.path.normpath(r\"${CLAUDE_PLUGIN_ROOT}/scripts/check-sql-files.py\"); p = (\"\\\\?\\\\\" + p) if os.name == \"nt\" else p; runpy.run_path(p, run_name=\"__main__\")'; exit 0",
"command": "python3 -c 'import os, runpy; r = r\"${CLAUDE_PLUGIN_ROOT}\"; r = r if r and not r.startswith(\"$\") else next((os.environ[k] for k in (\"CLAUDE_PLUGIN_ROOT\", \"COPILOT_PLUGIN_ROOT\", \"PLUGIN_ROOT\") if os.environ.get(k)), \"\"); p = os.path.normpath(os.path.join(r, \"scripts\", \"check-sql-files.py\")) if r else \"\"; p = chr(92) * 2 + \"?\" + chr(92) + p if p and os.name == \"nt\" else p; p and os.path.isfile(p) and runpy.run_path(p, run_name=\"__main__\")'; exit 0",
"timeout": 15
}
]
Expand Down
15 changes: 2 additions & 13 deletions .mcp.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,11 @@
"mcpServers": {
"cockroachdb-toolbox": {
"command": "toolbox",
"args": ["--config", "${CLAUDE_PLUGIN_ROOT}/tools.yaml", "--stdio"],
"env": {
"COCKROACHDB_HOST": "${COCKROACHDB_HOST}",
"COCKROACHDB_PORT": "${COCKROACHDB_PORT}",
"COCKROACHDB_USER": "${COCKROACHDB_USER}",
"COCKROACHDB_PASSWORD": "${COCKROACHDB_PASSWORD}",
"COCKROACHDB_DATABASE": "${COCKROACHDB_DATABASE}",
"COCKROACHDB_SSLMODE": "${COCKROACHDB_SSLMODE}"
}
"args": ["--config", "${CLAUDE_PLUGIN_ROOT}/tools.yaml", "--stdio"]
},
"cockroachdb-cloud": {
"type": "http",
"url": "https://cockroachlabs.cloud/mcp",
"headers": {
"mcp-cluster-id": "${COCKROACHDB_CLUSTER_ID}"
}
"url": "https://cockroachlabs.cloud/mcp"
}
}
}
10 changes: 1 addition & 9 deletions .vscode/mcp.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,7 @@
"servers": {
"cockroachdb-toolbox": {
"command": "toolbox",
"args": ["--config", "${workspaceFolder}/tools.yaml", "--stdio"],
"env": {
"COCKROACHDB_HOST": "${env:COCKROACHDB_HOST}",
"COCKROACHDB_PORT": "${env:COCKROACHDB_PORT}",
"COCKROACHDB_USER": "${env:COCKROACHDB_USER}",
"COCKROACHDB_PASSWORD": "${env:COCKROACHDB_PASSWORD}",
"COCKROACHDB_DATABASE": "${env:COCKROACHDB_DATABASE}",
"COCKROACHDB_SSLMODE": "${env:COCKROACHDB_SSLMODE}"
}
"args": ["--config", "${workspaceFolder}/tools.yaml", "--stdio"]
},
"cockroachdb-cloud": {
"type": "http",
Expand Down
3 changes: 2 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ The CockroachDB plugin for GitHub Copilot: plugin manifest (`plugin.json` at the
- **Never edit `.github/skills/` by hand.** Skills come from the [cockroachdb-skills](https://github.com/cockroachlabs/cockroachdb-skills) submodule via `scripts/sync-skills.sh`, which flattens the domain-grouped upstream tree (Copilot requires `.github/skills/<name>/` with the directory equal to the frontmatter `name:`), dereferences symlinks, and runs `scripts/transform-skills-for-copilot.py`. Edit the sync or transform scripts, or contribute upstream, never the synced output.
- **The transform exists to pass the awesome-copilot marketplace lint** (`@microsoft/vally`), which enforces three things per skill: no file links outside the skill directory, every file under `references/` reachable from SKILL.md, and SKILL.md at most 500 lines. Two subtleties the transform already handles, so keep them intact: link reachability ignores links inside fenced code blocks (an unbalanced fence count hides everything after it), and the over-length split must only cut at `## ` headers outside code fences.
- **Two MCP configs with different top-level keys, both on purpose:** `.mcp.json` (plugin installs) uses `mcpServers`; `.vscode/mcp.json` (workspace use) uses `servers`. Keep them in sync when adding a backend.
- **Hook scripts emit both output contracts** (top-level `permissionDecision`/`additionalContext` for the Copilot CLI, `hookSpecificOutput`/`systemMessage` for VS Code and Claude Code) and accept `tool_input`, `toolInput`, or `toolArgs` with `file_path`, `filePath`, or `path`. VS Code ignores hook matchers and runs hooks on every tool call, so scripts must exit fast and silent on irrelevant input. Keep the `runpy` long-path bootstrap and trailing `; exit 0` in hook commands.
- **No `env` block or optional header placeholders in `.mcp.json`.** It is read by both Copilot CLI and VS Code, which expand an unset variable differently (literal `${VAR}` in Copilot CLI, empty in VS Code), and Toolbox uses either as the value. Both clients pass the parent environment to stdio servers, so `tools.yaml` defaults apply when a variable is unset (issue cockroachdb/claude-plugin#27).
- **Hook scripts emit both output contracts** (top-level `permissionDecision`/`additionalContext` for the Copilot CLI, `hookSpecificOutput`/`systemMessage` for VS Code and Claude Code) and accept `tool_input`, `toolInput`, or `toolArgs` with `file_path`, `filePath`, or `path`. VS Code ignores hook matchers and runs hooks on every tool call, so scripts must exit fast and silent on irrelevant input. Keep the `runpy` long-path bootstrap, its fallback to the exported plugin-root variables (Claude Code exports `CLAUDE_PLUGIN_ROOT` instead of substituting it), and the trailing `; exit 0` in hook commands. Matchers must list each host's tool name: Copilot CLI uses `<server>-<tool>`, Claude Code uses `mcp__plugin_<plugin>_<server>__<tool>`.
- **Agent frontmatter is `name` and `description` only.** Do not add Claude-only fields like `model:` or `color:`; the user's selected Copilot model should apply.
- **Never bump versions by hand.** Release Please owns `version` in `plugin.json`, `.release-please-manifest.json`, and `CHANGELOG.md`. Conventional commits: `fix:`/`feat:` cut a release, `chore:`/`docs:` do not.
- **No counts in descriptions.** Counts go stale; name the things instead.
Expand Down
8 changes: 5 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,15 +111,17 @@ Never bump the version in `plugin.json` or `.release-please-manifest.json` manua
- Hook scripts must be Python 3 with **no external dependencies** (stdlib only).
- Read JSON from stdin, write JSON to stdout. PreToolUse blocks a tool with `hookSpecificOutput.permissionDecision` set to `deny`.
- VS Code ignores hook matchers (hooks run on every tool invocation), so scripts must exit early when the input is not relevant. VS Code also passes camelCase tool inputs (`filePath`) where Claude uses snake_case (`file_path`); accept both.
- Load hook scripts through the long-path-safe bootstrap below instead of passing the script path straight to `python3`. The plugin root can resolve to a deeply nested cache path that exceeds the 260-character `MAX_PATH` limit on Windows; passing the path directly makes Python fail to open the script. The bootstrap loads the script with `runpy`, prefixes the path with the `\\?\` long-path escape on Windows, keeps it inside single quotes so paths with spaces still work, and uses `; exit 0` so a failed bootstrap never disrupts editing:
- Load hook scripts through the bootstrap below instead of passing the script path straight to `python3`. Hosts treat `${CLAUDE_PLUGIN_ROOT}` differently: Copilot CLI substitutes it into the command text, while Claude Code exports it as an environment variable and leaves the placeholder to the shell, which never expands it inside single quotes. The bootstrap uses the substituted path when there is one and otherwise reads `CLAUDE_PLUGIN_ROOT`, `COPILOT_PLUGIN_ROOT`, or `PLUGIN_ROOT`. The plugin root can also exceed the 260-character Windows `MAX_PATH` limit, so on Windows the bootstrap prefixes the `\\?\` long-path escape, built with `chr(92)` so no escaping layer can change it. It exits quietly when it can't find the script, and `; exit 0` keeps a failed bootstrap from disrupting editing:
```json
"command": "python3 -c 'import sys, os, runpy; p = os.path.normpath(r\"${CLAUDE_PLUGIN_ROOT}/scripts/your-script.py\"); p = (\"\\\\?\\\\\" + p) if os.name == \"nt\" else p; runpy.run_path(p, run_name=\"__main__\")'; exit 0"
"command": "python3 -c 'import os, runpy; r = r\"${CLAUDE_PLUGIN_ROOT}\"; r = r if r and not r.startswith(\"$\") else next((os.environ[k] for k in (\"CLAUDE_PLUGIN_ROOT\", \"COPILOT_PLUGIN_ROOT\", \"PLUGIN_ROOT\") if os.environ.get(k)), \"\"); p = os.path.normpath(os.path.join(r, \"scripts\", \"your-script.py\")) if r else \"\"; p = chr(92) * 2 + \"?\" + chr(92) + p if p and os.name == \"nt\" else p; p and os.path.isfile(p) and runpy.run_path(p, run_name=\"__main__\")'; exit 0"
```
- Hook matchers must name the tool the way each host does. Copilot CLI joins the server and tool names with a hyphen (`cockroachdb-toolbox-cockroachdb-execute-sql`), and Claude Code uses `mcp__plugin_cockroachdb_cockroachdb-toolbox__cockroachdb-execute-sql`. VS Code ignores matchers.

### MCP Configuration

- `.mcp.json` (top-level `mcpServers`) is used by plugin installs; `.vscode/mcp.json` (top-level `servers`) is used in workspaces. Keep both in sync when adding a backend.
- Use `${ENV_VAR}` syntax for environment variable references.
- Don't give the Toolbox server an `env` block. Copilot CLI passes an unset `${VAR}` through as the literal text `${VAR}`, VS Code turns it into an empty string, and Toolbox uses either one as the value, so its `tools.yaml` defaults never apply (issue cockroachdb/claude-plugin#27). Both clients pass the parent environment to stdio servers, so set variables still reach Toolbox and unset ones fall back to `tools.yaml`.
- No placeholder works in both clients for an optional header: `${VAR}` stays literal in Copilot CLI when unset, and `${VAR:-}` stays literal in VS Code. That's why the plugin's `.mcp.json` leaves out the optional `mcp-cluster-id` header. `.vscode/mcp.json` is read only by VS Code, where `${env:VAR}` resolves to an empty string, which the Cloud server treats as absent.
- The `tools.yaml` file uses Toolbox v1.1.0 map-based format with `${VAR:default}` syntax for defaults.

### Skills
Expand Down
Loading
Loading