Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions .github/workflows/deploy-playground.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
name: Deploy Playground

on:
push:
branches: [main]
workflow_dispatch:

permissions:
contents: read
pages: write
id-token: write

concurrency:
group: pages
cancel-in-progress: true

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: 10
- uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
- run: pnpm install --frozen-lockfile
- name: Build playground
run: pnpm --filter webmcp-react-playground build
env:
GITHUB_PAGES: true
- name: Prepare Pages artifact
run: |
mkdir -p _site/playground
cp -r examples/playground/dist/* _site/playground/
- uses: actions/upload-pages-artifact@v3

deploy:
needs: build
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}playground/
steps:
- id: deployment
uses: actions/deploy-pages@v4
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,6 @@ CLAUDE.md
examples/*/node_modules/
examples/*/dist/
examples/nextjs/.next/
*.tsbuildinfo
*.tsbuildinfo
src/**/*.js
examples/*/src/**/*.js
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ React hooks for exposing typed tools on `navigator.modelContext`.
npm install webmcp-react zod
```

## Playground

Try it live: [**webmcp-react playground**](https://mcpcat.github.io/webmcp-react/playground/)

The playground registers several example tools and includes a DevPanel for testing tool execution. Install the Chrome extension to bridge tools to AI clients like Claude and Cursor.

## Quick start

Wrap your app in `<WebMCPProvider>` and register tools with `useMcpTool`:
Expand Down
2 changes: 2 additions & 0 deletions examples/playground/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { useState } from "react";
import { WebMCPProvider, useMcpTool, useWebMCPStatus } from "webmcp-react";
import { z } from "zod";
import { DevPanel } from "./components/DevPanel";
import { ExtensionBanner } from "./components/ExtensionBanner";
import "./App.css";

function SearchTool() {
Expand Down Expand Up @@ -126,6 +127,7 @@ export default function App() {

return (
<WebMCPProvider name="playground" version="1.0">
<ExtensionBanner />
<div className="app">
<header className="app-header">
<h1>webmcp-react playground</h1>
Expand Down
66 changes: 66 additions & 0 deletions examples/playground/src/components/ExtensionBanner.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
.extension-banner {
padding: 0.5rem 1rem;
font-size: 0.8125rem;
display: flex;
align-items: center;
justify-content: space-between;
gap: 0.75rem;
}

.extension-banner--connected {
background: #0d2818;
border-bottom: 1px solid #1a4d2e;
color: #4ade80;
}

.extension-banner--not-detected {
background: #2d1f00;
border-bottom: 1px solid #4d3800;
color: #fbbf24;
}

.extension-banner__content {
display: flex;
align-items: center;
gap: 0.5rem;
}

.extension-banner__dot {
width: 8px;
height: 8px;
border-radius: 50%;
flex-shrink: 0;
}

.extension-banner--connected .extension-banner__dot {
background: #4ade80;
}

.extension-banner--not-detected .extension-banner__dot {
background: #fbbf24;
}

.extension-banner a {
color: inherit;
text-decoration: underline;
text-underline-offset: 2px;
}

.extension-banner a:hover {
opacity: 0.8;
}

.extension-banner__dismiss {
background: none;
border: none;
color: inherit;
cursor: pointer;
padding: 0.25rem;
font-size: 1rem;
line-height: 1;
opacity: 0.6;
}

.extension-banner__dismiss:hover {
opacity: 1;
}
76 changes: 76 additions & 0 deletions examples/playground/src/components/ExtensionBanner.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { useState, useEffect } from "react";
import "./ExtensionBanner.css";

// TODO: Replace with actual Chrome Web Store URL once published
const CHROME_STORE_URL =
"https://chromewebstore.google.com/detail/webmcp-bridge/EXTENSION_ID_HERE";

const DETECTION_TIMEOUT = 2000;

type Status = "checking" | "connected" | "not-detected";

export function ExtensionBanner() {
const [status, setStatus] = useState<Status>("checking");
const [dismissed, setDismissed] = useState(false);

useEffect(() => {
let timeout: ReturnType<typeof setTimeout>;

function handleMessage(event: MessageEvent) {
if (event.source !== window) return;
if (event.data?.type === "WEBMCP_TOOLS_UPDATED") {
clearTimeout(timeout);
setStatus("connected");
}
}

window.addEventListener("message", handleMessage);

// Ask the extension to send tools
window.postMessage({ type: "WEBMCP_REQUEST_TOOLS" }, window.location.origin);

timeout = setTimeout(() => {
setStatus((prev) => (prev === "checking" ? "not-detected" : prev));
}, DETECTION_TIMEOUT);

return () => {
window.removeEventListener("message", handleMessage);
clearTimeout(timeout);
};
}, []);

if (status === "checking" || dismissed) return null;

if (status === "connected") {
return (
<div className="extension-banner extension-banner--connected">
<div className="extension-banner__content">
<span className="extension-banner__dot" />
WebMCP Bridge extension connected
</div>
</div>
);
}

return (
<div className="extension-banner extension-banner--not-detected">
<div className="extension-banner__content">
<span className="extension-banner__dot" />
<span>
Install the{" "}
<a href={CHROME_STORE_URL} target="_blank" rel="noopener noreferrer">
WebMCP Bridge extension
</a>{" "}
to connect your tools to AI clients
</span>
</div>
<button
className="extension-banner__dismiss"
onClick={() => setDismissed(true)}
aria-label="Dismiss"
>
&times;
</button>
</div>
);
}
1 change: 1 addition & 0 deletions examples/playground/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"jsx": "react-jsx",
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"skipLibCheck": true,
"isolatedModules": true,
Expand Down
1 change: 1 addition & 0 deletions examples/playground/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import react from "@vitejs/plugin-react";
import path from "node:path";

export default defineConfig({
base: process.env.GITHUB_PAGES ? "/webmcp-react/playground/" : "/",
plugins: [react()],
resolve: {
alias: {
Expand Down