pi-mcp-adapter 1.2.2 → 1.4.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,6 +5,22 @@ 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.4.0] - 2026-01-19
9
+
10
+ ### Changed
11
+
12
+ - **Non-blocking startup** - Pi starts immediately, MCP servers connect in background. First MCP call waits only if init isn't done yet.
13
+
14
+ ### Fixed
15
+
16
+ - Tool metadata now includes `inputSchema` after `/mcp reconnect` (was missing, breaking describe and error hints)
17
+
18
+ ## [1.3.0] - 2026-01-19
19
+
20
+ ### Changed
21
+
22
+ - **Parallel server connections** - All MCP servers now connect in parallel on startup instead of sequentially, significantly faster with many servers
23
+
8
24
  ## [1.2.2] - 2026-01-19
9
25
 
10
26
  ### Fixed
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/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;
@@ -34,25 +55,33 @@ export default function mcpAdapter(pi: ExtensionAPI) {
34
55
  });
35
56
 
36
57
  pi.on("session_start", async (_event, ctx) => {
58
+ // Non-blocking init - Pi starts immediately, MCP connects in background
37
59
  initPromise = initializeMcp(pi, ctx);
38
- state = await initPromise;
39
- initPromise = null;
40
60
 
41
- // Set up callback for auto-reconnect to update metadata
42
- state.lifecycle.setReconnectCallback((serverName) => {
43
- if (state) {
44
- updateServerMetadata(state, serverName);
61
+ initPromise.then(s => {
62
+ state = s;
63
+ initPromise = null;
64
+
65
+ // Set up callback for auto-reconnect to update metadata
66
+ s.lifecycle.setReconnectCallback((serverName) => {
67
+ if (state) {
68
+ updateServerMetadata(state, serverName);
69
+ }
70
+ });
71
+
72
+ // Update status bar when ready
73
+ if (ctx.hasUI) {
74
+ const totalTools = [...s.registeredTools.values()].flat().length;
75
+ if (totalTools > 0) {
76
+ ctx.ui.setStatus("mcp", ctx.ui.theme.fg("accent", `MCP: ${totalTools} tools`));
77
+ } else {
78
+ ctx.ui.setStatus("mcp", "");
79
+ }
45
80
  }
81
+ }).catch(err => {
82
+ console.error("MCP initialization failed:", err);
83
+ initPromise = null;
46
84
  });
47
-
48
- if (ctx.hasUI) {
49
- const totalTools = [...state.registeredTools.values()].flat().length;
50
- if (totalTools > 0) {
51
- ctx.ui.setStatus("mcp", ctx.ui.theme.fg("accent", `MCP: ${totalTools} tools`));
52
- } else {
53
- ctx.ui.setStatus("mcp", "");
54
- }
55
- }
56
85
  });
57
86
 
58
87
  pi.on("session_shutdown", async () => {
@@ -74,6 +103,15 @@ export default function mcpAdapter(pi: ExtensionAPI) {
74
103
  pi.registerCommand("mcp", {
75
104
  description: "Show MCP server status",
76
105
  handler: async (args, ctx) => {
106
+ // Wait for init if still in progress
107
+ if (!state && initPromise) {
108
+ try {
109
+ state = await initPromise;
110
+ } catch {
111
+ if (ctx.hasUI) ctx.ui.notify("MCP initialization failed", "error");
112
+ return;
113
+ }
114
+ }
77
115
  if (!state) {
78
116
  if (ctx.hasUI) ctx.ui.notify("MCP not initialized", "error");
79
117
  return;
@@ -107,6 +145,15 @@ export default function mcpAdapter(pi: ExtensionAPI) {
107
145
  return;
108
146
  }
109
147
 
148
+ // Wait for init if still in progress
149
+ if (!state && initPromise) {
150
+ try {
151
+ state = await initPromise;
152
+ } catch {
153
+ if (ctx.hasUI) ctx.ui.notify("MCP initialization failed", "error");
154
+ return;
155
+ }
156
+ }
110
157
  if (!state) {
111
158
  if (ctx.hasUI) ctx.ui.notify("MCP not initialized", "error");
112
159
  return;
@@ -152,6 +199,17 @@ Mode: tool (call) > describe > search > server (list) > nothing (status)`,
152
199
  includeSchemas?: boolean;
153
200
  server?: string;
154
201
  }) {
202
+ // Wait for init if still in progress
203
+ if (!state && initPromise) {
204
+ try {
205
+ state = await initPromise;
206
+ } catch {
207
+ return {
208
+ content: [{ type: "text", text: "MCP initialization failed" }],
209
+ details: { error: "init_failed" },
210
+ };
211
+ }
212
+ }
155
213
  if (!state) {
156
214
  return {
157
215
  content: [{ type: "text", text: "MCP not initialized" }],
@@ -586,80 +644,94 @@ async function initializeMcp(
586
644
  return { manager, lifecycle, registeredTools, toolMetadata, config };
587
645
  }
588
646
 
589
- for (const [name, definition] of serverEntries) {
647
+ if (ctx.hasUI) {
648
+ ctx.ui.setStatus("mcp", `Connecting to ${serverEntries.length} servers...`);
649
+ }
650
+
651
+ // Connect to all servers in parallel (max 10 concurrent)
652
+ const results = await parallelLimit(serverEntries, 10, async ([name, definition]) => {
590
653
  try {
591
- if (ctx.hasUI) {
592
- ctx.ui.setStatus("mcp", `Connecting to ${name}...`);
593
- }
594
-
595
654
  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
- }
655
+ return { name, definition, connection, error: null };
654
656
  } catch (error) {
655
657
  const message = error instanceof Error ? error.message : String(error);
658
+ return { name, definition, connection: null, error: message };
659
+ }
660
+ });
661
+ const prefix = config.settings?.toolPrefix ?? "server";
662
+
663
+ // Process results
664
+ for (const { name, definition, connection, error } of results) {
665
+ if (error || !connection) {
656
666
  if (ctx.hasUI) {
657
- ctx.ui.notify(`MCP: Failed to connect to ${name}: ${message}`, "error");
667
+ ctx.ui.notify(`MCP: Failed to connect to ${name}: ${error}`, "error");
658
668
  }
659
- console.error(`MCP: Failed to connect to ${name}: ${message}`);
669
+ console.error(`MCP: Failed to connect to ${name}: ${error}`);
670
+ continue;
671
+ }
672
+
673
+ // Collect tool names (NOT registered with Pi - only mcp proxy is registered)
674
+ const { collected: toolNames, failed: failedTools } = collectToolNames(
675
+ connection.tools,
676
+ { serverName: name, prefix }
677
+ );
678
+
679
+ // Collect resource tool names (if enabled)
680
+ if (definition.exposeResources !== false && connection.resources.length > 0) {
681
+ const resourceToolNames = collectResourceToolNames(
682
+ connection.resources,
683
+ { serverName: name, prefix }
684
+ );
685
+ toolNames.push(...resourceToolNames);
686
+ }
687
+
688
+ registeredTools.set(name, toolNames);
689
+
690
+ // Build tool metadata for searching (include inputSchema for describe/errors)
691
+ const metadata: ToolMetadata[] = connection.tools.map(tool => ({
692
+ name: formatToolName(tool.name, name, prefix),
693
+ originalName: tool.name,
694
+ description: tool.description ?? "",
695
+ inputSchema: tool.inputSchema,
696
+ }));
697
+ // Add resource tools to metadata
698
+ for (const resource of connection.resources) {
699
+ if (definition.exposeResources !== false) {
700
+ const baseName = `get_${resourceNameToToolName(resource.name)}`;
701
+ metadata.push({
702
+ name: formatToolName(baseName, name, prefix),
703
+ originalName: baseName,
704
+ description: resource.description ?? `Read resource: ${resource.uri}`,
705
+ resourceUri: resource.uri,
706
+ });
707
+ }
708
+ }
709
+ toolMetadata.set(name, metadata);
710
+
711
+ // Mark keep-alive servers
712
+ if (definition.lifecycle === "keep-alive") {
713
+ lifecycle.markKeepAlive(name, definition);
714
+ }
715
+
716
+ if (failedTools.length > 0 && ctx.hasUI) {
717
+ ctx.ui.notify(
718
+ `MCP: ${name} - ${failedTools.length} tools skipped`,
719
+ "warning"
720
+ );
660
721
  }
661
722
  }
662
723
 
724
+ // Summary notification
725
+ const connectedCount = results.filter(r => r.connection).length;
726
+ const failedCount = results.filter(r => r.error).length;
727
+ if (ctx.hasUI && connectedCount > 0) {
728
+ const totalTools = [...registeredTools.values()].flat().length;
729
+ const msg = failedCount > 0
730
+ ? `MCP: ${connectedCount}/${serverEntries.length} servers connected (${totalTools} tools)`
731
+ : `MCP: ${connectedCount} servers connected (${totalTools} tools)`;
732
+ ctx.ui.notify(msg, "info");
733
+ }
734
+
663
735
  // Start health checks for keep-alive servers
664
736
  lifecycle.startHealthChecks();
665
737
 
@@ -792,11 +864,12 @@ async function reconnectServers(
792
864
 
793
865
  state.registeredTools.set(name, toolNames);
794
866
 
795
- // Update tool metadata for searching
867
+ // Update tool metadata for searching (include inputSchema for describe/errors)
796
868
  const metadata: ToolMetadata[] = connection.tools.map(tool => ({
797
869
  name: formatToolName(tool.name, name, prefix),
798
870
  originalName: tool.name,
799
871
  description: tool.description ?? "",
872
+ inputSchema: tool.inputSchema,
800
873
  }));
801
874
  for (const resource of connection.resources) {
802
875
  if (definition.exposeResources !== false) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-mcp-adapter",
3
- "version": "1.2.2",
3
+ "version": "1.4.0",
4
4
  "description": "MCP (Model Context Protocol) adapter extension for Pi coding agent",
5
5
  "type": "module",
6
6
  "license": "MIT",