pi-codemcp 1.2.0 → 1.2.1
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 +1 -1
- package/package.json +1 -1
- package/sidecar/gateway.py +41 -1
- package/sidecar/models.py +8 -0
- package/src/tools.ts +15 -3
package/README.md
CHANGED
|
@@ -148,7 +148,7 @@ The Python sidecar enforces catalog cache TTL, execution timeout, per-tool timeo
|
|
|
148
148
|
|
|
149
149
|
## Search and execute flow
|
|
150
150
|
|
|
151
|
-
The agent searches for a capability, inspects the selected exact stub when needed, and executes a compact plan:
|
|
151
|
+
The agent searches for a capability, inspects the selected exact stub when needed, and executes a compact plan. Unscoped searches discover stale or missing server catalogs independently: available servers still return results, while `discovery_failures` explicitly reports unavailable servers. Server-scoped searches remain fail-fast.
|
|
152
152
|
|
|
153
153
|
```python
|
|
154
154
|
issues = await linear.list_issues({"assignee": "me", "limit": 50})
|
package/package.json
CHANGED
package/sidecar/gateway.py
CHANGED
|
@@ -36,6 +36,7 @@ from .models import (
|
|
|
36
36
|
SearchDetail,
|
|
37
37
|
SearchMode,
|
|
38
38
|
SearchResponse,
|
|
39
|
+
ServerDiscoveryFailure,
|
|
39
40
|
ServerToolSummary,
|
|
40
41
|
StatusResponse,
|
|
41
42
|
UpstreamStatus,
|
|
@@ -377,7 +378,11 @@ class GatewayRuntime:
|
|
|
377
378
|
discovery_servers = ()
|
|
378
379
|
else:
|
|
379
380
|
discovery_servers = (server,)
|
|
380
|
-
|
|
381
|
+
if server is None:
|
|
382
|
+
discovery_failures = await self._discover_servers_for_unscoped_search(discovery_servers)
|
|
383
|
+
else:
|
|
384
|
+
await self._ensure_servers_discovered(discovery_servers)
|
|
385
|
+
discovery_failures = []
|
|
381
386
|
self.stats_store.record_phase("discovery", _elapsed_ms(discovery_started))
|
|
382
387
|
bounded_limit = min(max(limit, 1), 20)
|
|
383
388
|
bounded_cursor = max(cursor, 0)
|
|
@@ -439,6 +444,7 @@ class GatewayRuntime:
|
|
|
439
444
|
has_more=next_cursor is not None,
|
|
440
445
|
project_scope_available=self.chain_store.project_store is not None,
|
|
441
446
|
execution_limits=self._execution_limits_view(),
|
|
447
|
+
discovery_failures=discovery_failures,
|
|
442
448
|
prelude=self.catalog.stub_prelude if include_prelude else None,
|
|
443
449
|
results=results,
|
|
444
450
|
)
|
|
@@ -981,6 +987,36 @@ class GatewayRuntime:
|
|
|
981
987
|
async def _ensure_catalog_complete(self) -> None:
|
|
982
988
|
await self._ensure_servers_discovered(self.handles.keys())
|
|
983
989
|
|
|
990
|
+
async def _discover_servers_for_unscoped_search(
|
|
991
|
+
self,
|
|
992
|
+
server_names: Iterable[str],
|
|
993
|
+
) -> list[ServerDiscoveryFailure]:
|
|
994
|
+
requested = set(server_names)
|
|
995
|
+
missing = [
|
|
996
|
+
(name, handle)
|
|
997
|
+
for name, handle in self.handles.items()
|
|
998
|
+
if name in requested and handle.tools is None
|
|
999
|
+
]
|
|
1000
|
+
if not missing:
|
|
1001
|
+
return []
|
|
1002
|
+
results = await asyncio.gather(
|
|
1003
|
+
*(handle.discover() for _, handle in missing),
|
|
1004
|
+
return_exceptions=True,
|
|
1005
|
+
)
|
|
1006
|
+
failures: list[ServerDiscoveryFailure] = []
|
|
1007
|
+
for (name, _handle), result in zip(missing, results, strict=True):
|
|
1008
|
+
if isinstance(result, Exception):
|
|
1009
|
+
failures.append(
|
|
1010
|
+
ServerDiscoveryFailure(
|
|
1011
|
+
server=name,
|
|
1012
|
+
error=_discovery_error_message(result),
|
|
1013
|
+
)
|
|
1014
|
+
)
|
|
1015
|
+
elif isinstance(result, BaseException):
|
|
1016
|
+
raise result
|
|
1017
|
+
await self._rebuild_catalog()
|
|
1018
|
+
return failures
|
|
1019
|
+
|
|
984
1020
|
async def _ensure_servers_discovered(self, server_names: Iterable[str]) -> None:
|
|
985
1021
|
requested = set(server_names)
|
|
986
1022
|
missing = [
|
|
@@ -1170,6 +1206,10 @@ def _elapsed_ms(started: float) -> float:
|
|
|
1170
1206
|
return (time.perf_counter() - started) * 1_000
|
|
1171
1207
|
|
|
1172
1208
|
|
|
1209
|
+
def _discovery_error_message(error: Exception) -> str:
|
|
1210
|
+
return str(error).strip() or type(error).__name__
|
|
1211
|
+
|
|
1212
|
+
|
|
1173
1213
|
def _parse_code(code: str) -> ast.AST | None:
|
|
1174
1214
|
normalized = textwrap.dedent(code).strip("\n")
|
|
1175
1215
|
wrapped = f"async def __codemcp_main():\n{textwrap.indent(normalized, ' ')}\n"
|
package/sidecar/models.py
CHANGED
|
@@ -63,6 +63,13 @@ class ServerToolSummary(BaseModel):
|
|
|
63
63
|
tool_count: int
|
|
64
64
|
|
|
65
65
|
|
|
66
|
+
class ServerDiscoveryFailure(BaseModel):
|
|
67
|
+
model_config = ConfigDict(extra="forbid", strict=True)
|
|
68
|
+
|
|
69
|
+
server: str
|
|
70
|
+
error: str
|
|
71
|
+
|
|
72
|
+
|
|
66
73
|
class ExecutionLimitsView(BaseModel):
|
|
67
74
|
model_config = ConfigDict(extra="forbid", strict=True)
|
|
68
75
|
|
|
@@ -85,6 +92,7 @@ class SearchResponse(BaseModel):
|
|
|
85
92
|
has_more: bool = False
|
|
86
93
|
project_scope_available: bool
|
|
87
94
|
execution_limits: ExecutionLimitsView
|
|
95
|
+
discovery_failures: list[ServerDiscoveryFailure] = Field(default_factory=list)
|
|
88
96
|
prelude: str | None = None
|
|
89
97
|
results: list[ToolSchemaView]
|
|
90
98
|
|
package/src/tools.ts
CHANGED
|
@@ -30,6 +30,7 @@ interface SearchRenderDetails extends CodeMcpOutputDetails {
|
|
|
30
30
|
nextCursor?: number;
|
|
31
31
|
detail: string;
|
|
32
32
|
preview: string[];
|
|
33
|
+
discoveryFailures: string[];
|
|
33
34
|
}
|
|
34
35
|
|
|
35
36
|
const SearchParameters = Type.Object({
|
|
@@ -154,7 +155,7 @@ export function registerCodeMcpTools(
|
|
|
154
155
|
name: "codemcp_search",
|
|
155
156
|
label: "MCP Search",
|
|
156
157
|
description:
|
|
157
|
-
"Search configured MCP capabilities or page through compact inventory. Default signature search includes exact stubs for up to three top matches plus compact alternatives; codemcp_inspect loads other selected stubs. Returns ranking evidence, pagination, scope, and execution limits; invalid server names fail with suggestions.",
|
|
158
|
+
"Search configured MCP capabilities or page through compact inventory. Default signature search includes exact stubs for up to three top matches plus compact alternatives; codemcp_inspect loads other selected stubs. Unscoped searches return available results plus explicit discovery failures; scoped searches remain fail-fast. Returns ranking evidence, pagination, scope, and execution limits; invalid server names fail with suggestions.",
|
|
158
159
|
promptSnippet: "Discover compact MCP capabilities or inventory",
|
|
159
160
|
promptGuidelines: [...SEARCH_PROMPT_GUIDELINES],
|
|
160
161
|
parameters: SearchParameters,
|
|
@@ -186,6 +187,11 @@ export function registerCodeMcpTools(
|
|
|
186
187
|
return [`${String(item.name)} ${String(item.tool_count ?? 0)}`];
|
|
187
188
|
})
|
|
188
189
|
: [];
|
|
190
|
+
const discoveryFailures = Array.isArray(result.discovery_failures)
|
|
191
|
+
? result.discovery_failures.flatMap((item) =>
|
|
192
|
+
isRecord(item) && typeof item.server === "string" ? [item.server] : [],
|
|
193
|
+
)
|
|
194
|
+
: [];
|
|
189
195
|
return {
|
|
190
196
|
content: [{ type: "text", text: output.text }],
|
|
191
197
|
details: {
|
|
@@ -197,6 +203,7 @@ export function registerCodeMcpTools(
|
|
|
197
203
|
nextCursor: typeof result.next_cursor === "number" ? result.next_cursor : undefined,
|
|
198
204
|
detail: typeof result.detail === "string" ? result.detail : "signatures",
|
|
199
205
|
preview,
|
|
206
|
+
discoveryFailures,
|
|
200
207
|
},
|
|
201
208
|
};
|
|
202
209
|
},
|
|
@@ -212,13 +219,18 @@ export function registerCodeMcpTools(
|
|
|
212
219
|
if (isPartial) return new Text(theme.fg("warning", "Searching catalog..."), 0, 0);
|
|
213
220
|
if (expanded) return renderExpandedJson(result.content);
|
|
214
221
|
const details = result.details as SearchRenderDetails | undefined;
|
|
222
|
+
const discoveryFailures = details?.discoveryFailures ?? [];
|
|
223
|
+
const unavailable = discoveryFailures.length;
|
|
215
224
|
let text = theme.fg(
|
|
216
|
-
"success",
|
|
217
|
-
`${details?.matchCount ?? 0} matches · ${details?.detail ?? "signatures"} · ${details?.totalToolCount ?? 0} tools · ${details?.serverCount ?? 0} servers`,
|
|
225
|
+
unavailable > 0 ? "warning" : "success",
|
|
226
|
+
`${details?.matchCount ?? 0} matches · ${details?.detail ?? "signatures"} · ${details?.totalToolCount ?? 0} tools · ${details?.serverCount ?? 0} servers${unavailable > 0 ? ` · ${unavailable} unavailable` : ""}`,
|
|
218
227
|
);
|
|
219
228
|
for (const name of details?.preview ?? []) {
|
|
220
229
|
text += `\n${theme.fg("dim", ` ${name}`)}`;
|
|
221
230
|
}
|
|
231
|
+
for (const server of discoveryFailures) {
|
|
232
|
+
text += `\n${theme.fg("warning", ` unavailable: ${server}`)}`;
|
|
233
|
+
}
|
|
222
234
|
if (details?.hasMore) {
|
|
223
235
|
text += `\n${theme.fg("muted", ` more at cursor ${details.nextCursor ?? "?"}`)}`;
|
|
224
236
|
}
|