From b3da57de35d4814b465d1807c4412d4f1318cb53 Mon Sep 17 00:00:00 2001
From: Paul Thumfart <158182803+Dumdart@users.noreply.github.com>
Date: Thu, 20 Aug 2026 20:26:52 +0200
Subject: [PATCH 01/11] Plugin: Add cloude code support
---
.claude-plugin/marketplace.json | 14 +++++++++++
tests/test_plugin_bundle.py | 24 +++++++++++++++++++
topicgate-plugin/.claude-plugin/plugin.json | 19 +++++++++++++++
topicgate-plugin/CONTRACT.md | 4 ++--
topicgate-plugin/SCENARIOS.md | 2 +-
.../skills/setup-topicgate/SKILL.md | 13 +++++-----
6 files changed, 67 insertions(+), 9 deletions(-)
create mode 100644 .claude-plugin/marketplace.json
create mode 100644 topicgate-plugin/.claude-plugin/plugin.json
diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json
new file mode 100644
index 0000000..dc58b92
--- /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.0.0"
+ }
+ ]
+}
diff --git a/tests/test_plugin_bundle.py b/tests/test_plugin_bundle.py
index a9345f8..74a3a33 100644
--- a/tests/test_plugin_bundle.py
+++ b/tests/test_plugin_bundle.py
@@ -42,6 +42,30 @@ 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_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..984ac11
--- /dev/null
+++ b/topicgate-plugin/.claude-plugin/plugin.json
@@ -0,0 +1,19 @@
+{
+ "name": "topicgate",
+ "version": "1.0.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/CONTRACT.md b/topicgate-plugin/CONTRACT.md
index 5079427..0e21aef 100644
--- a/topicgate-plugin/CONTRACT.md
+++ b/topicgate-plugin/CONTRACT.md
@@ -1,8 +1,8 @@
# 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 and Claude Code plugin 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,
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/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`.
From ea344bbadd619fa19b0f73a38ffdfbe2017e5dda Mon Sep 17 00:00:00 2001
From: Paul Thumfart <158182803+Dumdart@users.noreply.github.com>
Date: Thu, 20 Aug 2026 20:31:06 +0200
Subject: [PATCH 02/11] Plugin: add vscode copilot support
---
tests/test_plugin_bundle.py | 31 +++++++++++++++++++++++++++++++
topicgate-plugin/CONTRACT.md | 11 ++++++-----
topicgate-plugin/mcp.json | 13 +++++++++++++
topicgate-plugin/plugin.json | 18 ++++++++++++++++++
4 files changed, 68 insertions(+), 5 deletions(-)
create mode 100644 topicgate-plugin/mcp.json
create mode 100644 topicgate-plugin/plugin.json
diff --git a/tests/test_plugin_bundle.py b/tests/test_plugin_bundle.py
index 74a3a33..749c7e0 100644
--- a/tests/test_plugin_bundle.py
+++ b/tests/test_plugin_bundle.py
@@ -66,6 +66,37 @@ def test_plugin_bundle_matches_claude_code_ingestion_contract() -> None:
assert (PLUGIN_ROOT / manifest["mcpServers"].removeprefix("./")).is_file()
+def test_plugin_bundle_matches_agent_plugins_ingestion_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/CONTRACT.md b/topicgate-plugin/CONTRACT.md
index 0e21aef..b5451b7 100644
--- a/topicgate-plugin/CONTRACT.md
+++ b/topicgate-plugin/CONTRACT.md
@@ -1,12 +1,13 @@
# 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 and Claude Code plugin ingestion
-contracts used by the repository test suite.
+It is tested with FastMCP 3.4.7 and the Codex, Claude Code, and 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/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..0fb6bfe
--- /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.0.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"
+ ]
+}
From baca22ca7d598d4e8d5cb7fd622d4d931a7e82bd Mon Sep 17 00:00:00 2001
From: Paul Thumfart <158182803+Dumdart@users.noreply.github.com>
Date: Thu, 20 Aug 2026 20:49:22 +0200
Subject: [PATCH 03/11] Plugin: Add cursor plugin support
---
tests/test_plugin_bundle.py | 2 +-
topicgate-plugin/CONTRACT.md | 5 +++--
2 files changed, 4 insertions(+), 3 deletions(-)
diff --git a/tests/test_plugin_bundle.py b/tests/test_plugin_bundle.py
index 749c7e0..65caa0a 100644
--- a/tests/test_plugin_bundle.py
+++ b/tests/test_plugin_bundle.py
@@ -66,7 +66,7 @@ def test_plugin_bundle_matches_claude_code_ingestion_contract() -> None:
assert (PLUGIN_ROOT / manifest["mcpServers"].removeprefix("./")).is_file()
-def test_plugin_bundle_matches_agent_plugins_ingestion_contract() -> None:
+def test_plugin_bundle_matches_copilot_and_cursor_agent_plugins_contract() -> None:
manifest = json.loads(
(PLUGIN_ROOT / "plugin.json").read_text(encoding="utf-8")
)
diff --git a/topicgate-plugin/CONTRACT.md b/topicgate-plugin/CONTRACT.md
index b5451b7..0b158fc 100644
--- a/topicgate-plugin/CONTRACT.md
+++ b/topicgate-plugin/CONTRACT.md
@@ -1,8 +1,9 @@
# 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, Claude Code, and Agent Plugins 1.0
-ingestion contracts 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` and portable `mcp.json` are read-only. Control mode is a
separate, explicit configuration in `.mcp-control.json`; copy it only when
From 189233963703b6705fffb3906a1540a482de461c Mon Sep 17 00:00:00 2001
From: Paul Thumfart <158182803+Dumdart@users.noreply.github.com>
Date: Thu, 20 Aug 2026 21:28:49 +0200
Subject: [PATCH 04/11] Docs: add agent specific install guides
---
README.md | 47 ++++++---------------
docs/install/CLAUDE_CODE.md | 58 ++++++++++++++++++++++++++
docs/install/CODEX.md | 48 ++++++++++++++++++++++
docs/install/CURSOR.md | 55 +++++++++++++++++++++++++
docs/install/VSCODE_COPILOT.md | 75 ++++++++++++++++++++++++++++++++++
5 files changed, 248 insertions(+), 35 deletions(-)
create mode 100644 docs/install/CLAUDE_CODE.md
create mode 100644 docs/install/CODEX.md
create mode 100644 docs/install/CURSOR.md
create mode 100644 docs/install/VSCODE_COPILOT.md
diff --git a/README.md b/README.md
index 66a9af4..ad77f54 100644
--- a/README.md
+++ b/README.md
@@ -7,7 +7,7 @@
Get started ·
- Use it from Codex ·
+ Connect an agent ·
Understand observations ·
Desktop workflow
@@ -37,37 +37,16 @@ TopicGate supports exact MQTT paths and the standard `+` and `#` wildcard filter
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.
> [!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.
+> **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 agent host validated end to end so far. Claude Code, GitHub Copilot, and Cursor plugin packaging are covered by repository contract tests, but runtime validation is still pending. TopicGate uses `keyring` for the operating-system credential store; those integrations have not yet been tested across platforms.
The Windows development installation is:
```powershell
git clone https://github.com/Dumdart/TopicGate.git
cd TopicGate
-python -m venv .venv
-.\.venv\Scripts\Activate.ps1
python -m pip install -e .
```
-Then install mcp in readonly mode (codex):
-
-```powershell
-codex mcp add topicgate -- python -m topicgate
-```
-
-or with full access:
-
-```powershell
-codex mcp add topicgate -- python -m topicgate --mode control
-```
-
-Install Plugin (codex):
-
-```powershell
-codex plugin marketplace add .
-codex plugin add topicgate@topicgate
-```
-
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]"`.
### 2. Configure and observe
@@ -113,23 +92,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
diff --git a/docs/install/CLAUDE_CODE.md b/docs/install/CLAUDE_CODE.md
new file mode 100644
index 0000000..a704a46
--- /dev/null
+++ b/docs/install/CLAUDE_CODE.md
@@ -0,0 +1,58 @@
+# 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.
+
+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.
+
+## 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..66bdadb
--- /dev/null
+++ b/docs/install/CODEX.md
@@ -0,0 +1,48 @@
+# 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.
+
+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.
+
+## Install the plugin
+
+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..f8eb794
--- /dev/null
+++ b/docs/install/CURSOR.md
@@ -0,0 +1,55 @@
+# 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.
+
+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.
+
+## 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/VSCODE_COPILOT.md b/docs/install/VSCODE_COPILOT.md
new file mode 100644
index 0000000..0b5c059
--- /dev/null
+++ b/docs/install/VSCODE_COPILOT.md
@@ -0,0 +1,75 @@
+# 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.
+
+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
+
+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.
From 3dad19a6d198b2d3480bc1f817bb38253623c90a Mon Sep 17 00:00:00 2001
From: Paul Thumfart <158182803+Dumdart@users.noreply.github.com>
Date: Sat, 22 Aug 2026 11:19:46 +0200
Subject: [PATCH 05/11] Versioning: verify same versions on ci + cd
---
.github/workflows/cd.yaml | 18 ++++++++++
.github/workflows/ci.yaml | 2 ++
release/run_cd.py | 15 ++++++++
release/run_ci.py | 12 +++++++
release/verify_release_versions.py | 57 ++++++++++++++++++++++++++++++
5 files changed, 104 insertions(+)
create mode 100644 .github/workflows/cd.yaml
create mode 100644 release/run_cd.py
create mode 100644 release/run_ci.py
create mode 100644 release/verify_release_versions.py
diff --git a/.github/workflows/cd.yaml b/.github/workflows/cd.yaml
new file mode 100644
index 0000000..6e0c30c
--- /dev/null
+++ b/.github/workflows/cd.yaml
@@ -0,0 +1,18 @@
+name: Release
+
+on:
+ push:
+ tags:
+ - "v*"
+
+jobs:
+ publish-plugin:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Verify release versions
+ env:
+ RELEASE_TAG: ${{ github.ref_name }}
+ run: python -m release.run_cd
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index c838013..7bd4a7d 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -12,6 +12,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/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..f3ae074
--- /dev/null
+++ b/release/verify_release_versions.py
@@ -0,0 +1,57 @@
+from inspect import ClassFoundException
+import json
+import os
+import tomllib
+from pathlib import Path
+
+def plugin_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")
+ )
+
+ package_version = pyproject["project"]["version"]
+
+ plugin_base = plugin_base_version(plugin)
+ codex_plugin_base = plugin_base_version(codex_plugin)
+ claude_plugin_base = plugin_base_version(claude_plugin)
+
+ versions = {
+ "Python package": package_version,
+ "Plugin base version": plugin_base,
+ "Codex plugin base version": codex_plugin_base,
+ "Claude plugin base version": claude_plugin_base,
+ }
+
+ if expected:
+ versions["Git tag"] = expected
+
+ return versions
+
+def verify(versions: dict[str, str]):
+ if len(set(versions.values())) != 1:
+ raise SystemExit(
+ "Release version mismatch:\n"
+ + "\n".join(f" {name}: {version}"
+ for name, version in versions.items())
+ )
From 2f355b9867c5565b242b790e7ea35237332999c4 Mon Sep 17 00:00:00 2001
From: Paul Thumfart <158182803+Dumdart@users.noreply.github.com>
Date: Sat, 22 Aug 2026 11:34:33 +0200
Subject: [PATCH 06/11] Deployment: require CI; pypi publish
---
.github/workflows/cd.yaml | 41 ++++++++++++++++++++++++++++++++++++++-
.github/workflows/ci.yaml | 2 ++
2 files changed, 42 insertions(+), 1 deletion(-)
diff --git a/.github/workflows/cd.yaml b/.github/workflows/cd.yaml
index 6e0c30c..1449279 100644
--- a/.github/workflows/cd.yaml
+++ b/.github/workflows/cd.yaml
@@ -6,8 +6,13 @@ on:
- "v*"
jobs:
- publish-plugin:
+ ci:
+ uses: ./.github/workflows/ci.yaml
+
+ release_build:
runs-on: ubuntu-latest
+ needs:
+ - ci
steps:
- uses: actions/checkout@v4
@@ -16,3 +21,37 @@ jobs:
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 -e .
+
+ - 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-${{ github.ref_name }}
+ path: dist/
+
+ - name: Publish release distributions to PyPI
+ uses: pypa/gh-action-pypi-publish@release/v1
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index 7bd4a7d..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
From a67904decac891ffd932cfd426ef9a66d34264e7 Mon Sep 17 00:00:00 2001
From: Paul Thumfart <158182803+Dumdart@users.noreply.github.com>
Date: Sat, 22 Aug 2026 13:01:45 +0200
Subject: [PATCH 07/11] Release: Add MCP registry publishing
---
.github/workflows/cd.yaml | 28 +++++++++++++++++++++++++++-
release/verify_release_versions.py | 15 +++++++++++----
server.json | 17 +++++++++++++++++
3 files changed, 55 insertions(+), 5 deletions(-)
create mode 100644 server.json
diff --git a/.github/workflows/cd.yaml b/.github/workflows/cd.yaml
index 1449279..83a9cae 100644
--- a/.github/workflows/cd.yaml
+++ b/.github/workflows/cd.yaml
@@ -9,7 +9,7 @@ jobs:
ci:
uses: ./.github/workflows/ci.yaml
- release_build:
+ release-build:
runs-on: ubuntu-latest
needs:
- ci
@@ -55,3 +55,29 @@ jobs:
- 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
diff --git a/release/verify_release_versions.py b/release/verify_release_versions.py
index f3ae074..a39e314 100644
--- a/release/verify_release_versions.py
+++ b/release/verify_release_versions.py
@@ -4,7 +4,7 @@
import tomllib
from pathlib import Path
-def plugin_base_version(plugin) -> str:
+def json_base_version(plugin) -> str:
plugin_version = plugin["version"]
return plugin_version.split("+", 1)[0]
@@ -30,17 +30,24 @@ def get_versions(expected: str | None = None) -> dict[str, str]:
.read_text(encoding="utf-8")
)
+ server = json.loads(
+ Path("server.json")
+ .read_text(encoding="utf-8")
+ )
+
package_version = pyproject["project"]["version"]
- plugin_base = plugin_base_version(plugin)
- codex_plugin_base = plugin_base_version(codex_plugin)
- claude_plugin_base = plugin_base_version(claude_plugin)
+ plugin_base = json_base_version(plugin)
+ codex_plugin_base = json_base_version(codex_plugin)
+ claude_plugin_base = json_base_version(claude_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,
+ "Server base version": serverbase
}
if expected:
diff --git a/server.json b/server.json
new file mode 100644
index 0000000..d64ff04
--- /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.0.0",
+ "packages": [
+ {
+ "registryType": "pypi",
+ "identifier": "topicgate",
+ "version": "1.0.0",
+ "transport": {
+ "type": "stdio"
+ }
+ }
+ ]
+ }
From b6a71ce4a133b700b64c9a4eeec0bd71a590bfc8 Mon Sep 17 00:00:00 2001
From: Paul Thumfart <158182803+Dumdart@users.noreply.github.com>
Date: Sat, 22 Aug 2026 13:08:54 +0200
Subject: [PATCH 08/11] Release: add githubrelease automation;
---
.github/workflows/cd.yaml | 28 ++++++++++++++++++++++++++--
1 file changed, 26 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/cd.yaml b/.github/workflows/cd.yaml
index 83a9cae..e7498e7 100644
--- a/.github/workflows/cd.yaml
+++ b/.github/workflows/cd.yaml
@@ -28,7 +28,8 @@ jobs:
- name: Build release distributions
run: |
- python -m pip install -e .
+ python -m pip install --upgrade build
+ python -m build
- name: Upload distributions
uses: actions/upload-artifact@v4
@@ -50,7 +51,7 @@ jobs:
- name: Retrieve release distributions
uses: actions/download-artifact@v4
with:
- name: release-dists-${{ github.ref_name }}
+ name: release-dists
path: dist/
- name: Publish release distributions to PyPI
@@ -81,3 +82,26 @@ jobs:
- 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
From 12956549770d3875a4df356dbe0fa156f09a67e5 Mon Sep 17 00:00:00 2001
From: Paul Thumfart <158182803+Dumdart@users.noreply.github.com>
Date: Sat, 22 Aug 2026 13:15:11 +0200
Subject: [PATCH 09/11] Release: update install docs
---
README.md | 38 ++++++++++++++++++++--------------
docs/install/CLAUDE_CODE.md | 18 +++++++++++++++-
docs/install/CODEX.md | 18 +++++++++++++++-
docs/install/CURSOR.md | 18 +++++++++++++++-
docs/install/VSCODE_COPILOT.md | 18 +++++++++++++++-
5 files changed, 90 insertions(+), 20 deletions(-)
diff --git a/README.md b/README.md
index ad77f54..806e693 100644
--- a/README.md
+++ b/README.md
@@ -34,20 +34,22 @@ 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 agent host validated end to end so far. Claude Code, GitHub Copilot, and Cursor plugin packaging are covered by repository contract tests, but runtime validation is still pending. TopicGate uses `keyring` for the operating-system credential store; those integrations have not yet been tested across platforms.
-The Windows development installation is:
-
```powershell
-git clone https://github.com/Dumdart/TopicGate.git
-cd TopicGate
-python -m pip install -e .
+uv tool 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]"`.
+Alternatively, install it with pip:
+
+```powershell
+python -m pip install topicgate
+topicgate-gui
+```
### 2. Configure and observe
@@ -163,19 +165,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
index a704a46..4f1f0fd 100644
--- a/docs/install/CLAUDE_CODE.md
+++ b/docs/install/CLAUDE_CODE.md
@@ -2,9 +2,25 @@
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.
-## Install from a local checkout
+## Development: install from a local checkout
From the TopicGate repository root, register the current directory as a local marketplace and install the plugin:
diff --git a/docs/install/CODEX.md b/docs/install/CODEX.md
index 66bdadb..4ffbc3e 100644
--- a/docs/install/CODEX.md
+++ b/docs/install/CODEX.md
@@ -2,9 +2,25 @@
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.
-## Install the plugin
+## 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:
diff --git a/docs/install/CURSOR.md b/docs/install/CURSOR.md
index f8eb794..a0a47c3 100644
--- a/docs/install/CURSOR.md
+++ b/docs/install/CURSOR.md
@@ -2,9 +2,25 @@
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.
-## Test or install the plugin locally
+## Development: test or install the plugin locally
From a TopicGate source checkout, copy the `topicgate-plugin` directory to Cursor's local plugin directory:
diff --git a/docs/install/VSCODE_COPILOT.md b/docs/install/VSCODE_COPILOT.md
index 0b5c059..ba17341 100644
--- a/docs/install/VSCODE_COPILOT.md
+++ b/docs/install/VSCODE_COPILOT.md
@@ -2,6 +2,22 @@
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
@@ -22,7 +38,7 @@ After installation, start a new chat. The skills appear in **Chat: Configure Ski
## Install with GitHub Copilot CLI
-From the TopicGate repository root, register the current directory as a local marketplace and install the plugin:
+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 .
From ef065ca6d08e92970f4de83a82b268b564f90a6c Mon Sep 17 00:00:00 2001
From: Paul Thumfart <158182803+Dumdart@users.noreply.github.com>
Date: Sat, 22 Aug 2026 13:44:24 +0200
Subject: [PATCH 10/11] Docs: add recovery codes; update version
---
README.md | 4 +-
docs/install/UPGRADE_AND_RECOVERY.md | 101 ++++++++++++++++++++
pyproject.toml | 2 +-
server.json | 4 +-
topicgate-plugin/.claude-plugin/plugin.json | 2 +-
topicgate-plugin/.codex-plugin/plugin.json | 2 +-
topicgate-plugin/plugin.json | 2 +-
7 files changed, 110 insertions(+), 7 deletions(-)
create mode 100644 docs/install/UPGRADE_AND_RECOVERY.md
diff --git a/README.md b/README.md
index 806e693..3d14e1f 100644
--- a/README.md
+++ b/README.md
@@ -37,7 +37,7 @@ TopicGate supports exact MQTT paths and the standard `+` and `#` wildcard filter
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 agent host validated end to end so far. Claude Code, GitHub Copilot, and Cursor plugin packaging are covered by repository contract tests, but runtime validation is still pending. TopicGate uses `keyring` for the operating-system credential store; those integrations have not yet been tested across platforms.
+> **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
uv tool install topicgate
@@ -51,6 +51,8 @@ python -m pip install topicgate
topicgate-gui
```
+For upgrades, uninstallation, backups, and recovery, see [Installation recovery and upgrades](docs/install/UPGRADE_AND_RECOVERY.md).
+
### 2. Configure and observe
Run the desktop application:
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/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/server.json b/server.json
index d64ff04..e23a836 100644
--- a/server.json
+++ b/server.json
@@ -3,12 +3,12 @@
"name": "io.github.Dumdart/topicgate",
"title": "TopicGate",
"description": "Secure local access to MQTT topics through MCP.",
- "version": "1.0.0",
+ "version": "1.1.0",
"packages": [
{
"registryType": "pypi",
"identifier": "topicgate",
- "version": "1.0.0",
+ "version": "1.1.0",
"transport": {
"type": "stdio"
}
diff --git a/topicgate-plugin/.claude-plugin/plugin.json b/topicgate-plugin/.claude-plugin/plugin.json
index 984ac11..095cac1 100644
--- a/topicgate-plugin/.claude-plugin/plugin.json
+++ b/topicgate-plugin/.claude-plugin/plugin.json
@@ -1,6 +1,6 @@
{
"name": "topicgate",
- "version": "1.0.0",
+ "version": "1.1.0",
"description": "Secure local access to your MQTT topics through TopicGate MCP",
"author": {
"name": "Dumdart",
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/plugin.json b/topicgate-plugin/plugin.json
index 0fb6bfe..af5cd27 100644
--- a/topicgate-plugin/plugin.json
+++ b/topicgate-plugin/plugin.json
@@ -1,7 +1,7 @@
{
"$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
"name": "topicgate",
- "version": "1.0.0",
+ "version": "1.1.0",
"description": "Secure local access to your MQTT topics through TopicGate MCP",
"author": {
"name": "Dumdart",
From 268a2508327151ca0d3fbbf7494a7adcf26e3ce7 Mon Sep 17 00:00:00 2001
From: Paul Thumfart <158182803+Dumdart@users.noreply.github.com>
Date: Sat, 22 Aug 2026 14:38:11 +0200
Subject: [PATCH 11/11] Release: update marketplace version
---
.claude-plugin/marketplace.json | 2 +-
release/verify_release_versions.py | 29 ++++++++++++++++++++++++-----
uv.lock | 2 +-
3 files changed, 26 insertions(+), 7 deletions(-)
diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json
index dc58b92..609f6e4 100644
--- a/.claude-plugin/marketplace.json
+++ b/.claude-plugin/marketplace.json
@@ -8,7 +8,7 @@
"name": "topicgate",
"source": "./topicgate-plugin",
"description": "Secure local access to MQTT state through TopicGate",
- "version": "1.0.0"
+ "version": "1.1.0"
}
]
}
diff --git a/release/verify_release_versions.py b/release/verify_release_versions.py
index a39e314..a09f0ad 100644
--- a/release/verify_release_versions.py
+++ b/release/verify_release_versions.py
@@ -1,16 +1,15 @@
-from inspect import ClassFoundException
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]:
-
+def get_versions(expected: str | None = None) -> dict[str, str]:
pyproject = tomllib.loads(
Path("pyproject.toml").read_text(encoding="utf-8")
)
@@ -30,6 +29,24 @@ def get_versions(expected: str | None = None) -> dict[str, str]:
.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")
@@ -40,6 +57,7 @@ def get_versions(expected: str | None = None) -> dict[str, str]:
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 = {
@@ -47,7 +65,8 @@ def get_versions(expected: str | None = None) -> dict[str, str]:
"Plugin base version": plugin_base,
"Codex plugin base version": codex_plugin_base,
"Claude plugin base version": claude_plugin_base,
- "Server base version": serverbase
+ "Claude marketplace base version": marketplace_plugin_base,
+ "Server base version": serverbase,
}
if expected:
@@ -55,7 +74,7 @@ def get_versions(expected: str | None = None) -> dict[str, str]:
return versions
-def verify(versions: dict[str, str]):
+def verify(versions: dict[str, str]) -> None:
if len(set(versions.values())) != 1:
raise SystemExit(
"Release version mismatch:\n"
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" },