@@ -144,7 +144,7 @@ export const RESOURCE_MIME_TYPE = "text/html;profile=mcp-app";
144144 *
145145 * @see `ProtocolOptions` from @modelcontextprotocol/sdk for inherited options
146146 */
147- type AppOptions = ProtocolOptions & {
147+ export type AppOptions = ProtocolOptions & {
148148 /**
149149 * Automatically report size changes to the host using `ResizeObserver`.
150150 *
@@ -155,6 +155,19 @@ type AppOptions = ProtocolOptions & {
155155 * @default true
156156 */
157157 autoResize ?: boolean ;
158+ /**
159+ * Throw on detected misuse instead of logging a console error.
160+ *
161+ * Currently this affects calling host-bound methods (e.g.
162+ * {@link App.callServerTool `callServerTool`}, {@link App.sendMessage `sendMessage`})
163+ * before {@link App.connect `connect`} has completed the `ui/initialize`
164+ * handshake. With `strict: false` (default) a `console.error` is emitted;
165+ * with `strict: true` an `Error` is thrown.
166+ *
167+ * @remarks Throwing will become the default in a future release.
168+ * @default false
169+ */
170+ strict ?: boolean ;
158171} ;
159172
160173type RequestHandlerExtra = Parameters <
@@ -244,6 +257,32 @@ export class App extends ProtocolWithEvents<
244257 private _hostCapabilities ?: McpUiHostCapabilities ;
245258 private _hostInfo ?: Implementation ;
246259 private _hostContext ?: McpUiHostContext ;
260+ private _initializedSent = false ;
261+
262+ /**
263+ * Warn if a host-bound method is called before {@link connect `connect`} has
264+ * completed the `ui/initialize` → `ui/notifications/initialized` handshake.
265+ *
266+ * Calling these methods early can race the handshake on strict hosts and
267+ * leave the iframe permanently hidden. See
268+ * {@link https://github.com/anthropics/claude-ai-mcp/issues/61 claude-ai-mcp#61} /
269+ * {@link https://github.com/anthropics/claude-ai-mcp/issues/149 #149 }.
270+ *
271+ * @remarks This will become a thrown `Error` in a future minor release.
272+ */
273+ private _assertInitialized ( method : string ) : void {
274+ if ( this . _initializedSent ) return ;
275+ const msg =
276+ `[ext-apps] App.${ method } () called before connect() completed the ` +
277+ `ui/initialize handshake. Await app.connect() before calling this ` +
278+ `method, or move data loading to an ontoolresult handler. ` +
279+ `See https://github.com/anthropics/claude-ai-mcp/issues/149` ;
280+ if ( this . options ?. strict ) {
281+ throw new Error ( msg ) ;
282+ }
283+ // TODO(next-minor): make `strict: true` the default.
284+ console . error ( `${ msg } . This will throw in a future release.` ) ;
285+ }
247286
248287 protected readonly eventSchemas = {
249288 toolinput : McpUiToolInputNotificationSchema ,
@@ -851,6 +890,7 @@ export class App extends ProtocolWithEvents<
851890 params : CallToolRequest [ "params" ] ,
852891 options ?: RequestOptions ,
853892 ) : Promise < CallToolResult > {
893+ this . _assertInitialized ( "callServerTool" ) ;
854894 if ( typeof params === "string" ) {
855895 throw new Error (
856896 `callServerTool() expects an object as its first argument, but received a string ("${ params } "). ` +
@@ -915,6 +955,7 @@ export class App extends ProtocolWithEvents<
915955 params : ReadResourceRequest [ "params" ] ,
916956 options ?: RequestOptions ,
917957 ) : Promise < ReadResourceResult > {
958+ this . _assertInitialized ( "readServerResource" ) ;
918959 return await this . request (
919960 { method : "resources/read" , params } ,
920961 ReadResourceResultSchema ,
@@ -962,6 +1003,7 @@ export class App extends ProtocolWithEvents<
9621003 params ?: ListResourcesRequest [ "params" ] ,
9631004 options ?: RequestOptions ,
9641005 ) : Promise < ListResourcesResult > {
1006+ this . _assertInitialized ( "listServerResources" ) ;
9651007 return await this . request (
9661008 { method : "resources/list" , params } ,
9671009 ListResourcesResultSchema ,
@@ -1020,6 +1062,7 @@ export class App extends ProtocolWithEvents<
10201062 * @see {@link McpUiMessageRequest `McpUiMessageRequest` } for request structure
10211063 */
10221064 sendMessage ( params : McpUiMessageRequest [ "params" ] , options ?: RequestOptions ) {
1065+ this . _assertInitialized ( "sendMessage" ) ;
10231066 return this . request (
10241067 < McpUiMessageRequest > {
10251068 method : "ui/message" ,
@@ -1113,6 +1156,7 @@ export class App extends ProtocolWithEvents<
11131156 params : McpUiUpdateModelContextRequest [ "params" ] ,
11141157 options ?: RequestOptions ,
11151158 ) {
1159+ this . _assertInitialized ( "updateModelContext" ) ;
11161160 return this . request (
11171161 < McpUiUpdateModelContextRequest > {
11181162 method : "ui/update-model-context" ,
@@ -1149,6 +1193,7 @@ export class App extends ProtocolWithEvents<
11491193 * @see {@link McpUiOpenLinkResult `McpUiOpenLinkResult` } for result structure
11501194 */
11511195 openLink ( params : McpUiOpenLinkRequest [ "params" ] , options ?: RequestOptions ) {
1196+ this . _assertInitialized ( "openLink" ) ;
11521197 return this . request (
11531198 < McpUiOpenLinkRequest > {
11541199 method : "ui/open-link" ,
@@ -1229,6 +1274,7 @@ export class App extends ProtocolWithEvents<
12291274 params : McpUiDownloadFileRequest [ "params" ] ,
12301275 options ?: RequestOptions ,
12311276 ) {
1277+ this . _assertInitialized ( "downloadFile" ) ;
12321278 return this . request (
12331279 < McpUiDownloadFileRequest > {
12341280 method : "ui/download-file" ,
@@ -1313,6 +1359,7 @@ export class App extends ProtocolWithEvents<
13131359 params : McpUiRequestDisplayModeRequest [ "params" ] ,
13141360 options ?: RequestOptions ,
13151361 ) {
1362+ this . _assertInitialized ( "requestDisplayMode" ) ;
13161363 return this . request (
13171364 < McpUiRequestDisplayModeRequest > {
13181365 method : "ui/request-display-mode" ,
@@ -1475,6 +1522,7 @@ export class App extends ProtocolWithEvents<
14751522 "App is already connected. Call close() before connecting again." ,
14761523 ) ;
14771524 }
1525+ this . _initializedSent = false ;
14781526 await super . connect ( transport ) ;
14791527
14801528 try {
@@ -1502,6 +1550,7 @@ export class App extends ProtocolWithEvents<
15021550 await this . notification ( < McpUiInitializedNotification > {
15031551 method : "ui/notifications/initialized" ,
15041552 } ) ;
1553+ this . _initializedSent = true ;
15051554
15061555 if ( this . options ?. autoResize ) {
15071556 this . setupSizeChangedNotifications ( ) ;
0 commit comments