pi-mcp-adapter 2.3.5 → 2.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +26 -0
- package/README.md +74 -7
- package/cli.js +152 -86
- package/commands.ts +116 -9
- package/config.ts +566 -126
- package/direct-tools.ts +97 -6
- package/index.ts +97 -78
- package/init.ts +3 -12
- package/mcp-auth-flow.ts +61 -40
- package/mcp-panel.ts +51 -14
- package/mcp-setup-panel.ts +576 -0
- package/metadata-cache.ts +12 -3
- package/onboarding-state.ts +68 -0
- package/package.json +3 -1
- package/proxy-modes.ts +169 -25
- package/tool-metadata.ts +9 -1
- package/types.ts +33 -0
- package/utils.ts +18 -9
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,32 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [2.4.1] - 2026-04-22
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- Added standard-MCP-first config discovery: `~/.config/mcp/mcp.json` and project `.mcp.json` now load automatically, with Pi-owned files preserved as override layers.
|
|
14
|
+
- Added `pi-mcp-adapter init` as a native post-install helper that detects host-specific MCP configs and scaffolds Pi compatibility imports without using the old raw GitHub downloader flow.
|
|
15
|
+
- Added first-run onboarding inside the extension: `/mcp` now shows shared-config hints or actionable empty states, and `/mcp setup` opens a guided setup flow for compatibility imports, minimal `.mcp.json` scaffolding, detected config paths, RepoPrompt quick-add, and exact before/after write previews.
|
|
16
|
+
- Added automatic Pi-core reload after setup or direct-tool config changes, using the same flow as `/reload` so freshly configured direct tools can appear without a manual restart.
|
|
17
|
+
- Added a dedicated Pi-owned onboarding state file so shared-config hints behave as one-time guidance instead of repeating every session.
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
- Updated config precedence to prefer shared MCP files first, then Pi overrides, with `.pi/mcp.json` acting as the final Pi-specific project override.
|
|
21
|
+
- Updated Claude Code compatibility probing to prefer modern Claude MCP config locations before legacy paths.
|
|
22
|
+
- Updated project scaffolding so generated `.mcp.json` files are safe minimal shells instead of fake placeholder servers that fail on first reload.
|
|
23
|
+
- Updated the setup panel and README for clearer first-run guidance, improved spacing, and a more digestible shared-MCP-first setup story.
|
|
24
|
+
|
|
25
|
+
## [2.4.0] - 2026-04-13
|
|
26
|
+
|
|
27
|
+
### Added
|
|
28
|
+
- `settings.disableProxyTool` to hide the `mcp` proxy tool once configured direct tools are fully available from cache. Thanks @tanavamsikrishna for PR #41.
|
|
29
|
+
- Per-server `excludeTools` to hide specific MCP tools/resources by original or prefixed name across direct tools, proxy discovery, and the `/mcp` panel. Thanks @ahmadaccino for issue #36.
|
|
30
|
+
- `settings.autoAuth` to optionally trigger OAuth automatically from proxy/direct tool usage, then rerun the original blocked connect/tool operation once after authentication succeeds. Thanks @unimonkiez for issue #34.
|
|
31
|
+
|
|
32
|
+
### Fixed
|
|
33
|
+
- Regenerated `package-lock.json` so the root lockfile metadata matches `package.json` again, including the declared `open`, `@types/bun`, `@types/open`, and `tsx` entries.
|
|
34
|
+
- Kept the `mcp` proxy tool available as a first-session fallback when configured direct tools are still missing cache metadata, avoiding no-tool startup gaps.
|
|
35
|
+
|
|
10
36
|
## [2.3.5] - 2026-04-13
|
|
11
37
|
|
|
12
38
|
### Fixed
|
package/README.md
CHANGED
|
@@ -24,9 +24,21 @@ pi install npm:pi-mcp-adapter
|
|
|
24
24
|
|
|
25
25
|
Restart Pi after installation.
|
|
26
26
|
|
|
27
|
+
## What happens on first run
|
|
28
|
+
|
|
29
|
+
The adapter reads standard MCP files automatically. No extra setup needed if you already have them.
|
|
30
|
+
|
|
31
|
+
| You already have... | What happens |
|
|
32
|
+
|---------------------|--------------|
|
|
33
|
+
| `.mcp.json` or `~/.config/mcp/mcp.json` | Pi uses it immediately. The first time you open `/mcp`, you'll see a short heads-up explaining which file Pi detected and that Pi only writes adapter-specific overrides to its own files. |
|
|
34
|
+
| Host-specific configs (Cursor, Claude Code, Codex, etc.) but no standard MCP files | Run `/mcp setup` to adopt those host configs into Pi. The setup flow shows exactly what it found, lets you pick which ones to import, and previews the exact file changes before writing. |
|
|
35
|
+
| Nothing configured yet | Run `/mcp setup` to scaffold a minimal `.mcp.json`, quick-add RepoPrompt, or inspect what the adapter discovered on your machine. |
|
|
36
|
+
|
|
37
|
+
If you prefer the terminal, you can also run `pi-mcp-adapter init` after install to scan for host-specific configs and add missing compatibility imports to `~/.pi/agent/mcp.json`.
|
|
38
|
+
|
|
27
39
|
## Quick Start
|
|
28
40
|
|
|
29
|
-
|
|
41
|
+
Preferred project config: `.mcp.json`
|
|
30
42
|
|
|
31
43
|
```json
|
|
32
44
|
{
|
|
@@ -39,6 +51,20 @@ Create `~/.pi/agent/mcp.json`:
|
|
|
39
51
|
}
|
|
40
52
|
```
|
|
41
53
|
|
|
54
|
+
Preferred user-global shared config: `~/.config/mcp/mcp.json`
|
|
55
|
+
|
|
56
|
+
Pi also reads Pi-owned override files for settings and host-specific compatibility:
|
|
57
|
+
|
|
58
|
+
- `~/.pi/agent/mcp.json` — Pi global override
|
|
59
|
+
- `.pi/mcp.json` — Pi project override
|
|
60
|
+
|
|
61
|
+
Precedence is:
|
|
62
|
+
|
|
63
|
+
1. `~/.config/mcp/mcp.json`
|
|
64
|
+
2. `~/.pi/agent/mcp.json`
|
|
65
|
+
3. `.mcp.json`
|
|
66
|
+
4. `.pi/mcp.json`
|
|
67
|
+
|
|
42
68
|
Servers are **lazy by default** — they won't connect until you actually call one of their tools. The adapter caches tool metadata so search and describe work without live connections.
|
|
43
69
|
|
|
44
70
|
```
|
|
@@ -62,6 +88,19 @@ Two calls instead of 26 tools cluttering the context.
|
|
|
62
88
|
|
|
63
89
|
## Config
|
|
64
90
|
|
|
91
|
+
### File Layout
|
|
92
|
+
|
|
93
|
+
Use the shared MCP files when you want one setup to work across hosts, and Pi-owned files when you need Pi-specific overrides or settings.
|
|
94
|
+
|
|
95
|
+
| File | Purpose |
|
|
96
|
+
|------|---------|
|
|
97
|
+
| `~/.config/mcp/mcp.json` | User-global shared MCP config |
|
|
98
|
+
| `.mcp.json` | Project-local shared MCP config |
|
|
99
|
+
| `~/.pi/agent/mcp.json` | Pi global override and compatibility imports |
|
|
100
|
+
| `.pi/mcp.json` | Pi project override |
|
|
101
|
+
|
|
102
|
+
Pi-specific files are the write targets for imported or shared global servers when Pi needs to persist adapter-only settings such as `directTools`.
|
|
103
|
+
|
|
65
104
|
### Server Options
|
|
66
105
|
|
|
67
106
|
```json
|
|
@@ -91,6 +130,7 @@ Two calls instead of 26 tools cluttering the context.
|
|
|
91
130
|
| `idleTimeout` | Minutes before idle disconnect (overrides global) |
|
|
92
131
|
| `exposeResources` | Expose MCP resources as tools (default: true) |
|
|
93
132
|
| `directTools` | `true`, `string[]`, or `false` — register tools individually instead of through proxy |
|
|
133
|
+
| `excludeTools` | `string[]` of tool names to hide (matches original names like `get_screenshot` and prefixed names like `figma_get_screenshot`) |
|
|
94
134
|
| `debug` | Show server stderr (default: false) |
|
|
95
135
|
|
|
96
136
|
### Lifecycle Modes
|
|
@@ -116,6 +156,8 @@ Two calls instead of 26 tools cluttering the context.
|
|
|
116
156
|
| `toolPrefix` | `"server"` (default), `"short"` (strips `-mcp` suffix), or `"none"` |
|
|
117
157
|
| `idleTimeout` | Global idle timeout in minutes (default: 10, 0 to disable) |
|
|
118
158
|
| `directTools` | Global default for all servers (default: false). Per-server overrides this. |
|
|
159
|
+
| `disableProxyTool` | Hide the `mcp` proxy tool once configured direct tools are fully available from cache. |
|
|
160
|
+
| `autoAuth` | Auto-run OAuth on `connect`/tool calls when a server needs auth, then retry once (default: false). |
|
|
119
161
|
|
|
120
162
|
Per-server `idleTimeout` overrides the global setting.
|
|
121
163
|
|
|
@@ -169,11 +211,31 @@ To set a global default for all servers:
|
|
|
169
211
|
|
|
170
212
|
Per-server `directTools` overrides the global setting. The example above registers direct tools for every server except `huge-server`.
|
|
171
213
|
|
|
214
|
+
To exclude specific tools while still using `directTools: true`, add `excludeTools` on the server:
|
|
215
|
+
|
|
216
|
+
```json
|
|
217
|
+
{
|
|
218
|
+
"mcpServers": {
|
|
219
|
+
"figma": {
|
|
220
|
+
"url": "http://localhost:3845/mcp",
|
|
221
|
+
"directTools": true,
|
|
222
|
+
"excludeTools": ["get_figjam", "figma_get_code_connect_map"]
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
`excludeTools` filters direct tools, proxy search/list/describe, and the `/mcp` panel view.
|
|
229
|
+
|
|
172
230
|
Each direct tool costs ~150-300 tokens in the system prompt (name + description + schema). Good for targeted sets of 5-20 tools. For servers with 75+ tools, stick with the proxy or pick specific tools with a `string[]`.
|
|
173
231
|
|
|
174
|
-
Direct tools register from the metadata cache (`~/.pi/agent/mcp-cache.json`), so no server connections are needed at startup. On the first session after adding `directTools` to a new server, the cache won't exist yet — tools fall back to proxy-only and the cache populates in the background.
|
|
232
|
+
Direct tools register from the metadata cache (`~/.pi/agent/mcp-cache.json`), so no server connections are needed at startup. On the first session after adding `directTools` to a new server, the cache won't exist yet — tools fall back to proxy-only and the cache populates in the background. To force it: `/mcp reconnect <server>`.
|
|
233
|
+
|
|
234
|
+
When you change direct-tool toggles in `/mcp` or write new config through `/mcp setup`, the extension triggers Pi's normal reload flow automatically. That refreshes extensions, prompts, skills, and MCP tool registration in one shot, so newly configured direct tools can appear without a manual restart.
|
|
175
235
|
|
|
176
|
-
**Interactive configuration:** Run `/mcp` to open an interactive panel showing all servers with connection status, tools, and direct/proxy toggles. You can reconnect servers, initiate OAuth, and toggle tools between direct and proxy — all from one overlay.
|
|
236
|
+
**Interactive configuration:** Run `/mcp` to open an interactive panel showing all servers with connection status, tools, and direct/proxy toggles. You can reconnect servers, initiate OAuth, and toggle tools between direct and proxy — all from one overlay.
|
|
237
|
+
|
|
238
|
+
**Guided first-run setup:** Run `/mcp setup` to inspect detected shared MCP files, adopt compatibility imports from other hosts, open discovered config paths, preview exact before/after file diffs for writes, scaffold a minimal project `.mcp.json`, or quick-add RepoPrompt into a standard/shared MCP file.
|
|
177
239
|
|
|
178
240
|
**Subagent integration:** If you use the subagent extension, agents can request direct MCP tools in their frontmatter with `mcp:server-name` syntax. See the subagent README for details.
|
|
179
241
|
|
|
@@ -238,7 +300,7 @@ Restart pi, then ask the agent to show a chart — it calls `show_chart` and ope
|
|
|
238
300
|
|
|
239
301
|
### Import Existing Configs
|
|
240
302
|
|
|
241
|
-
|
|
303
|
+
Shared MCP files are loaded automatically. Use `imports` only for host-specific config formats that are not already covered by `.mcp.json` or `~/.config/mcp/mcp.json`.
|
|
242
304
|
|
|
243
305
|
```json
|
|
244
306
|
{
|
|
@@ -247,11 +309,13 @@ Already have MCP set up elsewhere? Import it:
|
|
|
247
309
|
}
|
|
248
310
|
```
|
|
249
311
|
|
|
250
|
-
Supported: `cursor`, `claude-code`, `claude-desktop`, `vscode`, `windsurf`, `codex`
|
|
312
|
+
Supported compatibility imports: `cursor`, `claude-code`, `claude-desktop`, `vscode`, `windsurf`, `codex`
|
|
313
|
+
|
|
314
|
+
`pi-mcp-adapter init` detects these host-specific configs and adds missing imports to `~/.pi/agent/mcp.json` for you.
|
|
251
315
|
|
|
252
316
|
### Project Config
|
|
253
317
|
|
|
254
|
-
|
|
318
|
+
Prefer `.mcp.json` for project-local shared MCP config. Use `.pi/mcp.json` only when you need a Pi-specific project override. Project files override both user-global shared MCP config and Pi global overrides.
|
|
255
319
|
|
|
256
320
|
## Usage
|
|
257
321
|
|
|
@@ -273,12 +337,15 @@ Tool names are fuzzy-matched on hyphens and underscores — `context7_resolve_li
|
|
|
273
337
|
|
|
274
338
|
| Command | What it does |
|
|
275
339
|
|---------|--------------|
|
|
276
|
-
| `/mcp` | Interactive panel
|
|
340
|
+
| `/mcp` | Interactive panel and first-run onboarding surface |
|
|
341
|
+
| `/mcp setup` | Guided setup for imports, a minimal `.mcp.json`, RepoPrompt quick-add, and config-path inspection |
|
|
277
342
|
| `/mcp tools` | List all tools |
|
|
278
343
|
| `/mcp reconnect` | Reconnect all servers |
|
|
279
344
|
| `/mcp reconnect <server>` | Connect or reconnect a single server |
|
|
280
345
|
| `/mcp-auth <server>` | OAuth setup |
|
|
281
346
|
|
|
347
|
+
If `settings.autoAuth` is `true`, `mcp({ connect: ... })`, `mcp({ tool: ... })`, and direct tool calls will automatically run OAuth when needed and retry once. In non-interactive sessions, browser-based OAuth still requires running `/mcp-auth <server>` manually.
|
|
348
|
+
|
|
282
349
|
## How It Works
|
|
283
350
|
|
|
284
351
|
- One `mcp` tool in context (~200 tokens) instead of hundreds
|
package/cli.js
CHANGED
|
@@ -2,107 +2,173 @@
|
|
|
2
2
|
|
|
3
3
|
import fs from "node:fs";
|
|
4
4
|
import path from "node:path";
|
|
5
|
-
import https from "node:https";
|
|
6
5
|
import os from "node:os";
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
const FILES = [
|
|
15
|
-
"index.ts",
|
|
16
|
-
"types.ts",
|
|
17
|
-
"config.ts",
|
|
18
|
-
"server-manager.ts",
|
|
19
|
-
"tool-registrar.ts",
|
|
20
|
-
"resource-tools.ts",
|
|
21
|
-
"lifecycle.ts",
|
|
22
|
-
"metadata-cache.ts",
|
|
23
|
-
"npx-resolver.ts",
|
|
24
|
-
"oauth-handler.ts",
|
|
25
|
-
"package.json",
|
|
26
|
-
"tsconfig.json",
|
|
27
|
-
"README.md",
|
|
28
|
-
"CHANGELOG.md",
|
|
29
|
-
"LICENSE",
|
|
30
|
-
];
|
|
31
|
-
|
|
32
|
-
function download(url) {
|
|
33
|
-
return new Promise((resolve, reject) => {
|
|
34
|
-
https.get(url, (res) => {
|
|
35
|
-
if (res.statusCode === 301 || res.statusCode === 302) {
|
|
36
|
-
return download(res.headers.location).then(resolve).catch(reject);
|
|
37
|
-
}
|
|
38
|
-
if (res.statusCode !== 200) {
|
|
39
|
-
return reject(new Error(`Failed to download ${url}: ${res.statusCode}`));
|
|
40
|
-
}
|
|
41
|
-
let data = "";
|
|
42
|
-
res.on("data", (chunk) => (data += chunk));
|
|
43
|
-
res.on("end", () => resolve(data));
|
|
44
|
-
res.on("error", reject);
|
|
45
|
-
}).on("error", reject);
|
|
46
|
-
});
|
|
47
|
-
}
|
|
6
|
+
import { pathToFileURL } from "node:url";
|
|
7
|
+
|
|
8
|
+
const HOME = os.homedir();
|
|
9
|
+
const PI_CONFIG_PATH = path.join(HOME, ".pi", "agent", "mcp.json");
|
|
10
|
+
const GENERIC_GLOBAL_CONFIG_PATH = path.join(HOME, ".config", "mcp", "mcp.json");
|
|
11
|
+
const PROJECT_CONFIG_PATH = path.resolve(process.cwd(), ".mcp.json");
|
|
12
|
+
const PROJECT_PI_CONFIG_PATH = path.resolve(process.cwd(), ".pi", "mcp.json");
|
|
48
13
|
|
|
49
|
-
|
|
50
|
-
|
|
14
|
+
const IMPORT_PATHS = {
|
|
15
|
+
cursor: [path.join(HOME, ".cursor", "mcp.json")],
|
|
16
|
+
"claude-code": [
|
|
17
|
+
path.join(HOME, ".claude", "mcp.json"),
|
|
18
|
+
path.join(HOME, ".claude.json"),
|
|
19
|
+
path.join(HOME, ".claude", "claude_desktop_config.json"),
|
|
20
|
+
],
|
|
21
|
+
"claude-desktop": [path.join(HOME, "Library", "Application Support", "Claude", "claude_desktop_config.json")],
|
|
22
|
+
codex: [path.join(HOME, ".codex", "config.json")],
|
|
23
|
+
windsurf: [path.join(HOME, ".windsurf", "mcp.json")],
|
|
24
|
+
vscode: [path.resolve(process.cwd(), ".vscode", "mcp.json")],
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
function printHelp(log = console.log) {
|
|
28
|
+
log("pi-mcp-adapter helper\n");
|
|
29
|
+
log("Install the package with:");
|
|
30
|
+
log(" pi install npm:pi-mcp-adapter\n");
|
|
31
|
+
log("Then optionally run:");
|
|
32
|
+
log(" pi-mcp-adapter init Detect host configs and scaffold Pi imports");
|
|
33
|
+
log(" pi-mcp-adapter init --dry-run");
|
|
34
|
+
}
|
|
51
35
|
|
|
52
|
-
|
|
53
|
-
|
|
36
|
+
function readJsonFile(filePath) {
|
|
37
|
+
return JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
38
|
+
}
|
|
54
39
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
fs.writeFileSync(path.join(EXT_DIR, file), content);
|
|
40
|
+
function loadPiConfig() {
|
|
41
|
+
if (!fs.existsSync(PI_CONFIG_PATH)) {
|
|
42
|
+
return { mcpServers: {} };
|
|
59
43
|
}
|
|
60
44
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
console.error("Warning: npm install failed. You may need to run it manually.");
|
|
45
|
+
const raw = readJsonFile(PI_CONFIG_PATH);
|
|
46
|
+
const mcpServers = raw.mcpServers ?? raw["mcp-servers"] ?? {};
|
|
47
|
+
if (!mcpServers || typeof mcpServers !== "object" || Array.isArray(mcpServers)) {
|
|
48
|
+
throw new Error(`Invalid MCP config at ${PI_CONFIG_PATH}: expected \"mcpServers\" to be an object`);
|
|
66
49
|
}
|
|
67
50
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
51
|
+
const normalized = { ...raw };
|
|
52
|
+
delete normalized["mcp-servers"];
|
|
53
|
+
|
|
54
|
+
const imports = Array.isArray(raw.imports) ? raw.imports.filter((value) => typeof value === "string") : undefined;
|
|
55
|
+
return {
|
|
56
|
+
...normalized,
|
|
57
|
+
mcpServers,
|
|
58
|
+
imports,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function findAvailableImports() {
|
|
63
|
+
const found = [];
|
|
64
|
+
|
|
65
|
+
for (const [kind, candidates] of Object.entries(IMPORT_PATHS)) {
|
|
66
|
+
const existing = candidates.find((candidate) => fs.existsSync(candidate));
|
|
67
|
+
if (existing) {
|
|
68
|
+
found.push({ kind, path: existing });
|
|
77
69
|
}
|
|
78
70
|
}
|
|
79
71
|
|
|
80
|
-
|
|
81
|
-
|
|
72
|
+
return found;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function printDiscovery(log, imports) {
|
|
76
|
+
log("Config discovery:\n");
|
|
77
|
+
|
|
78
|
+
const paths = [
|
|
79
|
+
["User-global standard MCP", GENERIC_GLOBAL_CONFIG_PATH],
|
|
80
|
+
["Pi global override", PI_CONFIG_PATH],
|
|
81
|
+
["Project standard MCP", PROJECT_CONFIG_PATH],
|
|
82
|
+
["Project Pi override", PROJECT_PI_CONFIG_PATH],
|
|
83
|
+
];
|
|
84
|
+
|
|
85
|
+
for (const [label, filePath] of paths) {
|
|
86
|
+
const prefix = fs.existsSync(filePath) ? "✓" : "-";
|
|
87
|
+
log(`${prefix} ${label}: ${filePath}`);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
log("\nCompatibility imports:\n");
|
|
91
|
+
if (imports.length === 0) {
|
|
92
|
+
log("- No host-specific MCP configs detected");
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
for (const entry of imports) {
|
|
97
|
+
log(`✓ ${entry.kind}: ${entry.path}`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function writePiConfig(config) {
|
|
102
|
+
fs.mkdirSync(path.dirname(PI_CONFIG_PATH), { recursive: true });
|
|
103
|
+
fs.writeFileSync(PI_CONFIG_PATH, `${JSON.stringify(config, null, 2)}\n`, "utf-8");
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async function runInit(argv, log = console.log) {
|
|
107
|
+
const dryRun = argv.includes("--dry-run");
|
|
108
|
+
const foundImports = findAvailableImports();
|
|
109
|
+
const existingConfig = loadPiConfig();
|
|
110
|
+
const existingImports = new Set(existingConfig.imports ?? []);
|
|
111
|
+
const importsToAdd = foundImports
|
|
112
|
+
.map((entry) => entry.kind)
|
|
113
|
+
.filter((kind) => !existingImports.has(kind));
|
|
114
|
+
|
|
115
|
+
printDiscovery(log, foundImports);
|
|
116
|
+
|
|
117
|
+
if (importsToAdd.length === 0) {
|
|
118
|
+
log("\nNo Pi config changes needed.");
|
|
119
|
+
log("Standard MCP configs are discovered automatically, and host-specific imports are already configured or unavailable.");
|
|
120
|
+
return 0;
|
|
82
121
|
}
|
|
83
122
|
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
123
|
+
const nextConfig = {
|
|
124
|
+
...existingConfig,
|
|
125
|
+
imports: [...existingImports, ...importsToAdd],
|
|
126
|
+
mcpServers: existingConfig.mcpServers ?? {},
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
log(`\nDetected host configs to import into Pi: ${importsToAdd.join(", ")}`);
|
|
130
|
+
|
|
131
|
+
if (dryRun) {
|
|
132
|
+
log(`Dry run: would update ${PI_CONFIG_PATH}`);
|
|
133
|
+
return 0;
|
|
95
134
|
}
|
|
96
135
|
|
|
97
|
-
|
|
98
|
-
|
|
136
|
+
writePiConfig(nextConfig);
|
|
137
|
+
log(`Updated ${PI_CONFIG_PATH}`);
|
|
138
|
+
log("Pi will now keep reading standard MCP configs automatically, while these imports cover host-specific config formats.");
|
|
139
|
+
return 0;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export async function main(argv = process.argv.slice(2), log = console.log, error = console.error) {
|
|
143
|
+
const [command, ...rest] = argv;
|
|
99
144
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
145
|
+
if (!command || command === "help" || command === "--help" || command === "-h") {
|
|
146
|
+
printHelp(log);
|
|
147
|
+
return 0;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (command === "install") {
|
|
151
|
+
error("The custom downloader has been retired.");
|
|
152
|
+
error("Use `pi install npm:pi-mcp-adapter` instead, then optionally run `pi-mcp-adapter init`.");
|
|
153
|
+
return 1;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (command === "init") {
|
|
157
|
+
return runInit(rest, log);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
error(`Unknown command: ${command}`);
|
|
161
|
+
printHelp(log);
|
|
162
|
+
return 1;
|
|
103
163
|
}
|
|
104
164
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
165
|
+
const isEntrypoint = process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href;
|
|
166
|
+
|
|
167
|
+
if (isEntrypoint) {
|
|
168
|
+
main().then((code) => {
|
|
169
|
+
process.exitCode = code;
|
|
170
|
+
}).catch((err) => {
|
|
171
|
+
console.error(`\nHelper failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
172
|
+
process.exit(1);
|
|
173
|
+
});
|
|
174
|
+
}
|
package/commands.ts
CHANGED
|
@@ -1,12 +1,24 @@
|
|
|
1
1
|
import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
|
|
2
2
|
import type { McpExtensionState } from "./state.js";
|
|
3
|
-
import type { McpConfig, ServerEntry, McpPanelCallbacks, McpPanelResult } from "./types.js";
|
|
4
|
-
import {
|
|
3
|
+
import type { McpConfig, ServerEntry, McpPanelCallbacks, McpPanelResult, ImportKind } from "./types.js";
|
|
4
|
+
import {
|
|
5
|
+
ensureCompatibilityImports,
|
|
6
|
+
getMcpDiscoverySummary,
|
|
7
|
+
getServerProvenance,
|
|
8
|
+
previewCompatibilityImports,
|
|
9
|
+
previewSharedServerEntry,
|
|
10
|
+
previewStarterProjectConfig,
|
|
11
|
+
writeDirectToolsConfig,
|
|
12
|
+
writeSharedServerEntry,
|
|
13
|
+
writeStarterProjectConfig,
|
|
14
|
+
} from "./config.js";
|
|
5
15
|
import { lazyConnect, updateMetadataCache, updateStatusBar, getFailureAgeSeconds } from "./init.js";
|
|
6
16
|
import { loadMetadataCache } from "./metadata-cache.js";
|
|
7
17
|
import { buildToolMetadata } from "./tool-metadata.js";
|
|
8
18
|
import { supportsOAuth, authenticate } from "./mcp-auth-flow.js";
|
|
9
19
|
import { hasStoredTokens } from "./mcp-auth.js";
|
|
20
|
+
import { loadOnboardingState, markSetupCompleted as persistSetupCompleted, markSharedConfigHintShown } from "./onboarding-state.js";
|
|
21
|
+
import { openPath } from "./utils.js";
|
|
10
22
|
|
|
11
23
|
export async function showStatus(state: McpExtensionState, ctx: ExtensionContext): Promise<void> {
|
|
12
24
|
if (!ctx.hasUI) return;
|
|
@@ -42,6 +54,7 @@ export async function showStatus(state: McpExtensionState, ctx: ExtensionContext
|
|
|
42
54
|
|
|
43
55
|
if (Object.keys(state.config.mcpServers).length === 0) {
|
|
44
56
|
lines.push("No MCP servers configured");
|
|
57
|
+
lines.push("Run /mcp setup to adopt imports or scaffold a starter .mcp.json");
|
|
45
58
|
}
|
|
46
59
|
|
|
47
60
|
ctx.ui.notify(lines.join("\n"), "info");
|
|
@@ -153,11 +166,8 @@ export async function authenticateServer(
|
|
|
153
166
|
return;
|
|
154
167
|
}
|
|
155
168
|
|
|
156
|
-
// Full automatic OAuth flow using SDK
|
|
157
169
|
try {
|
|
158
170
|
ctx.ui.setStatus("mcp-auth", `Authenticating ${serverName}...`);
|
|
159
|
-
|
|
160
|
-
// Runs the configured OAuth flow (interactive browser or non-interactive client_credentials)
|
|
161
171
|
const status = await authenticate(serverName, definition.url, definition);
|
|
162
172
|
|
|
163
173
|
if (status === "authenticated") {
|
|
@@ -180,15 +190,104 @@ export async function authenticateServer(
|
|
|
180
190
|
}
|
|
181
191
|
}
|
|
182
192
|
|
|
193
|
+
export interface PanelFlowResult {
|
|
194
|
+
configChanged: boolean;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function buildSharedConfigNoticeLines(configOverridePath?: string): { lines: string[]; fingerprint: string | null } {
|
|
198
|
+
const discovery = getMcpDiscoverySummary(configOverridePath);
|
|
199
|
+
const onboardingState = loadOnboardingState();
|
|
200
|
+
if (!discovery.hasSharedServers || onboardingState.sharedConfigHintShown) {
|
|
201
|
+
return { lines: [], fingerprint: null };
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const sharedSources = discovery.sources.filter((source) => source.kind === "shared" && source.serverCount > 0);
|
|
205
|
+
const sourceList = sharedSources.map((source) => source.path).join(", ");
|
|
206
|
+
return {
|
|
207
|
+
lines: [
|
|
208
|
+
`Using standard MCP config from ${sourceList}.`,
|
|
209
|
+
"Pi only writes compatibility imports and adapter-specific overrides into Pi-owned files when needed.",
|
|
210
|
+
],
|
|
211
|
+
fingerprint: discovery.fingerprint,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export async function openMcpSetup(
|
|
216
|
+
_state: McpExtensionState,
|
|
217
|
+
pi: ExtensionAPI,
|
|
218
|
+
ctx: ExtensionContext,
|
|
219
|
+
configOverridePath?: string,
|
|
220
|
+
mode: "empty" | "setup" = "setup",
|
|
221
|
+
): Promise<PanelFlowResult> {
|
|
222
|
+
if (!ctx.hasUI) return { configChanged: false };
|
|
223
|
+
|
|
224
|
+
const discovery = getMcpDiscoverySummary(configOverridePath);
|
|
225
|
+
const onboardingState = loadOnboardingState();
|
|
226
|
+
const { createMcpSetupPanel } = await import("./mcp-setup-panel.js");
|
|
227
|
+
let configChanged = false;
|
|
228
|
+
|
|
229
|
+
const callbacks = {
|
|
230
|
+
previewImports: (imports: ImportKind[]) => previewCompatibilityImports(imports, configOverridePath),
|
|
231
|
+
previewStarterProject: () => previewStarterProjectConfig(),
|
|
232
|
+
previewRepoPrompt: () => {
|
|
233
|
+
const repoPrompt = getMcpDiscoverySummary(configOverridePath).repoPrompt;
|
|
234
|
+
if (!repoPrompt.entry || !repoPrompt.targetPath || !repoPrompt.serverName) return null;
|
|
235
|
+
return previewSharedServerEntry(repoPrompt.targetPath, repoPrompt.serverName, repoPrompt.entry);
|
|
236
|
+
},
|
|
237
|
+
adoptImports: async (imports: ImportKind[]) => {
|
|
238
|
+
const result = ensureCompatibilityImports(imports, configOverridePath);
|
|
239
|
+
if (result.added.length > 0) configChanged = true;
|
|
240
|
+
return result;
|
|
241
|
+
},
|
|
242
|
+
scaffoldProjectConfig: async () => {
|
|
243
|
+
const path = writeStarterProjectConfig();
|
|
244
|
+
configChanged = true;
|
|
245
|
+
return { path };
|
|
246
|
+
},
|
|
247
|
+
addRepoPrompt: async () => {
|
|
248
|
+
const repoPrompt = getMcpDiscoverySummary(configOverridePath).repoPrompt;
|
|
249
|
+
if (!repoPrompt.entry || !repoPrompt.targetPath || !repoPrompt.serverName) {
|
|
250
|
+
throw new Error("RepoPrompt is not available to add from this setup screen.");
|
|
251
|
+
}
|
|
252
|
+
const path = writeSharedServerEntry(repoPrompt.targetPath, repoPrompt.serverName, repoPrompt.entry);
|
|
253
|
+
configChanged = true;
|
|
254
|
+
return { path, serverName: repoPrompt.serverName };
|
|
255
|
+
},
|
|
256
|
+
openPath: async (targetPath: string) => {
|
|
257
|
+
await openPath(pi, targetPath);
|
|
258
|
+
},
|
|
259
|
+
markSetupCompleted: () => {
|
|
260
|
+
persistSetupCompleted(discovery.fingerprint);
|
|
261
|
+
},
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
return new Promise<PanelFlowResult>((resolve) => {
|
|
265
|
+
ctx.ui.custom(
|
|
266
|
+
(tui, _theme, _keybindings, done) => {
|
|
267
|
+
return createMcpSetupPanel(discovery, callbacks, { mode, onboardingState }, tui, () => {
|
|
268
|
+
done();
|
|
269
|
+
resolve({ configChanged });
|
|
270
|
+
});
|
|
271
|
+
},
|
|
272
|
+
{ overlay: true, overlayOptions: { anchor: "center", width: 92 } },
|
|
273
|
+
);
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
|
|
183
277
|
export async function openMcpPanel(
|
|
184
278
|
state: McpExtensionState,
|
|
185
279
|
pi: ExtensionAPI,
|
|
186
280
|
ctx: ExtensionContext,
|
|
187
281
|
configOverridePath?: string,
|
|
188
|
-
): Promise<
|
|
282
|
+
): Promise<PanelFlowResult> {
|
|
283
|
+
if (Object.keys(state.config.mcpServers).length === 0) {
|
|
284
|
+
return openMcpSetup(state, pi, ctx, configOverridePath, "empty");
|
|
285
|
+
}
|
|
286
|
+
|
|
189
287
|
const config = state.config;
|
|
190
288
|
const cache = loadMetadataCache();
|
|
191
289
|
const provenanceMap = getServerProvenance(pi.getFlag("mcp-config") as string | undefined ?? configOverridePath);
|
|
290
|
+
const { lines: noticeLines, fingerprint } = buildSharedConfigNoticeLines(pi.getFlag("mcp-config") as string | undefined ?? configOverridePath);
|
|
192
291
|
|
|
193
292
|
const callbacks: McpPanelCallbacks = {
|
|
194
293
|
reconnect: async (serverName: string) => {
|
|
@@ -219,20 +318,28 @@ export async function openMcpPanel(
|
|
|
219
318
|
};
|
|
220
319
|
|
|
221
320
|
const { createMcpPanel } = await import("./mcp-panel.js");
|
|
321
|
+
let configChanged = false;
|
|
222
322
|
|
|
223
|
-
|
|
323
|
+
await new Promise<void>((resolve) => {
|
|
224
324
|
ctx.ui.custom(
|
|
225
325
|
(tui, _theme, _keybindings, done) => {
|
|
226
326
|
return createMcpPanel(config, cache, provenanceMap, callbacks, tui, (result: McpPanelResult) => {
|
|
227
327
|
if (!result.cancelled && result.changes.size > 0) {
|
|
228
328
|
writeDirectToolsConfig(result.changes, provenanceMap, config);
|
|
229
|
-
|
|
329
|
+
configChanged = true;
|
|
330
|
+
ctx.ui.notify("Direct tools updated. Pi will reload after this panel closes.", "info");
|
|
230
331
|
}
|
|
231
332
|
done();
|
|
232
333
|
resolve();
|
|
233
|
-
});
|
|
334
|
+
}, { noticeLines });
|
|
234
335
|
},
|
|
235
336
|
{ overlay: true, overlayOptions: { anchor: "center", width: 82 } },
|
|
236
337
|
);
|
|
237
338
|
});
|
|
339
|
+
|
|
340
|
+
if (noticeLines.length > 0 && fingerprint) {
|
|
341
|
+
markSharedConfigHintShown(fingerprint);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
return { configChanged };
|
|
238
345
|
}
|