npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

mcp-source-lint

v0.1.0

Published

Local static analyzer for MCP server TypeScript and JavaScript source.

Readme

mcp-source-lint

A local, no-account static-analysis linter for an MCP server's own TypeScript/JavaScript source. It parses your server with the Babel AST toolchain and flags correctness bugs that the MCP Inspector and metadata-only scanners do not catch — runs entirely on local files, no network, no API key, no account.

mcp-source-lint is a narrow correctness linter for MCP server source; it is not a security scanner or a schema quality grader.

Sibling to ast-lens-mcp: same Babel engine and test discipline, pointed at a different problem (MCP server correctness, not general code intelligence).

What it catches (v1)

| Rule | What it flags | Why it matters | |------|---------------|----------------| | no-console-on-stdio | console.log/info/debug/table/… and process.stdout.write in a stdio server | A stdio server speaks JSON-RPC over stdout. Any other stdout write interleaves into the protocol stream and silently corrupts framing — a common, hard-to-debug failure the Inspector never surfaces. | | require-tool-input-schema | A tool whose handler destructures input fields but whose registration declares no input schema | Without a schema the SDK passes no validated arguments object, so those fields are undefined/unvalidated at runtime. |

Both rules are deterministic (no LLM) and emit file:line citations.

Usage

# lint a server directory (or a single file)
npx mcp-source-lint ./path/to/your/mcp-server

# JSON output for CI
npx mcp-source-lint ./server --json

# one line per finding
npx mcp-source-lint ./server --compact

# describe the rules
npx mcp-source-lint --explain

Exit code is 1 when any error-severity finding is present, so it can gate CI.

Precision (it tries hard not to false-accuse)

A linter whose findings are wrong is worse than none, so v1 is built to minimize false positives:

  • Transport-aware. console.log is only an error on a stdio server. On a detected HTTP/SSE server the stdout rule is skipped entirely (stdout logging is fine there). When the transport can't be confirmed, stdout writes are reported as warnings, not errors.
  • stderr is never flagged. console.error/warn/trace write to stderr and are safe on stdio.
  • Parameterless tools are fine. A tool with no input correctly has no schema; the schema rule only fires when a handler actually destructures input fields.
  • Indirect schemas are trusted. When the params schema is a referenced const or a z.object(...) call rather than an inline literal, the rule stays silent rather than guess.
  • Scope. The schema rule runs only in files that import @modelcontextprotocol/sdk. Test files and build scripts are excluded from the stdout rule.

How it works

discover (fast-glob) → parse (@babel/parser, syntactic, no type-check) → detect transport → run rules → report (markdown / JSON / compact).

The transport is detected once per project by looking for the SDK's StdioServerTransport vs StreamableHTTPServerTransport/SSEServerTransport.

Limitations (honest, v1)

It is syntactic, not type-aware (it parses; it does not type-check) and single-file. Concretely, the following are not flagged today — by design, to keep false positives near zero:

  • Aliased / wrapped console. const log = console.log.bind(console), or logging behind a custom wrapper function, is not traced (name-based, not data-flow).
  • Plain-identifier input. A handler that reads input via args.foo rather than destructuring ({ foo }) is not yet flagged by require-tool-input-schema (the destructured form is the high-precision signal).
  • Low-level server API. The schema rule understands the high-level McpServer .tool()/.registerTool() API; servers built on the low-level Server/setRequestHandler API are still checked by the stdout rule but not yet by the schema rule.
  • Indirect / cross-module schemas. A params schema supplied as a referenced const, a spread, or imported from another file is treated as "present" (no false positive, no deep check).
  • Handler as a named reference. A tool whose handler is passed as a function identifier (not an inline function) is not analyzed.
  • Transport detection is name-based. It keys on the SDK transport class names (incl. a raw-source fallback so a broken entrypoint still counts); a type-only or re-export-only reference can still read as a usage.
  • Reachability is approximated, not proven. A stdout write is a hard error only on the server-connect path (its enclosing scope also calls .connect(). Writes in process.argv sub-command branches (auth setup, --help, transport selection) and in multi-transport servers are downgraded to warnings rather than proven unreachable — so a setup-only console.log is a warning, not a false "will corrupt the stream" error.

Two rules so far. Planned next: schema-handler-drift (declared input schema vs. fields the handler actually uses; also catches inputSchema: {} with a destructuring handler) and no-leaked-error-detail (secrets/raw errors returned to the client).

Develop

npm install
npm run build      # tsup -> dist/ (cli.js + index.js)
npm test           # vitest: rule unit tests + fixture true-positive/true-negative suite
npm run smoke      # run the CLI against the bundled broken-server fixture

The test/fixtures/ corpus (broken-server, clean-server, http-server, mixed-server, parse-fallback, traverse-error-project) is the precision proof: each rule has at least one fixture that must flag and one that must not, and the project-level tests cover transport edge cases plus crash-resistant traversal.

License

MIT © Morgan