From 82d2392e31efef7f2ef3b3a99a149161964ccfb2 Mon Sep 17 00:00:00 2001 From: Evgeny Gamov Date: Tue, 16 Jun 2026 23:49:18 +0500 Subject: [PATCH] test(core): cover the parseSchema contract Assert add_connection advertises fullSchema as its inputSchema (never the refined schema) and applies an optional parseSchema in the handler: a refinement rejection surfaces as an error and skips registry.add, while a passing config persists. Locks the wire/parse split at the core level. --- .../test/unit/mcp/connectionTools.test.ts | 36 +++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/packages/core/test/unit/mcp/connectionTools.test.ts b/packages/core/test/unit/mcp/connectionTools.test.ts index df9ead0..e43b288 100644 --- a/packages/core/test/unit/mcp/connectionTools.test.ts +++ b/packages/core/test/unit/mcp/connectionTools.test.ts @@ -98,7 +98,7 @@ describe('registerConnectionTools', () => { let registry: FakeRegistry let manager: FakeManager let handlers: Map - let configs: Map + let configs: Map const invoke = (name: string, args: Record = {}): Promise => { const handler = handlers.get(name) @@ -113,7 +113,11 @@ describe('registerConnectionTools', () => { handlers = new Map() configs = new Map() vi.spyOn(server, 'registerTool').mockImplementation( - (name: string, config: { description?: string }, handler: unknown) => { + ( + name: string, + config: { description?: string; inputSchema?: unknown }, + handler: unknown + ) => { configs.set(name, config) handlers.set(name, handler as Handler) return { @@ -212,6 +216,34 @@ describe('registerConnectionTools', () => { expect(text(result)).toContain('already exists') }) + it('add_connection advertises fullSchema and applies parseSchema in the handler', async () => { + const parseSchema = schema.superRefine((c, ctx) => { + if (c.host === 'bad') { + ctx.addIssue({ code: z.ZodIssueCode.custom, message: 'bad host' }) + } + }) + registry.add.mockImplementation((config: TestConfig) => resolved(config)) + register({ parseSchema }) + + // The advertised wire schema is always fullSchema, never the refined one + // (a ZodEffects has no .shape and would publish an empty inputSchema). + expect(configs.get('add_connection')?.inputSchema).toBe(schema) + + const rejected = await invoke('add_connection', { name: 'x', type: 'mysql', host: 'bad' }) + expect(rejected.isError).toBe(true) + expect(text(rejected)).toContain('bad host') + expect(registry.add).not.toHaveBeenCalled() + + const accepted = await invoke('add_connection', { name: 'y', type: 'mysql', host: 'good' }) + expect(accepted.isError).toBeFalsy() + expect(registry.add).toHaveBeenCalledWith({ + name: 'y', + type: 'mysql', + host: 'good', + readonly: false + }) + }) + it('update_connection patches, invalidates the cache and returns the public view', async () => { registry.update.mockReturnValue( resolved({ name: 'c', type: 'postgres', host: 'new', readonly: false })