@aprovan/mcp-app-server 0.1.0-dev.4d82df8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/.turbo/turbo-build.log +23 -0
  2. package/E2E_TESTING.md +224 -0
  3. package/LICENSE +373 -0
  4. package/README.md +164 -0
  5. package/dist/index.d.ts +128 -0
  6. package/dist/index.js +990 -0
  7. package/dist/index.js.map +1 -0
  8. package/dist/runtime/assets/__vite-browser-external-BIHI7g3E.js +1 -0
  9. package/dist/runtime/assets/index-tRNuU9ap.js +1059 -0
  10. package/dist/runtime/index.html +38 -0
  11. package/dist/server.d.ts +6 -0
  12. package/dist/server.js +1511 -0
  13. package/dist/server.js.map +1 -0
  14. package/dist/shell/shell.js +67 -0
  15. package/docs/widget-preview.png +0 -0
  16. package/e2e/__snapshots__/.gitkeep +0 -0
  17. package/e2e/global-setup.ts +114 -0
  18. package/e2e/global-teardown.ts +15 -0
  19. package/e2e/visual-regression.test.ts +86 -0
  20. package/e2e/widget-smoke.test.ts +109 -0
  21. package/index.html +32 -0
  22. package/package.json +51 -0
  23. package/playwright.config.ts +43 -0
  24. package/src/__tests__/live-update.test.ts +158 -0
  25. package/src/__tests__/local-backend.test.ts +100 -0
  26. package/src/__tests__/memory-backend.ts +144 -0
  27. package/src/__tests__/registry-backend.test.ts +256 -0
  28. package/src/__tests__/services.test.ts +153 -0
  29. package/src/__tests__/shim.test.ts +60 -0
  30. package/src/__tests__/widget-store.test.ts +188 -0
  31. package/src/e2e-visual.ts +148 -0
  32. package/src/index.ts +608 -0
  33. package/src/live-update.ts +150 -0
  34. package/src/logger.ts +44 -0
  35. package/src/reference-widgets/live-dashboard.ts +162 -0
  36. package/src/registry-backend.ts +306 -0
  37. package/src/runtime/index.html +38 -0
  38. package/src/runtime/main.ts +164 -0
  39. package/src/scripts/export-artifacts.ts +51 -0
  40. package/src/server.ts +398 -0
  41. package/src/services.ts +307 -0
  42. package/src/shell/main.ts +172 -0
  43. package/src/shim.ts +106 -0
  44. package/src/tunnel.ts +123 -0
  45. package/src/widget-store/index.ts +10 -0
  46. package/src/widget-store/local-backend.ts +77 -0
  47. package/src/widget-store/store.ts +242 -0
  48. package/src/widget-store/types.ts +53 -0
  49. package/tsconfig.json +10 -0
  50. package/tsup.config.ts +14 -0
  51. package/vite.runtime.config.ts +28 -0
  52. package/vite.shell.config.ts +30 -0
  53. package/vitest.config.ts +7 -0
