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.