Skip to content

Commit 30b2da0

Browse files
committed
updates
1 parent 06c0838 commit 30b2da0

File tree

75 files changed

+1458
-524
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

75 files changed

+1458
-524
lines changed

exercises/01.simple/01.problem.raw-html/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
"@epic-web/invariant": "^1.0.0",
1717
"@mcp-ui/server": "^5.10.0",
1818
"@modelcontextprotocol/sdk": "^1.17.5",
19-
"agents": "^0.0.113",
19+
"agents": "https://pkg.pr.new/cloudflare/agents@426",
2020
"zod": "^3.25.67"
2121
},
2222
"devDependencies": {

exercises/01.simple/01.solution.raw-html/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
"@epic-web/invariant": "^1.0.0",
1717
"@mcp-ui/server": "^5.10.0",
1818
"@modelcontextprotocol/sdk": "^1.17.5",
19-
"agents": "^0.0.113",
19+
"agents": "https://pkg.pr.new/cloudflare/agents@426",
2020
"zod": "^3.25.67"
2121
},
2222
"devDependencies": {

exercises/02.consistent/01.problem.remote-dom/README.mdx

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ const resource = createUIResource({
1818
content: {
1919
type: 'remoteDom',
2020
framework: 'react',
21-
script: `
21+
// some editors will syntax highlight a string followed by the /* js */ comment
22+
script: /* js */ `
2223
const stack = document.createElement('ui-stack');
2324
stack.setAttribute('direction', 'vertical');
2425
stack.setAttribute('spacing', '20');
@@ -50,6 +51,27 @@ const resource = createUIResource({
5051
host application.
5152
</callout-info>
5253

54+
You need to implement the `getTagRemoteDomUIScript` function to create a Remote DOM version of the tag viewer. Here's what you need to know:
55+
56+
- `ui-stack` - Container with layout properties (direction, spacing, align)
57+
- `ui-text` - Text content with automatic styling
58+
59+
```js
60+
const stack = document.createElement('ui-stack')
61+
stack.setAttribute('direction', 'vertical')
62+
stack.setAttribute('spacing', '20')
63+
stack.setAttribute('align', 'center')
64+
65+
const title = document.createElement('ui-text')
66+
title.setAttribute('content', 'Your content here')
67+
stack.appendChild(title)
68+
69+
// "root" is globally available in your script
70+
root.appendChild(stack)
71+
```
72+
73+
There's more available from remote-dom, but we'll keep it simple for now.
74+
5375
📜 [MCP UI
5476
Server
5577
Documentation](https://mcpui.dev/guide/server/typescript/usage-examples#basic-usage).

exercises/02.consistent/01.problem.remote-dom/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
"@epic-web/invariant": "^1.0.0",
1717
"@mcp-ui/server": "^5.10.0",
1818
"@modelcontextprotocol/sdk": "^1.17.5",
19-
"agents": "^0.0.113",
19+
"agents": "https://pkg.pr.new/cloudflare/agents@426",
2020
"zod": "^3.25.67"
2121
},
2222
"devDependencies": {

exercises/02.consistent/01.problem.remote-dom/worker/mcp/tools.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,10 @@ import { type CallToolResult } from '@modelcontextprotocol/sdk/types.js'
1818
import { z } from 'zod'
1919
import { type EpicMeMCP } from './index.ts'
2020
import { suggestTagsSampling } from './sampling.ts'
21+
// 💣 get rid of this:
2122
import { getTagViewUI } from './ui.ts'
23+
// 💰 bring this in instead:
24+
// import { getTagRemoteDomUIScript } from './ui.ts'
2225

2326
export async function initializeTools(agent: EpicMeMCP) {
2427
agent.server.registerTool(
@@ -242,8 +245,11 @@ export async function initializeTools(agent: EpicMeMCP) {
242245
createUIResource({
243246
uri: `ui://view-tag/${id}`,
244247
content: {
248+
// 🐨 instead of rawHtml, use remoteDom:
245249
type: 'rawHtml',
250+
// 🐨 instead of htmlString, use script and await getTagRemoteDomUIScript:
246251
htmlString: await getTagViewUI(agent.db, id),
252+
// 🐨 add framework: 'react'
247253
},
248254
encoding: 'text',
249255
}),

exercises/02.consistent/01.problem.remote-dom/worker/mcp/ui.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,23 @@
11
import { type DBClient } from '@epic-web/epicme-db-client'
22

3+
export async function getTagRemoteDomUIScript(db: DBClient, tagId: number) {
4+
const tag = await db.getTag(tagId)
5+
if (tag) {
6+
return /* js */ `
7+
// 🐨 Create a ui-stack element with vertical direction, spacing 20, and center alignment
8+
// 🐨 Create a ui-text element for the tag name and append it to the stack
9+
// 🐨 Create a ui-text element for the tag description and append it to the stack
10+
// 🐨 Append the stack to the (globally available) "root" element
11+
`.trim()
12+
} else {
13+
return /* js */ `
14+
// 🐨 Create a ui-stack element with vertical direction, spacing 20, and center alignment
15+
// 🐨 Create a ui-text element with content "Tag not found" and append it to the stack
16+
// 🐨 Append the stack to the (globally available) "root" element
17+
`.trim()
18+
}
19+
}
20+
321
export async function getTagViewUI(db: DBClient, tagId: number) {
422
const tag = await db.getTag(tagId)
523
if (tag) {

exercises/02.consistent/01.solution.remote-dom/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
"@epic-web/invariant": "^1.0.0",
1717
"@mcp-ui/server": "^5.10.0",
1818
"@modelcontextprotocol/sdk": "^1.17.5",
19-
"agents": "^0.0.113",
19+
"agents": "https://pkg.pr.new/cloudflare/agents@426",
2020
"zod": "^3.25.67"
2121
},
2222
"devDependencies": {

exercises/02.consistent/01.solution.remote-dom/worker/mcp/ui.ts

Lines changed: 66 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -17,35 +17,84 @@ const description = document.createElement('ui-text');
1717
description.setAttribute('content', ${JSON.stringify(tag.description)});
1818
stack.appendChild(description);
1919
20-
const deleteButton = document.createElement('ui-button');
21-
deleteButton.setAttribute('content', 'Delete Tag');
22-
deleteButton.addEventListener('press', () => {
23-
window.parent.postMessage(
24-
{
25-
type: 'tool',
26-
payload: {
27-
toolName: 'deleteTag',
28-
params: { tagId: tag.id.toString() },
29-
},
30-
},
31-
'*',
32-
)
33-
});
34-
stack.appendChild(deleteButton);
35-
3620
root.appendChild(stack);
3721
`.trim()
3822
} else {
3923
return /* js */ `
40-
console.error('Tag not found');
4124
const stack = document.createElement('ui-stack');
4225
stack.setAttribute('direction', 'vertical');
4326
stack.setAttribute('spacing', '20');
4427
stack.setAttribute('align', 'center');
28+
4529
const title = document.createElement('ui-text');
4630
title.setAttribute('content', 'Tag not found');
4731
stack.appendChild(title);
32+
4833
root.appendChild(stack);
34+
`.trim()
35+
}
36+
}
37+
38+
// we'll just keep this for reference for now...
39+
export async function getTagViewUI(db: DBClient, tagId: number) {
40+
const tag = await db.getTag(tagId)
41+
if (tag) {
42+
return /* html */ `
43+
<html>
44+
<head>
45+
<meta charset="UTF-8">
46+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
47+
<title>Epic Me</title>
48+
<style>
49+
* { margin: 0; padding: 0; box-sizing: border-box; }
50+
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; line-height: 1.6; color: oklch(20% 0.06 320); background-color: oklch(98% 0.02 320); }
51+
.container { max-width: 1200px; margin: 0 auto; padding: 2rem 1rem; }
52+
.title { font-size: 2rem; font-weight: 700; color: oklch(20% 0.06 320); margin-bottom: 2rem; text-align: center; }
53+
.description { text-align: center; color: oklch(40% 0.07 320); }
54+
</style>
55+
</head>
56+
<body>
57+
<div class="container">
58+
<h1 class="title">${tag.name}</h1>
59+
<p class="description">${tag.description}</p>
60+
</div>
61+
</body>
62+
</html>
4963
`
5064
}
65+
66+
return /* html */ `
67+
<html>
68+
<head>
69+
<meta charset="UTF-8">
70+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
71+
<title>Epic Me</title>
72+
<style>
73+
* { margin: 0; padding: 0; box-sizing: border-box; }
74+
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; line-height: 1.6; color: oklch(20% 0.06 320); background-color: oklch(98% 0.02 320); }
75+
.container { max-width: 1200px; margin: 0 auto; padding: 2rem 1rem; }
76+
.title { font-size: 2rem; font-weight: 700; color: oklch(20% 0.06 320); margin-bottom: 2rem; text-align: center; }
77+
.description { text-align: center; color: oklch(40% 0.07 320); }
78+
.error-state { text-align: center; padding: 4rem 2rem; }
79+
.error-state h1 { margin: 1rem 0 2rem; color: oklch(20% 0.06 320); }
80+
.error-icon { color: oklch(60% 0.18 0); width: 3rem; height: 3rem; display: inline-flex; align-items: center; justify-content: center; }
81+
.error-icon svg { width: 100%; height: 100%; }
82+
</style>
83+
</head>
84+
<body>
85+
<div class="container">
86+
<div class="error-state">
87+
<span class="error-icon" data-icon="alert-circle">
88+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
89+
<circle cx="12" cy="12" r="10"/>
90+
<line x1="12" y1="8" x2="12" y2="12"/>
91+
<line x1="12" y1="16" x2="12.01" y2="16"/>
92+
</svg>
93+
</span>
94+
<h1>Tag with id ${tagId} not found</h1>
95+
</div>
96+
</div>
97+
</body>
98+
</html>
99+
`
51100
}

exercises/03.complex/01.problem.iframe/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
"@epic-web/invariant": "^1.0.0",
2020
"@mcp-ui/server": "^5.10.0",
2121
"@modelcontextprotocol/sdk": "^1.17.5",
22-
"agents": "^0.0.113",
22+
"agents": "https://pkg.pr.new/cloudflare/agents@426",
2323
"isbot": "^5.1.30",
2424
"react": "^19.1.1",
2525
"react-dom": "^19.1.1",

exercises/03.complex/01.problem.iframe/worker/index.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,22 @@ export default {
1111
fetch: async (request, env, ctx) => {
1212
const url = new URL(request.url)
1313
if (url.pathname === '/mcp') {
14+
// 🐨 create a headers object based on the request
15+
// - then add the x-origin header set to the url.origin
16+
// - then create a new request with the headers
17+
// 💰 here's how you do that:
18+
// const headers = new Headers(request.headers)
19+
// headers.set('x-origin', url.origin)
20+
// const newRequest = new Request(request, { headers })
21+
1422
return EpicMeMCP.serve('/mcp', {
1523
binding: 'EPIC_ME_MCP_OBJECT',
16-
}).fetch(request, env, ctx)
24+
}).fetch(
25+
// 🐨 pass the newRequest instead of request
26+
request,
27+
env,
28+
ctx,
29+
)
1730
}
1831

1932
return requestHandler(request, {

0 commit comments

Comments
 (0)