Skip to content

feat: build interactive cortex MCP Apps query widget (syslog-mcp-yi66.2) - #74

Merged
jmagar merged 5 commits into
mainfrom
claude/dazzling-hodgkin-b18687
Jun 15, 2026
Merged

feat: build interactive cortex MCP Apps query widget (syslog-mcp-yi66.2)#74
jmagar merged 5 commits into
mainfrom
claude/dazzling-hodgkin-b18687

Conversation

@jmagar

@jmagar jmagar commented Jun 15, 2026

Copy link
Copy Markdown
Collaborator

Summary

The cortex MCP-UI widget rendered as a bare, unstyled, non-functional form — the prior query_widget.html was only a placeholder stub from the server-contract slice (syslog-mcp-yi66.1). This builds out the actual widget (syslog-mcp-yi66.2).

  • Self-contained, dependency-free, Aurora-dark widget: FTS5 query input + hostname/severity/limit filters.
  • Submit calls the cortex tool with action=search over an MCP Apps host bridge — window.openai.callTool when injected, else an mcp-ui postMessage adapter (type:"tool"ui-message-response).
  • Renders a compact results table (received_at/timestamp, host, severity badge, app, message, id) with idle/loading/empty/error/bridge-unavailable states and clean long-message wrapping.
  • All log fields rendered via textContent — no HTML injection from untrusted log data.
  • Server contract (MIME text/html;profile=mcp-app, _meta.ui.resourceUri, ui://cortex/query-widget) is unchanged — it was an intentional Locked decision, not a bug.

Review hardening (folded in)

Multi-agent review (security / frontend-races / simplicity) + goal-verifier ran; findings fixed inline:

  • P1 — origin-bound the inbound postMessage handler (event.source === window.parent) and required a positive messageId match — predictable ids + a global listener allowed sibling-frame response spoofing and stale-response cross-talk.
  • P2try/catch + single done guard around postMessage so a throw no longer leaks the listener/timer or hangs the promise.
  • P3 — shared withTimeout() bounds the window.openai.callTool path too (hung host no longer wedges the form); reflected host error strings capped at 200 chars.

Bead

syslog-mcp-yi66.2: Build the initial log query widget HTML

Testing

  • Extended resources/read test with 9 stable wire-format anchors (incl. event.source origin guard). Full cargo test green via pre-push hook; clippy/fmt clean.
  • Live-host end-to-end smoke is the sibling bead syslog-mcp-yi66.3.

Follow-ups

  • syslog-mcp-6jbt7 (P3, pre-existing): consolidate duplicated rmcp test-state helpers.

Version: 1.20.1 → 1.21.0


Summary by cubic

Builds the interactive MCP Apps query widget for ui://cortex/query-widget, documents it as a progressive enhancement with host-agnostic smoke tests, and hardens the host bridge with a defense-in-depth CSP. Also consolidates test helpers and deduplicates PUBLIC_URL config. Version: 1.21.3.

  • New Features

    • Self-contained Aurora-dark widget with FTS5 query plus hostname/severity/limit filters.
    • Calls window.openai.callTool when available; otherwise uses a postMessage adapter (type:"tool"ui-message-response) with a bounded timeout.
    • Compact results table with idle/loading/empty/error/bridge-unavailable states; docs and smoke tests validate resources/list, resources/read MIME + anchors, _meta.ui.resourceUri, and tools/call action=search returning both structuredContent and text.
  • Bug Fixes

    • Host bridge hardened: origin-bind postMessage (event.source === window.parent) with matching messageId, shared timeout with “not responding” status, try/catch + single completion guard, and host error strings capped at 200 chars.
    • Add a defense-in-depth CSP to the widget (img-src 'none'; object-src 'none'; base-uri 'none'; form-action 'none'); docs note origin-bound handling and the ui:// targetOrigin:"*" constraint.
    • Smoke tests stop interpolating server values into python -c (assert_gte reads via stdin; json_get documents literal-accessor contract).
    • Test refactors: dedupe PUBLIC_URL McpConfig literals via public_url_config() and fold the no-auth router into rmcp_router (no behavior change).

Written for commit c16a57b. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features

    • Added interactive MCP Apps query widget for syslog searches (available to MCP-UI capable hosts)
    • Query widget provides real-time filtering with hostname, severity, and limit controls
  • Documentation

    • Enhanced setup guides with query widget verification steps and wire-protocol examples
  • Chores

    • Version bumped to 1.21.2
    • Updated Docker image and server configurations

jmagar added 2 commits June 15, 2026 02:16
Replace the placeholder query widget HTML with a self-contained, dependency-free
Aurora-dark widget: FTS5 query input, hostname/severity/limit filters, results
table, and idle/loading/empty/error/bridge-unavailable states. Submit calls the
cortex tool with action=search via a window.openai-or-mcp-ui postMessage bridge.
Log fields render via textContent to prevent injection from untrusted messages.

Extend resources/read test with stable wire-format anchors. Refresh stale
MCPUI.md. Bump 1.20.1 -> 1.21.0.
Address review findings on the postMessage bridge:
- P1: bind onMessage to event.source===window.parent and require a positive
  messageId match (predictable ids + global listener allowed sibling-frame
  response spoofing and stale-response cross-talk).
- P2: wrap window.parent.postMessage in try/catch with a single 'done' guard +
  cleanup() so a throw (DataCloneError/SecurityError) no longer leaks the
  listener/timer or hangs the promise.
- P3: shared withTimeout() bounds the window.openai.callTool path too, so a hung
  host can no longer wedge the form; surface a 'not responding' status; 15s->12s.
- Cap reflected host error strings at 200 chars; drop redundant Date.now guard.

Lock the origin guard with an event.source wire-format test anchor.
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@coderabbitai

coderabbitai Bot commented Jun 15, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds the interactive ui://cortex/query-widget MCP Apps resource: a self-contained styled HTML widget with a dual-path host bridge (window.openai.callTool / postMessage fallback), safe textContent rendering, and a degraded "bridge unavailable" state. The release also refactors RMCP server test helpers, adds JSON-RPC smoke-test coverage, expands documentation, and bumps the package version to 1.21.2.

Changes

Cortex Query Widget v1.21.2 Release

Layer / File(s) Summary
Query widget HTML markup and JS runtime
src/mcp/ui/query_widget.html
Replaces the minimal static form with an Aurora-styled widget containing query/hostname/severity/limit controls, ARIA live status, and a hidden results table. Adds an IIFE that bridges via window.openai.callTool or falls back to parent postMessage with messageId correlation, shared timeout, response normalization across payload shapes, bounded error messages, and dynamic table rendering using textContent for all log fields.
RMCP test helper refactor and expanded widget assertions
src/mcp/rmcp_server_tests.rs
Introduces a shared make_state(auth_policy) helper; rewrites test_state() and mounted_state() as thin wrappers differing only by AuthPolicy; removes rmcp_router_no_auth_middleware and updates two call sites to plain rmcp_router(state); expands query widget HTML test to assert a set of stable anchor strings.
Smoke-test contract coverage
scripts/smoke-test.sh
Adds 65 lines of JSON-RPC assertions: resources/list presence of ui://cortex/query-widget, resources/read MIME type (text/html;profile=mcp-app) and HTML markers, tools/list _meta.ui.resourceUri field, and tools/call dual-payload contract (structuredContent.logs + content[0].text).
Documentation updates
README.md, docs/SETUP.md, docs/mcp/MCPUI.md, CHANGELOG.md
README gains a full "MCP Apps query widget" section with curl wire-contract examples and auth notes. SETUP.md adds an optional resources/read verification step. MCPUI.md replaces the "no annotations" statement with concrete bridge/fallback/injection-safe rendering descriptions. CHANGELOG documents all three 1.21.x releases.
Version bump to 1.21.2
Cargo.toml, mcpb/manifest.json, server.json, docker-compose.prod.yml
Updates the declared version from 1.20.1 to 1.21.2 across the Rust crate manifest, bundle manifest, server descriptor (including OCI image tag), and Docker Compose image default.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant QueryWidget as query_widget.html
  participant HostBridge as window.openai.callTool
  participant ParentFrame as Parent Frame
  participant MCPServer as Cortex MCP Server

  User->>QueryWidget: submit form (query, host, severity, limit)
  QueryWidget->>QueryWidget: disable submit, set status "Searching..."

  alt Host bridge available
    QueryWidget->>HostBridge: callTool("cortex", { action: "search", ... })
    HostBridge->>MCPServer: tools/call action=search
    MCPServer-->>HostBridge: { structuredContent: { logs }, content[0].text }
    HostBridge-->>QueryWidget: result object
  else postMessage fallback
    QueryWidget->>ParentFrame: postMessage({ messageId, type: "mcp-tool-call", params })
    ParentFrame->>MCPServer: tools/call action=search
    MCPServer-->>ParentFrame: response
    ParentFrame-->>QueryWidget: postMessage({ messageId, result })
  end

  QueryWidget->>QueryWidget: normalize to { count, logs }
  QueryWidget->>QueryWidget: render rows via textContent (no innerHTML)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐇 A widget hops into the frame,
With bridges and postMessage tame,
textContent safe from injection's bite,
Severity badges gleam just right —
The rabbit tags v1.21.2 with glee,
And smoke tests confirm what the logs should be! 🌿

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: building an interactive cortex MCP Apps query widget, which is the primary focus of this PR across multiple files including the widget HTML implementation, documentation, tests, and version updates.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/dazzling-hodgkin-b18687

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.

Add a 'MCP Apps query widget' section to README (URI ui://cortex/query-widget,
MIME text/html;profile=mcp-app, progressive-enhancement framing, JSON-RPC
verification, 'simple search UI not a dashboard') and a verify step in
docs/SETUP.md. Add host-agnostic widget wire-contract checks to
scripts/smoke-test.sh (resources/list, resources/read MIME+anchors,
tools/list _meta.ui.resourceUri, tools/call action=search structuredContent) —
no browser/Node/named UI host required. Verified live against a worktree build.

Bump 1.21.0 -> 1.21.1.
@jmagar

jmagar commented Jun 15, 2026

Copy link
Copy Markdown
Collaborator Author

Updated: this branch now also contains syslog-mcp-yi66.3 — README MCP Apps query widget section + docs/SETUP.md verify step, and host-agnostic widget wire-contract checks in scripts/smoke-test.sh (resources/list, resources/read MIME+anchors, tools/list _meta.ui.resourceUri, tools/call action=search structuredContent). Live-verified against a worktree build. Epic syslog-mcp-yi66 is now complete (3/3). Version 1.21.0 → 1.21.1.

…lpers

test_state/mounted_state now delegate to a single make_state(auth_policy)
builder; fold the byte-identical rmcp_router_no_auth_middleware into rmcp_router
(call sites already document the no-auth-middleware intent inline). Test-only,
no behavior change — all 36 rmcp server tests pass. Bump 1.21.1 -> 1.21.2.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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 `@CHANGELOG.md`:
- Line 10: The [Unreleased] compare link reference at the bottom of the
CHANGELOG.md file still points to v1.20.0 as the base comparison version, but
now that version 1.21.2 has been released, this link needs to be updated. Find
the [Unreleased] link reference (typically at the end of the file) and update
its comparison range to use v1.21.2 as the base version instead of v1.20.0, so
the link compares changes between v1.21.2 and HEAD.
🪄 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: ASSERTIVE

Plan: Pro

Run ID: 258250cd-f310-4fc9-9c93-635f23afcf0d

📥 Commits

Reviewing files that changed from the base of the PR and between d7c9022 and 07ae4ae.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock and included by **/*
📒 Files selected for processing (11)
  • CHANGELOG.md
  • Cargo.toml
  • README.md
  • docker-compose.prod.yml
  • docs/SETUP.md
  • docs/mcp/MCPUI.md
  • mcpb/manifest.json
  • scripts/smoke-test.sh
  • server.json
  • src/mcp/rmcp_server_tests.rs
  • src/mcp/ui/query_widget.html

Comment thread CHANGELOG.md

## [Unreleased]

## [1.21.2] - 2026-06-15

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 | 🟡 Minor | ⚡ Quick win

Update the Unreleased compare target.

The changelog now includes 1.21.2, but the [Unreleased] link still compares against v1.20.0. That makes the release diff range misleading; point it at v1.21.2 instead.

Suggested fix
-[Unreleased]: https://github.com/jmagar/cortex/compare/v1.20.0...HEAD
+[Unreleased]: https://github.com/jmagar/cortex/compare/v1.21.2...HEAD
📝 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
## [1.21.2] - 2026-06-15
[Unreleased]: https://github.com/jmagar/cortex/compare/v1.21.2...HEAD
🤖 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 `@CHANGELOG.md` at line 10, The [Unreleased] compare link reference at the
bottom of the CHANGELOG.md file still points to v1.20.0 as the base comparison
version, but now that version 1.21.2 has been released, this link needs to be
updated. Find the [Unreleased] link reference (typically at the end of the file)
and update its comparison range to use v1.21.2 as the base version instead of
v1.20.0, so the link compares changes between v1.21.2 and HEAD.

Address full-PR review follow-ups (PR #74):
- syslog-mcp-7kqp2: add a defense-in-depth CSP to query_widget.html (img/object/
  base/form locked; script-src/connect-src left open so host bridge injection
  isn't broken) and document the origin-bound message handling + ui:// targetOrigin
  '*' constraint in docs/mcp/MCPUI.md.
- syslog-mcp-l0gmx: assert_gte reads its value via stdin instead of python -c
  interpolation; json_get documents its literal-accessor-only contract.
- syslog-mcp-zvvzn: factor PUBLIC_URL McpConfig literals into public_url_config();
  fix a stray tab in smoke-test.sh.

Bump 1.21.2 -> 1.21.3.
@jmagar
jmagar merged commit 99c6c0c into main Jun 15, 2026
13 checks passed
@jmagar
jmagar deleted the claude/dazzling-hodgkin-b18687 branch June 15, 2026 13:54
jmagar added a commit that referenced this pull request Jun 15, 2026
Reconcile the test-coverage branch with main after PRs #73/#74.
All conflicts were version/metadata skew only (no source/test conflicts);
the branch forked at 1.20.1 before main bumped to 1.21.3.

- Resolve Cargo.toml/Cargo.lock/server.json/mcpb/manifest.json/
  docker-compose.prod.yml to main's line, then bump to 1.21.4.
- Consolidate the branch's parallel 1.20.2–1.20.12 CHANGELOG entries
  (never released on main) into a single 1.21.4 entry; keep main's
  1.21.0–1.21.3 entries.

Verified: cargo fmt --check, cargo clippy --all-targets, and the
canonical `cargo nextest run` (1826 passed) all green.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant