pi-mcp-adapter 1.2.1 → 1.3.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/CHANGELOG.md CHANGED
@@ -5,7 +5,19 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
- ## [1.2.0] - 2026-01-19
8
+ ## [1.3.0] - 2026-01-19
9
+
10
+ ### Changed
11
+
12
+ - **Parallel server connections** - All MCP servers now connect in parallel on startup instead of sequentially, significantly faster with many servers
13
+
14
+ ## [1.2.2] - 2026-01-19
15
+
16
+ ### Fixed
17
+
18
+ - Installer now downloads from `main` branch (renamed from `master`)
19
+
20
+ ## [1.2.1] - 2026-01-19
9
21
 
10
22
  ### Added
11
23
 
package/README.md CHANGED
@@ -129,4 +129,3 @@ See [ARCHITECTURE.md](./ARCHITECTURE.md) for details. Short version:
129
129
 
130
130
  - OAuth tokens obtained externally (no browser flow)
131
131
  - No automatic token refresh
132
- - Servers connect sequentially on startup
package/cli.js CHANGED
@@ -6,7 +6,7 @@ import https from "node:https";
6
6
  import os from "node:os";
7
7
  import { execSync } from "node:child_process";
8
8
 
9
- const REPO_URL = "https://raw.githubusercontent.com/nicobailon/pi-mcp-adapter/master";
9
+ const REPO_URL = "https://raw.githubusercontent.com/nicobailon/pi-mcp-adapter/main";
10
10
  const EXT_DIR = path.join(os.homedir(), ".pi", "agent", "extensions", "pi-mcp-adapter");
11
11
  const SETTINGS_FILE = path.join(os.homedir(), ".pi", "agent", "settings.json");
12
12
  const EXT_PATH = "~/.pi/agent/extensions/pi-mcp-adapter/index.ts";
