korakot commited on
Commit
ee7487c
Β·
verified Β·
1 Parent(s): 48a7d73

Upload app.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. app.py +79 -31
app.py CHANGED
@@ -2,22 +2,27 @@
2
 
3
  Architecture (matching PDF Viewer pattern):
4
  1. Resource at ui://render-html/viewer.html (text/html;profile=mcp-app)
5
- 2. Tools have _meta.ui.resourceUri AND _meta["ui/resourceUri"] pointing to viewer
6
  3. Tools return structuredContent + outputSchema
7
- 4. Viewer receives data via postMessage (ui/notifications/tool-result)
8
  """
9
 
10
  from mcp.server.fastmcp import FastMCP
11
  from mcp.server.transport_security import TransportSecuritySettings
12
  from mcp.types import CallToolResult, TextContent
13
- from pydantic import BaseModel, Field
14
  from typing import Annotated
15
 
16
  mcp = FastMCP("render-html", transport_security=TransportSecuritySettings(
17
  enable_dns_rebinding_protection=False,
18
  ))
19
 
20
- # ── The viewer HTML app ──────────────────────────────────────────────
 
 
 
 
 
21
 
22
  VIEWER_HTML = """<!DOCTYPE html>
23
  <html lang="en">
@@ -27,46 +32,87 @@ VIEWER_HTML = """<!DOCTYPE html>
27
  <title>Render HTML Viewer</title>
28
  <style>
29
  * { margin: 0; padding: 0; box-sizing: border-box; }
30
- html, body { width: 100%; height: 100%; overflow: auto; }
31
  #content { width: 100%; min-height: 100%; }
32
  #loading { display: flex; align-items: center; justify-content: center;
33
- height: 100vh; font-family: system-ui; color: #666; }
34
  </style>
35
  </head>
36
  <body>
37
- <div id="loading">Loading...</div>
38
  <div id="content" style="display:none;"></div>
39
  <script>
40
- const contentEl = document.getElementById('content');
41
- const loadingEl = document.getElementById('loading');
 
 
42
 
 
43
  function renderContent(data) {
44
  if (!data) return;
45
- const html = data.html || data.svg || '';
46
- if (html) {
47
- loadingEl.style.display = 'none';
48
- contentEl.style.display = 'block';
49
- contentEl.innerHTML = html;
50
- // Execute any script tags
51
- contentEl.querySelectorAll('script').forEach(old => {
52
- const s = document.createElement('script');
53
- if (old.src) s.src = old.src;
54
- else s.textContent = old.textContent;
55
- old.replaceWith(s);
56
- });
 
 
 
 
 
 
 
 
57
  }
58
  }
59
 
60
- // Listen for MCP Apps tool-result notification
61
- window.addEventListener('message', (event) => {
 
 
 
 
 
 
 
 
 
 
 
 
 
62
  if (event.source !== window.parent) return;
63
- const msg = event.data;
64
  if (!msg || msg.jsonrpc !== '2.0') return;
65
 
 
 
 
 
 
 
 
 
 
 
 
66
  if (msg.method === 'ui/notifications/tool-result') {
67
- renderContent(msg.params?.structuredContent);
 
 
 
 
 
68
  }
69
  }, { passive: true });
 
70
  </script>
71
  </body>
72
  </html>
@@ -74,14 +120,14 @@ VIEWER_HTML = """<!DOCTYPE html>
74
 
75
  VIEWER_URI = "ui://render-html/viewer.html"
76
 
77
- # Both forms of the meta key (PDF Viewer uses both)
78
  TOOL_META = {
79
  "ui": {"resourceUri": VIEWER_URI},
80
  "ui/resourceUri": VIEWER_URI,
81
  }
82
 
83
 
