pi-mcp-extension
Version:
Full-featured, lightweight MCP extension for the Pi coding agent: stdio, SSE & Streamable HTTP transports, OAuth 2.1 (PKCE + DCR), lazy/eager lifecycles — minimal dependencies, zero bloat.
79 lines (70 loc) • 3.01 kB
text/typescript
/**
* Per-server configuration provenance for `/mcp <name>` and `mcp_status`.
*
* A server entry can come from the global `<agentDir>/mcp.json`, the project's
* `<cwd>/.pi/mcp.json`, or `pi.registerMcpServer()`. The merged config that the
* runtime holds loses that information, so `loadConfigWithOrigins` reports the
* file-level origin and the runtime merges in the extension-registered names here.
*/
import type { FileConfigOrigin } from "./config.js";
import { sanitizeTerminalText } from "./terminal-text.js";
/** Where a configured server's winning entry came from. */
export type ServerConfigSource = FileConfigOrigin | "extension";
export interface ConfigProvenanceInput {
/** Winning origin per server name, as reported by `loadConfigWithOrigins`. */
origins: Record<string, FileConfigOrigin>;
/** Project config path for the cwd the current config was loaded from. */
projectConfigPath: string;
/** False when `<cwd>/.pi/mcp.json` was skipped (untrusted project, or bootstrap). */
projectConfigIncluded: boolean;
}
export interface ConfigProvenance {
/** Winning source per configured server name. */
sources: Record<string, ServerConfigSource>;
projectConfigPath: string;
projectConfigIncluded: boolean;
}
/**
* Combine file-level origins with the names registered by extensions.
* File entries win: `collectRegisteredServers` drops a registered server that a
* config file already defines, so an extension can never shadow a file entry.
*/
export function withExtensionServers(
input: ConfigProvenanceInput,
extensionServerNames: readonly string[],
): ConfigProvenance {
const sources: Record<string, ServerConfigSource> = {};
for (const [name, origin] of Object.entries(input.origins)) {
sources[name] = origin;
}
for (const name of extensionServerNames) {
if (!Object.hasOwn(sources, name)) {
sources[name] = "extension";
}
}
return {
sources,
projectConfigPath: input.projectConfigPath,
projectConfigIncluded: input.projectConfigIncluded,
};
}
/** Recorded source for `serverName`, or `undefined` when it is not in `provenance.sources`. */
export function lookupServerSource(
provenance: ConfigProvenance,
serverName: string,
): ServerConfigSource | undefined {
return Object.hasOwn(provenance.sources, serverName)
? provenance.sources[serverName]
: undefined;
}
/** Source shown for a server, or `"unknown"` when it is not in the loaded config. */
export function formatServerSource(source: ServerConfigSource | undefined): ServerConfigSource | "unknown" {
return source ?? "unknown";
}
/** Note explaining a skipped project config, or undefined when it was applied. */
export function formatProjectConfigNote(provenance: ConfigProvenance): string | undefined {
if (provenance.projectConfigIncluded) {
return undefined;
}
return `Project config: not loaded (${sanitizeTerminalText(provenance.projectConfigPath)}) — this project is not trusted, so its mcp.json is never read`;
}