Skip to content

Refactor mcplint project structure and update dependencies - #1

Merged
DLeibner merged 8 commits into
mainfrom
mcp-playground-web
Jul 23, 2026
Merged

Refactor mcplint project structure and update dependencies#1
DLeibner merged 8 commits into
mainfrom
mcp-playground-web

Conversation

@DLeibner

@DLeibner DLeibner commented Jul 14, 2026

Copy link
Copy Markdown
Owner
  • Transitioned to a monorepo setup, renaming the project to mcplint-monorepo.
  • Removed package-lock.json and added pnpm-lock.yaml for dependency management.
  • Updated .gitignore to include Next.js and environment files.
  • Created new apps/web directory with Next.js application structure, including configuration files and initial components.
  • Added environment configuration example and database connection settings for the web app.
  • Enhanced README for clarity on usage and project structure.
  • Implemented initial API routes for linting and report generation.

Summary by CodeRabbit

  • New Features
    • Added a web interface for auditing MCP tool surfaces from pasted JSON, uploaded files, or HTTPS endpoints.
    • Added scored reports with grades, findings, sharing controls, privacy options, and downloadable/shareable views.
    • Added hosted, read-only MCP access through check_mcp_server, plus installation guidance for supported clients.
    • Added CLI output formats, configurable rules, thresholds, and expanded design-quality checks.
  • Documentation
    • Reworked project, rules, and deployment documentation with quick-start and operational guidance.
  • Bug Fixes
    • Added protections for unsafe URLs, oversized requests, rate limits, and expired reports.

- Transitioned to a monorepo setup, renaming the project to `mcplint-monorepo`.
- Removed `package-lock.json` and added `pnpm-lock.yaml` for dependency management.
- Updated `.gitignore` to include Next.js and environment files.
- Created new `apps/web` directory with Next.js application structure, including configuration files and initial components.
- Added environment configuration example and database connection settings for the web app.
- Enhanced README for clarity on usage and project structure.
- Implemented initial API routes for linting and report generation.
@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR converts the repository into a monorepo with a core MCP linter and Next.js web application. It adds linting rules, hosted auditing, SSRF protection, rate limiting, report persistence, sharing, installation pages, tests, CI, deployment, and release automation.

Changes

mcplint platform

