Skip to content

feat(apify_mcp): add Apify MCP plugin - #384

Merged
devjain32 merged 8 commits into
corsairdev:mainfrom
Ayush7614:feat/apify_mcp-plugin
Aug 12, 2026
Merged

feat(apify_mcp): add Apify MCP plugin#384
devjain32 merged 8 commits into
corsairdev:mainfrom
Ayush7614:feat/apify_mcp-plugin

Conversation

@Ayush7614

@Ayush7614 Ayush7614 commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Description

  • Adds packages/apify_mcp/ with all 8 OSS operations wrapping the hosted Apify MCP server at https://mcp.apify.com
  • Auth: Apify API token via api_key (Authorization: Bearer header)
  • Caching: selective ctx.db upserts for actors, actorRuns, and actorOutputs (Slack pattern)
  • Registers plugin in BaseProviders / ProviderDisplayNames

Operations

Group Endpoint MCP tool
actors searchActors search-actors
actors fetchActorDetails fetch-actor-details
actors callActor call-actor
actors ragWebBrowser apify/rag-web-browser
runs getActorRun get-actor-run
runs getActorOutput get-actor-output
docs searchApifyDocs search-apify-docs
docs fetchApifyDocs fetch-apify-docs

Test plan

Screenshots / Demos (if applicable)

Screenshot 2026-08-10 at 3 24 48 PM

Closes #383

Summary by CodeRabbit

  • New Features

    • Added Apify as a supported provider.
    • Added Apify actor discovery, actor details, actor execution, web browsing, run status, and dataset output capabilities.
    • Added Apify documentation search and retrieval.
    • Added API-key authentication, response caching, validation, and structured error handling.
    • Added the Apify integration package and public endpoint schemas.
  • Tests

    • Added unit tests and optional live API coverage for Apify workflows.

Implements the 8 OSS operations via the hosted Apify MCP server with API
token auth, selective DB caching for actors/runs/outputs, and live tests.

Closes corsairdev#383
@vercel

vercel Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

@Ayush7614 is attempting to deploy a commit to the corsair Team on Vercel.

A member of the Team first needs to authorize it.

@github-actions github-actions Bot added the core Changes in packages/corsair label Jul 6, 2026
@greptile-apps

greptile-apps Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds the apifymcp plugin — a Corsair integration wrapping the hosted Apify MCP server at https://mcp.apify.com — following the standard plugin scaffold pattern and correctly registering the provider in constants.ts.

  • 8 endpoints across 3 groups (actors, runs, docs) with Zod-validated inputs, per-endpoint riskLevel metadata, and selective caching of actors, actor runs, and dataset outputs; fetchApifyDocs includes an HTTPS-only URL allowlist guard.
  • Auth handling uses requireAuth: true for write/paid operations (callActor, ragWebBrowser, getActorRun, getActorOutput) with an early AuthMissingError throw, while read/discovery endpoints pass through unauthenticated.
  • Tests include an always-on guards suite (AuthMissingError + URL validation) and an opt-in live suite (all 8 endpoints, caching assertions, chained run→output scenario).

Confidence Score: 5/5

  • The change is a self-contained new plugin; no existing functionality is modified beyond adding three lines to constants.ts.
  • All 8 endpoints are implemented, tested, and wired to the Corsair plugin system correctly. Auth guards, Zod input validation, error handlers (with Retry-After support), and selective caching all follow the established patterns. The only findings are a few unknown type annotations in client.ts that are missing the required explanatory comments — a style concern that doesn't affect runtime behavior.
  • No files require special attention. The one style note is confined to a few function signatures in packages/apifymcp/client.ts.

Important Files Changed

