korakot commited on
Commit
deb8d3c
Β·
verified Β·
1 Parent(s): de2d28e

Upload app.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. app.py +141 -47
app.py CHANGED
@@ -1,41 +1,126 @@
1
  """render-html MCP Apps server β€” renders arbitrary HTML inline in AI chat.
2
 
3
- Deploy as HF Space, connect as MCP connector in Claude/ChatGPT.
4
- Endpoint: https://korakot-render-html.hf.space/mcp
 
 
5
  """
6
 
7
- from fastmcp import FastMCP
8
- from mcp_ui_server import create_ui_resource
9
- from mcp_ui_server.core import UIResource
10
 
11
  mcp = FastMCP("render-html")
12
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
 
14
- @mcp.tool()
15
- def render_html(html: str, title: str = "Rendered Content") -> list[UIResource]:
 
 
 
 
 
 
 
 
 
 
16
  """Render arbitrary HTML content inline in the conversation.
17
 
18
  Use this to display images, SVG diagrams, charts, styled content,
19
- or any HTML/CSS/JS directly in the chat. The HTML is rendered in
20
- a sandboxed iframe.
21
 
22
  Args:
23
- html: Complete HTML content to render. Can include style, script,
24
- inline styles, SVG, images via img tags, etc.
25
  title: Optional title for the rendered content.
26
  """
27
- return [create_ui_resource({
28
- "uri": f"ui://render-html/{title.lower().replace(' ', '-')}",
29
- "content": {
30
- "type": "rawHtml",
31
- "htmlString": html,
32
- },
33
- "encoding": "text",
34
- })]
35
-
36
-
37
- @mcp.tool()
38
- def render_image(url: str, alt: str = "", width: int = 0) -> list[UIResource]:
 
 
 
 
 
39
  """Display an image from a URL inline in the conversation.
40
 
41
  Args:
@@ -47,36 +132,45 @@ def render_image(url: str, alt: str = "", width: int = 0) -> list[UIResource]:
47
  if width > 0:
48
  style = f'width:{width}px;max-width:100%;height:auto;'
49
 
50
- html = f'''<!DOCTYPE html>
51
- <html><body style="margin:0;display:flex;justify-content:center;align-items:center;min-height:100vh;">
52
- <img src="{url}" alt="{alt}" style="{style}" />
53
- </body></html>'''
54
-
55
- return [create_ui_resource({
56
- "uri": "ui://render-html/image",
57
- "content": {"type": "rawHtml", "htmlString": html},
58
- "encoding": "text",
59
- })]
60
-
61
-
62
- @mcp.tool()
63
- def render_svg(svg: str, title: str = "SVG Diagram") -> list[UIResource]:
 
 
 
 
 
 
 
 
 
64
  """Render an SVG diagram inline in the conversation.
65
 
66
  Args:
67
  svg: SVG markup string.
68
  title: Optional title.
69
  """
70
- html = f'''<!DOCTYPE html>
71
- <html><body style="margin:0;display:flex;justify-content:center;align-items:center;min-height:100vh;background:#fff;">
72
- {svg}
73
- </body></html>'''
74
-
75
- return [create_ui_resource({
76
- "uri": "ui://render-html/svg",
77
- "content": {"type": "rawHtml", "htmlString": html},
78
- "encoding": "text",
79
- })]
80
 
81
 
82
  if __name__ == "__main__":
 
1
  """render-html MCP Apps server β€” renders arbitrary HTML inline in AI chat.
2
 
