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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,24 @@ All notable changes to the mcpbridge-wrapper project will be documented in this
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.4.5] - 2026-07-02

### Added

- Added broker host identity metadata in `~/.mcpbridge_wrapper/broker.host.json` and surfaced the recorded host executable in `--broker-status` and `--doctor`.
- Added `MCPBRIDGE_WRAPPER_BROKER_HOST_CMD` so broker auto-spawn can be pinned to a stable launcher path while clients continue using `--broker`.

### Changed

- Broker proxies now reuse the live per-user singleton daemon by default even when their package version differs, avoiding daemon churn during `uvx` cache or environment changes. Restart the daemon explicitly when upgrading the broker host.
- Updated broker-mode documentation to clarify `uvx --broker` singleton behavior and the tradeoff between convenient auto-spawn and a dedicated stable host.

### Fixed

- Prevented repeated downstream `initialize` and `notifications/initialized` messages from being forwarded to the single upstream Xcode MCP session, which could make `xcrun mcpbridge` close the connection under multiple real MCP clients.
- Increased broker and proxy stream limits so large Xcode tool catalogs can pass through the singleton broker without line-length read failures.
- Ensured reconnect replaces the previous upstream `xcrun mcpbridge` child before launching a new one, preventing stale upstream processes and repeated Xcode permission prompts.

## [0.4.4] - 2026-03-10

### Added
Expand Down Expand Up @@ -123,6 +141,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- ✅ Support for all 20 Xcode MCP tools
- ✅ Configuration examples for Cursor, Claude Code, and Codex CLI

[0.4.5]: https://github.com/SoundBlaster/XcodeMCPWrapper/releases/tag/v0.4.5
[0.4.4]: https://github.com/SoundBlaster/XcodeMCPWrapper/releases/tag/v0.4.4
[0.4.3]: https://github.com/SoundBlaster/XcodeMCPWrapper/releases/tag/v0.4.3
[0.4.2]: https://github.com/SoundBlaster/XcodeMCPWrapper/releases/tag/v0.4.2
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<!-- mcp-name: io.github.SoundBlaster/xcode-mcpbridge-wrapper -->