Filename Overview
packages/apifymcp/client.ts Implements the Streamable HTTP MCP client with per-request connection lifecycle (documented as intentional), Retry-After parsing, and result normalization. Type assertions carry justification comments (addressed in prior review) except the unknown type annotations on function signatures, which still lack required explanatory comments per repo rule.
packages/apifymcp/endpoints/shared.ts Central dispatch layer: requireAuth guard is present and correct, unknown cache-helper parameters now carry required justification comments, and partial-dataset detection logic is well-tested. Caching silently skips when entity IDs are absent, which is safe but undocumented for the fallback path.
packages/apifymcp/endpoints/types.ts All 8 endpoint input schemas are well-typed with Zod; output schemas use z.unknown() with a justification comment. FetchApifyDocsInputSchema has a well-designed allowlist refine guard restricting URLs to docs.apify.com and crawlee.dev.
packages/apifymcp/error-handlers.ts Covers RATE_LIMIT (429, 3 retries with exponential backoff), AUTH (401, 0 retries), NOT_FOUND (404, 0 retries), and SERVER_ERROR (≥500, 2 retries). Primary status-based matching is correct; message-based fallback is a narrow safety net.
packages/apifymcp/api.test.ts Guards suite (always-on) covers AuthMissingError for 4 auth-required endpoints and URL allowlist validation. Live suite (opt-in via APIFY_LIVE_TESTS=1) covers all 8 endpoints including caching assertions and a chained callActor → getActorRun → getActorOutput scenario.
packages/apifymcp/index.ts Plugin factory with full Corsair integration: endpoint schemas, meta, authConfig, keyBuilder, and errorHandlers are all wired correctly. All 8 endpoints registered across 3 groups (actors, runs, docs).
packages/apifymcp/schema/database.ts Three DB schemas (ApifyMcpActor, ApifyMcpActorRun, ApifyMcpActorOutput) with all fields optional to accommodate varying MCP response shapes. output: z.unknown() carries a justification comment per repo rule.
packages/corsair/core/constants.ts Correctly registers apifymcp in BaseProviders, ProviderDisplayNames, and AllProviders in alphabetical order.

Sequence Diagram

sequenceDiagram
    participant Caller
    participant Endpoint as actors/runs/docs endpoint
    participant Shared as executeApifyMcpTool
    participant Client as callApifyMcpTool
    participant MCP as Apify MCP Server<br/>(mcp.apify.com)
    participant DB as ctx.db

    Caller->>Endpoint: call(ctx, input)
    Endpoint->>Shared: executeApifyMcpTool(ctx, eventName, toolName, args, options)
    alt "requireAuth && !ctx.key"
        Shared-->>Caller: throw AuthMissingError
    end
    Shared->>Client: callApifyMcpTool(toolName, args, ctx.key)
    Client->>MCP: "connect (StreamableHTTP)<br/>Authorization: Bearer {apiKey}"
    Client->>MCP: "callTool({ name, arguments })"
    MCP-->>Client: CallToolResult
    Client->>Client: normalizeToolResult → parseToolResult
    alt result.isError
        Client-->>Shared: throw ApifyMcpAPIError
    end
    Client-->>Shared: T (parsed payload)
    Client->>MCP: close session
    alt "cache === 'actors'"
        Shared->>DB: actors.upsertByEntityId(entityId, item)
    else "cache === 'actorRun'"
        Shared->>DB: actorRuns.upsertByEntityId(runId, payload)
    else "cache === 'actorOutput' && !isPartial"
        Shared->>DB: "actorOutputs.upsertByEntityId(datasetId, { output })"
    end
    Shared->>Shared: "logEventFromContext(completed|failed)"
    Shared-->>Caller: T
Loading

Reviews (5): Last reviewed commit: "fix(apifymcp): harden cache, docs URL, a..." | Re-trigger Greptile

Comment thread packages/apify/endpoints/shared.ts
Comment thread packages/apify/error-handlers.ts
Comment thread packages/apify/client.ts
Comment thread packages/apify/endpoints/shared.ts
Comment thread packages/apifymcp/client.ts Outdated
Enforce requireAuth fail-fast via AuthMissingError, add unknown/assertion
comments per plugin conventions, use instanceof in error handlers, and gate
live tests on APIFY_TOKEN.
@Ayush7614

Copy link
Copy Markdown
Contributor Author

@greptileai

get-actor-output was removed upstream; map getActorOutput to
get-dataset-items, fix actor cache key collisions, and harden live tests.
@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 2a5836cb-a87b-4703-b99f-dd97503e8c58

📥 Commits

