diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json
new file mode 100644
index 0000000..609f6e4
--- /dev/null
+++ b/.claude-plugin/marketplace.json
@@ -0,0 +1,14 @@
+{
+ "name": "topicgate",
+ "owner": {
+ "name": "Dumdart"
+ },
+ "plugins": [
+ {
+ "name": "topicgate",
+ "source": "./topicgate-plugin",
+ "description": "Secure local access to MQTT state through TopicGate",
+ "version": "1.1.0"
+ }
+ ]
+}
diff --git a/.github/workflows/cd.yaml b/.github/workflows/cd.yaml
new file mode 100644
index 0000000..e7498e7
--- /dev/null
+++ b/.github/workflows/cd.yaml
@@ -0,0 +1,107 @@
+name: Release
+
+on:
+ push:
+ tags:
+ - "v*"
+
+jobs:
+ ci:
+ uses: ./.github/workflows/ci.yaml
+
+ release-build:
+ runs-on: ubuntu-latest
+ needs:
+ - ci
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Verify release versions
+ env:
+ RELEASE_TAG: ${{ github.ref_name }}
+ run: python -m release.run_cd
+
+ - uses: actions/setup-python@v5
+ with:
+ python-version: "3.x"
+
+ - name: Build release distributions
+ run: |
+ python -m pip install --upgrade build
+ python -m build
+
+ - name: Upload distributions
+ uses: actions/upload-artifact@v4
+ with:
+ name: release-dists
+ path: dist/
+
+ pypi-publish:
+ runs-on: ubuntu-latest
+ needs:
+ - release-build
+ permissions:
+ id-token: write
+
+ environment:
+ name: pypi
+
+ steps:
+ - name: Retrieve release distributions
+ uses: actions/download-artifact@v4
+ with:
+ name: release-dists
+ path: dist/
+
+ - name: Publish release distributions to PyPI
+ uses: pypa/gh-action-pypi-publish@release/v1
+
+ mcp-registry-publish:
+ needs: pypi-publish
+ runs-on: ubuntu-latest
+ permissions:
+ id-token: write
+ contents: read
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Install MCP Publisher
+ run: |
+ curl -L \
+ "https://github.com/modelcontextprotocol/registry/releases/latest/download/mcp-publisher_linux_amd64.tar.gz" \
+ | tar xz
+ sudo mv mcp-publisher /usr/local/bin/
+
+ - name: Validate MCP server metadata
+ run: mcp-publisher validate server.json
+
+ - name: Authenticate with MCP Registry
+ run: mcp-publisher login github-oidc
+
+ - name: Publish to MCP Registry
+ run: mcp-publisher publish server.json
+
+ github-release:
+ needs:
+ - pypi-publish
+ - mcp-registry-publish
+ runs-on: ubuntu-latest
+ permissions:
+ contents: write
+
+ steps:
+ - name: Retrieve release distributions
+ uses: actions/download-artifact@v4
+ with:
+ name: release-dists
+ path: dist/
+
+ - name: Create GitHub Release
+ env:
+ GH_TOKEN: ${{ github.token }}
+ run: |
+ gh release create "${{ github.ref_name }}" dist/* \
+ --title "TopicGate ${{ github.ref_name }}" \
+ --generate-notes
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index c838013..f174c72 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -3,6 +3,8 @@ name: CI
on:
push:
pull_request:
+ workflow_call:
+
permissions:
contents: read
@@ -12,6 +14,8 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
+ - name: Verify release versions
+ run: python -m release.run_ci
- name: Set up Python
uses: actions/setup-python@v5
with:
diff --git a/README.md b/README.md
index 925f1c0..3d14e1f 100644
--- a/README.md
+++ b/README.md
@@ -7,7 +7,7 @@
Get started ·
- Use it from Codex ·
+ Connect an agent ·
Understand observations ·
Desktop workflow
@@ -34,39 +34,24 @@ TopicGate supports exact MQTT paths and the standard `+` and `#` wildcard filter
### 1. Install
-TopicGate requires Python 3.11+ and access to an MQTT 5-compatible broker. It is currently installed from a source checkout; package distribution is planned but not yet published.
+TopicGate requires Python 3.11+ and access to an MQTT 5-compatible broker. Install the released package with uv:
> [!IMPORTANT]
-> **Windows is the only validated platform today.** The macOS and Linux paths, desktop behaviour, and credential-store integrations have not been tested end to end. Codex is the only MCP host and plugin harness validated so far; other MCP clients may work, but are not currently supported installation paths.
-
-The Windows development installation is:
-
-```powershell
-git clone https://github.com/Dumdart/TopicGate.git
-cd TopicGate
-python -m pip install -e .
-```
-
-Then install mcp in readonly mode (codex):
+> **Windows is the only validated platform today.** The macOS and Linux paths, desktop behaviour, and credential-store integrations have not been tested end to end. TopicGate uses `keyring` for the operating-system credential store; those integrations have not yet been tested across platforms.
```powershell
-codex mcp add topicgate -- python -m topicgate
-```
-
-or with full access:
-
-```powershell
-codex mcp add topicgate -- python -m topicgate --mode control
+uv tool install topicgate
+topicgate-gui
```
-Install Plugin (codex):
+Alternatively, install it with pip:
```powershell
-codex plugin marketplace add .
-codex plugin add topicgate@topicgate
+python -m pip install topicgate
+topicgate-gui
```
-For an unvalidated macOS or Linux source checkout, activate the environment with `source .venv/bin/activate`. Install the optional MCP Apps dashboard with `uv sync --extra apps` or `python -m pip install -e ".[apps]"`.
+For upgrades, uninstallation, backups, and recovery, see [Installation recovery and upgrades](docs/install/UPGRADE_AND_RECOVERY.md).
### 2. Configure and observe
@@ -111,23 +96,21 @@ Use the absolute path to `topicgate` or `topicgate.exe` if the environment is no
fastmcp call --command topicgate --target list_brokers --json
```
-## Use it from Codex
+## Connect an agent
-TopicGate includes a Codex plugin with eight focused skills for setting up the connection, inspecting the current MQTT state, working with subscriptions, and safely refreshing or publishing only when control mode is enabled. Codex is the only plugin host tested by this project.
+TopicGate can be connected to an MCP-capable agent after you have configured a broker and observed data in the desktop application. The server is read-only by default; enable control mode only in a host you trust to change broker connections, subscriptions, or device state.
-
-
-
+Agent setup differs by host. Use the installation guide for yours:
-Install the bundled `topicgate-plugin` through your Codex plugin marketplace, enable it, and start a new thread. The plugin's default MCP configuration uses `topicgate --mode read-only` and the same platform application-data directory as TopicGate Desktop, so it can inspect the profiles, subscriptions, and observations configured there. If the executable is not on `PATH`, use TopicGate Desktop's MCP setup page to copy a configuration with the resolved absolute path.
+| Agent | Setup guide | Support |
+| --- | --- | --- |
+| Codex | [Install TopicGate for Codex](docs/install/CODEX.md) | Plugin and MCP server validated. |
+| Claude Code | [Install TopicGate for Claude Code](docs/install/CLAUDE_CODE.md) | Plugin package and MCP configuration; runtime validation pending. |
+| VS Code / GitHub Copilot | [Install TopicGate for GitHub Copilot](docs/install/VSCODE_COPILOT.md) | Agent Plugins 1.0 package; runtime validation pending. |
+| Cursor | [Install TopicGate for Cursor](docs/install/CURSOR.md) | Agent Plugins 1.0 package; runtime validation pending. |
-Try one of these prompts:
+If `topicgate` is not on the agent host's `PATH`, use the MCP setup page in TopicGate Desktop to copy a configuration with the resolved executable path.
-```text
-Help me set up TopicGate.
-Inspect my TopicGate MQTT state.
-Show the latest observed MQTT values.
-```
## How observations work
@@ -184,19 +167,23 @@ Set `TOPICGATE_DATA_DIR` to use a specific directory. The database contains brok
Use **File > Stored observations** in the desktop app to review cache use and retention. Deleting `topicgate.db` permanently removes saved profiles, subscriptions, settings, and observations unless you have backed it up first.
-## Distribution and onboarding roadmap
+## Development
-Before TopicGate is released as a package, the project plans to:
+For a source checkout:
-1. Publish platform-specific installation guidance and decide whether Windows should also receive an installer or packaged executable.
-2. Validate Windows Credential Locker, macOS Keychain, and Linux secret-service behaviour.
-3. Add backup and restore guidance plus migration and release notes.
-4. Publish the Codex plugin only against a released TopicGate version.
-5. Document troubleshooting for PATH, stdio launch, credentials, broker TLS, and dashboard dependencies.
+```powershell
+git clone https://github.com/Dumdart/TopicGate.git
+cd TopicGate
+uv sync --extra apps --extra test
+uv run topicgate-gui
+```
-The release goal is that users no longer need an editable source checkout, desktop and plugin installation are reproducible, and upgrade and recovery procedures are documented.
+Alternatively, use an editable pip install:
-## Development
+```powershell
+python -m pip install -e ".[apps,test]"
+topicgate-gui
+```
Run the full test suite before submitting changes:
diff --git a/docs/install/CLAUDE_CODE.md b/docs/install/CLAUDE_CODE.md
new file mode 100644
index 0000000..4f1f0fd
--- /dev/null
+++ b/docs/install/CLAUDE_CODE.md
@@ -0,0 +1,74 @@
+# Install TopicGate for Claude Code
+
+Claude Code can use TopicGate either through the bundled plugin or as a standalone stdio MCP server. The plugin is the recommended setup because it also installs the focused TopicGate skills.
+
+## Install TopicGate
+
+TopicGate's official MCP Registry identifier is `io.github.Dumdart/topicgate`. The registry provides package metadata rather than installing packages; after a release has been published, registry-aware clients can discover TopicGate with that identifier. Install the released PyPI package before connecting an MCP host:
+
+```powershell
+uv tool install topicgate
+topicgate-gui
+```
+
+Alternatively:
+
+```powershell
+python -m pip install topicgate
+topicgate-gui
+```
+
+Before connecting Claude Code, install TopicGate and use TopicGate Desktop to configure a broker, add subscriptions, and observe data. The MCP server reads the same local application-data directory as the desktop app.
+
+## Development: install from a local checkout
+
+From the TopicGate repository root, register the current directory as a local marketplace and install the plugin:
+
+```powershell
+claude plugin marketplace add .
+claude plugin install topicgate@topicgate
+```
+
+The equivalent commands inside an interactive Claude Code session are:
+
+```text
+/plugin marketplace add .
+/plugin install topicgate@topicgate
+```
+
+To load the plugin for one development session without installing it, use:
+
+```powershell
+claude --plugin-dir ./topicgate-plugin
+```
+
+The plugin automatically loads its skills and starts TopicGate in read-only mode. Restart Claude Code and open a new session after installing or updating the plugin.
+
+## Install from GitHub
+
+After the marketplace has been published, replace the local path with the GitHub repository:
+
+```powershell
+claude plugin marketplace add Dumdart/TopicGate
+claude plugin install topicgate@topicgate
+```
+
+## Install only the MCP server
+
+Use this when you do not want the plugin skills:
+
+```powershell
+claude mcp add topicgate -- topicgate --mode read-only
+```
+
+If `topicgate` is not on `PATH`, replace it with its absolute path. TopicGate Desktop's MCP setup page can copy a configuration with the resolved executable path.
+
+## Control mode
+
+Control mode can connect or disconnect brokers, change subscriptions, refresh observations, and publish MQTT messages. Use it only in a trusted environment:
+
+```powershell
+claude mcp add topicgate-control -- topicgate --mode control
+```
+
+Confirm the broker, topic, payload, and encoding before allowing a publish operation.
diff --git a/docs/install/CODEX.md b/docs/install/CODEX.md
new file mode 100644
index 0000000..4ffbc3e
--- /dev/null
+++ b/docs/install/CODEX.md
@@ -0,0 +1,64 @@
+# Install TopicGate for Codex
+
+Codex can use TopicGate either as a standalone MCP server or through the bundled plugin. The plugin is the recommended setup because it also provides focused TopicGate skills.
+
+## Install TopicGate
+
+TopicGate's official MCP Registry identifier is `io.github.Dumdart/topicgate`. The registry provides package metadata rather than installing packages; after a release has been published, registry-aware clients can discover TopicGate with that identifier. Install the released PyPI package before connecting an MCP host:
+
+```powershell
+uv tool install topicgate
+topicgate-gui
+```
+
+Alternatively:
+
+```powershell
+python -m pip install topicgate
+topicgate-gui
+```
+
+Before connecting Codex, install TopicGate and use TopicGate Desktop to configure a broker, add subscriptions, and observe data. The MCP server reads the same local application-data directory as the desktop app.
+
+## Development: install the plugin from a local checkout
+
+From a TopicGate source checkout, add the checkout as a Codex plugin marketplace and install the bundled plugin:
+
+```powershell
+codex plugin marketplace add .
+codex plugin add topicgate@topicgate
+```
+
+Enable the plugin and start a new Codex thread. Its default MCP configuration runs `topicgate --mode read-only`.
+
+
+
+
+
+Try one of these prompts:
+
+```text
+Help me set up TopicGate.
+Inspect my TopicGate MQTT state.
+Show the latest observed MQTT values.
+```
+
+## Install only the MCP server
+
+Use this when you do not want the plugin skills:
+
+```powershell
+codex mcp add topicgate -- topicgate --mode read-only
+```
+
+If the executable is not on `PATH`, replace `topicgate` with its absolute path. TopicGate Desktop's MCP setup page can copy a configuration with that path already resolved.
+
+## Control mode
+
+Control mode exposes operations that can connect or disconnect brokers, change subscriptions, refresh observations, and publish MQTT messages. Add it only to a trusted Codex environment:
+
+```powershell
+codex mcp add topicgate-control -- topicgate --mode control
+```
+
+Confirm the broker, topic, payload, and encoding before allowing a publish operation.
diff --git a/docs/install/CURSOR.md b/docs/install/CURSOR.md
new file mode 100644
index 0000000..a0a47c3
--- /dev/null
+++ b/docs/install/CURSOR.md
@@ -0,0 +1,71 @@
+# Install TopicGate for Cursor
+
+Cursor loads TopicGate through the portable Agent Plugins 1.0 package. The plugin installs eight TopicGate skills and starts the local MCP server in read-only mode.
+
+## Install TopicGate
+
+TopicGate's official MCP Registry identifier is `io.github.Dumdart/topicgate`. The registry provides package metadata rather than installing packages; after a release has been published, registry-aware clients can discover TopicGate with that identifier. Install the released PyPI package before connecting an MCP host:
+
+```powershell
+uv tool install topicgate
+topicgate-gui
+```
+
+Alternatively:
+
+```powershell
+python -m pip install topicgate
+topicgate-gui
+```
+
+Before installing the plugin, install TopicGate and use TopicGate Desktop to configure a broker, add subscriptions, and observe data. The MCP server reads the same local application-data directory as the desktop app.
+
+## Development: test or install the plugin locally
+
+From a TopicGate source checkout, copy the `topicgate-plugin` directory to Cursor's local plugin directory:
+
+```powershell
+$destination = Join-Path $env:USERPROFILE ".cursor\plugins\local\topicgate"
+New-Item -ItemType Directory -Force -Path $destination
+Copy-Item -Recurse -Force -Path ".\topicgate-plugin\*" -Destination $destination
+```
+
+Restart Cursor or run **Developer: Reload Window**. Open **Customize** and confirm that the TopicGate skills and MCP server are enabled. Skills appear under **Agent Decides** and can also be invoked with `/skill-name`.
+
+Cursor caches the copied plugin. Repeat the copy and reload steps after changing the local plugin source.
+
+Public one-click installation requires TopicGate to be submitted to and approved for the Cursor Marketplace. Until then, use the local installation above or distribute it through a Cursor team marketplace.
+
+## Install only the MCP server
+
+Use this project configuration when you want the MCP tools without the plugin skills. Create `.cursor/mcp.json` in the project:
+
+```json
+{
+ "mcpServers": {
+ "topicgate": {
+ "command": "topicgate",
+ "args": ["--mode", "read-only"]
+ }
+ }
+}
+```
+
+If `topicgate` is not on `PATH`, replace it with its absolute path. TopicGate Desktop's MCP setup page can copy a configuration with the resolved executable path.
+
+## Control mode
+
+The installed plugin intentionally uses read-only mode. To expose operations that connect or disconnect brokers, change subscriptions, refresh observations, or publish MQTT messages, configure a separate project MCP server:
+
+```json
+{
+ "mcpServers": {
+ "topicgate-control": {
+ "command": "topicgate",
+ "args": ["--mode", "control"]
+ }
+ }
+}
+```
+
+Use control mode only in a trusted project. Confirm the broker, topic, payload, and encoding before allowing a publish operation.
diff --git a/docs/install/UPGRADE_AND_RECOVERY.md b/docs/install/UPGRADE_AND_RECOVERY.md
new file mode 100644
index 0000000..9664523
--- /dev/null
+++ b/docs/install/UPGRADE_AND_RECOVERY.md
@@ -0,0 +1,101 @@
+# TopicGate installation recovery and upgrades
+
+## Upgrade
+
+Upgrade an installation managed by uv:
+
+```powershell
+uv tool upgrade topicgate
+```
+
+Or upgrade a pip installation:
+
+```powershell
+python -m pip install --upgrade topicgate
+```
+
+Start `topicgate-gui` or `topicgate` after upgrading. TopicGate automatically updates its local database before it opens it.
+
+## Uninstall
+
+Remove a uv-managed installation:
+
+```powershell
+uv tool uninstall topicgate
+```
+
+Or remove a pip installation:
+
+```powershell
+python -m pip uninstall topicgate
+```
+
+Uninstalling the package does not remove local data or broker passwords. Remove or update any MCP host configuration that still starts `topicgate` before uninstalling.
+
+## `topicgate` is not on `PATH`
+
+Open a new terminal after installation, then check whether the executable is available:
+
+```powershell
+Get-Command topicgate
+```
+
+If it is still unavailable, use the recovery path for the installation method you chose:
+
+For a uv tool installation, add its executable directory to `PATH` and reopen the terminal:
+
+```powershell
+uv tool update-shell
+uv tool dir --bin
+```
+
+For a pip installation, run the server through that Python interpreter:
+
+```powershell
+python -m topicgate --mode read-only
+```
+
+For a host configuration, use the absolute executable path copied from TopicGate Desktop's MCP setup page. That page also includes the data-directory environment variable needed to share the desktop application's data.
+
+## Data and backups
+
+TopicGate stores profiles, subscriptions, settings, and observed MQTT state in `topicgate.db`. Passwords are stored separately in the operating-system credential store.
+
+| Platform | Default data directory |
+| --- | --- |
+| Windows | `%LOCALAPPDATA%\Dumdart\TopicGate` |
+| Linux | `~/.local/share/TopicGate` |
+| macOS | `~/Library/Application Support/TopicGate` |
+
+Set `TOPICGATE_DATA_DIR` to use a different directory.
+
+To back up TopicGate, stop the desktop app and every MCP host that runs TopicGate, then copy the entire data directory to a safe location. Copying the whole directory preserves `topicgate.db` and any SQLite WAL sidecar files. On Windows, for example:
+
+```powershell
+Copy-Item -Recurse -Force "$env:LOCALAPPDATA\Dumdart\TopicGate" "$env:USERPROFILE\Documents\TopicGate-backup"
+```
+
+Restore a backup only while TopicGate is stopped.
+
+## Database migrations
+
+TopicGate runs database migrations automatically when the desktop application or MCP server starts. A concurrent desktop and MCP startup is coordinated so only one migration runs at a time. You do not need to run Alembic manually.
+
+Back up the data directory before a major upgrade. If startup reports a migration failure, stop every TopicGate process, copy the data directory, and retry once. Do not delete the database or operating-system credentials as the first recovery step.
+
+## Safe reset
+
+To reset profiles, settings, subscriptions, and observed state while preserving broker passwords:
+
+1. Stop TopicGate Desktop and every MCP host using TopicGate.
+2. Back up the entire data directory.
+3. Rename the data directory instead of deleting it.
+4. Start `topicgate-gui`; TopicGate creates a new database and fresh local profiles.
+
+For example, on Windows:
+
+```powershell
+Rename-Item "$env:LOCALAPPDATA\Dumdart\TopicGate" "TopicGate.backup-20260822"
+```
+
+The reset does not delete passwords because they are not stored in the database. The old credential entries remain unused because the new profiles have new IDs. To intentionally remove a broker password, delete its profile in TopicGate Desktop before resetting; profile deletion removes the matching operating-system credential. Do not manually clear `TopicGate MQTT` credential-store entries unless you intend to remove those passwords permanently.
diff --git a/docs/install/VSCODE_COPILOT.md b/docs/install/VSCODE_COPILOT.md
new file mode 100644
index 0000000..ba17341
--- /dev/null
+++ b/docs/install/VSCODE_COPILOT.md
@@ -0,0 +1,91 @@
+# Install TopicGate for VS Code and GitHub Copilot
+
+TopicGate is packaged as an Agent Plugins 1.0 plugin for GitHub Copilot in VS Code and GitHub Copilot CLI. The plugin installs eight TopicGate skills and starts the local MCP server in read-only mode.
+
+## Install TopicGate
+
+TopicGate's official MCP Registry identifier is `io.github.Dumdart/topicgate`. The registry provides package metadata rather than installing packages; after a release has been published, registry-aware clients can discover TopicGate with that identifier. Install the released PyPI package before connecting an MCP host:
+
+```powershell
+uv tool install topicgate
+topicgate-gui
+```
+
+Alternatively:
+
+```powershell
+python -m pip install topicgate
+topicgate-gui
+```
+
+Before installing the plugin, install TopicGate and use TopicGate Desktop to configure a broker, add subscriptions, and observe data. The MCP server reads the same local application-data directory as the desktop app.
+
+## Install in VS Code
+
+Agent plugins require a current VS Code release with GitHub Copilot and the `chat.plugins.enabled` setting enabled.
+
+Add the TopicGate marketplace to your VS Code `settings.json`:
+
+```json
+"chat.plugins.marketplaces": [
+ "Dumdart/TopicGate"
+]
+```
+
+Open the Extensions view, search for `@agentPlugins`, select TopicGate, and choose **Install**. You can also run **Chat: Open Customizations**, open the **Plugins** tab, and install TopicGate from the marketplace there.
+
+After installation, start a new chat. The skills appear in **Chat: Configure Skills**, and the `topicgate` server appears under **MCP: List Servers**.
+
+## Install with GitHub Copilot CLI
+
+For local development, from the TopicGate repository root, register the current directory as a local marketplace and install the plugin:
+
+```powershell
+copilot plugin marketplace add .
+copilot plugin install topicgate@topicgate
+```
+
+VS Code automatically discovers plugins installed by GitHub Copilot CLI. Start a new Copilot session and use `/skills list` to confirm that the TopicGate skills loaded.
+
+To install from GitHub after the marketplace has been published, use `Dumdart/TopicGate` instead of `.`:
+
+```powershell
+copilot plugin marketplace add Dumdart/TopicGate
+copilot plugin install topicgate@topicgate
+```
+
+## Install only the MCP server
+
+Use this workspace configuration when you want the MCP tools without the plugin skills. Create `.vscode/mcp.json` in the workspace:
+
+```json
+{
+ "servers": {
+ "topicgate": {
+ "type": "stdio",
+ "command": "topicgate",
+ "args": ["--mode", "read-only"]
+ }
+ }
+}
+```
+
+If `topicgate` is not on `PATH`, replace it with its absolute path. TopicGate Desktop's MCP setup page can copy a configuration with the resolved executable path.
+
+## Control mode
+
+The installed plugin intentionally uses read-only mode. To expose operations that connect or disconnect brokers, change subscriptions, refresh observations, or publish MQTT messages, configure a separate workspace MCP server:
+
+```json
+{
+ "servers": {
+ "topicgate-control": {
+ "type": "stdio",
+ "command": "topicgate",
+ "args": ["--mode", "control"]
+ }
+ }
+}
+```
+
+Use control mode only in a trusted workspace. Confirm the broker, topic, payload, and encoding before allowing a publish operation.
diff --git a/pyproject.toml b/pyproject.toml
index f491490..39760ce 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "topicgate"
-version = "1.0.0"
+version = "1.1.0"
description = "Secure local access to your MQTT topics"
readme = "README.md"
license = "MIT"
diff --git a/release/run_cd.py b/release/run_cd.py
new file mode 100644
index 0000000..5c7eeb0
--- /dev/null
+++ b/release/run_cd.py
@@ -0,0 +1,15 @@
+
+import os
+
+from release.verify_release_versions import get_versions, verify
+
+def run_cd():
+ expected = os.environ["RELEASE_TAG"].removeprefix("v")
+ versions = get_versions(expected)
+
+
+ verify(versions)
+ print(f"Verified release version {expected}")
+
+if __name__ == "__main__":
+ run_cd()
diff --git a/release/run_ci.py b/release/run_ci.py
new file mode 100644
index 0000000..638f053
--- /dev/null
+++ b/release/run_ci.py
@@ -0,0 +1,12 @@
+from release.verify_release_versions import get_versions, verify
+
+
+def run_ci():
+ versions = get_versions()
+
+ verify(versions)
+ print(f"Verified release versions: {versions}")
+
+
+if __name__ == "__main__":
+ run_ci()
diff --git a/release/verify_release_versions.py b/release/verify_release_versions.py
new file mode 100644
index 0000000..a09f0ad
--- /dev/null
+++ b/release/verify_release_versions.py
@@ -0,0 +1,83 @@
+import json
+import os
+import tomllib
+from pathlib import Path
+
+
+def json_base_version(plugin) -> str:
+ plugin_version = plugin["version"]
+ return plugin_version.split("+", 1)[0]
+
+
+def get_versions(expected: str | None = None) -> dict[str, str]:
+ pyproject = tomllib.loads(
+ Path("pyproject.toml").read_text(encoding="utf-8")
+ )
+
+ plugin = json.loads(
+ Path("topicgate-plugin/plugin.json")
+ .read_text(encoding="utf-8")
+ )
+
+ codex_plugin = json.loads(
+ Path("topicgate-plugin/.codex-plugin/plugin.json")
+ .read_text(encoding="utf-8")
+ )
+
+ claude_plugin = json.loads(
+ Path("topicgate-plugin/.claude-plugin/plugin.json")
+ .read_text(encoding="utf-8")
+ )
+
+ marketplace = json.loads(
+ Path(".claude-plugin/marketplace.json")
+ .read_text(encoding="utf-8")
+ )
+ marketplace_plugin = next(
+ (
+ candidate
+ for candidate in marketplace["plugins"]
+ if candidate["name"] == plugin["name"]
+ ),
+ None,
+ )
+ if marketplace_plugin is None:
+ raise SystemExit(
+ "Release version mismatch:\n"
+ f" Claude marketplace: missing plugin {plugin['name']}"
+ )
+
+ server = json.loads(
+ Path("server.json")
+ .read_text(encoding="utf-8")
+ )
+
+ package_version = pyproject["project"]["version"]
+
+ plugin_base = json_base_version(plugin)
+ codex_plugin_base = json_base_version(codex_plugin)
+ claude_plugin_base = json_base_version(claude_plugin)
+ marketplace_plugin_base = json_base_version(marketplace_plugin)
+ serverbase = json_base_version(server)
+
+ versions = {
+ "Python package": package_version,
+ "Plugin base version": plugin_base,
+ "Codex plugin base version": codex_plugin_base,
+ "Claude plugin base version": claude_plugin_base,
+ "Claude marketplace base version": marketplace_plugin_base,
+ "Server base version": serverbase,
+ }
+
+ if expected:
+ versions["Git tag"] = expected
+
+ return versions
+
+def verify(versions: dict[str, str]) -> None:
+ if len(set(versions.values())) != 1:
+ raise SystemExit(
+ "Release version mismatch:\n"
+ + "\n".join(f" {name}: {version}"
+ for name, version in versions.items())
+ )
diff --git a/server.json b/server.json
new file mode 100644
index 0000000..e23a836
--- /dev/null
+++ b/server.json
@@ -0,0 +1,17 @@
+ {
+ "$schema": "https://static.modelcontextprotocol.io/schemas/2025-10-17/server.schema.json",
+ "name": "io.github.Dumdart/topicgate",
+ "title": "TopicGate",
+ "description": "Secure local access to MQTT topics through MCP.",
+ "version": "1.1.0",
+ "packages": [
+ {
+ "registryType": "pypi",
+ "identifier": "topicgate",
+ "version": "1.1.0",
+ "transport": {
+ "type": "stdio"
+ }
+ }
+ ]
+ }
diff --git a/tests/test_plugin_bundle.py b/tests/test_plugin_bundle.py
index a9345f8..65caa0a 100644
--- a/tests/test_plugin_bundle.py
+++ b/tests/test_plugin_bundle.py
@@ -42,6 +42,61 @@ def test_plugin_bundle_matches_codex_ingestion_contract() -> None:
assert asset.is_file()
+def test_plugin_bundle_matches_claude_code_ingestion_contract() -> None:
+ manifest = json.loads(
+ (PLUGIN_ROOT / ".claude-plugin" / "plugin.json").read_text(
+ encoding="utf-8"
+ )
+ )
+ marketplace = json.loads(
+ (REPOSITORY_ROOT / ".claude-plugin" / "marketplace.json").read_text(
+ encoding="utf-8"
+ )
+ )
+ marketplace_plugin = next(
+ plugin for plugin in marketplace["plugins"] if plugin["name"] == manifest["name"]
+ )
+
+ assert marketplace_plugin["source"] == "./topicgate-plugin"
+ assert marketplace_plugin["version"] == manifest["version"]
+ assert manifest["author"]["name"]
+ assert manifest["skills"] == "./skills/"
+ assert manifest["mcpServers"] == "./.mcp.json"
+ assert (PLUGIN_ROOT / manifest["skills"].removeprefix("./")).is_dir()
+ assert (PLUGIN_ROOT / manifest["mcpServers"].removeprefix("./")).is_file()
+
+
+def test_plugin_bundle_matches_copilot_and_cursor_agent_plugins_contract() -> None:
+ manifest = json.loads(
+ (PLUGIN_ROOT / "plugin.json").read_text(encoding="utf-8")
+ )
+ mcp_config = json.loads(
+ (PLUGIN_ROOT / "mcp.json").read_text(encoding="utf-8")
+ )
+ marketplace = json.loads(
+ (REPOSITORY_ROOT / ".claude-plugin" / "marketplace.json").read_text(
+ encoding="utf-8"
+ )
+ )
+ marketplace_plugin = next(
+ plugin for plugin in marketplace["plugins"] if plugin["name"] == manifest["name"]
+ )
+ server_config = mcp_config["mcpServers"]["topicgate"]
+
+ assert manifest["$schema"] == (
+ "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json"
+ )
+ assert marketplace_plugin["source"] == "./topicgate-plugin"
+ assert marketplace_plugin["version"] == manifest["version"]
+ assert (PLUGIN_ROOT / "skills").is_dir()
+ assert mcp_config["$schema"] == (
+ "https://agent-plugins.org/schemas/1.0.0/mcp.schema.json"
+ )
+ assert server_config["type"] == "stdio"
+ assert server_config["command"] == "topicgate"
+ assert server_config["args"] == ["--mode", "read-only"]
+
+
def test_plugin_skills_have_valid_frontmatter() -> None:
skill_files = sorted((PLUGIN_ROOT / "skills").glob("*/SKILL.md"))
diff --git a/topicgate-plugin/.claude-plugin/plugin.json b/topicgate-plugin/.claude-plugin/plugin.json
new file mode 100644
index 0000000..095cac1
--- /dev/null
+++ b/topicgate-plugin/.claude-plugin/plugin.json
@@ -0,0 +1,19 @@
+{
+ "name": "topicgate",
+ "version": "1.1.0",
+ "description": "Secure local access to your MQTT topics through TopicGate MCP",
+ "author": {
+ "name": "Dumdart",
+ "url": "https://github.com/Dumdart"
+ },
+ "homepage": "https://github.com/Dumdart/TopicGate",
+ "repository": "https://github.com/Dumdart/TopicGate",
+ "license": "MIT",
+ "keywords": [
+ "mqtt",
+ "mcp",
+ "iot"
+ ],
+ "skills": "./skills/",
+ "mcpServers": "./.mcp.json"
+}
diff --git a/topicgate-plugin/.codex-plugin/plugin.json b/topicgate-plugin/.codex-plugin/plugin.json
index 6931fb6..0e51a5c 100644
--- a/topicgate-plugin/.codex-plugin/plugin.json
+++ b/topicgate-plugin/.codex-plugin/plugin.json
@@ -1,6 +1,6 @@
{
"name": "topicgate",
- "version": "1.0.0+codex.20260820100329",
+ "version": "1.1.0+codex.20260820100329",
"description": "Secure local access to your MQTT topics via TopicGate MCP",
"author": {
"name": "Dumdart",
diff --git a/topicgate-plugin/CONTRACT.md b/topicgate-plugin/CONTRACT.md
index 5079427..0b158fc 100644
--- a/topicgate-plugin/CONTRACT.md
+++ b/topicgate-plugin/CONTRACT.md
@@ -1,12 +1,14 @@
# TopicGate plugin contract
This plugin supports TopicGate 1.x and the TopicGate MCP contract `1.0`.
-It is tested with FastMCP 3.4.7 and the Codex plugin ingestion contract used by
-the repository test suite.
+It is tested with FastMCP 3.4.7 and the Codex, Claude Code, GitHub Copilot, and
+Cursor-compatible Agent Plugins 1.0 ingestion contracts used by the repository
+test suite.
-The installed `.mcp.json` is read-only. Control mode is a separate, explicit
-configuration in `.mcp-control.json`; copy it only when connection changes,
-subscription changes, observation refresh, and publishing are intended.
+The installed `.mcp.json` and portable `mcp.json` are read-only. Control mode is a
+separate, explicit configuration in `.mcp-control.json`; copy it only when
+connection changes, subscription changes, observation refresh, and publishing are
+intended.
TopicGate Desktop remains the owner of broker credentials, retention policy,
cache deletion, and other destructive or complex maintenance.
diff --git a/topicgate-plugin/SCENARIOS.md b/topicgate-plugin/SCENARIOS.md
index 7809471..d5fe8b7 100644
--- a/topicgate-plugin/SCENARIOS.md
+++ b/topicgate-plugin/SCENARIOS.md
@@ -152,7 +152,7 @@ expected skill path and the key assertions to check.
- Tells the user to install and verify TopicGate with `python -m pip install
topicgate` followed by `python -m topicgate --help`.
- Explains that `python topicgate` is invalid because the `-m` option is required.
-- Tells the user to refresh the plugin, restart Codex, and open a new thread.
+- Tells the user to refresh the plugin, restart the agent host, and open a new thread.
- Does not attempt to substitute another tool.
---
diff --git a/topicgate-plugin/mcp.json b/topicgate-plugin/mcp.json
new file mode 100644
index 0000000..7826383
--- /dev/null
+++ b/topicgate-plugin/mcp.json
@@ -0,0 +1,13 @@
+{
+ "$schema": "https://agent-plugins.org/schemas/1.0.0/mcp.schema.json",
+ "mcpServers": {
+ "topicgate": {
+ "type": "stdio",
+ "command": "topicgate",
+ "args": [
+ "--mode",
+ "read-only"
+ ]
+ }
+ }
+}
diff --git a/topicgate-plugin/plugin.json b/topicgate-plugin/plugin.json
new file mode 100644
index 0000000..af5cd27
--- /dev/null
+++ b/topicgate-plugin/plugin.json
@@ -0,0 +1,18 @@
+{
+ "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
+ "name": "topicgate",
+ "version": "1.1.0",
+ "description": "Secure local access to your MQTT topics through TopicGate MCP",
+ "author": {
+ "name": "Dumdart",
+ "url": "https://github.com/Dumdart"
+ },
+ "homepage": "https://github.com/Dumdart/TopicGate",
+ "repository": "https://github.com/Dumdart/TopicGate",
+ "license": "MIT",
+ "keywords": [
+ "mqtt",
+ "mcp",
+ "iot"
+ ]
+}
diff --git a/topicgate-plugin/skills/setup-topicgate/SKILL.md b/topicgate-plugin/skills/setup-topicgate/SKILL.md
index 7f6c736..ac39621 100644
--- a/topicgate-plugin/skills/setup-topicgate/SKILL.md
+++ b/topicgate-plugin/skills/setup-topicgate/SKILL.md
@@ -10,7 +10,7 @@ Give the user a short introduction before installation instructions:
- TopicGate is a local MQTT gateway with a desktop application and an MCP server.
- The desktop application manages broker profiles, credentials, subscriptions, and
retained observation settings.
-- The read-only MCP server lets Codex inspect broker health, subscriptions, topics,
+- The read-only MCP server lets the agent inspect broker health, subscriptions, topics,
and the latest MQTT values observed by TopicGate. Those values can be cached,
stale, or partial; they are not authoritative broker history.
@@ -20,8 +20,9 @@ If TopicGate tools are already available, do not suggest reinstalling. Briefly
introduce TopicGate, explain that this plugin uses the local read-only server, and
continue with the user's MQTT request.
-If the tools are unavailable, explain that the Codex plugin supplies skills and MCP
-configuration but requires the TopicGate Python package on the machine running Codex.
+If the tools are unavailable, explain that the TopicGate plugin supplies skills and
+MCP configuration but requires the TopicGate Python package on the machine running
+the agent host.
Do not run an installation command without the user's permission.
## Install and verify
@@ -38,7 +39,7 @@ without relying on a separate scripts directory being on `PATH`. `python topicga
is not a valid substitute; the `-m` option is required.
After verification, tell the user to reinstall or refresh the TopicGate plugin if
-needed, restart Codex, and open a new thread. The portable plugin configuration
+needed, restart the agent host, and open a new thread. The portable plugin configuration
expects the `topicgate` console executable to be on `PATH` and starts:
```console
@@ -46,7 +47,7 @@ topicgate --mode read-only
```
Do not tell the user to launch that blocking stdio command manually during ordinary
-Codex use.
+agent use.
If `topicgate` is not on `PATH`, use TopicGate Desktop's MCP setup page to copy a
configuration containing the resolved absolute executable path. Control mode must
@@ -58,7 +59,7 @@ Once the tools are available:
1. Ask the user to open TopicGate Desktop with `topicgate-gui` and create a broker
profile if none exists.
-2. Use `list_brokers` to confirm that Codex can see the configured profiles.
+2. Use `list_brokers` to confirm that the agent can see the configured profiles.
3. Offer a passive overview using `get_connection_status`, `list_subscriptions`, and
`get_broker_snapshot`.
diff --git a/uv.lock b/uv.lock
index b88ad71..383383e 100644
--- a/uv.lock
+++ b/uv.lock
@@ -1620,7 +1620,7 @@ wheels = [
[[package]]
name = "topicgate"
-version = "1.0.0"
+version = "1.1.0"
source = { editable = "." }
dependencies = [
{ name = "alembic" },