pi-mcp-client 0.10.0 → 0.12.0

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/README.md CHANGED
@@ -3,6 +3,38 @@
3
3
  Connect Pi to MCP servers so the model can use their tools and read their
4
4
  resources. You can also browse, preview, and use server-provided prompts.
5
5
 
6
+ > [!NOTE]
7
+ > On Pi **0.99.1 and later**, this extension is no longer needed for basic MCP
8
+ > use: Pi has built-in support for server tools, resources, and OAuth. Keep this
9
+ > extension if you need OS-backed credential storage or the additional features
10
+ > below.
11
+
12
+ ## ⚖️ Do you need this extension?
13
+
14
+ Comparison with [Pi 1.0.0](https://github.com/earendil-works/pi/releases/tag/v1.0.0).
15
+
16
+ | Feature | Built-in MCP | Pi MCP Client |
17
+ | --- | :---: | :---: |
18
+ | HTTP and stdio server tools | ✅ | ✅ |
19
+ | OAuth sign-in and token refresh | ✅ | ✅ |
20
+ | Tool namespaces and codemode calls | ✅ | ✅ |
21
+ | Resource listing, URI templates, and reads | ✅ | ✅ |
22
+ | [Separate OAuth accounts at the same endpoint](docs/authentication.md#use-multiple-accounts) | ✅ | ✅ |
23
+ | [OAuth credentials in the OS credential store](docs/authentication.md#sign-in-with-oauth) | ❌ | ✅ |
24
+ | [DPoP-bound OAuth tokens](docs/authentication.md#use-dpop-bound-tokens) | ❌ | ✅ |
25
+ | [Server prompts with preview before use](docs/commands.md#use-server-prompts) | ❌ | ✅ |
26
+ | [Resource argument completion](docs/tool-reference.md#complete-resource-arguments) | ❌ | ✅ |
27
+ | [Resource change watches](docs/commands.md#watch-resource-changes) | ❌ | ✅ |
28
+ | [Server requests for user input (elicitation)](docs/behavior.md#server-requests-for-input) | ❌ | ✅ |
29
+ | [Configuration imports with preview](docs/commands.md#import-server-definitions) | ❌ | ✅ |
30
+ | OAuth clients requiring a client secret | ✅ | ❌ |
31
+ | [OAuth authorization-server metadata override](https://github.com/earendil-works/pi/blob/v1.0.0/packages/coding-agent/docs/mcp.md#authenticate-with-oauth) | ✅ | ❌ |
32
+ | HTTP authentication with a provider's `/login` token | ✅ | ❌ |
33
+
34
+ Both read the same `mcp.json` files, but OAuth credentials don't transfer between
35
+ implementations. See
36
+ [switching to built-in MCP](docs/behavior.md#pis-built-in-mcp-support).
37
+
6
38
  ## 🚀 Installation
7
39
 
8
40
  ```sh
@@ -43,9 +75,9 @@ permissions. Tool activation isn't a per-call approval prompt. See
43
75
  ## ⚙️ Configuration
44
76
 
45
77
  Store server definitions in `~/.pi/agent/mcp.json` or a trusted project's
46
- `.mcp.json`. Project servers load only after an explicit
47
- [trust decision](docs/behavior.md#project-trust). Project definitions replace
48
- same-named global definitions in full.
78
+ `.pi/mcp.json`, the same files and shape as Pi's built-in MCP support. Project
79
+ servers load only in a project that Pi [trusts](docs/behavior.md#project-trust).
80
+ Project definitions replace same-named global definitions in full.
49
81
  You can edit these files or use `/mcp add` and `/mcp remove`. To reuse a
50
82
  Claude/Cursor JSON or Codex TOML file, run `/mcp import --scope global <path>` and
51
83
  review the selected connections before saving.
@@ -65,9 +97,17 @@ Detailed guides:
65
97
  - [Troubleshooting](docs/troubleshooting.md): Error codes, recovery, and large
66
98
  results.
67
99
 
100
+ ## 🧬 Pi Durable
101
+
102
+ Custom hosts can use the experimental `pi-mcp-client/durable` adapter for
103
+ metadata discovery, native tool activation, resource reads, and template
104
+ completion. It shares configuration and the MCP credential store with this
105
+ extension. See [the durable host contract](docs/durable.md) for installation,
106
+ restart ordering, trust, and UI requirements.
107
+
68
108
  ## 🧰 Requirements
69
109
 
70
- - Pi 0.85.1 or later.
110
+ - Pi 0.99 or later.
71
111
  - Node.js 22 or later.
72
112
  - The server executable for stdio connections.
73
113
  - An available OS credential store for OAuth. Linux requires a working Secret
package/dist/auth.d.ts ADDED
@@ -0,0 +1,72 @@
1
+ import { type OAuthClientProvider, type DpopSession, type OAuthClientInformationContext, type OAuthClientMetadata, type OAuthDiscoveryState, type StoredOAuthClientInformation, type StoredOAuthTokens } from "@modelcontextprotocol/client";
2
+ import { type ServerConfig, type ClientOptions } from "./config.js";
3
+ export type OAuthOptions = Pick<ClientOptions, "oauthScopes" | "oauthCallbackPort" | "oauthDpop">;
4
+ export declare function callbackUrl(options?: OAuthOptions): string;
5
+ /** Validate the full handoff URL before giving the response to the SDK. */
6
+ export declare function parseCallback(value: string, redirect: string, state: string): URLSearchParams;
7
+ export interface OAuthIdentity {
8
+ server: string;
9
+ url: string;
10
+ clientId?: string;
11
+ }
12
+ export interface SecretStore {
13
+ read(): string | null;
14
+ write(value: string): void;
15
+ remove(): void;
16
+ }
17
+ export type CredentialStoreFactory = (identity: OAuthIdentity) => Promise<SecretStore>;
18
+ export declare function credentialKey({ server, url, clientId }: OAuthIdentity): string;
19
+ export declare function credentialStore(identity: OAuthIdentity): Promise<SecretStore>;
20
+ export declare class OAuthProvider implements OAuthClientProvider {
21
+ private readonly store;
22
+ private readonly redirect?;
23
+ private readonly options;
24
+ readonly redirectUrl: string;
25
+ readonly clientMetadata: OAuthClientMetadata;
26
+ private data;
27
+ private snapshot;
28
+ private readonly epoch;
29
+ private readonly key;
30
+ private verifier?;
31
+ private discovery?;
32
+ private requestedScope?;
33
+ private dpopSession?;
34
+ private resolvedDpopSession?;
35
+ readonly expectedState: `${string}-${string}-${string}-${string}-${string}`;
36
+ private readonly clientId?;
37
+ constructor(identity: OAuthIdentity, store: SecretStore, redirect?: ((url: URL) => void | Promise<void>) | undefined, options?: OAuthOptions);
38
+ private assertCurrent;
39
+ private save;
40
+ state(): `${string}-${string}-${string}-${string}-${string}`;
41
+ clientInformation(ctx?: OAuthClientInformationContext): ({
42
+ client_id: string;
43
+ client_secret?: string | undefined;
44
+ client_id_issued_at?: number | undefined;
45
+ client_secret_expires_at?: number | undefined;
46
+ } & {
47
+ issuer?: string;
48
+ }) | undefined;
49
+ private lookupClientInformation;
50
+ saveClientInformation(info: StoredOAuthClientInformation, ctx?: OAuthClientInformationContext): void;
51
+ tokens(ctx?: OAuthClientInformationContext): StoredOAuthTokens | undefined;
52
+ saveTokens(tokens: StoredOAuthTokens): void;
53
+ dpop(): Promise<DpopSession | undefined>;
54
+ saveCodeVerifier(value: string): void;
55
+ codeVerifier(): string;
56
+ saveDiscoveryState(value: OAuthDiscoveryState): void;
57
+ discoveryState(): OAuthDiscoveryState | undefined;
58
+ redirectToAuthorization(url: URL): Promise<void>;
59
+ invalidateCredentials(scope: "all" | "client" | "tokens" | "verifier" | "discovery"): void;
60
+ }
61
+ /** Reuse stored tokens without requiring a working keyring for public servers.
62
+ * Only explicit login may register a client or open a browser; background
63
+ * authentication may reuse or refresh existing grants through the SDK.
64
+ */
65
+ export declare function connectionAuthProvider(server: string, config: ServerConfig, storeFactory?: CredentialStoreFactory): Promise<OAuthClientProvider | undefined>;
66
+ export type Revocation = "confirmed" | "unsupported" | "unconfirmed" | "not-needed";
67
+ /** Local removal is unconditional; revocation is bounded, issuer-bound, and best effort. */
68
+ export declare function logout(identity: OAuthIdentity, store: SecretStore, signal?: AbortSignal): Promise<Revocation>;
69
+ /** Explicit user action only; OAuth never opens a browser during search or execution. */
70
+ export declare function authenticate(identity: OAuthIdentity, open: (url: string) => Promise<void>, signal?: AbortSignal, store?: SecretStore, options?: OAuthOptions & {
71
+ handoff?: (authorizationUrl: string, signal: AbortSignal) => Promise<string | undefined>;
72
+ }): Promise<void>;
@@ -0,0 +1,7 @@
1
+ import type { ExtensionContext, KeybindingsManager, Theme } from "@earendil-works/pi-coding-agent";
2
+ import { type Component } from "@earendil-works/pi-tui";
3
+ export declare function reviewAuthorization(ctx: ExtensionContext, server: string, requested: string[], combined: string[], grantedScopesKnown: boolean, signal: AbortSignal): Promise<boolean>;
4
+ /** Height-bounded plain-text review. Enter advances; only explicit 'a' on the
5
+ * last page approves. Resize reflows the body and resets review to its start.
6
+ */
7
+ export declare function authorizationReview(server: string, text: string, rows: () => number, theme: Theme, keys: KeybindingsManager, done: (approved: boolean) => void, render: () => void): Component;
@@ -0,0 +1,14 @@
1
+ import { type OAuthIdentity, type OAuthOptions, type SecretStore } from "./auth.js";
2
+ /** Server scope names are data, not instructions. Keep their size and rendering bounded. */
3
+ export declare function parseScopes(value: string | undefined): string[] | undefined;
4
+ export declare function authorizationOptions(identity: OAuthIdentity, store: SecretStore, options: OAuthOptions, required: string[]): {
5
+ options: OAuthOptions;
6
+ grantedScopesKnown: boolean;
7
+ };
8
+ /** Scope names have already been reviewed; don't repeat an unbounded list in a loader. */
9
+ export declare function loginSummary(options: OAuthOptions, reviewed: boolean): string;
10
+ export interface ScopeChallenge {
11
+ scopes: string[];
12
+ identity: string;
13
+ expires: number;
14
+ }
@@ -0,0 +1,42 @@
1
+ import type { TSchema } from "typebox";
2
+ /** The MCP tool annotation hints Pi forwards to permission extensions. */
3
+ export interface ToolHints {
4
+ readOnlyHint?: boolean;
5
+ destructiveHint?: boolean;
6
+ idempotentHint?: boolean;
7
+ openWorldHint?: boolean;
8
+ }
9
+ /** Keep only boolean hints; everything else a server sends is untrusted noise. */
10
+ export declare function toolHints(value: unknown): ToolHints | undefined;
11
+ export interface CatalogTool {
12
+ server: string;
13
+ name: string;
14
+ nativeName: string;
15
+ description: string;
16
+ inputSchema: TSchema;
17
+ annotations?: ToolHints;
18
+ identity: string;
19
+ schemaHash: string;
20
+ }
21
+ export declare function plain(value: string): string;
22
+ export declare function line(value: string): string;
23
+ export declare function nativeName(server: string, name: string): string;
24
+ export declare function prepareTool(server: string, identity: string, tool: {
25
+ name: string;
26
+ description?: string;
27
+ inputSchema: unknown;
28
+ annotations?: unknown;
29
+ }): CatalogTool;
30
+ /** Discovery exposes names, never a callable signature. */
31
+ export declare function summarize(tool: CatalogTool, includeIdentifier?: boolean): string;
32
+ /** Suggestions are informational only; resolution is case-sensitive and exact. */
33
+ export declare function resolveTools(tools: CatalogTool[], identifiers: string[]): {
34
+ identifier: string;
35
+ tool: CatalogTool | undefined;
36
+ suggestions: string[];
37
+ }[];
38
+ export declare const DEFAULT_SEARCH_LIMIT = 5;
39
+ export declare const MAX_SEARCH_LIMIT = 50;
40
+ export declare function tokens(text: string): string[];
41
+ /** Exact selectors win; ordinary retrieval weights names over descriptions. */
42
+ export declare function searchTools(tools: CatalogTool[], query: string, server?: string, limit?: number): CatalogTool[];