Reviewing files that changed from the base of the PR and between 2321177 and 7a8acaf.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (17)
  • packages/apify/api.test.ts
  • packages/apify/client.ts
  • packages/apify/endpoints/actors.ts
  • packages/apify/endpoints/docs.ts
  • packages/apify/endpoints/index.ts
  • packages/apify/endpoints/runs.ts
  • packages/apify/endpoints/shared.ts
  • packages/apify/endpoints/types.ts
  • packages/apify/error-handlers.ts
  • packages/apify/index.ts
  • packages/apify/jest.config.cjs
  • packages/apify/package.json
  • packages/apify/schema/database.ts
  • packages/apify/schema/index.ts
  • packages/apify/tsconfig.json
  • packages/apify/tsup.config.ts
  • packages/corsair/core/constants.ts
💤 Files with no reviewable changes (1)
  • packages/apify/jest.config.cjs

📝 Walkthrough

Walkthrough

The PR adds the @corsair-dev/apify plugin. It wraps Apify MCP tools for actor discovery, execution, run management, dataset retrieval, and documentation access. It adds validation, caching, authentication, error handling, tests, package configuration, and provider registration.

Changes

Apify MCP plugin

Layer / File(s) Summary
Endpoint contracts and storage schema
packages/apify/endpoints/types.ts, packages/apify/schema/*
Adds Zod input and output schemas, inferred endpoint types, documentation URL restrictions, and database schemas for actors, runs, and outputs.
MCP client and error classification
packages/apify/client.ts, packages/apify/error-handlers.ts
Adds Streamable HTTP MCP calls, bearer-token support, response normalization, ApifyMcpAPIError, retry metadata, and retry policies.
Endpoint execution and caching
packages/apify/endpoints/*
Adds actor, run, output, and documentation endpoints. Shared execution performs authentication checks, MCP calls, cache updates, and event logging.
Plugin factory and package integration
packages/apify/index.ts, packages/apify/package.json, packages/apify/jest.config.cjs, packages/apify/tsconfig.json, packages/apify/tsup.config.ts, packages/corsair/core/constants.ts
Adds plugin wiring, endpoint metadata, API-key resolution, package tooling, build configuration, and the apify provider registration.
Integration validation
packages/apify/api.test.ts
Adds guarded tests for authentication and URL validation, plus opt-in live tests for responses, caching, event logging, actor execution, documentation, runs, and outputs.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant ApifyPlugin
  participant ApifyMCP
  participant CorsairDatabase
  participant CorsairEventLog
  Caller->>ApifyPlugin: Invoke typed endpoint
  ApifyPlugin->>ApifyMCP: Send MCP tool request
  ApifyMCP-->>ApifyPlugin: Return tool response
  ApifyPlugin->>CorsairDatabase: Cache recognized entities
  ApifyPlugin->>CorsairEventLog: Record endpoint result
  ApifyPlugin-->>Caller: Return normalized response
Loading

Possibly related PRs

Suggested labels: plugin

Suggested reviewers: devjain32

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the main change: adding the Apify MCP plugin.
Linked Issues check ✅ Passed The implementation covers all eight requested tools, hosted MCP transport, bearer authentication, provider registration, caching, and tests.
Out of Scope Changes check ✅ Passed The changes support the Apify MCP plugin objectives and show no unrelated implementation work.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

Tools execution failed with the following error:

Failed to run tools: 13 INTERNAL: Received RST_STREAM with code 0 (Call ended without gRPC status)


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

Resolve constants.ts (keep apify_mcp + apilabz) and regenerate lockfile.
@Dhirenderchoudhary

Copy link
Copy Markdown
Collaborator

@greptile review

@github-actions

github-actions Bot commented Aug 10, 2026

Copy link
Copy Markdown

Plugin PR scorecard — packages/apifymcp

Check Status Notes
R1 — Scope: plugin files only
R2 — Tests with assertions
R3 — Description complete
R3 — Linked issue / claim
R4 — Demo video / recording

Rules: PLUGIN_PR_RULES.md · re-runs on every push

@github-actions

Copy link
Copy Markdown

Hey @Ayush7614, thanks for the contribution! 🏴‍☠️ Before a maintainer reviews, please fix the items below — the review re-runs automatically on your next push.

Must fix

  • P1 packages/apify_mcp/endpoints/shared.ts:136requireAuth option is declared but never checked
    The options parameter accepts requireAuth?: boolean, and four callers (callActor, ragWebBrowser, getActorRun, getActorOutput) pass requireAuth: true, but executeApifyMcpTool never reads that field. The call to callApifyMcpTool always proceeds with ctx.key || undefined regardless, meaning there is no fail-fast path when auth is absent. A caller reading actors.ts would reasonably expect that { requireAuth: true } produces an early AuthMissingError, but that never happens — the call goes out unauthenticated and returns a 401 from the Apify server instead.
Optional improvements (P2)
  • P2 packages/apify_mcp/error-handlers.ts:16Type assertions cast Error to Partial<ApifyMcpAPIError> without a comment explaining why `instanceof ApifyMcpAPIErro

// Using type assertion instead of instanceof so the helpers work on any Error,
// allowing callers to check status/retryAfter before narrowing to ApifyMcpAPIError.
function getStatus(error: Error): number | undefined {
return (error as Partial).status;
}

// See above — same rationale for the Partial cast.
function getRetryAfter(error: Error): number | undefined {
return (error as Partial).retryAfter;
}


**Rule Used:** What: Type assertions in the plugins folder must b... ([source](https://app.greptile.com/corsair/-/custom-context?memory=ca37b488-9cdd-4901-bfb3-3bc1158ad62b))

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
- **P2** `packages/apify_mcp/client.ts:42` — **Two type assertions in `client.ts` lack the justification comments required for plugin code. The `as CallToolResult` cas**
  ```suggestion
	if (
		typeof result === 'object' &&
		result !== null &&
		'content' in result &&
		// Asserting CallToolResult after confirming the required `content` array is present;
		// a full type guard would need to validate every optional field which isn't worth the churn.
		Array.isArray((result as CallToolResult).content)
	) {
		return result as CallToolResult;
	}

Rule Used: What: Type assertions in the plugins folder must b... (source)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

  • P2 packages/apify_mcp/endpoints/shared.ts:19The three cache helpers take response: unknown with no comment explaining the necessity of the loose type, which viola

// unknown here because the Apify MCP response shape varies by tool and Actor;
// callers pass the raw parsed result which is narrowed inside the function.
async function cacheActors(ctx: ApifyMcpContext, response: unknown) {


**Rule Used:** What: All uses of `any` or `unknown` must be accom... ([source](https://app.greptile.com/corsair/github/corsairdev/corsair/-/custom-context?memory=5bb3208e-7b6b-42fc-b4a5-fa869401c2c8))

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
- **P2** `packages/apify_mcp/client.ts:126` — **New MCP client and transport created on every call**
  `callApifyMcpTool` constructs a fresh `StreamableHTTPClientTransport` and `Client`, runs `client.connect(transport)`, makes one tool call, then tears everything down in `finally`. Under concurrent load (or burst usage via `ragWebBrowser`/`callActor`) this creates a new HTTP connection for every individual request with no pooling or reuse. Consider a module-level singleton client keyed by API token, or at minimum document that this is an intentional simplicity trade-off.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
</details>

_If anything remains after your next push, a bot commit will clean it up; a maintainer always does the final review and merge._

@github-actions github-actions Bot added the bot:round-1 Review bot posted consolidated findings label Aug 10, 2026
@Dhirenderchoudhary

Copy link
Copy Markdown
Collaborator

@greptile review

@github-actions github-actions Bot added the bot:round-2 Review bot pushed an automated fix label Aug 10, 2026
@github-actions

Copy link
Copy Markdown

Remaining findings are being fixed by a bot commit — it will be re-reviewed automatically.

@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: 4

🧹 Nitpick comments (4)
packages/apifymcp/jest.config.cjs (1)

56-57: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

forceExit: true hides open handles from the MCP transport.

The comment states that Streamable HTTP sockets stay open after client.close(). forceExit suppresses the symptom in tests, but the same unreleased transport can leak sockets in production. Run Jest with --detectOpenHandles once and confirm that the client tears down its transport, then keep forceExit only if the remaining handle comes from the SDK.

🤖 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/apifymcp/jest.config.cjs` around lines 56 - 57, Investigate the
open-handle source around the Jest configuration and MCP client teardown by
running the test suite with --detectOpenHandles; ensure the client explicitly
closes its Streamable HTTP transport after client.close(). Retain forceExit only
if the remaining handle is confirmed to originate from the SDK, otherwise remove
it.
packages/apifymcp/api.test.ts (2)

59-92: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extend the auth-guard coverage to the remaining endpoints.

The guard block covers callActor, ragWebBrowser, getActorRun, and getActorOutput. It omits searchActors, fetchActorDetails, searchApifyDocs, and fetchApifyDocs. These endpoints run without a token in the live block only, so a missing auth check on them stays untested. Add the four remaining cases, or drive all eight through a table-driven test.

🤖 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/apifymcp/api.test.ts` around lines 59 - 92, Extend the “Apify MCP
endpoint guards” tests to cover searchActors, fetchActorDetails,
searchApifyDocs, and fetchApifyDocs, asserting each rejects with
AuthMissingError when createCtx() has no API key. Keep the existing four
endpoint checks intact, or consolidate all eight cases into a table-driven test
using each endpoint’s required input shape.

94-98: 📐 Maintainability & Code Quality | 🔵 Trivial

Live tests call the real Apify platform and consume paid compute.

callActor and ragWebBrowser start real actor runs. The runs block starts a second run. Each execution consumes Apify compute units on the account that owns the token. Restrict these tests to an explicit opt-in job rather than any environment that exposes APIFY_TOKEN, or record fixtures for the default test run.

🤖 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/apifymcp/api.test.ts` around lines 94 - 98, Restrict the live tests
in the “Apify MCP API Type Tests” suite to an explicit opt-in job instead of
enabling them whenever APIFY_TOKEN is present. Update the suite’s gating
configuration while preserving the existing tests for intentional live-test
runs, or replace the default execution with recorded fixtures.
packages/apifymcp/tsconfig.json (1)

17-19: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Exclude test files from the declaration build.

api.test.ts is included by include: ["./**/*"]. Since the build runs tsc --build with emitDeclarationOnly, it can emit test declarations and fail on test type errors. Add "**/*.test.ts" to exclude.

references: [] matches the sibling plugin configurations and does not require a change.

🤖 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/apifymcp/tsconfig.json` around lines 17 - 19, Add "**/*.test.ts" to
the exclude list in the TypeScript configuration so declaration builds omit test
files such as api.test.ts; preserve the existing dist and node_modules
exclusions and leave references 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 `@packages/apifymcp/api.test.ts`:
- Around line 7-13: Update the test setup to use Jest’s ESM mocking: replace the
jest.mock/requireActual block with jest.unstable_mockModule for corsair/core,
then dynamically import both corsair/core and the system under test after
registering the mock. Ensure the Jest invocation, including CI’s test command,
enables --experimental-vm-modules.

