Skip to content

Commit 4d42f57

Browse files
committed
refactor: reduce comments, align with repo conventions
- Remove @example block from forHostIframe JSDoc (only constructors use it) - Reduce example JSDoc to single line matching existing style - Merge redundant test case, remove describe-level comment - Move isInitializationTimeoutError to private static on App class - Use DEFAULT_REQUEST_TIMEOUT_MSEC instead of hardcoded 60000 - Streamline quickstart docs: remove sub-headings and anti-pattern block, use [!CAUTION] callout matching existing doc style Made-with: Cursor
1 parent 6b44a81 commit 4d42f57

5 files changed

Lines changed: 21 additions & 76 deletions

File tree

docs/quickstart.md

Lines changed: 4 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -480,12 +480,7 @@ You've built your first MCP App!
480480

481481
When building your own host (instead of using an existing MCP client), the order of operations matters. The host must start listening for messages **before** the View begins executing, otherwise the View's `ui/initialize` request will be lost.
482482

483-
### Correct Order
484-
485-
1. **Create and attach the iframe** to the document
486-
2. **Create the transport** using `PostMessageTransport.forHostIframe(iframe)`
487-
3. **Connect the bridge** with `await bridge.connect(transport)`
488-
4. **Then** set `iframe.srcdoc` or `iframe.src` to load the View
483+
`iframe.contentWindow` is available as soon as the iframe is in the DOM (it points to the initial `about:blank` document) — you do not need to wait for `onload` to create the transport.
489484

490485
```ts
491486
import {
@@ -497,33 +492,16 @@ const iframe = document.createElement("iframe");
497492
iframe.sandbox.add("allow-scripts");
498493
document.body.appendChild(iframe);
499494

500-
// Create transport — contentWindow exists once iframe is in DOM
501495
const transport = PostMessageTransport.forHostIframe(iframe);
502-
503-
// Connect bridge — now listening for messages
504496
const bridge = new AppBridge(mcpClient, hostInfo, hostCapabilities);
505497
await bridge.connect(transport);
506498

507-
// NOW load the content — ui/initialize will be received
499+
// Set content AFTER connecting — view's ui/initialize will be received
508500
iframe.srcdoc = htmlContent;
509501
```
510502

511-
The `iframe.contentWindow` reference is available as soon as the iframe is in the DOM (it points to the initial `about:blank` document). You do **not** need to wait for `onload` to create the transport.
512-
513-
### Anti-Pattern
514-
515-
```ts
516-
// ❌ WRONG: Setting srcdoc before connecting
517-
iframe.srcdoc = htmlContent; // View sends ui/initialize immediately!
518-
const transport = PostMessageTransport.forHostIframe(iframe);
519-
await bridge.connect(transport); // Too late — message was already lost
520-
```
521-
522-
If you see a timeout error like:
523-
524-
> `ui/initialize: no response within 60s — host may have loaded the View before connecting its transport`
525-
526-
This is the likely cause. Reorder your code to connect the transport first.
503+
> [!CAUTION]
504+
> Setting `srcdoc` or `src` **before** connecting the transport will cause the View's `ui/initialize` to be lost. If you see a timeout like `"no response within 60s"`, this is the likely cause.
527505
528506
## Next Steps
529507

src/app.ts

Lines changed: 16 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import {
22
type RequestOptions,
33
mergeCapabilities,
44
ProtocolOptions,
5+
DEFAULT_REQUEST_TIMEOUT_MSEC,
56
} from "@modelcontextprotocol/sdk/shared/protocol.js";
67

