Skip to content

fix: narrow engine MCP import path for bundlers#149

Closed
noodleonthecape wants to merge 3 commits into
mainfrom
fix/issue-144-mcp-server-entrypoint
Closed

fix: narrow engine MCP import path for bundlers#149
noodleonthecape wants to merge 3 commits into
mainfrom
fix/issue-144-mcp-server-entrypoint

Conversation

@noodleonthecape

Copy link
Copy Markdown

Summary

  • import createRelayMcpServer from @relaycast/mcp/server instead of the package root in the engine MCP adapter
  • add an explicit ./server export in @relaycast/mcp
  • mark @relaycast/mcp as side-effect-free so bundlers can drop unused entrypoints more aggressively

Why

This narrows the engine's dependency edge so Workers bundlers do not have to start from the @relaycast/mcp barrel entrypoint when they only need server creation. That should help keep dead MCP SDK example/client code out of reachable bundle graphs.

Validation

  • node -e "JSON.parse(require('fs').readFileSync('packages/mcp/package.json','utf8')); console.log('package.json ok')"
  • git diff --check
  • esbuild repro bundle of the engine MCP adapter with the narrowed import path, then grep for examples/client and node:readline in the emitted bundle

Closes #144

@coderabbitai

coderabbitai Bot commented May 31, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@agent-relay-bot[bot], we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 51 minutes and 16 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 6d34f394-11ea-4da2-9944-ce16e9f4bd1c

📥 Commits

Reviewing files that changed from the base of the PR and between 941681a and 4471d20.

📒 Files selected for processing (3)
  • .trajectories/active/traj_1776415870261_44fa1655/trajectory.json
  • .trajectories/index.json
  • packages/react/src/reducer.ts
📝 Walkthrough

Walkthrough

This PR adds explicit ESM exports for @relaycast/mcp, imports the MCP server entrypoint from @relaycast/mcp/server, hardens runtime parsing for A2A parts, normalizes agent type handling, and introduces local event/reaction shapes and safer WS event handling across engine, server, SDK, and UI packages.

Changes

Package Export Narrowing & Engine import

Layer / File(s) Summary
Package exports and engine import update
packages/mcp/package.json, packages/engine/src/adapters/node/mcp.ts
packages/mcp adds an exports map for . and ./server, sets sideEffects: false, and the engine adapter imports createRelayMcpServer from @relaycast/mcp/server instead of the package root.

A2A parsing and artifact typing

Layer / File(s) Summary
A2A parsing (engine)
packages/engine/src/engine/a2a.ts
Adds isRecord and makes extractTextFromParts/extractAttachmentsFromParts defensive; adds explicit artifact typing in translateA2aToRelay.
A2A parsing (server)
packages/server/src/engine/a2a.ts
Parallel defensive parsing and typing changes to server-side A2A handling (same helpers and fallbacks).

Agent registration normalization

Layer / File(s) Summary
Agent type normalization
packages/engine/src/routes/agent.ts, packages/server/src/routes/agent.ts
Derive agentType only when type is a string and pass type: agentType into agentEngine.registerAgent.

WebSocket resource events

Layer / File(s) Summary
MCP ws-bridge event shape
packages/mcp/src/resources/ws-bridge.ts
Use local ResourceEvent shape, filter client-only lifecycle events, and compute resource URIs from the cast event before notifying subscribers.

UI, provider, reducer, and adapters: event & reaction typing