Layer / File(s) Summary
Core linting foundation
packages/core/**, package.json
Adds snapshot ingestion, configurable rules, scoring, token counting, reporters, CLI support, fixtures, tests, and package build metadata.
Web ingestion and security
apps/web/app/api/lint/**, apps/web/app/api/mcp/**, apps/web/lib/lint.ts, apps/web/lib/ssrf.ts, apps/web/lib/guarded-fetch.ts, apps/web/lib/rate-limit.ts
Adds validated paste/URL auditing, guarded remote capture, request limits, origin checks, rate limiting, and a stateless MCP endpoint.
Persistence and report workflows
apps/web/lib/store.ts, apps/web/lib/db/**, apps/web/drizzle/**, apps/web/app/api/report/**, apps/web/app/r/**, apps/web/components/ReportView.tsx
Adds in-memory/Postgres run storage, ownership tokens, visibility controls, expiration purging, report APIs, report pages, gated findings, sharing controls, and Open Graph images.
Web presentation and delivery
apps/web/app/**, apps/web/components/**, .github/workflows/**, DEPLOYMENT.md, scripts/smoke-production.mjs
Adds the web UI, installation flows, analytics, styling, end-to-end tests, CI verification, production smoke tests, deployment configuration, and tag-based publishing workflows.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Visitor
  participant LintForm
  participant LintRoute
  participant LintEngine
  participant Store
  Visitor->>LintForm: Submit snapshot or HTTPS URL
  LintForm->>LintRoute: POST /api/lint
  LintRoute->>LintEngine: Build snapshot and run rules
  LintEngine->>Store: Persist report and snapshot
  Store->>LintRoute: Return report id and delete token
  LintRoute->>LintForm: Return id and composite score
  LintForm->>Visitor: Navigate to report page
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 15.19% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the monorepo refactor and dependency/workflow updates.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch mcp-playground-web

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 Checkov (3.3.8)
.claude/launch.json

Traceback (most recent call last):
File "/usr/local/bin/checkov", line 2, in
from checkov.main import Checkov
ModuleNotFoundError: No module named 'checkov'

.github/workflows/ci.yml

Traceback (most recent call last):
File "/usr/local/bin/checkov", line 2, in
from checkov.main import Checkov
ModuleNotFoundError: No module named 'checkov'

.github/workflows/release.yml

Traceback (most recent call last):
File "/usr/local/bin/checkov", line 2, in
from checkov.main import Checkov
ModuleNotFoundError: No module named 'checkov'

  • 2 others

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

🧹 Nitpick comments (7)
apps/web/app/globals.css (1)

352-354: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Replace deprecated break-word value.

The break-word value for the word-break property is deprecated. Consider using overflow-wrap: anywhere or overflow-wrap: break-word instead to achieve the same wrapping behavior without relying on a deprecated CSS feature.

💡 Proposed fix
   white-space: pre-wrap;
-  word-break: break-word;
+  overflow-wrap: break-word;
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/app/globals.css` around lines 352 - 354, Replace the deprecated
word-break: break-word declaration in the affected global style with
overflow-wrap: anywhere, while preserving the existing white-space: pre-wrap
behavior.

Source: Linters/SAST tools

apps/web/components/AuditCta.tsx (1)

46-59: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Handle fetch errors and loading states.

The form submission lacks error handling and a loading state. If the network request fails, the user will be left in a broken state without feedback. Additionally, consider disabling the submit button while the request is in flight.

Alternatively, since you are using React 19, you could use Server Actions or the new action prop for forms with useActionState to handle this more idiomatically.

💡 Proposed fix using a simple try/catch and loading state
   const [email, setEmail] = useState("");
   const [done, setDone] = useState(false);
+  const [busy, setBusy] = useState(false);
 
   if (done) {
@@ -46,14 +47,19 @@
         <form
           className="row"
           style={{ marginTop: 0 }}
           onSubmit={async (event) => {
             event.preventDefault();
-            await fetch("/api/interest", {
-              method: "POST",
-              headers: { "content-type": "application/json" },
-              body: JSON.stringify({ email, runId: id })
-            });
-            track("interest_submitted", { id });
-            setDone(true);
+            setBusy(true);
+            try {
+              await fetch("/api/interest", {
+                method: "POST",
+                headers: { "content-type": "application/json" },
+                body: JSON.stringify({ email, runId: id })
+              });
+              track("interest_submitted", { id });
+              setDone(true);
+            } catch (err) {
+              console.error("Failed to submit interest:", err);
+            } finally {
+              setBusy(false);
+            }
           }}
         >
@@ -75,3 +81,3 @@
           />
-          <button className="primary" type="submit">
+          <button className="primary" type="submit" disabled={busy}>
             Tell me when it ships
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/components/AuditCta.tsx` around lines 46 - 59, Update the form
submission handler in AuditCta to track an in-flight loading state, disable the
submit button while the request is pending, and wrap the interest fetch in
try/catch. Only call track and setDone after a successful response, and provide
user feedback when the request fails.
apps/web/components/Analytics.tsx (1)

7-7: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Prevent useEffect from accidentally returning a value.

If initAnalytics() is or becomes asynchronous, the implicit return will pass a Promise to React, which triggers a runtime warning (React expects either undefined or a synchronous cleanup function). Wrapping the call ensures undefined is always returned.

♻️ Proposed fix
-  useEffect(() => initAnalytics(), []);
+  useEffect(() => {
+    initAnalytics();
+  }, []);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/components/Analytics.tsx` at line 7, Update the useEffect callback
in Analytics to use a block body and invoke initAnalytics() without implicitly
returning its result, ensuring the effect always returns undefined even if
initAnalytics becomes asynchronous.
apps/web/lib/ssrf.test.ts (1)

7-48: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Optional: add coverage for 6to4/Teredo/rfc6052/rfc6145 ranges.

V4_IN_V6_RANGES in ssrf.ts also names "rfc6145", "rfc6052", "6to4", and "teredo", but only the ipv4Mapped case is exercised here (lines 25-27). A couple of addresses from those ranges (e.g. a 2002::/16 6to4 literal) would confirm the "refuse outright" branch behaves as documented — low risk since the fallback already fails closed, but cheap to add.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/lib/ssrf.test.ts` around lines 7 - 48, Extend the blocked-address
cases in the isBlockedAddress test suite to include representative addresses
from the 6to4, Teredo, RFC6052, and RFC6145 ranges named by V4_IN_V6_RANGES in
ssrf.ts. Assert each is blocked, preserving the existing table-driven test
structure and allowed-address coverage.
packages/core/src/config.ts (1)

31-46: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Avoid the Time-of-Check to Time-of-Use (TOCTOU) anti-pattern.

Using fs.access to check if a file exists before reading it is not recommended in Node.js as it introduces a minor race condition and requires redundant system calls. It is more idiomatic to attempt reading the file directly and catch the ENOENT error if it is missing.

(Note: If applied, you can also safely remove access from your node:fs/promises import at the top of the file).

♻️ Proposed refactor
   static async load(explicitPath?: string, cwd = process.cwd()): Promise<McplintConfig> {
     const path = explicitPath ?? resolve(cwd, this.defaultFileName);
-    if (!explicitPath && !(await this.exists(path))) return this.empty();
-    const raw = await readFile(path, "utf8");
-    return configSchema.parse(JSON.parse(raw));
-  }
-
-  private static async exists(path: string): Promise<boolean> {
-    try {
-      await access(path);
-      return true;
-    } catch {
-      return false;
-    }
+    try {
+      const raw = await readFile(path, "utf8");
+      return configSchema.parse(JSON.parse(raw));
+    } catch (error) {
+      if (!explicitPath && (error as any).code === "ENOENT") {
+        return this.empty();
+      }
+      throw error;
+    }
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/src/config.ts` around lines 31 - 46, Refactor Config.load to
remove the pre-read exists check and directly attempt readFile for the resolved
path. Catch only ENOENT errors and return this.empty() for missing files;
rethrow all other read or parsing errors. Remove the now-unused exists method
and access import.
packages/core/src/engine.ts (1)

28-42: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consolidate rule resolution logic.

You can simplify this method by extracting the severity and options resolution inline. This avoids duplicating the ResolvedRule object shape across multiple distinct branches.

♻️ Proposed refactor
   private resolveRule(rule: Rule): ResolvedRule | undefined {
-    const setting: RuleSetting | undefined = this.config.rules[rule.id];
+    const setting = this.config.rules[rule.id];
     if (setting === "off") return undefined;
-    if (setting === undefined) {
-      return { rule, severity: rule.severity, options: { ...rule.defaultOptions } };
-    }
-    if (typeof setting === "string") {
-      return { rule, severity: setting, options: { ...rule.defaultOptions } };
-    }
-    return {
-      rule,
-      severity: setting.severity ?? rule.severity,
-      options: { ...rule.defaultOptions, ...(setting.options ?? {}) }
-    };
+
+    const severity = typeof setting === "string" ? setting : setting?.severity ?? rule.severity;
+    const options = { ...rule.defaultOptions, ...(typeof setting === "object" ? setting.options : {}) };
+
+    return { rule, severity, options };
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/src/engine.ts` around lines 28 - 42, Refactor resolveRule so it
handles the "off" case separately, then computes severity and merged options
once from the configured setting or rule defaults before returning a single
ResolvedRule shape. Preserve the existing precedence for configured severity,
default severity, configured options, and default options.
packages/core/README.md (1)

15-15: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Specify a language for the fenced code block.

As highlighted by static analysis, this fenced code block lacks a language identifier. Consider adding text to improve syntax highlighting and satisfy markdown linters.

📝 Proposed fix
-```
+```text
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/README.md` at line 15, Update the fenced code block in the
README to include the text language identifier, changing the opening fence to
use text while preserving the block’s contents and closing fence.

Source: Linters/SAST tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/web/app/api/cron/purge/route.ts`:
- Around line 13-19: Update the authentication guard in GET so a missing
CRON_SECRET is treated as unauthorized rather than bypassing validation. Require
both a configured secret and an exact matching Bearer authorization header
before continuing; otherwise return the existing 401 Unauthorized response.

In `@apps/web/app/api/interest/route.ts`:
- Around line 17-36: Add endpoint/IP-based rate limiting in the POST handler
before the getDb().insert(interest) call, rejecting requests that exceed the
configured limit with an appropriate throttling response. Ensure the limit
applies to public database writes while preserving validation, local no-database
behavior, and successful inserts for allowed requests.

In `@apps/web/components/ShareControls.tsx`:
- Around line 19-32: Update toggle() to wrap its fetch and success handling in
try...finally, ensuring setBusy(false) always runs when the request throws or
completes. Apply the same try...finally loading-state cleanup to the deletion
fetch at apps/web/components/ShareControls.tsx lines 40-46; both sites require
direct changes.

In `@apps/web/lib/lint.ts`:
- Around line 50-97: Update withTimeout and the MCP capture flow so timeout
expiration actively aborts the in-flight request rather than only rejecting
Promise.race. Thread an AbortSignal (or equivalent) from buildSnapshot through
McpCapture.fromHttp into the HTTP transport, trigger it when the timer fires,
and preserve the existing LintError timeout behavior while preventing later
background rejection.

In `@apps/web/lib/rate-limit.ts`:
- Around line 15-35: Make the production path distinguish missing Upstash
configuration from intentional non-production bypasses: update the configuration
handling around configured, redis, and checkRateLimit so absent
UPSTASH_REDIS_REST_URL or UPSTASH_REDIS_REST_TOKEN in production emits a clear
misconfiguration signal and does not silently allow every request. Preserve the
existing limiter behavior when configuration is present and the intended
fail-open behavior outside production.

In `@apps/web/lib/store.ts`:
- Around line 39-42: Update hashIp to require IP_HASH_SALT before hashing,
removing the public development fallback; fail clearly when the secret is
missing so no IP hash is persisted with a predictable salt.
- Around line 126-151: Update PostgresStore.create() to wrap the runs insert and
conditional runFindings insert in a single db.batch([...]) operation, preserving
the existing values and skipping the findings insert when rows is empty so both
writes commit atomically.

In `@packages/core/src/rules/design-client-directives.ts`:
- Around line 24-28: Update isDirective to match referenced tool names
case-insensitively and only as whole tokens: normalize the sentence and each
name consistently, escape names for use in a regular expression, and apply
explicit token boundaries instead of sentence.includes(name). Preserve the
existing DIRECTIVE_VERB and some-based matching flow.
- Around line 42-56: Update blockCounts construction in the design-client
directive analysis to count each normalized directive key at most once per tool.
Deduplicate the keys derived from a tool’s directives before incrementing
blockCounts, while preserving the existing duplicated lookup and findings
behavior.

In `@packages/core/src/rules/schemas-param-desc-missing.ts`:
- Around line 19-21: Update the property guard in the schema iteration within
the rules-param description validation to exclude null values before accessing
prop.description or prop.$ref. Preserve the existing behavior for non-object
values and valid object properties while preventing malformed null schema
entries from reaching those property accesses.

---

Nitpick comments:
In `@apps/web/app/globals.css`:
- Around line 352-354: Replace the deprecated word-break: break-word declaration
in the affected global style with overflow-wrap: anywhere, while preserving the
existing white-space: pre-wrap behavior.

In `@apps/web/components/Analytics.tsx`:
- Line 7: Update the useEffect callback in Analytics to use a block body and
invoke initAnalytics() without implicitly returning its result, ensuring the
effect always returns undefined even if initAnalytics becomes asynchronous.

In `@apps/web/components/AuditCta.tsx`:
- Around line 46-59: Update the form submission handler in AuditCta to track an
in-flight loading state, disable the submit button while the request is pending,
and wrap the interest fetch in try/catch. Only call track and setDone after a
successful response, and provide user feedback when the request fails.

In `@apps/web/lib/ssrf.test.ts`:
- Around line 7-48: Extend the blocked-address cases in the isBlockedAddress
test suite to include representative addresses from the 6to4, Teredo, RFC6052,
and RFC6145 ranges named by V4_IN_V6_RANGES in ssrf.ts. Assert each is blocked,
preserving the existing table-driven test structure and allowed-address
coverage.

In `@packages/core/README.md`:
- Line 15: Update the fenced code block in the README to include the text
language identifier, changing the opening fence to use text while preserving the
block’s contents and closing fence.

In `@packages/core/src/config.ts`:
- Around line 31-46: Refactor Config.load to remove the pre-read exists check
and directly attempt readFile for the resolved path. Catch only ENOENT errors
and return this.empty() for missing files; rethrow all other read or parsing
errors. Remove the now-unused exists method and access import.

In `@packages/core/src/engine.ts`:
- Around line 28-42: Refactor resolveRule so it handles the "off" case
separately, then computes severity and merged options once from the configured
setting or rule defaults before returning a single ResolvedRule shape. Preserve
the existing precedence for configured severity, default severity, configured
options, and default options.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5cd71402-8e75-47a2-bce1-ec1af032a177

📥 Commits

Reviewing files that changed from the base of the PR and between a9967e3 and 54ffcc4.

⛔ Files ignored due to path filters (3)
  • package-lock.json is excluded by !**/package-lock.json
  • packages/core/tests/__snapshots__/report-golden.test.ts.snap is excluded by !**/*.snap
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (89)
  • .claude/launch.json
  • .gitignore
  • README.md
  • apps/web/.env.example
  • apps/web/app/api/cron/purge/route.ts
  • apps/web/app/api/interest/route.ts
  • apps/web/app/api/lint/route.ts
  • apps/web/app/globals.css
  • apps/web/app/layout.tsx
  • apps/web/app/page.tsx
  • apps/web/app/r/[id]/opengraph-image.tsx
  • apps/web/app/r/[id]/page.tsx
  • apps/web/app/rules/page.tsx
  • apps/web/components/Analytics.tsx
  • apps/web/components/AuditCta.tsx
  • apps/web/components/LintForm.tsx
  • apps/web/components/ReportView.tsx
  • apps/web/components/ShareControls.tsx
  • apps/web/drizzle.config.ts
  • apps/web/drizzle/0000_nice_silver_centurion.sql
  • apps/web/drizzle/meta/0000_snapshot.json
  • apps/web/drizzle/meta/_journal.json
  • apps/web/lib/analytics.ts
  • apps/web/lib/db/client.ts
  • apps/web/lib/db/schema.ts
  • apps/web/lib/guarded-fetch.ts
  • apps/web/lib/lint.ts
  • apps/web/lib/rate-limit.ts
  • apps/web/lib/ssrf.test.ts
  • apps/web/lib/ssrf.ts
  • apps/web/lib/store.ts
  • apps/web/lib/version.ts
  • apps/web/next.config.ts
  • apps/web/package.json
  • apps/web/tsconfig.json
  • apps/web/vercel.json
  • package.json
  • packages/core/README.md
  • packages/core/docs/rules.md
  • packages/core/fixtures/bad-server.json
  • packages/core/fixtures/good-server.json
  • packages/core/fixtures/private/.gitkeep
  • packages/core/package.json
  • packages/core/src/cli.ts
  • packages/core/src/config.ts
  • packages/core/src/engine.ts
  • packages/core/src/index.ts
  • packages/core/src/ingest/index.ts
  • packages/core/src/ingest/mcp-capture.ts
  • packages/core/src/ingest/snapshot-schema.ts
  • packages/core/src/project.ts
  • packages/core/src/reporters/json.ts
  • packages/core/src/reporters/md.ts
  • packages/core/src/reporters/tty.ts
  • packages/core/src/rules/BaseRule.ts
  • packages/core/src/rules/annotations-missing-hints.ts
  • packages/core/src/rules/descriptions-missing.ts
  • packages/core/src/rules/descriptions-too-long.ts
  • packages/core/src/rules/descriptions-too-short.ts
  • packages/core/src/rules/design-client-directives.ts
  • packages/core/src/rules/design-confusable-params.ts
  • packages/core/src/rules/design-crud-mirror.ts
  • packages/core/src/rules/design-duplicate-leading-words.ts
  • packages/core/src/rules/design-enum-combination-unencoded.ts
  • packages/core/src/rules/design-enum-in-prose.ts
  • packages/core/src/rules/design-list-no-limit.ts
  • packages/core/src/rules/design-negative-guidance-present.ts
  • packages/core/src/rules/design-overlap-cluster.ts
  • packages/core/src/rules/docs.ts
  • packages/core/src/rules/index.ts
  • packages/core/src/rules/naming-convention.ts
  • packages/core/src/rules/schemas-complexity-budget.ts
  • packages/core/src/rules/schemas-loose.ts
  • packages/core/src/rules/schemas-param-desc-missing.ts
  • packages/core/src/rules/surface-token-budget.ts
  • packages/core/src/rules/surface-tool-budget.ts
  • packages/core/src/scoring.ts
  • packages/core/src/tokens.ts
  • packages/core/src/types.ts
  • packages/core/tests/core-api.test.ts
  • packages/core/tests/engine.test.ts
  • packages/core/tests/helpers.ts
  • packages/core/tests/report-golden.test.ts
  • packages/core/tests/rules-tier1.test.ts
  • packages/core/tests/rules-tier2.test.ts
  • packages/core/tsconfig.json
  • packages/core/tsup.config.ts
  • pnpm-workspace.yaml
  • src/rules/docs.ts
💤 Files with no reviewable changes (1)
  • src/rules/docs.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 10

🧹 Nitpick comments (7)
apps/web/app/globals.css (1)

352-354: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Replace deprecated break-word value.

The break-word value for the word-break property is deprecated. Consider using overflow-wrap: anywhere or overflow-wrap: break-word instead to achieve the same wrapping behavior without relying on a deprecated CSS feature.

💡 Proposed fix
   white-space: pre-wrap;
-  word-break: break-word;
+  overflow-wrap: break-word;
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/app/globals.css` around lines 352 - 354, Replace the deprecated
word-break: break-word declaration in the affected global style with
overflow-wrap: anywhere, while preserving the existing white-space: pre-wrap
behavior.

Source: Linters/SAST tools

apps/web/components/AuditCta.tsx (1)

46-59: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Handle fetch errors and loading states.

The form submission lacks error handling and a loading state. If the network request fails, the user will be left in a broken state without feedback. Additionally, consider disabling the submit button while the request is in flight.

Alternatively, since you are using React 19, you could use Server Actions or the new action prop for forms with useActionState to handle this more idiomatically.

💡 Proposed fix using a simple try/catch and loading state
   const [email, setEmail] = useState("");
   const [done, setDone] = useState(false);
+  const [busy, setBusy] = useState(false);
 
   if (done) {
@@ -46,14 +47,19 @@
         <form
           className="row"
           style={{ marginTop: 0 }}
           onSubmit={async (event) => {
             event.preventDefault();
-            await fetch("/api/interest", {
-              method: "POST",
-              headers: { "content-type": "application/json" },
-              body: JSON.stringify({ email, runId: id })
-            });
-            track("interest_submitted", { id });
-            setDone(true);
+            setBusy(true);
+            try {
+              await fetch("/api/interest", {
+                method: "POST",
+                headers: { "content-type": "application/json" },
+                body: JSON.stringify({ email, runId: id })
+              });
+              track("interest_submitted", { id });
+              setDone(true);
+            } catch (err) {
+              console.error("Failed to submit interest:", err);
+            } finally {
+              setBusy(false);
+            }
           }}
         >
@@ -75,3 +81,3 @@
           />
-          <button className="primary" type="submit">
+          <button className="primary" type="submit" disabled={busy}>
             Tell me when it ships
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/components/AuditCta.tsx` around lines 46 - 59, Update the form
submission handler in AuditCta to track an in-flight loading state, disable the
submit button while the request is pending, and wrap the interest fetch in
try/catch. Only call track and setDone after a successful response, and provide
user feedback when the request fails.
apps/web/components/Analytics.tsx (1)

7-7: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Prevent useEffect from accidentally returning a value.

If initAnalytics() is or becomes asynchronous, the implicit return will pass a Promise to React, which triggers a runtime warning (React expects either undefined or a synchronous cleanup function). Wrapping the call ensures undefined is always returned.

♻️ Proposed fix
-  useEffect(() => initAnalytics(), []);
+  useEffect(() => {
+    initAnalytics();
+  }, []);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/components/Analytics.tsx` at line 7, Update the useEffect callback
in Analytics to use a block body and invoke initAnalytics() without implicitly
returning its result, ensuring the effect always returns undefined even if
initAnalytics becomes asynchronous.
apps/web/lib/ssrf.test.ts (1)

7-48: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Optional: add coverage for 6to4/Teredo/rfc6052/rfc6145 ranges.

V4_IN_V6_RANGES in ssrf.ts also names "rfc6145", "rfc6052", "6to4", and "teredo", but only the ipv4Mapped case is exercised here (lines 25-27). A couple of addresses from those ranges (e.g. a 2002::/16 6to4 literal) would confirm the "refuse outright" branch behaves as documented — low risk since the fallback already fails closed, but cheap to add.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/lib/ssrf.test.ts` around lines 7 - 48, Extend the blocked-address
cases in the isBlockedAddress test suite to include representative addresses
from the 6to4, Teredo, RFC6052, and RFC6145 ranges named by V4_IN_V6_RANGES in
ssrf.ts. Assert each is blocked, preserving the existing table-driven test
structure and allowed-address coverage.
packages/core/src/config.ts (1)

31-46: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Avoid the Time-of-Check to Time-of-Use (TOCTOU) anti-pattern.

Using fs.access to check if a file exists before reading it is not recommended in Node.js as it introduces a minor race condition and requires redundant system calls. It is more idiomatic to attempt reading the file directly and catch the ENOENT error if it is missing.

(Note: If applied, you can also safely remove access from your node:fs/promises import at the top of the file).

♻️ Proposed refactor
   static async load(explicitPath?: string, cwd = process.cwd()): Promise<McplintConfig> {
     const path = explicitPath ?? resolve(cwd, this.defaultFileName);
-    if (!explicitPath && !(await this.exists(path))) return this.empty();
-    const raw = await readFile(path, "utf8");
-    return configSchema.parse(JSON.parse(raw));
-  }
-
-  private static async exists(path: string): Promise<boolean> {
-    try {
-      await access(path);
-      return true;
-    } catch {
-      return false;
-    }
+    try {
+      const raw = await readFile(path, "utf8");
+      return configSchema.parse(JSON.parse(raw));
+    } catch (error) {
+      if (!explicitPath && (error as any).code === "ENOENT") {
+        return this.empty();
+      }
+      throw error;
+    }
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/src/config.ts` around lines 31 - 46, Refactor Config.load to
remove the pre-read exists check and directly attempt readFile for the resolved
path. Catch only ENOENT errors and return this.empty() for missing files;
rethrow all other read or parsing errors. Remove the now-unused exists method
and access import.
packages/core/src/engine.ts (1)

28-42: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consolidate rule resolution logic.

You can simplify this method by extracting the severity and options resolution inline. This avoids duplicating the ResolvedRule object shape across multiple distinct branches.

♻️ Proposed refactor
   private resolveRule(rule: Rule): ResolvedRule | undefined {
-    const setting: RuleSetting | undefined = this.config.rules[rule.id];
+    const setting = this.config.rules[rule.id];
     if (setting === "off") return undefined;
-    if (setting === undefined) {
-      return { rule, severity: rule.severity, options: { ...rule.defaultOptions } };
-    }
-    if (typeof setting === "string") {
-      return { rule, severity: setting, options: { ...rule.defaultOptions } };
-    }
-    return {
-      rule,
-      severity: setting.severity ?? rule.severity,
-      options: { ...rule.defaultOptions, ...(setting.options ?? {}) }
-    };
+
+    const severity = typeof setting === "string" ? setting : setting?.severity ?? rule.severity;
+    const options = { ...rule.defaultOptions, ...(typeof setting === "object" ? setting.options : {}) };
+
+    return { rule, severity, options };
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/src/engine.ts` around lines 28 - 42, Refactor resolveRule so it
handles the "off" case separately, then computes severity and merged options
once from the configured setting or rule defaults before returning a single
ResolvedRule shape. Preserve the existing precedence for configured severity,
default severity, configured options, and default options.
packages/core/README.md (1)

15-15: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Specify a language for the fenced code block.

As highlighted by static analysis, this fenced code block lacks a language identifier. Consider adding text to improve syntax highlighting and satisfy markdown linters.

📝 Proposed fix
-```
+```text
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/README.md` at line 15, Update the fenced code block in the
README to include the text language identifier, changing the opening fence to
use text while preserving the block’s contents and closing fence.

Source: Linters/SAST tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/web/app/api/cron/purge/route.ts`:
- Around line 13-19: Update the authentication guard in GET so a missing
CRON_SECRET is treated as unauthorized rather than bypassing validation. Require
both a configured secret and an exact matching Bearer authorization header
before continuing; otherwise return the existing 401 Unauthorized response.

In `@apps/web/app/api/interest/route.ts`:
- Around line 17-36: Add endpoint/IP-based rate limiting in the POST handler
before the getDb().insert(interest) call, rejecting requests that exceed the
configured limit with an appropriate throttling response. Ensure the limit
applies to public database writes while preserving validation, local no-database
behavior, and successful inserts for allowed requests.

In `@apps/web/components/ShareControls.tsx`:
- Around line 19-32: Update toggle() to wrap its fetch and success handling in
try...finally, ensuring setBusy(false) always runs when the request throws or
completes. Apply the same try...finally loading-state cleanup to the deletion
fetch at apps/web/components/ShareControls.tsx lines 40-46; both sites require
direct changes.

In `@apps/web/lib/lint.ts`:
- Around line 50-97: Update withTimeout and the MCP capture flow so timeout
expiration actively aborts the in-flight request rather than only rejecting
Promise.race. Thread an AbortSignal (or equivalent) from buildSnapshot through
McpCapture.fromHttp into the HTTP transport, trigger it when the timer fires,
and preserve the existing LintError timeout behavior while preventing later
background rejection.

In `@apps/web/lib/rate-limit.ts`:
- Around line 15-35: Make the production path distinguish missing Upstash
configuration from intentional non-production bypasses: update the configuration
handling around configured, redis, and checkRateLimit so absent
UPSTASH_REDIS_REST_URL or UPSTASH_REDIS_REST_TOKEN in production emits a clear
misconfiguration signal and does not silently allow every request. Preserve the
existing limiter behavior when configuration is present and the intended
fail-open behavior outside production.

In `@apps/web/lib/store.ts`:
- Around line 39-42: Update hashIp to require IP_HASH_SALT before hashing,
removing the public development fallback; fail clearly when the secret is
missing so no IP hash is persisted with a predictable salt.
- Around line 126-151: Update PostgresStore.create() to wrap the runs insert and
conditional runFindings insert in a single db.batch([...]) operation, preserving
the existing values and skipping the findings insert when rows is empty so both
writes commit atomically.

In `@packages/core/src/rules/design-client-directives.ts`:
- Around line 24-28: Update isDirective to match referenced tool names
case-insensitively and only as whole tokens: normalize the sentence and each
name consistently, escape names for use in a regular expression, and apply
explicit token boundaries instead of sentence.includes(name). Preserve the
existing DIRECTIVE_VERB and some-based matching flow.
- Around line 42-56: Update blockCounts construction in the design-client
directive analysis to count each normalized directive key at most once per tool.
Deduplicate the keys derived from a tool’s directives before incrementing
blockCounts, while preserving the existing duplicated lookup and findings
behavior.

In `@packages/core/src/rules/schemas-param-desc-missing.ts`:
- Around line 19-21: Update the property guard in the schema iteration within
the rules-param description validation to exclude null values before accessing
prop.description or prop.$ref. Preserve the existing behavior for non-object
values and valid object properties while preventing malformed null schema
entries from reaching those property accesses.

---

Nitpick comments:
In `@apps/web/app/globals.css`:
- Around line 352-354: Replace the deprecated word-break: break-word declaration
in the affected global style with overflow-wrap: anywhere, while preserving the
existing white-space: pre-wrap behavior.

In `@apps/web/components/Analytics.tsx`:
- Line 7: Update the useEffect callback in Analytics to use a block body and
invoke initAnalytics() without implicitly returning its result, ensuring the
effect always returns undefined even if initAnalytics becomes asynchronous.

In `@apps/web/components/AuditCta.tsx`:
- Around line 46-59: Update the form submission handler in AuditCta to track an
in-flight loading state, disable the submit button while the request is pending,
and wrap the interest fetch in try/catch. Only call track and setDone after a
successful response, and provide user feedback when the request fails.

In `@apps/web/lib/ssrf.test.ts`:
- Around line 7-48: Extend the blocked-address cases in the isBlockedAddress
test suite to include representative addresses from the 6to4, Teredo, RFC6052,
and RFC6145 ranges named by V4_IN_V6_RANGES in ssrf.ts. Assert each is blocked,
preserving the existing table-driven test structure and allowed-address
coverage.

In `@packages/core/README.md`:
- Line 15: Update the fenced code block in the README to include the text
language identifier, changing the opening fence to use text while preserving the
block’s contents and closing fence.

In `@packages/core/src/config.ts`:
- Around line 31-46: Refactor Config.load to remove the pre-read exists check
and directly attempt readFile for the resolved path. Catch only ENOENT errors
and return this.empty() for missing files; rethrow all other read or parsing
errors. Remove the now-unused exists method and access import.

In `@packages/core/src/engine.ts`:
- Around line 28-42: Refactor resolveRule so it handles the "off" case
separately, then computes severity and merged options once from the configured
setting or rule defaults before returning a single ResolvedRule shape. Preserve
the existing precedence for configured severity, default severity, configured
options, and default options.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5cd71402-8e75-47a2-bce1-ec1af032a177

📥 Commits

Reviewing files that changed from the base of the PR and between a9967e3 and 54ffcc4.

⛔ Files ignored due to path filters (3)
  • package-lock.json is excluded by !**/package-lock.json
  • packages/core/tests/__snapshots__/report-golden.test.ts.snap is excluded by !**/*.snap
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (89)
  • .claude/launch.json
  • .gitignore
  • README.md
  • apps/web/.env.example
  • apps/web/app/api/cron/purge/route.ts
  • apps/web/app/api/interest/route.ts
  • apps/web/app/api/lint/route.ts
  • apps/web/app/globals.css
  • apps/web/app/layout.tsx
  • apps/web/app/page.tsx
  • apps/web/app/r/[id]/opengraph-image.tsx
  • apps/web/app/r/[id]/page.tsx
  • apps/web/app/rules/page.tsx
  • apps/web/components/Analytics.tsx
  • apps/web/components/AuditCta.tsx
  • apps/web/components/LintForm.tsx
  • apps/web/components/ReportView.tsx
  • apps/web/components/ShareControls.tsx
  • apps/web/drizzle.config.ts
  • apps/web/drizzle/0000_nice_silver_centurion.sql
  • apps/web/drizzle/meta/0000_snapshot.json
  • apps/web/drizzle/meta/_journal.json
  • apps/web/lib/analytics.ts
  • apps/web/lib/db/client.ts
  • apps/web/lib/db/schema.ts
  • apps/web/lib/guarded-fetch.ts
  • apps/web/lib/lint.ts
  • apps/web/lib/rate-limit.ts
  • apps/web/lib/ssrf.test.ts
  • apps/web/lib/ssrf.ts
  • apps/web/lib/store.ts
  • apps/web/lib/version.ts
  • apps/web/next.config.ts
  • apps/web/package.json
  • apps/web/tsconfig.json
  • apps/web/vercel.json
  • package.json
  • packages/core/README.md
  • packages/core/docs/rules.md
  • packages/core/fixtures/bad-server.json
  • packages/core/fixtures/good-server.json
  • packages/core/fixtures/private/.gitkeep
  • packages/core/package.json
  • packages/core/src/cli.ts
  • packages/core/src/config.ts
  • packages/core/src/engine.ts
  • packages/core/src/index.ts
  • packages/core/src/ingest/index.ts
  • packages/core/src/ingest/mcp-capture.ts
  • packages/core/src/ingest/snapshot-schema.ts
  • packages/core/src/project.ts
  • packages/core/src/reporters/json.ts
  • packages/core/src/reporters/md.ts
  • packages/core/src/reporters/tty.ts
  • packages/core/src/rules/BaseRule.ts
  • packages/core/src/rules/annotations-missing-hints.ts
  • packages/core/src/rules/descriptions-missing.ts
  • packages/core/src/rules/descriptions-too-long.ts
  • packages/core/src/rules/descriptions-too-short.ts
  • packages/core/src/rules/design-client-directives.ts
  • packages/core/src/rules/design-confusable-params.ts
  • packages/core/src/rules/design-crud-mirror.ts
  • packages/core/src/rules/design-duplicate-leading-words.ts
  • packages/core/src/rules/design-enum-combination-unencoded.ts
  • packages/core/src/rules/design-enum-in-prose.ts
  • packages/core/src/rules/design-list-no-limit.ts
  • packages/core/src/rules/design-negative-guidance-present.ts
  • packages/core/src/rules/design-overlap-cluster.ts
  • packages/core/src/rules/docs.ts
  • packages/core/src/rules/index.ts
  • packages/core/src/rules/naming-convention.ts
  • packages/core/src/rules/schemas-complexity-budget.ts
  • packages/core/src/rules/schemas-loose.ts
  • packages/core/src/rules/schemas-param-desc-missing.ts
  • packages/core/src/rules/surface-token-budget.ts
  • packages/core/src/rules/surface-tool-budget.ts
  • packages/core/src/scoring.ts
  • packages/core/src/tokens.ts
  • packages/core/src/types.ts
  • packages/core/tests/core-api.test.ts
  • packages/core/tests/engine.test.ts
  • packages/core/tests/helpers.ts
  • packages/core/tests/report-golden.test.ts
  • packages/core/tests/rules-tier1.test.ts
  • packages/core/tests/rules-tier2.test.ts
  • packages/core/tsconfig.json
  • packages/core/tsup.config.ts
  • pnpm-workspace.yaml
  • src/rules/docs.ts
💤 Files with no reviewable changes (1)
  • src/rules/docs.ts
🛑 Comments failed to post (10)
apps/web/app/api/cron/purge/route.ts (1)

13-19: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Fail closed when CRON_SECRET is missing.

An unset secret currently bypasses authentication entirely, exposing the purge operation publicly.

Proposed fix
   const secret = process.env.CRON_SECRET;
-  if (secret && request.headers.get("authorization") !== `Bearer ${secret}`) {
+  if (!secret || request.headers.get("authorization") !== `Bearer ${secret}`) {
     return NextResponse.json({ error: "Unauthorized." }, { status: 401 });
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

export async function GET(request: Request): Promise<NextResponse> {
  // Vercel signs cron invocations; reject anything else so this is not a public
  // "delete a bunch of rows" button.
  const secret = process.env.CRON_SECRET;
  if (!secret || request.headers.get("authorization") !== `Bearer ${secret}`) {
    return NextResponse.json({ error: "Unauthorized." }, { status: 401 });
  }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/app/api/cron/purge/route.ts` around lines 13 - 19, Update the
authentication guard in GET so a missing CRON_SECRET is treated as unauthorized
rather than bypassing validation. Require both a configured secret and an exact
matching Bearer authorization header before continuing; otherwise return the
existing 401 Unauthorized response.
apps/web/app/api/interest/route.ts (1)

17-36: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Rate-limit this public database write.

A bot can submit unlimited distinct interest records, inflating storage and filling the database with unsolicited email addresses. Apply endpoint/IP rate limiting before the insert.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/app/api/interest/route.ts` around lines 17 - 36, Add
endpoint/IP-based rate limiting in the POST handler before the
getDb().insert(interest) call, rejecting requests that exceed the configured
limit with an appropriate throttling response. Ensure the limit applies to
public database writes while preserving validation, local no-database behavior,
and successful inserts for allowed requests.
apps/web/components/ShareControls.tsx (1)

19-32: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Prevent stuck loading states on fetch errors.

If a network error occurs, fetch will throw an exception. Because the state resets are not enclosed in a finally block, the component will fail to call setBusy(false), leaving the buttons permanently disabled for the session.

  • apps/web/components/ShareControls.tsx#L19-L32: wrap the fetch and success logic in a try...finally block to ensure setBusy(false) is always executed.
  • apps/web/components/ShareControls.tsx#L40-L46: wrap the deletion fetch similarly to guarantee the loading state is cleared on failure.
📍 Affects 1 file
  • apps/web/components/ShareControls.tsx#L19-L32 (this comment)
  • apps/web/components/ShareControls.tsx#L40-L46
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/components/ShareControls.tsx` around lines 19 - 32, Update toggle()
to wrap its fetch and success handling in try...finally, ensuring setBusy(false)
always runs when the request throws or completes. Apply the same try...finally
loading-state cleanup to the deletion fetch at
apps/web/components/ShareControls.tsx lines 40-46; both sites require direct
changes.
apps/web/lib/lint.ts (1)

50-97: 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

set -euo pipefail

echo "== locate symbols =="
rg -n "withTimeout|McpCapture\.fromHttp|class McpCapture|fromHttp\(|createGuardedFetch|AbortController|signal" apps/web lib . -g '!**/node_modules/**' -g '!**/dist/**' -g '!**/build/**' || true

echo "== file list candidates =="
fd -a "lint.ts|guarded-fetch|mcp.*capture|capture" apps/web lib . -t f || true

Repository: DLeibner/mcplint

Length of output: 3116


🏁 Script executed:

set -euo pipefail

# Read the target file and nearby definitions with line numbers.
echo "== apps/web/lib/lint.ts =="
wc -l apps/web/lib/lint.ts
sed -n '1,180p' apps/web/lib/lint.ts | cat -n

echo "== guarded fetch candidates =="
fd -a "guarded-fetch.ts|guarded-fetch.js|guarded-fetch.mjs" apps web . -t f || true

echo "== McpCapture definition candidates =="
fd -a "capture.ts|mcp*.ts|*.ts" apps web . -t f | rg "capture|mcp" || true

Repository: DLeibner/mcplint

Length of output: 4928


🏁 Script executed:

set -euo pipefail

python3 - <<'PY'
from pathlib import Path
for p in [Path('apps/web/lib/lint.ts')]:
    if p.exists():
        print(f"== {p} ==")
        for i, line in enumerate(p.read_text().splitlines(), 1):
            if 1 <= i <= 130:
                print(f"{i:4d}: {line}")
PY

echo "== symbol search =="
rg -n "fromHttp\s*\(|class McpCapture|createGuardedFetch\s*\(" apps web . -g '!**/node_modules/**' -g '!**/dist/**' -g '!**/build/**' || true

Repository: DLeibner/mcplint

Length of output: 5029


🏁 Script executed:

set -euo pipefail

echo "== mcp-capture.ts =="
wc -l packages/core/src/ingest/mcp-capture.ts
sed -n '1,220p' packages/core/src/ingest/mcp-capture.ts | cat -n

echo "== guarded-fetch.ts =="
wc -l apps/web/lib/guarded-fetch.ts
sed -n '1,220p' apps/web/lib/guarded-fetch.ts | cat -n

Repository: DLeibner/mcplint

Length of output: 8810


🏁 Script executed:

set -euo pipefail

echo "== packages/core/src/ingest/mcp-capture.ts =="
wc -l packages/core/src/ingest/mcp-capture.ts
sed -n '1,220p' packages/core/src/ingest/mcp-capture.ts | cat -n

echo "== apps/web/lib/guarded-fetch.ts =="
wc -l apps/web/lib/guarded-fetch.ts
sed -n '1,220p' apps/web/lib/guarded-fetch.ts | cat -n

Repository: DLeibner/mcplint

Length of output: 8848


Abort the capture when the timeout fires.

Promise.race only stops waiting; it doesn’t stop McpCapture.fromHttp(...), so timed-out requests keep running in the background and can still reject later. HttpCaptureOptions doesn’t expose an abort path today, so this needs a signal (or equivalent) threaded through McpCapture.fromHttp and into the HTTP transport.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/lib/lint.ts` around lines 50 - 97, Update withTimeout and the MCP
capture flow so timeout expiration actively aborts the in-flight request rather
than only rejecting Promise.race. Thread an AbortSignal (or equivalent) from
buildSnapshot through McpCapture.fromHttp into the HTTP transport, trigger it
when the timer fires, and preserve the existing LintError timeout behavior while
preventing later background rejection.
apps/web/lib/rate-limit.ts (1)

15-35: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Fail-open rate limiting is indistinguishable from a production misconfiguration.

If UPSTASH_REDIS_REST_URL/UPSTASH_REDIS_REST_TOKEN are absent for any reason in production (typo, unset env var, wrong environment scope), checkRateLimit silently returns { ok: true } for every request — exactly the amplification-abuse scenario the url mode limiter exists to prevent, per this file's own rationale. Nothing here signals that this happened.

🛡️ Proposed guard
 const configured =
   Boolean(process.env.UPSTASH_REDIS_REST_URL) && Boolean(process.env.UPSTASH_REDIS_REST_TOKEN);
+
+if (!configured && process.env.NODE_ENV === "production") {
+  console.error(
+    "Rate limiting is disabled in production: UPSTASH_REDIS_REST_URL/TOKEN are not set."
+  );
+}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

const configured =
  Boolean(process.env.UPSTASH_REDIS_REST_URL) && Boolean(process.env.UPSTASH_REDIS_REST_TOKEN);

if (!configured && process.env.NODE_ENV === "production") {
  console.error(
    "Rate limiting is disabled in production: UPSTASH_REDIS_REST_URL/TOKEN are not set."
  );
}

const redis = configured ? Redis.fromEnv() : undefined;

const limiters = redis
  ? {
      url: new Ratelimit({
        redis,
        limiter: Ratelimit.slidingWindow(10, "1 h"),
        prefix: "mcplint:url",
        analytics: true
      }),
      paste: new Ratelimit({
        redis,
        limiter: Ratelimit.slidingWindow(60, "1 h"),
        prefix: "mcplint:paste",
        analytics: true
      })
    }
  : undefined;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/lib/rate-limit.ts` around lines 15 - 35, Make the production path
distinguish missing Upstash configuration from intentional non-production
bypasses: update the configuration handling around configured, redis, and
checkRateLimit so absent UPSTASH_REDIS_REST_URL or UPSTASH_REDIS_REST_TOKEN in
production emits a clear misconfiguration signal and does not silently allow
every request. Preserve the existing limiter behavior when configuration is
present and the intended fail-open behavior outside production.
apps/web/lib/store.ts (2)

39-42: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Do not fall back to a public salt when persisting IP hashes.

If IP_HASH_SALT is omitted in production, hashes become predictable and linkable across deployments. Require a secret salt before hashing.

Proposed fix
 export function hashIp(ip: string): string {
-  const salt = process.env.IP_HASH_SALT ?? "mcplint-dev-salt";
+  const salt = process.env.IP_HASH_SALT;
+  if (!salt) throw new Error("IP_HASH_SALT is not set.");
   return createHash("sha256").update(`${salt}:${ip}`).digest("hex").slice(0, 32);
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

export function hashIp(ip: string): string {
  const salt = process.env.IP_HASH_SALT;
  if (!salt) throw new Error("IP_HASH_SALT is not set.");
  return createHash("sha256").update(`${salt}:${ip}`).digest("hex").slice(0, 32);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/lib/store.ts` around lines 39 - 42, Update hashIp to require
IP_HASH_SALT before hashing, removing the public development fallback; fail
clearly when the secret is missing so no IP hash is persisted with a predictable
salt.

126-151: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Inspect the declared Drizzle/Neon versions and existing atomic-write patterns.
fd -a 'package.json|pnpm-lock.yaml' . -x rg -n -C2 \
  '"drizzle-orm"|"`@neondatabase/serverless`"|drizzle-orm@|`@neondatabase/serverless`@' {}

rg -n -C3 --type=ts '\.(transaction|batch)\s*\(' .

Repository: DLeibner/mcplint

Length of output: 1378


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '== store.ts outline ==\n'
ast-grep outline apps/web/lib/store.ts --view expanded || true

printf '\n== relevant slice ==\n'
wc -l apps/web/lib/store.ts
sed -n '1,240p' apps/web/lib/store.ts | cat -n

printf '\n== transaction/batch usage ==\n'
rg -n -C 2 '\b(transaction|batch)\s*\(' apps web packages . --glob '!**/node_modules/**' --glob '!**/.next/**' || true

Repository: DLeibner/mcplint

Length of output: 10944


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '== apps/web/lib/store.ts ==\n'
sed -n '1,240p' apps/web/lib/store.ts | cat -n

printf '\n== drizzle / neon setup files ==\n'
fd -a 'db.ts|drizzle.ts|neon.ts|database.ts|postgres.ts' apps web . -t f | sed -n '1,40p'

printf '\n== transaction-like usage in repo ==\n'
rg -n -C 2 '\bdb\.(transaction|batch)\s*\(' apps web . --glob '!**/node_modules/**' --glob '!**/.next/**' || true

Repository: DLeibner/mcplint

Length of output: 8889


🌐 Web query:

Drizzle ORM Neon HTTP driver transaction support batch atomic writes @neondatabase/serverless 1.1.0

💡 Result:

The Drizzle ORM neon-http driver does not support interactive transactions (e.g., db.transaction(async (tx) => { ... })) because the underlying Neon HTTP protocol is stateless and does not maintain the session state required for interactive transactions [1][2][3]. Attempting to use the db.transaction() method with the neon-http driver will result in an error: "No transactions support in neon-http driver" [1][2][4]. However, the driver does support "batch" operations, which allow you to execute multiple queries in a single HTTP request [5][1]. While these are not ACID-compliant interactive transactions, they are atomic in the sense that they are sent to the server together in a single non-interactive batch [2]. You can use the db.batch() API for this purpose: const batchResponse = await db.batch([ db.insert(usersTable).values({ id: 1, name: 'John' }), db.update(usersTable).set({ name: 'Dan' }).where(eq(usersTable.id, 1)), ]); If you require full ACID transaction support, you should use the neon-websockets driver instead of neon-http, as it supports standard PostgreSQL transaction workflows [3]. Regarding the @neondatabase/serverless package compatibility, ensure you are using a version (e.g., 1.1.0) compatible with your Drizzle ORM version to avoid known issues with query execution syntax [6].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '== relevant files ==\n'
git ls-files | rg '^(apps/web/lib/db/(client|schema)\.ts|apps/web/package.json|pnpm-lock\.yaml|package\.json)$' || true

printf '\n== db client ==\n'
sed -n '1,220p' apps/web/lib/db/client.ts | cat -n

printf '\n== schema ==\n'
sed -n '1,240p' apps/web/lib/db/schema.ts | cat -n

printf '\n== package.json (root + apps/web) ==\n'
sed -n '1,220p' package.json | cat -n
printf '\n--- apps/web/package.json ---\n'
sed -n '1,220p' apps/web/package.json | cat -n

printf '\n== batch/transaction references ==\n'
rg -n -C 2 '\bdb\.(transaction|batch)\s*\(|\btransaction\s*\(|\bbatch\s*\(' apps/web . --glob '!**/node_modules/**' --glob '!**/.next/**' || true

Repository: DLeibner/mcplint

Length of output: 6998


Persist the run and finding-count rows in one batch. PostgresStore.create() does two separate inserts, so a runFindings failure leaves the runs row committed without its denormalized counts and breaks analytics. Use db.batch([...]) here.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/lib/store.ts` around lines 126 - 151, Update PostgresStore.create()
to wrap the runs insert and conditional runFindings insert in a single
db.batch([...]) operation, preserving the existing values and skipping the
findings insert when rows is empty so both writes commit atomically.
packages/core/src/rules/design-client-directives.ts (2)

24-28: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Match referenced tool names case-insensitively and at boundaries.

sentence.includes(name) misses casing differences and treats short names embedded in unrelated words as references. Normalize casing and use escaped token boundaries.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/src/rules/design-client-directives.ts` around lines 24 - 28,
Update isDirective to match referenced tool names case-insensitively and only as
whole tokens: normalize the sentence and each name consistently, escape names
for use in a regular expression, and apply explicit token boundaries instead of
sentence.includes(name). Preserve the existing DIRECTIVE_VERB and some-based
matching flow.

42-56: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Count duplication across distinct tools, not sentence occurrences.

If one description repeats the same directive twice, blockCounts reaches two and incorrectly reports it as duplicated across multiple tools. Deduplicate normalized keys within each tool before incrementing the counts.

Proposed fix
   for (const directives of perTool.values()) {
-    for (const sentence of directives) {
-      const key = BaseRule.words(sentence).join(" ");
+    const keys = new Set(directives.map((sentence) => BaseRule.words(sentence).join(" ")));
+    for (const key of keys) {
       blockCounts.set(key, (blockCounts.get(key) ?? 0) + 1);
     }
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

    const blockCounts = new Map<string, number>();
    for (const directives of perTool.values()) {
      const keys = new Set(
        directives.map((sentence) => BaseRule.words(sentence).join(" "))
      );
      for (const key of keys) {
        blockCounts.set(key, (blockCounts.get(key) ?? 0) + 1);
      }
    }

    const findings: Finding[] = [];
    for (const [toolName, directives] of perTool) {
      const block = directives.join(" ");
      const tokens = TokenCounter.count(block);
      const duplicated = directives.some(
        (s) => (blockCounts.get(BaseRule.words(s).join(" ")) ?? 0) >= 2
      );
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/src/rules/design-client-directives.ts` around lines 42 - 56,
Update blockCounts construction in the design-client directive analysis to count
each normalized directive key at most once per tool. Deduplicate the keys
derived from a tool’s directives before incrementing blockCounts, while
preserving the existing duplicated lookup and findings behavior.
packages/core/src/rules/schemas-param-desc-missing.ts (1)

19-21: 🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

Prevent null-pointer dereference on malformed schemas.

In JavaScript, typeof null === "object". If a third-party server supplies a malformed JSON schema where a property is explicitly null, this check will allow it through and prop.description will trigger a runtime TypeError, crashing the lint validation for the entire server.

Guard against null to ensure stability against invalid inputs.

🔒️ Proposed fix
         for (const [name, prop] of Object.entries(BaseRule.properties(schema))) {
-          if (typeof prop !== "object") continue;
+          if (typeof prop !== "object" || prop === null) continue;
           if (!prop.description && !prop.$ref) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

        for (const [name, prop] of Object.entries(BaseRule.properties(schema))) {
          if (typeof prop !== "object" || prop === null) continue;
          if (!prop.description && !prop.$ref) {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/src/rules/schemas-param-desc-missing.ts` around lines 19 - 21,
Update the property guard in the schema iteration within the rules-param
description validation to exclude null values before accessing prop.description
or prop.$ref. Preserve the existing behavior for non-object values and valid
object properties while preventing malformed null schema entries from reaching
those property accesses.

- Updated `.gitignore` to exclude test results and Playwright reports.
- Added `DEPLOYMENT.md` for deployment instructions and service authentication.
- Introduced GitHub Actions workflows for CI and publishing the CLI.
- Integrated Playwright for end-to-end testing and added related configurations.
- Enhanced README with installation and usage details for the MCP server.
- Implemented new API routes for handling MCP requests and report management.
- Added tests for the MCP API and report routes to ensure functionality.
- Improved styling and layout in the web application for better user experience.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (6)
.github/workflows/ci.yml (2)

22-23: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Set persist-credentials: false in the checkout step.

By default, actions/checkout persists the GitHub token in the local git configuration. To improve security and prevent malicious dependencies or test scripts from exfiltrating or using the token, it's a best practice to disable this.

🔒️ Proposed fix to prevent credential persistence
       - name: Check out repository
         uses: actions/checkout@v4
+        with:
+          persist-credentials: false
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ci.yml around lines 22 - 23, Update the “Check out
repository” actions/checkout@v4 step to set persist-credentials to false,
preventing the GitHub token from being stored in the local Git configuration.

Source: Linters/SAST tools


22-23: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Set persist-credentials: false in the checkout step.

By default, actions/checkout persists the GitHub token in the local git configuration. To improve security and prevent malicious dependencies or test scripts from exfiltrating or using the token, it's a best practice to disable this.

🔒️ Proposed fix to prevent credential persistence
       - name: Check out repository
         uses: actions/checkout@v4
+        with:
+          persist-credentials: false
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ci.yml around lines 22 - 23, Update the
actions/checkout@v4 step in the CI workflow to set persist-credentials to false,
preventing the GitHub token from being stored in the local Git configuration.

Source: Linters/SAST tools

.github/workflows/publish.yml (2)

15-15: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Set persist-credentials: false in the checkout step.

By default, actions/checkout persists the GitHub token in the local git configuration. To improve security during the build and publish steps, it is recommended to disable this.

🔒️ Proposed fix to prevent credential persistence
-      - uses: actions/checkout@v4
+      - uses: actions/checkout@v4
+        with:
+          persist-credentials: false
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/publish.yml at line 15, Update the actions/checkout@v4
step in the workflow to set persist-credentials to false, ensuring the checkout
action does not retain the GitHub token in local Git configuration.

Source: Linters/SAST tools


15-15: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Set persist-credentials: false in the checkout step.

By default, actions/checkout persists the GitHub token in the local git configuration. To improve security during the build and publish steps, it is recommended to disable this.

🔒️ Proposed fix to prevent credential persistence
-      - uses: actions/checkout@v4
+      - uses: actions/checkout@v4
+        with:
+          persist-credentials: false
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/publish.yml at line 15, Update the actions/checkout@v4
step in the workflow to set persist-credentials to false, ensuring the checkout
action does not retain the GitHub token in local Git configuration.

Source: Linters/SAST tools

apps/web/app/globals.css (2)

153-163: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Use modern clip-path for the visually hidden utility.

The clip CSS property is deprecated. It's recommended to use clip-path: inset(50%); instead, while keeping clip only as a legacy fallback if strictly needed (though modern browsers fully support clip-path).

♻️ Proposed fix
 .visually-hidden {
   position: absolute;
   width: 1px;
   height: 1px;
   padding: 0;
   margin: -1px;
   overflow: hidden;
-  clip: rect(0, 0, 0, 0);
+  clip-path: inset(50%);
   white-space: nowrap;
   border: 0;
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/app/globals.css` around lines 153 - 163, Update the .visually-hidden
utility to use the modern clip-path: inset(50%) declaration instead of the
deprecated clip property, retaining the existing accessibility-hiding styles and
only preserving clip as a legacy fallback if required.

Source: Linters/SAST tools


153-163: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Use modern clip-path for the visually hidden utility.

The clip CSS property is deprecated. It's recommended to use clip-path: inset(50%); instead, while keeping clip only as a legacy fallback if strictly needed (though modern browsers fully support clip-path).

♻️ Proposed fix
 .visually-hidden {
   position: absolute;
   width: 1px;
   height: 1px;
   padding: 0;
   margin: -1px;
   overflow: hidden;
-  clip: rect(0, 0, 0, 0);
+  clip-path: inset(50%);
   white-space: nowrap;
   border: 0;
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/app/globals.css` around lines 153 - 163, Update the .visually-hidden
utility to use modern clip-path with inset(50%) instead of relying on the
deprecated clip property; retain clip only if needed as a legacy fallback.

Source: Linters/SAST tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/web/app/api/report/`[id]/route.ts:
- Around line 10-14: Replace the dynamic RegExp in ownerToken with plain string
parsing to locate the mcplint_owner_${id} cookie and extract its value. Preserve
returning undefined when the cookie is absent, while avoiding regex evaluation
on attacker-controlled id and cookie header.

In `@apps/web/lib/mcp-server.ts`:
- Around line 73-105: Replace the `checkMcpServerInputSchema` `superRefine`
schema with a plain introspectable `z.object(...)` registration so clients
retain the url, headers, and snapshot metadata; enforce the
exactly-one-of-url-or-snapshot and headers-with-url constraints separately in
the tool’s runtime validation path.

In `@apps/web/lib/rate-limit.ts`:
- Around line 44-52: Update the missing-limiters guard in checkRateLimit so
every mode, including "paste", returns the existing not_configured failure
result when NODE_ENV is production. Preserve the unlimited success fallback for
non-production environments and keep the configured-limiters path unchanged.

---

Nitpick comments:
In @.github/workflows/ci.yml:
- Around line 22-23: Update the “Check out repository” actions/checkout@v4 step
to set persist-credentials to false, preventing the GitHub token from being
stored in the local Git configuration.
- Around line 22-23: Update the actions/checkout@v4 step in the CI workflow to
set persist-credentials to false, preventing the GitHub token from being stored
in the local Git configuration.

In @.github/workflows/publish.yml:
- Line 15: Update the actions/checkout@v4 step in the workflow to set
persist-credentials to false, ensuring the checkout action does not retain the
GitHub token in local Git configuration.
- Line 15: Update the actions/checkout@v4 step in the workflow to set
persist-credentials to false, ensuring the checkout action does not retain the
GitHub token in local Git configuration.

In `@apps/web/app/globals.css`:
- Around line 153-163: Update the .visually-hidden utility to use the modern
clip-path: inset(50%) declaration instead of the deprecated clip property,
retaining the existing accessibility-hiding styles and only preserving clip as a
legacy fallback if required.
- Around line 153-163: Update the .visually-hidden utility to use modern
clip-path with inset(50%) instead of relying on the deprecated clip property;
retain clip only if needed as a legacy fallback.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 79c9df27-19c6-4a56-a3b0-ccaefecda50b

📥 Commits

Reviewing files that changed from the base of the PR and between 54ffcc4 and 3513271.

⛔ Files ignored due to path filters (2)
  • packages/core/tests/__snapshots__/report-golden.test.ts.snap is excluded by !**/*.snap
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (38)
  • .github/workflows/ci.yml
  • .github/workflows/publish.yml
  • .gitignore
  • DEPLOYMENT.md
  • README.md
  • apps/web/.env.example
  • apps/web/app/api/cron/purge/route.ts
  • apps/web/app/api/lint/route.ts
  • apps/web/app/api/mcp/route.test.ts
  • apps/web/app/api/mcp/route.ts
  • apps/web/app/api/report/[id]/route.test.ts
  • apps/web/app/api/report/[id]/route.ts
  • apps/web/app/globals.css
  • apps/web/app/install/page.tsx
  • apps/web/app/layout.tsx
  • apps/web/app/page.tsx
  • apps/web/app/r/[id]/page.tsx
  • apps/web/components/InstallTabs.tsx
  • apps/web/components/LintForm.tsx
  • apps/web/components/ShareControls.tsx
  • apps/web/e2e/playground.spec.ts
  • apps/web/lib/analytics.ts
  • apps/web/lib/guarded-fetch.ts
  • apps/web/lib/install-links.test.ts
  • apps/web/lib/install-links.ts
  • apps/web/lib/lint.ts
  • apps/web/lib/mcp-server.ts
  • apps/web/lib/rate-limit.ts
  • apps/web/lib/server-metadata.test.ts
  • apps/web/lib/site.ts
  • apps/web/lib/store.ts
  • apps/web/next.config.ts
  • apps/web/package.json
  • apps/web/playwright.config.ts
  • apps/web/vitest.config.ts
  • packages/core/src/rules/docs.ts
  • packages/core/tests/core-api.test.ts
  • server.json
