pi-mcp-adapter 2.5.3 → 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 CHANGED
@@ -7,6 +7,15 @@ 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
+
10
19
  ## [2.5.3] - 2026-05-01
11
20
 
12
21
  ### Added
package/README.md CHANGED
@@ -126,7 +126,7 @@ Pi-specific files are the write targets for imported or shared global servers wh
126
126
  | `headers` | HTTP headers; supports `${VAR}` and `$env:VAR` interpolation |
127
127
  | `auth` | `"bearer"` or `"oauth"` |
128
128
  | `oauth.grantType` | `"authorization_code"` (default) or `"client_credentials"` for non-interactive machine auth |
129
- | `bearerToken` / `bearerTokenEnv` | Token or env var name |
129
+ | `bearerToken` / `bearerTokenEnv` | Token or env var name; `bearerToken` supports `${VAR}` and `$env:VAR` interpolation |
130
130
  | `lifecycle` | `"lazy"` (default), `"eager"`, or `"keep-alive"` |
131
131
  | `idleTimeout` | Minutes before idle disconnect (overrides global) |
132
132
  | `exposeResources` | Expose MCP resources as tools (default: true) |
@@ -159,7 +159,7 @@ Pi-specific files are the write targets for imported or shared global servers wh
159
159
  | `directTools` | Global default for all servers (default: false). Per-server overrides this. |
160
160
  | `disableProxyTool` | Hide the `mcp` proxy tool once configured direct tools are fully available from cache. |
161
161
  | `autoAuth` | Auto-run OAuth on `connect`/tool calls when a server needs auth, then retry once (default: false). |
162
- | `sampling` | Allow MCP servers to request LLM sampling through Pi's current/default model (default: true when UI approval is available). |
162
+ | `sampling` | Allow MCP servers to sample through Pi models, honoring `modelPreferences.hints` before current/default fallback (default: true when UI approval is available). |
163
163
  | `samplingAutoApprove` | Skip sampling confirmation prompts. Required for sampling in non-UI sessions (default: false). |
164
164
 
165
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, interpolateEnvRecord, resolveConfigPath } 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;
@@ -93,7 +93,7 @@ export function computeServerHash(definition: ServerEntry): string {
93
93
  url: definition.url,
94
94
  headers: interpolateEnvRecord(definition.headers),
95
95
  auth: definition.auth,
96
- bearerToken: definition.bearerToken ?? (definition.bearerTokenEnv ? process.env[definition.bearerTokenEnv] : undefined),
96
+ bearerToken: resolveBearerToken(definition),
97
97
  bearerTokenEnv: definition.bearerTokenEnv,
98
98
  exposeResources: definition.exposeResources,
99
99
  excludeTools: definition.excludeTools,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-mcp-adapter",
3
- "version": "2.5.3",
3
+ "version": "2.5.4",
4
4
  "description": "MCP (Model Context Protocol) adapter extension for Pi coding agent",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -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<any> | undefined;
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(options: SamplingHandlerOptions): Promise<{
118
- model: Model<any>;
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<any>[] = [];
123
- const currentModel = options.getCurrentModel();
124
- if (currentModel) candidates.push(currentModel);
126
+ const candidates: Model<Api>[] = [];
127
+ const availableModels = options.modelRegistry.getAvailable();
125
128
 
126
- for (const model of options.modelRegistry.getAvailable()) {
127
- if (!candidates.some((candidate) => candidate.provider === model.provider && candidate.id === model.id)) {
128
- candidates.push(model);
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,7 +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, resolveConfigPath } from "./utils.js";
20
+ import { interpolateEnvRecord, resolveBearerToken, resolveConfigPath } from "./utils.js";
21
21
 
22
22
  interface ServerConnection {
23
23
  client: Client;
@@ -170,8 +170,7 @@ export class McpServerManager {
170
170
 
171
171
  // For bearer auth, add the token to headers BEFORE creating requestInit
172
172
  if (definition.auth === "bearer") {
173
- const token = definition.bearerToken
174
- ?? (definition.bearerTokenEnv ? process.env[definition.bearerTokenEnv] : undefined);
173
+ const token = resolveBearerToken(definition);
175
174
  if (token) {
176
175
  headers["Authorization"] = `Bearer ${token}`;
177
176
  }
package/utils.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
2
2
  import { homedir, platform } from "node:os";
3
3
  import { join } from "node:path";
4
- import type { McpConfig } from "./types.js";
4
+ import type { McpConfig, ServerEntry } from "./types.js";
5
5
 
6
6
  async function execOpen(pi: ExtensionAPI, target: string, browser?: string) {
7
7
  const os = platform();
@@ -86,6 +86,13 @@ export function resolveConfigPath(value: string | undefined): string | undefined
86
86
  return resolved;
87
87
  }
88
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
+
89
96
  export function truncateAtWord(text: string, target: number): string {
90
97
  if (!text || text.length <= target) return text;
91
98