mirror of
https://gh-proxy.org/https://github.com/klinsmaya/multica.git
synced 2026-11-09 01:46:46 +08:00
Adds MCP configuration support to the Oh-My-Pi (omp) runtime. The MCP tab is
now visible for omp agents, and the managed config is materialised as
`{workDir}/.omp/mcp.json` — omp's highest-precedence project-level path —
before the runtime starts.
Materialisation lives in `execenv` and is recorded in `sidecarManifest`, so it
inherits refuse-to-clobber on a pre-existing file plus daemon-side teardown and
reuse cleanup. `Reuse` declines (returns nil, forcing a fresh Prepare) when the
config cannot be written, matching the Cursor path, so a leftover file from a
crashed run cannot silently carry stale MCP servers into the next task.
`.pi` and `.omp` are added to the repo-cache git excludes so runtime sidecars —
which for MCP routinely embed third-party tokens — cannot be committed by an
agent; `.pi/skills/` was already being written unexcluded before this change.
`listRuntimeLocalMcpServers` reports omp's user-scope inventory from
`~/.omp/agent/mcp.json`, with a comment recording that this is deliberately
narrower than omp's full discovery chain.
Pi itself is excluded by design. Upstream states the position in its README
("**No MCP.** Build CLI tools with READMEs, or build an extension that adds MCP
support."), confirmed against pi 0.67.2: no MCP code in the published bundle,
no MCP flag or subcommand, and a probe server placed in a project config is
never launched. There is no `.pi/mcp.json` convention to write into, so the
negative assertion pinning `pi` out of the allowlist is kept and now carries
that reasoning.
Closes #7558 for omp only; Pi would require authoring a Pi extension for the
daemon to inject, which is separate work.
83 lines
2.3 KiB
Go
83 lines
2.3 KiB
Go
package execenv
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestPrepareOmpMcpConfigUsesProviderNamespace(t *testing.T) {
|
|
workDir := t.TempDir()
|
|
manifest := &sidecarManifest{}
|
|
raw := json.RawMessage(`{"mcpServers":{"fetch":{"command":"uvx"}}}`)
|
|
|
|
if err := prepareOmpMcpConfig(workDir, "omp", raw, manifest); err != nil {
|
|
t.Fatalf("prepareOmpMcpConfig: %v", err)
|
|
}
|
|
path := filepath.Join(workDir, ".omp", "mcp.json")
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
t.Fatalf("read %s: %v", path, err)
|
|
}
|
|
if string(data) != string(raw) {
|
|
t.Fatalf("config = %s, want %s", data, raw)
|
|
}
|
|
if !containsPath(manifest.Files, path) || !containsPath(manifest.Dirs, filepath.Dir(path)) {
|
|
t.Fatalf("manifest = %#v, want config file and directory", manifest)
|
|
}
|
|
}
|
|
|
|
func TestPrepareOmpMcpConfigRefusesExistingManagedPath(t *testing.T) {
|
|
workDir := t.TempDir()
|
|
path := filepath.Join(workDir, ".omp", "mcp.json")
|
|
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
existing := []byte(`{"mcpServers":{"user":{"command":"echo"}}}`)
|
|
if err := os.WriteFile(path, existing, 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
err := prepareOmpMcpConfig(workDir, "omp", json.RawMessage(`{"mcpServers":{"managed":{}}}`), &sidecarManifest{})
|
|
if err == nil || !strings.Contains(err.Error(), "would overwrite") {
|
|
t.Fatalf("error = %v, want overwrite error", err)
|
|
}
|
|
data, readErr := os.ReadFile(path)
|
|
if readErr != nil {
|
|
t.Fatal(readErr)
|
|
}
|
|
if string(data) != string(existing) {
|
|
t.Fatalf("existing config changed to %s", data)
|
|
}
|
|
}
|
|
|
|
func TestPrepareOmpMcpConfigCleanupRemovesManagedFiles(t *testing.T) {
|
|
workDir := t.TempDir()
|
|
envRoot := t.TempDir()
|
|
manifest := &sidecarManifest{}
|
|
if err := prepareOmpMcpConfig(workDir, "omp", json.RawMessage(`{"mcpServers":{"fetch":{}}}`), manifest); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := writeSidecarManifest(envRoot, manifest); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := CleanupSidecars(envRoot); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := os.Stat(filepath.Join(workDir, ".omp", "mcp.json")); !errors.Is(err, os.ErrNotExist) {
|
|
t.Fatalf("managed config still exists, stat error = %v", err)
|
|
}
|
|
}
|
|
|
|
func containsPath(paths []string, want string) bool {
|
|
for _, path := range paths {
|
|
if path == want {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|