💤 Files with no reviewable changes (1)
  • apps/web/lib/lint.ts
🚧 Files skipped from review as they are similar to previous changes (13)
  • apps/web/app/layout.tsx
  • apps/web/.env.example
  • apps/web/package.json
  • apps/web/app/api/cron/purge/route.ts
  • apps/web/app/r/[id]/page.tsx
  • packages/core/tests/core-api.test.ts
  • .gitignore
  • README.md
  • apps/web/app/api/lint/route.ts
  • apps/web/lib/guarded-fetch.ts
  • apps/web/components/ShareControls.tsx
  • apps/web/lib/store.ts
  • packages/core/src/rules/docs.ts

Comment thread apps/web/app/api/report/[id]/route.ts
Comment thread apps/web/lib/mcp-server.ts Outdated
Comment thread apps/web/lib/rate-limit.ts
DLeibner and others added 4 commits July 20, 2026 14:50
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
- Added `workspaces-update=false` to `.npmrc` to prevent npm's workspace tree update.
- Revised deployment steps in `DEPLOYMENT.md` for clarity and accuracy regarding version checks and artifact validation.
- Updated `package.json` to include workspaces configuration.
- Modified GitHub Actions workflow to validate and publish release artifacts correctly.
- Removed obsolete `sync-version.mjs` script as version synchronization is now handled differently.
- Adjusted tests to ensure version validation aligns with the new structure.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
.github/workflows/release.yml (1)

