pi-mcp-extension 1.7.0 → 1.7.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/package.json +1 -1
- package/src/config.ts +55 -8
- package/src/oauth-provider.ts +2 -2
package/package.json
CHANGED
package/src/config.ts
CHANGED
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
* Configuration loading, validation, and merging for pi-mcp.
|
|
3
3
|
*
|
|
4
4
|
* Config file locations (Pi-native convention, highest priority first):
|
|
5
|
-
* 1. <cwd>/.pi/mcp.json
|
|
6
|
-
* 2.
|
|
5
|
+
* 1. <cwd>/.pi/mcp.json — project-level config
|
|
6
|
+
* 2. <agent dir>/mcp.json — global config; the agent dir is what Pi itself uses:
|
|
7
|
+
* $PI_CODING_AGENT_DIR when set (Pi profiles / alternative agent dirs), else ~/.pi/agent
|
|
7
8
|
*
|
|
8
9
|
* Project servers/settings override global servers/settings per-key (shallow merge).
|
|
9
10
|
* No deep merge, no env var interpolation — WYSIWYG config.
|
|
@@ -118,6 +119,25 @@ const McpConfigSchema = z.object({
|
|
|
118
119
|
mcpServers: z.record(ServerConfigSchema).default({}),
|
|
119
120
|
});
|
|
120
121
|
|
|
122
|
+
/**
|
|
123
|
+
* Project-level file: `settings` keys are optional and carry NO defaults, so a project file
|
|
124
|
+
* only overrides what it actually states. (Parsing it with SettingsSchema filled in the
|
|
125
|
+
* defaults — e.g. toolPrefix "mcp" — and the merge then overwrote the global settings.)
|
|
126
|
+
*/
|
|
127
|
+
const ProjectSettingsSchema = z.object({
|
|
128
|
+
toolPrefix: z
|
|
129
|
+
.string()
|
|
130
|
+
.regex(/^[a-zA-Z0-9_]+$/, "toolPrefix must match [a-zA-Z0-9_]")
|
|
131
|
+
.optional(),
|
|
132
|
+
requestTimeoutMs: z.number().positive().optional(),
|
|
133
|
+
maxRetries: z.number().int().min(0).max(10).optional(),
|
|
134
|
+
});
|
|
135
|
+
const ProjectConfigSchema = z.object({
|
|
136
|
+
settings: ProjectSettingsSchema.default({}),
|
|
137
|
+
mcpServers: z.record(ServerConfigSchema).default({}),
|
|
138
|
+
});
|
|
139
|
+
type ProjectConfig = z.output<typeof ProjectConfigSchema>;
|
|
140
|
+
|
|
121
141
|
// ─── Public Types ─────────────────────────────────────────────────────────────
|
|
122
142
|
|
|
123
143
|
export type AuthConfig = z.output<typeof AuthConfigSchema>;
|
|
@@ -139,7 +159,15 @@ async function readJsonFile(path: string): Promise<unknown | null> {
|
|
|
139
159
|
}
|
|
140
160
|
|
|
141
161
|
function parseConfig(raw: unknown, sourcePath: string): McpConfig {
|
|
142
|
-
|
|
162
|
+
return parseWith(McpConfigSchema, raw, sourcePath);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function parseWith<S extends z.ZodTypeAny>(
|
|
166
|
+
schema: S,
|
|
167
|
+
raw: unknown,
|
|
168
|
+
sourcePath: string,
|
|
169
|
+
): z.output<S> {
|
|
170
|
+
const result = schema.safeParse(raw);
|
|
143
171
|
if (!result.success) {
|
|
144
172
|
const issues = result.error.issues
|
|
145
173
|
.map((i) => ` ${i.path.join(".")}: ${i.message}`)
|
|
@@ -155,22 +183,41 @@ function parseConfig(raw: unknown, sourcePath: string): McpConfig {
|
|
|
155
183
|
|
|
156
184
|
function mergeConfigs(
|
|
157
185
|
globalCfg: McpConfig,
|
|
158
|
-
projectCfg:
|
|
186
|
+
projectCfg: ProjectConfig,
|
|
159
187
|
): McpConfig {
|
|
188
|
+
// Only the settings the project file states may override the global ones.
|
|
189
|
+
const stated = Object.fromEntries(
|
|
190
|
+
Object.entries(projectCfg.settings).filter(([, v]) => v !== undefined),
|
|
191
|
+
) as Partial<Settings>;
|
|
160
192
|
return {
|
|
161
193
|
// Shallow spread: project settings override global settings per key
|
|
162
|
-
settings: { ...globalCfg.settings, ...
|
|
194
|
+
settings: { ...globalCfg.settings, ...stated },
|
|
163
195
|
// Per-server override: project server entry completely replaces global entry with same name
|
|
164
196
|
mcpServers: { ...globalCfg.mcpServers, ...projectCfg.mcpServers },
|
|
165
197
|
};
|
|
166
198
|
}
|
|
167
199
|
|
|
168
200
|
/**
|
|
169
|
-
*
|
|
201
|
+
* Pi's agent directory, resolved the way Pi resolves it (`getAgentDir()` in
|
|
202
|
+
* @mariozechner/pi-coding-agent): $PI_CODING_AGENT_DIR when set (tilde expanded), else
|
|
203
|
+
* ~/.pi/agent. Pi profiles and alternative agent dirs only work through that variable.
|
|
204
|
+
*/
|
|
205
|
+
export function agentDir(env: NodeJS.ProcessEnv = process.env): string {
|
|
206
|
+
const fromEnv = env.PI_CODING_AGENT_DIR;
|
|
207
|
+
if (fromEnv && fromEnv.length > 0) {
|
|
208
|
+
if (fromEnv === "~") return homedir();
|
|
209
|
+
if (fromEnv.startsWith("~/")) return join(homedir(), fromEnv.slice(2));
|
|
210
|
+
return fromEnv;
|
|
211
|
+
}
|
|
212
|
+
return join(homedir(), ".pi", "agent");
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Load and merge global (<agent dir>/mcp.json) and project (<cwd>/.pi/mcp.json) configs.
|
|
170
217
|
* Project config takes precedence over global config.
|
|
171
218
|
* Returns a fully validated, merged config.
|
|
172
219
|
*/
|
|
173
|
-
export async function loadConfig(cwd: string, globalPath = join(
|
|
220
|
+
export async function loadConfig(cwd: string, globalPath = join(agentDir(), "mcp.json")): Promise<McpConfig> {
|
|
174
221
|
const projectPath = join(cwd, ".pi", "mcp.json");
|
|
175
222
|
|
|
176
223
|
const [globalRaw, projectRaw] = await Promise.all([
|
|
@@ -189,6 +236,6 @@ export async function loadConfig(cwd: string, globalPath = join(homedir(), ".pi"
|
|
|
189
236
|
|
|
190
237
|
if (projectRaw === null) return globalCfg;
|
|
191
238
|
|
|
192
|
-
const projectCfg =
|
|
239
|
+
const projectCfg = parseWith(ProjectConfigSchema, projectRaw, projectPath);
|
|
193
240
|
return mergeConfigs(globalCfg, projectCfg);
|
|
194
241
|
}
|
package/src/oauth-provider.ts
CHANGED
|
@@ -15,8 +15,8 @@ import type { OAuthClientProvider, OAuthDiscoveryState } from "@modelcontextprot
|
|
|
15
15
|
import type { OAuthClientMetadata, OAuthClientInformationMixed, OAuthTokens } from "@modelcontextprotocol/sdk/shared/auth.js";
|
|
16
16
|
import { readFile, writeFile, mkdir, unlink, chmod, open, stat } from "node:fs/promises";
|
|
17
17
|
import { join } from "node:path";
|
|
18
|
-
import { homedir } from "node:os";
|
|
19
18
|
import { createHash } from "node:crypto";
|
|
19
|
+
import { agentDir } from "./config.js";
|
|
20
20
|
|
|
21
21
|
// ─── Types ────────────────────────────────────────────────────────────────────
|
|
22
22
|
|
|
@@ -84,7 +84,7 @@ interface StoredState {
|
|
|
84
84
|
// ─── File helpers ─────────────────────────────────────────────────────────────
|
|
85
85
|
|
|
86
86
|
function authDir(storageDir?: string): string {
|
|
87
|
-
return storageDir ?? join(
|
|
87
|
+
return storageDir ?? join(agentDir(), "mcp-auth");
|
|
88
88
|
}
|
|
89
89
|
|
|
90
90
|
function statePath(serverName: string, storageDir?: string): string {
|