Skip to content

mcp-app: replace React/Vite/npm with static HTML+CSS+JS#525

Merged
owtaylor merged 1 commit into
rhel-lightspeed:mainfrom
owtaylor:static-html-css-js
Jul 13, 2026
Merged

mcp-app: replace React/Vite/npm with static HTML+CSS+JS#525
owtaylor merged 1 commit into
rhel-lightspeed:mainfrom
owtaylor:static-html-css-js

Conversation

@owtaylor

@owtaylor owtaylor commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator

Remove the entire mcp-app/ directory (React, Vite, Tailwind, TypeScript, Zod, npm) and replace it with three plain source files assembled by the Python server at serve time:

  • ui_resources/run-script-app.html: static template with CSS/JS placeholders
  • ui_resources/run-script-app.css: plain CSS with host theme variable indirection and light-dark() fallbacks
  • ui_resources/run-script-app.js: vanilla JS module with MCP host protocol, theming, and DOM-based state management

The server inlines CSS and JS into the HTML when serving the resource, producing a single self-contained page (~19KB vs ~279KB with React).

@modelcontextprotocol/ext-apps and @modelcontextprotocol/sdk dependencies are replaced with a direct JSON-RPC 2.0 postMessage implementation.

Also removes Node.js setup and npm build steps from all CI workflows, and the hatch force-include/artifacts config from pyproject.toml.

@owtaylor
owtaylor requested a review from Jazzcort July 9, 2026 13:17
@owtaylor
owtaylor requested a review from a team as a code owner July 9, 2026 13:17
@codecov

codecov Bot commented Jul 9, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

Flag Coverage Δ
unittests 97.52% <100.00%> (+0.12%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
src/linux_mcp_server/server.py 96.75% <100.00%> (+3.89%) ⬆️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Jazzcort
Jazzcort previously approved these changes Jul 10, 2026

@Jazzcort Jazzcort 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.

LGTM! The comments are just some of my thoughts. Feel free to merge this as is. 😁

@@ -0,0 +1,433 @@
export {};

@Jazzcort Jazzcort Jul 10, 2026

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.

I removed this and the app still works fine so maybe it's not needed? 🤔

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

This is a signal (to vscode, for example) that the file is a module and not a classic script.

Unless we enable type checking for this file and add a bunch of /** @type {string|null} */ annotations, the difference in handling is small (or non-existent?). I don't really want to add such annotations... an LLM should be able to maintain the code without that, and it will just make changes harder to review. But I think I'll leave the marker.

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.

Got it! I would like this rather than a lot of annotations like /** @type {string|null} */ 😆

"*",
);
return new Promise((resolve, reject) => {
const timer = opts?.timeout

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.

What do you think if we have a default timeout apply here like

const timeout = opts?.timeout ?? DEFAULT_REQUEST_TIMEOUT_MSEC;
const timer = setTimeout(() => {
    .....
}, timeout);

So all the requests can be cleaned up even if the response is missing. It's a rare case and given the fact that each mcp app's lifecycle is very short. This is not that important but there is also no downside for having a default timeout in every request. What do you think @owtaylor ?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Yeah, make sense to me. I've pushed a new version that does that, and also cleans up sendRequest vs. callServerTool to avoid duplication.

Remove the entire mcp-app/ directory (React, Vite, Tailwind, TypeScript,
Zod, npm) and replace it with three plain source files assembled by the
Python server at serve time:

- ui_resources/run-script-app.html: static template with CSS/JS placeholders
- ui_resources/run-script-app.css: plain CSS with host theme variable
  indirection and light-dark() fallbacks
- ui_resources/run-script-app.js: vanilla JS module with MCP host protocol,
  theming, and DOM-based state management

The server inlines CSS and JS into the HTML when serving the resource,
producing a single self-contained page (~19KB vs ~279KB with React).

@modelcontextprotocol/ext-apps and @modelcontextprotocol/sdk
dependencies are replaced with a direct JSON-RPC 2.0 postMessage implementation.

Also removes Node.js setup and npm build steps from all CI workflows,
and the hatch force-include/artifacts config from pyproject.toml.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

@Jazzcort Jazzcort 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.

LGTM!

@owtaylor
owtaylor merged commit 911cba7 into rhel-lightspeed:main Jul 13, 2026
35 of 36 checks passed
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.

2 participants