@mcp-web/app 0.1.0

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.
@@ -0,0 +1,122 @@
1
+ import { type ReactNode } from 'react';
2
+ import type { App, McpUiHostContext } from '@modelcontextprotocol/ext-apps';
3
+ /**
4
+ * Value provided by `MCPAppProvider`.
5
+ *
6
+ * Contains the ext-apps `App` instance, connection state, and the
7
+ * current host context (theme, styles, display mode, locale, etc.).
8
+ */
9
+ export interface MCPAppContextValue {
10
+ /** The connected ext-apps `App` instance, null during initialization */
11
+ app: App | null;
12
+ /** Whether initialization completed successfully */
13
+ isConnected: boolean;
14
+ /** Connection error if initialization failed, null otherwise */
15
+ error: Error | null;
16
+ /** Current host context, undefined until connected */
17
+ hostContext: McpUiHostContext | undefined;
18
+ }
19
+ /**
20
+ * Provider that creates the ext-apps `App` instance and applies host styles.
21
+ *
22
+ * This provider:
23
+ * - Creates and manages the `App` connection via `useApp()`
24
+ * - Automatically applies the host's CSS custom properties, theme, and fonts
25
+ * - Tracks host context changes (theme toggles, display mode changes, etc.)
26
+ * - Makes the app instance and host context available to child components
27
+ *
28
+ * @internal Used by `renderMCPApp` — not typically used directly.
29
+ */
30
+ export declare function MCPAppProvider({ children }: {
31
+ children: ReactNode;
32
+ }): import("react/jsx-runtime").JSX.Element;
33
+ /**
34
+ * Access the full MCP App context including the app instance and host context.
35
+ *
36
+ * Must be called within an `MCPAppProvider` (which is set up automatically
37
+ * by `renderMCPApp`).
38
+ *
39
+ * @returns The context value with app, connection state, and host context
40
+ * @throws If called outside of an MCPAppProvider
41
+ *
42
+ * @internal Used by `useMCPAppProps` and `useMCPApp`.
43
+ */
44
+ export declare function useMCPAppContext(): MCPAppContextValue;
45
+ /**
46
+ * Get the current host context from the MCP host application.
47
+ *
48
+ * Returns the full `McpUiHostContext` from the host (e.g., Claude Desktop),
49
+ * which includes theme, styles, display mode, locale, container dimensions,
50
+ * and more. The value updates automatically when the host sends
51
+ * `host-context-changed` notifications.
52
+ *
53
+ * Must be called within an `MCPAppProvider` (set up automatically
54
+ * by `renderMCPApp`).
55
+ *
56
+ * @returns The current host context, or undefined if not yet connected
57
+ *
58
+ * @example Access host display mode
59
+ * ```tsx
60
+ * import { useMCPHostContext } from '@mcp-web/app';
61
+ *
62
+ * function MyApp() {
63
+ * const hostContext = useMCPHostContext();
64
+ *
65
+ * return (
66
+ * <div>
67
+ * <p>Display mode: {hostContext?.displayMode}</p>
68
+ * <p>Platform: {hostContext?.platform}</p>
69
+ * </div>
70
+ * );
71
+ * }
72
+ * ```
73
+ */
74
+ export declare function useMCPHostContext(): McpUiHostContext | undefined;
75
+ /**
76
+ * Get the current theme preference from the MCP host application.
77
+ *
78
+ * Returns `"light"` or `"dark"` based on the host's current theme.
79
+ * Updates reactively when the user toggles theme in the host.
80
+ *
81
+ * This hook reads the theme from the `data-theme` attribute on
82
+ * `document.documentElement`, which is set automatically by the
83
+ * host styles system. It uses a `MutationObserver` internally so
84
+ * it will re-render your component whenever the theme changes.
85
+ *
86
+ * Must be called within an `MCPAppProvider` (set up automatically
87
+ * by `renderMCPApp`).
88
+ *
89
+ * @returns The current theme — `"light"` or `"dark"`
90
+ *
91
+ * @example Sync with Tailwind CSS dark mode
92
+ * ```tsx
93
+ * import { useMCPHostTheme } from '@mcp-web/app';
94
+ * import { useEffect } from 'react';
95
+ *
96
+ * function MyApp(props: MyProps) {
97
+ * const theme = useMCPHostTheme();
98
+ *
99
+ * useEffect(() => {
100
+ * document.documentElement.classList.toggle('dark', theme === 'dark');
101
+ * }, [theme]);
102
+ *
103
+ * return (
104
+ * <div className="bg-white dark:bg-gray-900">
105
+ * {props.children}
106
+ * </div>
107
+ * );
108
+ * }
109
+ * ```
110
+ *
111
+ * @example Conditional rendering based on theme
112
+ * ```tsx
113
+ * import { useMCPHostTheme } from '@mcp-web/app';
114
+ *
115
+ * function Logo() {
116
+ * const theme = useMCPHostTheme();
117
+ * return <img src={theme === 'dark' ? '/logo-light.svg' : '/logo-dark.svg'} />;
118
+ * }
119
+ * ```
120
+ */
121
+ export declare function useMCPHostTheme(): import("@modelcontextprotocol/ext-apps").McpUiTheme;
122
+ //# sourceMappingURL=mcp-app-context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp-app-context.d.ts","sourceRoot":"","sources":["../src/mcp-app-context.tsx"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAEf,OAAO,KAAK,EAAE,GAAG,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AAE5E;;;;;GAKG;AACH,MAAM,WAAW,kBAAkB;IACjC,wEAAwE;IACxE,GAAG,EAAE,GAAG,GAAG,IAAI,CAAC;IAChB,oDAAoD;IACpD,WAAW,EAAE,OAAO,CAAC;IACrB,gEAAgE;IAChE,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,sDAAsD;IACtD,WAAW,EAAE,gBAAgB,GAAG,SAAS,CAAC;CAC3C;AAID;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAAC,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,EAAE,SAAS,CAAA;CAAE,2CAoCnE;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,IAAI,kBAAkB,CAUrD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,iBAAiB,IAAI,gBAAgB,GAAG,SAAS,CAEhE;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AACH,wBAAgB,eAAe,wDAE9B"}
@@ -0,0 +1,140 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { createContext, useCallback, useContext, useState, } from 'react';
3
+ import { useApp, useHostStyles, useDocumentTheme } from '@modelcontextprotocol/ext-apps/react';
4
+ const MCPAppContext = createContext(null);
5
+ /**
6
+ * Provider that creates the ext-apps `App` instance and applies host styles.
7
+ *
8
+ * This provider:
9
+ * - Creates and manages the `App` connection via `useApp()`
10
+ * - Automatically applies the host's CSS custom properties, theme, and fonts
11
+ * - Tracks host context changes (theme toggles, display mode changes, etc.)
12
+ * - Makes the app instance and host context available to child components
13
+ *
14
+ * @internal Used by `renderMCPApp` — not typically used directly.
15
+ */
16
+ export function MCPAppProvider({ children }) {
17
+ const [hostContext, setHostContext] = useState(undefined);
18
+ const onAppCreated = useCallback((app) => {
19
+ app.onhostcontextchanged = (params) => {
20
+ setHostContext((prev) => ({ ...prev, ...params }));
21
+ };
22
+ }, []);
23
+ const { app, isConnected, error } = useApp({
24
+ appInfo: {
25
+ name: 'mcp-web-app',
26
+ version: '0.1.0',
27
+ },
28
+ capabilities: {},
29
+ onAppCreated,
30
+ });
31
+ // Capture initial host context once connected
32
+ const initialContext = app?.getHostContext();
33
+ if (initialContext && !hostContext) {
34
+ setHostContext(initialContext);
35
+ }
36
+ // Automatically apply host CSS variables, theme, and fonts
37
+ useHostStyles(app, initialContext);
38
+ return (_jsx(MCPAppContext.Provider, { value: { app, isConnected, error, hostContext }, children: children }));
39
+ }
40
+ /**
41
+ * Access the full MCP App context including the app instance and host context.
42
+ *
43
+ * Must be called within an `MCPAppProvider` (which is set up automatically
44
+ * by `renderMCPApp`).
45
+ *
46
+ * @returns The context value with app, connection state, and host context
47
+ * @throws If called outside of an MCPAppProvider
48
+ *
49
+ * @internal Used by `useMCPAppProps` and `useMCPApp`.
50
+ */
51
+ export function useMCPAppContext() {
52
+ const ctx = useContext(MCPAppContext);
53
+ if (!ctx) {
54
+ throw new Error('useMCPAppContext must be used within an MCPAppProvider. ' +
55
+ 'If you are using renderMCPApp(), this is set up automatically. ' +
56
+ 'Otherwise, wrap your component tree with <MCPAppProvider>.');
57
+ }
58
+ return ctx;
59
+ }
60
+ /**
61
+ * Get the current host context from the MCP host application.
62
+ *
63
+ * Returns the full `McpUiHostContext` from the host (e.g., Claude Desktop),
64
+ * which includes theme, styles, display mode, locale, container dimensions,
65
+ * and more. The value updates automatically when the host sends
66
+ * `host-context-changed` notifications.
67
+ *
68
+ * Must be called within an `MCPAppProvider` (set up automatically
69
+ * by `renderMCPApp`).
70
+ *
71
+ * @returns The current host context, or undefined if not yet connected
72
+ *
73
+ * @example Access host display mode
74
+ * ```tsx
75
+ * import { useMCPHostContext } from '@mcp-web/app';
76
+ *
77
+ * function MyApp() {
78
+ * const hostContext = useMCPHostContext();
79
+ *
80
+ * return (
81
+ * <div>
82
+ * <p>Display mode: {hostContext?.displayMode}</p>
83
+ * <p>Platform: {hostContext?.platform}</p>
84
+ * </div>
85
+ * );
86
+ * }
87
+ * ```
88
+ */
89
+ export function useMCPHostContext() {
90
+ return useMCPAppContext().hostContext;
91
+ }
92
+ /**
93
+ * Get the current theme preference from the MCP host application.
94
+ *
95
+ * Returns `"light"` or `"dark"` based on the host's current theme.
96
+ * Updates reactively when the user toggles theme in the host.
97
+ *
98
+ * This hook reads the theme from the `data-theme` attribute on
99
+ * `document.documentElement`, which is set automatically by the
100
+ * host styles system. It uses a `MutationObserver` internally so
101
+ * it will re-render your component whenever the theme changes.
102
+ *
103
+ * Must be called within an `MCPAppProvider` (set up automatically
104
+ * by `renderMCPApp`).
105
+ *
106
+ * @returns The current theme — `"light"` or `"dark"`
107
+ *
108
+ * @example Sync with Tailwind CSS dark mode
109
+ * ```tsx
110
+ * import { useMCPHostTheme } from '@mcp-web/app';
111
+ * import { useEffect } from 'react';
112
+ *
113
+ * function MyApp(props: MyProps) {
114
+ * const theme = useMCPHostTheme();
115
+ *
116
+ * useEffect(() => {
117
+ * document.documentElement.classList.toggle('dark', theme === 'dark');
118
+ * }, [theme]);
119
+ *
120
+ * return (
121
+ * <div className="bg-white dark:bg-gray-900">
122
+ * {props.children}
123
+ * </div>
124
+ * );
125
+ * }
126
+ * ```
127
+ *
128
+ * @example Conditional rendering based on theme
129
+ * ```tsx
130
+ * import { useMCPHostTheme } from '@mcp-web/app';
131
+ *
132
+ * function Logo() {
133
+ * const theme = useMCPHostTheme();
134
+ * return <img src={theme === 'dark' ? '/logo-light.svg' : '/logo-dark.svg'} />;
135
+ * }
136
+ * ```
137
+ */
138
+ export function useMCPHostTheme() {
139
+ return useDocumentTheme();
140
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=mcp-app-context.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp-app-context.test.d.ts","sourceRoot":"","sources":["../src/mcp-app-context.test.tsx"],"names":[],"mappings":""}
@@ -0,0 +1,254 @@
1
+ import { describe, expect, mock, test, beforeEach } from 'bun:test';
2
+ import { createElement } from 'react';
3
+ import { renderToString } from 'react-dom/server';
4
+ // --- Mocks ---
5
+ // Track calls to ext-apps hooks
6
+ let mockUseAppReturn = {
7
+ app: null,
8
+ isConnected: false,
9
+ error: null,
10
+ };
11
+ let mockUseAppOptions = null;
12
+ let mockUseHostStylesCalls = [];
13
+ let mockUseDocumentThemeReturn = 'light';
14
+ // Mock `@modelcontextprotocol/ext-apps/react` before importing our module
15
+ mock.module('@modelcontextprotocol/ext-apps/react', () => ({
16
+ useApp: (options) => {
17
+ mockUseAppOptions = options;
18
+ return mockUseAppReturn;
19
+ },
20
+ useHostStyles: (...args) => {
21
+ mockUseHostStylesCalls.push(args);
22
+ },
23
+ useDocumentTheme: () => mockUseDocumentThemeReturn,
24
+ }));
25
+ // Import after mocking
26
+ const { MCPAppProvider, useMCPAppContext, useMCPHostContext, useMCPHostTheme, } = await import('./mcp-app-context.js');
27
+ // --- Helpers ---
28
+ /** Decode HTML entities produced by renderToString. */
29
+ function decodeHtml(html) {
30
+ return html
31
+ .replace(/<[^>]+>/g, '')
32
+ .replace(/&quot;/g, '"')
33
+ .replace(/&amp;/g, '&')
34
+ .replace(/&lt;/g, '<')
35
+ .replace(/&gt;/g, '>')
36
+ .replace(/&#x27;/g, "'")
37
+ .replace(/&#39;/g, "'");
38
+ }
39
+ /** Render a component inside MCPAppProvider and return the HTML string. */
40
+ function renderWithProvider(children) {
41
+ return renderToString(createElement(MCPAppProvider, null, children));
42
+ }
43
+ /** Render with provider and extract the decoded text content. */
44
+ function renderText(component) {
45
+ const html = renderWithProvider(createElement(component));
46
+ return decodeHtml(html);
47
+ }
48
+ /** Component that reads context and renders its values for assertion. */
49
+ function ContextReader() {
50
+ const ctx = useMCPAppContext();
51
+ return createElement('div', { 'data-testid': 'context' }, JSON.stringify({
52
+ hasApp: ctx.app !== null,
53
+ isConnected: ctx.isConnected,
54
+ hasError: ctx.error !== null,
55
+ hasHostContext: ctx.hostContext !== undefined,
56
+ }));
57
+ }
58
+ /** Component that reads host context and renders it. */
59
+ function HostContextReader() {
60
+ const hostContext = useMCPHostContext();
61
+ return createElement('div', null, hostContext ? JSON.stringify(hostContext) : 'undefined');
62
+ }
63
+ /** Component that reads theme and renders it. */
64
+ function ThemeReader() {
65
+ const theme = useMCPHostTheme();
66
+ return createElement('div', null, theme);
67
+ }
68
+ // --- Tests ---
69
+ beforeEach(() => {
70
+ mockUseAppReturn = {
71
+ app: null,
72
+ isConnected: false,
73
+ error: null,
74
+ };
75
+ mockUseAppOptions = null;
76
+ mockUseHostStylesCalls = [];
77
+ mockUseDocumentThemeReturn = 'light';
78
+ });
79
+ describe('MCPAppProvider', () => {
80
+ test('renders children', () => {
81
+ const html = renderWithProvider(createElement('span', null, 'hello world'));
82
+ expect(html).toContain('hello world');
83
+ });
84
+ test('calls useApp with correct appInfo', () => {
85
+ renderWithProvider(createElement('div'));
86
+ expect(mockUseAppOptions).toBeDefined();
87
+ const options = mockUseAppOptions;
88
+ expect(options.appInfo).toEqual({
89
+ name: 'mcp-web-app',
90
+ version: '0.1.0',
91
+ });
92
+ expect(options.capabilities).toEqual({});
93
+ expect(typeof options.onAppCreated).toBe('function');
94
+ });
95
+ test('calls useHostStyles with app and initial context', () => {
96
+ mockUseAppReturn = {
97
+ app: { getHostContext: () => null },
98
+ isConnected: true,
99
+ error: null,
100
+ };
101
+ renderWithProvider(createElement('div'));
102
+ expect(mockUseHostStylesCalls.length).toBeGreaterThan(0);
103
+ const lastCall = mockUseHostStylesCalls[mockUseHostStylesCalls.length - 1];
104
+ // First arg is app, second is initial context
105
+ expect(lastCall[0]).toBe(mockUseAppReturn.app);
106
+ });
107
+ test('provides context with disconnected state initially', () => {
108
+ mockUseAppReturn = {
109
+ app: null,
110
+ isConnected: false,
111
+ error: null,
112
+ };
113
+ const text = renderText(ContextReader);
114
+ const parsed = JSON.parse(text);
115
+ expect(parsed).toEqual({
116
+ hasApp: false,
117
+ isConnected: false,
118
+ hasError: false,
119
+ hasHostContext: false,
120
+ });
121
+ });
122
+ test('provides context with connected state and app', () => {
123
+ const mockHostContext = { theme: 'dark', displayMode: 'inline' };
124
+ mockUseAppReturn = {
125
+ app: { getHostContext: () => mockHostContext },
126
+ isConnected: true,
127
+ error: null,
128
+ };
129
+ const text = renderText(ContextReader);
130
+ const parsed = JSON.parse(text);
131
+ expect(parsed).toEqual({
132
+ hasApp: true,
133
+ isConnected: true,
134
+ hasError: false,
135
+ hasHostContext: true,
136
+ });
137
+ });
138
+ test('provides context with error state', () => {
139
+ mockUseAppReturn = {
140
+ app: null,
141
+ isConnected: false,
142
+ error: new Error('Connection failed'),
143
+ };
144
+ const text = renderText(ContextReader);
145
+ const parsed = JSON.parse(text);
146
+ expect(parsed).toEqual({
147
+ hasApp: false,
148
+ isConnected: false,
149
+ hasError: true,
150
+ hasHostContext: false,
151
+ });
152
+ });
153
+ test('sets initial host context from app.getHostContext()', () => {
154
+ const mockHostContext = {
155
+ theme: 'dark',
156
+ displayMode: 'inline',
157
+ locale: 'en-US',
158
+ };
159
+ mockUseAppReturn = {
160
+ app: { getHostContext: () => mockHostContext },
161
+ isConnected: true,
162
+ error: null,
163
+ };
164
+ const text = renderText(HostContextReader);
165
+ const parsed = JSON.parse(text);
166
+ expect(parsed).toEqual(mockHostContext);
167
+ });
168
+ test('host context is undefined when app has no context', () => {
169
+ mockUseAppReturn = {
170
+ app: { getHostContext: () => null },
171
+ isConnected: true,
172
+ error: null,
173
+ };
174
+ const text = renderText(HostContextReader);
175
+ expect(text).toBe('undefined');
176
+ });
177
+ test('onAppCreated callback registers onhostcontextchanged handler', () => {
178
+ renderWithProvider(createElement('div'));
179
+ const options = mockUseAppOptions;
180
+ // Simulate what useApp does: call onAppCreated with an app instance
181
+ const fakeApp = {};
182
+ options.onAppCreated(fakeApp);
183
+ // The handler should be registered
184
+ expect(typeof fakeApp.onhostcontextchanged).toBe('function');
185
+ });
186
+ });
187
+ describe('useMCPAppContext', () => {
188
+ test('throws when used outside MCPAppProvider', () => {
189
+ expect(() => {
190
+ renderToString(createElement(ContextReader));
191
+ }).toThrow('useMCPAppContext must be used within an MCPAppProvider');
192
+ });
193
+ test('error message includes guidance about renderMCPApp', () => {
194
+ try {
195
+ renderToString(createElement(ContextReader));
196
+ // Should not reach here
197
+ expect(true).toBe(false);
198
+ }
199
+ catch (error) {
200
+ expect(error.message).toContain('renderMCPApp');
201
+ expect(error.message).toContain('MCPAppProvider');
202
+ }
203
+ });
204
+ });
205
+ describe('useMCPHostContext', () => {
206
+ test('throws when used outside MCPAppProvider', () => {
207
+ expect(() => {
208
+ renderToString(createElement(HostContextReader));
209
+ }).toThrow('useMCPAppContext must be used within an MCPAppProvider');
210
+ });
211
+ test('returns undefined when not connected', () => {
212
+ mockUseAppReturn = {
213
+ app: null,
214
+ isConnected: false,
215
+ error: null,
216
+ };
217
+ const text = renderText(HostContextReader);
218
+ expect(text).toBe('undefined');
219
+ });
220
+ test('returns host context when connected', () => {
221
+ const context = {
222
+ theme: 'light',
223
+ displayMode: 'full',
224
+ platform: 'darwin',
225
+ };
226
+ mockUseAppReturn = {
227
+ app: { getHostContext: () => context },
228
+ isConnected: true,
229
+ error: null,
230
+ };
231
+ const text = renderText(HostContextReader);
232
+ expect(JSON.parse(text)).toEqual(context);
233
+ });
234
+ });
235
+ describe('useMCPHostTheme', () => {
236
+ test('returns light theme by default', () => {
237
+ mockUseDocumentThemeReturn = 'light';
238
+ const text = renderText(ThemeReader);
239
+ expect(text).toBe('light');
240
+ });
241
+ test('returns dark theme when host is dark', () => {
242
+ mockUseDocumentThemeReturn = 'dark';
243
+ const text = renderText(ThemeReader);
244
+ expect(text).toBe('dark');
245
+ });
246
+ test('delegates to useDocumentTheme from ext-apps', () => {
247
+ // The mock already tracks that useDocumentTheme is called.
248
+ // If it weren't called, useMCPHostTheme would not return the mock value.
249
+ mockUseDocumentThemeReturn = 'dark';
250
+ const text = renderText(ThemeReader);
251
+ // This confirms useMCPHostTheme delegates to our mocked useDocumentTheme
252
+ expect(text).toBe('dark');
253
+ });
254
+ });
@@ -0,0 +1,71 @@
1
+ import type { ComponentType } from 'react';
2
+ /**
3
+ * Options for rendering an MCP App.
4
+ */
5
+ export interface RenderMCPAppOptions {
6
+ /**
7
+ * Component to show while waiting for props.
8
+ * @default A simple "Loading..." div
9
+ */
10
+ loading?: ComponentType;
11
+ /**
12
+ * ID of the root element to mount the app.
13
+ * @default 'root'
14
+ */
15
+ rootId?: string;
16
+ /**
17
+ * Whether to wrap the app in React.StrictMode.
18
+ * @default true
19
+ */
20
+ strictMode?: boolean;
21
+ }
22
+ /**
23
+ * Render a React component as an MCP App.
24
+ *
25
+ * This helper sets up the React root and handles props subscription,
26
+ * so your component can be a regular React component that receives props -
27
+ * no MCP-specific code required in your component.
28
+ *
29
+ * @param Component - Your React component (receives props from the MCP handler)
30
+ * @param options - Optional configuration for loading state and root element
31
+ *
32
+ * @example Basic usage with an existing component
33
+ * ```tsx
34
+ * // src/apps/stats.tsx
35
+ * import { renderMCPApp } from '@mcp-web/app';
36
+ * import { Stats } from '../components/Stats';
37
+ *
38
+ * // Stats is a regular component: function Stats({ total, completed }: StatsProps) { ... }
39
+ * renderMCPApp(Stats);
40
+ * ```
41
+ *
42
+ * @example With custom loading component
43
+ * ```tsx
44
+ * import { renderMCPApp } from '@mcp-web/app';
45
+ * import { Stats } from '../components/Stats';
46
+ * import { Spinner } from '../components/Spinner';
47
+ *
48
+ * renderMCPApp(Stats, {
49
+ * loading: Spinner,
50
+ * });
51
+ * ```
52
+ *
53
+ * @example Inline component definition
54
+ * ```tsx
55
+ * import { renderMCPApp } from '@mcp-web/app';
56
+ *
57
+ * interface ChartProps {
58
+ * data: number[];
59
+ * title: string;
60
+ * }
61
+ *
62
+ * renderMCPApp<ChartProps>(({ data, title }) => (
63
+ * <div>
64
+ * <h1>{title}</h1>
65
+ * <Chart data={data} />
66
+ * </div>
67
+ * ));
68
+ * ```
69
+ */
70
+ export declare function renderMCPApp<P extends Record<string, unknown>>(Component: ComponentType<P>, options?: RenderMCPAppOptions): void;
71
+ //# sourceMappingURL=render-mcp-app.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render-mcp-app.d.ts","sourceRoot":"","sources":["../src/render-mcp-app.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAM3C;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC;;;OAGG;IACH,OAAO,CAAC,EAAE,aAAa,CAAC;IAExB;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AA0CD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AACH,wBAAgB,YAAY,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC5D,SAAS,EAAE,aAAa,CAAC,CAAC,CAAC,EAC3B,OAAO,GAAE,mBAAwB,GAChC,IAAI,CAoBN"}
@@ -0,0 +1,87 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { StrictMode, Suspense } from 'react';
3
+ import { createRoot } from 'react-dom/client';
4
+ import { MCPAppProvider } from './mcp-app-context.js';
5
+ import { useMCPAppProps } from './use-mcp-app-props.js';
6
+ /**
7
+ * Default loading component shown while waiting for props.
8
+ * Uses host CSS variable with fallback for theme-aware loading text.
9
+ */
10
+ function DefaultLoading() {
11
+ return (_jsx("div", { style: {
12
+ display: 'flex',
13
+ alignItems: 'center',
14
+ justifyContent: 'center',
15
+ height: '100%',
16
+ fontFamily: 'var(--font-sans, system-ui, sans-serif)',
17
+ color: 'var(--color-text-secondary, #666)',
18
+ }, children: "Loading..." }));
19
+ }
20
+ /**
21
+ * Internal wrapper that handles props subscription.
22
+ */
23
+ function MCPAppWrapper({ Component, Loading, }) {
24
+ const props = useMCPAppProps();
25
+ if (!props) {
26
+ return _jsx(Loading, {});
27
+ }
28
+ return _jsx(Component, { ...props });
29
+ }
30
+ /**
31
+ * Render a React component as an MCP App.
32
+ *
33
+ * This helper sets up the React root and handles props subscription,
34
+ * so your component can be a regular React component that receives props -
35
+ * no MCP-specific code required in your component.
36
+ *
37
+ * @param Component - Your React component (receives props from the MCP handler)
38
+ * @param options - Optional configuration for loading state and root element
39
+ *
40
+ * @example Basic usage with an existing component
41
+ * ```tsx
42
+ * // src/apps/stats.tsx
43
+ * import { renderMCPApp } from '@mcp-web/app';
44
+ * import { Stats } from '../components/Stats';
45
+ *
46
+ * // Stats is a regular component: function Stats({ total, completed }: StatsProps) { ... }
47
+ * renderMCPApp(Stats);
48
+ * ```
49
+ *
50
+ * @example With custom loading component
51
+ * ```tsx
52
+ * import { renderMCPApp } from '@mcp-web/app';
53
+ * import { Stats } from '../components/Stats';
54
+ * import { Spinner } from '../components/Spinner';
55
+ *
56
+ * renderMCPApp(Stats, {
57
+ * loading: Spinner,
58
+ * });
59
+ * ```
60
+ *
61
+ * @example Inline component definition
62
+ * ```tsx
63
+ * import { renderMCPApp } from '@mcp-web/app';
64
+ *
65
+ * interface ChartProps {
66
+ * data: number[];
67
+ * title: string;
68
+ * }
69
+ *
70
+ * renderMCPApp<ChartProps>(({ data, title }) => (
71
+ * <div>
72
+ * <h1>{title}</h1>
73
+ * <Chart data={data} />
74
+ * </div>
75
+ * ));
76
+ * ```
77
+ */
78
+ export function renderMCPApp(Component, options = {}) {
79
+ const { loading: Loading = DefaultLoading, rootId = 'root', strictMode = true } = options;
80
+ const rootElement = document.getElementById(rootId);
81
+ if (!rootElement) {
82
+ throw new Error(`[renderMCPApp] Could not find element with id "${rootId}". ` +
83
+ `Make sure your HTML has <div id="${rootId}"></div>`);
84
+ }
85
+ const app = (_jsx(Suspense, { fallback: _jsx(Loading, {}), children: _jsx(MCPAppProvider, { children: _jsx(MCPAppWrapper, { Component: Component, Loading: Loading }) }) }));
86
+ createRoot(rootElement).render(strictMode ? _jsx(StrictMode, { children: app }) : app);
87
+ }