3
+ Architecture (matching PDF Viewer pattern):
4
+ 1. Resource at ui://render-html/viewer.html β†’ the viewer app (text/html;profile=mcp-app)
5
+ 2. Tools return structuredContent with _meta.ui.resourceUri pointing to viewer
6
+ 3. Viewer receives data via postMessage (ui/notifications/tool-result)
7
  """
8
 
9
+ from mcp.server.fastmcp import FastMCP
10
+ from mcp.types import CallToolResult, TextContent
11
+ import json
12
 
13
  mcp = FastMCP("render-html")
14
 
15
+ # ── The viewer HTML app ──────────────────────────────────────────────
16
+ # This is served as an MCP resource. The host renders it in a sandboxed
17
+ # iframe and sends tool results to it via postMessage.
18
+
19
+ VIEWER_HTML = """<!DOCTYPE html>
20
+ <html lang="en">
21
+ <head>
22
+ <meta charset="UTF-8">
23
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
24
+ <title>Render HTML Viewer</title>
25
+ <style>
26
+ * { margin: 0; padding: 0; box-sizing: border-box; }
27
+ html, body { width: 100%; height: 100%; overflow: auto; }
28
+ #content { width: 100%; min-height: 100%; }
29
+ #loading { display: flex; align-items: center; justify-content: center;
30
+ height: 100vh; font-family: system-ui; color: #666; }
31
+ </style>
32
+ </head>
33
+ <body>
34
+ <div id="loading">Loading...</div>
35
+ <div id="content" style="display:none;"></div>
36
+ <script>
37
+ const contentEl = document.getElementById('content');
38
+ const loadingEl = document.getElementById('loading');
39
+
40
+ function renderContent(data) {
41
+ if (!data) return;
42
+ const html = data.html || data.svg || '';
43
+ if (html) {
44
+ loadingEl.style.display = 'none';
45
+ contentEl.style.display = 'block';
46
+ contentEl.innerHTML = html;
47
+ // Execute any script tags
48
+ contentEl.querySelectorAll('script').forEach(old => {
49
+ const s = document.createElement('script');
50
+ if (old.src) s.src = old.src;
51
+ else s.textContent = old.textContent;
52
+ old.replaceWith(s);
53
+ });
54
+ }
55
+ }
56
+
57
+ // Listen for MCP Apps tool-result notification
58
+ window.addEventListener('message', (event) => {
59
+ if (event.source !== window.parent) return;
60
+ const msg = event.data;
61
+ if (!msg || msg.jsonrpc !== '2.0') return;
62
+
63
+ if (msg.method === 'ui/notifications/tool-result') {
64
+ renderContent(msg.params?.structuredContent);
65
+ }
66
+ }, { passive: true });
67
+ </script>
68
+ </body>
69
+ </html>
70
+ """
71
+
72
+ VIEWER_URI = "ui://render-html/viewer.html"
73
+
74
+
75
+ # ── Register the viewer as an MCP resource ───────────────────────────
76
+ @mcp.resource(
77
+ VIEWER_URI,
78
+ name="HTML Viewer",
79
+ description="Viewer app that renders arbitrary HTML content",
80
+ mime_type="text/html;profile=mcp-app",
81
+ )
82
+ def viewer_resource() -> str:
83
+ return VIEWER_HTML
84
+
85
 
86
+ # ── Tools ────────────────────────────────────────────────────────────
87
+
88
+ @mcp.tool(
89
+ name="render_html",
90
+ annotations={
91
+ "title": "Render HTML",
92
+ "readOnlyHint": True,
93
+ "openWorldHint": True,
94
+ },
95
+ meta={"ui": {"resourceUri": VIEWER_URI}},
96
+ )
97
+ def render_html(html: str, title: str = "Rendered Content") -> CallToolResult:
98
  """Render arbitrary HTML content inline in the conversation.
99
 
100
  Use this to display images, SVG diagrams, charts, styled content,
101
+ or any HTML/CSS/JS directly in the chat.
 
102
 
103
  Args:
104
+ html: Complete HTML content to render.
 
105
  title: Optional title for the rendered content.
106
  """
107
+ return CallToolResult(
108
+ content=[TextContent(type="text", text=f"Rendered: {title}")],
109
+ structuredContent={"html": html, "title": title},
110
+ _meta={"ui": {"resourceUri": VIEWER_URI}},
111
+ )
112
+
113
+
114
+ @mcp.tool(
115
+ name="render_image",
116
+ annotations={
117
+ "title": "Render Image",
118
+ "readOnlyHint": True,
119
+ "openWorldHint": True,
120
+ },
121
+ meta={"ui": {"resourceUri": VIEWER_URI}},
122
+ )
123
+ def render_image(url: str, alt: str = "", width: int = 0) -> CallToolResult:
124
  """Display an image from a URL inline in the conversation.
125
 
126
  Args:
 
132
  if width > 0:
133
  style = f'width:{width}px;max-width:100%;height:auto;'
134
 
135
+ img_html = (
136
+ f'<div style="display:flex;justify-content:center;align-items:center;'
137
+ f'min-height:200px;padding:8px;">'
138
+ f'<img src="{url}" alt="{alt}" style="{style}" />'
139
+ f'</div>'
140
+ )
141
+ return CallToolResult(
142
+ content=[TextContent(type="text", text=f"Image: {alt or url}")],
143
+ structuredContent={"html": img_html, "title": alt or "Image"},
144
+ _meta={"ui": {"resourceUri": VIEWER_URI}},
145
+ )
146
+
147
+
148
+ @mcp.tool(
149
+ name="render_svg",
150
+ annotations={
151
+ "title": "Render SVG",
152
+ "readOnlyHint": True,
153
+ "openWorldHint": True,
154
+ },
155
+ meta={"ui": {"resourceUri": VIEWER_URI}},
156
+ )
157
+ def render_svg(svg: str, title: str = "SVG Diagram") -> CallToolResult:
158
  """Render an SVG diagram inline in the conversation.
159
 
160
  Args:
161
  svg: SVG markup string.
162
  title: Optional title.
163
  """
164
+ wrapped = (
165
+ f'<div style="display:flex;justify-content:center;align-items:center;'
166
+ f'min-height:200px;padding:8px;background:#fff;">'
167
+ f'{svg}</div>'
168
+ )
169
+ return CallToolResult(
170
+ content=[TextContent(type="text", text=f"SVG: {title}")],
171
+ structuredContent={"svg": wrapped, "title": title},
172
+ _meta={"ui": {"resourceUri": VIEWER_URI}},
173
+ )
174
 
175
 
176
  if __name__ == "__main__":