-
Notifications
You must be signed in to change notification settings - Fork 781
Adding an example of a timer chatgpt app #606
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
WalkthroughAdds a new Timer ChatGPT App example (Python MCP backend + React TypeScript widget), supporting tool/resource handlers, local server bootstrap, web client components/hooks/types, and adjusts example requirements linking to the local mcp-agent dependency. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant UI as Web Client (React)
participant MCP as MCP Server (Python)
participant OpenAI as OpenAI Host
User->>UI: interact (start/reset)
UI->>UI: update local state / useWidgetState sync
UI->>OpenAI: (dev/mock) callTool / send state updates
OpenAI->>MCP: CallTool request (routed via OpenAI Apps host)
MCP->>MCP: _call_tool_request processes input, builds response + widget resource
MCP-->>OpenAI: returns tool result (including embedded widget resource)
OpenAI-->>UI: provides widget resource / initial state to client
UI->>UI: countdown loop (useEffect), on complete -> sendFollowUpMessage
OpenAI->>UI: follow-up message displays
Estimated code review effort🎯 4 (Complex) | ⏱️ ~70 minutes
Suggested reviewers
Poem
Pre-merge checks and finishing touches✅ Passed checks (2 passed)
✨ Finishing touches🧪 Generate unit tests (beta)
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. Comment |
Added a gif to enhance the README with visual context.
Adding public no-auth endpoint for users to try
saqadri
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nice!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 12
🧹 Nitpick comments (10)
examples/cloud/chatgpt_apps/timer/web/public/index.html (1)
1-17: Well-structured HTML entry point for the Timer React app.The file follows React conventions with a minimal but complete entry point structure. The metadata is appropriate (charset, viewport, description) and accessibility is properly considered with the
langattribute and noscript fallback.Optional enhancements to consider:
- Verify that the
theme-colorvalue (#000000) aligns with your actual UI color scheme, or update it to match your app's primary theme.- Consider adding a
<link rel="icon" href="/favicon.ico" />if you have a favicon to display in browser tabs.- If your app should work as a Progressive Web App (PWA), consider adding a manifest link:
<link rel="manifest" href="/manifest.json" />.These are optional improvements for enhanced branding and PWA support, but not blockers for functionality.
examples/cloud/chatgpt_apps/timer/web/tsconfig.json (1)
3-3: Update ES5 target to a modern standard for React 18+ compatibility.The TypeScript configuration targets ES5, but uses
react-jsx(line 17), which requires ES2020+ features for proper JSX handling and modern React compatibility. This mismatch may cause transpilation issues or unexpected runtime behavior.Update the target to ES2020 or higher:
- "target": "es5", + "target": "es2020",Alternatively, if ES5 compatibility is a hard requirement, revert
jsxto the olderreactmode (though this is less ideal for modern React 18+).Also applies to: 17-17
examples/cloud/chatgpt_apps/basic_app/main.py (1)
45-52: Consider making asset path resolution more dynamic.The hard-coded asset paths (lines 46–47) include build-specific hashes (
9c62c88b,57005a98) that will become stale each time the web client is rebuilt. While this is acknowledged in the surrounding comments as an intentional example pattern, developers copying this approach may forget to update these hashes after rebuilding.For a production-ready example, consider scanning the build directory to discover and resolve the actual asset files dynamically, or document the need to regenerate these hashes after each build.
examples/cloud/chatgpt_apps/timer/web/README.md (1)
3-25: Generic setup instructions lack timer-specific documentation.The instructions (Install dependencies, Dev Flow, Building) are generic and could apply to any React app. To be helpful for developers, this README should:
- Briefly describe what the timer app does
- Document how to interact with the OpenAI Apps integration
- Explain any environment setup required for MCP integration (as hinted by the AI summary mentioning Python MCP backend)
- Add troubleshooting or configuration notes if applicable
examples/cloud/chatgpt_apps/timer/web/package.json (1)
30-41: Recommend adding Node version constraint.Add an
"engines"field to document the Node.js version requirement and ensure consistency across development and CI environments:"browserslist": { "production": [ ">0.2%", "not dead", "not op_mini all" ], "development": [ "last 1 chrome version", "last 1 firefox version", "last 1 safari version" ] - } + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=9.0.0" + }examples/cloud/chatgpt_apps/timer/web/src/components/App.css (1)
37-70: Remove unused boilerplate styles.The styles for
.App-logo,.App-header,.App-link, and theApp-logo-spinanimation appear to be unused Create React App boilerplate. These classes are not referenced in the App component or Timer components.Apply this diff to remove the unused styles:
-.App-logo { - height: 40vmin; - pointer-events: none; -} - -@media (prefers-reduced-motion: no-preference) { - .App-logo { - animation: App-logo-spin infinite 20s linear; - } -} - -.App-header { - background-color: #282c34; - min-height: 100vh; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - font-size: calc(10px + 2vmin); - color: white; -} - -.App-link { - color: #61dafb; -} - -@keyframes App-logo-spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -}examples/cloud/chatgpt_apps/timer/web/src/components/ui/button.tsx (1)
10-48: Add interactive states for better UX.The button lacks hover, focus, and active states, which are important for accessibility and user feedback. While the transition property is defined, there are no state-specific styles to transition to.
Consider adding interactive states. Here's an approach using inline event handlers or CSS-in-JS:
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>( ({ className, variant = "default", size = "default", ...props }, ref) => { + const [isHovered, setIsHovered] = React.useState(false); + const [isFocused, setIsFocused] = React.useState(false); + const baseStyles: React.CSSProperties = { display: 'inline-flex', alignItems: 'center', justifyContent: 'center', borderRadius: '6px', fontSize: '14px', fontWeight: 500, transition: 'all 0.2s', cursor: 'pointer', border: 'none', outline: 'none', } const variantStyles: React.CSSProperties = { default: { backgroundColor: '#3b82f6', color: 'white', + ...(isHovered && { backgroundColor: '#2563eb' }), + ...(isFocused && { boxShadow: '0 0 0 3px rgba(59, 130, 246, 0.5)' }), }, outline: { backgroundColor: 'transparent', border: '1px solid #e5e7eb', color: '#374151', + ...(isHovered && { backgroundColor: '#f9fafb' }), + ...(isFocused && { boxShadow: '0 0 0 3px rgba(229, 231, 235, 0.5)' }), }, }[variant] return ( <button ref={ref} className={className} + onMouseEnter={() => setIsHovered(true)} + onMouseLeave={() => setIsHovered(false)} + onFocus={() => setIsFocused(true)} + onBlur={() => setIsFocused(false)} style={{ ...baseStyles, ...sizeStyles, ...variantStyles, ...props.style, + ...(props.disabled && { opacity: 0.6, cursor: 'not-allowed' }), }} {...props} /> ) } )examples/cloud/chatgpt_apps/timer/web/src/utils/dev-openai-global.ts (1)
1-68: Consider extracting shared development utilities.This file is identical to
examples/cloud/chatgpt_apps/basic_app/web/src/utils/dev-openai-global.ts(see relevant code snippets). While code duplication in example projects is sometimes acceptable for independence, consider whether these development utilities could be extracted to a shared package or template to improve maintainability.Potential approaches:
- Create a shared
@examples/dev-utilspackage that both examples depend on- Keep the duplication for example independence (current approach)
- Add a comment referencing the other example to keep them in sync during updates
If you choose to keep the duplication, no action is needed—this is just a maintainability consideration.
examples/cloud/chatgpt_apps/timer/web/src/utils/hooks/use-openai-global.ts (1)
8-37: Consider extracting to a shared utility.This hook is identical to the one in
examples/cloud/chatgpt_apps/basic_app/web/src/utils/hooks/use-openai-global.ts. Since multiple ChatGPT app examples will likely need this functionality, consider extracting it to a shared package or common utilities folder to reduce duplication and maintenance burden.examples/cloud/chatgpt_apps/timer/web/src/utils/types.ts (1)
10-97: Consider extracting shared types to a common package.Lines 10-97 define types that are identical to those in
examples/cloud/chatgpt_apps/basic_app/web/src/utils/types.ts. The comment on lines 35-36 acknowledges this duplication and mentions eventually using a public package.To reduce maintenance burden across multiple ChatGPT app examples, consider:
- Creating a shared types package in
examples/cloud/chatgpt_apps/shared/or similar- Exporting common types from a single source
- Importing them in both timer and basic_app examples
This will ensure consistency and simplify updates when the public package becomes available.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (2)
examples/cloud/chatgpt_apps/basic_app/web/yarn.lockis excluded by!**/yarn.lock,!**/*.lockexamples/cloud/chatgpt_apps/timer/web/yarn.lockis excluded by!**/yarn.lock,!**/*.lock
📒 Files selected for processing (25)
examples/cloud/chatgpt_app/requirements.txt(0 hunks)examples/cloud/chatgpt_apps/basic_app/main.py(1 hunks)examples/cloud/chatgpt_apps/basic_app/requirements.txt(1 hunks)examples/cloud/chatgpt_apps/timer/README.md(1 hunks)examples/cloud/chatgpt_apps/timer/main.py(1 hunks)examples/cloud/chatgpt_apps/timer/mcp_agent.config.yaml(1 hunks)examples/cloud/chatgpt_apps/timer/requirements.txt(1 hunks)examples/cloud/chatgpt_apps/timer/web/.gitignore(1 hunks)examples/cloud/chatgpt_apps/timer/web/README.md(1 hunks)examples/cloud/chatgpt_apps/timer/web/package.json(1 hunks)examples/cloud/chatgpt_apps/timer/web/public/index.html(1 hunks)examples/cloud/chatgpt_apps/timer/web/src/components/App.css(1 hunks)examples/cloud/chatgpt_apps/timer/web/src/components/App.tsx(1 hunks)examples/cloud/chatgpt_apps/timer/web/src/components/Timer.css(1 hunks)examples/cloud/chatgpt_apps/timer/web/src/components/Timer.tsx(1 hunks)examples/cloud/chatgpt_apps/timer/web/src/components/ui/button.tsx(1 hunks)examples/cloud/chatgpt_apps/timer/web/src/components/ui/card.tsx(1 hunks)examples/cloud/chatgpt_apps/timer/web/src/index.css(1 hunks)examples/cloud/chatgpt_apps/timer/web/src/index.tsx(1 hunks)examples/cloud/chatgpt_apps/timer/web/src/utils/dev-openai-global.ts(1 hunks)examples/cloud/chatgpt_apps/timer/web/src/utils/hooks/use-openai-global.ts(1 hunks)examples/cloud/chatgpt_apps/timer/web/src/utils/hooks/use-theme.ts(1 hunks)examples/cloud/chatgpt_apps/timer/web/src/utils/hooks/use-widget-state.ts(1 hunks)examples/cloud/chatgpt_apps/timer/web/src/utils/types.ts(1 hunks)examples/cloud/chatgpt_apps/timer/web/tsconfig.json(1 hunks)
💤 Files with no reviewable changes (1)
- examples/cloud/chatgpt_app/requirements.txt
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-07-22T18:59:49.368Z
Learnt from: CR
Repo: lastmile-ai/mcp-agent PR: 0
File: examples/usecases/reliable_conversation/CLAUDE.md:0-0
Timestamp: 2025-07-22T18:59:49.368Z
Learning: Applies to examples/usecases/reliable_conversation/examples/reliable_conversation/src/**/*.py : Use mcp-agent's Agent abstraction for ALL LLM interactions, including quality evaluation, to ensure consistent tool access, logging, and error handling.
Applied to files:
examples/cloud/chatgpt_apps/basic_app/requirements.txtexamples/cloud/chatgpt_apps/timer/requirements.txt
📚 Learning: 2025-07-22T18:59:49.368Z
Learnt from: CR
Repo: lastmile-ai/mcp-agent PR: 0
File: examples/usecases/reliable_conversation/CLAUDE.md:0-0
Timestamp: 2025-07-22T18:59:49.368Z
Learning: Applies to examples/usecases/reliable_conversation/examples/reliable_conversation/src/utils/config.py : Configuration values such as quality_threshold, max_refinement_attempts, consolidation_interval, and evaluator_model_provider must be loaded from mcp_agent.config.yaml.
Applied to files:
examples/cloud/chatgpt_apps/timer/mcp_agent.config.yaml
🧬 Code graph analysis (8)
examples/cloud/chatgpt_apps/timer/web/src/utils/hooks/use-theme.ts (2)
examples/cloud/chatgpt_apps/timer/web/src/utils/types.ts (1)
Theme(48-48)examples/cloud/chatgpt_apps/timer/web/src/utils/hooks/use-openai-global.ts (1)
useOpenAiGlobal(8-37)
examples/cloud/chatgpt_apps/timer/web/src/index.tsx (1)
examples/cloud/chatgpt_apps/timer/web/src/utils/dev-openai-global.ts (1)
setupDevOpenAiGlobal(7-68)
examples/cloud/chatgpt_apps/timer/web/src/utils/hooks/use-widget-state.ts (2)
examples/cloud/chatgpt_apps/timer/web/src/utils/types.ts (1)
UnknownObject(46-46)examples/cloud/chatgpt_apps/timer/web/src/utils/hooks/use-openai-global.ts (1)
useOpenAiGlobal(8-37)
examples/cloud/chatgpt_apps/timer/web/src/utils/hooks/use-openai-global.ts (2)
examples/cloud/chatgpt_apps/basic_app/web/src/utils/hooks/use-openai-global.ts (1)
useOpenAiGlobal(8-37)examples/cloud/chatgpt_apps/timer/web/src/utils/types.ts (3)
OpenAiGlobals(10-33)SetGlobalsEvent(93-97)SET_GLOBALS_EVENT_TYPE(92-92)
examples/cloud/chatgpt_apps/timer/web/src/components/App.tsx (5)
examples/cloud/chatgpt_apps/timer/web/src/utils/hooks/use-theme.ts (1)
useTheme(4-6)examples/cloud/chatgpt_apps/timer/web/src/utils/hooks/use-openai-global.ts (1)
useOpenAiGlobal(8-37)examples/cloud/chatgpt_apps/timer/web/src/utils/types.ts (1)
TimerWidgetState(1-8)examples/cloud/chatgpt_apps/timer/web/src/utils/hooks/use-widget-state.ts (1)
useWidgetState(13-45)examples/cloud/chatgpt_apps/timer/web/src/components/Timer.tsx (1)
Timer(14-160)
examples/cloud/chatgpt_apps/timer/web/src/utils/dev-openai-global.ts (2)
examples/cloud/chatgpt_apps/basic_app/web/src/utils/dev-openai-global.ts (1)
setupDevOpenAiGlobal(7-68)examples/cloud/chatgpt_apps/timer/web/src/utils/types.ts (1)
OpenAiGlobals(10-33)
examples/cloud/chatgpt_apps/timer/web/src/utils/types.ts (1)
examples/cloud/chatgpt_apps/basic_app/web/src/utils/types.ts (13)
OpenAiGlobals(5-28)UnknownObject(41-41)Theme(43-43)UserAgent(58-64)DisplayMode(67-67)SafeArea(52-54)CallTool(81-84)RequestDisplayMode(68-74)SafeAreaInsets(45-50)DeviceType(56-56)CallToolResponse(76-78)SET_GLOBALS_EVENT_TYPE(87-87)SetGlobalsEvent(88-92)
examples/cloud/chatgpt_apps/timer/main.py (2)
src/mcp_agent/server/app_server.py (2)
app(280-282)create_mcp_server_for_app(642-2361)examples/cloud/chatgpt_apps/basic_app/main.py (4)
_resource_description(96-97)_tool_meta(112-120)_embedded_widget_resource(100-109)main(157-191)
🪛 markdownlint-cli2 (0.18.1)
examples/cloud/chatgpt_apps/timer/README.md
110-110: Bare URL used
(MD034, no-bare-urls)
111-111: Bare URL used
(MD034, no-bare-urls)
🔇 Additional comments (9)
examples/cloud/chatgpt_apps/timer/web/.gitignore (1)
1-23: Solid .gitignore configuration for a Node.js/React project.The file is well-organized with clear sections covering dependencies, testing artifacts, production builds, and miscellaneous files. All standard patterns for a React/TypeScript web application are appropriately included, and the reference to GitHub's documentation is helpful for maintainers.
examples/cloud/chatgpt_apps/basic_app/main.py (1)
195-195: Trailing newline addition looks good.Minimal formatting improvement—no functional changes.
examples/cloud/chatgpt_apps/timer/web/package.json (2)
20-23: Good: Standard React development and build scripts.The scripts are correctly configured for a Create React App template.
24-29: Good: Standard ESLint configuration for React.The ESLint config appropriately extends the Create React App presets.
examples/cloud/chatgpt_apps/timer/web/src/index.css (1)
1-13: LGTM!Standard global CSS reset with appropriate font stacks and font smoothing properties.
examples/cloud/chatgpt_apps/timer/web/src/utils/hooks/use-theme.ts (1)
1-6: LGTM!Clean hook implementation with appropriate default fallback to "light" theme.
examples/cloud/chatgpt_apps/timer/web/src/components/Timer.css (1)
1-143: LGTM!Comprehensive styling with proper dark theme support, accessibility considerations (completed state with pulsing animation), and a clean grid-based layout. The completed state styling provides good visual feedback.
examples/cloud/chatgpt_apps/timer/web/src/components/ui/card.tsx (1)
3-58: LGTM!The Card components follow React best practices with proper ref forwarding, style composition, and displayName assignments. The inline style approach with spread override pattern is clean and flexible.
examples/cloud/chatgpt_apps/timer/web/src/components/Timer.tsx (1)
92-92: Verify callback stability for onTimerUpdate.The
onTimerUpdatecallback is included in the dependency array but may not be stable across renders if the parent doesn't memoize it. This could cause the effect to re-run unnecessarily, clearing and restarting the interval.Ensure the parent component wraps
onTimerUpdateinuseCallback, or consider usinguseRefto store the latest callback:const onTimerUpdateRef = useRef(onTimerUpdate); useEffect(() => { onTimerUpdateRef.current = onTimerUpdate; }, [onTimerUpdate]);Then use
onTimerUpdateRef.currentin the effect and removeonTimerUpdatefrom the dependency array.
| @@ -0,0 +1,2 @@ | |||
| # Core framework dependency | |||
| mcp-agent @ file://../../../../ # Link to the local mcp-agent project root | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix invalid local dependency reference
pip treats file:// URIs as absolute paths; combining the scheme with a relative path makes installation fail on every environment outside your workstation. RFC 3986 also disallows relative paths for the file scheme, so this line cannot be resolved when someone runs uv pip install -r requirements.txt.(github.com)
Switch to a plain relative path (or compute an absolute path at runtime) so local installs and CI builds succeed:
-mcp-agent @ file://../../../../ # Link to the local mcp-agent project root
+mcp-agent @ ../../../../ # Link to the local mcp-agent project root📝 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.
| mcp-agent @ file://../../../../ # Link to the local mcp-agent project root | |
| mcp-agent @ ../../../../ # Link to the local mcp-agent project root |
🤖 Prompt for AI Agents
examples/cloud/chatgpt_apps/basic_app/requirements.txt lines 2-2: the dependency
uses a file:// URI with a relative path which pip rejects; change the line to
reference the local package with a plain relative path (or an editable install)
so it works across environments — e.g. replace "mcp-agent @ file://../../../../"
with "mcp-agent @ ../../../../" or use " -e ../../../../ " / "mcp-agent @
../../../../#egg=mcp-agent" depending on desired install style, then verify pip
install -r requirements.txt succeeds in CI.
| JS_PATH = ASSETS_DIR / "js" / "main.50dd757e.js" | ||
| CSS_PATH = ASSETS_DIR / "css" / "main.bf8e60c9.css" | ||
|
|
||
|
|
||
| # METHOD 1: Inline the JS and CSS into the HTML template | ||
| TIMER_JS = JS_PATH.read_text(encoding="utf-8") | ||
| TIMER_CSS = CSS_PATH.read_text(encoding="utf-8") | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Resolve brittle asset lookups
main.50dd757e.js and main.bf8e60c9.css are hashed outputs from the React build. Their names change whenever the bundle contents change, so the current code starts throwing FileNotFoundError after the next yarn build. Create React App explicitly documents the hash-in-filename behavior, so we need to discover the generated files at runtime (e.g., via glob or asset-manifest.json) instead of freezing the old hash.(create-react-app.dev)
A minimal fix is to glob for main.*.js/css and fail fast if nothing matches:
-from pathlib import Path
+from pathlib import Path
+from typing import Iterable
…
-# Make sure these paths align with the build output paths (dynamic per build)
-JS_PATH = ASSETS_DIR / "js" / "main.50dd757e.js"
-CSS_PATH = ASSETS_DIR / "css" / "main.bf8e60c9.css"
+def _resolve_bundle_file(subdir: str, pattern: str) -> Path:
+ matches: Iterable[Path] = (ASSETS_DIR / subdir).glob(pattern)
+ try:
+ return max(matches)
+ except ValueError as exc:
+ raise FileNotFoundError(
+ f"No assets matching {pattern} under {ASSETS_DIR/subdir}. Did you run `yarn build`?"
+ ) from exc
+
+JS_PATH = _resolve_bundle_file("js", "main.*.js")
+CSS_PATH = _resolve_bundle_file("css", "main.*.css")Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In examples/cloud/chatgpt_apps/timer/main.py around lines 46 to 53, the code
hardcodes hashed asset filenames which will break after new builds; replace the
static paths with a runtime discovery (e.g., glob the ASSETS_DIR/js/main.*.js
and ASSETS_DIR/css/main.*.css or read asset-manifest.json) to find the generated
filenames, pick the matched file (fail fast with a clear FileNotFoundError if
none), and then read_text() from the discovered Path instead of the hardcoded
JS_PATH/CSS_PATH.
| TIMER_JS = JS_PATH.read_text(encoding="utf-8") | ||
| TIMER_CSS = CSS_PATH.read_text(encoding="utf-8") | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Load bundle contents lazily
Because TIMER_JS/TIMER_CSS are read at import time, any missing build artifacts raise before main() can emit a helpful error. That makes uv run main.py unusable right after cloning the repo. Consider delaying the read_text() calls until runtime (e.g., inside _embedded_widget_resource() or a helper that first checks ASSETS_DIR.exists()) so the friendly “Please build the web client…” message can run. A quick fix is to wrap the reads in a helper:
-TIMER_JS = JS_PATH.read_text(encoding="utf-8")
-TIMER_CSS = CSS_PATH.read_text(encoding="utf-8")
+def _load_timer_assets() -> tuple[str, str]:
+ if not ASSETS_DIR.exists():
+ raise FileNotFoundError(
+ f"Assets directory not found at {ASSETS_DIR}. "
+ "Please build the web client before running the server."
+ )
+ return (
+ JS_PATH.read_text(encoding="utf-8"),
+ CSS_PATH.read_text(encoding="utf-8"),
+ )
+
+TIMER_JS, TIMER_CSS = _load_timer_assets()🤖 Prompt for AI Agents
In examples/cloud/chatgpt_apps/timer/main.py around lines 51 to 53, TIMER_JS and
TIMER_CSS are read at import time which causes missing build artifacts to raise
before main() can show the friendly build message; change to lazy-loading by
removing top-level read_text() calls and instead read the files at runtime
(e.g., inside _embedded_widget_resource() or a small helper function) that first
checks ASSETS_DIR.exists() and only then calls read_text(); ensure any file-read
errors are caught and rethrown or converted into the existing user-facing
"Please build the web client..." error so running uv run main.py after clone
doesn't crash during import.
| <div id="coinflip-root"></div> | ||
| <style> | ||
| {TIMER_CSS} | ||
| </style> | ||
| <script type="module"> | ||
| {TIMER_JS} | ||
| </script> | ||
| """ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix widget root id mismatch
Inline mode renders <div id="coinflip-root">, but the deployed template (and React bundle) target timer-root. The default inline path therefore never mounts the widget. Please swap the id to timer-root (and optionally rename any residual “coinflip” strings) so both inline and deployed modes share the same mounting point:
-<div id="coinflip-root"></div>
+<div id="timer-root"></div>📝 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.
| <div id="coinflip-root"></div> | |
| <style> | |
| {TIMER_CSS} | |
| </style> | |
| <script type="module"> | |
| {TIMER_JS} | |
| </script> | |
| """ | |
| <div id="timer-root"></div> | |
| <style> | |
| {TIMER_CSS} | |
| </style> | |
| <script type="module"> | |
| {TIMER_JS} | |
| </script> | |
| """ |
🤖 Prompt for AI Agents
In examples/cloud/chatgpt_apps/timer/main.py around lines 55–62 the inline HTML
uses <div id="coinflip-root"> but the deployed template and React bundle mount
to timer-root, so the widget never mounts; change the div id to timer-root and
rename any remaining “coinflip” identifiers (IDs, variables, CSS selectors, or
strings) to “timer” so inline and deployed modes use the same mounting point.
| 5. After deployment, update main.py:767 with your actual server URL: | ||
|
|
||
| ```python | ||
| SERVER_URL = "https://<server_id>.deployments.mcp-agent.com" | ||
| ``` | ||
|
|
||
| 6. Switch to using deployed assets (optional but recommended): | ||
|
|
||
| Update main.py:782 to use `DEPLOYED_HTML_TEMPLATE`: | ||
|
|
||
| ```python | ||
| html=DEPLOYED_HTML_TEMPLATE, | ||
| ``` | ||
|
|
||
| Then bump the template uri: | ||
|
|
||
| ```python | ||
| template_uri="ui://widget/timer-<date-string>.html", | ||
| ``` | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Replace stale line-number references
main.py is ~330 lines, so instructions like “update main.py:767” and “main.py:782” point to nowhere, which will send readers on a wild goose chase. Please rewrite these steps to reference the actual symbols (e.g., “update the SERVER_URL constant”) instead of hard-coded editor offsets. For example:
-5. After deployment, update main.py:767 with your actual server URL:
+5. After deployment, update the `SERVER_URL` constant in `main.py` with your actual server URL:
…
-6. Switch to using deployed assets (optional but recommended):
- Update main.py:782 to use `DEPLOYED_HTML_TEMPLATE`:
+6. Switch to using deployed assets (optional but recommended):
+ Update the widget definition in `main.py` to use `DEPLOYED_HTML_TEMPLATE`:🤖 Prompt for AI Agents
In examples/cloud/chatgpt_apps/timer/README.md around lines 156 to 175, the
README uses hard-coded file offsets like "main.py:767" and "main.py:782" which
no longer match the file; replace those references with symbol-based guidance:
instruct readers to update the SERVER_URL constant (e.g., "update the SERVER_URL
constant in main.py to your deployment URL") and to switch to
DEPLOYED_HTML_TEMPLATE by updating the html parameter and template_uri in the
code (e.g., "set html=DEPLOYED_HTML_TEMPLATE and update template_uri to
ui://widget/timer-<date-string>.html"), keeping the instructions
language-agnostic to line numbers and showing the exact constant/parameter names
to change.
| const handleTimerUpdate = (h: number, m: number, s: number, running: boolean) => { | ||
| setWidgetState({ | ||
| hours: h, | ||
| minutes: m, | ||
| seconds: s, | ||
| message: message, | ||
| isRunning: running, | ||
| isPaused: false | ||
| }); | ||
|
|
||
| // Notify the model when timer completes | ||
| if (h === 0 && m === 0 && s === 0 && !running) { | ||
| window.openai?.sendFollowUpMessage({ | ||
| prompt: "The timer has completed!", | ||
| }); | ||
| } | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Prevent duplicate follow-up messages on re-renders.
The handleTimerUpdate callback will send a follow-up message every time the timer reaches zero, but if the component re-renders while the timer is in the completed state (h=0, m=0, s=0, running=false), it may trigger the message multiple times.
Consider tracking whether the completion message has been sent:
function App() {
const theme = useTheme();
const toolOutput = useOpenAiGlobal("toolOutput") as TimerWidgetState | null;
const [widgetState, setWidgetState] = useWidgetState<TimerWidgetState>();
+ const completionNotifiedRef = React.useRef(false);
// Prioritize toolOutput (from MCP server) over widgetState for initial values
const hours = toolOutput?.hours ?? widgetState?.hours ?? 0;
const minutes = toolOutput?.minutes ?? widgetState?.minutes ?? 0;
const seconds = toolOutput?.seconds ?? widgetState?.seconds ?? 0;
const message = toolOutput?.message ?? widgetState?.message ?? "";
+ // Reset notification flag when timer values change
+ React.useEffect(() => {
+ if (hours > 0 || minutes > 0 || seconds > 0) {
+ completionNotifiedRef.current = false;
+ }
+ }, [hours, minutes, seconds]);
+
const handleTimerUpdate = (h: number, m: number, s: number, running: boolean) => {
setWidgetState({
hours: h,
minutes: m,
seconds: s,
message: message,
isRunning: running,
isPaused: false
});
// Notify the model when timer completes
- if (h === 0 && m === 0 && s === 0 && !running) {
+ if (h === 0 && m === 0 && s === 0 && !running && !completionNotifiedRef.current) {
+ completionNotifiedRef.current = true;
window.openai?.sendFollowUpMessage({
prompt: "The timer has completed!",
});
}
};Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In examples/cloud/chatgpt_apps/timer/web/src/components/App.tsx around lines 20
to 36, the handler sends a follow-up every time the timer is observed at 0:0:0
and stopped, causing duplicate messages on re-renders; add a sent-flag (either
in widget state as completionSent or as a useRef) and check it before calling
window.openai.sendFollowUpMessage, set the flag to true immediately after
sending, and reset the flag to false whenever the timer is restarted (non-zero
time or running becomes true) so the completion message is sent exactly once per
completion.
| useEffect(() => { | ||
| if (isRunning) { | ||
| intervalRef.current = setInterval(() => { | ||
| // Use a ref to get current values and calculate new time atomically | ||
| setHours((h) => { | ||
| setMinutes((m) => { | ||
| setSeconds((s) => { | ||
| // Calculate total seconds and decrement | ||
| let totalSeconds = h * 3600 + m * 60 + s - 1; | ||
|
|
||
| // Check if timer completed | ||
| if (totalSeconds <= 0) { | ||
| setIsRunning(false); | ||
| setIsCompleted(true); | ||
| setHours(0); | ||
| setMinutes(0); | ||
| if (onTimerUpdate) { | ||
| onTimerUpdate(0, 0, 0, false); | ||
| } | ||
| return 0; | ||
| } | ||
|
|
||
| // Calculate new time components | ||
| const newHours = Math.floor(totalSeconds / 3600); | ||
| const newMinutes = Math.floor((totalSeconds % 3600) / 60); | ||
| const newSeconds = totalSeconds % 60; | ||
|
|
||
| // Update states | ||
| setHours(newHours); | ||
| setMinutes(newMinutes); | ||
|
|
||
| return newSeconds; | ||
| }); | ||
| return m; | ||
| }); | ||
| return h; | ||
| }); | ||
| }, 1000); | ||
| } else { | ||
| if (intervalRef.current) { | ||
| clearInterval(intervalRef.current); | ||
| intervalRef.current = null; | ||
| } | ||
| } | ||
|
|
||
| return () => { | ||
| if (intervalRef.current) { | ||
| clearInterval(intervalRef.current); | ||
| } | ||
| }; | ||
| }, [isRunning, onTimerUpdate]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
Refactor nested setState calls for atomic timer updates.
The nested setHours, setMinutes, and setSeconds calls (lines 46-78) create multiple state updates per tick and don't guarantee atomic consistency. While React batches updates, the pattern is fragile and difficult to reason about.
Consider using a single state object or useReducer for atomic updates:
- const [hours, setHours] = useState(initialHours);
- const [minutes, setMinutes] = useState(initialMinutes);
- const [seconds, setSeconds] = useState(initialSeconds);
+ const [time, setTime] = useState({
+ hours: initialHours,
+ minutes: initialMinutes,
+ seconds: initialSeconds
+ });
const [isRunning, setIsRunning] = useState(false);
const [isCompleted, setIsCompleted] = useState(false);Then in the interval:
if (isRunning) {
intervalRef.current = setInterval(() => {
- setHours((h) => {
- setMinutes((m) => {
- setSeconds((s) => {
- let totalSeconds = h * 3600 + m * 60 + s - 1;
+ setTime((prevTime) => {
+ let totalSeconds = prevTime.hours * 3600 + prevTime.minutes * 60 + prevTime.seconds - 1;
if (totalSeconds <= 0) {
setIsRunning(false);
setIsCompleted(true);
if (onTimerUpdate) {
onTimerUpdate(0, 0, 0, false);
}
- return 0;
+ return { hours: 0, minutes: 0, seconds: 0 };
}
const newHours = Math.floor(totalSeconds / 3600);
const newMinutes = Math.floor((totalSeconds % 3600) / 60);
const newSeconds = totalSeconds % 60;
- setHours(newHours);
- setMinutes(newMinutes);
- return newSeconds;
- });
- return m;
- });
- return h;
+ return { hours: newHours, minutes: newMinutes, seconds: newSeconds };
});
}, 1000);Committable suggestion skipped: line range outside the PR's diff.
| const root = ReactDOM.createRoot( | ||
| document.getElementById("coinflip-root") as HTMLElement | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
Fix incorrect root element ID.
The root element ID is "coinflip-root" but this is a timer application, not a coinflip app. This appears to be copy-pasted from another example without updating the ID.
Apply this diff to fix the root element ID:
const root = ReactDOM.createRoot(
- document.getElementById("coinflip-root") as HTMLElement
+ document.getElementById("timer-root") as HTMLElement
);Make sure the corresponding HTML file also uses id="timer-root" for the mount point.
Run this script to check if the HTML file needs updating:
🏁 Script executed:
#!/bin/bash
# Search for the root element definition in HTML files
rg -n "coinflip-root" -g "*.html"Length of output: 203
I need to locate the timer app's HTML file and verify its current state:
Let me search for the timer app's HTML file directly in the repository structure:
I need to directly inspect the repository structure for the timer app. Let me run a shell script:
---
🏁 Script executed:
```shell
#!/bin/bash
# Find all HTML files in the timer app directory
fd -e html "examples/cloud/chatgpt_apps/timer" 2>/dev/null || echo "No files found"
# Also check if the timer directory exists
if [ -d "examples/cloud/chatgpt_apps/timer" ]; then
echo "Timer directory found. Listing its structure:"
find examples/cloud/chatgpt_apps/timer -type f | head -20
else
echo "Timer directory does not exist"
fi
Length of output: 1226
Now let me check the actual content of both files to verify if there's a mismatch:
---
🏁 Script executed:
```shell
#!/bin/bash
# Check the HTML file for the root element ID
echo "=== Timer app HTML file (index.html) ==="
cat examples/cloud/chatgpt_apps/timer/web/public/index.html | grep -i "root"
echo ""
echo "=== Timer app TypeScript file (index.tsx) lines 10-12 ==="
sed -n '8,15p' examples/cloud/chatgpt_apps/timer/web/src/index.tsx
Length of output: 297
Fix incorrect root element ID in TypeScript file.
The timer app's TypeScript file searches for "coinflip-root" while the corresponding HTML file defines "timer-root". This ID mismatch prevents the app from mounting correctly.
Apply this diff to fix the root element ID in examples/cloud/chatgpt_apps/timer/web/src/index.tsx:
const root = ReactDOM.createRoot(
- document.getElementById("coinflip-root") as HTMLElement
+ document.getElementById("timer-root") as HTMLElement
);The HTML file at examples/cloud/chatgpt_apps/timer/web/public/index.html already has the correct id="timer-root", so no changes are needed there.
📝 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.
| const root = ReactDOM.createRoot( | |
| document.getElementById("coinflip-root") as HTMLElement | |
| ); | |
| const root = ReactDOM.createRoot( | |
| document.getElementById("timer-root") as HTMLElement | |
| ); |
🤖 Prompt for AI Agents
In examples/cloud/chatgpt_apps/timer/web/src/index.tsx around lines 10 to 12,
the code looks up an element with id "coinflip-root" but the HTML uses id
"timer-root"; update the argument to document.getElementById to "timer-root"
(keeping the HTMLElement type assertion) so the React app mounts to the correct
DOM node.
| const setWidgetState = useCallback((state: SetStateAction<T | null>) => { | ||
| _setWidgetState((prevState) => { | ||
| const newState = typeof state === "function" ? state(prevState) : state; | ||
|
|
||
| if (newState != null) { | ||
| window.openai.setWidgetState(newState); | ||
| } | ||
|
|
||
| return newState; | ||
| }); | ||
| }, []); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add null check for window.openai before calling setWidgetState.
Line 37 calls window.openai.setWidgetState(newState) without verifying that window.openai exists. This will throw a runtime error if the hook is used before the OpenAI global is initialized or during SSR.
Apply this defensive check:
if (newState != null) {
- window.openai.setWidgetState(newState);
+ window.openai?.setWidgetState(newState);
}🤖 Prompt for AI Agents
In examples/cloud/chatgpt_apps/timer/web/src/utils/hooks/use-widget-state.ts
around lines 32 to 42, the code calls window.openai.setWidgetState(newState)
without ensuring the global exists; add a defensive guard so the call only
happens when running in the browser and window.openai is defined (e.g. check
typeof window !== "undefined" && window.openai before calling, or use optional
chaining window.openai?.setWidgetState(newState)); keep the existing behavior
when present and avoid calling into window.openai during SSR or before the
global is initialized.
| export type TimerWidgetState = { | ||
| hours: number; | ||
| minutes: number; | ||
| seconds: number; | ||
| message?: string; | ||
| isRunning: boolean; | ||
| isPaused: boolean; | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
Verify usage of isPaused field.
The TimerWidgetState type includes an isPaused field (line 7), but the Timer component in examples/cloud/chatgpt_apps/timer/web/src/components/Timer.tsx doesn't implement pause functionality. Either implement pause/resume or remove this unused field.
Check if pause functionality is planned for a future update or should be removed:
🏁 Script executed:
#!/bin/bash
# Search for any usage of isPaused in the timer app
rg -n "isPaused" examples/cloud/chatgpt_apps/timer/Length of output: 245
---
🏁 Script executed:
```shell
#!/bin/bash
# Check Timer.tsx for isPaused usage and implementation
rg -n -A5 -B5 "isPaused" examples/cloud/chatgpt_apps/timer/web/src/components/Timer.tsx
Length of output: 0
🏁 Script executed:
#!/bin/bash
# Check App.tsx for isPaused state management and handlers
rg -n -A10 -B5 "isPaused" examples/cloud/chatgpt_apps/timer/web/src/components/App.tsxLength of output: 387
🏁 Script executed:
#!/bin/bash
# Search for pause or resume related functions/handlers in the timer app
rg -n "pause|resume" examples/cloud/chatgpt_apps/timer/web/src/ -iLength of output: 165
---
🏁 Script executed:
```shell
#!/bin/bash
# Check how App.tsx renders Timer component and what props are passed
cat examples/cloud/chatgpt_apps/timer/web/src/components/App.tsx
Length of output: 1647
🏁 Script executed:
#!/bin/bash
# Check Timer.tsx component signature to see if it accepts isPaused prop
cat examples/cloud/chatgpt_apps/timer/web/src/components/Timer.tsx | head -50Length of output: 1739
Remove unused isPaused field from TimerWidgetState type.
The isPaused field is never used. It's initialized to false in App.tsx but never passed to the Timer component, and the Timer component has no pause/resume functionality. Remove this field from the type definition in examples/cloud/chatgpt_apps/timer/web/src/utils/types.ts and from the state initialization in App.tsx:27.
🤖 Prompt for AI Agents
In examples/cloud/chatgpt_apps/timer/web/src/utils/types.ts lines 1-8 and
App.tsx around line 27, remove the unused isPaused field from the
TimerWidgetState type and from the state initialization in App.tsx: delete the
isPaused property from the type definition and remove the isPaused: false entry
where the state is initialized so the state shape matches actual usage and no
unused field remains.
There was a problem hiding this 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
♻️ Duplicate comments (1)
examples/cloud/chatgpt_apps/timer/README.md (1)
158-176: Replace stale line-number references with symbol-based guidance.The hard-coded line offsets (
main.py:767,main.py:782) don't match the actual file structure and will confuse readers. Replace these with symbol names and parameter references for clarity.Apply this diff to fix the deployment instructions:
-5. After deployment, update main.py:767 with your actual server URL: +5. After deployment, update the `SERVER_URL` constant in `main.py` with your actual server URL: ```python SERVER_URL = "https://<server_id>.deployments.mcp-agent.com"
- Switch to using deployed assets (optional but recommended):
-Update main.py:782 to use
DEPLOYED_HTML_TEMPLATE:
+Update the widget definition inmain.pyto useDEPLOYED_HTML_TEMPLATEand settemplate_urito a cache-busting URL:html=DEPLOYED_HTML_TEMPLATE, +template_uri="ui://widget/timer-<date-string>.html",-Then bump the template uri:
-
python -template_uri="ui://widget/timer-<date-string>.html", -Then redeploy:
</blockquote></details> </blockquote></details> <details> <summary>📜 Review details</summary> **Configuration used**: CodeRabbit UI **Review profile**: CHILL **Plan**: Pro <details> <summary>📥 Commits</summary> Reviewing files that changed from the base of the PR and between 04c16c308687701e36d529b7af5eb018e56094f0 and acbd9587bcb775ac33e223e8134f078602ec96f4. </details> <details> <summary>📒 Files selected for processing (1)</summary> * `examples/cloud/chatgpt_apps/timer/README.md` (1 hunks) </details> <details> <summary>🧰 Additional context used</summary> <details> <summary>🧠 Learnings (1)</summary> <details> <summary>📓 Common learnings</summary>Learnt from: CR
Repo: lastmile-ai/mcp-agent PR: 0
File: examples/usecases/reliable_conversation/CLAUDE.md:0-0
Timestamp: 2025-07-22T18:59:49.368Z
Learning: Applies to examples/usecases/reliable_conversation/examples/reliable_conversation/src/**/*.py : Use mcp-agent's Agent abstraction for ALL LLM interactions, including quality evaluation, to ensure consistent tool access, logging, and error handling.</details> </details><details> <summary>🪛 markdownlint-cli2 (0.18.1)</summary> <details> <summary>examples/cloud/chatgpt_apps/timer/README.md</summary> 112-112: Bare URL used (MD034, no-bare-urls) --- 113-113: Bare URL used (MD034, no-bare-urls) </details> </details> </details> </details> <!-- This is an auto-generated comment by CodeRabbit for review status -->
| - Serve the web client at http://127.0.0.1:8000 | ||
| - Serve static assets (JS/CSS) at http://127.0.0.1:8000/static |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wrap bare URLs in angle brackets for markdown compliance.
Lines 112–113 contain bare URLs that violate markdown linting (MD034). Wrap them for consistency with markdown standards.
Apply this diff:
- Serve the web client at http://127.0.0.1:8000
- Serve static assets (JS/CSS) at http://127.0.0.1:8000/static
+ Serve the web client at <http://127.0.0.1:8000>
+ Serve static assets (JS/CSS) at <http://127.0.0.1:8000/static>📝 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.
| - Serve the web client at http://127.0.0.1:8000 | |
| - Serve static assets (JS/CSS) at http://127.0.0.1:8000/static | |
| - Serve the web client at <http://127.0.0.1:8000> | |
| - Serve static assets (JS/CSS) at <http://127.0.0.1:8000/static> |
🧰 Tools
🪛 markdownlint-cli2 (0.18.1)
112-112: Bare URL used
(MD034, no-bare-urls)
113-113: Bare URL used
(MD034, no-bare-urls)
🤖 Prompt for AI Agents
In examples/cloud/chatgpt_apps/timer/README.md around lines 112 to 113, the two
bare URLs violate MD034; wrap each URL in angle brackets (e.g.
<http://127.0.0.1:8000> and <http://127.0.0.1:8000/static>) so they are valid
Markdown links; update those two lines accordingly and save the file.
Summary by CodeRabbit
New Features
Documentation
Updates