Skip to content

Add UI-backed whoami tool to MCP server#1894

Merged
chelojimenez merged 7 commits intomainfrom
codex/inspect-mcp-server
Apr 24, 2026
Merged

Add UI-backed whoami tool to MCP server#1894
chelojimenez merged 7 commits intomainfrom
codex/inspect-mcp-server

Conversation

@chelojimenez
Copy link
Copy Markdown
Contributor

@chelojimenez chelojimenez commented Apr 22, 2026

Summary

  • Add a bundled MCP app UI for whoami and wire it into the tool response
  • Gate UI behavior on client app capabilities and route tool registration through a session-aware registrar
  • Update the MCP package build so the UI is bundled before dev, deploy, and typecheck flows

Testing

  • Not run (not requested)

Note

Medium Risk
Adds a new Vite/React build step that is now required for dev/deploy/typecheck, so misconfigurations could break CI/builds or runtime asset availability. The new HTML-to-TS bundling script relies on dist output paths and could fail if the UI build output structure changes.

Overview
Introduces a UI build pipeline for the @mcpjam/mcp Cloudflare Worker package: new vite-based scripts are added and dev/deploy/start/typecheck now run build:ui first.

Adds scripts/bundle-mcp-app-html.mjs (and a .d.ts shim) to convert built UI HTML from dist/ into a generated TS module (src/generated/McpAppsHtml.bundled.ts), exposed both as a CLI script and a Vite plugin hook.

Updates dependencies/devDependencies to support this (react/react-dom, @modelcontextprotocol/ext-apps, vite, @vitejs/plugin-react, vite-plugin-singlefile, and React type packages).

Reviewed by Cursor Bugbot for commit 95c87d7. Bugbot is set up for automated code reviews on this repo. Configure here.

@chelojimenez
Copy link
Copy Markdown
Contributor Author

chelojimenez commented Apr 22, 2026

Snyk checks have passed. No issues have been found so far.

Status Scan Engine Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 22, 2026

MCP worker preview

Preview worker mcpjam-mcp-pr-1894 deleted — the preview URL no longer resolves.
Merged changes are live on mcpjam-mcp-staging via deploy-mcp-staging.yml.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 22, 2026

Internal preview

Preview URL: https://mcp-inspector-pr-1894.up.railway.app
Deployed commit: 158cbb4
PR head commit: 95c87d7
Backend target: staging fallback.
Health: ❌ Convex unreachable — see upsert-preview job logs (staging may need convex deploy)
Access is employee-only in non-production environments.

@chelojimenez chelojimenez marked this pull request as ready for review April 24, 2026 06:16
@dosubot dosubot Bot added the size:XL This PR changes 500-999 lines, ignoring generated files. label Apr 24, 2026
@dosubot dosubot Bot added the enhancement New feature or request label Apr 24, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 24, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0a5439cc-8c30-494d-9047-bc2151bc4db3

📥 Commits

Reviewing files that changed from the base of the PR and between 9ceeac0 and 95c87d7.

📒 Files selected for processing (1)
  • mcp/src/tools/sessionToolRegistrar.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • mcp/src/tools/sessionToolRegistrar.ts

Walkthrough

Adds a Vite+React whoami UI (HTML, CSS module, TSX), global styles, Vite config with single-file bundling, and Vite typings. Introduces a bundle script that inlines built app HTML into a generated TypeScript file plus a .d.ts. Adds a SessionToolRegistrar API and factory, updates server and tool registrations to use the registrar, and makes server init detect client UI capabilities and call setUiEnabled. Updates tsconfig to include DOM libs and Vite-related entries.


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.

Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 2f04093. Configure here.


export function bundleMcpAppsHtml(): void;
export function bundleMcpAppsHtmlPlugin(): Plugin;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Wrong relative path in declare module specifier

Low Severity

The declare module specifier "./scripts/bundle-mcp-app-html.mjs" is resolved relative to the .d.ts file's own location (mcp/scripts/), so it declares types for the non-existent path mcp/scripts/scripts/bundle-mcp-app-html.mjs. This means the type declaration has no effect — it doesn't match the import "./scripts/bundle-mcp-app-html.mjs" used in vite.config.ts (which resolves from mcp/). The @ts-expect-error on that import line masks the issue today, but the .d.ts file is dead code. Using top-level exports (without the declare module wrapper) would let TypeScript co-locate the types with the .mjs file automatically.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 2f04093. Configure here.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

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 (10)
mcp/tsconfig.json (1)

6-28: Consider separating worker and UI type contexts.

By widening lib with DOM and DOM.Iterable in a single config that also type-checks the Cloudflare Worker entrypoint, you allow browser-only globals (e.g., window, document, HTMLElement) to silently satisfy type-checks inside worker code. The Workers runtime types and DOM types overlap on several globals (fetch, Request, Response, WebSocket, URL, etc.) with subtly different shapes — collisions here are a classic source of “works in dev, fails at deploy” surprises.

A cleaner arrangement is a base tsconfig.json for the worker (no DOM lib) plus a tsconfig.ui.json via project references that includes only src/ui/** with DOM libs. Not a blocker; the build still works thanks to skipLibCheck, but the separation preserves the sandboxing the worker target deserves.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mcp/tsconfig.json` around lines 6 - 28, The current tsconfig includes DOM
libs in "lib" which lets browser globals leak into Cloudflare Worker code;
create a base tsconfig (remove "DOM" and "DOM.Iterable" from the "lib" array in
the existing tsconfig and keep worker-specific settings like "strict", "noEmit",
"esModuleInterop", "skipLibCheck", and "worker-configuration.d.ts"), then add a
separate tsconfig.ui.json that extends the base and adds "DOM" and
"DOM.Iterable" to "lib" and restricts "include" to UI files (e.g., "src/ui/**"
or "src/**/*.tsx") so UI TSX sees DOM types while worker entrypoints are
type-checked without DOM globals; update any build/test scripts to reference the
new tsconfig.ui.json for UI tasks and keep the original tsconfig for worker
compilation.
mcp/package.json (1)

