7 Commits
Author SHA1 Message Date
rune 017a0dbd9d Parse a pasted JSON args array correctly instead of mangling each token
Real incident: pasting gethomepage.dev's example args array (JSON,
quotes/commas/brackets and all) into the plain "Arguments" field
produced tokens like "mcp-remote," with the comma baked in, which
crashed npx with EINVALIDTAGNAME on the literal package name
"mcp-remote,". The existing char-by-char tokenizer treats quote
characters as its own quoting mechanism and consumes them, so a JSON
array's per-item quotes never get stripped and commas outside them
become part of the token.

parseArguments now tries decoding a well-formed JSON array of strings
first (only when the whole trimmed input is bracket-wrapped valid
JSON), falling back to the original shell-style tokenizer otherwise —
so pasting a server's args straight from its JSON config now works.
Tooltip and help doc updated; a partial paste (e.g. missing the opening
bracket) still isn't valid JSON and falls back as before, documented as
a known limitation rather than silently guessed at.
2026-08-26 14:26:45 +02:00
rune 41f0b8f083 Fix Edit/Save not applying to an already-connected External MCP server
ExternalMCPManager.reconfigure() only started brand-new clients — a
server ID that already had a client (in ANY state, including .crashed)
was silently skipped even when its settings had just changed. Editing a
server in Settings and clicking Save persisted correctly but never
reached the live connection, which just kept running with its old
(often broken) config until the next app launch. Rune hit this directly
editing Obsidian's URL/token after the toggle-fields bug corrupted it.

Added ExternalMCPServer: Equatable so reconfigure can detect a changed
config for a still-enabled server and restart it fresh (extracted the
restart-attempt-reset logic already used by retryClient into a shared
restartFresh helper).
2026-08-26 14:12:28 +02:00
rune 57b3477903 Fix External MCP server bugs; add npx/Node.js detection and install help
Root-caused two real issues Rune hit with Obsidian/Homepage external MCP
servers:

1. Toggling a server's enable switch silently wiped transportKind/env/
   url/bearerToken/headers back to stdio defaults (only id/name/command/
   args/isEnabled/timeout/createdAt were preserved) — almost certainly
   how Obsidian's config got corrupted into an empty-command stdio entry
   despite never being edited directly. Fixed via
   ExternalMCPServer.withEnabledToggled(), which flips only isEnabled.

