Code smell detection · MCP server

Your code stinks.
Here's exactly where.

codesmell scans source for the classics — long functions, deep nesting, duplicated blocks, magic numbers, silent catches — with deterministic heuristics. No API key, no LLM, no upload. Paste code below, or point any MCP client at the remote server.

13 rules11 languages0 dependencies at runtime100% deterministic
Cursor · mcp.json
{
  "mcpServers": {
    "codesmell": {
      "url": "https://codesmell.net/api/mcp"
    }
  }
}
Playground

Paste code. See the stink.

Runs the full engine locally in your browser — nothing is uploaded. Give the file a name (its extension drives language detection) and hit Analyze, or press ⌘↵. Switch to the live MCP server to test the deployed endpoint instead.

0 lines
⌘↵ to analyze

Results land here: severity-tagged smells with line numbers, file stats, and a health score.

or browse 15 examples →
Examples

Every rule, in real code.

15 snippets across 11 languages — a different failure mode each. The findings below are computed live in your browser by the same engine the REST API and the MCP server run.

Order service

order-service.ts

The classic: deep nesting, a swallowed exception, an argument list nobody can remember.

84score · B
1 errors2 warnings4 info
25 lines1 functionslongest 21 ln
order-service.ts
// TODO: refactor this whole flow
export class OrderService {
  async process(order, user, discount, taxRate, shipping, currency, locale) {
    try {
      if (order.items.length > 0) {
        if (user.isActive) {
          if (discount > 0) {
            if (order.total > 9999) {
              console.log("vip order, applying surprise discount");
            }
            const total = order.total * (1 - discount / 100) * (1 + 0.0825) + 4.99;
            order.total = total;
          }
        }
      }
      // const legacyTotal = order.qty * order.price;
      // if (legacyTotal > 100) {
      //   applyLegacyDiscount(legacyTotal);
      // }
      await fetch("/api/orders", { method: "POST" });
    } catch (err) {}
    return order.total;
  }
}

findings

infotodo-commentL1

Deferred-work comment. Track it in an issue instead.

Fix: Technical Debt — Move the note into a tracked issue; a comment will rot in the file.

// TODO: refactor this whole flow
warninglong-parameter-listL3

Function "process" takes 7 parameters (budget 4). Introduce a parameter object.

Fix: Introduce Parameter Object — Group the arguments into an object that says what they are.