85-101: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicated mcp-publisher download/checksum/extract logic across jobs.

The download-verify-extract sequence for mcp-publisher (curl, sha256sum check, tar extract) is duplicated between the verify job and publish-registry job. Since both reference the same pinned version/checksum, extracting this into a small composite action would reduce drift risk if the pin is ever updated in only one place.

Also applies to: 250-259

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/release.yml around lines 85 - 101, Extract the repeated
mcp-publisher setup sequence—curl download, sha256sum verification, and tar
extraction—from the verify and publish-registry jobs into a small composite
action. Have both jobs invoke that action while preserving the shared pinned
version, checksum, archive location, and extracted binary behavior, so future
pin updates occur in one place.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/release.yml:
- Around line 30-33: Update both checkout steps in the release workflow to set
persist-credentials to false alongside fetch-depth, ensuring the checkout action
does not retain the GITHUB_TOKEN in Git configuration for either job.

---

Nitpick comments:
In @.github/workflows/release.yml:
- Around line 85-101: Extract the repeated mcp-publisher setup sequence—curl
download, sha256sum verification, and tar extraction—from the verify and
publish-registry jobs into a small composite action. Have both jobs invoke that
action while preserving the shared pinned version, checksum, archive location,
and extracted binary behavior, so future pin updates occur in one place.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c256d54f-9098-4c54-a1e1-34de89b13c62