In `@packages/apifymcp/client.ts`:
- Around line 10-30: Update ApifyMcpAPIError and the MCP transport flow so
Retry-After is read from the HTTP response headers via a custom fetch or
transport wrapper, parsed into a delay value, and passed through the error
options to assign retryAfter. Ensure getRetryAfter() returns this parsed value
while preserving existing StreamableHTTPError status and body handling.

In `@packages/apifymcp/endpoints/shared.ts`:
- Around line 84-131: The cacheActorOutput flow currently keys all actor-output
responses only by datasetId, allowing distinct limit, offset, or fields queries
to overwrite one another. Update executeApifyMcpTool and cacheActorOutput so
only canonical complete snapshots are cached, or derive a normalized
query-specific cache identity and persist the associated query metadata; ensure
partial and filtered getActorOutput responses cannot be stored under the plain
dataset ID.

In `@packages/apifymcp/endpoints/types.ts`:
- Around line 67-72: Add the same explicit documentation-host allowlist used by
the hosted fetch-apify-docs tool to FetchApifyDocsInputSchema, applying it after
the existing URL validation so only approved Apify or Crawlee documentation
hosts are accepted while preserving HTTPS URL parsing.

---

Nitpick comments:
In `@packages/apifymcp/api.test.ts`:
- Around line 59-92: Extend the “Apify MCP endpoint guards” tests to cover
searchActors, fetchActorDetails, searchApifyDocs, and fetchApifyDocs, asserting
each rejects with AuthMissingError when createCtx() has no API key. Keep the
existing four endpoint checks intact, or consolidate all eight cases into a
table-driven test using each endpoint’s required input shape.
- Around line 94-98: Restrict the live tests in the “Apify MCP API Type Tests”
suite to an explicit opt-in job instead of enabling them whenever APIFY_TOKEN is
present. Update the suite’s gating configuration while preserving the existing
tests for intentional live-test runs, or replace the default execution with
recorded fixtures.