package/README.md ADDED
@@ -0,0 +1,164 @@
1
+ # @aprovan/mcp-app-server
2
+
3
+ MCP App Server — hosts MCP tools that save and render interactive widgets in Claude Desktop and Claude web, optionally bridged to third-party APIs via the Aprovan Registry.
4
+
5
+ ## Architecture
6
+
7
+ Widgets are stored as **raw, uncompiled** `.tsx`/`.ts` source files and compiled **in the
8
+ browser** at render time using the shared `@aprovan/patchwork-compiler` runtime — the same
9
+ path the chat app uses. The server does no widget compilation, Tailwind injection, or CDN
10
+ resolution; those concerns belong to the compiler + image packages.
11
+
12
+ The MCP Apps protocol imposes two constraints that shape the render path:
13
+
14
+ 1. The resource document Claude renders **must itself be the app** that connects to the host
15
+ (`App.connect()` → `window.parent`), and
16
+ 2. it runs under a **strict CSP with no `unsafe-eval`**, so esbuild-wasm cannot run there.
17
+
18
+ So rendering is split across two pieces (the "bridged" design):
19
+
20
+ - **Shell** (`/shell/shell.js`) — a small bundle of the ext-apps client that is the resource
21
+ document. It connects to Claude (handshake + sizing), and embeds the runtime in a nested,
22
+ **CSP-free** iframe served from the widget host. It relays the widget's
23
+ service / live-update / `updateContext` calls to the host over the ext-apps `App`.
24
+ - **Runtime** (`/runtime/`) — runs inside that nested iframe (no host CSP), fetches the saved
25
+ widget's raw source from `/widget/:name/:hash/files`, compiles it with esbuild-wasm, loads
26
+ the image (Tailwind/React) from the CDN, and mounts it. The widget's `window.patchwork.*` and
27
+ service-namespace calls are forwarded to the shell via `postMessage`.
28
+
29
+ Tools:
30
+
31
+ - `save_widget` persists a widget's raw files (+ manifest) to the widget store and returns the
32
+ shell resource pointed at it (with startup `inputs`).
33
+ - `render_widget` re-renders any previously saved widget by name/hash.
34
+
35
+ ## Quick start
36
+
37
+ ```bash
38
+ pnpm dev # start with tsx watch (hot-reload)
39
+ # or
40
+ pnpm build && pnpm start
41
+ ```
42
+
43
+ Server runs on `http://0.0.0.0:3000` by default.
44
+
45
+ ## Registry integration (APR-61)
46
+
47
+ The server can optionally connect to the [Aprovan Registry](https://github.com/AprovanLabs/registry) MCP server to expose 30+ third-party APIs (GitHub, Slack, Stripe, Datadog, …) as callable widget services.
48
+
49
+ ### Enabling the Registry backend
50
+
51
+ Set `REGISTRY_PROVIDERS` before starting the server:
52
+
53
+ ```bash
54
+ REGISTRY_PROVIDERS=github,slack,stripe pnpm dev
55
+ ```
56
+
57
+ The server will spawn `npx @utdk/mcp` automatically with the requested providers.
58
+
59
+ ### Credential configuration
60
+
61
+ The server forwards all environment variables to the Registry child process, so you only need to set credentials once in the parent environment. Below are the most common providers:
62
+
63
+ | Provider | Required env vars | Notes |
64
+ |----------|-------------------|-------|
65
+ | **GitHub** | `GITHUB_TOKEN` | Personal access token or fine-grained token |
66
+ | **Stripe** | `STRIPE_SECRET_KEY` | Secret key (`sk_test_…` or `sk_live_…`) |
67
+ | **Slack** | `SLACK_CLIENT_ID`, `SLACK_CLIENT_SECRET` | OAuth2 client credentials; flows via client-credentials grant |
68
+ | **Notion** | `NOTION_API_KEY` | Integration token from Notion developer portal |
69
+ | **Jira** | `JIRA_EMAIL`, `JIRA_API_TOKEN` | User email + API token (basic auth) |
70
+ | **Datadog** | `DD_API_KEY`, `DD_APP_KEY` | API key + application key |
71
+ | **HubSpot** | `HUBSPOT_CLIENT_ID`, `HUBSPOT_CLIENT_SECRET` | OAuth2 client credentials |
72
+ | **Linear** | `LINEAR_API_KEY` | Personal API key |
73
+ | **Airtable** | `AIRTABLE_API_KEY` | Personal access token |
74
+ | **Zendesk** | `ZENDESK_CLIENT_ID`, `ZENDESK_CLIENT_SECRET` | OAuth2 client credentials |
75
+ | **Salesforce** | `SALESFORCE_CLIENT_ID`, `SALESFORCE_CLIENT_SECRET` | Connected app credentials |
76
+ | **SendGrid** | `SENDGRID_API_KEY` | API key |
77
+ | **Twilio** | `TWILIO_ACCOUNT_SID`, `TWILIO_AUTH_TOKEN` | Account SID + auth token |
78
+ | **Intercom** | `INTERCOM_ACCESS_TOKEN` | Access token from Intercom developer hub |
79
+ | **Figma** | `FIGMA_ACCESS_TOKEN` | Personal access token |
80
+ | **OpenAI** | `OPENAI_API_KEY` | API key |
81
+ | **Discord** | `DISCORD_BOT_TOKEN` | Bot token |
82
+
83
+ For providers not listed here, inspect the provider package at `packages/utdk/<provider>/package.json` in the registry repo and look at the `utdk.auth` field.
84
+
85
+ ### Local development example
86
+
87
+ Create a `.env` file (never commit this):
88
+
89
+ ```bash
90
+ # .env — loaded by dotenv or direnv
91
+ REGISTRY_PROVIDERS=github,stripe
92
+
93
+ GITHUB_TOKEN=ghp_...
94
+ STRIPE_SECRET_KEY=sk_test_...
95
+ ```
96
+
97
+ Then start:
98
+
99
+ ```bash
100
+ # with dotenv-cli
101
+ dotenv pnpm dev
102
+
103
+ # or export manually
104
+ export $(grep -v '^#' .env | xargs) && pnpm dev
105
+ ```
106
+
107
+ ### Using Registry tools in a widget
108
+
109
+ Once the Registry is connected, any tool namespace (e.g. `github`, `stripe`) can be requested in the `save_widget` `services` parameter:
110
+
111
+ ```
112
+ Tool: save_widget
113
+ {
114
+ "source": "...",
115
+ "services": ["github", "stripe"]
116
+ }
117
+ ```
118
+
119
+ Inside the widget, services are available as global namespace objects:
120
+
121
+ ```typescript
122
+ // List repositories
123
+ const repos = await github.repos_list({ per_page: 10 });
124
+
125
+ // Create a payment intent
126
+ const intent = await stripe.payment_intents_create({
127
+ amount: 2000,
128
+ currency: "usd",
129
+ });
130
+ ```
131
+
132
+ The call chain is: widget → `callServerTool` → `ServiceBridge` → `RegistryBackend` → `@utdk/mcp` → provider API.
133
+
134
+ ### Overriding the Registry command
135
+
136
+ By default the server uses `npx @utdk/mcp`. You can point to a locally built binary instead:
137
+
138
+ ```bash
139
+ REGISTRY_COMMAND=/path/to/registry/apps/mcp-server/dist/server.js \
140
+ REGISTRY_PROVIDERS=github \
141
+ GITHUB_TOKEN=ghp_... \
142
+ node dist/server.js
143
+ ```
144
+
145
+ ### Namespace collision avoidance
146
+
147
+ Registry tool namespaces (one per provider) are registered separately from any locally-defined service tools. To check which namespaces are active, call `search_services` with no arguments from a Claude conversation using the connected MCP server.
148
+
149
+ ## Environment variables
150
+
151
+ | Variable | Default | Description |
152
+ |----------|---------|-------------|
153
+ | `PORT` | `3000` | HTTP port to listen on |
154
+ | `HOST` | `0.0.0.0` | Host interface |
155
+ | `REGISTRY_PROVIDERS` | _(unset)_ | Enable Registry backend; comma-separated provider list |
156
+ | `REGISTRY_COMMAND` | `npx` | Command used to spawn the Registry MCP server |
157
+ | `REGISTRY_ARGS` | _(unset)_ | Space-separated extra args appended after `@utdk/mcp` |
158
+
159
+ ## MCP endpoints
160
+
161
+ | Method | Path | Description |
162
+ |--------|------|-------------|
163
+ | `POST` | `/mcp` | MCP Streamable HTTP endpoint |
164
+ | `GET` | `/health` | Health check |
@@ -0,0 +1,128 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import { VirtualFile, Manifest } from '@aprovan/patchwork-compiler';
3
+
4
+ /**
5
+ * A single buffered event for a data stream.
6
+ */
7
+ interface StreamEvent {
8
+ seq: number;
9
+ data: unknown;
10
+ timestamp: number;
11
+ }
12
+ /**
13
+ * Push a data update to all sessions that have subscribed to `stream`.
14
+ *
15
+ * The event is appended to the ring buffer. Each subscribed session's McpServer
16
+ * then sends a `notifications/tools/list_changed` notification to the host.
17
+ * The host forwards this to the widget, which uses it as a signal to call
18
+ * `poll_updates` and retrieve the buffered data.
19
+ *
20
+ * @returns The new sequence number assigned to this event.
21
+ */
22
+ declare function pushStreamUpdate(stream: string, data: unknown): Promise<number>;
23
+
24
+ interface ServiceBackend {
25
+ call(namespace: string, procedure: string, args: unknown[]): Promise<unknown>;
26
+ }
27
+ interface ServiceToolInfo {
28
+ name: string;
29
+ namespace: string;
30
+ procedure: string;
31
+ description?: string;
32
+ parameters?: Record<string, unknown>;
33
+ }
34
+ interface ServiceBridgeConfig {
35
+ backend: ServiceBackend;
36
+ tools: ServiceToolInfo[];
37
+ }
38
+
39
+ interface FileStats {
40
+ size: number;
41
+ mtime: Date;
42
+ isFile(): boolean;
43
+ isDirectory(): boolean;
44
+ }
45
+ interface DirEntry {
46
+ name: string;
47
+ isFile(): boolean;
48
+ isDirectory(): boolean;
49
+ }
50
+ interface FSProvider {
51
+ readFile(path: string, encoding?: "utf8" | "base64"): Promise<string>;
52
+ writeFile(path: string, content: string): Promise<void>;
53
+ unlink(path: string): Promise<void>;
54
+ stat(path: string): Promise<FileStats>;
55
+ mkdir(path: string, options?: {
56
+ recursive?: boolean;
57
+ }): Promise<void>;
58
+ readdir(path: string): Promise<DirEntry[]>;
59
+ rmdir(path: string, options?: {
60
+ recursive?: boolean;
61
+ }): Promise<void>;
62
+ exists(path: string): Promise<boolean>;
63
+ }
64
+ interface StoredWidget {
65
+ /** Storage path of the widget directory (e.g. "widgets/timer/abc123"). */
66
+ path: string;
67
+ resourceUri: string;
68
+ /** Raw, uncompiled widget source files. Compilation happens in the browser. */
69
+ files: VirtualFile[];
70
+ /** Entry file path within {@link files} (e.g. "main.tsx"). */
71
+ entry: string;
72
+ manifest: Manifest;
73
+ createdAt: number;
74
+ }
75
+ interface StoredWidgetInfo {
76
+ path: string;
77
+ resourceUri: string;
78
+ name: string;
79
+ version: string;
80
+ description?: string;
81
+ services?: string[];
82
+ entry?: string;
83
+ createdAt: number;
84
+ }
85
+ interface WidgetStoreOptions {
86
+ storageDir?: string;
87
+ backend?: FSProvider;
88
+ }
89
+
90
+ /**
91
+ * Persistent store for **raw, uncompiled** widget source files.
92
+ *
93
+ * Widgets are saved as their original `.tsx`/`.ts` files plus a `manifest.json`;
94
+ * compilation happens in the browser via the shared `@aprovan/patchwork-compiler`
95
+ * runtime when the widget is rendered. Layout:
96
+ *
97
+ * ```
98
+ * widgets/<name>/<hash>/
99
+ * files/main.tsx
100
+ * files/price-card.tsx
101
+ * manifest.json { ...manifest, entry, createdAt }
102
+ * ```
103
+ */
104
+ declare class WidgetStore {
105
+ private provider;
106
+ private storageDir;
107
+ constructor(options?: WidgetStoreOptions);
108
+ private fullPath;
109
+ private readFilesRecursive;
110
+ saveWidget(hash: string, files: VirtualFile[], manifest: Manifest, entry: string): Promise<StoredWidget>;
111
+ getWidget(name: string, hash: string): Promise<StoredWidget | null>;
112
+ listWidgets(): Promise<StoredWidgetInfo[]>;
113
+ deleteWidget(name: string, hash: string): Promise<boolean>;
114
+ hasWidget(name: string, hash: string): Promise<boolean>;
115
+ resourceUriFor(name: string, hash: string): string;
116
+ loadAll(): Promise<StoredWidget[]>;
117
+ }
118
+ declare function getWidgetStore(options?: WidgetStoreOptions): WidgetStore;
119
+ declare function resetWidgetStore(): void;
120
+
121
+ interface McpAppServerOptions {
122
+ services?: ServiceBridgeConfig;
123
+ /** Base URL for serving widgets (e.g., tunnel URL). Defaults to localhost:3002. */
124
+ widgetBaseUrl?: string;
125
+ }
126
+ declare function createMcpAppServer(options?: McpAppServerOptions): McpServer;
127
+
128
+ export { type McpAppServerOptions, type ServiceBackend, type ServiceBridgeConfig, type ServiceToolInfo, type StoredWidget, type StoredWidgetInfo, type StreamEvent, WidgetStore, type WidgetStoreOptions, createMcpAppServer, getWidgetStore, pushStreamUpdate, resetWidgetStore };