<!-- version-badge:start -->
[![Version](https://img.shields.io/badge/version-0.4.4-blue.svg)](https://github.com/SoundBlaster/XcodeMCPWrapper/releases/tag/v0.4.4)
[![Version](https://img.shields.io/badge/version-0.4.5-blue.svg)](https://github.com/SoundBlaster/XcodeMCPWrapper/releases/tag/v0.4.5)
<!-- version-badge:end -->
[![Python 3.9+](https://img.shields.io/badge/python-3.9+-blue.svg)](https://www.python.org/downloads/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
Expand Down Expand Up @@ -621,6 +621,7 @@ See [Web UI Setup Guide](docs/webui-setup.md) for detailed configuration.
## Known Issues

- **Broker cold-start — Xcode approval timing race (0 tools with green dot):** When the broker daemon starts a new `xcrun mcpbridge` process (on first launch or after a daemon restart), Xcode shows a per-process "Allow Connection?" dialog. If your MCP client sends `tools/list` *before* Xcode grants approval, it receives an empty list and **caches it permanently** — showing 0 tools with a green connected indicator and no error message. Each unique binary path (direct wrapper vs broker daemon) triggers a *separate* dialog. After approval the permission persists — no re-approval is needed on subsequent sessions. **Workaround:** watch for the Xcode dialog immediately after enabling broker mode; after clicking Allow, reload the MCP connection in your client (disable → re-enable in settings). See [Troubleshooting: 0 tools after first broker connection](docs/troubleshooting.md#mcp-client-shows-0-tools-green-dot-after-first-broker-connection) for client-specific recovery steps and the diagnostic command.
- **Singleton broker host behavior:** `--broker` reuses one live daemon per local user, including `uvx --from mcpbridge-wrapper mcpbridge-wrapper --broker` clients. The first client that auto-spawns the daemon determines the Python host identity Xcode sees; for the most stable permission identity, start a dedicated broker host from a fixed path or set `MCPBRIDGE_WRAPPER_BROKER_HOST_CMD` before auto-spawn.
- **BUG-T5 → FU-P13-T7 (P0):** Empty-content tool results can still violate strict `structuredContent` expectations in strict MCP clients.
- **BUG-T6 → FU-P13-T8 (P0):** Web UI port collisions can happen when multiple MCP sessions start with the same `--web-ui-port` (for example `8080`), producing `address already in use`.
- **BUG-T7 → FU-P13-T9 (P0):** `resources/list` and `resources/templates/list` probing may return non-standard error shapes in some client paths.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Model Context Protocol (MCP).
[https://github.com/SoundBlaster/XcodeMCPWrapper](https://github.com/SoundBlaster/XcodeMCPWrapper)

<!-- version-badge:start -->
[![Version](https://img.shields.io/badge/version-0.4.4-blue.svg)](https://github.com/SoundBlaster/XcodeMCPWrapper/releases/tag/v0.4.4)
[![Version](https://img.shields.io/badge/version-0.4.5-blue.svg)](https://github.com/SoundBlaster/XcodeMCPWrapper/releases/tag/v0.4.5)
<!-- version-badge:end -->

## Overview
Expand Down Expand Up @@ -462,6 +462,7 @@ Important for multi-agent setups:
## Known Issues

- **Broker cold-start — Xcode approval timing race (0 tools with green dot):** When the broker daemon starts a new `xcrun mcpbridge` process (on first launch or after a daemon restart), Xcode shows a per-process "Allow Connection?" dialog. If your MCP client sends `tools/list` *before* Xcode grants approval, it receives an empty list and **caches it permanently** — showing 0 tools with a green connected indicator and no error message. Each unique binary path (direct wrapper vs broker daemon) triggers a *separate* dialog. After approval the permission persists — no re-approval is needed on subsequent sessions. **Workaround:** watch for the Xcode dialog immediately after enabling broker mode; after clicking Allow, reload the MCP connection in your client (disable → re-enable in settings). See <doc:Troubleshooting> for client-specific recovery steps and the diagnostic command.
- **Singleton broker host behavior:** `--broker` reuses one live daemon per local user, including `uvx --from mcpbridge-wrapper mcpbridge-wrapper --broker` clients. The first client that auto-spawns the daemon determines the Python host identity Xcode sees; for the most stable permission identity, start a dedicated broker host from a fixed path or set `MCPBRIDGE_WRAPPER_BROKER_HOST_CMD` before auto-spawn.
- **BUG-T5 → FU-P13-T7 (P0):** Empty-content tool results can still violate strict `structuredContent` expectations in strict MCP clients.
- **BUG-T6 → FU-P13-T8 (P0):** Web UI port collisions can happen when multiple MCP sessions start with the same `--web-ui-port` (for example `8080`), producing `address already in use`.
- **BUG-T7 → FU-P13-T9 (P0):** `resources/list` and `resources/templates/list` probing may return non-standard error shapes in some client paths.
Expand Down
49 changes: 49 additions & 0 deletions docs/broker-mode.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,55 @@ mcpbridge-wrapper --broker-stop

Use broker mode when you want lower process churn across repeated MCP client restarts.

## Singleton broker host behavior

Broker mode keeps one long-lived daemon per local macOS user. The daemon owns the
single upstream `xcrun mcpbridge` session and every `--broker` client connects to
that daemon through `~/.mcpbridge_wrapper/broker.sock`. This includes `uvx`
clients such as:

```json
{
"mcpServers": {
"xcode-tools": {
"command": "uvx",
"args": ["--from", "mcpbridge-wrapper", "mcpbridge-wrapper", "--broker"]
}
}
}
```

If a short-lived proxy has a different package version than the live daemon, the
proxy reuses that daemon and prints a warning instead of stopping and replacing it
automatically. Restart the daemon explicitly when you choose to upgrade the host:

```bash
mcpbridge-wrapper --broker-stop
mcpbridge-wrapper --broker-daemon --web-ui
```

With `uvx`, the first client that needs to auto-spawn the daemon also determines
the daemon host identity that Xcode sees. That identity is usually a Python
executable from the `uvx` runtime/cache. For the most stable Xcode permission
identity, start a dedicated broker host from a fixed path, or pin auto-spawn to a
stable launcher with `MCPBRIDGE_WRAPPER_BROKER_HOST_CMD`. The configured command
is used only when `--broker` needs to auto-spawn a daemon; existing daemons are
reused.

```bash
export MCPBRIDGE_WRAPPER_BROKER_HOST_CMD="/Users/egor/bin/mcpbridge-wrapper-host"
uvx --from 'mcpbridge-wrapper[webui]' mcpbridge-wrapper --broker --web-ui
```

The daemon writes host identity metadata to:

```text
~/.mcpbridge_wrapper/broker.host.json
```

Use `mcpbridge-wrapper --broker-status` or `mcpbridge-wrapper --doctor` to inspect
the recorded host executable.

---

## Multi-agent topology and Web UI ownership
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "mcpbridge-wrapper"
version = "0.4.4"
version = "0.4.5"
description = "Python wrapper for xcrun mcpbridge that adds structuredContent to MCP responses"
readme = "README.md"
requires-python = ">=3.9"
Expand Down
4 changes: 2 additions & 2 deletions server.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"name": "io.github.SoundBlaster/xcode-mcpbridge-wrapper",
"title": "Xcode: MCP Bridge Wrapper for Cursor",
"description": "Python wrapper that makes Xcode's MCP bridge compatible with strict MCP clients like Cursor",
"version": "0.4.4",
"version": "0.4.5",
"repository": {
"url": "https://github.com/SoundBlaster/XcodeMCPWrapper",
"source": "github"
Expand All @@ -13,7 +13,7 @@
{
"registryType": "pypi",
"identifier": "mcpbridge-wrapper",
"version": "0.4.4",
"version": "0.4.5",
"transport": {
"type": "stdio"
},
Expand Down
22 changes: 20 additions & 2 deletions src/mcpbridge_wrapper/__main__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Entry point for mcpbridge-wrapper."""

import contextlib
import json
import os
import signal
import subprocess
Expand Down Expand Up @@ -956,6 +957,7 @@ def main() -> int:
print(f"PID file: {broker_config.pid_file}")
print(f"Socket: {broker_config.socket_path}")
print(f"Version file: {broker_config.version_file}")
print(f"Host file: {broker_config.host_file}")

pid: Optional[int] = None
if broker_config.pid_file.exists():
Expand All @@ -981,6 +983,12 @@ def main() -> int:
print(f"WARNING: version mismatch! proxy={__version__}, daemon={daemon_version}")
else:
print("Daemon version: (unknown)")
if broker_config.host_file.exists():
with contextlib.suppress(OSError, json.JSONDecodeError):
host_payload = json.loads(broker_config.host_file.read_text())
host_executable = host_payload.get("executable")
if host_executable:
print(f"Daemon host: {host_executable}")
return 0

# --broker-stop: stop running broker daemon and exit
Expand All @@ -996,7 +1004,12 @@ def main() -> int:
pid_val = int(pid_file.read_text().strip())
except (ValueError, OSError):
print("Corrupt PID file; cleaning up.", file=sys.stderr)
for p in (pid_file, broker_config.socket_path, broker_config.version_file):
for p in (
pid_file,
broker_config.socket_path,
broker_config.version_file,
broker_config.host_file,
):
p.unlink(missing_ok=True)
return 0

Expand Down Expand Up @@ -1039,7 +1052,12 @@ def main() -> int:
)
return 1

for p in (pid_file, broker_config.socket_path, broker_config.version_file):
for p in (
pid_file,
broker_config.socket_path,
broker_config.version_file,
broker_config.host_file,
):
p.unlink(missing_ok=True)
print("Broker stopped and files cleaned up.")
return 0
Expand Down
Loading
Loading