In `@packages/apifymcp/jest.config.cjs`:
- Around line 56-57: Investigate the open-handle source around the Jest
configuration and MCP client teardown by running the test suite with
--detectOpenHandles; ensure the client explicitly closes its Streamable HTTP
transport after client.close(). Retain forceExit only if the remaining handle is
confirmed to originate from the SDK, otherwise remove it.

In `@packages/apifymcp/tsconfig.json`:
- Around line 17-19: Add "**/*.test.ts" to the exclude list in the TypeScript
configuration so declaration builds omit test files such as api.test.ts;
preserve the existing dist and node_modules exclusions and leave references
unchanged.
🪄 Autofix

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: 3e8ec87e-d9d2-4387-baaa-d61bab88513f

📥 Commits

Reviewing files that changed from the base of the PR and between 99ade55 and 2321177.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (17)
  • packages/apifymcp/api.test.ts
  • packages/apifymcp/client.ts
  • packages/apifymcp/endpoints/actors.ts
  • packages/apifymcp/endpoints/docs.ts
  • packages/apifymcp/endpoints/index.ts
  • packages/apifymcp/endpoints/runs.ts
  • packages/apifymcp/endpoints/shared.ts
  • packages/apifymcp/endpoints/types.ts
  • packages/apifymcp/error-handlers.ts
  • packages/apifymcp/index.ts
  • packages/apifymcp/jest.config.cjs
  • packages/apifymcp/package.json
  • packages/apifymcp/schema/database.ts
  • packages/apifymcp/schema/index.ts
  • packages/apifymcp/tsconfig.json
  • packages/apifymcp/tsup.config.ts
  • packages/corsair/core/constants.ts

