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
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in new issue
Block a user