84
- # ── Register the viewer as an MCP resource ───────────────────────────
85
  @mcp.resource(
86
  VIEWER_URI,
87
  name="HTML Viewer",
@@ -92,7 +138,7 @@ def viewer_resource() -> str:
92
  return VIEWER_HTML
93
 
94
 
95
- # ── Output schemas ───────────────────────────────────────────────────
96
 
97
  class HtmlOutput(BaseModel):
98
  html: str
@@ -103,7 +149,7 @@ class SvgOutput(BaseModel):
103
  title: str
104
 
105
 
106
- # ── Tools ────────────────────────────────────────────────────────────
107
 
108
  @mcp.tool(
109
  name="render_html",
@@ -118,10 +164,12 @@ def render_html(html: str, title: str = "Rendered Content") -> Annotated[CallToo
118
  """Render arbitrary HTML content inline in the conversation.
119
 
120
  Use this to display images, SVG diagrams, charts, styled content,
121
- or any HTML/CSS/JS directly in the chat.
 
122
 
123
  Args:
124
- html: Complete HTML content to render.
 
125
  title: Optional title for the rendered content.
126
  """
127
  return CallToolResult(
 
2
 
3
  Architecture (matching PDF Viewer pattern):
4
  1. Resource at ui://render-html/viewer.html (text/html;profile=mcp-app)
5
+ 2. Tools have _meta.ui.resourceUri pointing to viewer
6
  3. Tools return structuredContent + outputSchema
7
+ 4. Viewer implements MCP Apps handshake: ui/initialize -> tool-result
8
  """
9
 
10
  from mcp.server.fastmcp import FastMCP
11
  from mcp.server.transport_security import TransportSecuritySettings
12
  from mcp.types import CallToolResult, TextContent
13
+ from pydantic import BaseModel
14
  from typing import Annotated
15
 
16
  mcp = FastMCP("render-html", transport_security=TransportSecuritySettings(
17
  enable_dns_rebinding_protection=False,
18
  ))
19
 
20
+ # -- The viewer HTML app ---------------------------------------------------
21
+ # Implements the MCP Apps lifecycle:
22
+ # 1. View -> Host: ui/initialize (JSON-RPC request)
23
+ # 2. Host -> View: response with host context
24
+ # 3. View -> Host: ui/notifications/initialized
25
+ # 4. Host -> View: ui/notifications/tool-result with structuredContent
26
 
27
  VIEWER_HTML = """<!DOCTYPE html>
28
  <html lang="en">
 
32
  <title>Render HTML Viewer</title>
33
  <style>
34
  * { margin: 0; padding: 0; box-sizing: border-box; }
35
+ html, body { width: 100%; height: 100%; overflow: auto; background: transparent; }
36
  #content { width: 100%; min-height: 100%; }
37
  #loading { display: flex; align-items: center; justify-content: center;
38
+ height: 100vh; font-family: system-ui; color: #666; font-size: 14px; }
39
  </style>
40
  </head>
41
  <body>
42
+ <div id="loading">Loading&#8230;</div>
43
  <div id="content" style="display:none;"></div>
44
  <script>
45
+ (function() {
46
+ var contentEl = document.getElementById('content');
47
+ var loadingEl = document.getElementById('loading');
48
+ var msgId = 1;
49
 
50
+ // -- Render HTML content from structuredContent --
51
  function renderContent(data) {
52
  if (!data) return;
53
+ var html = data.html || data.svg || '';
54
+ if (!html) return;
55
+ loadingEl.style.display = 'none';
56
+ contentEl.style.display = 'block';
57
+ contentEl.innerHTML = html;
58
+ // Re-execute script tags
59
+ var scripts = contentEl.querySelectorAll('script');
60
+ for (var i = 0; i < scripts.length; i++) {
61
+ var old = scripts[i];
62
+ var s = document.createElement('script');
63
+ if (old.src) s.src = old.src;
64
+ else s.textContent = old.textContent;
65
+ old.parentNode.replaceChild(s, old);
66
+ }
67
+ }
68
+
69
+ // -- Send JSON-RPC message to host --
70
+ function sendToHost(msg) {
71
+ if (window.parent && window.parent !== window) {
72
+ window.parent.postMessage(msg, '*');
73
  }
74
  }
75
 
76
+ // -- MCP Apps initialization handshake --
77
+ // Step 1: Send ui/initialize request to host
78
+ var initId = msgId++;
79
+ sendToHost({
80
+ jsonrpc: '2.0',
81
+ id: initId,
82
+ method: 'ui/initialize',
83
+ params: {
84
+ appInfo: { name: 'render-html', version: '1.0.0' },
85
+ capabilities: {}
86
+ }
87
+ });
88
+
89
+ // -- Listen for messages from host --
90
+ window.addEventListener('message', function(event) {
91
  if (event.source !== window.parent) return;
92
+ var msg = event.data;
93
  if (!msg || msg.jsonrpc !== '2.0') return;
94
 
95
+ // Step 2: Host responds to ui/initialize with host context
96
+ if (msg.id === initId && msg.result) {
97
+ // Step 3: Send ui/notifications/initialized
98
+ sendToHost({
99
+ jsonrpc: '2.0',
100
+ method: 'ui/notifications/initialized',
101
+ params: {}
102
+ });
103
+ }
104
+
105
+ // Step 4: Receive tool-result notification
106
  if (msg.method === 'ui/notifications/tool-result') {
107
+ renderContent(msg.params && msg.params.structuredContent);
108
+ }
109
+
110
+ // Also handle tool-input (streaming partial args)
111
+ if (msg.method === 'ui/notifications/tool-input') {
112
+ // Could show preview; for render-html we wait for final result
113
  }
114
  }, { passive: true });
115
+ })();
116
  </script>
117
  </body>
118
  </html>
 
120
 
121
  VIEWER_URI = "ui://render-html/viewer.html"
122
 
123
+ # Both forms of the meta key (matching PDF Viewer)
124
  TOOL_META = {
125
  "ui": {"resourceUri": VIEWER_URI},
126
  "ui/resourceUri": VIEWER_URI,
127
  }
128
 
129
 
130
+ # -- Register the viewer as an MCP resource --------------------------------
131
  @mcp.resource(
132
  VIEWER_URI,
133
  name="HTML Viewer",
 
138
  return VIEWER_HTML
139
 
140
 
141
+ # -- Output schemas --------------------------------------------------------
142
 
143
  class HtmlOutput(BaseModel):
144
  html: str
 
149
  title: str
150
 
151
 
152
+ # -- Tools -----------------------------------------------------------------
153
 
154
  @mcp.tool(
155
  name="render_html",
 
164
  """Render arbitrary HTML content inline in the conversation.
165
 
166
  Use this to display images, SVG diagrams, charts, styled content,
167
+ or any HTML/CSS/JS directly in the chat. The HTML is rendered in
168
+ a sandboxed iframe.
169
 
170
  Args:
171
+ html: Complete HTML content to render. Can include style, script,
172
+ inline styles, SVG, images via img tags, etc.
173
  title: Optional title for the rendered content.
174
  """
175
  return CallToolResult(