15-15: typecheck performs a full Vite build — heavier than it needs to be.

Because McpAppsHtml.bundled.ts is authored by the Vite build pipeline, tsc --noEmit depends on the artifact existing, so the ordering is defensible. Worth noting, though, that this makes typecheck as slow as a full production build — inconvenient in CI and pre-commit hooks. Consider committing a stub (or lazy-loading the bundled HTML from disk at runtime) so that typecheck can run without the Vite build step. Optional; flagged for awareness.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mcp/package.json` at line 15, The "typecheck" npm script currently runs a
full Vite build before running "tsc --noEmit" because the generated artifact
McpAppsHtml.bundled.ts is required; to avoid the heavy build, add a committed
stub of McpAppsHtml.bundled.ts (or change the runtime to lazy-load/read the
bundled HTML from disk) so TypeScript can typecheck without running the Vite
build. Locate the "typecheck" script in package.json and either (a) add a
minimal committed McpAppsHtml.bundled.ts that satisfies the type imports/exports
used by the code, or (b) modify the code paths that import
McpAppsHtml.bundled.ts to use a runtime file-read/lazy import that does not
require the build artifact during typecheck; ensure the chosen approach
preserves existing types and update imports/exports accordingly.
mcp/vite.config.ts (1)

6-7: The .d.ts file's relative module specifier doesn't resolve via ambient declarations; remove it or convert the helper to TypeScript.

The declaration file at mcp/scripts/bundle-mcp-app-html.d.ts uses a relative specifier ("./scripts/bundle-mcp-app-html.mjs"), but TypeScript's ambient declare module resolution only matches non-relative specifiers. Consequently, the .d.ts provides no actual typing for this import, and the @ts-expect-error remains necessary.

Two clean options: (a) port the helper to TypeScript (.mts) for native type-checking, eliminating both the shim and the suppression, or (b) restructure the declaration to use a non-relative specifier. The current pairing is redundant.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mcp/vite.config.ts` around lines 6 - 7, The import of bundleMcpAppsHtmlPlugin
uses a relative ESM path ("./scripts/bundle-mcp-app-html.mjs") but the ambient
declaration in mcp/scripts/bundle-mcp-app-html.d.ts only matches non-relative
module names, so the .d.ts doesn't provide types and the `@ts-expect-error` is
still required; fix by either (A) porting the helper to TypeScript (rename to
.mts, update export in the helper, change the import of bundleMcpAppsHtmlPlugin
to the .mts module, remove the `@ts-expect-error` and delete the redundant .d.ts),
or (B) change the declaration to a non-relative module name that matches the
import (or change the import to a matching non-relative specifier), update the
.d.ts to declare that exact module name, and then remove the `@ts-expect-error` so
TypeScript picks up the ambient types.
mcp/scripts/bundle-mcp-app-html.mjs (2)

24-28: Surface a friendlier error when the expected dist HTML is missing.

If the Vite build hasn't produced dist/src/ui/whoami.html yet (e.g. the script is run standalone, or a future refactor relocates the output), readFileSync throws a raw ENOENT that gives no clue this is about MCP app bundling. A one-line wrap makes the failure self-describing and cuts down debugging time for anyone who extends the APPS list.

♻️ Proposed wrap
 ${APPS.map(({ exportName, fileName }) => {
-  const html = readFileSync(join(DIST_DIR, fileName), "utf-8");
+  const absolutePath = join(DIST_DIR, fileName);
+  let html;
+  try {
+    html = readFileSync(absolutePath, "utf-8");
+  } catch (cause) {
+    throw new Error(
+      `Failed to read bundled MCP app HTML for ${exportName} at ${absolutePath}. Did the Vite build run first?`,
+      { cause }
+    );
+  }
 
   return `export const ${exportName} = ${JSON.stringify(html)};`;
 }).join("\n\n")}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mcp/scripts/bundle-mcp-app-html.mjs` around lines 24 - 28, The script uses
readFileSync(DIST_DIR, fileName) inside the APPS.map and will surface a raw
ENOENT if an expected dist HTML is missing; wrap the readFileSync call in a
try/catch around the APPS mapping (or inside the map) and when catching an error
for a missing file (e.g., from readFileSync) rethrow a new, friendlier Error
that includes the missing fileName and DIST_DIR and mentions this is during MCP
app bundling (refer to APPS, exportName, fileName, readFileSync, DIST_DIR) so
consumers see a clear message like "MCP bundling failed: missing built HTML for
<fileName> in <DIST_DIR>" instead of the raw ENOENT.

9-12: Consider gitignoring the generated bundle.

src/generated/McpAppsHtml.bundled.ts is a build artefact — the HTML it embeds will churn whenever the React app changes, producing very noisy diffs (every CSS/JS byte of the bundled app ends up as a JSON-escaped string in the committed file). Adding mcp/src/generated/ to .gitignore and ensuring it is regenerated before dev, deploy, and typecheck (as the PR description already promises) keeps review diffs focused on source.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mcp/scripts/bundle-mcp-app-html.mjs` around lines 9 - 12, Add the generated
bundle directory to gitignore and ensure the build step regenerates it: add
"mcp/src/generated/" to .gitignore so the file referenced by OUTPUT_TS_PATH
(McpAppsHtml.bundled.ts) is not committed, and update the relevant scripts
(invoke the bundle-mcp-app-html.mjs script) so the generated file is produced
automatically before running dev, deploy, and typecheck (or add it to CI
pre-steps) to keep the repo free of noisy generated diffs while still
guaranteeing the bundle exists when needed.
mcp/src/ui/whoami.tsx (2)

