From e328f6814330de9835075a2b306a010d1e25a670 Mon Sep 17 00:00:00 2001 From: viragtripathi Date: Mon, 21 Sep 2026 21:02:25 -0400 Subject: [PATCH] fix: resolve packaged MCP resources from plugin root --- .github/workflows/release-please.yml | 8 ++- .github/workflows/validate-plugin.yml | 19 +++++++ CONTRIBUTING.md | 4 +- README.md | 43 +++++++------- plugins/cockroachdb/.mcp.json | 21 +++---- tests/test_plugin_package.py | 80 +++++++++++++++++++++++++++ 6 files changed, 138 insertions(+), 37 deletions(-) create mode 100644 .github/workflows/validate-plugin.yml create mode 100644 tests/test_plugin_package.py diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index 70d6564..54edf25 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -13,14 +13,16 @@ jobs: release-please: runs-on: ubuntu-latest steps: + - uses: actions/checkout@v4 + + - name: Validate plugin package + run: python3 -m unittest -v tests/test_plugin_package.py + - uses: googleapis/release-please-action@v4 id: release with: token: ${{ secrets.GITHUB_TOKEN }} - - uses: actions/checkout@v4 - if: ${{ steps.release.outputs.release_created }} - - name: Create archive if: ${{ steps.release.outputs.release_created }} run: | diff --git a/.github/workflows/validate-plugin.yml b/.github/workflows/validate-plugin.yml new file mode 100644 index 0000000..bae99d3 --- /dev/null +++ b/.github/workflows/validate-plugin.yml @@ -0,0 +1,19 @@ +name: Validate plugin + +on: + pull_request: + push: + branches: + - main + +permissions: + contents: read + +jobs: + plugin-package: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Validate MCP package resources + run: python3 -m unittest -v tests/test_plugin_package.py diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c052b3c..6ac967e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -39,6 +39,7 @@ codex plugin add cockroachdb@cockroachdb-codex-plugin Validate that the marketplace install layout is correct: ```bash +python3 -m unittest -v tests/test_plugin_package.py REPO_URL="file://$(pwd)" ./scripts/validate-marketplace-install.sh HEAD ``` @@ -143,7 +144,8 @@ This repo uses [Release Please](https://github.com/googleapis/release-please) fo ### MCP Configuration - `.mcp.json` defines MCP server backends. -- Use `${ENV_VAR}` syntax for environment variable references. +- Use `env_vars` to forward variables to stdio servers and + `env_http_headers` to source HTTP header values from variables. - The `tools.yaml` file uses Toolbox v1.1.0 map-based format with `${VAR:default}` syntax for defaults. ### Skills diff --git a/README.md b/README.md index e3863ae..c93a2a7 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ CockroachDB plugin for [OpenAI Codex CLI](https://developers.openai.com/codex/). - `cockroachdb-toolbox` (stdio) — self-hosted [MCP Toolbox](https://mcp-toolbox.dev/integrations/cockroachdb/source/) for any cluster (local dev, self-hosted, or Cloud). Codex spawns the Toolbox process. - `cockroachdb-toolbox-http` (SSE) — remote/multi-user Toolbox over HTTP. - **Skills** sourced from [`cockroachlabs/cockroachdb-skills`](https://github.com/cockroachlabs/cockroachdb-skills) — covers query/schema design, observability, security, migrations (MOLT), and cluster lifecycle. -- **Safety hooks** (ship as `hooks.json` + `scripts/`; activation depends on Codex runtime — see Known Limitations): +- **Safety hooks** (ship as `hooks.json` + `scripts/`; activation depends on the Codex runtime version): - `validate-sql.py` (PreToolUse) — blocks `DROP DATABASE`/`TRUNCATE`, warns on `SERIAL`/multi-DDL. - `check-sql-files.py` (PostToolUse) — lints SQL/Go/Java/Python/Ruby/JS/TS files for CockroachDB anti-patterns. @@ -60,6 +60,10 @@ export COCKROACHDB_DATABASE=defaultdb export COCKROACHDB_SSLMODE=disable # local dev only; use 'require' or 'verify-full' otherwise ``` +Codex starts this backend from the installed plugin root, loads the bundled +`./tools.yaml`, and forwards these variables to Toolbox. No manual MCP +registration or path into the plugin cache is needed. + ## Usage Once installed, Codex auto-discovers the MCP tools. Try: @@ -76,10 +80,18 @@ The plugin's skills will be auto-loaded by Codex based on task context. |---|---|---| | `cockroachdb-cloud` | HTTP | Managed CockroachDB Cloud MCP. Requires `COCKROACHDB_CLUSTER_ID`. Zero local install. | | `cockroachdb-toolbox` | stdio | Self-hosted Toolbox against any cluster (local dev, self-hosted, or Cloud). Codex spawns the process. Read-only by default; enable writes via `tools.yaml`. | -| `cockroachdb-toolbox-http` | HTTP/SSE | Remote/multi-user Toolbox deployments. Run `toolbox --config tools.yaml` separately. | +| `cockroachdb-toolbox-http` | HTTP/SSE | Remote/multi-user Toolbox deployments. Client-only; start Toolbox separately. | Enable/disable per-backend via Codex's MCP toggle UI. +The `cockroachdb-toolbox-http` entry only connects to +`http://127.0.0.1:5000/mcp`; it does not start a server. Before enabling it, +start Toolbox independently from a directory containing your Toolbox config: + +```bash +toolbox --config tools.yaml +``` + ## Troubleshooting **`toolbox: command not found`** — install [MCP Toolbox](https://mcp-toolbox.dev/documentation/introduction/#install-toolbox) (Homebrew, binary download, or container image). @@ -88,6 +100,12 @@ Enable/disable per-backend via Codex's MCP toggle UI. **Skills not appearing** — verify the installed plugin cache contains skills: `find ~/.codex/plugins/cache/cockroachdb-codex-plugin/cockroachdb/*/skills -name SKILL.md | wc -l`. +**Toolbox uses an unexpected config path** — check `codex mcp list` for a +manually registered `cockroachdb-toolbox` server. A user-level server with the +same name can shadow the plugin-provided server. The plugin itself uses its +installed, bundled `tools.yaml`; it does not require an absolute checkout or +versioned cache path. + **`SSL error: certificate verify failed` or `node is running secure mode, SSL connection required`** — your cluster runs in secure mode. Set: ```bash @@ -101,27 +119,6 @@ Then extend `tools.yaml` `queryParams:` block with `sslrootcert: ${COCKROACHDB_S For a quick local dev cluster, start one in insecure mode: `cockroach start-single-node --insecure --listen-addr=localhost:26257 &` and use `COCKROACHDB_SSLMODE=disable`. -## Known limitations (Codex 0.134.0) - -Two Codex runtime gaps affect this plugin until upstream support lands: - -1. **Plugin-bundled MCP servers don't get path/env interpolation.** `${PLUGIN_ROOT}` in `.mcp.json` args is passed through literally, and `${VAR}` references in the `env:` block are not expanded. Codex also doesn't inherit `COCKROACHDB_*` env from your shell into spawned MCP processes. Workaround below. -2. **Plugin-bundled `hooks.json` doesn't fire.** Both PreToolUse and PostToolUse hooks are recognized at install time but don't execute against tool calls in 0.134.0. The hooks ship for forward-compatibility. - -The HTTP backends (`cockroachdb-toolbox-http`, `cockroachdb-cloud`) are not affected by #1. - -**Workaround for #1** — register the toolbox MCP manually with absolute paths and concrete env values: - -```bash -codex mcp add cockroachdb-toolbox \ - --env COCKROACHDB_HOST=localhost \ - --env COCKROACHDB_PORT=26257 \ - --env COCKROACHDB_USER=root \ - --env COCKROACHDB_DATABASE=defaultdb \ - --env COCKROACHDB_SSLMODE=disable \ - -- toolbox --config ~/.codex/plugins/cache/cockroachdb-codex-plugin/cockroachdb/0.1.0/tools.yaml --stdio -``` - ## Contributing See [`CONTRIBUTING.md`](./CONTRIBUTING.md). Skills are sourced from [`cockroachlabs/cockroachdb-skills`](https://github.com/cockroachlabs/cockroachdb-skills) — open skill PRs there, not in this repo. diff --git a/plugins/cockroachdb/.mcp.json b/plugins/cockroachdb/.mcp.json index 45ad91b..0947956 100644 --- a/plugins/cockroachdb/.mcp.json +++ b/plugins/cockroachdb/.mcp.json @@ -3,21 +3,22 @@ "cockroachdb-cloud": { "type": "http", "url": "https://cockroachlabs.cloud/mcp", - "headers": { - "mcp-cluster-id": "${COCKROACHDB_CLUSTER_ID}" + "env_http_headers": { + "mcp-cluster-id": "COCKROACHDB_CLUSTER_ID" } }, "cockroachdb-toolbox": { "command": "toolbox", "args": ["--config", "./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}" - } + "cwd": ".", + "env_vars": [ + "COCKROACHDB_HOST", + "COCKROACHDB_PORT", + "COCKROACHDB_USER", + "COCKROACHDB_PASSWORD", + "COCKROACHDB_DATABASE", + "COCKROACHDB_SSLMODE" + ] }, "cockroachdb-toolbox-http": { "type": "http", diff --git a/tests/test_plugin_package.py b/tests/test_plugin_package.py new file mode 100644 index 0000000..2fd5cb2 --- /dev/null +++ b/tests/test_plugin_package.py @@ -0,0 +1,80 @@ +import json +import shutil +import tempfile +import unittest +from pathlib import Path + + +REPO_ROOT = Path(__file__).resolve().parents[1] +PLUGIN_ROOT = REPO_ROOT / "plugins" / "cockroachdb" +MCP_MANIFEST = PLUGIN_ROOT / ".mcp.json" + + +class PluginMcpPackageTest(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + cls.servers = json.loads(MCP_MANIFEST.read_text(encoding="utf-8"))["mcpServers"] + + def test_stdio_toolbox_resolves_bundled_config_from_plugin_root(self) -> None: + server = self.servers["cockroachdb-toolbox"] + + self.assertEqual(server["cwd"], ".") + config_flag = server["args"].index("--config") + config_path = server["args"][config_flag + 1] + relative_config = Path(config_path) + + self.assertEqual(config_path, "./tools.yaml") + self.assertFalse(relative_config.is_absolute()) + self.assertNotIn("..", relative_config.parts) + + with tempfile.TemporaryDirectory() as install_parent: + installed_root = Path(install_parent) / "cockroachdb" + shutil.copytree(PLUGIN_ROOT, installed_root) + installed_manifest = json.loads( + (installed_root / ".mcp.json").read_text(encoding="utf-8") + ) + installed_server = installed_manifest["mcpServers"]["cockroachdb-toolbox"] + installed_config_flag = installed_server["args"].index("--config") + installed_config = installed_server["args"][installed_config_flag + 1] + resolved_config = ( + installed_root / installed_server["cwd"] / installed_config + ).resolve() + + self.assertEqual(resolved_config, (installed_root / "tools.yaml").resolve()) + self.assertTrue(resolved_config.is_file()) + + def test_stdio_toolbox_forwards_connection_environment(self) -> None: + server = self.servers["cockroachdb-toolbox"] + + self.assertNotIn("env", server) + self.assertEqual( + server["env_vars"], + [ + "COCKROACHDB_HOST", + "COCKROACHDB_PORT", + "COCKROACHDB_USER", + "COCKROACHDB_PASSWORD", + "COCKROACHDB_DATABASE", + "COCKROACHDB_SSLMODE", + ], + ) + + def test_cloud_mcp_sources_cluster_header_from_environment(self) -> None: + server = self.servers["cockroachdb-cloud"] + + self.assertNotIn("headers", server) + self.assertEqual( + server["env_http_headers"], + {"mcp-cluster-id": "COCKROACHDB_CLUSTER_ID"}, + ) + + def test_http_toolbox_remains_an_independent_client_connection(self) -> None: + server = self.servers["cockroachdb-toolbox-http"] + + self.assertEqual(server["type"], "http") + self.assertEqual(server["url"], "http://127.0.0.1:5000/mcp") + self.assertNotIn("command", server) + + +if __name__ == "__main__": + unittest.main()