async process(order, user, discount, taxRate, shipping, currency, locale) {
infomagic-numberL8

Unexplained numeric literal "9999". Name it as a constant.

Fix: Replace Magic Literal — Name the value as a constant so its intent is explicit.

if (order.total > 9999) {
infoconsole-statementL9

Debug print left in the code. Remove or route through a logger.

Fix: Remove Dead Code — Delete the stray print, or route it through the application logger.

console.log("vip order, applying surprise discount");
warningdeep-nestingL9–23

Nesting depth 6 inside "process" (budget 4). Use early returns and guard clauses.

Fix: Replace Nested Conditional with Guard Clauses — Return early on the failure cases, then flatten the happy path.

console.log("vip order, applying surprise discount");
infocommented-out-codeL16–19

4 lines of commented-out code. Delete it — version control remembers.

Fix: Remove Dead Code — Delete it — version control remembers what it did.

// const legacyTotal = order.qty * order.price;
errorempty-catchL21

catch block swallows the error silently. Log or rethrow.

Fix: Replace Exception with Precheck — Guard the risky call, or handle and rethrow — silence hides the failure.

} catch (err) {}
Remote MCP

One URL. Every MCP client.

Stateless Streamable HTTP — no session, no auth, no API key. Let your agent review code for smells before you commit it. Prefer scripting? The same engine is exposed as a plain REST endpoint.

Cursor · mcp.json
{
  "mcpServers": {
    "codesmell": {
      "url": "https://codesmell.net/api/mcp"
    }
  }
}
Claude Desktop · config
{
  "mcpServers": {
    "codesmell": {
      "type": "http",
      "url": "https://codesmell.net/api/mcp"
    }
  }
}
tools/call · detect
{
  "method": "tools/call",
  "params": {
    "name": "detect",
    "arguments": {
      "code": "function f(a,b,c,d,e){\n  if(a){if(b){if(c){work();}}}\n}",
      "filename": "handlers.ts"
    }
  }
}
REST · POST /api/analyze
curl -s https://codesmell.net/api/analyze \
  -H 'content-type: application/json' \
  -d '{"code": "try { risky(); } catch (e) {}", "filename": "risky.ts"}'

What the remote endpoint supports

  • ✓ detect → smells with severity + line numbers, stats, 0–100 health score
  • ✓ rules → the full rule catalog with default thresholds
  • ✓ language auto-detection from filename or content; per-call threshold overrides
  • ✓ JS/TS, Python, Go, Rust, Java, C#, C/C++, Ruby, PHP
Rule catalog

Thirteen smells, zero vibes.

Every rule is a deterministic heuristic with a tunable threshold — the same catalog the engine, the playground, and the MCP server use. Each one ships with the refactoring that fixes it. Heuristics inform refactoring; they don't replace judgment.

long-function50

Function body exceeds the line budget. Long functions mix responsibilities — extract cohesive blocks into named helpers.

Fix: Extract Function

long-parameter-list4

Function takes too many parameters. Prefer an options object or introduce a parameter object.

Fix: Introduce Parameter Object

deep-nesting4

Code is indented too deep inside a function — a pyramid of conditionals. Use early returns and guard clauses.

Fix: Replace Nested Conditional with Guard Clauses

duplicated-code4

A block of identical consecutive lines appears more than once. Extract shared logic into a function.

Fix: Extract Function

god-file400

The file is too large or contains too many functions — it likely has too many responsibilities. Split it by domain.

Fix: Extract Class

long-line120

A single line exceeds the character budget, hurting readability in reviews and diffs.

Fix: Extract Variable

magic-number

An unexplained numeric literal appears in logic. Name it as a constant so intent is explicit.

Fix: Replace Magic Literal

empty-catch

A catch/except block swallows the error silently. At minimum, log or rethrow.

Fix: Replace Exception with Precheck

console-statement

Debug print statement left in the code (console.log, print, println!, fmt.Println, …).

Fix: Remove Dead Code

todo-comment

TODO/FIXME/HACK/XXX comment — deferred work that tends to rot. Track it in an issue instead.

Fix: Technical Debt

commented-out-code

A block of commented-out code. Dead weight — delete it; version control remembers.

Fix: Remove Dead Code

nested-ternary

A ternary expression nested inside another ternary. Use if/else for clarity.

Fix: Decompose Conditional

high-complexity20

A function takes too many paths — too many branches, loops, catches and logical operators to follow. Split it into smaller pieces.

Fix: Replace Conditional with Polymorphism

FAQ

Questions, answered plainly.

The same facts the README and SPEC.md carry — written for humans and for agents that read the page.

What does codesmell detect?

Thirteen code smells with deterministic heuristics: long-function, long-parameter-list, deep-nesting, duplicated-code, god-file, long-line, magic-number, empty-catch, console-statement, todo-comment, commented-out-code, nested-ternary and high-complexity. Every finding carries a severity, a line number, the offending snippet, file stats, and a 0–100 health score with a letter grade.

Which languages does it support?

JavaScript, TypeScript, Python, Go, Rust, Java, C#, C, C++, Ruby and PHP — 11 languages plus an unknown fallback. The language is detected from the filename extension, or from content markers when no filename is given, and can be overridden per request.

Does it need an API key, an account, or an LLM?

No. There is no API key, no account, no upload and no LLM in the loop. The same heuristics run in three places: in your browser, on the REST endpoint, and behind the MCP server. The engine is pure TypeScript with zero runtime dependencies.

How do I connect an MCP client?

Point any MCP client at https://codesmell.net/api/mcp and it works over stateless Streamable HTTP — no session, no auth, no handshake state to manage. The server exposes two tools: detect, which takes { code, filename?, language?, thresholds? } and returns the findings, and rules, which returns the rule catalog with the default thresholds.

Can I call it over plain HTTP instead of MCP?

Yes. POST https://codesmell.net/api/analyze with a JSON body of { code, filename?, language?, thresholds? } and you get { language, smells[], stats, score, grade } back. The same engine powers both endpoints. The browser playground runs it locally by default — switch it to “live MCP” and the code is sent to the deployed server as a real tools/call detect request.

Can I see examples of what it detects?

Yes — the examples gallery holds 15 snippets across 11 languages (JavaScript, TypeScript, Python, Go, Rust, Java, C#, C, C++, Ruby, PHP), each written around one failure mode: a swallowed exception, a conditional pyramid, a magic number, a duplicated block, a god file. The findings next to every snippet are computed live in your browser by the same engine, and one sample is a clean counter-example that triggers nothing at all.

Is the result deterministic?

Yes. The same input always produces the same findings, stats and score. There is no clock, no randomness, no network call and no model in the detection path, which makes it usable in CI and pre-commit hooks where reproducible output matters.

How accurate are the heuristics?

They are heuristics, not a compiler. Strings are not modeled, so a brace or a number inside a string can be misread, and the findings are deliberately capped at 20 per rule to keep responses bounded. The rule catalog, the defaults and the known blind spots are documented in SPEC.md, and the false-positive rate is tracked with a repeatable sweep over real code.

Is there a rate limit?

Yes — 60 requests per minute per IP on the REST and MCP endpoints, with x-ratelimit-limit and x-ratelimit-remaining headers on every response. The health endpoint at /api/health is never rate-limited, and the browser playground is not affected because it runs entirely client-side.