You are working on an MCP (Model Context Protocol) server that connects TestDino to AI agents (Cursor, Claude Code, VS Code, etc.). This is an npm package published as testdino-mcp. Users install it and configure it in their AI tool to interact with TestDino's test data via natural language.
Read this file fully before doing anything. Follow it without deviation.
- What: MCP server (stdio transport, not HTTP)
- Language: TypeScript (strict mode, ES modules)
- Runtime: Node.js >= 20
- Published to: npm (
testdino-mcp) - API: Talks to
https://api.testdino.comusing Bearer token auth (PAT) - Users: Developers and QA engineers using AI coding tools
- Read this file completely
- Understand the tool you're modifying — read its source file and the corresponding section in
docs/TOOLS.md - Check existing patterns in
src/tools/— follow them exactly, don't invent new conventions - If adding a new tool, use
src/tools/testruns/list-testruns.tsas the reference implementation
src/
├── index.ts ← Server entry point (registers tools + resources)
├── lib/
│ ├── env.ts ← API URL + PAT resolution
│ ├── endpoints.ts ← All API endpoint URL builders (centralized)
│ ├── request.ts ← HTTP fetch wrapper (apiRequest / apiRequestJson)
│ └── file-utils.ts ← Local file reading, base64 encoding, step validation
└── tools/
├── health.ts ← PAT validation + account info
├── index.ts ← Barrel export for all tools
├── testruns/ ← list-testruns, get-run-details
├── testcases/ ← list-testcase, get-testcase-details, debug-testcase
├── manual-testcases/ ← CRUD for manual test cases
└── manual-testsuites/← list + create test suites
Data flow for every tool call:
AI agent calls tool → index.ts routes by name → handler validates args
→ getApiKey() resolves PAT → endpoints.X() builds URL → apiRequestJson() fetches
→ handler formats response → returns { content: [{ type: "text", text: ... }] }
Every tool file exports exactly two things:
- Tool definition —
export const xyzTool = { name, description, inputSchema } - Handler function —
export async function handleXyz(args) { ... }
name: snake_case (e.g.,list_testruns,get_run_details)description: Write for AI agents, not humans. Be specific about what the tool returns and when to use it. Include usage hintsinputSchema: JSON Schema format. Every property MUST have adescription. Useenumfor known values. Markrequiredfields accurately- Descriptions are the AI's only guide — vague descriptions = wrong tool usage
- Auth first: Always call
getApiKey(args)and throw if missing - Validate required params: Check presence, throw with specific message
- Build URL via endpoints.ts: Never construct URLs manually in handlers
- Type conversions: Use
String()for string params,Number()for numeric. Be consistent - Response format: Return
{ content: [{ type: "text", text: JSON.stringify(response, null, 2) }] } - Error handling: Wrap in try/catch, prefix errors with context:
throw new Error("Failed to [action]: [detail]")
- Add endpoint URL builder in
src/lib/endpoints.ts - Create tool file in the appropriate
src/tools/<category>/directory - Export from
src/tools/index.ts - Register in
src/index.ts(add to tools array + add routing if-block) - Update
docs/TOOLS.mdwith full documentation - Update
docs/skill.mdif it affects AI agent workflows - Run full verify:
npm run typecheck && npm run lint && npm run test
- Strict mode enabled — no
anyunless absolutely necessary (useRecord<string, unknown>) - All imports use
.jsextensions (ES module requirement) - Use named exports only — no default exports
- Keep handler functions focused: auth → validate → fetch → format → return
- Tool files:
kebab-case.ts(e.g.,list-testruns.ts) - Tool names (MCP):
snake_case(e.g.,list_testruns) - Tool definition exports:
camelCaseTool(e.g.,listTestRunsTool) - Handler exports:
handleCamelCase(e.g.,handleListTestRuns) - Interfaces/types:
PascalCase(e.g.,ListTestRunsArgs)
- Missing PAT:
"Missing TESTDINO_PAT environment variable. Please configure it in your .cursor/mcp.json file under the 'env' section." - Missing required param:
"[paramName] is required" - API failure:
"Failed to [action]: [error detail]" - Keep messages actionable — tell the user what to do, not just what went wrong
- Don't construct API URLs directly in tool handlers — always use
endpoints.ts - Don't add new dependencies without strong justification
- Don't use
console.log— MCP uses stdio. Useconsole.errororlogError/logInfofor debug output - Don't change the response shape (
{ content: [{ type: "text", text }] }) — it's the MCP protocol - Don't duplicate utility code — if
file-utils.tshas what you need, import it
Version appears in THREE places and must stay in sync:
package.json→versionserver.json→version(andpackages[0].version)src/index.ts→ server constructorversion
When bumping version, update all three. Use /bump-version command.
npm run build # TypeScript compilation (tsc)
npm run dev # Run from source (tsx src/index.ts)
npm run typecheck # Type check without emitting (tsc --noEmit)
npm run lint # ESLint
npm run lint:fix # ESLint with auto-fix
npm run format # Prettier write
npm run format:check # Prettier check
npm run test # Jest (ESM mode)
npm run test:coverage # Jest with coverage reportQuality gate (run before every commit):
npm run typecheck && npm run lint && npm run testFull gate (before publish/PR):
npm run format && npm run typecheck && npm run lint && npm run test && npm run build- Framework: Vitest with v8 coverage
- Config:
vitest.config.ts - Test pattern:
*.test.ts - Mock
fetchviatests/helpers/mockFetch.ts— never hit the real API in tests - Shared test factories in
tests/helpers/mockTypes.ts
tests/
├── setup.ts ← Global setup (env cleanup between tests)
├── helpers/ ← Mock factories, shared types
├── unit/ ← Unit tests (mirror src/ structure)
│ ├── lib/ ← env, endpoints, request, file-utils
│ └── tools/ ← All 12 tool handlers
└── integration/ ← End-to-end MCP server tests
Unit tests in tests/unit/ (mirror src/), integration in tests/integration/, helpers in tests/helpers/.
| Metric | Minimum |
|---|---|
| Lines | 80% |
| Functions | 80% |
| Branches | 60% |
| Statements | 80% |
- Branching logic — every if/else, switch case, ternary path
- Boundary conditions — empty arrays, undefined params, max limits, zero values
- Async coordination — concurrent calls, promise races, init ordering
- Non-trivial data transforms — query string building, response formatting, file encoding
- Error recovery paths — not just error detection, verify actual recovery behavior
- Validation logic — required params, enum values, type coercion edge cases
- Every bug fix must include a regression test that would have caught the bug
For tool handlers specifically:
- Missing PAT → correct error message
- Missing required params → specific error (not generic TypeError)
- API returns error → properly wrapped with context
- Params correctly forwarded to endpoint builder and API call
expect(new Foo()).toBeDefined()— constructor breaks → all tests break anywayexpect(typeof obj.method).toBe('function')— TypeScript enforces this at compile timeexpect(error.stack).toBeDefined()— built-in JS Error behaviorexpect(Enum.VALUE).toBe('value')— asserts source code equals source coderesolves.not.toThrow()with no state verification — proves nothing- Duplicate invocations — same assertions called twice add noise, not coverage
- Code paths already exercised by another test — don't re-test what's covered
- Tests that only check the happy path when the fix is about failure recovery
- Runtime
readonlychecks — TS readonly is compile-time only
Three docs files serve different purposes:
| File | Audience | Purpose |
|---|---|---|
docs/TOOLS.md |
Human users | Comprehensive tool reference with examples |
docs/skill.md |
AI agents | Patterns, workflows, decision trees for tool usage |
docs/INSTALLATION.md |
Human users | Setup instructions for different AI tools |
When modifying tools, update docs/TOOLS.md. If the change affects how an AI agent should choose or use tools, also update docs/skill.md.
All bugs, regressions, and technical debt are tracked in ISSUES.md at the project root.
Format: Each issue has an ISS-NNN ID with: severity, status, symptoms, root cause (file:line), fix description, files changed, tests added.
Status flow: FOUND → IN PROGRESS → FIXED (with resolution summary and date)
Severity levels: CRITICAL | HIGH | IMPORTANT | MEDIUM | LOW
When to write issues:
/reviewskill finds Critical/Important bugs → logs them toISSUES.md/fixskill starts from anISSUES.mdentry and updates it on completion/retroskill checks if bugs were found during the task and logs new ones- Any time a bug is discovered during development → add it before you forget
Rules:
- Every issue must have a specific file:line reference
- Every issue must have a concrete fix suggestion
- Fixes without
ISSUES.mdupdates are incomplete — tracking is not optional - Generalizable patterns get promoted to the Known Patterns & Pitfalls table in this file
This is a public npm package. Before publishing:
- Bump version in all 3 locations (use
/bump-version) - Run full quality gate
- Run
npm run buildto regeneratedist/ - Commit with message:
chore: Bump version to X.Y.Z npm publish- Create GitHub release tag
| Pattern | Rule |
|---|---|
| stdio transport | MCP servers communicate via stdin/stdout. console.log will corrupt the protocol. Always use console.error for debug output |
| PAT resolution | getApiKey() checks args.token first, then process.env.TESTDINO_PAT. Both paths must work |
| Endpoint centralization | All API URLs live in endpoints.ts. Tool handlers never build URLs. This makes API changes a single-file edit |
| Query param filtering | buildQueryString() in endpoints.ts skips undefined/null values automatically |
| File attachments | Only manual test case tools use file attachments. file-utils.ts handles base64 encoding. Max 10MB per file |
| Response wrapping | The TestDino API sometimes wraps responses in { success, data }. Health tool handles this — check if new endpoints do too |
| Boolean params | Some API params expect string "true"/"false", not boolean. Use String() when passing to query params |
| Version sync | Version in 3 places. Forgetting one causes confusion. Always update all three |
# Run the server in dev mode (auto-restarts on changes)
npm run dev
# The server reads from stdin and writes to stdout
# You can pipe MCP JSON-RPC messages to test| Symptom | Likely cause |
|---|---|
| Server crashes silently | console.log used instead of console.error (corrupts stdio) |
| "Unknown tool" error | Tool not registered in src/index.ts (missing from tools array or routing) |
| Auth always fails | PAT not in env, or handler not calling getApiKey(args) |
| Empty API response | Wrong endpoint URL — check endpoints.ts query string building |
| Type errors after adding tool | Missing .js extension in import path |
These fire automatically — no manual invocation needed:
| Event | Hook | What it does |
|---|---|---|
| New session / resume / compaction | SessionStart → session-context.sh |
Injects git state, version consistency, memory count, and key rules into context |
| Before compaction | PreCompact → pre-compact.sh |
Captures uncommitted changes and prompts to save lessons before context is lost |
| Turn ends | Stop → stop-check.sh |
If 3+ files changed, reminds to run /retro to capture lessons |
On compaction specifically, the session-context hook re-anchors critical rules:
- Check CLAUDE.md protocols
- Check auto-memory for relevant lessons
- Tool pattern reference (list-testruns.ts)
- Quality gate after every change
- Version sync across 3 files
- No console.log (stdio corruption)
- CHECK MEMORY — Read auto-memory for lessons relevant to this area. If a prior lesson matches, follow its rule — it exists because something went wrong before
- UNDERSTAND — Restate what's being asked in one sentence
- READ — Read the affected files. Don't assume you know how they work
- PLAN — List files to modify, changes needed, what NOT to touch
- IMPLEMENT — Small incremental steps. Run typecheck after each change
- VERIFY — Run quality gate. Fix issues before moving on
- DOCUMENT — Update docs if the change is user-facing
- STOP — Don't retry the same thing
- READ THE ERROR — Understand what actually failed
- TRACE — Follow the error to its source (don't fix symptoms)
- FIX — Fix the root cause
- VERIFY — Confirm the fix and that nothing else broke
When told you're wrong, your approach is rejected, or verification fails on second attempt:
- STOP — Halt the current approach. Don't "just try one more thing"
- ANALYZE — List 3 specific reasons why your approach might be wrong. Reference actual code, not abstract possibilities
- IDENTIFY — State the gap: "I did X because I assumed Y, but the actual constraint is Z"
- LEARN — Write a lesson to auto-memory (feedback type) with:
- What triggered it
- What was wrong and why
- What should have been done instead
- The reusable rule (one sentence)
- ACT — Proceed with the corrected approach. If still uncertain, ask before acting
Learning rules that compound across sessions:
Before starting any task:
- Check auto-memory for lessons matching the current task type (tool changes, endpoint work, docs, publishing)
- If a prior lesson matches, follow its rule — don't repeat past mistakes
After completing any non-trivial task:
- Did anything go wrong? (rework, user correction, failed verification, wrong approach)
- If yes → write a lesson to auto-memory (feedback type) with: trigger, what was wrong, what should have been done, and the reusable rule
- If no → move on. Don't write lessons for things that went smoothly
After user corrections or rework → run /retro
Promotion to permanent rules: When the same lesson pattern appears 3+ times in auto-memory:
- It's no longer a one-off — it's a pattern
- Propose adding it to the Known Patterns & Pitfalls table in this CLAUDE.md
- Get user approval before modifying
- Not an HTTP server (it's stdio)
- Not a CLI tool for end users (it's a server that AI tools spawn)
- Not a monorepo (single package, single concern)
- Not a framework (it's a concrete implementation with 12 specific tools)
Keep it simple. Every tool follows the same pattern. Consistency is more important than cleverness.