From d51ac4a3cbd7c7a317e5c7d3d3f9ef871ad0c251 Mon Sep 17 00:00:00 2001 From: frostebite Date: Mon, 28 Sep 2026 23:29:12 +0100 Subject: [PATCH] feat(clients): auto-configure Pi Adds a Pi configurator so "Configure All Detected Clients" covers Pi the way it already covers Cursor, Codex and Claude Code. Pi differs from every other JSON client here in two ways that both had to be handled rather than assumed: 1. Pi ships no MCP client. As of 0.84.x there is no `mcp` subcommand, no `--mcp-config` equivalent and no `mcp.json` reader anywhere in the distributed package -- its README states "No MCP. Build CLI tools with READMEs, or build an extension that adds MCP support." Writing the config file alone would therefore look successful and do nothing, so the installation steps name the extension explicitly (`pi install npm:pi-mcp-adapter`) and say the restart is required. 2. The file Pi MCP extensions read is the tool-agnostic shared `~/.config/mcp/mcp.json`, not a Pi-owned path. That makes the inherited `IsInstalled` ("does the config path's parent directory exist") wrong in both directions: a Cursor or Claude Code user has a `~/.config/mcp` tree without ever installing Pi (over-report), and a Pi user who has not yet configured any MCP server has no `~/.config/mcp` at all (under-report -- and that is precisely the machine this configurator exists to fix). Presence is therefore keyed on Pi's own agent directory, honouring `PI_CODING_AGENT_DIR`, which nothing else writes. Tests cover both halves: presence follows the agent directory, and a shared config tree with no Pi install beside it does not read as "detected". Both skip (Pass) on a host where the two rules cannot be distinguished, so they cannot pass vacuously in CI. Docs: capability matrix row, the terminal-client list, and the per-client toggle section. --- .../Clients/Configurators/PiConfigurator.cs | 98 +++++++++++++++++++ .../Configurators/PiConfigurator.cs.meta | 11 +++ .../EditMode/Clients/IsInstalledTests.cs | 47 +++++++++ website/docs/getting-started/clients.md | 5 +- 4 files changed, 160 insertions(+), 1 deletion(-) create mode 100644 MCPForUnity/Editor/Clients/Configurators/PiConfigurator.cs create mode 100644 MCPForUnity/Editor/Clients/Configurators/PiConfigurator.cs.meta diff --git a/MCPForUnity/Editor/Clients/Configurators/PiConfigurator.cs b/MCPForUnity/Editor/Clients/Configurators/PiConfigurator.cs new file mode 100644 index 000000000..ade5a703b --- /dev/null +++ b/MCPForUnity/Editor/Clients/Configurators/PiConfigurator.cs @@ -0,0 +1,98 @@ +using System; +using System.Collections.Generic; +using System.IO; +using MCPForUnity.Editor.Models; + +namespace MCPForUnity.Editor.Clients.Configurators +{ + /// + /// Pi (pi.dev) configurator. + /// + /// Two things make Pi different from every other JSON client in this folder, and both are + /// load-bearing rather than cosmetic: + /// + /// 1. Pi has no built-in MCP client. As of pi 0.84.x there is no mcp subcommand, + /// no --mcp-config equivalent, and no mcp.json reader anywhere in the + /// distributed package; its own README states "No MCP. Build CLI tools with READMEs, or + /// build an extension that adds MCP support." Reaching this server from Pi therefore + /// requires an MCP extension, and the unityMCP entry written below is what that + /// extension reads. names the extension explicitly, + /// because on Pi a config file alone is a silent no-op — the exact "configured, enabled, and + /// silently providing nothing" shape this window exists to prevent. + /// + /// 2. The file is shared, so presence cannot be inferred from it. Pi MCP extensions read + /// the tool-agnostic ~/.config/mcp/mcp.json, not a Pi-owned path. That makes the + /// inherited IsInstalled ("does the config path's parent directory exist") wrong in + /// both directions — see . + /// + public class PiConfigurator : JsonFileMcpConfigurator + { + public PiConfigurator() : base(new McpClient + { + name = "Pi", + windowsConfigPath = GetSharedMcpConfigPath(), + macConfigPath = GetSharedMcpConfigPath(), + linuxConfigPath = GetSharedMcpConfigPath() + }) + { } + + /// + /// Pi-exclusive presence check, deliberately NOT the inherited + /// ParentDirectoryExists(GetConfigPath()). + /// + /// + /// The config path here is a SHARED, tool-agnostic file. Its parent directory therefore says + /// nothing about whether Pi exists: a Cursor or Claude Code user has a + /// ~/.config/mcp tree without ever installing Pi, and a Pi user who has not yet + /// configured any MCP server has no ~/.config/mcp at all. Keying detection on that + /// path would make "Configure All Detected Clients" offer Pi on machines that do not have + /// it while skipping the exact machines this configurator exists to fix. + /// + /// + /// + /// Pi's own agent directory is the correct marker: pi creates it on first run for settings, + /// sessions and auth, nothing else writes there, and PI_CODING_AGENT_DIR relocates + /// it — so an explicitly relocated install is detected too rather than hardcoding + /// ~/.pi. + /// + /// + public override bool IsInstalled + { + get + { + string agentDir = GetPiAgentDirectory(); + return !string.IsNullOrEmpty(agentDir) && Directory.Exists(agentDir); + } + } + + public override IList GetInstallationSteps() => new List + { + "Install an MCP extension for Pi — Pi ships no MCP client of its own:\npi install npm:pi-mcp-adapter", + "Restart Pi so the extension loads (extensions are read at startup)", + "Paste the configuration JSON into the file at the path above, or use Configure", + "Start Pi and ask it to search MCP — e.g. \"search MCP for manage_gameobject\"" + }; + + /// The tool-agnostic shared MCP config Pi MCP extensions read, in the user's home. + private static string GetSharedMcpConfigPath() + { + string home = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); + return Path.Combine(home, ".config", "mcp", "mcp.json"); + } + + /// + /// Pi's agent directory: $PI_CODING_AGENT_DIR when set (Pi's own documented + /// relocation override), else ~/.pi/agent. Null when the home directory cannot be + /// resolved, which callers must treat as "not installed" rather than as a match. + /// + private static string GetPiAgentDirectory() + { + string overrideDir = Environment.GetEnvironmentVariable("PI_CODING_AGENT_DIR"); + if (!string.IsNullOrWhiteSpace(overrideDir)) + return overrideDir.Trim(); + + string home = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); + return string.IsNullOrEmpty(home) ? null : Path.Combine(home, ".pi", "agent"); + } + } +} diff --git a/MCPForUnity/Editor/Clients/Configurators/PiConfigurator.cs.meta b/MCPForUnity/Editor/Clients/Configurators/PiConfigurator.cs.meta new file mode 100644 index 000000000..2e4b06e63 --- /dev/null +++ b/MCPForUnity/Editor/Clients/Configurators/PiConfigurator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 068ab66d5e4b41f98382460c25b4f3a8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/UnityMCPTests/Assets/Tests/EditMode/Clients/IsInstalledTests.cs b/TestProjects/UnityMCPTests/Assets/Tests/EditMode/Clients/IsInstalledTests.cs index e7d4467cb..ee425619e 100644 --- a/TestProjects/UnityMCPTests/Assets/Tests/EditMode/Clients/IsInstalledTests.cs +++ b/TestProjects/UnityMCPTests/Assets/Tests/EditMode/Clients/IsInstalledTests.cs @@ -41,5 +41,52 @@ public void JsonClient_Installed_WhenParentDirExists() bool expected = parent != null && Directory.Exists(parent); Assert.AreEqual(expected, claude.IsInstalled); } + + /// + /// Pi's presence check must key on Pi's OWN agent directory, honouring + /// PI_CODING_AGENT_DIR. The file it writes is the tool-agnostic shared + /// ~/.config/mcp/mcp.json, so the inherited "parent directory of the config path + /// exists" rule would answer a different question entirely. + /// + [Test] + public void PiConfigurator_PresenceIsAgentDirBased() + { + var pi = new PiConfigurator(); + Assert.AreEqual(Directory.Exists(PiAgentDirectory()), pi.IsInstalled, + "Pi detection must follow Pi's agent directory, honouring PI_CODING_AGENT_DIR"); + } + + /// + /// The specific over-report this configurator guards against: a shared MCP config tree with + /// no Pi install beside it must NOT read as "Pi detected". Cursor and Claude Code users have + /// ~/.config/mcp without ever installing Pi, so keying on it would offer Pi in + /// "Configure All Detected Clients" on machines that do not have it. Skipped (Pass) when Pi + /// is genuinely installed here or when no shared tree exists -- neither host can distinguish + /// the two rules, so asserting there would prove nothing. + /// + [Test] + public void PiConfigurator_SharedMcpConfigTreeAlone_IsNotInstalled() + { + var pi = new PiConfigurator(); + string sharedParent = Path.GetDirectoryName(pi.GetConfigPath()); + + if (sharedParent == null || !Directory.Exists(sharedParent)) + Assert.Pass("no shared MCP config tree on this machine -- nothing to distinguish"); + if (Directory.Exists(PiAgentDirectory())) + Assert.Pass("Pi is installed on this machine -- the over-report case needs a host without it"); + + Assert.IsFalse(pi.IsInstalled, + "the shared MCP config tree is not evidence of a Pi install; detection must key on Pi's agent dir"); + } + + private static string PiAgentDirectory() + { + string overrideDir = System.Environment.GetEnvironmentVariable("PI_CODING_AGENT_DIR"); + if (!string.IsNullOrWhiteSpace(overrideDir)) + return overrideDir.Trim(); + + string home = System.Environment.GetFolderPath(System.Environment.SpecialFolder.UserProfile); + return Path.Combine(home, ".pi", "agent"); + } } } diff --git a/website/docs/getting-started/clients.md b/website/docs/getting-started/clients.md index 0be1ff516..245d17a25 100644 --- a/website/docs/getting-started/clients.md +++ b/website/docs/getting-started/clients.md @@ -26,13 +26,14 @@ MCP for Unity auto-configures every client the package detects on your machine. | **Gemini CLI** | HTTP | yes | yes | yes | Auto-connects. | | **OpenClaw** | HTTP / stdio | yes | yes | yes | Requires `openclaw-mcp-bridge` plugin enabled. Follows MCP for Unity's transport choice. | | **Antigravity** | HTTP | yes | yes | varies | Requires an MCP toggle in Antigravity settings. | +| **Pi** | stdio / HTTP | yes | yes | yes | Needs an MCP extension (Pi ships no MCP client of its own): `pi install npm:pi-mcp-adapter`. Writes the shared `~/.config/mcp/mcp.json`. | ## How to pick - **You want it to just work**: Claude Desktop. Stdio means no port conflicts and no firewall prompts. - **You're building a multi-agent or remote workflow**: anything HTTP. Multiple clients can share one Python server; see [Multi-Instance Routing](/guides/multi-instance). - **You're already in your IDE**: Cursor, VS Code Copilot, or Cline — keeps you in flow. -- **You want a terminal**: Claude Code, Copilot CLI, Codex, Gemini CLI, or Qwen Code. +- **You want a terminal**: Claude Code, Copilot CLI, Codex, Gemini CLI, Qwen Code, or Pi. ## Manual configuration @@ -45,6 +46,8 @@ A few clients need an MCP toggle flipped on after the configurator writes their - **Cursor** → Settings → MCP → enable the `unityMCP` server - **Antigravity** → Settings → MCP servers → enable - **OpenClaw** → enable the `openclaw-mcp-bridge` plugin +- **Pi** → install an MCP extension first (`pi install npm:pi-mcp-adapter`) and restart Pi; Pi has no + built-in MCP client, so the config file alone does nothing Everything else just connects on next launch.