pi-mcp-extension 1.3.0 → 1.3.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 +7 -7
- package/package.json +4 -4
- package/src/tool-bridge.ts +78 -11
package/README.md
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
# pi-mcp
|
|
1
|
+
# pi-mcp-extension
|
|
2
2
|
|
|
3
|
-
[](https://www.npmjs.com/package/pi-mcp)
|
|
4
|
-
[](https://github.com/irahardianto/pi-mcp/blob/main/LICENSE)
|
|
3
|
+
[](https://www.npmjs.com/package/pi-mcp-extension)
|
|
4
|
+
[](https://github.com/irahardianto/pi-mcp-extension/blob/main/LICENSE)
|
|
5
5
|
|
|
6
6
|
**Connect [Pi](https://pi.dev) to any MCP server.** Supabase, DeepSource, Playwright, Context7, filesystem, databases — if it speaks [MCP](https://modelcontextprotocol.io), Pi can use it.
|
|
7
7
|
|
|
8
|
-
`pi-mcp` is a production-ready [Model Context Protocol](https://modelcontextprotocol.io/) client extension for the [Pi coding agent](https://pi.dev). It manages server connections, discovers tools, and bridges them into Pi so the LLM can call them directly.
|
|
8
|
+
`pi-mcp-extension` is a production-ready [Model Context Protocol](https://modelcontextprotocol.io/) client extension for the [Pi coding agent](https://pi.dev). It manages server connections, discovers tools, and bridges them into Pi so the LLM can call them directly.
|
|
9
9
|
|
|
10
10
|
## Features
|
|
11
11
|
|
|
@@ -25,20 +25,20 @@
|
|
|
25
25
|
## Installation
|
|
26
26
|
|
|
27
27
|
```bash
|
|
28
|
-
pi install npm:pi-mcp
|
|
28
|
+
pi install npm:pi-mcp-extension
|
|
29
29
|
```
|
|
30
30
|
|
|
31
31
|
Or try without installing:
|
|
32
32
|
|
|
33
33
|
```bash
|
|
34
|
-
pi -e npm:pi-mcp
|
|
34
|
+
pi -e npm:pi-mcp-extension
|
|
35
35
|
```
|
|
36
36
|
|
|
37
37
|
## Quick Start
|
|
38
38
|
|
|
39
39
|
1. Install the extension:
|
|
40
40
|
```bash
|
|
41
|
-
pi install npm:pi-mcp
|
|
41
|
+
pi install npm:pi-mcp-extension
|
|
42
42
|
```
|
|
43
43
|
|
|
44
44
|
2. Create a config file at `~/.pi/agent/mcp.json` (global) or `.pi/mcp.json` (project-level):
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-mcp-extension",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.1",
|
|
4
4
|
"description": "MCP (Model Context Protocol) client extension for the Pi coding agent — connect Pi to any MCP server",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": [
|
|
@@ -16,11 +16,11 @@
|
|
|
16
16
|
"license": "MIT",
|
|
17
17
|
"repository": {
|
|
18
18
|
"type": "git",
|
|
19
|
-
"url": "git+https://github.com/irahardianto/pi-mcp.git"
|
|
19
|
+
"url": "git+https://github.com/irahardianto/pi-mcp-extension.git"
|
|
20
20
|
},
|
|
21
|
-
"homepage": "https://github.com/irahardianto/pi-mcp",
|
|
21
|
+
"homepage": "https://github.com/irahardianto/pi-mcp-extension",
|
|
22
22
|
"bugs": {
|
|
23
|
-
"url": "https://github.com/irahardianto/pi-mcp/issues"
|
|
23
|
+
"url": "https://github.com/irahardianto/pi-mcp-extension/issues"
|
|
24
24
|
},
|
|
25
25
|
"files": [
|
|
26
26
|
"src/",
|
package/src/tool-bridge.ts
CHANGED
|
@@ -30,13 +30,20 @@ export type PiExtensionAPI = Pick<ExtensionAPI, "registerTool" | "getActiveTools
|
|
|
30
30
|
|
|
31
31
|
/**
|
|
32
32
|
* Convert a JSON Schema object to a TypeBox schema.
|
|
33
|
-
* Handles the common subset used by real-world MCP servers
|
|
34
|
-
*
|
|
35
|
-
*
|
|
33
|
+
* Handles the common subset used by real-world MCP servers:
|
|
34
|
+
* - Primitives (string, number, integer, boolean, null)
|
|
35
|
+
* - Arrays, objects (with required/optional/additionalProperties)
|
|
36
|
+
* - Enums (string enums → Union of Literals)
|
|
37
|
+
* - Nullable types ("type": ["string", "null"])
|
|
38
|
+
* - $ref (local #/$defs/ and #/definitions/ references)
|
|
39
|
+
* - oneOf / anyOf → TypeBox Union
|
|
40
|
+
* - allOf → TypeBox Intersect
|
|
41
|
+
* Falls back to Type.Any() for unresolvable $ref or missing type.
|
|
36
42
|
*/
|
|
37
43
|
export function convertJsonSchemaToTypebox(
|
|
38
44
|
schema: unknown,
|
|
39
45
|
depth = 0,
|
|
46
|
+
defs?: Record<string, unknown>,
|
|
40
47
|
): TSchema {
|
|
41
48
|
// Guard against infinite recursion and malformed schemas
|
|
42
49
|
if (!schema || typeof schema !== "object" || Array.isArray(schema) || depth > 10) {
|
|
@@ -47,6 +54,71 @@ export function convertJsonSchemaToTypebox(
|
|
|
47
54
|
const description = typeof s["description"] === "string" ? s["description"] : undefined;
|
|
48
55
|
const opts = description ? { description } : {};
|
|
49
56
|
|
|
57
|
+
// Extract $defs / definitions for $ref resolution (carried through recursive calls)
|
|
58
|
+
const resolvedDefs: Record<string, unknown> = {
|
|
59
|
+
...((s["$defs"] ?? s["definitions"]) as Record<string, unknown> | undefined),
|
|
60
|
+
...defs,
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
// ── Handle $ref ──────────────────────────────────────────────────────────
|
|
64
|
+
if (typeof s["$ref"] === "string") {
|
|
65
|
+
const ref = s["$ref"] as string;
|
|
66
|
+
let resolved: unknown;
|
|
67
|
+
|
|
68
|
+
// Local references: #/$defs/Foo, #/definitions/Foo
|
|
69
|
+
if (ref.startsWith("#/")) {
|
|
70
|
+
const parts = ref.slice(2).split("/");
|
|
71
|
+
if (parts[0] === "$defs" || parts[0] === "definitions") {
|
|
72
|
+
const key = parts.slice(1).join("/");
|
|
73
|
+
resolved = resolvedDefs[key];
|
|
74
|
+
} else {
|
|
75
|
+
// Fallback: try walking the defs map by the last part
|
|
76
|
+
const key = parts[parts.length - 1]!;
|
|
77
|
+
resolved = resolvedDefs[key];
|
|
78
|
+
}
|
|
79
|
+
} else {
|
|
80
|
+
// External $ref — cannot resolve, fall back
|
|
81
|
+
console.warn(
|
|
82
|
+
`[pi-mcp] Cannot resolve external $ref "${ref}", using Type.Any()`,
|
|
83
|
+
);
|
|
84
|
+
return Type.Any(opts);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (!resolved) {
|
|
88
|
+
console.warn(
|
|
89
|
+
`[pi-mcp] Could not resolve $ref "${ref}", using Type.Any()`,
|
|
90
|
+
);
|
|
91
|
+
return Type.Any(opts);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Merge description from referencing schema into resolved schema
|
|
95
|
+
const merged = { ...(resolved as Record<string, unknown>) };
|
|
96
|
+
if (description && !merged["description"]) {
|
|
97
|
+
merged["description"] = description;
|
|
98
|
+
}
|
|
99
|
+
return convertJsonSchemaToTypebox(merged, depth + 1, resolvedDefs);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// ── Handle oneOf / anyOf → TypeBox Union ─────────────────────────────────
|
|
103
|
+
if (Array.isArray(s["oneOf"])) {
|
|
104
|
+
const members = (s["oneOf"] as unknown[])
|
|
105
|
+
.map((sub) => convertJsonSchemaToTypebox(sub, depth + 1, resolvedDefs));
|
|
106
|
+
return members.length === 1 ? members[0]! : Type.Union(members, opts);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (Array.isArray(s["anyOf"])) {
|
|
110
|
+
const members = (s["anyOf"] as unknown[])
|
|
111
|
+
.map((sub) => convertJsonSchemaToTypebox(sub, depth + 1, resolvedDefs));
|
|
112
|
+
return members.length === 1 ? members[0]! : Type.Union(members, opts);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// ── Handle allOf → TypeBox Intersect ─────────────────────────────────────
|
|
116
|
+
if (Array.isArray(s["allOf"])) {
|
|
117
|
+
const members = (s["allOf"] as unknown[])
|
|
118
|
+
.map((sub) => convertJsonSchemaToTypebox(sub, depth + 1, resolvedDefs));
|
|
119
|
+
return members.length === 1 ? members[0]! : Type.Intersect(members, opts);
|
|
120
|
+
}
|
|
121
|
+
|
|
50
122
|
// Handle nullable types: { "type": ["string", "null"] }
|
|
51
123
|
const rawType = s["type"];
|
|
52
124
|
const type = Array.isArray(rawType)
|
|
@@ -84,7 +156,7 @@ export function convertJsonSchemaToTypebox(
|
|
|
84
156
|
case "array": {
|
|
85
157
|
const items = s["items"];
|
|
86
158
|
base = Type.Array(
|
|
87
|
-
items ? convertJsonSchemaToTypebox(items, depth + 1) : Type.Unknown(),
|
|
159
|
+
items ? convertJsonSchemaToTypebox(items, depth + 1, resolvedDefs) : Type.Unknown(),
|
|
88
160
|
opts,
|
|
89
161
|
);
|
|
90
162
|
break;
|
|
@@ -104,7 +176,7 @@ export function convertJsonSchemaToTypebox(
|
|
|
104
176
|
|
|
105
177
|
const props: Record<string, TSchema> = {};
|
|
106
178
|
for (const [key, value] of Object.entries(properties)) {
|
|
107
|
-
const converted = convertJsonSchemaToTypebox(value, depth + 1);
|
|
179
|
+
const converted = convertJsonSchemaToTypebox(value, depth + 1, resolvedDefs);
|
|
108
180
|
props[key] = required.has(key) ? converted : Type.Optional(converted);
|
|
109
181
|
}
|
|
110
182
|
|
|
@@ -117,12 +189,7 @@ export function convertJsonSchemaToTypebox(
|
|
|
117
189
|
break;
|
|
118
190
|
}
|
|
119
191
|
default: {
|
|
120
|
-
//
|
|
121
|
-
if (s["oneOf"] || s["anyOf"] || s["allOf"] || s["$ref"]) {
|
|
122
|
-
console.error(
|
|
123
|
-
`[pi-mcp] Unsupported JSON Schema construct (oneOf/anyOf/allOf/$ref), using Type.Any(). Schema: ${JSON.stringify(s).slice(0, 200)}`,
|
|
124
|
-
);
|
|
125
|
-
}
|
|
192
|
+
// Truly unsupported or missing type field
|
|
126
193
|
base = Type.Any(opts);
|
|
127
194
|
break;
|
|
128
195
|
}
|