fix(esmole): publish typed add_connection inputSchema
add_connection advertised an empty `{ properties: {} }` while the server
validated strictly, so clients could not see field types or the required
`type` literal and sent port/verifyTls as strings.
Cause: the connection schema was wrapped in `.superRefine`, producing a
ZodEffects with no `.shape`. The MCP SDK's normalizeObjectSchema returns
undefined for it and falls back to EMPTY_OBJECT_JSON_SCHEMA. update_connection
passed a raw shape, so it serialized fine - hence the asymmetry.
Split the schema: a strict z.object (connectionObjectSchema) is advertised as
the wire schema, and the refined connectionConfigSchema is passed as a new
optional parseSchema applied in the handler. Strict unknown-key rejection and
the apiKey-or-user+password check are both preserved. fullSchema is now typed
as a ZodObject so a ZodEffects can no longer be passed by mistake.
This commit is contained in:
1 parent
a34d759598
commit
9bbd179a40
4 files changed
+94
-12
No files matched your search
@@ -53,6 +53,69 @@ describe('createServer', () => {
|
||||
await client.close()
|
||||
})
|
||||
|
||||
it('publishes a typed inputSchema for add_connection, not an empty object', async () => {
|
||||
const registry = createRegistry({ storePath: join(dir, 'connections.json'), env: {} })
|
||||
const server = createServer(registry, createManager(registry))
|
||||
const client = await connectClient(server)
|
||||
const { tools } = await client.listTools()
|
||||
const add = tools.find((tool) => tool.name === 'add_connection')
|
||||
const schema = add?.inputSchema
|
||||
expect(Object.keys(schema?.properties ?? {})).toEqual(
|
||||
expect.arrayContaining([
|
||||
'name',
|
||||
'host',
|
||||
'port',
|
||||
'user',
|
||||
'password',
|
||||
'type',
|
||||
'scheme',
|
||||
'verifyTls',
|
||||
'apiKey'
|
||||
])
|
||||
)
|
||||
expect(schema?.required ?? []).toEqual(expect.arrayContaining(['name', 'host', 'type']))
|
||||
expect(schema?.additionalProperties).toBe(false)
|
||||
await client.close()
|
||||
})
|
||||
|
||||
it('add_connection still enforces the apiKey-or-user+password cross-field check', async () => {
|
||||
const registry = createRegistry({ storePath: join(dir, 'connections.json'), env: {} })
|
||||
const server = createServer(registry, createManager(registry))
|
||||
const client = await connectClient(server)
|
||||
const result = await client.callTool({
|
||||
name: 'add_connection',
|
||||
arguments: { name: 'es-noauth', host: 'es.local', type: 'elasticsearch' }
|
||||
})
|
||||
const content = result.content as Array<{ type: string; text: string }>
|
||||
expect(result.isError).toBe(true)
|
||||
expect(content[0].text).toContain('provide either apiKey or both user and password')
|
||||
await client.close()
|
||||
})
|
||||
|
||||
it('add_connection rejects unknown keys at the SDK boundary, not in the handler', async () => {
|
||||
const registry = createRegistry({ storePath: join(dir, 'connections.json'), env: {} })
|
||||
const server = createServer(registry, createManager(registry))
|
||||
const client = await connectClient(server)
|
||||
const result = await client.callTool({
|
||||
name: 'add_connection',
|
||||
arguments: {
|
||||
name: 'es-bogus',
|
||||
host: 'es.local',
|
||||
type: 'elasticsearch',
|
||||
apiKey: 'x',
|
||||
bogus: 1
|
||||
}
|
||||
})
|
||||
const content = result.content as Array<{ type: string; text: string }>
|
||||
expect(result.isError).toBe(true)
|
||||
// The SDK-level "Invalid arguments for tool" prefix proves the strict wire
|
||||
// schema rejected the unknown key before the handler ran, rather than the
|
||||
// key being silently stripped or caught only by the handler's parseSchema.
|
||||
expect(content[0].text).toContain('Invalid arguments for tool add_connection')
|
||||
expect(content[0].text).toContain('Unrecognized key')
|
||||
await client.close()
|
||||
})
|
||||
|
||||
it('list_connections renders the public view of a stored connection', async () => {
|
||||
const registry = createRegistry({ storePath: join(dir, 'connections.json'), env: {} })
|
||||
registry.add({
|
||||
|
||||
Reference in new issue
Block a user