pi-mcp-adapter 2.5.2 → 2.5.4
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 +14 -0
- package/README.md +5 -4
- package/metadata-cache.ts +5 -5
- package/package.json +1 -1
- package/sampling-handler.ts +32 -11
- package/server-manager.ts +7 -21
- package/utils.ts +37 -2
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [2.5.4] - 2026-05-04
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
- Ignored npm lockfiles and removed checked-in `package-lock.json` files.
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
- Resolved `${VAR}` and `$env:VAR` placeholders in HTTP bearer tokens.
|
|
17
|
+
- Honored MCP sampling `modelPreferences.hints` before falling back to the current/default model.
|
|
18
|
+
|
|
19
|
+
## [2.5.3] - 2026-05-01
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
- Added environment variable and `~` expansion for stdio server `cwd` values.
|
|
23
|
+
|
|
10
24
|
## [2.5.2] - 2026-04-29
|
|
11
25
|
|
|
12
26
|
### Fixed
|
package/README.md
CHANGED
|
@@ -120,12 +120,13 @@ Pi-specific files are the write targets for imported or shared global servers wh
|
|
|
120
120
|
|-------|-------------|
|
|
121
121
|
| `command` | Executable for stdio transport |
|
|
122
122
|
| `args` | Command arguments |
|
|
123
|
-
| `env` | Environment variables
|
|
124
|
-
| `cwd` | Working directory |
|
|
123
|
+
| `env` | Environment variables; supports `${VAR}` and `$env:VAR` interpolation |
|
|
124
|
+
| `cwd` | Working directory; supports `${VAR}`, `$env:VAR`, and `~` expansion |
|
|
125
125
|
| `url` | HTTP endpoint (StreamableHTTP with SSE fallback) |
|
|
126
|
+
| `headers` | HTTP headers; supports `${VAR}` and `$env:VAR` interpolation |
|
|
126
127
|
| `auth` | `"bearer"` or `"oauth"` |
|
|
127
128
|
| `oauth.grantType` | `"authorization_code"` (default) or `"client_credentials"` for non-interactive machine auth |
|
|
128
|
-
| `bearerToken` / `bearerTokenEnv` | Token or env var name |
|
|
129
|
+
| `bearerToken` / `bearerTokenEnv` | Token or env var name; `bearerToken` supports `${VAR}` and `$env:VAR` interpolation |
|
|
129
130
|
| `lifecycle` | `"lazy"` (default), `"eager"`, or `"keep-alive"` |
|
|
130
131
|
| `idleTimeout` | Minutes before idle disconnect (overrides global) |
|
|
131
132
|
| `exposeResources` | Expose MCP resources as tools (default: true) |
|
|
@@ -158,7 +159,7 @@ Pi-specific files are the write targets for imported or shared global servers wh
|
|
|
158
159
|
| `directTools` | Global default for all servers (default: false). Per-server overrides this. |
|
|
159
160
|
| `disableProxyTool` | Hide the `mcp` proxy tool once configured direct tools are fully available from cache. |
|
|
160
161
|
| `autoAuth` | Auto-run OAuth on `connect`/tool calls when a server needs auth, then retry once (default: false). |
|
|
161
|
-
| `sampling` | Allow MCP servers to
|
|
162
|
+
| `sampling` | Allow MCP servers to sample through Pi models, honoring `modelPreferences.hints` before current/default fallback (default: true when UI approval is available). |
|
|
162
163
|
| `samplingAutoApprove` | Skip sampling confirmation prompts. Required for sampling in non-UI sessions (default: false). |
|
|
163
164
|
|
|
164
165
|
Per-server `idleTimeout` overrides the global setting.
|
package/metadata-cache.ts
CHANGED
|
@@ -7,7 +7,7 @@ import { getToolUiResourceUri } from "@modelcontextprotocol/ext-apps/app-bridge"
|
|
|
7
7
|
import type { McpTool, McpResource, ServerEntry, ToolMetadata } from "./types.js";
|
|
8
8
|
import { formatToolName, isToolExcluded } from "./types.js";
|
|
9
9
|
import { resourceNameToToolName } from "./resource-tools.js";
|
|
10
|
-
import { extractToolUiStreamMode } from "./utils.js";
|
|
10
|
+
import { extractToolUiStreamMode, interpolateEnvRecord, resolveBearerToken, resolveConfigPath } from "./utils.js";
|
|
11
11
|
|
|
12
12
|
const CACHE_VERSION = 1;
|
|
13
13
|
const CACHE_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000;
|
|
@@ -88,12 +88,12 @@ export function computeServerHash(definition: ServerEntry): string {
|
|
|
88
88
|
const identity: Record<string, unknown> = {
|
|
89
89
|
command: definition.command,
|
|
90
90
|
args: definition.args,
|
|
91
|
-
env: definition.env,
|
|
92
|
-
cwd: definition.cwd,
|
|
91
|
+
env: interpolateEnvRecord(definition.env),
|
|
92
|
+
cwd: resolveConfigPath(definition.cwd),
|
|
93
93
|
url: definition.url,
|
|
94
|
-
headers: definition.headers,
|
|
94
|
+
headers: interpolateEnvRecord(definition.headers),
|
|
95
95
|
auth: definition.auth,
|
|
96
|
-
bearerToken: definition
|
|
96
|
+
bearerToken: resolveBearerToken(definition),
|
|
97
97
|
bearerTokenEnv: definition.bearerTokenEnv,
|
|
98
98
|
exposeResources: definition.exposeResources,
|
|
99
99
|
excludeTools: definition.excludeTools,
|
package/package.json
CHANGED
package/sampling-handler.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { complete, type AssistantMessage, type Message, type Model, type TextContent } from "@mariozechner/pi-ai";
|
|
1
|
+
import { complete, type Api, type AssistantMessage, type Message, type Model, type TextContent } from "@mariozechner/pi-ai";
|
|
2
2
|
import { truncateAtWord } from "./utils.js";
|
|
3
3
|
import type { ExtensionUIContext, ModelRegistry } from "@mariozechner/pi-coding-agent";
|
|
4
4
|
import type { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
CreateMessageRequestSchema,
|
|
7
7
|
type CreateMessageRequest,
|
|
8
8
|
type CreateMessageResult,
|
|
9
|
+
type ModelPreferences,
|
|
9
10
|
type SamplingMessage,
|
|
10
11
|
type SamplingMessageContentBlock,
|
|
11
12
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
@@ -15,7 +16,7 @@ export interface SamplingHandlerOptions {
|
|
|
15
16
|
autoApprove: boolean;
|
|
16
17
|
ui?: ExtensionUIContext;
|
|
17
18
|
modelRegistry: ModelRegistry;
|
|
18
|
-
getCurrentModel: () => Model<
|
|
19
|
+
getCurrentModel: () => Model<Api> | undefined;
|
|
19
20
|
getSignal: () => AbortSignal | undefined;
|
|
20
21
|
}
|
|
21
22
|
|
|
@@ -50,7 +51,7 @@ export async function handleSamplingRequest(
|
|
|
50
51
|
}
|
|
51
52
|
|
|
52
53
|
const messages = params.messages.map(convertSamplingMessage);
|
|
53
|
-
const { model, apiKey, headers } = await resolveSamplingModel(options);
|
|
54
|
+
const { model, apiKey, headers } = await resolveSamplingModel(options, params.modelPreferences);
|
|
54
55
|
await confirmSampling(
|
|
55
56
|
options,
|
|
56
57
|
"Approve MCP sampling request",
|
|
@@ -114,21 +115,35 @@ function messageText(message: Message): string {
|
|
|
114
115
|
}).join("\n");
|
|
115
116
|
}
|
|
116
117
|
|
|
117
|
-
async function resolveSamplingModel(
|
|
118
|
-
|
|
118
|
+
async function resolveSamplingModel(
|
|
119
|
+
options: SamplingHandlerOptions,
|
|
120
|
+
modelPreferences: ModelPreferences | undefined,
|
|
121
|
+
): Promise<{
|
|
122
|
+
model: Model<Api>;
|
|
119
123
|
apiKey?: string;
|
|
120
124
|
headers?: Record<string, string>;
|
|
121
125
|
}> {
|
|
122
|
-
const candidates: Model<
|
|
123
|
-
const
|
|
124
|
-
if (currentModel) candidates.push(currentModel);
|
|
126
|
+
const candidates: Model<Api>[] = [];
|
|
127
|
+
const availableModels = options.modelRegistry.getAvailable();
|
|
125
128
|
|
|
126
|
-
for (const
|
|
127
|
-
|
|
128
|
-
|
|
129
|
+
for (const hint of modelPreferences?.hints ?? []) {
|
|
130
|
+
const normalizedHint = hint.name?.trim().toLowerCase();
|
|
131
|
+
if (!normalizedHint) continue;
|
|
132
|
+
for (const model of availableModels) {
|
|
133
|
+
const searchableNames = [`${model.provider}/${model.id}`, model.id, model.name];
|
|
134
|
+
if (searchableNames.some((name) => name.toLowerCase().includes(normalizedHint))) {
|
|
135
|
+
addSamplingCandidate(candidates, model);
|
|
136
|
+
}
|
|
129
137
|
}
|
|
130
138
|
}
|
|
131
139
|
|
|
140
|
+
const currentModel = options.getCurrentModel();
|
|
141
|
+
if (currentModel) addSamplingCandidate(candidates, currentModel);
|
|
142
|
+
|
|
143
|
+
for (const model of availableModels) {
|
|
144
|
+
addSamplingCandidate(candidates, model);
|
|
145
|
+
}
|
|
146
|
+
|
|
132
147
|
const errors: string[] = [];
|
|
133
148
|
for (const model of candidates) {
|
|
134
149
|
const auth = await options.modelRegistry.getApiKeyAndHeaders(model);
|
|
@@ -144,6 +159,12 @@ async function resolveSamplingModel(options: SamplingHandlerOptions): Promise<{
|
|
|
144
159
|
throw new Error("No Pi model is available for MCP sampling");
|
|
145
160
|
}
|
|
146
161
|
|
|
162
|
+
function addSamplingCandidate(candidates: Model<Api>[], model: Model<Api>): void {
|
|
163
|
+
if (!candidates.some((candidate) => candidate.provider === model.provider && candidate.id === model.id)) {
|
|
164
|
+
candidates.push(model);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
147
168
|
async function confirmSampling(options: SamplingHandlerOptions, title: string, message: string): Promise<void> {
|
|
148
169
|
if (options.autoApprove) return;
|
|
149
170
|
if (!options.ui) {
|
package/server-manager.ts
CHANGED
|
@@ -17,6 +17,7 @@ import { logger } from "./logger.js";
|
|
|
17
17
|
import { McpOAuthProvider } from "./mcp-oauth-provider.js";
|
|
18
18
|
import { supportsOAuth } from "./mcp-auth-flow.js";
|
|
19
19
|
import { registerSamplingHandler, type ServerSamplingConfig } from "./sampling-handler.js";
|
|
20
|
+
import { interpolateEnvRecord, resolveBearerToken, resolveConfigPath } from "./utils.js";
|
|
20
21
|
|
|
21
22
|
interface ServerConnection {
|
|
22
23
|
client: Client;
|
|
@@ -91,7 +92,7 @@ export class McpServerManager {
|
|
|
91
92
|
command,
|
|
92
93
|
args,
|
|
93
94
|
env: resolveEnv(definition.env),
|
|
94
|
-
cwd: definition.cwd,
|
|
95
|
+
cwd: resolveConfigPath(definition.cwd),
|
|
95
96
|
stderr: definition.debug ? "inherit" : "ignore",
|
|
96
97
|
});
|
|
97
98
|
} else if (definition.url) {
|
|
@@ -169,8 +170,7 @@ export class McpServerManager {
|
|
|
169
170
|
|
|
170
171
|
// For bearer auth, add the token to headers BEFORE creating requestInit
|
|
171
172
|
if (definition.auth === "bearer") {
|
|
172
|
-
const token = definition
|
|
173
|
-
?? (definition.bearerTokenEnv ? process.env[definition.bearerTokenEnv] : undefined);
|
|
173
|
+
const token = resolveBearerToken(definition);
|
|
174
174
|
if (token) {
|
|
175
175
|
headers["Authorization"] = `Bearer ${token}`;
|
|
176
176
|
}
|
|
@@ -362,28 +362,14 @@ function resolveEnv(env?: Record<string, string>): Record<string, string> {
|
|
|
362
362
|
}
|
|
363
363
|
|
|
364
364
|
if (!env) return resolved;
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
resolved[key] = value
|
|
369
|
-
.replace(/\$\{(\w+)\}/g, (_, name) => process.env[name] ?? "")
|
|
370
|
-
.replace(/\$env:(\w+)/g, (_, name) => process.env[name] ?? "");
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
return resolved;
|
|
365
|
+
|
|
366
|
+
const overrides = interpolateEnvRecord(env);
|
|
367
|
+
return overrides ? { ...resolved, ...overrides } : resolved;
|
|
374
368
|
}
|
|
375
369
|
|
|
376
370
|
/**
|
|
377
371
|
* Resolve headers with environment variable interpolation.
|
|
378
372
|
*/
|
|
379
373
|
function resolveHeaders(headers?: Record<string, string>): Record<string, string> | undefined {
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
const resolved: Record<string, string> = {};
|
|
383
|
-
for (const [key, value] of Object.entries(headers)) {
|
|
384
|
-
resolved[key] = value
|
|
385
|
-
.replace(/\$\{(\w+)\}/g, (_, name) => process.env[name] ?? "")
|
|
386
|
-
.replace(/\$env:(\w+)/g, (_, name) => process.env[name] ?? "");
|
|
387
|
-
}
|
|
388
|
-
return resolved;
|
|
374
|
+
return interpolateEnvRecord(headers);
|
|
389
375
|
}
|
package/utils.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
2
|
-
import { platform } from "node:os";
|
|
3
|
-
import
|
|
2
|
+
import { homedir, platform } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import type { McpConfig, ServerEntry } from "./types.js";
|
|
4
5
|
|
|
5
6
|
async function execOpen(pi: ExtensionAPI, target: string, browser?: string) {
|
|
6
7
|
const os = platform();
|
|
@@ -58,6 +59,40 @@ export function getConfigPathFromArgv(): string | undefined {
|
|
|
58
59
|
return undefined;
|
|
59
60
|
}
|
|
60
61
|
|
|
62
|
+
export function interpolateEnvVars(value: string): string {
|
|
63
|
+
return value
|
|
64
|
+
.replace(/\$\{(\w+)\}/g, (_, name) => process.env[name] ?? "")
|
|
65
|
+
.replace(/\$env:(\w+)/g, (_, name) => process.env[name] ?? "");
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function interpolateEnvRecord(values: Record<string, string> | undefined): Record<string, string> | undefined {
|
|
69
|
+
if (!values) return undefined;
|
|
70
|
+
|
|
71
|
+
const resolved: Record<string, string> = {};
|
|
72
|
+
for (const [key, value] of Object.entries(values)) {
|
|
73
|
+
resolved[key] = interpolateEnvVars(value);
|
|
74
|
+
}
|
|
75
|
+
return resolved;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function resolveConfigPath(value: string | undefined): string | undefined {
|
|
79
|
+
if (value === undefined) return undefined;
|
|
80
|
+
|
|
81
|
+
const resolved = interpolateEnvVars(value);
|
|
82
|
+
if (resolved === "~") return homedir();
|
|
83
|
+
if (resolved.startsWith("~/") || resolved.startsWith("~\\")) {
|
|
84
|
+
return join(homedir(), resolved.slice(2));
|
|
85
|
+
}
|
|
86
|
+
return resolved;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function resolveBearerToken(definition: Pick<ServerEntry, "bearerToken" | "bearerTokenEnv">): string | undefined {
|
|
90
|
+
if (definition.bearerToken !== undefined) {
|
|
91
|
+
return interpolateEnvVars(definition.bearerToken);
|
|
92
|
+
}
|
|
93
|
+
return definition.bearerTokenEnv ? process.env[definition.bearerTokenEnv] : undefined;
|
|
94
|
+
}
|
|
95
|
+
|
|
61
96
|
export function truncateAtWord(text: string, target: number): string {
|
|
62
97
|
if (!text || text.length <= target) return text;
|
|
63
98
|
|