78
import {
@@ -1989,11 +1990,8 @@ export class App extends ProtocolWithEvents<
19891990
// Disconnect if initialization fails.
19901991
void this.close();
19911992

1992-
// Improve timeout message with actionable diagnosis for host developers.
1993-
// This commonly happens when the host loads the View before connecting
1994-
// its transport, causing the ui/initialize message to be lost.
1995-
if (isInitializationTimeoutError(error)) {
1996-
const timeoutMs = options?.timeout ?? 60000;
1993+
if (App.isInitializationTimeoutError(error)) {
1994+
const timeoutMs = options?.timeout ?? DEFAULT_REQUEST_TIMEOUT_MSEC;
19971995
const timeoutSec = Math.round(timeoutMs / 1000);
19981996
throw new Error(
19991997
`ui/initialize: no response within ${timeoutSec}s — ` +
@@ -2005,22 +2003,19 @@ export class App extends ProtocolWithEvents<
20052003
throw error;
20062004
}
20072005
}
2008-
}
20092006

2010-
/**
2011-
* Check if an error indicates the ui/initialize request timed out.
2012-
*/
2013-
function isInitializationTimeoutError(error: unknown): boolean {
2014-
if (!(error instanceof Error)) {
2015-
return false;
2007+
private static isInitializationTimeoutError(error: unknown): boolean {
2008+
if (!(error instanceof Error)) {
2009+
return false;
2010+
}
2011+
const message = error.message.toLowerCase();
2012+
const name = error.name.toLowerCase();
2013+
return (
2014+
message.includes("timeout") ||
2015+
message.includes("timed out") ||
2016+
message.includes("requesttimeout") ||
2017+
name.includes("timeout") ||
2018+
name === "aborterror"
2019+
);
20162020
}
2017-
const message = error.message.toLowerCase();
2018-
const name = error.name.toLowerCase();
2019-
return (
2020-
message.includes("timeout") ||
2021-
message.includes("timed out") ||
2022-
message.includes("requesttimeout") ||
2023-
name.includes("timeout") ||
2024-
name === "aborterror"
2025-
);
20262021
}

src/message-transport.examples.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,10 +58,7 @@ function PostMessageTransport_constructor_host() {
5858
}
5959

6060
/**
61-
* Example: Host using forHostIframe helper (recommended).
62-
*
63-
* The helper validates the iframe is connected and returns a transport bound
64-
* to its contentWindow. Connect before setting srcdoc/src.
61+
* Example: Host using forHostIframe helper.
6562
*/
6663
async function PostMessageTransport_forHostIframe(bridge: AppBridge) {
6764
//#region PostMessageTransport_forHostIframe

src/message-transport.test.ts

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -392,9 +392,6 @@ describe("PostMessageTransport", () => {
392392
// forHostIframe() — static factory for host-side transport
393393
// ==========================================================================
394394
describe("forHostIframe()", () => {
395-
// These tests require a real DOM environment. We create minimal fakes
396-
// that satisfy the checks in forHostIframe().
397-
398395
it("throws when iframe is not connected to the document", () => {
399396
const iframe = {
400397
isConnected: false,
@@ -404,14 +401,6 @@ describe("PostMessageTransport", () => {
404401
expect(() => PostMessageTransport.forHostIframe(iframe)).toThrow(
405402
/iframe must be in the document/,
406403
);
407-
});
408-
409-
it("error message mentions appendChild", () => {
410-
const iframe = {
411-
isConnected: false,
412-
contentWindow: {},
413-
} as unknown as HTMLIFrameElement;
414-
415404
expect(() => PostMessageTransport.forHostIframe(iframe)).toThrow(
416405
/appendChild/,
417406
);

src/message-transport.ts

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -204,20 +204,6 @@ export class PostMessageTransport implements Transport {
204204
* @returns A PostMessageTransport configured for host→iframe communication
205205
* @throws Error if the iframe is not connected to the document
206206
* @throws Error if contentWindow is unavailable
207-
*
208-
* @example Correct host construction order
209-
* ```ts source="./message-transport.examples.ts#PostMessageTransport_forHostIframe"
210-
* const iframe = document.createElement("iframe");
211-
* iframe.sandbox.add("allow-scripts");
212-
* document.body.appendChild(iframe);
213-
*
214-
* // Create transport BEFORE loading content
215-
* const transport = PostMessageTransport.forHostIframe(iframe);
216-
* await bridge.connect(transport);
217-
*
218-
* // NOW load the view — ui/initialize will be received
219-
* iframe.srcdoc = "<html>...</html>";
220-
* ```
221207
*/
222208
static forHostIframe(iframe: HTMLIFrameElement): PostMessageTransport {
223209
if (!iframe.isConnected) {

0 commit comments

Comments
 (0)