diff --git a/packages/esmole-mcp/src/schema.ts b/packages/esmole-mcp/src/schema.ts new file mode 100644 index 0000000..8344ec9 --- /dev/null +++ b/packages/esmole-mcp/src/schema.ts @@ -0,0 +1,29 @@ +import { baseConnectionShape, type ResolvedConnection, sshConfigSchema } from '@dbmole/core' +import * as z from 'zod' + +export const connectionConfigSchema = z + .object({ + ...baseConnectionShape, + user: z.string().min(1).optional(), + type: z.literal('elasticsearch'), + scheme: z.enum(['http', 'https']).default('https'), + verifyTls: z.boolean().default(true), + 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 type ConnectionConfig = z.infer +export type EsResolvedConnection = ResolvedConnection + +export const defaultPort = (): number => 9200 + +export type { SshConfig } from '@dbmole/core' +export { sshConfigSchema } diff --git a/packages/esmole-mcp/test/unit/schema.test.ts b/packages/esmole-mcp/test/unit/schema.test.ts new file mode 100644 index 0000000..cf94295 --- /dev/null +++ b/packages/esmole-mcp/test/unit/schema.test.ts @@ -0,0 +1,67 @@ +import { describe, expect, it } from 'vitest' +import { connectionConfigSchema, defaultPort } from '../../src/schema.js' + +describe('esmole connectionConfigSchema', () => { + it('accepts a basic-auth connection with scheme/verifyTls/readonly defaults', () => { + const c = connectionConfigSchema.parse({ + name: 'es1', + host: 'es.local', + type: 'elasticsearch', + user: 'elastic', + password: 'p' + }) + expect(c.scheme).toBe('https') + expect(c.verifyTls).toBe(true) + expect(c.readonly).toBe(false) + }) + it('accepts an apiKey connection without user/password', () => { + const c = connectionConfigSchema.parse({ + name: 'es2', + host: 'es.local', + type: 'elasticsearch', + apiKey: 'abc==' + }) + expect(c.apiKey).toBe('abc==') + }) + it('rejects a connection with neither user/password nor apiKey', () => { + expect(() => + connectionConfigSchema.parse({ name: 'es3', host: 'es.local', type: 'elasticsearch' }) + ).toThrow() + }) + it('rejects a connection with user but no password and no apiKey', () => { + expect(() => + connectionConfigSchema.parse({ + name: 'es3b', + host: 'es.local', + type: 'elasticsearch', + user: 'elastic' + }) + ).toThrow() + }) + it('rejects unknown keys (strict)', () => { + expect(() => + connectionConfigSchema.parse({ + name: 'es4', + host: 'h', + type: 'elasticsearch', + apiKey: 'x', + bogus: 1 + }) + ).toThrow() + }) + it('allows http scheme + verifyTls false for 7.x dev', () => { + const c = connectionConfigSchema.parse({ + name: 'es5', + host: 'h', + type: 'elasticsearch', + apiKey: 'x', + scheme: 'http', + verifyTls: false + }) + expect(c.scheme).toBe('http') + expect(c.verifyTls).toBe(false) + }) + it('defaultPort is 9200', () => { + expect(defaultPort()).toBe(9200) + }) +})