113-121: structuredContent is taken on faith — a shallow guard would harden the UI.

Casting toolResult.structuredContent as WhoamiPayload | undefined will happily accept any object the host happens to hand back (including legacy servers, malformed payloads, or another tool's result delivered mid-session). A cheap structural check avoids a confusing partial render where, say, payload.id is undefined and the "Raw JSON" panel shows something entirely unrelated.

🛡️ Suggested guard
-  const payload = toolResult.structuredContent as WhoamiPayload | undefined;
-  if (!payload) {
+  const payload = isWhoamiPayload(toolResult.structuredContent)
+    ? toolResult.structuredContent
+    : undefined;
+  if (!payload) {
     return (
       <MessageBox
         label="Missing structured content"
         message="The whoami tool did not include structured content for the UI view."
       />
     );
   }
function isWhoamiPayload(value: unknown): value is WhoamiPayload {
  return (
    !!value &&
    typeof value === "object" &&
    "id" in value &&
    typeof (value as { id: unknown }).id === "string"
  );
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mcp/src/ui/whoami.tsx` around lines 113 - 121, Replace the blind cast of
toolResult.structuredContent to WhoamiPayload with a runtime type guard: add an
isWhoamiPayload(value: unknown): value is WhoamiPayload that checks value is an
object and that value.id exists and is a string (and any other required fields),
then use if (!isWhoamiPayload(toolResult.structuredContent)) return the existing
MessageBox; otherwise assign payload = toolResult.structuredContent (narrowed by
the guard) and proceed — reference toolResult.structuredContent, WhoamiPayload,
and the new isWhoamiPayload helper when making the change.

210-214: Non-null assertion on #root silently hides mount failures.

document.getElementById("root")! will coerce a missing element to a null-that-pretends-not-to-be, and createRoot(null).render(...) throws from deep inside React with a stack that points nowhere useful. A guarded lookup gives whoever is embedding this bundle a crisp error message.

♻️ Small guard
-createRoot(document.getElementById("root")!).render(
-  <StrictMode>
-    <WhoamiApp />
-  </StrictMode>
-);
+const rootElement = document.getElementById("root");
+if (!rootElement) {
+  throw new Error("whoami UI: missing `#root` mount point in bundled HTML.");
+}
+createRoot(rootElement).render(
+  <StrictMode>
+    <WhoamiApp />
+  </StrictMode>
+);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mcp/src/ui/whoami.tsx` around lines 210 - 214, Replace the non-null assertion
on the root element lookup so we fail fast with a clear message instead of
letting React throw deep errors: check document.getElementById("root") before
calling createRoot and, if missing, throw or log an explicit error like "Missing
root element '#root' - mount failed" and skip calling createRoot(...). Update
the code around createRoot(...).render(...) / WhoamiApp to use the guarded root
element variable.
mcp/src/tools/sessionToolRegistrar.ts (1)

85-90: The pair of as any casts masks a real type mismatch with registerAppTool.

Once lands the exact registerAppTool signature is known (see verification in the sibling comment), these casts should be replaced with a properly typed call — future schema changes in @modelcontextprotocol/ext-apps/server will otherwise land as runtime errors rather than type errors.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mcp/src/tools/sessionToolRegistrar.ts` around lines 85 - 90, The call to
registerAppTool in sessionToolRegistrar.ts currently uses two "as any" casts
around the argument object (containing resourceUri: ui.resourceUri) and the
callback ((ui.callback ?? callback) as any), which hides a real type mismatch;
replace those casts by aligning the call with the actual registerAppTool
signature from `@modelcontextprotocol/ext-apps/server`: import and use the correct
parameter and callback types (or the exported Request/Tool/Callback types) and
shape the first argument to match the expected Tool/Register payload (e.g.,
properly typed resourceUri field and any nested properties) and type the
callback parameter to the expected handler type instead of force-casting to any
so TypeScript will surface schema changes as compile errors.
mcp/src/server.ts (1)

59-67: Unsafe cast around ClientCapabilities.extensions — pin it to a named type.

The inline intersection (ClientCapabilities & { extensions?: Record<string, unknown> }) will silently decouple from any future SDK-provided extension typings. Hoisting a named type (or a tiny type guard) keeps the contract visible and lets the compiler help if the SDK eventually types extensions natively.

♻️ Suggested tidy-up
+type CapabilitiesWithExtensions = ClientCapabilities & {
+  extensions?: Record<string, unknown>;
+};
+
 function getUiCapability(
   clientCapabilities: ClientCapabilities | undefined
 ): { mimeTypes?: string[] } | undefined {
-  const extensions = (clientCapabilities as
-    | (ClientCapabilities & { extensions?: Record<string, unknown> })
-    | undefined)?.extensions;
-
-  return extensions?.[UI_EXTENSION_ID] as { mimeTypes?: string[] } | undefined;
+  const extensions = (clientCapabilities as CapabilitiesWithExtensions | undefined)
+    ?.extensions;
+  return extensions?.[UI_EXTENSION_ID] as { mimeTypes?: string[] } | undefined;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mcp/src/server.ts` around lines 59 - 67, The inline intersection in
getUiCapability should be replaced by a named type or small type guard to make
the contract visible and catch future SDK changes; define a type like
ClientCapabilitiesWithExtensions = ClientCapabilities & { extensions?:
Record<string, unknown> } (or implement function
isClientCapabilitiesWithExtensions(obj): obj is
ClientCapabilitiesWithExtensions) and then use that named type or guard in
getUiCapability instead of the inline (ClientCapabilities & { extensions?:
Record<string, unknown> }) cast so the compiler can track the shape of
extensions via the symbol ClientCapabilitiesWithExtensions (or
isClientCapabilitiesWithExtensions) and avoid silent decoupling.
mcp/src/tools/whoami.ts (1)

22-55: Consolidate the twin callbacks into a single source of truth.

The plain and UI callbacks are near-identical twins: same fetch, same text payload, the UI variant merely sprinkles in structuredContent. Per sessionToolRegistrar.ts, the UI registrar already falls back to the primary callback when ui.callback is omitted (ui.callback ?? callback). Attaching structuredContent to the primary response lets you drop the duplicated UI callback entirely — the registrar will reuse it for both the text-fallback tool and the app-UI tool, and future edits won't need to be mirrored in two places.

♻️ Proposed consolidation
     async () => {
       const payload = await getWhoamiPayload(agent);

       return {
         content: [
           {
             type: "text",
             text: JSON.stringify(payload, null, 2),
           },
         ],
+        structuredContent: payload,
       };
     },
     {
       resourceUri: WHOAMI_RESOURCE_URI,
       html: WHOAMI_APP_HTML,
       resourceName: "MCPJam whoami UI",
       resourceMeta: {
         ui: {
           prefersBorder: true,
         },
       },
-      callback: async () => {
-        const payload = await getWhoamiPayload(agent);
-
-        return {
-          content: [
-            {
-              type: "text",
-              text: JSON.stringify(payload, null, 2),
-            },
-          ],
-          structuredContent: payload,
-        };
-      },
     }

Worth confirming whether the text-fallback tool is expected to emit structuredContent (some hosts log or display it). If emitting it there is undesirable, keep the split but extract a shared buildResponse(payload, { withStructured }) helper to eliminate the string-formatting duplication.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mcp/src/tools/whoami.ts` around lines 22 - 55, The two nearly identical
callbacks (the top-level callback and the ui.callback under
WHOAMI_RESOURCE_URI/WHOAMI_APP_HTML) should be consolidated into a single source
of truth: remove the duplicated ui.callback and let the registrar fallback use
the primary callback; modify the primary callback (the one that calls
getWhoamiPayload(agent)) to include structuredContent: payload in its returned
object along with the text JSON so both the text-fallback tool and the UI tool
receive the same data; if emitting structuredContent in the text-fallback is
undesirable instead extract a shared helper (e.g., buildResponse(payload, {
withStructured })) and call it from both callback and ui.callback to avoid
duplication.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@mcp/package.json`:
- Around line 11-12: The current "dev" and "start" scripts run a one-shot
"build:ui" then "wrangler dev --env staging", so UI edits under src/ui/** don't
retrigger Vite or rebuild McpAppsHtml.bundled.ts; add the "concurrently"
devDependency and change the "dev" and "start" scripts to run "npm run ui:watch"
in parallel with "wrangler dev --env staging" (e.g., via concurrently running
"npm:ui:watch" and "wrangler dev --env staging") so Vite keeps rebuilding and
the worker serves updated bundled HTML without restarting the process. Ensure
"ui:watch" remains defined and used by the new scripts.

In `@mcp/src/ui/global.css`:
- Around line 33-35: The font-family declaration uses a quoted identifier
"SFMono-Regular" which Stylelint flags; update the font-family property in
global.css (the rule setting font-family) to remove the quotes around
SFMono-Regular so it reads SFMono-Regular, "SF Mono", ui-monospace, Menlo,
Consolas, monospace and keep quotes only for family names that require them.

In `@mcp/src/ui/whoami.module.css`:
- Around line 186-190: Insert a blank line between the custom-property cluster
and the following declaration inside the dark-mode .shell block: move the
background rule so there's an empty line after the --code-text: `#eef3fb`; line;
specifically update the block containing --code-text and the background
declaration (referencing the --code-text custom property and the background
rule) to include one blank line before the background declaration to satisfy
Stylelint's declaration-empty-line-before rule.

---

Nitpick comments:
In `@mcp/package.json`:
- Line 15: The "typecheck" npm script currently runs a full Vite build before
running "tsc --noEmit" because the generated artifact McpAppsHtml.bundled.ts is
required; to avoid the heavy build, add a committed stub of
McpAppsHtml.bundled.ts (or change the runtime to lazy-load/read the bundled HTML
from disk) so TypeScript can typecheck without running the Vite build. Locate
the "typecheck" script in package.json and either (a) add a minimal committed
McpAppsHtml.bundled.ts that satisfies the type imports/exports used by the code,
or (b) modify the code paths that import McpAppsHtml.bundled.ts to use a runtime
file-read/lazy import that does not require the build artifact during typecheck;
ensure the chosen approach preserves existing types and update imports/exports
accordingly.

In `@mcp/scripts/bundle-mcp-app-html.mjs`:
- Around line 24-28: The script uses readFileSync(DIST_DIR, fileName) inside the
APPS.map and will surface a raw ENOENT if an expected dist HTML is missing; wrap
the readFileSync call in a try/catch around the APPS mapping (or inside the map)
and when catching an error for a missing file (e.g., from readFileSync) rethrow
a new, friendlier Error that includes the missing fileName and DIST_DIR and
mentions this is during MCP app bundling (refer to APPS, exportName, fileName,
readFileSync, DIST_DIR) so consumers see a clear message like "MCP bundling
failed: missing built HTML for <fileName> in <DIST_DIR>" instead of the raw
ENOENT.
- Around line 9-12: Add the generated bundle directory to gitignore and ensure
the build step regenerates it: add "mcp/src/generated/" to .gitignore so the
file referenced by OUTPUT_TS_PATH (McpAppsHtml.bundled.ts) is not committed, and
update the relevant scripts (invoke the bundle-mcp-app-html.mjs script) so the
generated file is produced automatically before running dev, deploy, and
typecheck (or add it to CI pre-steps) to keep the repo free of noisy generated
diffs while still guaranteeing the bundle exists when needed.

In `@mcp/src/server.ts`:
- Around line 59-67: The inline intersection in getUiCapability should be
replaced by a named type or small type guard to make the contract visible and
catch future SDK changes; define a type like ClientCapabilitiesWithExtensions =
ClientCapabilities & { extensions?: Record<string, unknown> } (or implement
function isClientCapabilitiesWithExtensions(obj): obj is
ClientCapabilitiesWithExtensions) and then use that named type or guard in
getUiCapability instead of the inline (ClientCapabilities & { extensions?:
Record<string, unknown> }) cast so the compiler can track the shape of
extensions via the symbol ClientCapabilitiesWithExtensions (or
isClientCapabilitiesWithExtensions) and avoid silent decoupling.

In `@mcp/src/tools/sessionToolRegistrar.ts`:
- Around line 85-90: The call to registerAppTool in sessionToolRegistrar.ts
currently uses two "as any" casts around the argument object (containing
resourceUri: ui.resourceUri) and the callback ((ui.callback ?? callback) as
any), which hides a real type mismatch; replace those casts by aligning the call
with the actual registerAppTool signature from
`@modelcontextprotocol/ext-apps/server`: import and use the correct parameter and
callback types (or the exported Request/Tool/Callback types) and shape the first
argument to match the expected Tool/Register payload (e.g., properly typed
resourceUri field and any nested properties) and type the callback parameter to
the expected handler type instead of force-casting to any so TypeScript will
surface schema changes as compile errors.

In `@mcp/src/tools/whoami.ts`:
- Around line 22-55: The two nearly identical callbacks (the top-level callback
and the ui.callback under WHOAMI_RESOURCE_URI/WHOAMI_APP_HTML) should be
consolidated into a single source of truth: remove the duplicated ui.callback
and let the registrar fallback use the primary callback; modify the primary
callback (the one that calls getWhoamiPayload(agent)) to include
structuredContent: payload in its returned object along with the text JSON so
both the text-fallback tool and the UI tool receive the same data; if emitting
structuredContent in the text-fallback is undesirable instead extract a shared
helper (e.g., buildResponse(payload, { withStructured })) and call it from both
callback and ui.callback to avoid duplication.

In `@mcp/src/ui/whoami.tsx`:
- Around line 113-121: Replace the blind cast of toolResult.structuredContent to
WhoamiPayload with a runtime type guard: add an isWhoamiPayload(value: unknown):
value is WhoamiPayload that checks value is an object and that value.id exists
and is a string (and any other required fields), then use if
(!isWhoamiPayload(toolResult.structuredContent)) return the existing MessageBox;
otherwise assign payload = toolResult.structuredContent (narrowed by the guard)
and proceed — reference toolResult.structuredContent, WhoamiPayload, and the new
isWhoamiPayload helper when making the change.
- Around line 210-214: Replace the non-null assertion on the root element lookup
so we fail fast with a clear message instead of letting React throw deep errors:
check document.getElementById("root") before calling createRoot and, if missing,
throw or log an explicit error like "Missing root element '#root' - mount
failed" and skip calling createRoot(...). Update the code around
createRoot(...).render(...) / WhoamiApp to use the guarded root element
variable.

In `@mcp/tsconfig.json`:
- Around line 6-28: The current tsconfig includes DOM libs in "lib" which lets
browser globals leak into Cloudflare Worker code; create a base tsconfig (remove
"DOM" and "DOM.Iterable" from the "lib" array in the existing tsconfig and keep
worker-specific settings like "strict", "noEmit", "esModuleInterop",
"skipLibCheck", and "worker-configuration.d.ts"), then add a separate
tsconfig.ui.json that extends the base and adds "DOM" and "DOM.Iterable" to
"lib" and restricts "include" to UI files (e.g., "src/ui/**" or "src/**/*.tsx")
so UI TSX sees DOM types while worker entrypoints are type-checked without DOM
globals; update any build/test scripts to reference the new tsconfig.ui.json for
UI tasks and keep the original tsconfig for worker compilation.

In `@mcp/vite.config.ts`:
- Around line 6-7: The import of bundleMcpAppsHtmlPlugin uses a relative ESM
path ("./scripts/bundle-mcp-app-html.mjs") but the ambient declaration in
mcp/scripts/bundle-mcp-app-html.d.ts only matches non-relative module names, so
the .d.ts doesn't provide types and the `@ts-expect-error` is still required; fix
by either (A) porting the helper to TypeScript (rename to .mts, update export in
the helper, change the import of bundleMcpAppsHtmlPlugin to the .mts module,
remove the `@ts-expect-error` and delete the redundant .d.ts), or (B) change the
declaration to a non-relative module name that matches the import (or change the
import to a matching non-relative specifier), update the .d.ts to declare that
exact module name, and then remove the `@ts-expect-error` so TypeScript picks up
the ambient types.
🪄 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

Run ID: 41e85e66-643a-4e04-afd2-9bd405459b14

📥 Commits

Reviewing files that changed from the base of the PR and between ae95ed1 and 2f04093.

⛔ Files ignored due to path filters (2)
  • mcp/src/generated/McpAppsHtml.bundled.ts is excluded by !**/generated/**
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (17)
  • mcp/package.json
  • mcp/scripts/bundle-mcp-app-html.d.ts
  • mcp/scripts/bundle-mcp-app-html.mjs
  • mcp/src/server.ts
  • mcp/src/shared/whoami.ts
  • mcp/src/tools/doctor.ts
  • mcp/src/tools/getOrg.ts
  • mcp/src/tools/getWorkspaces.ts
  • mcp/src/tools/sessionToolRegistrar.ts
  • mcp/src/tools/whoami.ts
  • mcp/src/ui/global.css
  • mcp/src/ui/vite-env.d.ts
  • mcp/src/ui/whoami.html
  • mcp/src/ui/whoami.module.css
  • mcp/src/ui/whoami.tsx
  • mcp/tsconfig.json
  • mcp/vite.config.ts

Comment thread mcp/package.json
Comment on lines +11 to +12
"dev": "npm run build:ui && wrangler dev --env staging",
"start": "npm run build:ui && wrangler dev --env staging",
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.

⚠️ Potential issue | 🟡 Minor

Dev loop won't pick up UI edits after the first build.

dev and start do a one-shot build:ui and then hand off to wrangler dev. Edits under src/ui/** will neither retrigger Vite nor regenerate McpAppsHtml.bundled.ts, so the bundled HTML the worker serves will grow stale until the process is restarted. You already have ui:watch; wiring it alongside wrangler dev (e.g., via concurrently or npm-run-all -p) restores a proper inner loop.

🔧 One possible shape
-    "dev": "npm run build:ui && wrangler dev --env staging",
-    "start": "npm run build:ui && wrangler dev --env staging",
+    "dev": "npm run build:ui && concurrently -k -n ui,wrangler \"npm run ui:watch\" \"wrangler dev --env staging\"",
+    "start": "npm run build:ui && concurrently -k -n ui,wrangler \"npm run ui:watch\" \"wrangler dev --env staging\"",

(Adding concurrently as a devDependency would be required.)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mcp/package.json` around lines 11 - 12, The current "dev" and "start" scripts
run a one-shot "build:ui" then "wrangler dev --env staging", so UI edits under
src/ui/** don't retrigger Vite or rebuild McpAppsHtml.bundled.ts; add the
"concurrently" devDependency and change the "dev" and "start" scripts to run
"npm run ui:watch" in parallel with "wrangler dev --env staging" (e.g., via
concurrently running "npm:ui:watch" and "wrangler dev --env staging") so Vite
keeps rebuilding and the worker serves updated bundled HTML without restarting
the process. Ensure "ui:watch" remains defined and used by the new scripts.

Comment thread mcp/src/ui/global.css
Comment on lines +33 to +35
font-family:
"SFMono-Regular", "SF Mono", ui-monospace, Menlo, Consolas, monospace;
}
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.

⚠️ Potential issue | 🟡 Minor

Drop the quotes from SFMono-Regular.

Per the CSS spec, unquoted identifier-style family names are preferred when no whitespace or reserved characters are present. Stylelint flags this one accordingly.

🎨 Proposed tweak
 code,
 pre {
   font-family:
-    "SFMono-Regular", "SF Mono", ui-monospace, Menlo, Consolas, monospace;
+    SFMono-Regular, "SF Mono", ui-monospace, Menlo, Consolas, monospace;
 }
📝 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
font-family:
"SFMono-Regular", "SF Mono", ui-monospace, Menlo, Consolas, monospace;
}
font-family:
SFMono-Regular, "SF Mono", ui-monospace, Menlo, Consolas, monospace;
}
🧰 Tools
🪛 Stylelint (17.9.0)

[error] 34-34: Expected no quotes around "SFMono-Regular" (font-family-name-quotes)

(font-family-name-quotes)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mcp/src/ui/global.css` around lines 33 - 35, The font-family declaration uses
a quoted identifier "SFMono-Regular" which Stylelint flags; update the
font-family property in global.css (the rule setting font-family) to remove the
quotes around SFMono-Regular so it reads SFMono-Regular, "SF Mono",
ui-monospace, Menlo, Consolas, monospace and keep quotes only for family names
that require them.

Comment on lines +186 to +190
--code-text: #eef3fb;
background:
radial-gradient(circle at top left, rgba(45, 212, 191, 0.18), transparent 42%),
radial-gradient(circle at top right, rgba(251, 191, 36, 0.16), transparent 35%),
linear-gradient(180deg, #0c1219 0%, #111a24 100%);
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.

⚠️ Potential issue | 🟡 Minor

Stylelint: missing blank line before background declaration.

Stylelint's declaration-empty-line-before rule expects a blank line separating the custom-property cluster from the subsequent background rule inside the dark-mode .shell block.

🎨 Proposed fix
     --code-bg: rgba(7, 10, 16, 0.96);
     --code-text: `#eef3fb`;
+
     background:
       radial-gradient(circle at top left, rgba(45, 212, 191, 0.18), transparent 42%),
📝 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
--code-text: #eef3fb;
background:
radial-gradient(circle at top left, rgba(45, 212, 191, 0.18), transparent 42%),
radial-gradient(circle at top right, rgba(251, 191, 36, 0.16), transparent 35%),
linear-gradient(180deg, #0c1219 0%, #111a24 100%);
--code-text: `#eef3fb`;
background:
radial-gradient(circle at top left, rgba(45, 212, 191, 0.18), transparent 42%),
radial-gradient(circle at top right, rgba(251, 191, 36, 0.16), transparent 35%),
linear-gradient(180deg, `#0c1219` 0%, `#111a24` 100%);
🧰 Tools
🪛 Stylelint (17.9.0)

[error] 187-190: Expected empty line before declaration (declaration-empty-line-before)

(declaration-empty-line-before)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mcp/src/ui/whoami.module.css` around lines 186 - 190, Insert a blank line
between the custom-property cluster and the following declaration inside the
dark-mode .shell block: move the background rule so there's an empty line after
the --code-text: `#eef3fb`; line; specifically update the block containing
--code-text and the background declaration (referencing the --code-text custom
property and the background rule) to include one blank line before the
background declaration to satisfy Stylelint's declaration-empty-line-before
rule.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

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)
mcp/src/tools/sessionToolRegistrar.ts (1)

131-138: Resource display name can collide when two tools share a title.

Defaulting the resource name to ${config.title ?? name} UI means any two tools whose human-readable title happens to match will register resources under the same name. Defaulting to the tool's unique name (titles are for display only, names are the identifiers) removes the surprise at zero cost.

♻️ Proposed fix
-      const resource = server.registerResource(
-        ui.resourceName ?? `${config.title ?? name} UI`,
+      const resource = server.registerResource(
+        ui.resourceName ?? `${name}-ui`,
         ui.resourceUri,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mcp/src/tools/sessionToolRegistrar.ts` around lines 131 - 138, The resource
naming currently defaults to human-readable titles which can collide; update the
logic around server.registerResource so that when ui.resourceName is not
provided it uses the tool's unique identifier (name) rather than config.title —
e.g. derive the display identifier from name (e.g. `${name} UI`) and keep
ui.resourceDescription fallback unchanged; update references where
ui.resourceName is computed so server.registerResource uses ui.resourceName ??
`${name} UI` instead of `${config.title ?? name} UI` to avoid collisions.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@mcp/src/tools/sessionToolRegistrar.ts`:
- Around line 88-95: createSessionToolRegistrar currently doesn't remember the
last UI state so late-registered ToolUiToggle instances default to
uiEnabled=false; add a registrar-level variable (e.g., lastUiEnabled)
initialized from resolveUiEnabled() (or false if unavailable) and use that value
when constructing new ToolUiToggle objects so they are seeded with the current
UI state; update setUiEnabled, syncUiState, and any code paths that create
ToolUiToggle (references: createSessionToolRegistrar, SessionToolRegistrar,
ToolUiToggle, setUiEnabled, syncUiState) to read/write lastUiEnabled so
newly-registered tools reflect the last known UI state immediately.

---

Nitpick comments:
In `@mcp/src/tools/sessionToolRegistrar.ts`:
- Around line 131-138: The resource naming currently defaults to human-readable
titles which can collide; update the logic around server.registerResource so
that when ui.resourceName is not provided it uses the tool's unique identifier
(name) rather than config.title — e.g. derive the display identifier from name
(e.g. `${name} UI`) and keep ui.resourceDescription fallback unchanged; update
references where ui.resourceName is computed so server.registerResource uses
ui.resourceName ?? `${name} UI` instead of `${config.title ?? name} UI` to avoid
collisions.
🪄 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

Run ID: 505665e3-0843-49d2-a351-5d4d332ba0c5

📥 Commits

Reviewing files that changed from the base of the PR and between b804c60 and 033df2e.

📒 Files selected for processing (1)
  • mcp/src/tools/sessionToolRegistrar.ts

Comment thread mcp/src/tools/sessionToolRegistrar.ts Outdated
Comment on lines +88 to +95
export function createSessionToolRegistrar(server: McpServer): SessionToolRegistrar {
const uiToggles: ToolUiToggle[] = [];
const resolveUiEnabled = () =>
getUiCapability(
server.server.getClientCapabilities() as
| { extensions?: Record<string, unknown> }
| undefined
)?.mimeTypes?.includes(RESOURCE_MIME_TYPE) ?? false;
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.

⚠️ Potential issue | 🟡 Minor

Late-registered tools can miss the current UI state.

createSessionToolRegistrar never remembers the last value passed to setUiEnabled, and every new ToolUiToggle starts with uiEnabled = false. If a tool is registered after setUiEnabled(true) — which is exactly the ordering described in the PR (init() flips the flag, oninitialized flips it again) — the new tool will advertise its text-fallback variant until the next inbound request happens to trip syncUiState. A client that has already cached tools/list will never see the app-UI variant for that tool.

Cache the last state at the registrar level and seed new toggles with it.

♻️ Proposed fix
 export function createSessionToolRegistrar(server: McpServer): SessionToolRegistrar {
   const uiToggles: ToolUiToggle[] = [];
+  let currentUiEnabled = false;
   const resolveUiEnabled = () =>
     getUiCapability(
       server.server.getClientCapabilities() as
         | { extensions?: Record<string, unknown> }
         | undefined
     )?.mimeTypes?.includes(RESOURCE_MIME_TYPE) ?? false;
@@
-      uiToggles.push(
-        createToolUiToggle({
+      const toggle = createToolUiToggle({
           name,
           hiddenPlainName,
           hiddenUiName,
           plainTool,
           uiTool,
           resource,
-        })
-      );
+      });
+      toggle.setUiEnabled(currentUiEnabled);
+      uiToggles.push(toggle);
@@
     setUiEnabled(enabled) {
+      currentUiEnabled = enabled;
       for (const toggle of uiToggles) {
         toggle.setUiEnabled(enabled);
       }
     },

Also applies to: 153-178, 234-240

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mcp/src/tools/sessionToolRegistrar.ts` around lines 88 - 95,
createSessionToolRegistrar currently doesn't remember the last UI state so
late-registered ToolUiToggle instances default to uiEnabled=false; add a
registrar-level variable (e.g., lastUiEnabled) initialized from
resolveUiEnabled() (or false if unavailable) and use that value when
constructing new ToolUiToggle objects so they are seeded with the current UI
state; update setUiEnabled, syncUiState, and any code paths that create
ToolUiToggle (references: createSessionToolRegistrar, SessionToolRegistrar,
ToolUiToggle, setUiEnabled, syncUiState) to read/write lastUiEnabled so
newly-registered tools reflect the last known UI state immediately.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@mcp/src/tools/sessionToolRegistrar.ts`:
- Around line 67-100: The tool registration is currently non-atomic:
registerAppTool mutates server state before registerAppResource and leaves a
dangling tool if the resource call throws; wrap the registerAppResource call in
a try/catch and on any error perform rollback by un-registering the tool (call
the inverse/unregister function or remove the entry from server state for the
tool returned by registerAppTool) and then rethrow or surface a clear combined
error; ensure you reference registerAppTool, registerAppResource, server, the
tool returned by registerAppTool, and ui.resourceUri when implementing the
rollback so the tool+UI registration is atomic from the caller's perspective.
- Around line 104-108: The setUiEnabled() method currently does nothing, so
calls like registrar.setUiEnabled(isUiEnabled(...)) are dead; either remove the
dead plumbing (delete setUiEnabled() and all registrar.setUiEnabled(...) calls
and leave advertising behavior unchanged) or implement real state tracking in
the SessionToolRegistrar class (add a private uiEnabled boolean, implement
setUiEnabled(ui: boolean) to store it, and make the code that advertises the app
tool consult this.uiEnabled so the isUiEnabled(...) result actually gates
advertising). Use the existing symbol names setUiEnabled,
registrar.setUiEnabled, and isUiEnabled to locate and update the code.
🪄 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

Run ID: 6e187609-2295-4455-9e60-1ca42ea583c7

📥 Commits

Reviewing files that changed from the base of the PR and between 033df2e and 9ceeac0.

📒 Files selected for processing (1)
  • mcp/src/tools/sessionToolRegistrar.ts

Comment on lines +67 to +100
const tool = registerAppTool(
server,
name,
{
...config,
_meta: {
...(config._meta ?? {}),
ui: {
resourceUri: ui.resourceUri,
},
},
} as any,
(ui.callback ?? callback) as any
);

registerAppResource(
server,
ui.resourceName ?? `${config.title ?? name} UI`,
ui.resourceUri,
{
description:
ui.resourceDescription ?? `${config.title ?? name} interactive UI`,
_meta: ui.resourceMeta,
},
async () => ({
contents: [
{
uri: ui.resourceUri,
text: ui.html,
_meta: ui.resourceMeta,
},
],
})
);
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.

⚠️ Potential issue | 🟡 Minor

Partial-registration hazard when the resource call throws.

registerAppTool runs first and mutates server state; if registerAppResource subsequently throws (duplicate URI, validation, etc.), the tool is left registered without its companion UI resource — clients would then see a tool whose _meta.ui.resourceUri dangles. Consider wrapping the resource registration in a try/catch that either rolls back the tool registration or surfaces a clearer error, so the two registrations remain atomic from the caller's perspective.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mcp/src/tools/sessionToolRegistrar.ts` around lines 67 - 100, The tool
registration is currently non-atomic: registerAppTool mutates server state
before registerAppResource and leaves a dangling tool if the resource call
throws; wrap the registerAppResource call in a try/catch and on any error
perform rollback by un-registering the tool (call the inverse/unregister
function or remove the entry from server state for the tool returned by
registerAppTool) and then rethrow or surface a clear combined error; ensure you
reference registerAppTool, registerAppResource, server, the tool returned by
registerAppTool, and ui.resourceUri when implementing the rollback so the
tool+UI registration is atomic from the caller's perspective.

Comment on lines +104 to +108
setUiEnabled() {
// The hosted example server always advertises its app tool and relies on
// the host to ignore UI metadata when unsupported. Keep the method for
// compatibility with existing call sites.
},
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.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Confirm setUiEnabled has no other implementations and inspect all call sites.
rg -nP -C3 '\bsetUiEnabled\b'
rg -nP -C3 '\bisUiEnabled\b'

Repository: MCPJam/inspector

Length of output: 2563


🏁 Script executed:

sed -n '66,80p' mcp/src/server.ts

Repository: MCPJam/inspector

Length of output: 268


🏁 Script executed:

rg -nP 'registerAppTool|registerAppResource' mcp/src/tools/sessionToolRegistrar.ts -A5 -B2

Repository: MCPJam/inspector

Length of output: 582


setUiEnabled() is a no-op — capability detection in server.ts serves no purpose.

The method's comment is explicit: always advertise the app tool and let the host ignore UI metadata when unsupported. However, this means registrar.setUiEnabled(isUiEnabled(...)) does nothing. The capability check runs but its result is discarded, leaving dead code in server.ts that implies feature-flagged behavior without delivering it.

If capability-based gating was a PR objective, either remove the dead plumbing or implement real state tracking. Otherwise, the current posture—always advertise, host filters—is legitimate but should not masquerade as gated.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mcp/src/tools/sessionToolRegistrar.ts` around lines 104 - 108, The
setUiEnabled() method currently does nothing, so calls like
registrar.setUiEnabled(isUiEnabled(...)) are dead; either remove the dead
plumbing (delete setUiEnabled() and all registrar.setUiEnabled(...) calls and
leave advertising behavior unchanged) or implement real state tracking in the
SessionToolRegistrar class (add a private uiEnabled boolean, implement
setUiEnabled(ui: boolean) to store it, and make the code that advertises the app
tool consult this.uiEnabled so the isUiEnabled(...) result actually gates
advertising). Use the existing symbol names setUiEnabled,
registrar.setUiEnabled, and isUiEnabled to locate and update the code.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 95c87d7772

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread mcp/src/ui/whoami.tsx
key: string
): string | undefined {
const value = record?.[key];
return typeof value === "string" && value.length > 0 ? value : undefined;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Handle numeric creation timestamps in whoami view

getStringValue only returns values when the field is a non-empty string, but Convex system/user timestamps like _creationTime are numeric. In the current flow, createdAt falls back through getStringValue(userRecord, "_creationTime"), so valid numeric timestamps are treated as missing and the UI shows "Not provided" even when the payload contains creation time data.

Useful? React with 👍 / 👎.

@chelojimenez chelojimenez merged commit 934c606 into main Apr 24, 2026
13 checks passed
@chelojimenez chelojimenez deleted the codex/inspect-mcp-server branch April 24, 2026 07:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request size:XL This PR changes 500-999 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant