Skip to content

Commit 680dd3c

Browse files
authored
Merge pull request #1012 from nteract/chatgpt-plugin
Add renderInteractiveChart tool and widget
2 parents 68f24e5 + 8ce5d16 commit 680dd3c

13 files changed

Lines changed: 1230 additions & 61 deletions

File tree

.clinerules

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
- Install: `npm install semiotic`
55
<!-- semiotic-bundle-sizes:start -->
66
<!-- Auto-generated by scripts/sync-bundle-sizes.mjs — do not edit by hand. -->
7-
- **Use sub-path imports** — `semiotic/xy` (90KB gz), `semiotic/ordinal` (74KB gz), `semiotic/network` (68KB gz), `semiotic/geo` (55KB gz), `semiotic/realtime` (95KB gz), `semiotic/server` (127KB gz), `semiotic/utils` (37KB gz), `semiotic/recipes` (9KB gz), `semiotic/themes` (4KB gz), `semiotic/data` (3KB gz), `semiotic/value` (6KB gz), `semiotic/ai` (246KB gz). Full `semiotic` is 203KB gz. Entry files are pre-bundled and do NOT tree-shake — a single chart imported from `semiotic/ai` still ships nearly the whole bundle, so generate production imports from the family sub-paths.
7+
- **Use sub-path imports** — `semiotic/xy` (90KB gz), `semiotic/ordinal` (74KB gz), `semiotic/network` (68KB gz), `semiotic/geo` (55KB gz), `semiotic/realtime` (95KB gz), `semiotic/server` (128KB gz), `semiotic/utils` (38KB gz), `semiotic/recipes` (9KB gz), `semiotic/themes` (4KB gz), `semiotic/data` (3KB gz), `semiotic/value` (6KB gz), `semiotic/ai` (250KB gz). Full `semiotic` is 203KB gz.
88
<!-- semiotic-bundle-sizes:end -->
99
- CLI: `npx semiotic-ai [--schema|--compact|--examples|--doctor|--audit-a11y]` · MCP: `npx semiotic-mcp`
1010

.cursorrules

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
- Install: `npm install semiotic`
55
<!-- semiotic-bundle-sizes:start -->
66
<!-- Auto-generated by scripts/sync-bundle-sizes.mjs — do not edit by hand. -->
7-
- **Use sub-path imports** — `semiotic/xy` (90KB gz), `semiotic/ordinal` (74KB gz), `semiotic/network` (68KB gz), `semiotic/geo` (55KB gz), `semiotic/realtime` (95KB gz), `semiotic/server` (127KB gz), `semiotic/utils` (37KB gz), `semiotic/recipes` (9KB gz), `semiotic/themes` (4KB gz), `semiotic/data` (3KB gz), `semiotic/value` (6KB gz), `semiotic/ai` (246KB gz). Full `semiotic` is 203KB gz. Entry files are pre-bundled and do NOT tree-shake — a single chart imported from `semiotic/ai` still ships nearly the whole bundle, so generate production imports from the family sub-paths.
7+
- **Use sub-path imports** — `semiotic/xy` (90KB gz), `semiotic/ordinal` (74KB gz), `semiotic/network` (68KB gz), `semiotic/geo` (55KB gz), `semiotic/realtime` (95KB gz), `semiotic/server` (128KB gz), `semiotic/utils` (38KB gz), `semiotic/recipes` (9KB gz), `semiotic/themes` (4KB gz), `semiotic/data` (3KB gz), `semiotic/value` (6KB gz), `semiotic/ai` (250KB gz). Full `semiotic` is 203KB gz.
88
<!-- semiotic-bundle-sizes:end -->
99
- CLI: `npx semiotic-ai [--schema|--compact|--examples|--doctor|--audit-a11y]` · MCP: `npx semiotic-mcp`
1010

.github/copilot-instructions.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
- Install: `npm install semiotic`
55
<!-- semiotic-bundle-sizes:start -->
66
<!-- Auto-generated by scripts/sync-bundle-sizes.mjs — do not edit by hand. -->
7-
- **Use sub-path imports**`semiotic/xy` (90KB gz), `semiotic/ordinal` (74KB gz), `semiotic/network` (68KB gz), `semiotic/geo` (55KB gz), `semiotic/realtime` (95KB gz), `semiotic/server` (127KB gz), `semiotic/utils` (37KB gz), `semiotic/recipes` (9KB gz), `semiotic/themes` (4KB gz), `semiotic/data` (3KB gz), `semiotic/value` (6KB gz), `semiotic/ai` (246KB gz). Full `semiotic` is 203KB gz. Entry files are pre-bundled and do NOT tree-shake — a single chart imported from `semiotic/ai` still ships nearly the whole bundle, so generate production imports from the family sub-paths.
7+
- **Use sub-path imports**`semiotic/xy` (90KB gz), `semiotic/ordinal` (74KB gz), `semiotic/network` (68KB gz), `semiotic/geo` (55KB gz), `semiotic/realtime` (95KB gz), `semiotic/server` (128KB gz), `semiotic/utils` (38KB gz), `semiotic/recipes` (9KB gz), `semiotic/themes` (4KB gz), `semiotic/data` (3KB gz), `semiotic/value` (6KB gz), `semiotic/ai` (250KB gz). Full `semiotic` is 203KB gz.
88
<!-- semiotic-bundle-sizes:end -->
99
- CLI: `npx semiotic-ai [--schema|--compact|--examples|--doctor|--audit-a11y]` · MCP: `npx semiotic-mcp`
1010

.windsurfrules

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
- Install: `npm install semiotic`
55
<!-- semiotic-bundle-sizes:start -->
66
<!-- Auto-generated by scripts/sync-bundle-sizes.mjs — do not edit by hand. -->
7-
- **Use sub-path imports** — `semiotic/xy` (90KB gz), `semiotic/ordinal` (74KB gz), `semiotic/network` (68KB gz), `semiotic/geo` (55KB gz), `semiotic/realtime` (95KB gz), `semiotic/server` (127KB gz), `semiotic/utils` (37KB gz), `semiotic/recipes` (9KB gz), `semiotic/themes` (4KB gz), `semiotic/data` (3KB gz), `semiotic/value` (6KB gz), `semiotic/ai` (246KB gz). Full `semiotic` is 203KB gz. Entry files are pre-bundled and do NOT tree-shake — a single chart imported from `semiotic/ai` still ships nearly the whole bundle, so generate production imports from the family sub-paths.
7+
- **Use sub-path imports** — `semiotic/xy` (90KB gz), `semiotic/ordinal` (74KB gz), `semiotic/network` (68KB gz), `semiotic/geo` (55KB gz), `semiotic/realtime` (95KB gz), `semiotic/server` (128KB gz), `semiotic/utils` (38KB gz), `semiotic/recipes` (9KB gz), `semiotic/themes` (4KB gz), `semiotic/data` (3KB gz), `semiotic/value` (6KB gz), `semiotic/ai` (250KB gz). Full `semiotic` is 203KB gz.
88
<!-- semiotic-bundle-sizes:end -->
99
- CLI: `npx semiotic-ai [--schema|--compact|--examples|--doctor|--audit-a11y]` · MCP: `npx semiotic-mcp`
1010

CHATGPT_APPS_DEPLOYMENT.md

Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
# ChatGPT Apps Deployment Playbook
2+
3+
This guide describes how to turn the `semiotic-mcp` ChatGPT Apps experiment into a public ChatGPT app. The important distinction:
4+
5+
- A public HTTPS MCP endpoint lets you test and share a developer-mode connector.
6+
- "Anyone using ChatGPT can use it" requires OpenAI app review and publication through the OpenAI Platform Dashboard.
7+
8+
## What This Repo Provides
9+
10+
The current MCP server already exposes:
11+
12+
- `renderInteractiveChart` — renders a Semiotic chart server-side and returns a ChatGPT/MCP Apps widget payload.
13+
- `ui://semiotic/chart-widget.html` — the `text/html;profile=mcp-app` widget template used by ChatGPT.
14+
- Streamable HTTP mode via `semiotic-mcp --http --port <port>`.
15+
16+
The deployable unit should be this repo or an npm package version that includes these changes.
17+
18+
## 1. Prepare The Production Build
19+
20+
Build from a clean checkout on the host or CI system:
21+
22+
```bash
23+
npm ci
24+
npm run dist:prod
25+
npm run build:mcp
26+
npm run typescript:mcp
27+
npx vitest run src/__tests__/scenarios/mcp-protocol.test.ts
28+
```
29+
30+
Start command:
31+
32+
```bash
33+
node ai/dist/mcp-server.js --http --port "$PORT"
34+
```
35+
36+
If the host does not set `PORT`, use a fixed port:
37+
38+
```bash
39+
node ai/dist/mcp-server.js --http --port 3001
40+
```
41+
42+
The server accepts MCP requests on the HTTP server regardless of path, so a reverse proxy can expose `/mcp` to ChatGPT while forwarding to the Node process.
43+
44+
## 2. Host A Stable HTTPS Endpoint
45+
46+
For developer-mode testing, a tunnel is acceptable. For public submission, use a stable public HTTPS domain, not a local tunnel.
47+
48+
Recommended requirements:
49+
50+
- Public URL: `https://mcp.yourdomain.example/mcp`
51+
- TLS certificate from the hosting provider or reverse proxy.
52+
- Long-lived HTTP responses and server-sent events supported.
53+
- No proxy buffering for MCP streaming responses.
54+
- Logs for request ID, tool name, response status, latency, and uncaught errors.
55+
- Restart policy for the Node process.
56+
57+
Suitable hosts include Fly.io, Render, Railway, Google Cloud Run, Azure Container Apps, Kubernetes, or a VM behind nginx/Caddy. Vercel can work only if the chosen runtime supports the MCP Streamable HTTP behavior without timing out or buffering.
58+
59+
Example reverse proxy shape:
60+
61+
```nginx
62+
location /mcp {
63+
proxy_pass http://127.0.0.1:3001;
64+
proxy_http_version 1.1;
65+
proxy_buffering off;
66+
proxy_set_header Host $host;
67+
proxy_set_header X-Forwarded-Proto https;
68+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
69+
}
70+
```
71+
72+
## 3. Smoke-Test The Endpoint
73+
74+
Use MCP Inspector first:
75+
76+
```bash
77+
npx @modelcontextprotocol/inspector@latest
78+
```
79+
80+
Connect it to:
81+
82+
```text
83+
https://mcp.yourdomain.example/mcp
84+
```
85+
86+
Verify:
87+
88+
- `tools/list` includes `renderInteractiveChart`.
89+
- `resources/list` includes `ui://semiotic/chart-widget.html`.
90+
- Calling `renderInteractiveChart` with a small `BarChart` returns `structuredContent`, `_meta.svg`, and no tool error.
91+
- The widget renders in the inspector without console errors.
92+
93+
Use this minimal test payload:
94+
95+
```json
96+
{
97+
"component": "BarChart",
98+
"props": {
99+
"title": "Revenue by Quarter",
100+
"data": [
101+
{ "quarter": "Q1", "revenue": 120 },
102+
{ "quarter": "Q2", "revenue": 180 }
103+
],
104+
"categoryAccessor": "quarter",
105+
"valueAccessor": "revenue",
106+
"width": 420,
107+
"height": 280
108+
}
109+
}
110+
```
111+
112+
## 4. Connect It In ChatGPT Developer Mode
113+
114+
In ChatGPT:
115+
116+
1. Open `Settings -> Apps & Connectors -> Advanced settings`.
117+
2. Enable Developer Mode if your organization allows it.
118+
3. Go to `Settings -> Connectors -> Create`.
119+
4. Fill in:
120+
- Connector name: `Semiotic Charts`
121+
- Description: `Render interactive Semiotic charts from pasted or generated data. Use for chart selection, configuration diagnosis, static SVG rendering, and interactive chart previews.`
122+
- Connector URL: `https://mcp.yourdomain.example/mcp`
123+
5. Create the connector and confirm ChatGPT discovers the tool list.
124+
6. In a new chat, add the connector with `+ -> More`.
125+
7. Prompt: `Use Semiotic Charts to render an interactive bar chart for this data: ...`
126+
127+
Refresh the connector metadata in ChatGPT settings whenever tool names, descriptions, input schemas, output schemas, or widget metadata change.
128+
129+
## 5. Production Hardening Before Public Submission
130+
131+
Before asking OpenAI to review the app:
132+
133+
- Confirm the endpoint is stable under repeated `initialize`, `tools/list`, `resources/read`, and `tools/call` requests.
134+
- Keep `renderInteractiveChart` read-only and idempotent.
135+
- Do not remove or rename published tools or UI resource URIs once users depend on them.
136+
- Keep the widget CSP exact. The current widget uses no external fetches or assets, so empty `connectDomains` and `resourceDomains` are intentional.
137+
- Put abuse controls in front of the Node process. The server itself ships with open CORS (`*`), no auth, no request body size limit, and no rate limiting — acceptable for developer-mode testing, not for a public endpoint that renders arbitrary payloads. At minimum configure rate limiting and a request body size cap (e.g. 1 MB) at the reverse proxy, and consider an idle-session eviction policy (sessions are only removed when the client closes its transport).
138+
- Add operational monitoring and error alerts.
139+
- Create a privacy policy page, even if the app stores no user data.
140+
- Prepare screenshots showing the widget in ChatGPT.
141+
- Prepare golden prompts and expected outputs for review.
142+
- Test ChatGPT web and mobile layouts.
143+
144+
If auth is added later, implement OAuth before submission. Do not submit a public app that depends on a temporary secret, local environment, or organization-only endpoint.
145+
146+
## 6. Submit For Public ChatGPT Distribution
147+
148+
Use the OpenAI Platform Dashboard app management flow.
149+
150+
Prerequisites:
151+
152+
- Your organization or individual publisher identity is verified.
153+
- You have `api.apps.write` to create/submit drafts.
154+
- You have `api.apps.read` to view drafts and review status.
155+
- The MCP server URL is a real public endpoint OpenAI can reach during review.
156+
- The app complies with the ChatGPT app submission guidelines.
157+
158+
Submission package:
159+
160+
- App name.
161+
- Publisher name.
162+
- Short and long descriptions.
163+
- App icon and screenshots.
164+
- Privacy policy URL.
165+
- Public MCP server URL, usually `https://mcp.yourdomain.example/mcp`.
166+
- Tool list discovered from the endpoint.
167+
- Widget/resource metadata discovered from the endpoint.
168+
- Test account details if auth is required.
169+
- Example prompts and expected behavior.
170+
- Notes explaining that the app renders user-provided or model-generated chart data and does not need external data access.
171+
172+
After approval, click `Publish` in the Platform Dashboard. Publication creates the public ChatGPT app listing and the Codex plugin distribution derived from the approved app metadata.
173+
174+
## 7. Versioning And Maintenance
175+
176+
OpenAI snapshots app metadata during review. The live server still handles tool calls, but changed metadata does not automatically update the published app.
177+
178+
For server-only fixes:
179+
180+
- Safe if tool names, schemas, widget URIs, CSP, and behavior remain backward-compatible.
181+
- Deploy normally.
182+
183+
For metadata or contract changes:
184+
185+
1. Deploy the backward-compatible server change.
186+
2. Create or update a draft app version in the Platform Dashboard.
187+
3. Scan the MCP endpoint.
188+
4. Submit the new version for review.
189+
5. Publish after approval.
190+
191+
Avoid breaking changes:
192+
193+
- Do not remove `renderInteractiveChart`.
194+
- Do not remove `ui://semiotic/chart-widget.html`.
195+
- Do not make required input fields stricter without keeping old calls working.
196+
- Do not change the widget URI unless the old URI remains available.
197+
198+
## References
199+
200+
- OpenAI Apps SDK: Connect from ChatGPT: https://developers.openai.com/apps-sdk/deploy/connect-chatgpt
201+
- OpenAI Apps SDK: Deploy your app: https://developers.openai.com/apps-sdk/deploy
202+
- OpenAI Apps SDK: Test your integration: https://developers.openai.com/apps-sdk/deploy/testing
203+
- OpenAI Apps SDK: Submit and maintain your app: https://developers.openai.com/apps-sdk/deploy/submission
204+
- OpenAI Apps SDK: App submission guidelines: https://developers.openai.com/apps-sdk/app-submission-guidelines

README.md

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -411,13 +411,16 @@ Add to your MCP client config (e.g. `claude_desktop_config.json` for Claude Desk
411411
}
412412
```
413413

414-
No API keys or authentication required. The server runs locally via stdio. HTTP mode is also available for inspectors and web clients: `npx semiotic-mcp --http --port 3001`.
414+
No API keys or authentication required. The server runs locally via stdio. HTTP mode is also available for inspectors, web clients, and ChatGPT Apps SDK experiments: `npx semiotic-mcp --http --port 3001`.
415+
416+
For ChatGPT developer mode, expose the HTTP endpoint over HTTPS with a tunnel and create a connector that points at `https://<your-tunnel>/mcp`. The experimental Apps SDK surface is `renderInteractiveChart`, which returns a `text/html;profile=mcp-app` widget template plus a hidden SVG payload rendered by Semiotic on the MCP server.
415417

416418
### Tools
417419

418420
| Tool | Description |
419421
|------|-------------|
420-
| **`renderChart`** | Render a Semiotic chart to static SVG. Supports the components returned by `getSchema` that are marked `[renderable]`. Pass `{ component: "LineChart", props: { data: [...], xAccessor: "x", yAccessor: "y" } }`. Returns SVG string or validation errors with fix suggestions. |
422+
| **`renderChart`** | Render a Semiotic chart to static SVG. Supports the components returned by `getSchema` that are marked `[renderable]`. Pass `{ component: "LineChart", props: { data: [...], xAccessor: "x", yAccessor: "y" } }`. Returns SVG string plus a "Render evidence" JSON block (mark counts by scene type, resolved axis domains, empty flag, annotation count, accessible name) so agents can verify the chart drew data marks, or validation errors with fix suggestions. |
423+
| **`renderInteractiveChart`** | Render a static-data chart as a ChatGPT Apps widget. Uses the same Semiotic server render path as `renderChart`, then hydrates an iframe UI with fit, zoom, data, hover, and render-evidence controls. |
421424
| **`getSchema`** | Return the prop schema for a specific component. Pass `{ component: "LineChart" }` to get its props, or omit `component` to list all 47 chart schemas. Components marked `[renderable]` are available through `renderChart`; realtime charts require a browser/live environment. |
422425
| **`suggestChart`** | Legacy sample-row recommender. Pass `{ data: [{...}, ...] }` with 1–5 sample objects plus optional broad intent/capability filters. |
423426
| **`suggestCharts`** | Capability-based recommender for bounded row data. Returns ranked chart suggestions with scores, reasons, caveats, import paths, and ready-to-use props. |
@@ -439,6 +442,7 @@ No API keys or authentication required. The server runs locally via stdio. HTTP
439442
| **`semiotic://behavior-contracts`** | Agent-visible semantic rules for color precedence, required prop combinations, push refs, and renderability. |
440443
| **`semiotic://system-prompt`** | Compact AI instructions with import rules, chart props, SSR guidance, and pitfalls. |
441444
| **`semiotic://examples`** | Copy-paste chart examples by data shape. |
445+
| **`ui://semiotic/chart-widget.html`** | ChatGPT Apps / MCP Apps widget template used by `renderInteractiveChart`. |
442446

443447
### Prompts
444448

@@ -490,6 +494,25 @@ Args: {
490494
→ Returns: <svg>...</svg>
491495
```
492496

497+
### Example: render a ChatGPT Apps widget
498+
499+
```
500+
Tool: renderInteractiveChart
501+
Args: {
502+
"component": "BarChart",
503+
"props": {
504+
"title": "Revenue by Quarter",
505+
"data": [
506+
{ "quarter": "Q1", "revenue": 120 },
507+
{ "quarter": "Q2", "revenue": 180 }
508+
],
509+
"categoryAccessor": "quarter",
510+
"valueAccessor": "revenue"
511+
}
512+
}
513+
→ Returns: structured chart summary for the model + hidden SVG/widget metadata for ChatGPT.
514+
```
515+
493516
### Example: diagnose a broken config
494517

495518
```

0 commit comments

Comments
 (0)