Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -547,24 +547,25 @@ test('rolls back only candidate-owned IPC after a registration collision', async
test('closes the claimed Host connection when native capability construction fails', async () => {
const ipc = ipcHarness();
const host = connectionHarness('invalid-capability');
const invalidTool = {
...nativeTool(),
parameters: z.string(),
} as unknown as MakaTool;

// A native-capability construction failure must still tear down the claimed
// Host connection. A tool whose *schema* is unrepresentable no longer fails
// construction — it is skipped and warned per-tool so one bad tool cannot
// drop every Desktop capability (see runtime-host-native-capabilities.test.ts)
// — so trigger a genuine construction error: two tools colliding on one name.
await assert.rejects(
() =>
createDesktopRuntimeHostCandidate(
host.connection,
deps(ipc, {
browserTools: [invalidTool],
browserTools: [nativeTool(), nativeTool()],
resolveBrowserUrl: () => 'https://example.com/',
releaseBrowserSession() {},
computerUseTools: emptyComputerUseTools(),
releaseComputerUseSession() {},
}),
),
/tool schema must be an object/,
/Duplicate Desktop native capability tool/,
);

assert.equal(ipc.size, 0);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import test from 'node:test';
import { buildComputerUseTools, type ComputerUseToolSet } from '@maka/runtime/computer-use-tools';
import { type CuDispatchBackend } from '@maka/runtime/computer-use-types';
import { type MakaTool, type MakaToolContext } from '@maka/runtime/tool-runtime';
import { buildMcpTools, type McpToolProvider } from '@maka/runtime/mcp-tools';
import type { McpToolBinding } from '@maka/core/mcp';
import type { ClientCapabilityProvider } from '@maka/runtime-host/client';
import {
decodeClientCapabilityReplaceInput,
Expand Down Expand Up @@ -133,6 +135,287 @@ test('publishes the real Computer Use schema through the Client Capability proto
assert.equal(Array.isArray(coordinateSchema?.coordinate?.items), true);
});

test('offers and dispatches MCP tools whose parameters are JSON Schema, not Zod', async () => {
let receivedArgs: unknown;
const mcpProvider: McpToolProvider = {
toolSnapshot: () => ({
revision: 1,
tools: [
{
descriptor: {
serverId: 'filesystem',
name: 'read_file',
description: 'Read a file',
inputSchema: {
type: 'object',
properties: { value: { type: 'string' } },
},
},
binding: 'binding-1' as McpToolBinding,
},
],
}),
callTool: async (_binding, args) => {
receivedArgs = args;
return { content: [{ type: 'text', text: JSON.stringify(args) }] };
},
};
// Real production projection: parameters become jsonSchema(...), not Zod.
const mcpTools = buildMcpTools(mcpProvider);
const mcpTool = mcpTools[0];
assert.ok(mcpTool, 'expected buildMcpTools to project one tool');

const provider = createDesktopNativeCapabilityProvider({
browserTools: [],
resolveBrowserUrl: () => 'https://example.com/',
releaseBrowserSession() {},
computerUseTools: computerTools(),
releaseComputerUseSession() {},
additionalGroups: () => [
{
offerId: 'desktop_mcp',
label: 'MCP',
description: 'MCP tools',
tools: mcpTools,
},
],
});

// Offer path: the JSON Schema is advertised verbatim and survives protocol encoding.
const offer = provider.offers().find((entry) => entry.offerId === 'desktop_mcp');
assert.ok(offer, 'expected a desktop_mcp offer');
const inputSchema = offer.tools[0]?.inputSchema;
assert.equal(inputSchema?.type, 'object');
assert.deepEqual(
Object.keys((inputSchema?.properties as object | undefined) ?? {}),
['value'],
);
assert.doesNotThrow(() =>
decodeClientCapabilityReplaceInput({
registrationId: 'registration-1',
offers: provider.offers(),
}),
);

// Call path: args flow through to callTool without a Zod parser, and the MCP
// result is projected back over the protocol.
const result = await call(
provider,
capabilityFrame({
offerId: 'desktop_mcp',
serverId: 'desktop_mcp',
toolName: mcpTool.name,
arguments: { value: 'hi' },
}),
);
assert.deepEqual(receivedArgs, { value: 'hi' });
assert.deepEqual(result, {
content: [{ type: 'text', text: JSON.stringify({ value: 'hi' }) }],
});
});

test('drops one unrepresentable tool instead of failing every Desktop capability', () => {
const mcpProvider: McpToolProvider = {
toolSnapshot: () => ({
revision: 1,
tools: [
{
descriptor: {
serverId: 'filesystem',
name: 'read_file',
description: 'Read a file',
inputSchema: {
type: 'object',
properties: { value: { type: 'string' } },
},
},
binding: 'binding-read' as McpToolBinding,
},
{
descriptor: {
serverId: 'filesystem',
name: 'read_tuple',
// `prefixItems` (a pydantic `tuple[...]` produces it) is outside the
// Client Capability schema allowlist, so this tool cannot be offered.
description: 'Uses a JSON Schema keyword the protocol rejects',
inputSchema: {
type: 'object',
properties: {
pair: {
type: 'array',
prefixItems: [{ type: 'string' }, { type: 'number' }],
},
},
},
},
binding: 'binding-tuple' as McpToolBinding,
},
],
}),
callTool: async () => ({ content: [] }),
};
const mcpTools = buildMcpTools(mcpProvider);
const survivingToolName = mcpTools[0]?.name;
const droppedToolName = mcpTools[1]?.name;
assert.ok(survivingToolName && droppedToolName);

const warnings: string[] = [];
const originalWarn = console.warn;
console.warn = (...args: unknown[]) => {
warnings.push(String(args[0]));
};
let provider: ReturnType<typeof createDesktopNativeCapabilityProvider>;
try {
provider = createDesktopNativeCapabilityProvider({
browserTools: [tool('browser_snapshot', z.object({}), async () => 'ok')],
resolveBrowserUrl: () => 'https://example.com/',
releaseBrowserSession() {},
computerUseTools: computerTools(),
releaseComputerUseSession() {},
additionalGroups: () => [
{ offerId: 'desktop_mcp', label: 'MCP', description: 'MCP tools', tools: mcpTools },
],
});
} finally {
console.warn = originalWarn;
}

// The unrepresentable tool is dropped, yet its server and every other Desktop
// capability still register — the #4591 outage took all of them down at once.
assert.deepEqual(
provider.offers().map((offer) => offer.offerId),
['desktop_browser', 'desktop_mcp'],
);
const mcpOffer = provider.offers().find((offer) => offer.offerId === 'desktop_mcp');
assert.deepEqual(
mcpOffer?.tools.map((descriptor) => descriptor.name),
[survivingToolName],
);
assert.equal(warnings.some((line) => line.includes(droppedToolName)), true);

// The surviving frame still encodes cleanly over the protocol.
assert.doesNotThrow(() =>
decodeClientCapabilityReplaceInput({
registrationId: 'registration-1',
offers: provider.offers(),
}),
);
});

test('does not dispatch a tool that was dropped from its offer', async () => {
const mcpProvider: McpToolProvider = {
toolSnapshot: () => ({
revision: 1,
tools: [
{
descriptor: {
serverId: 'filesystem',
name: 'read_file',
description: 'Read a file',
inputSchema: { type: 'object', properties: { value: { type: 'string' } } },
},
binding: 'binding-ok' as McpToolBinding,
},
{
descriptor: {
serverId: 'filesystem',
name: 'read_tuple',
description: 'Uses a JSON Schema keyword the protocol rejects',
inputSchema: {
type: 'object',
properties: { pair: { type: 'array', prefixItems: [{ type: 'string' }] } },
},
},
binding: 'binding-bad' as McpToolBinding,
},
],
}),
callTool: async () => ({ content: [{ type: 'text', text: 'should not run' }] }),
};
const mcpTools = buildMcpTools(mcpProvider);
const droppedName = mcpTools[1]?.name;
assert.ok(droppedName);

const originalWarn = console.warn;
console.warn = () => {};
let provider: ReturnType<typeof createDesktopNativeCapabilityProvider>;
try {
provider = createDesktopNativeCapabilityProvider({
browserTools: [],
resolveBrowserUrl: () => 'https://example.com/',
releaseBrowserSession() {},
computerUseTools: computerTools(),
releaseComputerUseSession() {},
additionalGroups: () => [
{ offerId: 'desktop_mcp', label: 'MCP', description: 'MCP tools', tools: mcpTools },
],
});
} finally {
console.warn = originalWarn;
}

const mcpOffer = provider.offers().find((offer) => offer.offerId === 'desktop_mcp');
assert.equal(mcpOffer?.tools.some((descriptor) => descriptor.name === droppedName), false);
// A tool that was never advertised must not be dispatchable, even though the
// MakaTool still exists in the source group — bindings track the advertised
// snapshot, not the raw group.
await assert.rejects(
() =>
call(
provider,
capabilityFrame({
offerId: 'desktop_mcp',
serverId: 'desktop_mcp',
toolName: droppedName,
arguments: { pair: ['x'] },
}),
),
/not offered/u,
);
});

test('skips a capability with invalid metadata without blaming its tool schemas', () => {
const warnings: string[] = [];
const originalWarn = console.warn;
console.warn = (...args: unknown[]) => {
warnings.push(String(args[0]));
};
let provider: ReturnType<typeof createDesktopNativeCapabilityProvider>;
try {
provider = createDesktopNativeCapabilityProvider({
browserTools: [tool('browser_snapshot', z.object({}), async () => 'ok')],
resolveBrowserUrl: () => 'https://example.com/',
releaseBrowserSession() {},
computerUseTools: computerTools(),
releaseComputerUseSession() {},
additionalGroups: () => [
// Invalid offerId — a caller misconfiguration, not a tool-schema
// problem. Its one tool has a perfectly valid schema.
{
offerId: 'bad offer id!',
label: 'Bad',
description: 'bad',
tools: [tool('fine_tool', z.object({}), async () => 'ok')],
},
],
});
} finally {
console.warn = originalWarn;
}

// The misconfigured capability is dropped; Browser still registers.
assert.deepEqual(
provider.offers().map((offer) => offer.offerId),
['desktop_browser'],
);
// The diagnostic blames the capability's metadata, not the tool's schema.
assert.equal(
warnings.some((line) => line.includes('bad offer id!') && line.includes('offer metadata')),
true,
);
assert.equal(warnings.some((line) => line.includes('fine_tool')), false);
});

test('publishes every production Desktop-owned tool schema through the protocol', () => {
const settingsTools = buildClientSettingsTools({
async read() {
Expand Down
Loading