📥 Commits

Reviewing files that changed from the base of the PR and between 3513271 and 416ab8f.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (8)
  • .github/workflows/release.yml
  • .npmrc
  • DEPLOYMENT.md
  • apps/web/lib/server-metadata.test.ts
  • apps/web/package.json
  • package.json
  • packages/core/package.json
  • scripts/smoke-production.mjs
🚧 Files skipped from review as they are similar to previous changes (4)
  • apps/web/lib/server-metadata.test.ts
  • apps/web/package.json
  • package.json
  • packages/core/package.json

Comment thread .github/workflows/release.yml
DLeibner added 2 commits July 23, 2026 09:10
- Updated `.npmrc` to set `tag-version-prefix` to an empty string for bare semver tags.
- Revised `DEPLOYMENT.md` to reflect changes in version tagging and installation commands.
- Added `package-lock.json` for npm dependency management and removed `pnpm-lock.yaml`.
- Modified `package.json` scripts to utilize npm commands instead of pnpm.
- Updated CI workflows to use npm for dependency installation and build processes.
- Adjusted README to guide users on using npm for development and testing.
…ing checkout

- Modified `.github/workflows/ci.yml` and `.github/workflows/release.yml` to set `persist-credentials: false` for the checkout action, enhancing security by preventing GitHub Actions from persisting credentials in the environment.
- Updated error messages in `apps/web/lib/mcp-server.ts` and `apps/web/app/api/lint/route.ts` for clarity.
- Refactored cookie handling in `apps/web/app/api/report/[id]/route.ts` for improved readability and performance.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
DEPLOYMENT.md (1)

