Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 98 additions & 0 deletions MCPForUnity/Editor/Clients/Configurators/PiConfigurator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
using System;
using System.Collections.Generic;
using System.IO;
using MCPForUnity.Editor.Models;

namespace MCPForUnity.Editor.Clients.Configurators
{
/// <summary>
/// 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. <b>Pi has no built-in MCP client.</b> As of pi 0.84.x there is no <c>mcp</c> subcommand,
/// no <c>--mcp-config</c> equivalent, and no <c>mcp.json</c> 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 <c>unityMCP</c> entry written below is what that
/// extension reads. <see cref="GetInstallationSteps"/> 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. <b>The file is shared, so presence cannot be inferred from it.</b> Pi MCP extensions read
/// the tool-agnostic <c>~/.config/mcp/mcp.json</c>, not a Pi-owned path. That makes the
/// inherited <c>IsInstalled</c> ("does the config path's parent directory exist") wrong in
/// both directions — see <see cref="IsInstalled"/>.
/// </summary>
public class PiConfigurator : JsonFileMcpConfigurator
{
public PiConfigurator() : base(new McpClient
{
name = "Pi",
windowsConfigPath = GetSharedMcpConfigPath(),
macConfigPath = GetSharedMcpConfigPath(),
linuxConfigPath = GetSharedMcpConfigPath()
})
{ }

/// <summary>
/// Pi-exclusive presence check, deliberately NOT the inherited
/// <c>ParentDirectoryExists(GetConfigPath())</c>.
///
/// <para>
/// 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
/// <c>~/.config/mcp</c> tree without ever installing Pi, and a Pi user who has not yet
/// configured any MCP server has no <c>~/.config/mcp</c> 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.
/// </para>
///
/// <para>
/// 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 <c>PI_CODING_AGENT_DIR</c> relocates
/// it — so an explicitly relocated install is detected too rather than hardcoding
/// <c>~/.pi</c>.
/// </para>
/// </summary>
public override bool IsInstalled
{
get
{
string agentDir = GetPiAgentDirectory();
return !string.IsNullOrEmpty(agentDir) && Directory.Exists(agentDir);
}
}

public override IList<string> GetInstallationSteps() => new List<string>
{
"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\""
};

/// <summary>The tool-agnostic shared MCP config Pi MCP extensions read, in the user's home.</summary>
private static string GetSharedMcpConfigPath()
{
string home = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
return Path.Combine(home, ".config", "mcp", "mcp.json");
}

/// <summary>
/// Pi's agent directory: <c>$PI_CODING_AGENT_DIR</c> when set (Pi's own documented
/// relocation override), else <c>~/.pi/agent</c>. Null when the home directory cannot be
/// resolved, which callers must treat as "not installed" rather than as a match.
/// </summary>
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");
}
}
}
11 changes: 11 additions & 0 deletions MCPForUnity/Editor/Clients/Configurators/PiConfigurator.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -41,5 +41,52 @@ public void JsonClient_Installed_WhenParentDirExists()
bool expected = parent != null && Directory.Exists(parent);
Assert.AreEqual(expected, claude.IsInstalled);
}

/// <summary>
/// Pi's presence check must key on Pi's OWN agent directory, honouring
/// <c>PI_CODING_AGENT_DIR</c>. The file it writes is the tool-agnostic shared
/// <c>~/.config/mcp/mcp.json</c>, so the inherited "parent directory of the config path
/// exists" rule would answer a different question entirely.
/// </summary>
[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");
}

/// <summary>
/// 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
/// <c>~/.config/mcp</c> 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.
/// </summary>
[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");
}
}
}
5 changes: 4 additions & 1 deletion website/docs/getting-started/clients.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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.

Expand Down
Loading