Comment on lines +7 to +13
jest.mock('corsair/core', () => {
const original = jest.requireActual('corsair/core');
return {
...original,
logEventFromContext: jest.fn().mockResolvedValue(null),
};
});

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

🧩 Analysis chain

🌐 Web query:

Jest 29 jest.mock hoisting not supported native ESM unstable_mockModule ts-jest useESM

💡 Result:

In Jest 29 and later, the standard jest.mock hoisting mechanism, which works by moving calls to the top of the file during compilation, is not supported for native ECMAScript Modules (ESM) [1][2][3]. This is because ESM evaluates static import statements before the script code itself is executed [1][4]. To mock modules in an ESM environment, you must use the jest.unstable_mockModule API [1][5]. As the name implies, this API is considered experimental/unstable and is the required alternative to jest.mock for ESM [1][3]. Key technical considerations include: 1. Dynamic Imports: Because static imports are evaluated first, you cannot use static import statements for modules you intend to mock [1][4]. Instead, you must use dynamic import calls after the jest.unstable_mockModule registration to load the system under test (SUT) and the mocked dependencies [1][6]. 2. Configuration with ts-jest: When using ts-jest, you must enable the useESM: true configuration option [7][8]. This allows ts-jest to transform code into ESM syntax [8][9]. You must also ensure your Jest configuration is compatible with ESM (e.g., using correct presets like ts-jest/presets/default-esm) [7][9]. 3. API Usage: Unlike jest.mock, jest.unstable_mockModule requires a factory function (which can be sync or async) [1][2]. The basic pattern for a test file is: - Perform necessary imports (from @jest/globals, etc.) [6]. - Call jest.unstable_mockModule('module-path', factory) [1][6]. - Use dynamic await import('./path-to-sut') to load your code [1][6]. Note: The jest.unstable_mockModule API has known limitations, including potential issues with virtual mocks being scoped correctly outside of the immediate test file [10]. For mocking CommonJS (CJS) modules within an ESM project, you may continue to use jest.mock by using createRequire to bring require into your ESM environment [1][3].