153-161: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Make the mcp-publisher pin reproducible.

These lines claim a checksum-verified v1.8.0 binary but provide no download source, platform artifact, checksum, or verification command. Add those steps, or reference the workflow’s pinned installation logic, so operators cannot accidentally run an unverified binary. The official Registry documents GitHub OIDC authentication, while upstream releases expose platform-specific checksums. (modelcontextprotocol.io)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@DEPLOYMENT.md` around lines 153 - 161, Update the mcp-publisher v1.8.0
deployment instructions to include a reproducible, checksum-verified
installation: specify the official download source, platform-specific artifact,
expected checksum, and verification command, or explicitly reference the
workflow’s existing pinned installation logic. Keep the validate, login
github-oidc, and publish commands unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@DEPLOYMENT.md`:
- Line 207: Update the npm version example in DEPLOYMENT.md by replacing the
pipe-separated alternatives for version type and workspace target with separate,
independently copyable shell command examples. Preserve the documented minor,
major, `@mcplint/web`, and mcplint options without using shell pipe syntax.

---

Nitpick comments:
In `@DEPLOYMENT.md`:
- Around line 153-161: Update the mcp-publisher v1.8.0 deployment instructions
to include a reproducible, checksum-verified installation: specify the official
download source, platform-specific artifact, expected checksum, and verification
command, or explicitly reference the workflow’s existing pinned installation
logic. Keep the validate, login github-oidc, and publish commands unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 262c6a43-a04d-4b30-90b6-2a7169adbc43

