@qusic/pi-dynamic-mcp 0.20261015.0 → 0.20261016.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
@@ -4,7 +4,7 @@ Small [Pi](https://github.com/earendil-works/pi) extensions for personal use, pu
4
4
 
5
5
  | Extension | npm | Install | Description |
6
6
  | --- | --- | --- | --- |
7
- | [compact-filter](./compact-filter.ts) | [![npm](https://img.shields.io/npm/v/%40qusic%2Fpi-compact-filter)](https://www.npmjs.com/package/@qusic/pi-compact-filter) | `pi install npm:@qusic/pi-compact-filter` | Prune stale tracked paths from Pi's compaction context. |
7
+ | [compact-filter](./compact-filter.ts) | [![npm](https://img.shields.io/npm/v/%40qusic%2Fpi-compact-filter)](https://www.npmjs.com/package/@qusic/pi-compact-filter) | `pi install npm:@qusic/pi-compact-filter` | Keep Pi compaction file lists scoped to the current summary window. |
8
8
  | [copilot-sync](./copilot-sync.ts) | [![npm](https://img.shields.io/npm/v/%40qusic%2Fpi-copilot-sync)](https://www.npmjs.com/package/@qusic/pi-copilot-sync) | `pi install npm:@qusic/pi-copilot-sync` | Sync live GitHub Copilot model capabilities into Pi. |
9
9
  | [copilot2api](./copilot2api.ts) | [![npm](https://img.shields.io/npm/v/%40qusic%2Fpi-copilot2api)](https://www.npmjs.com/package/@qusic/pi-copilot2api) | `pi install npm:@qusic/pi-copilot2api` | Add copilot2api as a native Pi provider with live model discovery. |
10
10
  | [dynamic-mcp](./dynamic-mcp.ts) | [![npm](https://img.shields.io/npm/v/%40qusic%2Fpi-dynamic-mcp)](https://www.npmjs.com/package/@qusic/pi-dynamic-mcp) | `pi install npm:@qusic/pi-dynamic-mcp` | Dynamically manage MCP servers for the current Pi session. |
package/dynamic-mcp.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  // npm-description: Dynamically manage MCP servers for the current Pi session.
2
2
  // npm-keywords: mcp, model-context-protocol, dynamic-mcp, session-scoped, stdio, streamable-http
3
3
 
4
+ import { isDeepStrictEqual } from "node:util";
4
5
  import { StringEnum, Type } from "@earendil-works/pi-ai";
5
6
  import type { ExtensionAPI, McpServerConfig } from "@earendil-works/pi-coding-agent";
6
7
 
@@ -24,36 +25,38 @@ const parameters = Type.Object({
24
25
  });
25
26
 
26
27
  export default function (pi: ExtensionAPI) {
27
- const managed = new Map<string, "stdio" | "http">();
28
+ const managed = new Map<string, McpServerConfig>();
28
29
 
29
30
  pi.registerTool({
30
31
  name: "mcp",
31
32
  label: "MCP",
32
33
  description:
33
- "Manage session MCP servers: add(name, config) with command/args (stdio) or url (HTTP), remove(name), or list(). list shows servers added by this tool, not connection status. env and headers support ${VAR}.",
34
+ "Manage session MCP servers: add(name, config) with command/args (stdio) or url (HTTP), remove(name), or list(). list shows server configs managed by this tool, not connection status. env and headers support ${VAR}.",
34
35
  parameters,
35
36
  async execute(_id, { action, name, config }) {
36
37
  let text: string;
37
38
  if (action === "list") {
38
- text = [...managed].map(([name, type]) => `${name} (${type})`).join("\n") || "No MCP servers added by this tool";
39
+ text = JSON.stringify(Object.fromEntries(managed), null, 2);
39
40
  } else if (!name) {
40
41
  throw new Error("Server name is required");
41
42
  } else if (action === "remove") {
42
- if (!managed.has(name)) throw new Error(`Server "${name}" was not added by this extension`);
43
+ if (!managed.has(name)) throw new Error(`Server "${name}" is not managed by this tool`);
43
44
  pi.unregisterMcpServer(name);
44
45
  managed.delete(name);
45
46
  text = `Removed "${name}"`;
46
47
  } else {
47
48
  if (!config || Boolean(config.command) === Boolean(config.url)) {
48
- throw new Error("Provide either command (stdio) or url (HTTP) in config");
49
+ throw new Error("Provide exactly one of command (stdio) or url (HTTP) in config");
49
50
  }
50
51
  const common = { description: config.description, timeout: config.timeout };
51
52
  const serverConfig: McpServerConfig = config.url
52
53
  ? { ...common, url: config.url, headers: config.headers }
53
54
  : { ...common, command: config.command!, args: config.args, cwd: config.cwd, env: config.env };
55
+ const duplicate = [...managed].find(([, existing]) => isDeepStrictEqual(existing, serverConfig));
56
+ if (duplicate) throw new Error(`Server "${duplicate[0]}" is already registered with an identical config`);
54
57
  pi.registerMcpServer(name, serverConfig);
55
- managed.set(name, config.url ? "http" : "stdio");
56
- text = `Registered "${name}"; connection starts in the background`;
58
+ managed.set(name, structuredClone(serverConfig));
59
+ text = `Registered "${name}". Use codemode to find and call its tools.`;
57
60
  }
58
61
  return { content: [{ type: "text", text }], details: undefined };
59
62
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qusic/pi-dynamic-mcp",
3
- "version": "0.20261015.0",
3
+ "version": "0.20261016.0",
4
4
  "homepage": "https://github.com/Qusic/pi-extensions#readme",
5
5
  "bugs": {
6
6
  "url": "https://github.com/Qusic/pi-extensions/issues"