Add an Architecture section: the three-package workspace, core being
private and bundled, and the root-level workspace commands. New sessions
had no map of the repo.
Rewrite the MCP SDK note to warn that the advertised inputSchema must
expose .shape: a .superRefine/.refine (ZodEffects) serializes to an empty
{ properties: {} }. Document the fullSchema/parseSchema split so the
add_connection bug fixed this cycle cannot recur. Fix the stale
test/unit/helpers.ts path to its per-package location.
3.5 KiB
3.5 KiB
Agent rules for dbmole-mcp
Applies to every change in this repo, by any agent or human.
Architecture
- npm workspace monorepo, three packages under
packages/:@dbmole/core— shared Manager/registry/SSH-tunnel/MCP plumbing.private, never published; each leaf bundles it via tsup (noExternal).dbmole-mcp— PostgreSQL + MySQL serveresmole-mcp— Elasticsearch server
- Backends spread
baseConnectionShape, add engine fields +type, call.strict(). - Run workspace commands from the root, they fan out across packages:
npm run typecheck/build/test:unit/test:int/coverage/lint.
Code style
-
4-space indentation
-
No semicolons where possible (
semicolons: asNeeded) -
Single quotes where possible
-
Functions as
const fn = () => {}where possible -
No trailing commas before a closing
},],)(trailingCommas: none) -
Always use braces for
if/else/loop bodies — never single-line bodies likeif (x) return y. Write:if (typeof value === 'bigint') { return value.toString() }(Biome
useBlockStatementsenforces this) -
No template-literal interpolation for building strings: use concatenation —
'[' + engine + '] ' + message, NOT`[${engine}] ${message}`. Plain multi-line template literals WITHOUT${}(e.g. SQL text blocks) are fine. -
Biome enforces all of the above — run
npm run lint:fixbefore every commit -
Line width 100
TypeScript discipline
- No
as unknown as Xdouble-casts anywhere - No
@ts-ignore/@ts-expect-error/anyinsrc/ - In tests prefer typed fakes (see
packages/dbmole-mcp/test/unit/helpers.ts); a singleascast is a last resort, only when there is genuinely no typed way - Types must line up by construction — if they don't, fix the design, not the cast
Testing
- TDD: write the failing test first, watch it fail, implement, watch it pass
- Full coverage: unit tests (mocked IO) + integration tests via testcontainers
- Coverage thresholds ≥90% lines/functions on the unit project — never lower them
npm run test:unit(fast),npm run test:int(needs Docker)
Commits
- Conventional Commits:
<type>(<scope>): <imperative summary> - Subject ≤50 chars (hard cap 72), no trailing period
- Body only when the why is not obvious from the diff; wrap at 72
- NO AI attribution: no
Co-Authored-By: Claude ..., no "Generated with ..." - No emoji
Project constraints
- stdout is the MCP protocol channel — diagnostics go to
console.errorONLY, neverconsole.log - ESM with NodeNext resolution: relative imports need the
.jsextension - MCP SDK v1 (
@modelcontextprotocol/sdk):registerToolaccepts a ZodRawShape (plain object of zod fields) OR a full Zod object (verified on 1.29: AnySchema,.strict()honored at runtime). The advertisedinputSchemaMUST expose.shape— pass a ZodRawShape or a plainz.object/.strict(), NEVER a.superRefine/.refine(ZodEffects has no.shape, so the SDK publishes an empty{ properties: {} }and clients see no fields). Keep cross-field refinements on a separate parse schema:registerConnectionToolstakesfullSchema(strict object, advertised) plus optionalparseSchema(refined, applied in the handler) — see esmoleadd_connection. Use a strictz.objectwhere unknown-key rejection matters; raw shapes are fine elsewhere. Subpath imports (server/mcp.js,server/stdio.js,client/index.js,inMemory.js,types.js) - zod 3 (
import * as z from 'zod')