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:
admin committed 2026-06-16 23:15:08 +05:00
1 parent a34d759598
commit 9bbd179a40
4 files changed
+94 -12

No files matched your search

+15 -9
View File
@@ -1,7 +1,12 @@
import { baseConnectionShape, type ResolvedConnection, sshConfigSchema } from '@dbmole/core'
import * as z from 'zod'
export const connectionConfigSchema = z
// Plain strict object: kept separate from the refined schema below so the MCP
// SDK can serialize it to a JSON Schema with real properties. A `.superRefine`
// wrapper is a ZodEffects with no `.shape`, which the SDK can't introspect and
// would publish as an empty `{ properties: {} }`. This is add_connection's wire
// schema; the cross-field check lives on connectionConfigSchema.
export const connectionObjectSchema = z
.object({
...baseConnectionShape,
user: z.string().min(1).optional(),
@@ -11,14 +16,15 @@ export const connectionConfigSchema = z
apiKey: z.string().min(1).optional()
})
.strict()
.superRefine((c, ctx) => {
if (c.apiKey === undefined && !(c.user !== undefined && c.password !== undefined)) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: 'provide either apiKey or both user and password'
})
}
})
export const connectionConfigSchema = connectionObjectSchema.superRefine((c, ctx) => {
if (c.apiKey === undefined && !(c.user !== undefined && c.password !== undefined)) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: 'provide either apiKey or both user and password'
})
}
})
export type ConnectionConfig = z.infer<typeof connectionConfigSchema>
export type EsResolvedConnection = ResolvedConnection<ConnectionConfig>
+3 -1
View File
@@ -9,6 +9,7 @@ import type { EsRegistry } from './registry.js'
import {
type ConnectionConfig,
connectionConfigSchema,
connectionObjectSchema,
defaultPort,
type EsResolvedConnection,
sshConfigSchema
@@ -53,7 +54,8 @@ export const createServer = (registry: EsRegistry, manager: EsManager): McpServe
registerConnectionTools<ConnectionConfig, EsBackend>(server, {
registry,
manager,
fullSchema: connectionConfigSchema,
fullSchema: connectionObjectSchema,
parseSchema: connectionConfigSchema,
patchSchema,
publicView,
formatError: esFormatError,