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.
This commit is contained in:
2026-08-13 16:05:42 +02:00
parent 30e18f92a5
commit 25028e3405
9 changed files with 1219 additions and 198 deletions
+340
View File
@@ -1,6 +1,346 @@
{
"sourceLanguage" : "en",
"strings" : {
"Remote (HTTP)" : {
"localizations" : {
"da" : {
"stringUnit" : {
"state" : "translated",
"value" : "Fjern (HTTP)"
}
},
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Remote (HTTP)"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Distant (HTTP)"
}
},
"nb" : {
"stringUnit" : {
"state" : "translated",
"value" : "Ekstern (HTTP)"
}
},
"sv" : {
"stringUnit" : {
"state" : "translated",
"value" : "Fjärr (HTTP)"
}
}
}
},
"Command: a local program Confab launches itself. Remote (HTTP): an already-running MCP server reachable by URL." : {
"localizations" : {
"da" : {
"stringUnit" : {
"state" : "translated",
"value" : "Kommando: et lokalt program Confab selv starter. Fjern (HTTP): en allerede kørende MCP-server, der kan nås via URL."
}
},
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Befehl: ein lokales Programm, das Confab selbst startet. Remote (HTTP): ein bereits laufender MCP-Server, der über eine URL erreichbar ist."
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Commande : un programme local que Confab lance lui-même. Distant (HTTP) : un serveur MCP déjà en cours d'exécution, accessible par URL."
}
},
"nb" : {
"stringUnit" : {
"state" : "translated",
"value" : "Kommando: et lokalt program Confab selv starter. Ekstern (HTTP): en allerede kjørende MCP-server som kan nås via URL."
}
},
"sv" : {
"stringUnit" : {
"state" : "translated",
"value" : "Kommando: ett lokalt program som Confab själv startar. Fjärr (HTTP): en redan igångkörd MCP-server som nås via URL."
}
}
}
},
"Type" : {
"localizations" : {
"da" : {
"stringUnit" : {
"state" : "translated",
"value" : "Type"
}
},
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Typ"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Type"
}
},
"nb" : {
"stringUnit" : {
"state" : "translated",
"value" : "Type"
}
},
"sv" : {
"stringUnit" : {
"state" : "translated",
"value" : "Typ"
}
}
}
},
"Bearer Token" : {
"localizations" : {
"da" : {
"stringUnit" : {
"state" : "translated",
"value" : "Bearer-token"
}
},
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Bearer-Token"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Jeton Bearer"
}
},
"nb" : {
"stringUnit" : {
"state" : "translated",
"value" : "Bearer-token"
}
},
"sv" : {
"stringUnit" : {
"state" : "translated",
"value" : "Bearer-token"
}
}
}
},
"Environment Variables" : {
"localizations" : {
"da" : {
"stringUnit" : {
"state" : "translated",
"value" : "Miljøvariabler"
}
},
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Umgebungsvariablen"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Variables d'environnement"
}
},
"nb" : {
"stringUnit" : {
"state" : "translated",
"value" : "Miljøvariabler"
}
},
"sv" : {
"stringUnit" : {
"state" : "translated",
"value" : "Miljövariabler"
}
}
}
},
"Extra Headers" : {
"localizations" : {
"da" : {
"stringUnit" : {
"state" : "translated",
"value" : "Ekstra headers"
}
},
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Zusätzliche Header"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "En-têtes supplémentaires"
}
},
"nb" : {
"stringUnit" : {
"state" : "translated",
"value" : "Ekstra headers"
}
},
"sv" : {
"stringUnit" : {
"state" : "translated",
"value" : "Extra headers"
}
}
}
},
"Optional" : {
"localizations" : {
"da" : {
"stringUnit" : {
"state" : "translated",
"value" : "Valgfri"
}
},
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Optional"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Facultatif"
}
},
"nb" : {
"stringUnit" : {
"state" : "translated",
"value" : "Valgfritt"
}
},
"sv" : {
"stringUnit" : {
"state" : "translated",
"value" : "Valfritt"
}
}
}
},
"Key" : {
"localizations" : {
"da" : {
"stringUnit" : {
"state" : "translated",
"value" : "Nøgle"
}
},
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Schlüssel"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Clé"
}
},
"nb" : {
"stringUnit" : {
"state" : "translated",
"value" : "Nøkkel"
}
},
"sv" : {
"stringUnit" : {
"state" : "translated",
"value" : "Nyckel"
}
}
}
},
"Value" : {
"localizations" : {
"da" : {
"stringUnit" : {
"state" : "translated",
"value" : "Værdi"
}
},
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Wert"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Valeur"
}
},
"nb" : {
"stringUnit" : {
"state" : "translated",
"value" : "Verdi"
}
},
"sv" : {
"stringUnit" : {
"state" : "translated",
"value" : "Värde"
}
}
}
},
"None configured" : {
"localizations" : {
"da" : {
"stringUnit" : {
"state" : "translated",
"value" : "Ingen konfigureret"
}
},
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Keine konfiguriert"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Aucun configuré"
}
},
"nb" : {
"stringUnit" : {
"state" : "translated",
"value" : "Ingen konfigurert"
}
},
"sv" : {
"stringUnit" : {
"state" : "translated",
"value" : "Inga konfigurerade"
}
}
}
},
"By Provider" : {
"localizations" : {
"da" : {