@geohar/pi-mcp-combiner 0.14.3 → 0.16.0-dev.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 +71 -48
- package/dist/client/config-ladder.d.ts +1 -1
- package/dist/client/config-ladder.d.ts.map +1 -1
- package/dist/client/control.d.ts +1 -1
- package/dist/client/control.d.ts.map +1 -1
- package/dist/client/elicitation.d.ts +4 -2
- package/dist/client/elicitation.d.ts.map +1 -1
- package/dist/client/elicitation.js +8 -2
- package/dist/client/elicitation.js.map +1 -1
- package/dist/client/footer.d.ts +1 -1
- package/dist/client/footer.d.ts.map +1 -1
- package/dist/client/panel.d.ts +1 -1
- package/dist/client/panel.d.ts.map +1 -1
- package/dist/client/panel.js +1 -1
- package/dist/client/panel.js.map +1 -1
- package/dist/client/prompts.d.ts +1 -1
- package/dist/client/prompts.d.ts.map +1 -1
- package/dist/client/proxy-tool.d.ts +1 -1
- package/dist/client/proxy-tool.d.ts.map +1 -1
- package/dist/client/resource-naming.d.ts +8 -0
- package/dist/client/resource-naming.d.ts.map +1 -0
- package/dist/client/resource-naming.js +33 -0
- package/dist/client/resource-naming.js.map +1 -0
- package/dist/client/settings.d.ts +5 -4
- package/dist/client/settings.d.ts.map +1 -1
- package/dist/client/settings.js +3 -3
- package/dist/client/settings.js.map +1 -1
- package/dist/client/tool-matching.d.ts +32 -0
- package/dist/client/tool-matching.d.ts.map +1 -0
- package/dist/client/tool-matching.js +43 -0
- package/dist/client/tool-matching.js.map +1 -0
- package/dist/client/types.d.ts +71 -0
- package/dist/client/types.d.ts.map +1 -0
- package/dist/client/types.js +16 -0
- package/dist/client/types.js.map +1 -0
- package/dist/client/widget-support.d.ts +17 -0
- package/dist/client/widget-support.d.ts.map +1 -0
- package/dist/client/widget-support.js +89 -0
- package/dist/client/widget-support.js.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +109 -53
- package/dist/index.js.map +1 -1
- package/dist/{client/connection.d.ts → native/combiner-connection.d.ts} +46 -38
- package/dist/native/combiner-connection.d.ts.map +1 -0
- package/dist/native/combiner-connection.js +412 -0
- package/dist/native/combiner-connection.js.map +1 -0
- package/dist/native/index.d.ts +30 -0
- package/dist/native/index.d.ts.map +1 -0
- package/dist/native/index.js +61 -0
- package/dist/native/index.js.map +1 -0
- package/dist/native/resources.d.ts +18 -0
- package/dist/native/resources.d.ts.map +1 -0
- package/dist/native/resources.js +97 -0
- package/dist/native/resources.js.map +1 -0
- package/dist/native/tool-surface.d.ts +26 -0
- package/dist/native/tool-surface.d.ts.map +1 -0
- package/dist/native/tool-surface.js +113 -0
- package/dist/native/tool-surface.js.map +1 -0
- package/dist/pi.d.ts +19 -0
- package/dist/pi.d.ts.map +1 -1
- package/package.json +3 -5
- package/src/client/config-ladder.ts +1 -1
- package/src/client/control.ts +1 -1
- package/src/client/elicitation.ts +10 -2
- package/src/client/footer.ts +1 -1
- package/src/client/panel.ts +2 -2
- package/src/client/prompts.ts +1 -1
- package/src/client/proxy-tool.ts +1 -1
- package/src/client/resource-naming.ts +36 -0
- package/src/client/settings.ts +7 -6
- package/src/client/tool-matching.ts +69 -0
- package/src/client/types.ts +80 -0
- package/src/client/widget-support.ts +86 -0
- package/src/index.ts +109 -65
- package/src/native/combiner-connection.ts +483 -0
- package/src/native/index.ts +100 -0
- package/src/native/resources.ts +112 -0
- package/src/native/tool-surface.ts +147 -0
- package/src/pi.ts +16 -0
- package/dist/client/connection.d.ts.map +0 -1
- package/dist/client/connection.js +0 -332
- package/dist/client/connection.js.map +0 -1
- package/dist/client/direct-tools.d.ts +0 -41
- package/dist/client/direct-tools.d.ts.map +0 -1
- package/dist/client/direct-tools.js +0 -122
- package/dist/client/direct-tools.js.map +0 -1
- package/dist/client/resources.d.ts +0 -20
- package/dist/client/resources.d.ts.map +0 -1
- package/dist/client/resources.js +0 -119
- package/dist/client/resources.js.map +0 -1
- package/dist/client/script.d.ts +0 -10
- package/dist/client/script.d.ts.map +0 -1
- package/dist/client/script.js +0 -130
- package/dist/client/script.js.map +0 -1
- package/src/client/connection.ts +0 -373
- package/src/client/direct-tools.ts +0 -146
- package/src/client/resources.ts +0 -137
- package/src/client/script.ts +0 -150
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
// NATIVE RESOURCE TOOLS : read_* tools for
|
|
2
|
+
// INTERACTIVE (mcp-app / ui://) resources — the parity half of legacy-client/
|
|
3
|
+
// resources.ts that pi's built-in read_mcp_resource deliberately does NOT cover
|
|
4
|
+
// (it skips ui:// listings and renders nothing).
|
|
5
|
+
//
|
|
6
|
+
// Parity contract with legacy read_<resource> tools:
|
|
7
|
+
// - SAME naming: read_<resourceNameToToolName(resource.name ?? uri)>
|
|
8
|
+
// - SAME attribution + project filtering: resourceServer() + `<server>_`/
|
|
9
|
+
// `<server>-` prefix matching against the per-project ServerFilter
|
|
10
|
+
// - SAME read behavior: connection.readResource(uri) → renderResourceResult
|
|
11
|
+
// (markup as text, json prettified, blobs as byte notes, 16KB guard)
|
|
12
|
+
// - SAME surfacing: append the combiner UI-host URL (this session's token),
|
|
13
|
+
// auto-open in the browser when a dialog-capable UI exists (uiAutoOpen)
|
|
14
|
+
// - idempotent registration; re-syncs on resources/list_changed (the
|
|
15
|
+
// connection's onToolsChanged hook covers both list-change notifications)
|
|
16
|
+
//
|
|
17
|
+
// TEXT/image resources are NOT re-registered here — pi's built-in
|
|
18
|
+
// read_mcp_resource + tool_search discovery covers them in native mode; that is
|
|
19
|
+
// the deliberate (documented) divergence from legacy, which registered one
|
|
20
|
+
// zero-arg tool per resource of every kind.
|
|
21
|
+
|
|
22
|
+
import type { ExtensionAPI, ToolDefinition } from "../pi.js"
|
|
23
|
+
import type { NativeCombinerConnection, ResourceSummary } from "./combiner-connection.js"
|
|
24
|
+
import { isInteractiveResource, openInBrowser, renderResourceResult } from "../client/widget-support.js"
|
|
25
|
+
import { resourceNameToToolName, resourceServer } from "../client/resource-naming.js"
|
|
26
|
+
import type { ServerFilter } from "../client/config-ladder.js"
|
|
27
|
+
|
|
28
|
+
export type NativeResourceOptions = {
|
|
29
|
+
connection: NativeCombinerConnection
|
|
30
|
+
/** Per-project server filter, same semantics as legacy filterResources. */
|
|
31
|
+
serverFilter?: ServerFilter
|
|
32
|
+
/** Auto-open the widget URL on read (index.ts's uiAutoOpen setting). */
|
|
33
|
+
uiAutoOpen?: boolean
|
|
34
|
+
log: (level: "info" | "warn" | "error", message: string) => void
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Project server filter for resources — mirrors legacy filterResources
|
|
38
|
+
* attribution semantics (uri host first, `<server>_` name-prefix fallback). */
|
|
39
|
+
export function filterInteractiveResources(
|
|
40
|
+
resources: ResourceSummary[],
|
|
41
|
+
filter: ServerFilter | undefined,
|
|
42
|
+
): ResourceSummary[] {
|
|
43
|
+
const interactive = resources.filter(isInteractiveResource)
|
|
44
|
+
if (!filter) return interactive
|
|
45
|
+
const matches = (r: ResourceSummary, entry: string): boolean =>
|
|
46
|
+
resourceServer(r) === entry ||
|
|
47
|
+
(r.name ?? "").startsWith(`${entry}_`) ||
|
|
48
|
+
(r.name ?? "").startsWith(`${entry}-`)
|
|
49
|
+
if (filter.allow?.length) return interactive.filter((r) => filter.allow!.some((e) => matches(r, e)))
|
|
50
|
+
if (filter.deny?.length) return interactive.filter((r) => !filter.deny!.some((e) => matches(r, e)))
|
|
51
|
+
return interactive
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function toReadTool(toolName: string, resource: ResourceSummary, opts: NativeResourceOptions): ToolDefinition {
|
|
55
|
+
const label = resource.name ?? resource.uri
|
|
56
|
+
const url = opts.connection.uiUrlFor(resource.uri)
|
|
57
|
+
return {
|
|
58
|
+
name: toolName,
|
|
59
|
+
label: `MCP resource: ${label}`,
|
|
60
|
+
description: [
|
|
61
|
+
resource.description ?? `Read MCP resource ${resource.uri}`,
|
|
62
|
+
`uri: ${resource.uri}`,
|
|
63
|
+
resource.mimeType ? `type: ${resource.mimeType}` : undefined,
|
|
64
|
+
"interactive app resource — markup as text; the interactive widget opens from the URL appended to results",
|
|
65
|
+
]
|
|
66
|
+
.filter(Boolean)
|
|
67
|
+
.join(" | "),
|
|
68
|
+
promptSnippet: `Read MCP resource ${label}.`,
|
|
69
|
+
parameters: { type: "object", properties: {} },
|
|
70
|
+
exposure: "direct",
|
|
71
|
+
namespace: { name: "mcp_combiner", description: "mcp-combiner aggregated tools" },
|
|
72
|
+
execute: async (_toolCallId, _params, _signal, _onUpdate, ctx) => {
|
|
73
|
+
try {
|
|
74
|
+
const result = await opts.connection.readResource(resource.uri)
|
|
75
|
+
let text = renderResourceResult(result)
|
|
76
|
+
if (url) {
|
|
77
|
+
text = `${text}\n\ninteractive: ${url}`
|
|
78
|
+
if (opts.uiAutoOpen !== false && ctx?.hasUI) openInBrowser(url)
|
|
79
|
+
}
|
|
80
|
+
return { content: [{ type: "text", text }], details: { mode: "resource", uri: resource.uri, url: url || undefined } }
|
|
81
|
+
} catch (e) {
|
|
82
|
+
throw new Error(`${toolName}: resource read failed (${e instanceof Error ? e.message : String(e)})`)
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** Register read_* tools for the interactive resources (idempotent, like legacy).
|
|
89
|
+
* Returns the tool names registered this pass. */
|
|
90
|
+
export async function activateNativeResources(
|
|
91
|
+
pi: ExtensionAPI,
|
|
92
|
+
opts: NativeResourceOptions,
|
|
93
|
+
registered: Set<string>,
|
|
94
|
+
): Promise<string[]> {
|
|
95
|
+
let resources: ResourceSummary[] = []
|
|
96
|
+
try {
|
|
97
|
+
resources = filterInteractiveResources(await opts.connection.listResources(true), opts.serverFilter)
|
|
98
|
+
} catch (e) {
|
|
99
|
+
opts.log("warn", `interactive resource discovery failed (${e instanceof Error ? e.message : String(e)})`)
|
|
100
|
+
return []
|
|
101
|
+
}
|
|
102
|
+
const added: string[] = []
|
|
103
|
+
for (const resource of resources) {
|
|
104
|
+
const toolName = `read_${resourceNameToToolName(resource.name ?? resource.uri)}`
|
|
105
|
+
if (registered.has(toolName)) continue
|
|
106
|
+
registered.add(toolName)
|
|
107
|
+
pi.registerTool(toReadTool(toolName, resource, opts))
|
|
108
|
+
added.push(toolName)
|
|
109
|
+
}
|
|
110
|
+
if (added.length) opts.log("info", `native resources: ${added.length} interactive resource tool(s) registered`)
|
|
111
|
+
return added
|
|
112
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
// NATIVE TOOL SURFACE : declares the combiner's tools
|
|
2
|
+
// to Pi FIRST-CLASS, via pi.registerTool with exposure/namespace/annotations — the
|
|
3
|
+
// replacement for client/proxy-tool.ts + client/script.ts + direct-tools.ts promotion.
|
|
4
|
+
//
|
|
5
|
+
// What this buys (the whole point of the native path):
|
|
6
|
+
//
|
|
7
|
+
// - every tool is declared under its own name (`github_search_code`), one hop,
|
|
8
|
+
// no mcp() wrapper and no mcpScript
|
|
9
|
+
// - exposure IS pi's native mechanism: `direct` for the daily drivers (declared to
|
|
10
|
+
// the model verbatim), `codemode` for the rest (callable from codemode scripts,
|
|
11
|
+
// listed only as a namespace — 4%-token cost since pi 1.0), so the combiner's
|
|
12
|
+
// 671 tools stay out of the declaration budget by default
|
|
13
|
+
// - annotations (readOnly/destructive hints) ride along, so pi-permission-system
|
|
14
|
+
// and other tool_call gates classify combiner calls without any adapter glue
|
|
15
|
+
// - tool_search / codemode searchTools() find undeclared ones by BM25, replacing
|
|
16
|
+
// directTools:"search" promotion with pi's own discovery
|
|
17
|
+
//
|
|
18
|
+
// Exposure mapping reuses the existing config vocabulary: the `combiner.directTools`
|
|
19
|
+
// allowlist in the mcp.json ladder means "these are direct" here; everything else is
|
|
20
|
+
// `codemode`. `directTools: "search"` degenerates to plain codemode (pi's tool_search
|
|
21
|
+
// replaces it). Per-project allow/deny keeps flowing combiner-side via applyFilter.
|
|
22
|
+
//
|
|
23
|
+
// Wire shape: register once at session_start (tools list is stable modulo
|
|
24
|
+
// list_changed, which re-registers on the same names). Tools the combiner offers
|
|
25
|
+
// during a session surface via the connection's onToolsChanged hook → activate again.
|
|
26
|
+
|
|
27
|
+
import type { ExtensionAPI, ToolDefinition, ToolParameters } from "../pi.js"
|
|
28
|
+
import type { NativeCombinerConnection, ToolSummary } from "./combiner-connection.js"
|
|
29
|
+
import { applyServerFilter, matchesGlob, RESERVED_TOOL_NAMES } from "../client/tool-matching.js"
|
|
30
|
+
import type { ServerFilter } from "../client/config-ladder.js"
|
|
31
|
+
|
|
32
|
+
function isRecord(v: unknown): v is Record<string, unknown> {
|
|
33
|
+
return typeof v === "object" && v !== null && !Array.isArray(v)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export type NativeToolSurfaceOptions = {
|
|
37
|
+
/** The resolved combiner connection (owns elicitation, filters, caches). */
|
|
38
|
+
connection: NativeCombinerConnection
|
|
39
|
+
/** The per-project server filter (same object index.ts passes to applyFilter);
|
|
40
|
+
* applied defensively here too so a filter race never widens surface exposure. */
|
|
41
|
+
serverFilter?: ServerFilter
|
|
42
|
+
/** The combiner block's directTools spec, as resolved from the ladder: a glob
|
|
43
|
+
* allowlist → "direct" exposure for matches; "search" → everything codemode. */
|
|
44
|
+
directSpec?: string[] | "search"
|
|
45
|
+
/** Warn when the direct-exposure set grows large (every declared schema rides in
|
|
46
|
+
* every request). Settings kill-switch: warnLargeDirectExposure (default true). */
|
|
47
|
+
warnLargeDirectExposure?: boolean
|
|
48
|
+
/** Namespace shown in codemode's grouped listing (pi normalizes -/_ alike). */
|
|
49
|
+
namespaceName?: string
|
|
50
|
+
/** Names that must never be shadowed by a combiner tool (pi builtins + ours). */
|
|
51
|
+
reserved?: readonly string[]
|
|
52
|
+
log: (level: "info" | "warn" | "error", message: string) => void
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Map a CallToolResult content block to pi's text-block contract. pi tool results
|
|
56
|
+
* are arrays of text blocks today; image/other blocks are summarized to text and the
|
|
57
|
+
* full result rides `details` for renderers (same shape the SDK client half uses). */
|
|
58
|
+
function textBlockOf(b: unknown): { type: "text"; text: string } {
|
|
59
|
+
const block = isRecord(b) ? b : undefined
|
|
60
|
+
if (block && block.type === "text") {
|
|
61
|
+
return { type: "text", text: String(block.text) }
|
|
62
|
+
}
|
|
63
|
+
const kind = block?.type ?? (block ? "content" : b)
|
|
64
|
+
return { type: "text", text: `[${String(kind)} block]` }
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function toToolResult(call: { content?: unknown; structuredContent?: unknown }): {
|
|
68
|
+
content: { type: "text"; text: string }[]
|
|
69
|
+
} {
|
|
70
|
+
const blocks = Array.isArray(call.content) ? call.content : []
|
|
71
|
+
return { content: blocks.map((b) => textBlockOf(b)) }
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Register the combiner's tools natively. */
|
|
75
|
+
const DIRECT_EXPOSURE_WARN_THRESHOLD = 50
|
|
76
|
+
|
|
77
|
+
/** Turn one combiner ToolSummary into pi's ToolDefinition, plus the native-only fields
|
|
78
|
+
* pi 1.0 accepts on registerTool (exposure/namespace/annotations). */
|
|
79
|
+
function toNativeTool(sum: ToolSummary, conn: NativeCombinerConnection, exposure: ToolDefinition["exposure"]): ToolDefinition {
|
|
80
|
+
const name = sum.name
|
|
81
|
+
return {
|
|
82
|
+
name,
|
|
83
|
+
label: name,
|
|
84
|
+
description: sum.description ?? name,
|
|
85
|
+
parameters: (sum.inputSchema as ToolParameters) ?? { type: "object", properties: {} },
|
|
86
|
+
// NATIVE-ONLY fields (pi ≥ 1.0 host contract): exposure decides how the tool is
|
|
87
|
+
// declared; namespace groups it in codemode's listing; annotations feed
|
|
88
|
+
// pi-permission-system's classification. Kept optional on ToolDefinition so the
|
|
89
|
+
// extension stays load-compatible with pi 0.99.x where these fields are ignored.
|
|
90
|
+
exposure,
|
|
91
|
+
namespace: { name: "mcp_combiner", description: "mcp-combiner aggregated tools" },
|
|
92
|
+
annotations: sum.annotations,
|
|
93
|
+
execute: async (_toolCallId, params, signal) => {
|
|
94
|
+
// The abort signal rides into the transport; the combiner can cancel
|
|
95
|
+
// upstream work, and widget holds respect it (notifications/cancelled).
|
|
96
|
+
void signal
|
|
97
|
+
try {
|
|
98
|
+
const result = await conn.callTool(name, params)
|
|
99
|
+
return { ...toToolResult(result), details: result }
|
|
100
|
+
} catch (e) {
|
|
101
|
+
// pi's error convention: THROWING marks the call an error (no isError
|
|
102
|
+
// property on successful results).
|
|
103
|
+
throw e instanceof Error ? e : new Error(String(e))
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Register the combiner's tools natively. Returns the names it registered (for
|
|
110
|
+
* status/panel surfaces). Idempotent by name — pi.registerTool replaces by name with
|
|
111
|
+
* a warning, so re-registration on list_changed must pass the same names. */
|
|
112
|
+
export async function activateNativeTools(pi: ExtensionAPI, opts: NativeToolSurfaceOptions): Promise<string[]> {
|
|
113
|
+
const registered = [] as string[]
|
|
114
|
+
const cachedNames: string[] = registered
|
|
115
|
+
const tools = applyServerFilter(await opts.connection.listTools(true), opts.serverFilter)
|
|
116
|
+
const allowlist = Array.isArray(opts.directSpec) ? opts.directSpec : []
|
|
117
|
+
const reserved = new Set<string>([...(opts.reserved ?? []), ...RESERVED_TOOL_NAMES])
|
|
118
|
+
|
|
119
|
+
const directNames: string[] = []
|
|
120
|
+
for (const sum of tools) {
|
|
121
|
+
if (reserved.has(sum.name)) {
|
|
122
|
+
opts.log("warn", `skipping combiner tool "${sum.name}" — collides with a reserved name`)
|
|
123
|
+
continue
|
|
124
|
+
}
|
|
125
|
+
// Allowlist glob match → declared verbatim ("direct"); everything else rides
|
|
126
|
+
// codemode: callable from scripts, discoverable via tool_search/searchTools(),
|
|
127
|
+
// near-zero declaration cost. "search" degrades to the same codemode mapping
|
|
128
|
+
// (pi's tool_search replaces the adapter's search-promote semantics).
|
|
129
|
+
const exposure = allowlist.some((p) => matchesGlob(sum.name, p)) ? "direct" : "codemode"
|
|
130
|
+
if (exposure === "direct") directNames.push(sum.name)
|
|
131
|
+
pi.registerTool(toNativeTool(sum, opts.connection, exposure))
|
|
132
|
+
cachedNames.push(sum.name)
|
|
133
|
+
}
|
|
134
|
+
if (
|
|
135
|
+
directNames.length > DIRECT_EXPOSURE_WARN_THRESHOLD &&
|
|
136
|
+
opts.warnLargeDirectExposure !== false
|
|
137
|
+
) {
|
|
138
|
+
opts.log(
|
|
139
|
+
"warn",
|
|
140
|
+
`native mode: ${directNames.length} tools declared "direct" — every schema rides in every ` +
|
|
141
|
+
`request. Consider trimming the allowlist (directTools) or disabling this warning ` +
|
|
142
|
+
`with "warnLargeDirectExposure": false in extensions/mcp-combiner.json.`,
|
|
143
|
+
)
|
|
144
|
+
}
|
|
145
|
+
opts.log("info", `native tool surface: ${cachedNames.length} tools registered`)
|
|
146
|
+
return cachedNames
|
|
147
|
+
}
|
package/src/pi.ts
CHANGED
|
@@ -118,6 +118,22 @@ export type ToolDefinition = {
|
|
|
118
118
|
theme: RenderTheme,
|
|
119
119
|
context: { [key: string]: unknown },
|
|
120
120
|
) => TuiComponent
|
|
121
|
+
/** NATIVE-exposure fields (pi ≥ 1.0 host contract; optional so 0.99.x hosts ignore
|
|
122
|
+
* them instead of erroring — unknown fields are dropped by the 0.99 loader):
|
|
123
|
+
* exposure — "direct" | "model-only" | "codemode" | "deferred" | "hidden":
|
|
124
|
+
* how the tool is declared to the model (codemode: callable from
|
|
125
|
+
* codemode scripts, listed as a namespace in the codemode tool's
|
|
126
|
+
* description — near-zero token cost as of pi 1.0)
|
|
127
|
+
* namespace — groups the tool in codemode's listing, like an MCP server
|
|
128
|
+
* annotations — MCP tool hints; reach pi.getAllTools() for permission gates */
|
|
129
|
+
exposure?: "direct" | "model-only" | "codemode" | "deferred" | "hidden"
|
|
130
|
+
namespace?: { name: string; description: string }
|
|
131
|
+
annotations?: {
|
|
132
|
+
readOnlyHint?: boolean
|
|
133
|
+
destructiveHint?: boolean
|
|
134
|
+
idempotentHint?: boolean
|
|
135
|
+
openWorldHint?: boolean
|
|
136
|
+
}
|
|
121
137
|
}
|
|
122
138
|
|
|
123
139
|
export type CommandSpec = {
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"connection.d.ts","sourceRoot":"","sources":["../../src/client/connection.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,332 +0,0 @@
|
|
|
1
|
-
// The combiner MCP client: one streamable-HTTP connection, one token, reconnect.
|
|
2
|
-
//
|
|
3
|
-
// This is the piece that makes the extension the adapter: it owns the Client +
|
|
4
|
-
// StreamableHTTPClientTransport pair against the combiner's /mcp endpoint, mints the
|
|
5
|
-
// per-Pi-session grouping token into the URL path (URL form beats headers combiner-
|
|
6
|
-
// side, so the token rides the path), sends the optional inbound bearer, bridges
|
|
7
|
-
// elicitation to Pi's UI, and keeps a tools/list cache fresh via tools/list_changed.
|
|
8
|
-
//
|
|
9
|
-
// Lifecycle: lazy by default (connect on first mcp() call), `eager` connects at
|
|
10
|
-
// session_start. A session reset (new/resume/fork/reload) rebinds the token and
|
|
11
|
-
// drops the client; a transport close or stale-session 404 triggers a one-shot
|
|
12
|
-
// reconnect-and-retry on the next call. The combiner's handover means the same token
|
|
13
|
-
// resumes its parked isolated upstream sessions after a combiner restart.
|
|
14
|
-
import { Client, StreamableHTTPClientTransport } from "@modelcontextprotocol/client";
|
|
15
|
-
import { handleElicitation } from "./elicitation.js";
|
|
16
|
-
const CLIENT_INFO = { name: "pi-mcp-combiner", version: "0.1.0" };
|
|
17
|
-
const CONNECT_TIMEOUT_MS = 8_000;
|
|
18
|
-
function isRecord(v) {
|
|
19
|
-
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
20
|
-
}
|
|
21
|
-
/** Pull {message, requestedSchema} out of an elicitation/create request params,
|
|
22
|
-
* tolerating both form-mode and legacy shapes. Returns undefined when unusable. */
|
|
23
|
-
function toElicitParams(raw) {
|
|
24
|
-
const p = isRecord(raw) ? raw : undefined;
|
|
25
|
-
if (!p)
|
|
26
|
-
return undefined;
|
|
27
|
-
const out = {};
|
|
28
|
-
if (typeof p.message === "string")
|
|
29
|
-
out.message = p.message;
|
|
30
|
-
if (isRecord(p.requestedSchema))
|
|
31
|
-
out.requestedSchema = p.requestedSchema;
|
|
32
|
-
return out;
|
|
33
|
-
}
|
|
34
|
-
/** Build the tokened endpoint URL: <origin>/mcp/<token> (combiner's URL form). */
|
|
35
|
-
export function tokenedUrl(baseUrl, token) {
|
|
36
|
-
try {
|
|
37
|
-
const u = new URL(baseUrl);
|
|
38
|
-
// Normalize to bare /mcp (strip any existing path segments below it), append token.
|
|
39
|
-
const path = u.pathname.replace(/\/mcp(?:\/[^/]*)*\/?$/, "/mcp");
|
|
40
|
-
u.pathname = `${path.replace(/\/$/, "")}/${encodeURIComponent(token)}`;
|
|
41
|
-
return u.toString();
|
|
42
|
-
}
|
|
43
|
-
catch {
|
|
44
|
-
return baseUrl;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
export class CombinerConnection {
|
|
48
|
-
conn;
|
|
49
|
-
log;
|
|
50
|
-
/** Latest dialog-capable UI, refreshed each session_start. */
|
|
51
|
-
elicitUi = { hasUI: false };
|
|
52
|
-
prompts;
|
|
53
|
-
promptsFetchedAt = 0;
|
|
54
|
-
resources;
|
|
55
|
-
resourcesFetchedAt = 0;
|
|
56
|
-
token;
|
|
57
|
-
client;
|
|
58
|
-
connecting;
|
|
59
|
-
tools;
|
|
60
|
-
toolsFetchedAt = 0;
|
|
61
|
-
hooks = {};
|
|
62
|
-
state = "disconnected";
|
|
63
|
-
lastError;
|
|
64
|
-
constructor(conn, log) {
|
|
65
|
-
this.conn = conn;
|
|
66
|
-
this.log = log;
|
|
67
|
-
}
|
|
68
|
-
/** Bind lifecycle hooks (tools changed / state transitions). */
|
|
69
|
-
setHooks(hooks) {
|
|
70
|
-
this.hooks = hooks;
|
|
71
|
-
}
|
|
72
|
-
/** Effective grouping token for this session (explicit URL token wins). */
|
|
73
|
-
get sessionToken() {
|
|
74
|
-
return this.effectiveToken();
|
|
75
|
-
}
|
|
76
|
-
/** Bind the UI slice used by elicitation (called from session_start). */
|
|
77
|
-
bindUi(ui) {
|
|
78
|
-
this.elicitUi = ui;
|
|
79
|
-
}
|
|
80
|
-
/** Swap connection inputs (base URL / bearer env) when a session re-resolves a
|
|
81
|
-
* different combiner URL (e.g. a worktree branch pinning its own combiner).
|
|
82
|
-
* No-op when nothing changed; otherwise drops the client for a fresh connect. */
|
|
83
|
-
rebind(conn) {
|
|
84
|
-
if (conn.baseUrl === this.conn.baseUrl &&
|
|
85
|
-
conn.bearerTokenEnv === this.conn.bearerTokenEnv &&
|
|
86
|
-
conn.urlToken === this.conn.urlToken) {
|
|
87
|
-
return false;
|
|
88
|
-
}
|
|
89
|
-
this.conn = conn;
|
|
90
|
-
void this.reset("connection rebound (session config changed)");
|
|
91
|
-
return true;
|
|
92
|
-
}
|
|
93
|
-
/** Set the grouping token for this session; resets any open connection. */
|
|
94
|
-
setToken(token) {
|
|
95
|
-
if (this.token === token)
|
|
96
|
-
return;
|
|
97
|
-
this.token = token;
|
|
98
|
-
void this.reset("token changed");
|
|
99
|
-
}
|
|
100
|
-
/** Drop the connection + caches (session reset / shutdown). Never throws. */
|
|
101
|
-
async reset(reason) {
|
|
102
|
-
const client = this.client;
|
|
103
|
-
this.client = undefined;
|
|
104
|
-
this.connecting = undefined;
|
|
105
|
-
this.tools = undefined;
|
|
106
|
-
this.state = "disconnected";
|
|
107
|
-
try {
|
|
108
|
-
await client?.close();
|
|
109
|
-
}
|
|
110
|
-
catch {
|
|
111
|
-
// best-effort
|
|
112
|
-
}
|
|
113
|
-
if (reason)
|
|
114
|
-
this.log("info", `connection reset (${reason})`);
|
|
115
|
-
}
|
|
116
|
-
/** Effective token: explicit URL token wins over the minted session token. */
|
|
117
|
-
effectiveToken() {
|
|
118
|
-
return this.conn.urlToken ?? this.token;
|
|
119
|
-
}
|
|
120
|
-
/** Connect (idempotent, single-flight). Throws with a human message on failure. */
|
|
121
|
-
async ensureConnected() {
|
|
122
|
-
if (this.client)
|
|
123
|
-
return this.client;
|
|
124
|
-
if (this.connecting)
|
|
125
|
-
return this.connecting;
|
|
126
|
-
const token = this.effectiveToken();
|
|
127
|
-
if (!token)
|
|
128
|
-
throw new Error("no grouping token set (session not started)");
|
|
129
|
-
const url = tokenedUrl(this.conn.baseUrl, token);
|
|
130
|
-
let endpoint;
|
|
131
|
-
try {
|
|
132
|
-
endpoint = new URL(url);
|
|
133
|
-
}
|
|
134
|
-
catch {
|
|
135
|
-
throw new Error(`combiner URL "${url}" is not parseable; set a valid url (…/mcp) in settings or mcp.json`);
|
|
136
|
-
}
|
|
137
|
-
const bearer = this.conn.bearerTokenEnv ? process.env[this.conn.bearerTokenEnv] : undefined;
|
|
138
|
-
this.state = "connecting";
|
|
139
|
-
this.connecting = (async () => {
|
|
140
|
-
const client = new Client(CLIENT_INFO, { capabilities: { elicitation: {} } });
|
|
141
|
-
const transport = new StreamableHTTPClientTransport(endpoint, {
|
|
142
|
-
authProvider: bearer ? { token: async () => bearer } : undefined,
|
|
143
|
-
});
|
|
144
|
-
// Elicitation bridge — the combiner's permission gate depends on it.
|
|
145
|
-
client.setRequestHandler("elicitation/create", (req) => {
|
|
146
|
-
const params = toElicitParams(isRecord(req) ? req.params : undefined);
|
|
147
|
-
if (!params)
|
|
148
|
-
return { action: "decline" };
|
|
149
|
-
return handleElicitation(params, this.elicitUi);
|
|
150
|
-
});
|
|
151
|
-
client.setNotificationHandler("notifications/tools/list_changed", () => {
|
|
152
|
-
this.tools = undefined; // refetch on next list
|
|
153
|
-
this.prompts = undefined;
|
|
154
|
-
this.resources = undefined;
|
|
155
|
-
try {
|
|
156
|
-
this.hooks.onToolsChanged?.();
|
|
157
|
-
}
|
|
158
|
-
catch {
|
|
159
|
-
// hook errors must never break the notification path
|
|
160
|
-
}
|
|
161
|
-
});
|
|
162
|
-
client.setNotificationHandler("notifications/resources/list_changed", () => {
|
|
163
|
-
this.resources = undefined;
|
|
164
|
-
try {
|
|
165
|
-
this.hooks.onToolsChanged?.();
|
|
166
|
-
}
|
|
167
|
-
catch {
|
|
168
|
-
// hook errors must never break the notification path
|
|
169
|
-
}
|
|
170
|
-
});
|
|
171
|
-
// Stage 2: the widget hold announces its UI URL via a log
|
|
172
|
-
// notification ("Interactive UI ready: <url>") while the agent's
|
|
173
|
-
// call is in flight — open it so the user sees the widget at once.
|
|
174
|
-
client.setNotificationHandler("notifications/message", (n) => {
|
|
175
|
-
const data = n.params?.data;
|
|
176
|
-
if (typeof data !== "string")
|
|
177
|
-
return;
|
|
178
|
-
const m = data.match(/Interactive UI ready: (\S+)/);
|
|
179
|
-
if (m) {
|
|
180
|
-
try {
|
|
181
|
-
this.hooks.onWidgetUrl?.(m[1]);
|
|
182
|
-
}
|
|
183
|
-
catch {
|
|
184
|
-
// hook errors must never break the notification path
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
});
|
|
188
|
-
client.onclose = () => {
|
|
189
|
-
if (this.client === client) {
|
|
190
|
-
this.client = undefined;
|
|
191
|
-
this.state = "disconnected";
|
|
192
|
-
}
|
|
193
|
-
};
|
|
194
|
-
await Promise.race([
|
|
195
|
-
client.connect(transport),
|
|
196
|
-
new Promise((_, reject) => setTimeout(() => reject(new Error("connect timeout")), CONNECT_TIMEOUT_MS)),
|
|
197
|
-
]);
|
|
198
|
-
this.client = client;
|
|
199
|
-
this.state = "connected";
|
|
200
|
-
this.lastError = undefined;
|
|
201
|
-
this.hooks.onStateChange?.(this.state);
|
|
202
|
-
return client;
|
|
203
|
-
})();
|
|
204
|
-
try {
|
|
205
|
-
return await this.connecting;
|
|
206
|
-
}
|
|
207
|
-
catch (e) {
|
|
208
|
-
this.state = "failed";
|
|
209
|
-
this.lastError = e instanceof Error ? e.message : String(e);
|
|
210
|
-
this.log("warn", `combiner connect failed (${this.lastError})`);
|
|
211
|
-
this.hooks.onStateChange?.(this.state);
|
|
212
|
-
throw new Error(`cannot reach the combiner at ${this.conn.baseUrl}: ${this.lastError}`);
|
|
213
|
-
}
|
|
214
|
-
finally {
|
|
215
|
-
this.connecting = undefined;
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
/** tools/list, cached until invalidated (list_changed / reset / reconnect). */
|
|
219
|
-
async listTools(force = false) {
|
|
220
|
-
if (!force && this.tools && Date.now() - this.toolsFetchedAt < 5_000)
|
|
221
|
-
return this.tools;
|
|
222
|
-
const client = await this.ensureConnected();
|
|
223
|
-
const res = await client.listTools();
|
|
224
|
-
this.tools = (res.tools ?? []);
|
|
225
|
-
this.toolsFetchedAt = Date.now();
|
|
226
|
-
return this.tools;
|
|
227
|
-
}
|
|
228
|
-
/** prompts/list, cached alongside tools (combiner namespaces `<server>_<name>`). */
|
|
229
|
-
async listPrompts(force = false) {
|
|
230
|
-
if (!force && this.prompts && Date.now() - this.promptsFetchedAt < 5_000)
|
|
231
|
-
return this.prompts;
|
|
232
|
-
const client = await this.ensureConnected();
|
|
233
|
-
const res = await client.listPrompts();
|
|
234
|
-
this.prompts = (res.prompts ?? []);
|
|
235
|
-
this.promptsFetchedAt = Date.now();
|
|
236
|
-
return this.prompts;
|
|
237
|
-
}
|
|
238
|
-
/** prompts/get through the combiner. */
|
|
239
|
-
async getPrompt(name, args) {
|
|
240
|
-
const client = await this.ensureConnected();
|
|
241
|
-
return client.getPrompt({ name, arguments: args });
|
|
242
|
-
}
|
|
243
|
-
/** resources/list, cached alongside tools/prompts. */
|
|
244
|
-
async listResources(force = false) {
|
|
245
|
-
if (!force && this.resources && Date.now() - this.resourcesFetchedAt < 5_000)
|
|
246
|
-
return this.resources;
|
|
247
|
-
const client = await this.ensureConnected();
|
|
248
|
-
const res = await client.listResources();
|
|
249
|
-
this.resources = (res.resources ?? []);
|
|
250
|
-
this.resourcesFetchedAt = Date.now();
|
|
251
|
-
return this.resources;
|
|
252
|
-
}
|
|
253
|
-
/** resources/read through the combiner. */
|
|
254
|
-
async readResource(uri) {
|
|
255
|
-
const client = await this.ensureConnected();
|
|
256
|
-
return client.readResource({ uri });
|
|
257
|
-
}
|
|
258
|
-
/** callTool with a single stale-session retry: on transport/stale errors we reset
|
|
259
|
-
* and reconnect once (the combiner may have bounced; handover keeps our token). */
|
|
260
|
-
async callTool(name, args) {
|
|
261
|
-
const attempt = async () => {
|
|
262
|
-
const client = await this.ensureConnected();
|
|
263
|
-
return client.callTool({ name, arguments: args ?? {} });
|
|
264
|
-
};
|
|
265
|
-
try {
|
|
266
|
-
return await attempt();
|
|
267
|
-
}
|
|
268
|
-
catch (e) {
|
|
269
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
270
|
-
const stale = /404|stale|session|not found|closed|fetch failed/i.test(msg);
|
|
271
|
-
if (!stale)
|
|
272
|
-
throw e;
|
|
273
|
-
this.log("info", `call "${name}" went stale (${msg}); reconnecting once`);
|
|
274
|
-
await this.reset("stale session");
|
|
275
|
-
return await attempt();
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
/** Control-plane origin for the combiner's REST routes (/health, /sessions/*). */
|
|
279
|
-
controlOrigin() {
|
|
280
|
-
try {
|
|
281
|
-
return new URL(this.conn.baseUrl).origin;
|
|
282
|
-
}
|
|
283
|
-
catch {
|
|
284
|
-
return this.conn.baseUrl;
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
/** The interactive-UI URL for a resource (the combiner's UI host serves the
|
|
288
|
-
* widget for THIS session's token), or empty when no token is set. */
|
|
289
|
-
uiUrlFor(resourceUri) {
|
|
290
|
-
const token = this.effectiveToken();
|
|
291
|
-
if (!token || !resourceUri)
|
|
292
|
-
return "";
|
|
293
|
-
return `${this.controlOrigin()}/ui/${encodeURIComponent(token)}/?resource=${encodeURIComponent(resourceUri)}`;
|
|
294
|
-
}
|
|
295
|
-
/** Headers for control-plane calls: the same inbound bearer as /mcp (the combiner
|
|
296
|
-
* gates its mutating routes — /sessions*, /handover* — with it; /health is open
|
|
297
|
-
* but presenting anyway is harmless). */
|
|
298
|
-
controlHeaders() {
|
|
299
|
-
const token = this.conn.bearerTokenEnv ? process.env[this.conn.bearerTokenEnv] : undefined;
|
|
300
|
-
return token ? { authorization: `Bearer ${token}` } : {};
|
|
301
|
-
}
|
|
302
|
-
/** POST the project-derived server filter for our token. The combiner accepts
|
|
303
|
-
* pending filters pre-connect, so this is safe before the first connect. */
|
|
304
|
-
async applyFilter(filter) {
|
|
305
|
-
const token = this.effectiveToken();
|
|
306
|
-
if (!token)
|
|
307
|
-
throw new Error("no grouping token set (session not started)");
|
|
308
|
-
const body = {};
|
|
309
|
-
if (filter.allow?.length)
|
|
310
|
-
body.allowed_servers = filter.allow;
|
|
311
|
-
else if (filter.deny?.length)
|
|
312
|
-
body.disabled_servers = filter.deny;
|
|
313
|
-
else
|
|
314
|
-
return;
|
|
315
|
-
const res = await fetch(`${this.controlOrigin()}/sessions/token/${encodeURIComponent(token)}/filter`, {
|
|
316
|
-
method: "POST",
|
|
317
|
-
headers: { "content-type": "application/json", ...this.controlHeaders() },
|
|
318
|
-
body: JSON.stringify(body),
|
|
319
|
-
});
|
|
320
|
-
if (!res.ok)
|
|
321
|
-
throw new Error(`filter apply failed: ${res.status} ${await res.text().catch(() => "")}`);
|
|
322
|
-
this.log("info", `session server filter applied (${filter.allow ? "allow" : "deny"}-list)`);
|
|
323
|
-
}
|
|
324
|
-
/** GET /health — the control-plane health snapshot. */
|
|
325
|
-
async health() {
|
|
326
|
-
const res = await fetch(`${this.controlOrigin()}/health`, { headers: this.controlHeaders() });
|
|
327
|
-
if (!res.ok)
|
|
328
|
-
throw new Error(`/health returned ${res.status}`);
|
|
329
|
-
return res.json();
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
//# sourceMappingURL=connection.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"connection.js","sourceRoot":"","sources":["../../src/client/connection.ts"],"names":[],"mappings":"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"}
|