Citations:


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- candidate files ---'
git ls-files | rg '(^|/)(jest\.config\.cjs|package\.json|tsconfig[^/]*\.json|api\.test\.ts|.*jest.*|.*babel.*|.*swc.*)$' | head -200

printf '%s\n' '--- Jest and module configuration references ---'
rg -n --glob '!node_modules/**' --glob '!dist/**' --glob '!build/**' \
  'useESM|extensionsToTreatAsEsm|preset.*esm|transform|type[[:space:]]*:[[:space:]]*"module"|unstable_mockModule|jest\.mock|`@jest/globals`' \
  jest.config.cjs package.json packages/apifymcp package.json tsconfig*.json packages 2>/dev/null | head -300

printf '%s\n' '--- target file outline and relevant contents ---'
wc -l packages/apifymcp/api.test.ts
ast-grep outline packages/apifymcp/api.test.ts || true
sed -n '1,40p' packages/apifymcp/api.test.ts
sed -n '90,180p' packages/apifymcp/api.test.ts

printf '%s\n' '--- package metadata ---'
cat package.json
if [ -f packages/apifymcp/package.json ]; then cat packages/apifymcp/package.json; fi

Repository: corsairdev/corsair

Length of output: 38221


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- apifymcp Jest configuration ---'
cat -n packages/apifymcp/jest.config.cjs

printf '%s\n' '--- apifymcp TypeScript configuration ---'
cat -n packages/apifymcp/tsconfig.json
printf '%s\n' '--- shared TypeScript configurations ---'
for f in tsconfig.json tsconfig.base.json tsconfig.test.json packages/corsair/tsconfig.test.json; do
  if [ -f "$f" ]; then
    echo "--- $f ---"
    cat -n "$f"
  fi
done

printf '%s\n' '--- test invocation and ESM runtime settings ---'
rg -n --hidden --glob '!node_modules/**' --glob '!dist/**' --glob '!build/**' \
  'pnpm (exec )?jest|jest --|NODE_OPTIONS|experimental-vm-modules|--experimental-vm-modules|packages/apifymcp|turbo.*test|\"test\"[[:space:]]*:' \
  package.json pnpm-workspace.yaml turbo.json .github packages/apifymcp packages/corsair 2>/dev/null | head -300

printf '%s\n' '--- comparable mock patterns in ESM-configured tests ---'
for f in packages/vercel/api.test.ts packages/confluence/operations.test.ts packages/digitalocean/operations.test.ts; do
  if [ -f "$f" ]; then
    echo "--- $f ---"
    sed -n '1,35p' "$f"
  fi
done

printf '%s\n' '--- Jest/ts-jest versions and lockfile entries ---'
rg -n '(^|/)(jest|ts-jest)@|jest@29\.7|ts-jest@29\.4|experimental-vm-modules' pnpm-lock.yaml package.json packages/apifymcp/package.json 2>/dev/null | head -100

Repository: corsairdev/corsair

Length of output: 38053


🏁 Script executed:

#!/bin/bash
set -eu

python3 - <<'PY'
from pathlib import Path
import json
import re

root = Path(".")
pkg = json.loads((root / "packages/apifymcp/package.json").read_text())
root_pkg = json.loads((root / "package.json").read_text())
cfg = (root / "packages/apifymcp/jest.config.cjs").read_text()
test = (root / "packages/apifymcp/api.test.ts").read_text()

