Skip to main content

Momus-MCP

The harshest critic among the gods, pointed at your test suite.

Momus is the Greek personification of satire, mockery and blame, the one god whose entire job was to find fault, and who was thrown off Olympus for doing it too well.

What he does

A green suite is not evidence. An assertion that compares a value with itself passes forever. So do a mock that echoes back the exact value it was just told to return and a stub for a method that stopped existing months ago on the class it doubles. All three are green, and none of them proves the code works. Coding agents are good at writing exactly this kind of test, because a green suite is the goal they were handed, not a means to one.

Momus-MCP reads your test suite and the production code it exercises, builds a symbol graph from the real classes and interfaces, and checks every mock, spy and assertion against that graph. He never runs your code, never talks to the network, and never writes to your workspace. Findings are deterministic: the same workspace produces the same output, byte for byte, every time you audit it.

Quick start

Momus-MCP is not yet published to npm, so install it from source. You need Node.js 20 or newer and the Claude Code CLI.

bash
git clone https://github.com/AraneaDev/Momus-MCP.git
cd Momus-MCP
npm install
npx momus audit .
claude mcp add momus -- "$(pwd)/node_modules/.bin/momus" serve

Examples

Two calls, both run for real against Momus-MCP's own source.

Listing the rule catalogue:

bash
$ npx momus rules | head -20
TAUT-001 self-comparison (error) — assertion compares an expression with itself
TAUT-002 mock-echo (error) — assertion re-asserts a stub's own configured return
TAUT-003 constant-tautology (error) — both assertion sides are compile-time constants
TAUT-004 mock-only-assertion (warning) — test exercises no production code
TAUT-005 zero-reach-stub (warning) — mock configured but never invoked or asserted
TAUT-006 unconfigured-spy-assert (warning) — toHaveBeenCalled* on a spy with no stub and no call path
DRIFT-001 missing-member (error) — stubbed member does not exist on the production target
DRIFT-002 signature-mismatch (warning) — stub call signature diverges from production (arity)
DRIFT-003 return-type-mismatch (warning) — configured value not assignable to the production return type
DRIFT-004 constructor-drift (error) — double construction omits required constructor parameters (PHP)
DRIFT-005 missing-export (error) — mocked name is not exported by the target module (vi.mock factory keys, Python patch targets)
DRIFT-006 stale-mock (warning) — mock target changed since the base ref but the mock file was not updated (git-diff mode)
MOCK-001 mock-saturation (warning) — over-mocking heuristic
MOCK-002 mock-of-self (info) — the test mocks a module it also imports as the SUT

Suppression: // @momus-ignore | // @momus-ignore:RULE | /** @momus-ignore */ | // @momus-ignore-file[:RULE]

Auditing a fixture folder from a clean checkout:

bash
$ npx momus audit packages/parser-typescript/test/fixtures 2>&1 | head -20
# Momus audit — packages/parser-typescript/test/fixtures

Audited 59 files · 0 issues (0 error · 0 warning · 0 info) · 6951ms — CLEAN:true

Capabilities

CategoryRulesWhat it catches
Tautological assertionsTAUT-001 to TAUT-006Self-comparison, mock-echo, constant-tautology, mock-only assertions, zero-reach stubs, unconfigured-spy assertions
Mock-contract driftDRIFT-001 to DRIFT-006Missing members, signature mismatches, return-type mismatches, constructor drift, missing exports, stale mocks (git-diff aware)
Mock hygieneMOCK-001, MOCK-002Over-mocking (saturation), mocking the module under test

Thirteen of the fourteen rules run against all four languages. Constructor drift is the exception and runs on PHP only, because in TypeScript the compiler already catches it. Each parser resolves the real symbols of its own ecosystem, so a drift finding in Rust rests on the same symbol knowledge as one in TypeScript:

LanguageTest frameworksMock APIs it understandsEnabled
TypeScript/JavaScriptVitest, Jestvi.mock, vi.fn, vi.spyOn, vi.mocked, and the Jest equivalentsby default
PHPPHPUnit, PestcreateMock, getMockForAbstractClass, Mockery's mock()by default
Pythonpytest, unittestpatch, patch.object, Mock(spec=), mocker, monkeypatchlanguages.python
Rustbuilt-in test harnessmockall (#[automock], mock!, expect_*().returning()), mockitolanguages.rust

Python and Rust are opt-in, so adding Momus-MCP to an existing workspace cannot suddenly flood it with findings from a parser you did not ask for.

Configuration

Momus-MCP reads .momusrc from the workspace root; npx momus init scaffolds one for you.

jsonc
{
  "languages": { "typescript": true, "php": false },
  "testFilePatterns": ["**/*.{test,spec}.{ts,tsx,js,jsx,mjs}", "**/__tests__/**"],
  "ignorePatterns": ["**/node_modules/**", "**/dist/**", "**/.git/**"],
  "rules": {
    "TAUT-002": { "severity": "error" }
  },
  "tokenBudget": { "maxIssuesPerReport": 50, "maxIssueLineTokens": 100 },
  "cache": { "dir": ".momus/cache", "enabled": true }
}

languages turns a parser on or off per project. ignorePatterns keeps generated and vendored code out of the audit. rules lets you tighten or loosen a specific rule's severity. tokenBudget caps how much a single report costs an agent to read: by contract, a finding stays under 100 tokens.