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
4 changes: 1 addition & 3 deletions packages/mcp-server-supabase/src/server.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -628,9 +628,7 @@ describe('tools', () => {
service: invalidService,
},
});
await expect(getLogsPromise).rejects.toThrow(
`unsupported log service type: invalid-service`
);
await expect(getLogsPromise).rejects.toThrow('Invalid enum value');
});

test('create branch', async () => {
Expand Down
22 changes: 8 additions & 14 deletions packages/mcp-server-supabase/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,16 +178,13 @@ export function createSupabaseMcpServer(options: SupabaseMcpServerOptions) {
),
organization_id: z.string(),
confirm_cost_id: z
.string()
.string({
required_error:
'User must confirm understanding of costs before creating a project.',
})
.describe('The cost confirmation ID. Call `confirm_cost` first.'),
}),
execute: async ({ name, region, organization_id, confirm_cost_id }) => {
if (!confirm_cost_id) {
throw new Error(
'User must confirm understanding of costs before creating a project.'
);
}

const cost = await getNextProjectCost(
managementApiClient,
organization_id
Expand Down Expand Up @@ -513,16 +510,13 @@ export function createSupabaseMcpServer(options: SupabaseMcpServerOptions) {
.default('develop')
.describe('Name of the branch to create'),
confirm_cost_id: z
.string()
.string({
required_error:
'User must confirm understanding of costs before creating a branch.',
})
.describe('The cost confirmation ID. Call `confirm_cost` first.'),
}),
execute: async ({ project_id, name, confirm_cost_id }) => {
if (!confirm_cost_id) {
throw new Error(
'User must confirm understanding of costs before creating a branch.'
);
}

const cost = getBranchCost();
const costHash = await hashObject(cost);
if (costHash !== confirm_cost_id) {
Expand Down
117 changes: 116 additions & 1 deletion packages/mcp-utils/src/server.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,120 @@
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import type { Server } from '@modelcontextprotocol/sdk/server/index.js';
import {
CallToolResultSchema,
type CallToolRequest,
} from '@modelcontextprotocol/sdk/types.js';
import { describe, expect, test } from 'vitest';
import { resource, resources, resourceTemplate } from './server.js';
import { z } from 'zod';
import {
createMcpServer,
resource,
resources,
resourceTemplate,
tool,
} from './server.js';
import { StreamTransport } from './stream-transport.js';

export const MCP_CLIENT_NAME = 'test-client';
export const MCP_CLIENT_VERSION = '0.1.0';

type SetupOptions = {
server: Server;
};

/**
* Sets up an MCP client and server for testing.
*/
async function setup(options: SetupOptions) {
const { server } = options;
const clientTransport = new StreamTransport();
const serverTransport = new StreamTransport();

clientTransport.readable.pipeTo(serverTransport.writable);
serverTransport.readable.pipeTo(clientTransport.writable);

const client = new Client(
{
name: MCP_CLIENT_NAME,
version: MCP_CLIENT_VERSION,
},
{
capabilities: {},
}
);

await server.connect(serverTransport);
await client.connect(clientTransport);

/**
* Calls a tool with the given parameters.
*
* Wrapper around the `client.callTool` method to handle the response and errors.
*/
async function callTool(params: CallToolRequest['params']) {
const output = await client.callTool(params);
const { content } = CallToolResultSchema.parse(output);
const [textContent] = content;

if (!textContent) {
return undefined;
}

if (textContent.type !== 'text') {
throw new Error('tool result content is not text');
}

if (textContent.text === '') {
throw new Error('tool result content is empty');
}

const result = JSON.parse(textContent.text);

if (output.isError) {
throw new Error(result.error.message);
}

return result;
}

return { client, clientTransport, callTool, server, serverTransport };
}

describe('tools', () => {
test('parameter set to default value when omitted by caller', async () => {
const server = createMcpServer({
name: 'test-server',
version: '0.0.0',
tools: {
search: tool({
description: 'Search text',
parameters: z.object({
query: z.string(),
caseSensitive: z.boolean().default(false),
}),
execute: async (args) => {
return args;
},
}),
},
});

const { callTool } = await setup({ server });

// Call the tool without the optional parameter
const result = await callTool({
name: 'search',
arguments: {
query: 'hello',
},
});

expect(result).toEqual({
query: 'hello',
caseSensitive: false,
});
});
});

describe('resources helper', () => {
test('should add scheme to resource URIs', () => {
Expand Down
2 changes: 1 addition & 1 deletion packages/mcp-utils/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -340,7 +340,7 @@ export function createMcpServer(options: McpServerOptions) {
throw new Error('tool not found');
}

const args = request.params.arguments as z.infer<Tool['parameters']>;
const args = tool.parameters.parse(request.params.arguments);

if (!args) {
throw new Error('missing arguments');
Expand Down