Merge upstream mattt/iMCP (1.5.4) - #1
Merged
Merged
Conversation
Info.plist has declared SUFeedURL/SUPublicEDKey (and the entitlements reserve Sparkle's -spks/-spki XPC mach ports) since day one, but no target ever linked the Sparkle package or instantiated an updater. The result: every install is permanently frozen at whatever version it first shipped, silently, with no error and no "Check for Updates" menu item to surface the problem. This is why users can stay on known-crashy builds indefinitely: my own installed copy was v1.4.0 (Jan 2026), three months and one release behind the fix for the Bonjour-disconnect crash in mattt#166/1.4.1, with no way to find out short of reading raw Claude Desktop MCP logs. - Add the Sparkle SPM package to the iMCP app target. - Construct SPUStandardUpdaterController(startingUpdater: true, ...) in App.swift so Sparkle's normal periodic background check actually runs, using the existing SUFeedURL/SUPublicEDKey. - Add a "Check for Updates..." item to the menu bar dropdown, wired to updater.checkForUpdates() and disabled while a check is unavailable. Verified: package graph resolves, `xcodebuild build -scheme iMCP` succeeds, the built app launches and stays up with Sparkle.framework loaded, `imcp-serverTests` (incl. ServiceGroupConfigurationTests) still pass, and `swift format lint --strict --recursive .` is clean. Note: https://downloads.imcp.app/appcast.xml (the configured SUFeedURL) currently fails to resolve in DNS (NXDOMAIN), so this alone won't help until that feed is live — flagging separately in the PR description.
…ttt#151) The MCP SDK's NetworkTransport has reconnection logic that creates unstructured tasks holding CheckedContinuation references. For server-side incoming connections, this reconnection path can cause double-resume when connections are cancelled, crashing the app. Disable reconnection and heartbeats for server-accepted connections since they are client features — if a client disconnects, it should reconnect itself. Also make removeConnection idempotent to prevent stop() being called twice on the same connection. Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Cancel the Bonjour browser on every discovery exit path imcp-server retries discovery in a loop, creating a new NWBrowser each time. The 30-second timeout path never cancelled the previous browser, so every timed-out attempt left a DNSServiceBrowse connection to mDNSResponder open. On a machine where the app wasn't running, that leaked one connection every ~35 seconds until the daemon ran out of descriptors and DNS failed for every process. Move discovery into BonjourDiscovery.discoverEndpoint, which cancels the browser in a defer, and share the file with the CLI test target so the behavior is covered by a test against a real NWBrowser. Also drop a module-level browser that was never started and a write-only property. Fixes mattt#192. Fixes mattt#174. * Use a valid absent service type and require the timeout error in the test
* Exempt connections awaiting approval from the setup timeout handleNewConnection races connection setup against a 10-second stall timer, but setup includes waiting for the user to answer the approval dialog. A first connection from a new client was therefore torn down before anyone could click Allow, which is why the first attempt always failed and the retry (now trusted) succeeded. Track connections whose setup is blocked on the approval handler and skip them in the timeout check. The handshake before the prompt is still covered, and a denied dialog still closes the connection. Fixes mattt#193. * Pause the setup timer during approval and restart it afterwards A one-shot timer that merely skipped connections awaiting approval could still close a connection approved just before the deadline, and stopped enforcing anything once approval finished. Keep a timer per connection: cancel it when the approval handler starts, give the connection a fresh budget when approval returns, and clear it when setup ends.
…vents (mattt#175) * Set @id on calendar events from EKEvent.eventIdentifier Without an identifier, Event values produced from EKEvent encoded as JSON-LD blank nodes. Two events with the same name/dates/calendar serialised to byte-identical objects, and downstream JSON-LD-aware consumers collapsed them into a single node. Symptom: events_fetch appeared to silently filter out duplicate-named events. Populate Event.identifier from EKEvent.eventIdentifier in both events_fetch and events_create so each event carries a stable @id, matching what Ontology already does for Person (CNContact.identifier) and ItemList (EKCalendar.calendarIdentifier). * Fix date-only inputs being misread as having a timezone suffix ISO8601DateFormatter.lenientDate(fromISO8601String:) ran a regex to decide whether the input already carried a timezone offset and thus should not be re-parsed in local time. The regex #"([Zz]|[+-]\\d{2}(:?\\d{2})?)$"# matches the trailing -DD of any bare yyyy-MM-dd date — "2026-06-15" looks like it ends with a -15 offset — so date-only inputs returned nil instead of falling through to the "yyyy-MM-dd" DateFormatter fallback. In events_fetch this manifested as a silent regression: when the caller passed e.g. start="2026-06-02", end="2026-06-15", parsing failed for both, hasStart/hasEnd stayed false, and the tool fell back to its 'now → now + 1 week' default range. The user saw events from the current week and assumed events past that point had been filtered out (in this case two same-name events where only the earlier one was in the default range). Gate the timezone-suffix check on dateString.count > 10 so it can't fire on a bare yyyy-MM-dd. The day component can no longer collide with the offset branch of the regex; date-only inputs now reach the "yyyy-MM-dd" fallback as intended.
Madrid 0.4.0 deprecated fetchMessages(with:in:limit:), and the project treats warnings as errors, so any bump past 0.1.0 failed the build at the call in Messages.swift. Move that call to fetch(_:) with an equivalent predicate, and pin 0.5.0, which reads the write-ahead log and exposes Message.chatID for the follow-up Messages PRs.
* Bump Madrid to 0.4.0 iMCP has been pinned to Madrid 0.1.0. Madrid has since shipped 0.2.0, 0.3.0, and 0.4.0. This bumps the pin to 0.4.0 with no behaviour change, so that later work can use APIs added in those releases (notably `Message.readAt` / `Message.isRead` from 0.3.0, for mattt#154). The version requirement was already `upToNextMajorVersion` from 0.1.0, which 0.4.0 satisfies, so this is mechanically a re-resolve. The declared minimum is raised alongside the lockfile to keep the two in agreement. Source compatibility was verified by compiling iMCP's Madrid call sites (`Database()`, `Database(path:)`, `fetchParticipant(matching:)`, `fetchMessages(with:in:limit:)`, and the `Message` properties read in `messages_fetch`) against 0.4.0. They compile unchanged. Behaviour was checked against 0.1.0 for the two cases that could have differed, and matches in both: - With no participants, 0.1.0 skipped the participant condition. In 0.4.0 the deprecated `fetchMessages` shim likewise omits the predicate for an empty set, and `.and([])` compiles to an absent WHERE clause rather than a false one. - Message ordering was `ORDER BY m.date DESC` in 0.1.0, and the 0.4.0 default sort is `[.date(.descending), .id(.descending)]` — the same order, with an added tiebreak. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Add read status to messages_fetch Closes mattt#154. Adds an optional `isRead` filter to `messages_fetch`, and reports read status on every message it returns: - `isRead` (input, optional): if true, fetch read messages; if false, unread incoming; if omitted, fetch all. - `isRead` (output): present on every message. - `dateRead` (output): ISO 8601, omitted when the message is unread. The tri-state input mirrors `reminders_fetch`'s `completed` parameter rather than introducing a single-purpose `unreadOnly` flag, so the same parameter also expresses "only messages I have read". Filtering for unread excludes outgoing messages. Read receipts populate a read date on messages you sent, so without that guard "unread" would also report your own messages the recipient has not read yet. Read status is derived from Madrid's `Message.readAt`, which is the read timestamp `chat.db` records rather than a dedicated unread flag. A message marked read without a recorded timestamp therefore counts as unread, so this is a superset of genuinely-unread and may not match the Messages app badge exactly. Known limitation: `messages_fetch` fetches `max(limit, 1024)` candidates and filters them down to `limit`, so the filter only sees the most recent 1024 messages. A large *read* backlog can push unread messages outside that window. Filtering in SQL instead would remove the limitation, but Madrid has no read-status predicate to compose; mattt/Madrid#17 proposes one. The tool's schema would not change if the filtering later moves into the query. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: mattt <mattt@me.com>
"Copy server command to clipboard" silently copies with no feedback, so there's no way to tell the click registered. Play the existing system Pop sound cue on copy, matching the confirmation pattern already used elsewhere in the app. Claude-Session: https://claude.ai/code/session_01QLDiNzrDds62dnD9NoCEJV Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
VoiceOver announced each service's icon and name as two separate, unlabeled elements with no indication of on/off state. Add an accessibility label and value so each toggle reads as e.g. "Calendar, Enabled". Claude-Session: https://claude.ai/code/session_01QLDiNzrDds62dnD9NoCEJV Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Removing a single trusted client required a right-click context menu or select-then-Delete, neither of which is discoverable, while bulk removal already had a visible "Remove All" button. Add a visible per-row remove button alongside the existing context menu. Claude-Session: https://claude.ai/code/session_01QLDiNzrDds62dnD9NoCEJV Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
The Trusted Clients section didn't say how entries get there, that trusted clients connect silently apart from a notification, or that trust is keyed to the client's self-reported name. Spell all three out: an actionable empty state, a fuller section caption, and a footnote on name-based identity. Claude-Session: https://claude.ai/code/session_01QLDiNzrDds62dnD9NoCEJV Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Registers the app as a login item via SMAppService with a toggle in Settings > General. State is re-read on appear so changes made in System Settings > Login Items stay in sync, and the toggle reverts if registration fails. Claude-Session: https://claude.ai/code/session_01QLDiNzrDds62dnD9NoCEJV Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* Fix Settings window intermittently not appearing iMCP is an accessory app (LSUIElement = YES, no Dock icon). SwiftUI's openSettings() routes through a responder-chain action that only reliably lands when the app is already frontmost, which is ambiguous right as the custom menu bar popover dismisses — causing the window to randomly fail to open. Every other window-showing action in this file (About iMCP, connection approval) already works around this by calling NSApp.activate(ignoringOtherApps: true) first; Settings was the one path missing it. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QLDiNzrDds62dnD9NoCEJV * Trim comment to one line Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QLDiNzrDds62dnD9NoCEJV --------- Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Clang module files record the mtime of the SDK headers they were built
from. When the runner image updates its Xcode, a restored
ModuleCache.noindex is rejected wholesale ("has been modified since the
module file was built"), which is what broke main after mattt#188 landed.
Keep caching DerivedData and the SwiftPM cache, but let the module cache
rebuild; it is cheap.
The dialog said "This will give the client access to enabled services" without naming them, so users had no way to know what they were actually granting access to. Pass the currently enabled service names through to the dialog and list them explicitly. Claude-Session: https://claude.ai/code/session_01QLDiNzrDds62dnD9NoCEJV Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com> Co-authored-by: mattt <mattt@me.com>
MenuBarExtra's window-style panel keeps its stale taller frame when the Services section collapses, leaving the compact content vertically centered in an oversized translucent panel — and reopening the menu doesn't fix it, since the panel is only sized at first presentation. Track the content's measured height and resize the panel window to match, both on content changes and each time the menu is presented. The window is looked up by class name directly because MenuBarExtraAccess's introspection fails to find it on macOS 26. The collapse animation is removed so the frame can't lag the content, and the content is top-aligned as a fallback. Claude-Session: https://claude.ai/code/session_01QLDiNzrDds62dnD9NoCEJV Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* Add disabled-tools state and enforce it in the MCP server A JSON-encoded set of disabled tool names is persisted the same way as trusted clients and pushed into ServerNetworkManager the same way as service bindings. ListTools omits disabled tools and CallTool rejects them, so a client holding a stale list still cannot invoke one. Connected clients get tools/listChanged on every change. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QLDiNzrDds62dnD9NoCEJV * Guard disabled-tools pushes against out-of-order delivery Each write to ServerController.disabledTools fired an unstructured Task carrying a snapshot to the actor. Two rapid writes could reach the actor out of order, and the actor's equality guard would accept the stale snapshot, leaving live ListTools/CallTool enforcement diverged from the persisted value until the next toggle. A monotonic generation counter is now attached to each push; the actor discards any delivery whose generation is not newer than the last one it applied. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QLDiNzrDds62dnD9NoCEJV * Add Services section to Settings with per-tool toggles Each service lists its tools with human-readable titles, descriptions, and a Read-only badge from the tool annotations, plus a switch per tool backed by the disabled-tools set. The service master toggle is the same binding the menu uses. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QLDiNzrDds62dnD9NoCEJV * Fix accessibility and initial paint of service tool toggles Two Important findings from the Task 2 review, both in App/Views/ServicesSettingsView.swift: 1. `.accessibilityElement(children: .combine)` on each tool row merged the label and the interactive Toggle into a single non-actionable AX element, so VoiceOver users could not flip a tool's switch. Removed the combine modifier and gave both the per-tool and per-service Toggle real accessibility labels (tool title / service name) with `.labelsHidden()` to keep the visual layout unchanged while exposing a named, actionable control to assistive technology. 2. Freshly rendered/scrolled switch rows could transiently paint "off" while the persisted value was "on" (view identity was reused across rows with different underlying state). Per the controller's ruling, mitigated by keying each switch's view identity to its current value via `.id("\(name)-\(currentValue)")`, so a newly materialized row constructs its switch with the right state. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QLDiNzrDds62dnD9NoCEJV * List each service's tools in its menu row tooltip Hovering a service row now shows what enabling it exposes, using the tool annotation titles. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QLDiNzrDds62dnD9NoCEJV * Route Settings service toggles through activation and client notification The Settings service toggle (ServicesSettingsView.serviceBinding) wrote config.binding.wrappedValue directly, bypassing the activation flow that the menu path (ServiceToggleView) already performs. Enabling a service like Calendar from Settings never triggered the macOS permission prompt, and there was no revert-to-off if activation failed or was denied. It also never pushed the updated bindings to ServerNetworkManager, so connected MCP clients were not notified via tools/listChanged when a service was toggled from Settings (only the menu scene's ContentView .onChange did this). Add ServerController.setService(_:enabled:), which updates the binding, calls config.service.activate() when enabling an unactivated service (reverting on failure), and pushes currentServiceBindings to the network manager so notifyToolListChanged fires for connected clients. Route ServicesSettingsView.serviceBinding's setter through it, removing the now-redundant manual objectWillChange.send() from the view. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QLDiNzrDds62dnD9NoCEJV --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: mattt <mattt@me.com>
* Add Call History service for phone call log access Adds a new MCP service that reads the macOS CallHistory database (synced from iPhone) at ~/Library/Application Support/CallHistoryDB/ CallHistory.storedata. Exposes a callhistory_fetch tool with filtering by participant, date range, and call type (incoming/outgoing/missed). Follows the same patterns as the existing Messages service: direct SQLite access, security-scoped bookmark persistence, and file picker fallback for sandboxed environments. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Fix CallHistory SQL schema and CLI graceful shutdown - CallHistory: Use ZADDRESS and ZNAME columns directly from ZCALLRECORD instead of JOINing on a non-existent ZHANDLE foreign key. The macOS CallHistory database stores phone numbers inline, not via a handle table. - CallHistory: Include contact name in results when available. - CLI: Add successTerminationBehavior: .gracefullyShutdownGroup to prevent ServiceGroup from crashing with a fatal error on normal disconnect. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Make call_type an enum in the schema * Fix formatting for swift-format lint --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: mattt <mattt@me.com>
The private half of the previous key was not available, so update checks are signed with a new pair. No shipped build contains Sparkle yet, so nothing in the field verifies against the old key.
…tt#205) * Automate releases with GitHub Actions and a Sparkle appcast step Scripts/release.sh gains an appcast command that signs the release zip with the Sparkle EdDSA key, generates appcast.xml, and uploads it next to the release asset, refusing to continue when the app's SUPublicEDKey doesn't match the signing key. notarytool can now take an App Store Connect API key instead of a keychain profile, uploads and release creation honor DRY_RUN, and the release zip is built after stapling so the shipped bundle carries the ticket. The Release workflow runs the same steps on a version tag push, importing the Developer ID certificate and provisioning profile from repository secrets. Manual runs default to a dry run and attach the build products to the workflow run. * Publish releases only after every asset is in place Create the GitHub release as a draft, upload the zip and the appcast, then publish it and mark it latest, so a failure partway through never leaves a half-built release public. Publishing requires the version tag at HEAD, and manual workflow runs that publish must start from that tag. Capture the notarization credential helper's output so its validation failure stops the script. * Sign archives with the Developer ID identity and make publishing rerunnable The project's Release configuration signs with an Apple Development identity, which a release machine doesn't have, so archive with the configured Developer ID certificate, routing the provisioning profile to the app target alone because the embedded CLI has its own bundle identifier. Reuse an existing draft release on a rerun and refuse to touch one that is already published. Stop the local release flow after the tag push, since the Release workflow publishes from there. * Require the release tag on origin before creating a release Check that the tag has been pushed and matches the local tag, and pass --verify-tag so gh never mints a tag from the default branch. * Push only the release tag Every pushed version tag starts a release, so pushing all local tags could publish more than one.
A runner image update changes the toolchain but not the cache key, so the restored module cache no longer matched and every build failed until the caches were deleted by hand. Reading the build number from xcodebuild makes the key change with the image.
When the MCP client closed stdin, the stdin handler returned without an error and MCPService browsed Bonjour again immediately, so each new session died at once and the helper spun until it was killed. stdin EOF now fails the session, a clean return terminates the helper so the client can relaunch it, and the connection is cancelled before the task group unwinds so a pending receive can't keep it alive. The menubar app registers the approval handler before advertising the service, and StdioProxy and MCPService move out of the CLI entry point.
Wire up Sparkle so the app actually checks for updates
…ist by the user's default order Flip contactType on a mutable copy instead of re-implementing the mapping, so company-toggled cards keep URLs, birthday, social profiles, IM, and relations. Cards with only an organization name stay excluded. Route contacts_me through the same helper. Follow-up to mattt#206.
Every occurrence of a recurring event shares one identifier, and event(withIdentifier:) resolves it to the first occurrence, so thisEvent could remove the wrong one and futureEvents could remove the whole series. An optional start date now refetches the exact occurrence, recurring events without one are rejected, and an unknown span throws instead of defaulting to thisEvent. Follow-up to mattt#209.
* Rename Call History service to Phone * Use green for the Phone service to match the app icon * Add a phone_call tool that dials a number via tel: URL * Model the phone_call result as a CommunicateAction type * Move call history SQLite access into CallHistoryDatabase and CallRecord types * Validate phone tool arguments and request database access only on fetch * Incorporate review feedback * Reject * and # in phone_call numbers since the Phone app will not dial them
…rectly (mattt#219) A second client's request replaced the first window's reference, leaving the first dialog stuck on screen and its client marked pending forever.
mattt#198) The service asked for chat.db alone. Messages keeps that database in WAL mode: every new message is committed to chat.db-wal first and only reaches chat.db when SQLite checkpoints the log, every few MB of writes. With a grant on the single file the log was unreadable, so Madrid opened the database with immutable=1 and messages_fetch could not see anything newer than the last checkpoint, often hours behind, then everything at once. The file picker now asks for ~/Library/Messages (directories only, constrained to a folder named Messages) and the bookmark covers the folder, which lets SQLite read chat.db together with its -wal and -shm companions (Madrid's Database.AccessMode.live). The security scope now stays open until the fetch is done, since SQLite opens the log lazily. A bookmark stored by an earlier version (chat.db alone) keeps working in immutable mode; the first fetch of a launch offers to re-select the folder, and declining is remembered for that launch. Claude-Session: https://claude.ai/code/session_0187kUP2mjhwMfJkwRbP5DG7 Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
…e-ahead log (mattt#220) * Grant the CallHistoryDB folder so phone_calls_fetch can read the write-ahead log The store is a WAL-mode database, and a grant on the file alone leaves its -wal and -shm companions unreadable, so every read failed with "authorization denied". * Trim the call history README note
Dismissing the folder picker threw invalidFileSelected. During a Messages upgrade from a file grant, that error reached the service toggle and turned Messages off, although the old grant still worked and the alert's Cancel button already kept it. The picker now returns nil on dismissal; Messages keeps the old grant during an upgrade and otherwise reports userDeclinedAccess, and Phone reports the same instead of a wrong selection. The once-per-launch warning about a file-only Messages grant was a plain check-and-set on a shared flag that concurrent tool calls could race. It is now guarded by a lock, with the log call outside it.
The SDK ends its message loop when a client disconnects but leaves the socket open until the health poll detects it. Await loop completion to release the connection immediately, and guard against repeated stops during app shutdown.
* Request service permissions from tool handlers * Share location authorization across concurrent requests
) * Replace MenuBarExtraAccess with a local menu panel controller The app used the package only to dismiss the menu and to resize its panel. A small internal controller now gets the panel window from an embedded NSView, orders it out for menu commands, and resizes it to fit its content. * Dismiss the menu with SwiftUI's dismiss action Ordering the panel window out hides it while MenuBarExtra still considers the status item presented, so the next click on the icon does nothing. The menu buttons now call the dismiss action from the environment. * Shorten the menu dismissal comment
* Keep iMCP running when its menu bar item is removed * Reflow updater comment with semantic line breaks * Open Settings on launch when the menu bar item is hidden
Also points the Ontology, Madrid, and JSONSchema dependencies at their mattt/ repositories instead of the loopwork-ai redirects. Re-resolving JSONSchema at its new location picks up 1.3.1, a patch release within the existing range.
Resolve conflicts with the local fork changes: - LAN mode: keep the allowLANConnections toggle; loopback-only mode now also sets upstream's requiredLocalEndpoint (mattt#242). Before the listener starts, the setting replaces the idle listener instead of starting it. - Settings: keep the Network Access section alongside upstream's menu bar / launch-at-login settings. - release.sh: keep the local `install` command next to the new appcast and publish commands. - project.pbxproj: keep personal team and bundle ID, take upstream version 1.5.4 (build 16). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GAFz1WodA51dwyzn4eyjbc
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GAFz1WodA51dwyzn4eyjbc
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Merges 56 commits from mattt/iMCP
main, up to release 1.5.4.Conflict resolutions
ServerController.swift: keeps the LAN toggle (allowLANConnections) and adds upstream's per-tool disable feature. When LAN is off, the listener also sets upstream's loopbackrequiredLocalEndpoint(Accept connections only over loopback mattt/iMCP#242), so only this Mac can connect. When LAN is on, that restriction is removed. If the setting is applied before the listener has started, it replaces the idle listener instead of starting it early.SettingsView.swift: the Network Access section sits alongside upstream's Show in Menu Bar and Launch at Login settings. Its toggle is reformatted to passswift format lint --strict.release.sh: keeps the localinstallcommand next to the newappcast,upload-appcastandpublishcommands.project.pbxproj: keeps the personal team, thecom.primadeluxe.iMCPbundle ID and automatic signing, and takes upstream's version 1.5.4 (build 16).Known limitation
The upstream CLI always connects to the loopback address after Bonjour discovery (
CLI/BonjourDiscovery.swift). LAN mode therefore does nothing forimcp-serverrunning on another machine.Testing
xcodebuildbuild on macOS 26 / Xcode 26.0.🤖 Generated with Claude Code
https://claude.ai/code/session_01GAFz1WodA51dwyzn4eyjbc