Skip to content

Commit b36e5bd

Browse files
authored
Merge pull request #43 from lynnswap:codex/proxy-handshake-config
feat(proxy): add configurable upstream handshake
2 parents 5aa1238 + 187f51e commit b36e5bd

28 files changed

Lines changed: 1131 additions & 246 deletions

Docs/troubleshooting.md

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,5 @@ When the proxy runs in `--refresh-code-issues-mode upstream`, Xcode's live diagn
8282
- If a client bursts too many queued refreshes for the same tab in `upstream` mode, the proxy may return `refresh queue overloaded` instead of letting the queue grow without bound.
8383
- If you need Xcode's native live diagnostics behavior, start the proxy with `--refresh-code-issues-mode upstream` (or `MCP_XCODE_REFRESH_CODE_ISSUES_MODE=upstream`).
8484

85-
## Xcode dialog does not appear
86-
Make sure `--lazy-init` is not set (when enabled, the dialog appears on the first request instead of at startup).
87-
8885
## `session not found`
8986
Ensure the client is using the same session.

Package.resolved

Lines changed: 10 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Package.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ let package = Package(
3535
dependencies: [
3636
.package(url: "https://github.com/apple/swift-nio.git", from: "2.0.0"),
3737
.package(url: "https://github.com/apple/swift-log.git", from: "1.5.0"),
38+
.package(url: "https://github.com/dduan/TOMLDecoder.git", from: "0.4.3"),
3839
],
3940
targets: [
4041
.target(
@@ -51,6 +52,7 @@ let package = Package(
5152
.product(name: "Logging", package: "swift-log"),
5253
.product(name: "NIOConcurrencyHelpers", package: "swift-nio"),
5354
.product(name: "NIO", package: "swift-nio"),
55+
.product(name: "TOMLDecoder", package: "TOMLDecoder"),
5456
],
5557
path: "Sources/ProxyCore",
5658
swiftSettings: strictSwiftSettings

README.ja.md

Lines changed: 37 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -116,32 +116,47 @@ claude mcp add --transport stdio xcode -- xcode-mcp-proxy
116116
- max body size: `1048576` bytes
117117
- initialization: eager at startup
118118
- discovery: `~/Library/Caches/XcodeMCPProxy/endpoint.json`
119+
120+
#### オプション
121+
122+
| オプション | 説明 |
123+
|--------|-------------|
124+
| `--upstream-command cmd` | `mcpbridge` 実行コマンド |
125+
| `--upstream-args a,b,c` | `mcpbridge` 引数(カンマ区切り) |
126+
| `--upstream-arg value` | `mcpbridge` 引数の追加(単一項目) |
127+
| `--upstream-processes n` | アップストリーム `mcpbridge` プロセスの起動数(デフォルト: 1、最大: 10) |
128+
| `--session-id id` | 明示的な Xcode MCP セッション ID |
129+
| `--max-body-bytes n` | リクエストボディの最大サイズ |
130+
| `--request-timeout seconds` | リクエストタイムアウト設定(`0` で初期化以外のタイムアウトを無効化。`initialize` 時のハンドシェイクには固定のタイムアウトが適用されます) |
131+
| `--config path` | アップストリームのハンドシェイクを上書きするためのプロキシ設定(TOML)のパス |
132+
| `--refresh-code-issues-mode mode` | `XcodeRefreshCodeIssuesInFile` の提供モード。プロキシ側のナビゲーター問題として処理(`proxy`、デフォルト)、または Xcode のライブ診断へパススルー(`upstream`|
133+
| `--force-restart` | ポートが使用中の場合、既存の `xcode-mcp-proxy-server` を終了して再起動 |
134+
119135
#### 環境変数
120136

121-
- `MCP_XCODE_PID``--xcode-pid` の代替)
122-
- `MCP_XCODE_SESSION_ID`(Xcode MCP セッション ID を固定。通常は不要)
123-
- `MCP_XCODE_REFRESH_CODE_ISSUES_MODE``proxy` または `upstream``XcodeRefreshCodeIssuesInFile` の実装モードを切り替え)
124-
- `MCP_LOG_LEVEL`(ログレベル: trace|debug|info|notice|warning|error|critical)
137+
| 変数 | 説明 |
138+
|------|------|
139+
| `LISTEN` | listen アドレス。例: `127.0.0.1:8765` |
140+
| `HOST` | listen ホスト。`LISTEN` 未指定時に `PORT` と組み合わせて使用 |
141+
| `PORT` | listen ポート。`LISTEN` 未指定時に `HOST` と組み合わせて使用 |
142+
| `MCP_XCODE_PID` | upstream `mcpbridge` へそのまま渡す互換 env。proxy 自身は解釈しない |
143+
| `MCP_XCODE_SESSION_ID` | 任意の明示的 upstream session ID |
144+
| `MCP_XCODE_CONFIG` | proxy config TOML のパス。`--config` が優先 |
145+
| `MCP_XCODE_REFRESH_CODE_ISSUES_MODE` | `proxy` または `upstream` |
146+
| `MCP_LOG_LEVEL` | ログレベル: `trace`, `debug`, `info`, `notice`, `warning`, `error`, `critical` |
125147

126148
ログは stderr に出力されます。
127149

128-
Note: `--upstream-processes` > 1 を使う場合、`--session-id` / `MCP_XCODE_SESSION_ID` でセッション ID を固定すると、Xcode の許可ダイアログを減らせる場合があります。
150+
#### Proxy Config
129151

130-
#### オプション
152+
| キー || 既定値 |
153+
|------|----|--------|
154+
| `upstream_handshake.protocolVersion` | string | `"2025-03-26"` |
155+
| `upstream_handshake.clientName` | string | `"XcodeMCPKit"` |
156+
| `upstream_handshake.clientVersion` | string | `"dev"` |
157+
| `upstream_handshake.capabilities` | table | `{}` |
131158

132-
| オプション | 説明 |
133-
|-----------|------|
134-
| `--upstream-command cmd` | `mcpbridge` コマンド |
135-
| `--upstream-args a,b,c` | `mcpbridge` 引数(カンマ区切り) |
136-
| `--upstream-arg value` | `mcpbridge` 引数を1つ追加 |
137-
| `--upstream-processes n` | upstream `mcpbridge``n` プロセス起動(default: 1, max: 10) |
138-
| `--xcode-pid pid` | 対象 Xcode の PID |
139-
| `--session-id id` | Xcode MCP セッション ID(通常は不要) |
140-
| `--max-body-bytes n` | 最大ボディサイズ |
141-
| `--request-timeout seconds` | リクエストタイムアウト(`0` で無制限) |
142-
| `--refresh-code-issues-mode proxy|upstream` | `XcodeRefreshCodeIssuesInFile` を proxy 側の navigator issues 経由(既定)で返すか、Xcode の live diagnostics へ直通するかを切り替え |
143-
| `--lazy-init` | 初回リクエストまで初期化を遅延 |
144-
| `--force-restart` | listen ポートが使用中の場合、既存の `xcode-mcp-proxy-server` を終了して起動し直す |
159+
`clientVersion` を省略した場合、`clientName` に対応する Xcode の `IDEChat*Version` があれば、その version を自動で使います。
145160

146161
### アダプタ: `xcode-mcp-proxy`
147162

@@ -154,7 +169,9 @@ Note: `--upstream-processes` > 1 を使う場合、`--session-id` / `MCP_XCODE_S
154169

155170
#### 環境変数
156171

157-
- `XCODE_MCP_PROXY_ENDPOINT`(上流 URL を上書き。`--url` が優先)
172+
| 変数 | 説明 |
173+
|------|------|
174+
| `XCODE_MCP_PROXY_ENDPOINT` | 上流 URL を上書き。`--url` が優先 |
158175

159176
## トラブルシューティング
160177

README.md

Lines changed: 33 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -117,17 +117,6 @@ See Quick Start for how to launch.
117117
- initialization: eager at startup
118118
- discovery: `~/Library/Caches/XcodeMCPProxy/endpoint.json`
119119

120-
#### Environment Variables
121-
122-
- `MCP_XCODE_PID` (alternative to `--xcode-pid`)
123-
- `MCP_XCODE_SESSION_ID` (fixes the Xcode MCP session ID; usually not needed)
124-
- `MCP_XCODE_REFRESH_CODE_ISSUES_MODE` (`proxy` or `upstream`; controls how `XcodeRefreshCodeIssuesInFile` is served)
125-
- `MCP_LOG_LEVEL` (log level: trace|debug|info|notice|warning|error|critical)
126-
127-
Logs are written to stderr.
128-
129-
Note: when using `--upstream-processes` > 1, fixing the session id via `--session-id` / `MCP_XCODE_SESSION_ID` can help reduce permission dialog prompts in Xcode.
130-
131120
#### Options
132121

133122
| Option | Description |
@@ -136,14 +125,39 @@ Note: when using `--upstream-processes` > 1, fixing the session id via `--sessio
136125
| `--upstream-args a,b,c` | `mcpbridge` args (comma-separated) |
137126
| `--upstream-arg value` | Append a single `mcpbridge` arg |
138127
| `--upstream-processes n` | Spawn `n` upstream `mcpbridge` processes (default: 1, max: 10) |
139-
| `--xcode-pid pid` | Xcode PID |
140-
| `--session-id id` | Xcode MCP session ID (usually not needed) |
128+
| `--session-id id` | Explicit Xcode MCP session ID |
141129
| `--max-body-bytes n` | Max request body size |
142-
| `--request-timeout seconds` | Request timeout (`0` disables) |
143-
| `--refresh-code-issues-mode proxy|upstream` | Serve `XcodeRefreshCodeIssuesInFile` via proxy navigator issues (`proxy`, default) or pass through to Xcode live diagnostics (`upstream`) |
144-
| `--lazy-init` | Delay initialization until first request |
130+
| `--request-timeout seconds` | Request timeout (`0` disables non-initialize timeouts; `initialize` still uses a bounded handshake timeout) |
131+
| `--config path` | Path to proxy config TOML for overriding the upstream handshake |
132+
| `--refresh-code-issues-mode mode` | Serve `XcodeRefreshCodeIssuesInFile` via proxy navigator issues (`proxy`, default) or pass through to Xcode live diagnostics (`upstream`) |
145133
| `--force-restart` | If the listen port is in use, terminate an existing `xcode-mcp-proxy-server` and restart |
146134

135+
#### Environment Variables
136+
137+
| Variable | Description |
138+
|----------|-------------|
139+
| `LISTEN` | Listen address; example: `127.0.0.1:8765` |
140+
| `HOST` | Listen host; used with `PORT` when `LISTEN` is unset |
141+
| `PORT` | Listen port; used with `HOST` when `LISTEN` is unset |
142+
| `MCP_XCODE_PID` | Passed through to upstream `mcpbridge`; the proxy itself does not parse it |
143+
| `MCP_XCODE_SESSION_ID` | Optional explicit upstream session ID |
144+
| `MCP_XCODE_CONFIG` | Proxy config TOML path; `--config` takes precedence |
145+
| `MCP_XCODE_REFRESH_CODE_ISSUES_MODE` | `proxy` or `upstream` |
146+
| `MCP_LOG_LEVEL` | Log level: `trace`, `debug`, `info`, `notice`, `warning`, `error`, `critical` |
147+
148+
Logs are written to stderr.
149+
150+
#### Proxy Config
151+
152+
| Key | Type | Default |
153+
|-----|------|---------|
154+
| `upstream_handshake.protocolVersion` | string | `"2025-03-26"` |
155+
| `upstream_handshake.clientName` | string | `"XcodeMCPKit"` |
156+
| `upstream_handshake.clientVersion` | string | `"dev"` |
157+
| `upstream_handshake.capabilities` | table | `{}` |
158+
159+
If `clientVersion` is omitted, the proxy auto-resolves it from the Xcode `IDEChat*Version` entry matching `clientName` when available.
160+
147161
### Adapter: `xcode-mcp-proxy`
148162

149163
#### Options
@@ -155,7 +169,9 @@ Note: when using `--upstream-processes` > 1, fixing the session id via `--sessio
155169

156170
#### Environment Variables
157171

158-
- `XCODE_MCP_PROXY_ENDPOINT` (override upstream URL; `--url` takes precedence)
172+
| Variable | Description |
173+
|----------|-------------|
174+
| `XCODE_MCP_PROXY_ENDPOINT` | Override upstream URL; `--url` takes precedence |
159175

160176
## Troubleshooting
161177

Sources/ProxyCLI/CLICommandRuntime.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ package struct CLICommandRuntime {
2525
return 1
2626
}
2727

28+
if let removedFlagMessage = invocation.removedFlagMessage {
29+
logSink.error(removedFlagMessage)
30+
return 1
31+
}
32+
2833
if invocation.serverOnlyFlag != nil {
2934
logSink.error(
3035
"This option is only supported by xcode-mcp-proxy-server (proxy server)."

Sources/ProxyCLI/ProxyCLIInvocationScanner.swift

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import Foundation
2+
import XcodeMCPProxy
23

34
package struct ProxyCLIAdapterScan {
45
package var showHelp = false
56
package var usesRemovedURLHelper = false
7+
package var removedFlagMessage: String?
68
package var hasExplicitURL = false
79
package var hasStdioFlag = false
810
package var serverOnlyFlag: String?
@@ -14,8 +16,7 @@ package struct ProxyCLIServerScan {
1416
package var hasListenFlag = false
1517
package var hasHostFlag = false
1618
package var hasPortFlag = false
17-
package var hasXcodePIDFlag = false
18-
package var hasLazyInitFlag = false
19+
package var hasConfigFlag = false
1920
package var hasRefreshCodeIssuesModeFlag = false
2021
package var forceRestart = false
2122
package var dryRun = false
@@ -27,6 +28,7 @@ package struct ProxyCLIInstallScan {
2728

2829
package enum ProxyCLIInvocationScanner {
2930
private static let serverOnlyFlags: Set<String> = [
31+
"--config",
3032
"--listen",
3133
"--host",
3234
"--port",
@@ -35,13 +37,12 @@ package enum ProxyCLIInvocationScanner {
3537
"--upstream-args",
3638
"--upstream-arg",
3739
"--upstream-processes",
38-
"--xcode-pid",
3940
"--session-id",
4041
"--refresh-code-issues-mode",
41-
"--lazy-init",
4242
]
4343

4444
private static let serverOnlyValueFlags: Set<String> = [
45+
"--config",
4546
"--listen",
4647
"--host",
4748
"--port",
@@ -50,20 +51,19 @@ package enum ProxyCLIInvocationScanner {
5051
"--upstream-args",
5152
"--upstream-arg",
5253
"--upstream-processes",
53-
"--xcode-pid",
5454
"--session-id",
5555
"--refresh-code-issues-mode",
5656
]
5757

5858
private static let serverForwardedValueFlags: Set<String> = [
59+
"--config",
5960
"--listen",
6061
"--host",
6162
"--port",
6263
"--upstream-command",
6364
"--upstream-args",
6465
"--upstream-arg",
6566
"--upstream-processes",
66-
"--xcode-pid",
6767
"--session-id",
6868
"--max-body-bytes",
6969
"--request-timeout",
@@ -94,6 +94,16 @@ package enum ProxyCLIInvocationScanner {
9494
case "--stdio":
9595
scan.hasStdioFlag = true
9696
cursor.advancePastCurrentAndOptionalValue(where: { !$0.hasPrefix("-") })
97+
case "--lazy-init":
98+
if scan.removedFlagMessage == nil {
99+
scan.removedFlagMessage = CLIParser.removedLazyInitMessage
100+
}
101+
cursor.advance()
102+
case "--xcode-pid":
103+
if scan.removedFlagMessage == nil {
104+
scan.removedFlagMessage = CLIParser.removedXcodePIDMessage
105+
}
106+
cursor.advancePastCurrentAndOptionalValue(where: { _ in true })
97107
case "--request-timeout":
98108
cursor.advancePastCurrentAndOptionalValue(where: shouldConsumeRequestTimeoutValue)
99109
case let flag where serverOnlyFlags.contains(flag):
@@ -138,19 +148,18 @@ package enum ProxyCLIInvocationScanner {
138148
throw ProxyServerCommandError.message(
139149
"--url is not supported in server mode (use xcode-mcp-proxy)"
140150
)
151+
case "--xcode-pid":
152+
throw ProxyServerCommandError.message(CLIParser.removedXcodePIDMessage)
141153
case "--lazy-init":
142-
scan.hasLazyInitFlag = true
143-
scan.forwardedArgs.append(arg)
144-
cursor.advance()
145-
continue
154+
throw ProxyServerCommandError.message(CLIParser.removedLazyInitMessage)
146155
case "--listen":
147156
scan.hasListenFlag = true
148157
case "--host":
149158
scan.hasHostFlag = true
150159
case "--port":
151160
scan.hasPortFlag = true
152-
case "--xcode-pid":
153-
scan.hasXcodePIDFlag = true
161+
case "--config":
162+
scan.hasConfigFlag = true
154163
case "--refresh-code-issues-mode":
155164
scan.hasRefreshCodeIssuesModeFlag = true
156165
default:

Sources/ProxyCLI/ProxyServerCommandRuntime.swift

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,7 @@ package struct ProxyServerCommandRuntime {
1515
dependencies.stdout(XcodeMCPProxyServerCommand.serverUsage())
1616
return 0
1717
}
18-
XcodeMCPProxyServerCommand.applyDefaults(
19-
from: environment,
20-
to: &options,
21-
resolveXcodePID: dependencies.resolveXcodePID,
22-
stderr: dependencies.stderr
23-
)
18+
try XcodeMCPProxyServerCommand.applyDefaults(from: environment, to: &options)
2419

2520
let isDryRun = options.dryRun || XcodeMCPProxyServerCommand.isTruthy(environment["DRY_RUN"])
2621
if isDryRun {

Sources/ProxyCLI/XcodeMCPProxyCLICommand+Parsing.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ extension XcodeMCPProxyCLICommand {
77
var invocation = CLICommandInvocation()
88
invocation.showHelp = scan.showHelp
99
invocation.usesRemovedURLHelper = scan.usesRemovedURLHelper
10+
invocation.removedFlagMessage = scan.removedFlagMessage
1011
invocation.hasExplicitURL = scan.hasExplicitURL
1112
invocation.hasStdioFlag = scan.hasStdioFlag
1213
invocation.serverOnlyFlag = scan.serverOnlyFlag
@@ -79,6 +80,7 @@ extension XcodeMCPProxyCLICommand {
7980
8081
Notes:
8182
- Proxy server: xcode-mcp-proxy-server
83+
- --config is only supported by xcode-mcp-proxy-server
8284
- Discovery file: \(discoveryFileURL.path)
8385
"""
8486
}

Sources/ProxyCLI/XcodeMCPProxyCLICommand.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ package struct CLICommandLogSink {
2020
package struct CLICommandInvocation {
2121
package var showHelp = false
2222
package var usesRemovedURLHelper = false
23+
package var removedFlagMessage: String?
2324
package var hasExplicitURL = false
2425
package var hasStdioFlag = false
2526
package var serverOnlyFlag: String?

0 commit comments

Comments
 (0)