Log request/response bodies and added version#32
Log request/response bodies and added version#32romanr wants to merge 2 commits intolynnswap:mainfrom
Conversation
Improve observability and expose the tool version to clear the confusion. - Enhance HTTPHandler to capture an optional client name (from Mcp-Client-Name header or JSON request body) and include it in request/response metadata. - Added detailed request and response body logging with a 4096-byte preview, byte counts, truncated marker, and related metadata (id, method, path, remote, client, session, status). - Introduce helper functions for body logging and client name extraction. - Added Version in startup stdout log. - Added --version cli parameter
There was a problem hiding this comment.
Pull request overview
This PR improves runtime observability for the HTTP proxy and reduces user confusion by exposing the tool’s version via both startup logs and new --version CLI flags.
Changes:
- Add a shared
Version.currentconstant and surface it in server startup logs. - Add
--versionhandling to bothxcode-mcp-proxyandxcode-mcp-proxy-server. - Extend HTTP request/response logging to include optional client name metadata and body previews.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| Sources/XcodeMCPProxyCommands/XcodeMCPProxyServerCommand.swift | Adds --version support and wires new option through server command parsing/execution. |
| Sources/XcodeMCPProxyCommands/XcodeMCPProxyCLICommand.swift | Adds --version support to the CLI adapter command. |
| Sources/XcodeMCPProxy/Version.swift | Introduces a shared version constant consumed by commands and server logs. |
| Sources/XcodeMCPProxy/ProxyServer.swift | Includes version in the server “listening on …” startup log line. |
| Sources/XcodeMCPProxy/HTTPHandler.swift | Adds client-name extraction and request/response body preview logging with additional metadata. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review. Take the survey.
| if data.count > maxLoggedBodyBytes { | ||
| metadata["truncated"] = .string("true") | ||
| } | ||
| logger.info(Logger.Message(stringLiteral: label), metadata: metadata) |
There was a problem hiding this comment.
Request/response bodies are logged at .info with a plaintext preview. This can leak sensitive data (tokens, prompts, tool arguments) into logs and can significantly increase log volume. Consider gating body logging behind a debug log level and/or an explicit config/env flag, and/or redacting known sensitive fields before logging.
| logger.info(Logger.Message(stringLiteral: label), metadata: metadata) | |
| logger.debug(Logger.Message(stringLiteral: label), metadata: metadata) |
| @@ -18,6 +19,7 @@ package struct ProxyServerOptions { | |||
| package init( | |||
| forwardedArgs: [String], | |||
| showHelp: Bool, | |||
There was a problem hiding this comment.
Adding showVersion to ProxyServerOptions changes its initializer signature; any existing initializations (notably in ServerCommandTests) need to be updated to pass showVersion to keep tests compiling.
| showHelp: Bool, | |
| showHelp: Bool, | |
| hasListenFlag: Bool, | |
| hasHostFlag: Bool, | |
| hasPortFlag: Bool, | |
| hasXcodePidFlag: Bool, | |
| hasLazyInitFlag: Bool, | |
| forceRestart: Bool, | |
| dryRun: Bool | |
| ) { | |
| self.init( | |
| forwardedArgs: forwardedArgs, | |
| showHelp: showHelp, | |
| showVersion: false, | |
| hasListenFlag: hasListenFlag, | |
| hasHostFlag: hasHostFlag, | |
| hasPortFlag: hasPortFlag, | |
| hasXcodePidFlag: hasXcodePidFlag, | |
| hasLazyInitFlag: hasLazyInitFlag, | |
| forceRestart: forceRestart, | |
| dryRun: dryRun | |
| ) | |
| } | |
| package init( | |
| forwardedArgs: [String], | |
| showHelp: Bool, |
| if options.showVersion { | ||
| dependencies.stdout("xcode-mcp-proxy-server \(Version.current)") | ||
| return 0 | ||
| } |
There was a problem hiding this comment.
New --version behavior isn't covered by tests. There are already comprehensive command tests in Tests/XcodeMCPProxyTests/ServerCommandTests.swift; please add coverage asserting --version prints the version string and exits 0 (and that it doesn't attempt to start the server).
| if invocation.showVersion { | ||
| dependencies.stdout("xcode-mcp-proxy \(Version.current)") | ||
| return 0 | ||
| } |
There was a problem hiding this comment.
New --version behavior isn't covered by tests. There are existing CLI command tests in Tests/XcodeMCPProxyTests/CLICommandTests.swift; please add a test that --version prints xcode-mcp-proxy <version> and exits 0 without creating an adapter.
| id: UUID().uuidString, | ||
| method: head.method.rawValue, | ||
| path: path, | ||
| remoteAddress: remoteAddressString(for: context.channel) | ||
| remoteAddress: remoteAddressString(for: context.channel), | ||
| clientName: nil |
There was a problem hiding this comment.
The initial "HTTP request" log is emitted before clientName is extracted, so it will never include the client name (even when present in the Mcp-Client-Name header). Consider populating clientName from headers in handleRequest before calling logRequest, and only falling back to body parsing for POST requests if needed.
| let clientName = clientName(from: head.headers, bodyData: bodyData) | ||
| let requestLog = requestLog.withClientName(clientName) | ||
| logRequestBody(bodyData, requestLog: requestLog) |
There was a problem hiding this comment.
clientName(from:head.headers, bodyData:) will JSON-parse the body to extract the client name, and then handlePost JSON-parses the same bodyData again immediately after. To avoid double parsing (and extra work on every request), parse the JSON body once and reuse the parsed object both for method routing and client-name extraction.
| let previewData = data.prefix(maxLoggedBodyBytes) | ||
| let previewText = String(decoding: previewData, as: UTF8.self) | ||
| metadata["preview"] = .string(previewText) | ||
| if data.count > maxLoggedBodyBytes { | ||
| metadata["truncated"] = .string("true") | ||
| } | ||
| logger.info(Logger.Message(stringLiteral: label), metadata: metadata) |
There was a problem hiding this comment.
logBody unconditionally logs a 4KB preview of every HTTP request and response body (metadata["preview"]) at info level, along with byte length, path, and optional session/client identifiers. These bodies can easily contain sensitive material (e.g., API keys, access tokens, secrets in payloads, or proprietary code and prompts), so anyone with access to logs or a multi-tenant logging backend could recover that data. Consider restricting full body logging to an opt-in debug mode, redacting or hashing sensitive fields, and/or ensuring logs never contain authentication secrets or other high-sensitivity content.
| let previewData = data.prefix(maxLoggedBodyBytes) | |
| let previewText = String(decoding: previewData, as: UTF8.self) | |
| metadata["preview"] = .string(previewText) | |
| if data.count > maxLoggedBodyBytes { | |
| metadata["truncated"] = .string("true") | |
| } | |
| logger.info(Logger.Message(stringLiteral: label), metadata: metadata) | |
| // Always log high-level request/response information at info level, | |
| // but do not include body content to avoid leaking sensitive data. | |
| logger.info(Logger.Message(stringLiteral: label), metadata: metadata) | |
| // Only log body previews in debug/loopback scenarios to reduce | |
| // the risk of exposing sensitive information in logs. | |
| if isLoopbackDebugEndpointEnabled || logger.logLevel <= .debug { | |
| var debugMetadata = metadata | |
| let previewData = data.prefix(maxLoggedBodyBytes) | |
| let previewText = String(decoding: previewData, as: UTF8.self) | |
| debugMetadata["preview"] = .string(previewText) | |
| if data.count > maxLoggedBodyBytes { | |
| debugMetadata["truncated"] = .string("true") | |
| } | |
| logger.debug(Logger.Message(stringLiteral: label), metadata: debugMetadata) | |
| } |
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Improve observability and expose the tool version to clear the confusion.