Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion packages/provider-pi/src/server/mcpBridge.testkit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export async function loadMcpBridge(
readonly toolSearchDisabled?: boolean;
readonly allowsTool?: (name: string) => boolean;
readonly runtimeMode?: string;
readonly inputSchema?: Record<string, unknown>;
} = {},
) {
const handlers = new Map<string, AgentStartHook>();
Expand Down Expand Up @@ -72,7 +73,7 @@ export async function loadMcpBridge(
{ name: "preview_snapshot", description: "Inspect the collaborative browser." },
].map((tool) => ({
...tool,
inputSchema: { type: "object", properties: {} },
inputSchema: options.inputSchema ?? { type: "object", properties: {} },
outputSchema: { type: "object", properties: { count: { type: "number" } } },
}));
const source = NodeModule.stripTypeScriptTypes(
Expand Down
26 changes: 26 additions & 0 deletions packages/provider-pi/src/server/mcpExtensionSource.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,32 @@ describe("Pi MCP tool exposure", () => {
);
});

describe("Pi MCP tool schemas", () => {
it.each([{ type: "object" }, {}])(
"declares empty properties on object schemas that omit them: %j",
async (inputSchema) => {
const bridge = await loadMcpBridge({ inputSchema });
assert.isAbove(bridge.tools.length, 0);
for (const tool of bridge.tools) {
assert.deepEqual(tool.parameters, { type: "object", properties: {} });
}
},
);

it("keeps declared properties and other schema keywords", async () => {
const inputSchema = {
type: "object",
properties: { depth: { type: "number" } },
required: ["depth"],
additionalProperties: false,
};
const bridge = await loadMcpBridge({ inputSchema });
for (const tool of bridge.tools) {
assert.deepEqual(tool.parameters, inputSchema);
}
});
});

describe("Pi tool discovery permissions", () => {
it.each(["approval-required", "auto-accept-edits", "auto"])(
"allows annotated T3 reads in %s while gating mutations and replacements",
Expand Down
8 changes: 7 additions & 1 deletion packages/provider-pi/src/server/mcpExtensionSource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,13 @@ function parseSseOrJson(body: string, contentType: string): JsonRpcResponse {
function jsonSchemaToTypebox(schema: Record<string, unknown> | undefined) {
const unsafe = (Type as { Unsafe?: (value: unknown) => unknown }).Unsafe;
if (typeof unsafe === "function" && schema !== undefined) {
return unsafe(schema);
// Zero-argument MCP tools may omit "properties", which some OpenAI-compatible
// servers reject. Declare it, as Pi does for its own MCP tools.
return unsafe({
...schema,
type: schema.type ?? "object",
...(schema.properties === undefined ? { properties: {} } : {}),
});
}
return Type.Object({}, { additionalProperties: true });
}
Expand Down
Loading