checks = {
    "root package is ESM": root_pkg.get("type") == "module",
    "plugin package is ESM": pkg.get("type") == "module",
    "ts-jest useESM is enabled": "useESM: true" in cfg,
    "TypeScript module is ESNext": "module: 'ESNext'" in cfg,
    "TypeScript files are treated as ESM": "extensionsToTreatAsEsm: ['.ts']" in cfg,
    "static core import exists": bool(re.search(
        r"^\s*import\s*\{[^}]*\blogEventFromContext\b[^}]*\}\s*from\s*['\"]corsair/core['\"]",
        test, re.M
    )),
    "mock call exists": "jest.mock('corsair/core'" in test,
}
import_pos = test.find("import { AuthMissingError, logEventFromContext } from 'corsair/core';")
mock_pos = test.find("jest.mock('corsair/core'")
checks["static import precedes mock call"] = import_pos >= 0 and mock_pos > import_pos

# Inspect only declared launcher/configuration text. Do not execute repository code.
runtime_text = "\n".join(
    p.read_text()
    for p in [
        root / ".github/workflows/pr-checks.yml",
        root / "package.json",
        root / "packages/apifymcp/package.json",
        root / "turbo.json",
    ]
    if p.exists()
)
checks["searched launcher text has VM-module flag"] = bool(re.search(
    r"experimental-vm-modules|NODE_OPTIONS", runtime_text
))

for name, result in checks.items():
    print(f"{name}: {'PASS' if result else 'FAIL'}")

if not all(checks[k] for k in checks if k != "searched launcher text has VM-module flag"):
    raise SystemExit("repository facts do not match the expected ESM/mock shape")
PY

printf '%s\n' '--- narrow CI launcher context ---'
sed -n '35,75p' .github/workflows/pr-checks.yml
printf '%s\n' '--- turbo test task ---'
sed -n '20,45p' turbo.json

Repository: corsairdev/corsair

Length of output: 1983


Replace the CommonJS mock with an ESM-compatible mock.

ts-jest emits ESM for this package, so static imports execute before jest.mock. Use jest.unstable_mockModule and dynamic imports for corsair/core and the system under test. Replace jest.requireActual with an ESM-compatible approach. Launch Jest with --experimental-vm-modules; the CI test command does not currently set it.

🤖 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/apifymcp/api.test.ts` around lines 7 - 13, Update the test setup to
use Jest’s ESM mocking: replace the jest.mock/requireActual block with
jest.unstable_mockModule for corsair/core, then dynamically import both
corsair/core and the system under test after registering the mock. Ensure the
Jest invocation, including CI’s test command, enables --experimental-vm-modules.

Comment thread packages/apify/client.ts
Comment thread packages/apify/endpoints/shared.ts
Comment thread packages/apify/endpoints/types.ts
@Dhirenderchoudhary

Copy link
Copy Markdown
Collaborator

@greptile review

@github-actions

Copy link
Copy Markdown

Maintainer review needed

Automated rounds are exhausted. Remaining findings:

  • P1 packages/apifymcp/endpoints/shared.ts:146requireAuth option is declared but never checked
    The options parameter accepts requireAuth?: boolean, and four callers (callActor, ragWebBrowser, getActorRun, getActorOutput) pass requireAuth: true, but executeApifyMcpTool never reads that field. The call to callApifyMcpTool always proceeds with ctx.key || undefined regardless, meaning there is no fail-fast path when auth is absent. A caller reading actors.ts would reasonably expect that { requireAuth: true } produces an early AuthMissingError, but that never happens — the call goes out unauthenticated and returns a 401 from the Apify server instead.

@github-actions github-actions Bot added the needs-maintainer Automated rounds exhausted - human review needed label Aug 10, 2026
@Dhirenderchoudhary Dhirenderchoudhary removed the needs-maintainer Automated rounds exhausted - human review needed label Aug 10, 2026
@Dhirenderchoudhary

Copy link
Copy Markdown
Collaborator

LGTM fixed blocking changes and locally tested with api

@devjain32
devjain32 merged commit 06dfb06 into corsairdev:main Aug 12, 2026
9 of 10 checks passed
@coderabbitai coderabbitai Bot mentioned this pull request Aug 12, 2026
6 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bot:round-1 Review bot posted consolidated findings bot:round-2 Review bot pushed an automated fix core Changes in packages/corsair

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Integration request]: Apify MCP

3 participants