pi-mcp-adapter 2.5.1 → 2.5.2
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 +5 -0
- package/README.md +6 -6
- package/agent-dir.ts +20 -0
- package/cli.js +11 -1
- package/config.ts +2 -2
- package/mcp-auth.ts +5 -5
- package/mcp-setup-panel.ts +2 -2
- package/metadata-cache.ts +12 -11
- package/npx-resolver.ts +14 -9
- package/oauth-handler.ts +6 -3
- package/onboarding-state.ts +3 -3
- package/package.json +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [2.5.2] - 2026-04-29
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- Respected `PI_CODING_AGENT_DIR` for Pi-owned MCP config and state files, including metadata cache, npx cache, onboarding state, OAuth credentials, and `pi-mcp-adapter init` writes.
|
|
14
|
+
|
|
10
15
|
## [2.5.1] - 2026-04-24
|
|
11
16
|
|
|
12
17
|
### Fixed
|
package/README.md
CHANGED
|
@@ -34,7 +34,7 @@ The adapter reads standard MCP files automatically. No extra setup needed if you
|
|
|
34
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
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
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
|
|
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 the Pi agent dir (`~/.pi/agent/mcp.json` by default, or `$PI_CODING_AGENT_DIR/mcp.json` when set).
|
|
38
38
|
|
|
39
39
|
## Quick Start
|
|
40
40
|
|
|
@@ -55,13 +55,13 @@ Preferred user-global shared config: `~/.config/mcp/mcp.json`
|
|
|
55
55
|
|
|
56
56
|
Pi also reads Pi-owned override files for settings and host-specific compatibility:
|
|
57
57
|
|
|
58
|
-
-
|
|
58
|
+
- `<Pi agent dir>/mcp.json` — Pi global override (`~/.pi/agent/mcp.json` by default)
|
|
59
59
|
- `.pi/mcp.json` — Pi project override
|
|
60
60
|
|
|
61
61
|
Precedence is:
|
|
62
62
|
|
|
63
63
|
1. `~/.config/mcp/mcp.json`
|
|
64
|
-
2.
|
|
64
|
+
2. `<Pi agent dir>/mcp.json`
|
|
65
65
|
3. `.mcp.json`
|
|
66
66
|
4. `.pi/mcp.json`
|
|
67
67
|
|
|
@@ -96,7 +96,7 @@ Use the shared MCP files when you want one setup to work across hosts, and Pi-ow
|
|
|
96
96
|
|------|---------|
|
|
97
97
|
| `~/.config/mcp/mcp.json` | User-global shared MCP config |
|
|
98
98
|
| `.mcp.json` | Project-local shared MCP config |
|
|
99
|
-
|
|
|
99
|
+
| `<Pi agent dir>/mcp.json` | Pi global override and compatibility imports (`~/.pi/agent/mcp.json` by default) |
|
|
100
100
|
| `.pi/mcp.json` | Pi project override |
|
|
101
101
|
|
|
102
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`.
|
|
@@ -231,7 +231,7 @@ To exclude specific tools while still using `directTools: true`, add `excludeToo
|
|
|
231
231
|
|
|
232
232
|
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[]`.
|
|
233
233
|
|
|
234
|
-
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>`.
|
|
234
|
+
Direct tools register from the metadata cache in the Pi agent dir (`~/.pi/agent/mcp-cache.json` by default, or `$PI_CODING_AGENT_DIR/mcp-cache.json` when set), 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>`.
|
|
235
235
|
|
|
236
236
|
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.
|
|
237
237
|
|
|
@@ -313,7 +313,7 @@ Shared MCP files are loaded automatically. Use `imports` only for host-specific
|
|
|
313
313
|
|
|
314
314
|
Supported compatibility imports: `cursor`, `claude-code`, `claude-desktop`, `vscode`, `windsurf`, `codex`
|
|
315
315
|
|
|
316
|
-
`pi-mcp-adapter init` detects these host-specific configs and adds missing imports to
|
|
316
|
+
`pi-mcp-adapter init` detects these host-specific configs and adds missing imports to the Pi agent dir config for you.
|
|
317
317
|
|
|
318
318
|
### Project Config
|
|
319
319
|
|
package/agent-dir.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { homedir } from "node:os";
|
|
2
|
+
import { join, resolve } from "node:path";
|
|
3
|
+
|
|
4
|
+
export function getAgentDir(): string {
|
|
5
|
+
const configured = process.env.PI_CODING_AGENT_DIR?.trim();
|
|
6
|
+
if (!configured) {
|
|
7
|
+
return join(homedir(), ".pi", "agent");
|
|
8
|
+
}
|
|
9
|
+
if (configured === "~") {
|
|
10
|
+
return homedir();
|
|
11
|
+
}
|
|
12
|
+
if (configured.startsWith("~/")) {
|
|
13
|
+
return resolve(homedir(), configured.slice(2));
|
|
14
|
+
}
|
|
15
|
+
return resolve(configured);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function getAgentPath(...segments: string[]): string {
|
|
19
|
+
return join(getAgentDir(), ...segments);
|
|
20
|
+
}
|
package/cli.js
CHANGED
|
@@ -6,7 +6,17 @@ import os from "node:os";
|
|
|
6
6
|
import { pathToFileURL } from "node:url";
|
|
7
7
|
|
|
8
8
|
const HOME = os.homedir();
|
|
9
|
-
|
|
9
|
+
|
|
10
|
+
function expandHome(input) {
|
|
11
|
+
if (input === "~") return HOME;
|
|
12
|
+
if (input.startsWith("~/")) return path.resolve(HOME, input.slice(2));
|
|
13
|
+
return path.resolve(input);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const AGENT_DIR = process.env.PI_CODING_AGENT_DIR?.trim()
|
|
17
|
+
? expandHome(process.env.PI_CODING_AGENT_DIR.trim())
|
|
18
|
+
: path.join(HOME, ".pi", "agent");
|
|
19
|
+
const PI_CONFIG_PATH = path.join(AGENT_DIR, "mcp.json");
|
|
10
20
|
const GENERIC_GLOBAL_CONFIG_PATH = path.join(HOME, ".config", "mcp", "mcp.json");
|
|
11
21
|
const PROJECT_CONFIG_PATH = path.resolve(process.cwd(), ".mcp.json");
|
|
12
22
|
const PROJECT_PI_CONFIG_PATH = path.resolve(process.cwd(), ".pi", "mcp.json");
|
package/config.ts
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
import { existsSync, readFileSync, writeFileSync, mkdirSync, renameSync } from "node:fs";
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
4
|
import { dirname, join, resolve } from "node:path";
|
|
5
|
+
import { getAgentPath } from "./agent-dir.js";
|
|
5
6
|
import type { McpConfig, ServerEntry, McpSettings, ImportKind, ServerProvenance } from "./types.js";
|
|
6
7
|
|
|
7
|
-
const PI_GLOBAL_CONFIG_PATH = join(homedir(), ".pi", "agent", "mcp.json");
|
|
8
8
|
const GENERIC_GLOBAL_CONFIG_PATH = join(homedir(), ".config", "mcp", "mcp.json");
|
|
9
9
|
const PROJECT_CONFIG_NAME = ".mcp.json";
|
|
10
10
|
const PROJECT_PI_CONFIG_NAME = ".pi/mcp.json";
|
|
@@ -90,7 +90,7 @@ export interface ConfigWritePreview {
|
|
|
90
90
|
}
|
|
91
91
|
|
|
92
92
|
export function getPiGlobalConfigPath(overridePath?: string): string {
|
|
93
|
-
return overridePath ? resolve(overridePath) :
|
|
93
|
+
return overridePath ? resolve(overridePath) : getAgentPath("mcp.json");
|
|
94
94
|
}
|
|
95
95
|
|
|
96
96
|
export function getGenericGlobalConfigPath(): string {
|
package/mcp-auth.ts
CHANGED
|
@@ -5,12 +5,13 @@
|
|
|
5
5
|
* and PKCE state for MCP servers. Maintains backward compatibility with
|
|
6
6
|
* per-server directory structure.
|
|
7
7
|
*
|
|
8
|
-
* Token storage location:
|
|
8
|
+
* Token storage location: $MCP_OAUTH_DIR/<server>/tokens.json when set,
|
|
9
|
+
* otherwise <Pi agent dir>/mcp-oauth/<server>/tokens.json
|
|
9
10
|
*/
|
|
10
11
|
|
|
11
12
|
import { mkdirSync, readFileSync, writeFileSync, existsSync, rmSync } from 'fs';
|
|
12
13
|
import { join } from 'path';
|
|
13
|
-
import {
|
|
14
|
+
import { getAgentPath } from './agent-dir.js';
|
|
14
15
|
|
|
15
16
|
/** OAuth token storage format */
|
|
16
17
|
export interface StoredTokens {
|
|
@@ -39,9 +40,8 @@ export interface AuthEntry {
|
|
|
39
40
|
|
|
40
41
|
// Base directory for auth storage - can be overridden via env var for testing
|
|
41
42
|
function getAuthBaseDir(): string {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
: join(homedir(), '.pi', 'agent', 'mcp-oauth');
|
|
43
|
+
const override = process.env.MCP_OAUTH_DIR?.trim();
|
|
44
|
+
return override ? override : getAgentPath('mcp-oauth');
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
/**
|
package/mcp-setup-panel.ts
CHANGED
|
@@ -459,7 +459,7 @@ export class McpSetupPanel {
|
|
|
459
459
|
this.callbacks.previewImports(this.discovery.imports.filter((entry) => this.selectedImports.has(entry.kind)).map((entry) => entry.kind)),
|
|
460
460
|
[
|
|
461
461
|
`Detected imports: ${this.discovery.imports.map((entry) => `${entry.kind} (${entry.serverCount} servers)`).join(", ")}`,
|
|
462
|
-
"Selected imports are written into
|
|
462
|
+
"Selected imports are written into the Pi agent dir config as Pi-owned compatibility state.",
|
|
463
463
|
],
|
|
464
464
|
);
|
|
465
465
|
case "view-example":
|
|
@@ -480,7 +480,7 @@ export class McpSetupPanel {
|
|
|
480
480
|
return this.formatPreview([
|
|
481
481
|
"Read order:",
|
|
482
482
|
"1. ~/.config/mcp/mcp.json",
|
|
483
|
-
"2.
|
|
483
|
+
"2. <Pi agent dir>/mcp.json",
|
|
484
484
|
"3. .mcp.json",
|
|
485
485
|
"4. .pi/mcp.json",
|
|
486
486
|
"Pi writes compatibility imports and adapter-only overrides to Pi-owned files.",
|
package/metadata-cache.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// metadata-cache.ts - Persistent MCP metadata cache
|
|
2
2
|
import { existsSync, readFileSync, writeFileSync, renameSync, mkdirSync } from "node:fs";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { dirname } from "node:path";
|
|
4
|
+
import { getAgentPath } from "./agent-dir.js";
|
|
5
5
|
import { createHash } from "node:crypto";
|
|
6
6
|
import { getToolUiResourceUri } from "@modelcontextprotocol/ext-apps/app-bridge";
|
|
7
7
|
import type { McpTool, McpResource, ServerEntry, ToolMetadata } from "./types.js";
|
|
@@ -11,7 +11,6 @@ import { extractToolUiStreamMode } from "./utils.js";
|
|
|
11
11
|
|
|
12
12
|
const CACHE_VERSION = 1;
|
|
13
13
|
const CACHE_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000;
|
|
14
|
-
const CACHE_PATH = join(homedir(), ".pi", "agent", "mcp-cache.json");
|
|
15
14
|
|
|
16
15
|
export interface CachedTool {
|
|
17
16
|
name: string;
|
|
@@ -40,13 +39,14 @@ export interface MetadataCache {
|
|
|
40
39
|
}
|
|
41
40
|
|
|
42
41
|
export function getMetadataCachePath(): string {
|
|
43
|
-
return
|
|
42
|
+
return getAgentPath("mcp-cache.json");
|
|
44
43
|
}
|
|
45
44
|
|
|
46
45
|
export function loadMetadataCache(): MetadataCache | null {
|
|
47
|
-
|
|
46
|
+
const cachePath = getMetadataCachePath();
|
|
47
|
+
if (!existsSync(cachePath)) return null;
|
|
48
48
|
try {
|
|
49
|
-
const raw = JSON.parse(readFileSync(
|
|
49
|
+
const raw = JSON.parse(readFileSync(cachePath, "utf-8"));
|
|
50
50
|
if (!raw || typeof raw !== "object") return null;
|
|
51
51
|
if (raw.version !== CACHE_VERSION) return null;
|
|
52
52
|
if (!raw.servers || typeof raw.servers !== "object") return null;
|
|
@@ -57,13 +57,14 @@ export function loadMetadataCache(): MetadataCache | null {
|
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
export function saveMetadataCache(cache: MetadataCache): void {
|
|
60
|
-
const
|
|
60
|
+
const cachePath = getMetadataCachePath();
|
|
61
|
+
const dir = dirname(cachePath);
|
|
61
62
|
mkdirSync(dir, { recursive: true });
|
|
62
63
|
|
|
63
64
|
let merged: MetadataCache = { version: CACHE_VERSION, servers: {} };
|
|
64
65
|
try {
|
|
65
|
-
if (existsSync(
|
|
66
|
-
const existing = JSON.parse(readFileSync(
|
|
66
|
+
if (existsSync(cachePath)) {
|
|
67
|
+
const existing = JSON.parse(readFileSync(cachePath, "utf-8")) as MetadataCache;
|
|
67
68
|
if (existing && existing.version === CACHE_VERSION && existing.servers) {
|
|
68
69
|
merged.servers = { ...existing.servers };
|
|
69
70
|
}
|
|
@@ -75,9 +76,9 @@ export function saveMetadataCache(cache: MetadataCache): void {
|
|
|
75
76
|
merged.version = CACHE_VERSION;
|
|
76
77
|
merged.servers = { ...merged.servers, ...cache.servers };
|
|
77
78
|
|
|
78
|
-
const tmpPath = `${
|
|
79
|
+
const tmpPath = `${cachePath}.${process.pid}.tmp`;
|
|
79
80
|
writeFileSync(tmpPath, JSON.stringify(merged, null, 2), "utf-8");
|
|
80
|
-
renameSync(tmpPath,
|
|
81
|
+
renameSync(tmpPath, cachePath);
|
|
81
82
|
}
|
|
82
83
|
|
|
83
84
|
export function computeServerHash(definition: ServerEntry): string {
|
package/npx-resolver.ts
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
// npx-resolver.ts - Resolve npx/npm exec binaries to avoid npm parent processes
|
|
2
2
|
import { existsSync, readFileSync, realpathSync, readdirSync, statSync, writeFileSync, renameSync, mkdirSync, openSync, readSync, closeSync } from "node:fs";
|
|
3
|
-
import { homedir } from "node:os";
|
|
4
3
|
import { join, dirname, extname, resolve, sep } from "node:path";
|
|
4
|
+
import { getAgentPath } from "./agent-dir.js";
|
|
5
5
|
import { spawn, spawnSync } from "node:child_process";
|
|
6
6
|
|
|
7
7
|
const CACHE_VERSION = 1;
|
|
8
8
|
const CACHE_TTL_MS = 24 * 60 * 60 * 1000;
|
|
9
|
-
const CACHE_PATH = join(homedir(), ".pi", "agent", "mcp-npx-cache.json");
|
|
10
9
|
|
|
11
10
|
interface NpxCacheEntry {
|
|
12
11
|
resolvedBin: string;
|
|
@@ -367,10 +366,15 @@ function getNpmCacheDir(): string | null {
|
|
|
367
366
|
return null;
|
|
368
367
|
}
|
|
369
368
|
|
|
369
|
+
function getNpxCachePath(): string {
|
|
370
|
+
return getAgentPath("mcp-npx-cache.json");
|
|
371
|
+
}
|
|
372
|
+
|
|
370
373
|
function loadCache(): NpxCache | null {
|
|
371
|
-
|
|
374
|
+
const cachePath = getNpxCachePath();
|
|
375
|
+
if (!existsSync(cachePath)) return null;
|
|
372
376
|
try {
|
|
373
|
-
const raw = JSON.parse(readFileSync(
|
|
377
|
+
const raw = JSON.parse(readFileSync(cachePath, "utf-8"));
|
|
374
378
|
if (!raw || typeof raw !== "object") return null;
|
|
375
379
|
if (raw.version !== CACHE_VERSION) return null;
|
|
376
380
|
if (!raw.entries || typeof raw.entries !== "object") return null;
|
|
@@ -381,13 +385,14 @@ function loadCache(): NpxCache | null {
|
|
|
381
385
|
}
|
|
382
386
|
|
|
383
387
|
function saveCacheEntry(key: string, entry: NpxCacheEntry): void {
|
|
384
|
-
const
|
|
388
|
+
const cachePath = getNpxCachePath();
|
|
389
|
+
const dir = dirname(cachePath);
|
|
385
390
|
mkdirSync(dir, { recursive: true });
|
|
386
391
|
|
|
387
392
|
let merged: NpxCache = { version: CACHE_VERSION, entries: {} };
|
|
388
393
|
try {
|
|
389
|
-
if (existsSync(
|
|
390
|
-
const existing = JSON.parse(readFileSync(
|
|
394
|
+
if (existsSync(cachePath)) {
|
|
395
|
+
const existing = JSON.parse(readFileSync(cachePath, "utf-8")) as NpxCache;
|
|
391
396
|
if (existing && existing.version === CACHE_VERSION && existing.entries) {
|
|
392
397
|
merged.entries = { ...existing.entries };
|
|
393
398
|
}
|
|
@@ -397,9 +402,9 @@ function saveCacheEntry(key: string, entry: NpxCacheEntry): void {
|
|
|
397
402
|
}
|
|
398
403
|
|
|
399
404
|
merged.entries[key] = entry;
|
|
400
|
-
const tmpPath = `${
|
|
405
|
+
const tmpPath = `${cachePath}.${process.pid}.tmp`;
|
|
401
406
|
writeFileSync(tmpPath, JSON.stringify(merged, null, 2), "utf-8");
|
|
402
|
-
renameSync(tmpPath,
|
|
407
|
+
renameSync(tmpPath, cachePath);
|
|
403
408
|
}
|
|
404
409
|
|
|
405
410
|
function safeRealpath(path: string): string {
|
package/oauth-handler.ts
CHANGED
|
@@ -1,19 +1,22 @@
|
|
|
1
1
|
// oauth-handler.ts - OAuth token management for MCP servers
|
|
2
2
|
import { existsSync, readFileSync } from "node:fs";
|
|
3
3
|
import { join } from "node:path";
|
|
4
|
-
import {
|
|
4
|
+
import { getAgentPath } from "./agent-dir.js";
|
|
5
5
|
import type { OAuthTokens } from "@modelcontextprotocol/sdk/shared/auth.js";
|
|
6
6
|
|
|
7
7
|
// Token storage path for a server
|
|
8
8
|
function getTokensPath(serverName: string): string {
|
|
9
|
-
|
|
9
|
+
const override = process.env.MCP_OAUTH_DIR?.trim();
|
|
10
|
+
const authDir = override ? override : getAgentPath("mcp-oauth");
|
|
11
|
+
return join(authDir, serverName, "tokens.json");
|
|
10
12
|
}
|
|
11
13
|
|
|
12
14
|
/**
|
|
13
15
|
* Get stored OAuth tokens for a server (if any).
|
|
14
16
|
* Returns undefined if no tokens or tokens are expired.
|
|
15
17
|
*
|
|
16
|
-
* Token file location:
|
|
18
|
+
* Token file location: $MCP_OAUTH_DIR/<server>/tokens.json when set,
|
|
19
|
+
* otherwise <Pi agent dir>/mcp-oauth/<server>/tokens.json
|
|
17
20
|
*
|
|
18
21
|
* Expected format:
|
|
19
22
|
* {
|
package/onboarding-state.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, writeFileSync, renameSync } from "node:fs";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { dirname } from "node:path";
|
|
3
|
+
import { getAgentPath } from "./agent-dir.js";
|
|
4
4
|
|
|
5
5
|
export interface McpOnboardingState {
|
|
6
6
|
version: 1;
|
|
@@ -16,7 +16,7 @@ const DEFAULT_STATE: McpOnboardingState = {
|
|
|
16
16
|
};
|
|
17
17
|
|
|
18
18
|
export function getOnboardingStatePath(): string {
|
|
19
|
-
return
|
|
19
|
+
return getAgentPath("mcp-onboarding.json");
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
export function loadOnboardingState(): McpOnboardingState {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-mcp-adapter",
|
|
3
|
-
"version": "2.5.
|
|
3
|
+
"version": "2.5.2",
|
|
4
4
|
"description": "MCP (Model Context Protocol) adapter extension for Pi coding agent",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
},
|
|
38
38
|
"files": [
|
|
39
39
|
"cli.js",
|
|
40
|
+
"agent-dir.ts",
|
|
40
41
|
"index.ts",
|
|
41
42
|
"state.ts",
|
|
42
43
|
"utils.ts",
|