Layer / File(s) Summary
Local event/reaction types & handler normalization
packages/react/src/reducer.ts, packages/react/src/provider.tsx, packages/react/src/adapters/messages.ts, packages/observer-dashboard/src/components/*
Introduce local reaction and unified event shapes; cast incoming arrays/fields before mapping; update reducer handlers to use unified types while preserving existing update logic and outputs.

SDK typing and WS emission changes

Layer / File(s) Summary
SDK: message IDs, subscription guards, WS, and identity types
packages/sdk-typescript/src/agent.ts, packages/sdk-typescript/src/ws.ts, packages/sdk-typescript/src/identity.ts
Derive stable Relaycast event ids when missing, add runtime guards in subscription matching, emit validated serverEvent.type with camelized payload from WS, and replace RegisterAgentInput/RegisterOrRotateInput with explicit field-based typings.

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

"A rabbit hops through exports so bright,
Narrower paths trim the bundle's weight,
Parsers watch parts with careful eyes,
Events align, no more surprise,
Tiny paws tidy the code tonight."

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The PR includes numerous unrelated changes across 12+ files (A2A parsing, agent registration, WebSocket handling, reactions typing, identity types) that are not connected to the MCP import path narrowing objective from #144. Remove changes unrelated to #144 scope: revert modifications to a2a.ts, agent.ts, ws-bridge.ts, reducer.ts, messages.ts, and other files not required for the MCP import path fix.
Docstring Coverage ⚠️ Warning Docstring coverage is 3.70% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The PR title 'fix: narrow engine MCP import path for bundlers' directly and clearly summarizes the main change: narrowing the import path to improve bundler efficiency.
Description check ✅ Passed The PR description is directly related to the changeset, explaining the three main modifications and their bundling benefits with clear validation steps.
Linked Issues check ✅ Passed The PR successfully addresses the primary objective from #144: narrowing the MCP import path in the engine to prevent example files from @modelcontextprotocol/sdk from being bundled into Workers builds.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/issue-144-mcp-server-entrypoint

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 and usage tips.

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request configures subpath exports for the @relaycast/mcp package, exposing a new ./server entry point, and updates the Node MCP adapter to import from @relaycast/mcp/server. Feedback suggests adding a typesVersions mapping in package.json to maintain TypeScript compatibility for consumers using older module resolution settings.

Comment thread packages/mcp/package.json
Comment on lines +7 to +17
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
},
"./server": {
"types": "./dist/server.d.ts",
"import": "./dist/server.js"
}
},
"sideEffects": false,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

For TypeScript consumers using 'moduleResolution': 'node' (or 'node10'), subpath exports defined in the 'exports' field are not automatically resolved. To ensure maximum compatibility and prevent compilation errors for consumers importing from '@relaycast/mcp/server', it is recommended to add a 'typesVersions' mapping in 'package.json'.

  "exports": {
    ".": {
      "types": "./dist/index.d.ts",
      "import": "./dist/index.js"
    },
    "./server": {
      "types": "./dist/server.d.ts",
      "import": "./dist/server.js"
    }
  },
  "typesVersions": {
    "*": {
      "server": [
        "./dist/server.d.ts"
      ]
    }
  },
  "sideEffects": false,

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

1 issue found across 2 files

You’re at about 90% of the monthly reviewed-line limit. You may want to disable incremental reviews to conserve quota. Reviews will continue until that limit is exceeded. If you need help avoiding interruptions, please contact contact@cubic.dev.

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread packages/mcp/package.json

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/react/src/reducer.ts (1)

229-240: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Guard prev.parent.reactions before mapping or spreading it.

The thread-parent path still assumes prev.parent.reactions is always an array. If a parent message was loaded without reactions, both add and remove events can crash in these branches.

Suggested fix
-        const parentReactions = prev.parent.reactions as ReactionGroupView[];
+        const parentReactions = (prev.parent.reactions ?? []) as ReactionGroupView[];
         const existing = parentReactions.find((r) => r.emoji === event.emoji);
         let newReactions: ReactionGroupView[];
         if (existing) {
           if (existing.agents.includes(event.agentName)) return prev;
           newReactions = parentReactions.map((r) =>
             r.emoji === event.emoji
               ? { ...r, count: r.count + 1, agents: [...r.agents, event.agentName] }
               : r,
           );
         } else {
-          newReactions = [...prev.parent.reactions, { emoji: event.emoji, count: 1, agents: [event.agentName] }];
+          newReactions = [...parentReactions, { emoji: event.emoji, count: 1, agents: [event.agentName] }];
         }
-        const newReactions = (prev.parent.reactions as ReactionGroupView[])
+        const newReactions = ((prev.parent.reactions ?? []) as ReactionGroupView[])
           .map((r) =>
             r.emoji === event.emoji
               ? { ...r, count: Math.max(0, r.count - 1), agents: r.agents.filter((a) => a !== event.agentName) }
               : r,
           )

Also applies to: 280-286

🤖 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/react/src/reducer.ts` around lines 229 - 240, The code assumes
prev.parent.reactions is always an array which can be undefined and cause
crashes in the add/remove reaction branches; update the logic around
prev.parent.reactions (used with ReactionGroupView, existing, newReactions, and
the mapping/spread operations) to first normalize or guard it (e.g. const
parentReactions = Array.isArray(prev.parent.reactions) ? prev.parent.reactions :
[]) and use that safe array for find, map and the spread when constructing
newReactions so add/remove events won’t throw when reactions are missing.
🧹 Nitpick comments (1)
packages/mcp/src/resources/ws-bridge.ts (1)

97-108: ⚡ Quick win

Refine the concern: WsClient already drops malformed WS messages, so the cast doesn’t need runtime Zod validation

In packages/mcp/src/resources/ws-bridge.ts (97-108), WsClient.on('*', ...) is fed WsClientEvent payloads from packages/sdk-typescript/src/ws.ts: malformed JSON is dropped, and wildcard emissions happen only when parsing succeeds or when the payload is an object with a string type (plus open/close/error/reconnecting are emitted as { type: ... }). So the “unchecked cast can allow null/non-object through at runtime” concern doesn’t match the SDK behavior.

Optional: remove the event as ResourceEvent cast (or align ResourceEvent with WsClientEvent/make type required) to improve type safety without adding extra Zod parsing.

🤖 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/mcp/src/resources/ws-bridge.ts` around lines 97 - 108, The unchecked
cast to ResourceEvent in the WsClient.on('*', ...) handler is unnecessary
because WsClient already drops malformed messages; remove the runtime cast (the
"event as ResourceEvent") and instead either (a) align the ResourceEvent type
with WsClientEvent by making the type property required so TypeScript matches
the payload, or (b) change the handler signature to accept WsClientEvent and
pass that into eventToResourceUris; update references to ResourceEvent,
WsClientEvent, WsClient.on('*', ...) and eventToResourceUris accordingly to
preserve type safety without adding Zod validation.
🤖 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/react/src/reducer.ts`:
- Around line 7-13: EventMessage currently omits the server timestamp so
createdAt is lost and handlers fall back to new Date(), causing time rewrites
and reordering; add createdAt?: string to the EventMessage type and update the
message construction in handleMessageCreated() and handleThreadReply() to use
event.message.createdAt when populating the local MessageWithMeta (instead of
generating a new timestamp), ensuring historical message times are preserved.
- Around line 174-185: In updateReactionsOnMessage ensure you never pass
undefined into the updater: when calling updater(updated[idx].reactions as
ReactionGroupView[]) default updated[idx].reactions to an empty array (e.g. use
updated[idx].reactions ?? []) so the updater always receives
ReactionGroupView[]; then assign the updater result back to
updated[idx].reactions (cast to MessageWithMeta['reactions'] if needed) and
return updated.

---

Outside diff comments:
In `@packages/react/src/reducer.ts`:
- Around line 229-240: The code assumes prev.parent.reactions is always an array
which can be undefined and cause crashes in the add/remove reaction branches;
update the logic around prev.parent.reactions (used with ReactionGroupView,
existing, newReactions, and the mapping/spread operations) to first normalize or
guard it (e.g. const parentReactions = Array.isArray(prev.parent.reactions) ?
prev.parent.reactions : []) and use that safe array for find, map and the spread
when constructing newReactions so add/remove events won’t throw when reactions
are missing.

---

Nitpick comments:
In `@packages/mcp/src/resources/ws-bridge.ts`:
- Around line 97-108: The unchecked cast to ResourceEvent in the
WsClient.on('*', ...) handler is unnecessary because WsClient already drops
malformed messages; remove the runtime cast (the "event as ResourceEvent") and
instead either (a) align the ResourceEvent type with WsClientEvent by making the
type property required so TypeScript matches the payload, or (b) change the
handler signature to accept WsClientEvent and pass that into
eventToResourceUris; update references to ResourceEvent, WsClientEvent,
WsClient.on('*', ...) and eventToResourceUris accordingly to preserve type
safety without adding Zod validation.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: d898285b-17d7-4d9c-8930-2f157d28f70e

📥 Commits

Reviewing files that changed from the base of the PR and between 2ad248e and 941681a.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (15)
  • .trajectories/index.json
  • packages/engine/src/engine/a2a.ts
  • packages/engine/src/routes/agent.ts
  • packages/mcp/package.json
  • packages/mcp/src/resources/ws-bridge.ts
  • packages/observer-dashboard/src/components/DashboardLayout.tsx
  • packages/observer-dashboard/src/components/MessageCard.tsx
  • packages/react/src/adapters/messages.ts
  • packages/react/src/provider.tsx
  • packages/react/src/reducer.ts
  • packages/sdk-typescript/src/agent.ts
  • packages/sdk-typescript/src/identity.ts
  • packages/sdk-typescript/src/ws.ts
  • packages/server/src/engine/a2a.ts
  • packages/server/src/routes/agent.ts
💤 Files with no reviewable changes (1)
  • .trajectories/index.json
✅ Files skipped from review due to trivial changes (1)
  • packages/sdk-typescript/src/ws.ts

Comment on lines +7 to +13
type EventMessage = {
id: string;
agentName: string;
agentId?: string;
text: string;
attachments?: MessageWithMeta['attachments'];
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Keep the server timestamp in EventMessage.

createdAt drops out of the local event shape here, so handleMessageCreated() and handleThreadReply() fall back to new Date().toISOString(). That rewrites historical message times and can reorder messages after reconnects or delayed delivery.

Suggested fix
 type EventMessage = {
   id: string;
   agentName: string;
   agentId?: string;
   text: string;
+  createdAt: string;
   attachments?: MessageWithMeta['attachments'];
 };

Then use event.message.createdAt when constructing the local MessageWithMeta objects.

🤖 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/react/src/reducer.ts` around lines 7 - 13, EventMessage currently
omits the server timestamp so createdAt is lost and handlers fall back to new
Date(), causing time rewrites and reordering; add createdAt?: string to the
EventMessage type and update the message construction in handleMessageCreated()
and handleThreadReply() to use event.message.createdAt when populating the local
MessageWithMeta (instead of generating a new timestamp), ensuring historical
message times are preserved.

Comment on lines 174 to +185
function updateReactionsOnMessage(
messages: MessageWithMeta[],
messageId: string,
updater: (reactions: ReactionGroup[]) => ReactionGroup[],
updater: (reactions: ReactionGroupView[]) => ReactionGroupView[],
): MessageWithMeta[] | null {
const idx = messages.findIndex((m) => m.id === messageId);
if (idx === -1) return null;
const updated = [...messages];
updated[idx] = { ...updated[idx], reactions: updater(updated[idx].reactions) };
updated[idx] = {
...updated[idx],
reactions: updater(updated[idx].reactions as ReactionGroupView[]) as MessageWithMeta['reactions'],
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Default missing reactions to an empty array here.

MessageWithMeta.reactions is treated as nullable elsewhere in this stack, but this cast passes undefined through to the updater. The first .find()/.map() in the reaction handlers will then throw for any message hydrated without reactions.

Suggested fix
   updated[idx] = {
     ...updated[idx],
-    reactions: updater(updated[idx].reactions as ReactionGroupView[]) as MessageWithMeta['reactions'],
+    reactions: updater(((updated[idx].reactions ?? []) as ReactionGroupView[])) as MessageWithMeta['reactions'],
   };
🤖 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/react/src/reducer.ts` around lines 174 - 185, In
updateReactionsOnMessage ensure you never pass undefined into the updater: when
calling updater(updated[idx].reactions as ReactionGroupView[]) default
updated[idx].reactions to an empty array (e.g. use updated[idx].reactions ?? [])
so the updater always receives ReactionGroupView[]; then assign the updater
result back to updated[idx].reactions (cast to MessageWithMeta['reactions'] if
needed) and return updated.

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.

engine/mcp: @modelcontextprotocol/sdk example files bundle into Workers builds (dead code, ~4.5MB, drags in fs/readline/http)

2 participants