@@ -59,7 +59,7 @@ async function main() {
59
59
 
60
60
  console.log("\nInstalling dependencies...");
61
61
  try {
62
- execSync("npm install --production", { cwd: EXT_DIR, stdio: "inherit" });
62
+ execSync("npm install --omit=dev", { cwd: EXT_DIR, stdio: "inherit" });
63
63
  } catch {
64
64
  console.error("Warning: npm install failed. You may need to run it manually.");
65
65
  }
package/index.ts CHANGED
@@ -24,6 +24,27 @@ interface McpExtensionState {
24
24
  config: McpConfig;
25
25
  }
26
26
 
27
+ /** Run async tasks with concurrency limit */
28
+ async function parallelLimit<T, R>(
29
+ items: T[],
30
+ limit: number,
31
+ fn: (item: T) => Promise<R>
32
+ ): Promise<R[]> {
33
+ const results: R[] = [];
34
+ let index = 0;
35
+
36
+ async function worker() {
37
+ while (index < items.length) {
38
+ const i = index++;
39
+ results[i] = await fn(items[i]);
40
+ }
41
+ }
42
+
43
+ const workers = Array(Math.min(limit, items.length)).fill(null).map(() => worker());
44
+ await Promise.all(workers);
45
+ return results;
46
+ }
47
+
27
48
  export default function mcpAdapter(pi: ExtensionAPI) {
28
49
  let state: McpExtensionState | null = null;
29
50
  let initPromise: Promise<McpExtensionState> | null = null;
@@ -586,78 +607,92 @@ async function initializeMcp(
586
607
  return { manager, lifecycle, registeredTools, toolMetadata, config };
587
608
  }
588
609
 
589
- for (const [name, definition] of serverEntries) {
610
+ if (ctx.hasUI) {
611
+ ctx.ui.setStatus("mcp", `Connecting to ${serverEntries.length} servers...`);
612
+ }
613
+
614
+ // Connect to all servers in parallel (max 10 concurrent)
615
+ const results = await parallelLimit(serverEntries, 10, async ([name, definition]) => {
590
616
  try {
591
- if (ctx.hasUI) {
592
- ctx.ui.setStatus("mcp", `Connecting to ${name}...`);
593
- }
594
-
595
617
  const connection = await manager.connect(name, definition);
596
- const prefix = config.settings?.toolPrefix ?? "server";
597
-
598
- // Collect tool names (NOT registered with Pi - only mcp proxy is registered)
599
- const { collected: toolNames, failed: failedTools } = collectToolNames(
600
- connection.tools,
601
- { serverName: name, prefix }
602
- );
603
-
604
- // Collect resource tool names (if enabled)
605
- if (definition.exposeResources !== false && connection.resources.length > 0) {
606
- const resourceToolNames = collectResourceToolNames(
607
- connection.resources,
608
- { serverName: name, prefix }
609
- );
610
- toolNames.push(...resourceToolNames);
611
- }
612
-
613
- registeredTools.set(name, toolNames);
614
-
615
- // Build tool metadata for searching (include inputSchema for describe/errors)
616
- const metadata: ToolMetadata[] = connection.tools.map(tool => ({
617
- name: formatToolName(tool.name, name, prefix),
618
- originalName: tool.name,
619
- description: tool.description ?? "",
620
- inputSchema: tool.inputSchema,
621
- }));
622
- // Add resource tools to metadata
623
- for (const resource of connection.resources) {
624
- if (definition.exposeResources !== false) {
625
- const baseName = `get_${resourceNameToToolName(resource.name)}`;
626
- metadata.push({
627
- name: formatToolName(baseName, name, prefix),
628
- originalName: baseName,
629
- description: resource.description ?? `Read resource: ${resource.uri}`,
630
- resourceUri: resource.uri,
631
- });
632
- }
633
- }
634
- toolMetadata.set(name, metadata);
635
-
636
- // Mark keep-alive servers
637
- if (definition.lifecycle === "keep-alive") {
638
- lifecycle.markKeepAlive(name, definition);
639
- }
640
-
641
- if (failedTools.length > 0 && ctx.hasUI) {
642
- ctx.ui.notify(
643
- `MCP: ${name} - ${failedTools.length} tools skipped`,
644
- "warning"
645
- );
646
- }
647
-
648
- if (ctx.hasUI) {
649
- ctx.ui.notify(
650
- `MCP: ${name} connected (${connection.tools.length} tools, ${connection.resources.length} resources)`,
651
- "info"
652
- );
653
- }
618
+ return { name, definition, connection, error: null };
654
619
  } catch (error) {
655
620
  const message = error instanceof Error ? error.message : String(error);
621
+ return { name, definition, connection: null, error: message };
622
+ }
623
+ });
624
+ const prefix = config.settings?.toolPrefix ?? "server";
625
+
626
+ // Process results
627
+ for (const { name, definition, connection, error } of results) {
628
+ if (error || !connection) {
656
629
  if (ctx.hasUI) {
657
- ctx.ui.notify(`MCP: Failed to connect to ${name}: ${message}`, "error");
630
+ ctx.ui.notify(`MCP: Failed to connect to ${name}: ${error}`, "error");
658
631
  }
659
- console.error(`MCP: Failed to connect to ${name}: ${message}`);
632
+ console.error(`MCP: Failed to connect to ${name}: ${error}`);
633
+ continue;
660
634
  }
635
+
636
+ // Collect tool names (NOT registered with Pi - only mcp proxy is registered)
637
+ const { collected: toolNames, failed: failedTools } = collectToolNames(
638
+ connection.tools,
639
+ { serverName: name, prefix }
640
+ );
641
+
642
+ // Collect resource tool names (if enabled)
643
+ if (definition.exposeResources !== false && connection.resources.length > 0) {
644
+ const resourceToolNames = collectResourceToolNames(
645
+ connection.resources,
646
+ { serverName: name, prefix }
647
+ );
648
+ toolNames.push(...resourceToolNames);
649
+ }
650
+
651
+ registeredTools.set(name, toolNames);
652
+
653
+ // Build tool metadata for searching (include inputSchema for describe/errors)
654
+ const metadata: ToolMetadata[] = connection.tools.map(tool => ({
655
+ name: formatToolName(tool.name, name, prefix),
656
+ originalName: tool.name,
657
+ description: tool.description ?? "",
658
+ inputSchema: tool.inputSchema,
659
+ }));
660
+ // Add resource tools to metadata
661
+ for (const resource of connection.resources) {
662
+ if (definition.exposeResources !== false) {
663
+ const baseName = `get_${resourceNameToToolName(resource.name)}`;
664
+ metadata.push({
665
+ name: formatToolName(baseName, name, prefix),
666
+ originalName: baseName,
667
+ description: resource.description ?? `Read resource: ${resource.uri}`,
668
+ resourceUri: resource.uri,
669
+ });
670
+ }
671
+ }
672
+ toolMetadata.set(name, metadata);
673
+
674
+ // Mark keep-alive servers
675
+ if (definition.lifecycle === "keep-alive") {
676
+ lifecycle.markKeepAlive(name, definition);
677
+ }
678
+
679
+ if (failedTools.length > 0 && ctx.hasUI) {
680
+ ctx.ui.notify(
681
+ `MCP: ${name} - ${failedTools.length} tools skipped`,
682
+ "warning"
683
+ );
684
+ }
685
+ }
686
+
687
+ // Summary notification
688
+ const connectedCount = results.filter(r => r.connection).length;
689
+ const failedCount = results.filter(r => r.error).length;
690
+ if (ctx.hasUI && connectedCount > 0) {
691
+ const totalTools = [...registeredTools.values()].flat().length;
692
+ const msg = failedCount > 0
693
+ ? `MCP: ${connectedCount}/${serverEntries.length} servers connected (${totalTools} tools)`
694
+ : `MCP: ${connectedCount} servers connected (${totalTools} tools)`;
695
+ ctx.ui.notify(msg, "info");
661
696
  }
662
697
 
663
698
  // Start health checks for keep-alive servers
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-mcp-adapter",
3
- "version": "1.2.1",
3
+ "version": "1.3.0",
4
4
  "description": "MCP (Model Context Protocol) adapter extension for Pi coding agent",
5
5
  "type": "module",
6
6
  "license": "MIT",