2. npx (installed via Homebrew) was invisible to Confab because GUI apps
   only inherit launchd's minimal PATH, not the Terminal PATH. Tried
   spawning the user's login shell to ask for its real PATH — this
   caused two real hangs in one session (first an -ilc pipe deadlock,
   then a waitUntilExit()/CFRunLoop reentrancy issue even after fixing
   that) and was abandoned entirely in favor of LoginShellEnvironment:
   deterministic, subprocess-free directory probing (Homebrew, MacPorts,
   Volta, nvm's alias file) that can't hang by construction.

Also added:
- Edit capability for existing External MCP servers (previously only
  Add/Toggle/Delete) — the second thing Rune explicitly asked for, and
  the way to fix a corrupted entry like Obsidian's without deleting it.
- MCPClientError.commandNotFound: a stdio server's command is checked
  against PATH up front in StdioMCPTransport.prepare() and fails
  immediately with a clear reason instead of cycling through 3 rounds of
  crash/restart backoff (5s/15s/30s) for a permanently-missing binary.
- A "Get Node.js" button appears when this happens, opening a sheet with
  a copyable `brew install node`, a one-click install (via
  NodeInstallHelper, using the terminationHandler/readabilityHandler
  pattern already proven safe elsewhere in this file — deliberately not
  waitUntilExit()), or a nodejs.org link if Homebrew isn't present.
- ExternalMCPManager.retryClient(id:) to manually retry after fixing the
  underlying cause.
- Help book: new "Servers That Use npx" section, updated Server Status
  section, updated Settings blurb.

37 new/changed tests covering the toggle fix, PATH probing, the
commandNotFound fast-fail path, and missing-command detection — full
suite (374 tests) passes clean.
2026-08-26 14:02:17 +02:00
rune acda09942c Fix actor-isolation warnings in ExternalMCPManager's tool conversion
ExternalMCPManager.convertToolDefinition/convertInputSchema were made
nonisolated static func in an earlier session for direct unit testing,
but this project's -default-isolation=MainActor makes every type
implicitly main-actor-isolated unless marked nonisolated — so those
functions referencing Tool.Function.Parameters.Property's init and
ExternalMCPServer.slug (both plain data types with no explicit
isolation) triggered "main actor-isolated ... can not be referenced
from a nonisolated context" warnings, surfacing as Xcode's opaque
"exit code 0 but produced no further output" compile failure in a
Release build.

Marked Tool (and all its nested types) in AIProvider.swift, and
ExternalMCPServer/MCPTransportKind/MCPToolDefinition/MCPInputSchema/
MCPPropertySchema in ExternalMCPModels.swift, nonisolated at the type
level - they're plain DTOs for JSON request/response mapping with no
reason to be actor-isolated at all. Verified with a clean Release
build (matching how the warnings were originally surfaced).
2026-08-16 15:24:48 +02:00
rune 25028e3405 Add env-var support and native HTTP transport for External MCP Servers
External MCP Servers previously only spoke stdio (spawn a local
command + args). Adds:
- env vars for stdio servers (merged into the subprocess environment,
  not embedded in the args string), with a masked key-value editor
- a native Streamable HTTP transport (URL + Bearer token + custom
  headers), so HTTP-based MCP servers like Obsidian's Local REST API
  plugin connect directly without needing npx/Node.js as a bridge

Introduces an MCPTransport abstraction (stdio/HTTP) so ExternalMCPClient
stays transport-agnostic — mirrors how Provider.swift already abstracts
AI backends in this codebase.

Also fixes a real crash found via live testing against Obsidian:
convertInputSchema force-unwrapped a tool parameter's `type`, which
isn't required by JSON Schema — Obsidian's plugin was the first real
server to send a parameter without one. Live-verified end to end
(vault search/read/write/edit) before this commit, per the project's
standing rule to hold external-service-dependent changes until they're
actually confirmed working, not just compiling and passing tests.
2026-08-13 16:05:42 +02:00
runeandClaude Sonnet 5 cf3f4ebfe4 Relicense oAI from AGPL-3.0-or-later to PolyForm Noncommercial 1.0.0
Switches the project from AGPL to a source-available license that
restricts commercial use — selling oAI or any part of it, standalone
or bundled into another product/service, now requires a separate
commercial license from the copyright holder. Noncommercial use,
study, modification, and sharing remain fully permitted.

Updates: LICENSE (canonical PolyForm Noncommercial 1.0.0 text +
commercial licensing contact note), SPDX headers and file-header
boilerplate across all Swift source files, the in-app About dialog's
license link (+ its localization catalog entry), README.md and
DEVELOPMENT.md license sections.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-15 11:16:09 +02:00
runeandClaude Sonnet 5 e6f965ff19 Add external MCP server support (stdio JSON-RPC)
Lets the AI connect to any external stdio MCP server (e.g. safaridriver
--mcp) configured in Settings, with tools auto-discovered and prefixed
by server slug. Includes crash detection with backoff restart (5s/15s/30s)
and a Settings UI to add/enable/disable/remove servers.

Fixes the temp-dir allowlist in MCPService.isPathAllowed to also match
/tmp and /private/tmp (not just NSTemporaryDirectory(), which resolves
to a different per-user Darwin temp dir) so the MCP file tools can
actually read files external servers and image generation write there.
Also switches the Add Server sheet's argument parsing to a quote-aware
tokenizer so args containing spaces survive intact.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-14 08:01:00 +02:00