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

+13 -2
View File
@@ -10,7 +10,17 @@ import { errorMessage, fail, ok } from './respond.js'
export type ConnectionToolsDeps<TConfig extends { name: string }, TBackend extends Backend> = {
registry: Registry<TConfig>
manager: Manager<TConfig, TBackend>
fullSchema: z.ZodType<TConfig, z.ZodTypeDef, unknown>
// Advertised as add_connection's inputSchema AND used to parse the call.
// Typed as a `ZodObject` (NOT a bare `ZodType`) on purpose: the SDK can only
// emit JSON Schema properties for a schema that exposes `.shape`. A
// `.superRefine`/`ZodEffects` wrapper has no `.shape`, so it would publish an
// empty `{ properties: {} }` — the type now rejects that at compile time.
// Put cross-field refinements on `parseSchema`, not here.
fullSchema: z.ZodObject<z.ZodRawShape, z.UnknownKeysParam, z.ZodTypeAny, TConfig, unknown>
// Optional stricter validator (e.g. cross-field `.superRefine`) applied in the
// handler after the wire-level parse. Keeps refinements that can't live on the
// advertised object schema without breaking its serialization.
parseSchema?: z.ZodType<TConfig, z.ZodTypeDef, unknown>
patchSchema: z.ZodTypeAny
publicView: (resolved: ResolvedConnection<TConfig>) => unknown
ping: (managed: ManagedConnection<TConfig, TBackend>) => Promise<Record<string, unknown>>
@@ -38,7 +48,8 @@ export const registerConnectionTools = <TConfig extends { name: string }, TBacke
},
async (config) => {
try {
return ok({ added: deps.publicView(registry.add(deps.fullSchema.parse(config))) })
const parsed = (deps.parseSchema ?? deps.fullSchema).parse(config)
return ok({ added: deps.publicView(registry.add(parsed)) })
} catch (error) {
return fail(errorMessage(error))
}