📥 Commits

Reviewing files that changed from the base of the PR and between 416ab8f and 982a612.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (14)
  • .claude/launch.json
  • .github/workflows/ci.yml
  • .github/workflows/release.yml
  • .npmrc
  • DEPLOYMENT.md
  • README.md
  • apps/web/app/api/lint/route.ts
  • apps/web/app/api/report/[id]/route.ts
  • apps/web/lib/mcp-server.ts
  • apps/web/lib/rate-limit.ts
  • apps/web/lib/store.ts
  • apps/web/package.json
  • apps/web/playwright.config.ts
  • package.json
🚧 Files skipped from review as they are similar to previous changes (10)
  • .claude/launch.json
  • .npmrc
  • apps/web/package.json
  • apps/web/playwright.config.ts
  • apps/web/lib/rate-limit.ts
  • apps/web/lib/mcp-server.ts
  • README.md
  • apps/web/lib/store.ts
  • apps/web/app/api/report/[id]/route.ts
  • apps/web/app/api/lint/route.ts

Comment thread DEPLOYMENT.md
```bash
npm version patch -w @mcplint/web --include-workspace-root
# or: npm version patch -w mcplint --include-workspace-root
# or: npm version minor|major -w @mcplint/web|mcplint --include-workspace-root

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Replace the pipe characters with separate shell examples.

minor|major and @mcplint/web|mcplint are parsed as shell pipelines, not alternatives, so copying this command will fail or execute unintended commands.

Proposed fix
- npm version minor|major -w `@mcplint/web`|mcplint --include-workspace-root
+ npm version minor -w `@mcplint/web` --include-workspace-root
+ npm version major -w `@mcplint/web` --include-workspace-root
+ npm version minor -w mcplint --include-workspace-root
+ npm version major -w mcplint --include-workspace-root
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# or: npm version minor|major -w @mcplint/web|mcplint --include-workspace-root
# or: npm version minor -w `@mcplint/web` --include-workspace-root
# or: npm version major -w `@mcplint/web` --include-workspace-root
# or: npm version minor -w mcplint --include-workspace-root
# or: npm version major -w mcplint --include-workspace-root
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@DEPLOYMENT.md` at line 207, Update the npm version example in DEPLOYMENT.md
by replacing the pipe-separated alternatives for version type and workspace
target with separate, independently copyable shell command examples. Preserve
the documented minor, major, `@mcplint/web`, and mcplint options without using
shell pipe syntax.

@DLeibner
DLeibner merged commit 54d8a12 into main Jul 23, 2026
3 checks passed
@DLeibner
DLeibner deleted the mcp-playground-web branch July 23, 2026 07:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant