pi-mono/apps/p2/plugins/mcp/environments.ts
Armin Ronacher 7dc38ff0e9 feat(p2): MCP servers on the host, on environments and from projects
A manager on the p2 server owns every MCP connection and process. Session
workers hold one view each and lease the locations their conversations
work at; the manager runs the owner's mcp.json servers there (stdio per
session and location, or shared; HTTP shared by default) and offers their
tools as deferred Durable tools found through tool_search.

Stdio servers on an environment start through its pi-env daemon, one
connection per environment shared by its instances, through cmd.exe on
Windows. A project's .pi/mcp.json, read by the worker at each location,
may add stdio servers that run there and switch the owner's servers on or
off; the manager refuses everything else. A lease gets servers only on
environments its session works on.
2026-11-03 16:48:38 +01:00

44 lines
1.7 KiB
TypeScript

// The supervisor's pi-env connections to environments: one per environment and configuration revision, shared by every
// instance running there and closed with the last one. A connection lost meanwhile starts again at its next request
// (pi-env); the daemon killed the programs it ran, and their instances reconnect.
import type { Connection } from "@earendil-works/pi-env";
import { envRevision } from "@p2/app/v1/node";
import type { EnvConfig } from "../envs/config.ts";
/** A connection to environment `name` configured as `config`, which starts at its first request. */
export type ConnectEnv = (name: string, config: EnvConfig) => Connection;
/** One instance's use of an environment's connection; released once, when the instance closes. */
export type EnvLease = { readonly connection: Connection; release(): void };
/** The open connections by environment and revision, with how many instances use each. */
export class EnvConnections {
readonly #connect: ConnectEnv;
readonly #open = new Map<string, { readonly connection: Connection; users: number }>();
constructor(connect: ConnectEnv) {
this.#connect = connect;
}
/** The connection to `name` as configured now (opened for its first user); release it once done. */
acquire(name: string, config: EnvConfig): EnvLease {
const key = JSON.stringify([name, envRevision(config)]);
let entry = this.#open.get(key);
if (entry === undefined) {
entry = { connection: this.#connect(name, config), users: 0 };
this.#open.set(key, entry);
}
const used = entry;
used.users++;
return {
connection: used.connection,
release: () => {
used.users--;
if (used.users > 0) return;
this.#open.delete(key);
used.connection.close();
},
};
}
}