> ## Documentation Index
> Fetch the complete documentation index at: https://sunpeak.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# MCP Apps Requests - View-to-Host API

> Make requests from an MCP App View to the host: call server tools, request LLM completions via sampling, send messages, update model context, open links, download files, request display modes, and send logs.

<Badge color="green">MCP Apps SDK</Badge>

```ts theme={null}
import { App } from "@modelcontextprotocol/ext-apps";
```

## Overview

The `App` class provides methods for the View to make requests to the host. The host proxies server requests to the MCP server and handles UI requests directly. For handling incoming host events, see [Event Handlers](/mcp-apps/app/event-handlers).

<Tip>
  If you are deciding which method to use from an iframe View, start with the [View
  API Guide](/mcp-apps/app/view-api-guide). It compares `callServerTool()`,
  `readServerResource()`, `updateModelContext()`, `sendMessage()`, sampling, app-side
  tools, downloads, links, display modes, logs, and teardown.
</Tip>

## [callServerTool](/mcp-apps/app/requests/call-server-tool)

Call a tool on the originating MCP server (proxied through the host).

```ts theme={null}
async callServerTool(
  params: { name: string; arguments?: Record<string, unknown> },
  options?: RequestOptions,
): Promise<CallToolResult>
```

```ts theme={null}
const result = await app.callServerTool({
  name: "get_weather",
  arguments: { location: "Tokyo" },
});

if (result.isError) {
  console.error("Tool returned error:", result.content);
} else {
  console.log(result.structuredContent);
}
```

<Note>
  Tool-level execution errors are returned in the result with `isError: true` rather than throwing exceptions. Transport or protocol failures throw. Always check `result.isError`.
</Note>

## [sendMessage](/mcp-apps/app/requests/send-message)

Send a message to the host's chat interface.

```ts theme={null}
async sendMessage(
  params: { role: "user"; content: ContentBlock[] },
  options?: RequestOptions,
): Promise<{ isError?: boolean }>
```

```ts theme={null}
const result = await app.sendMessage({
  role: "user",
  content: [{ type: "text", text: "Show me details for item #42" }],
});

if (result.isError) {
  console.error("Host rejected the message");
}
```

## [updateModelContext](/mcp-apps/app/requests/update-model-context)

Update the host's model context with app state. Context is available to the model in future turns without triggering an immediate response. Each call overwrites any previous context.

```ts theme={null}
async updateModelContext(
  params: {
    content?: ContentBlock[];
    structuredContent?: Record<string, unknown>;
  },
  options?: RequestOptions,
): Promise<void>
```

```ts theme={null}
await app.updateModelContext({
  content: [{
    type: "text",
    text: `User is viewing page ${currentPage} of ${totalPages}`,
  }],
});
```

<Tip>
  For large follow-up messages, offload data to `updateModelContext` first, then send a brief trigger via `sendMessage`:

  ```ts theme={null}
  await app.updateModelContext({
    content: [{ type: "text", text: fullTranscript }],
  });
  await app.sendMessage({
    role: "user",
    content: [{ type: "text", text: "Summarize the key points" }],
  });
  ```
</Tip>

## [openLink](/mcp-apps/app/requests/open-link)

Request the host to open an external URL in the browser.

```ts theme={null}
async openLink(
  params: { url: string },
  options?: RequestOptions,
): Promise<{ isError?: boolean }>
```

```ts theme={null}
const { isError } = await app.openLink({
  url: "https://docs.example.com",
});
if (isError) {
  console.warn("Link request denied by host");
}
```

## [downloadFile](/mcp-apps/app/requests/download-file)

Request the host to download a file. Since MCP Apps run in sandboxed iframes where direct downloads are blocked, this provides a host-mediated mechanism.

```ts theme={null}
async downloadFile(
  params: { contents: (EmbeddedResource | ResourceLink)[] },
  options?: RequestOptions,
): Promise<{ isError?: boolean }>
```

```ts theme={null}
// Download embedded text content
await app.downloadFile({
  contents: [{
    type: "resource",
    resource: {
      uri: "file:///export.json",
      mimeType: "application/json",
      text: JSON.stringify(data, null, 2),
    },
  }],
});

// Download embedded binary content
await app.downloadFile({
  contents: [{
    type: "resource",
    resource: {
      uri: "file:///image.png",
      mimeType: "image/png",
      blob: base64EncodedPng,
    },
  }],
});

// Download via resource link (host fetches)
await app.downloadFile({
  contents: [{
    type: "resource_link",
    uri: "https://api.example.com/reports/q4.pdf",
    name: "Q4 Report",
    mimeType: "application/pdf",
  }],
});
```

## [readServerResource](/mcp-apps/app/requests/read-server-resource)

Read a resource from the originating MCP server (proxied through the host).

```ts theme={null}
async readServerResource(
  params: { uri: string },
  options?: RequestOptions,
): Promise<ReadResourceResult>
```

```ts theme={null}
const result = await app.readServerResource({
  uri: "videos://bunny-1mb",
});

const content = result.contents[0];
if (content && "blob" in content) {
  const binary = Uint8Array.from(atob(content.blob), (c) => c.charCodeAt(0));
  const blob = new Blob([binary], { type: content.mimeType });
  videoElement.src = URL.createObjectURL(blob);
}
```

## [listServerResources](/mcp-apps/app/requests/list-server-resources)

List available resources on the originating MCP server (proxied through the host). Supports pagination via cursor.

```ts theme={null}
async listServerResources(
  params?: { cursor?: string },
  options?: RequestOptions,
): Promise<ListResourcesResult>
```

```ts theme={null}
const result = await app.listServerResources();

for (const resource of result.resources) {
  console.log(resource.name, resource.uri, resource.mimeType);
}

// Paginate if more results exist
if (result.nextCursor) {
  const next = await app.listServerResources({ cursor: result.nextCursor });
}
```

## [requestDisplayMode](/mcp-apps/app/requests/request-display-mode)

Request the host to change the display mode.

```ts theme={null}
async requestDisplayMode(
  params: { mode: McpUiDisplayMode },
  options?: RequestOptions,
): Promise<{ mode: McpUiDisplayMode }>
```

The returned `mode` is the mode actually set — it may differ from the requested mode if unsupported.

```ts theme={null}
const ctx = app.getHostContext();
const newMode = ctx?.displayMode === "inline" ? "fullscreen" : "inline";

if (ctx?.availableDisplayModes?.includes(newMode)) {
  const result = await app.requestDisplayMode({ mode: newMode });
  container.classList.toggle("fullscreen", result.mode === "fullscreen");
}
```

## [sendLog](/mcp-apps/app/requests/send-log)

Send log messages to the host for debugging. Logs are not added to the conversation.

```ts theme={null}
sendLog(params: { level: string; data: string; logger?: string }): Promise<void>
```

```ts theme={null}
app.sendLog({
  level: "info",
  data: "Weather data refreshed",
  logger: "WeatherApp",
});
```

## requestTeardown

Request the host to tear down this app. The host may approve or ignore the request. If approved, the host will send the standard `ui/resource-teardown` request back to the app (triggering [`onteardown`](/mcp-apps/app/event-handlers/onteardown)) before unmounting the iframe.

```ts theme={null}
async requestTeardown(params?: {}): Promise<void>
```

```ts theme={null}
// App-initiated close (e.g., "Done" button)
await app.requestTeardown();
```

## [createSamplingMessage](/mcp-apps/app/requests/create-sampling-message)

Request an LLM completion from the host via standard MCP `sampling/createMessage`. The host has full discretion over model selection and may modify or reject the request. Check `app.getHostCapabilities()?.sampling` before calling.

```ts theme={null}
async createSamplingMessage(
  params: CreateMessageRequest["params"],
  options?: RequestOptions,
): Promise<CreateMessageResult | CreateMessageResultWithTools>
```

```ts theme={null}
const result = await app.createSamplingMessage({
  messages: [
    { role: "user", content: { type: "text", text: "Summarize this." } },
  ],
  maxTokens: 100,
});
console.log(result.content);
```

When `params.tools` is provided, the result may contain `tool_use` blocks and `stopReason: "toolUse"`. Check `getHostCapabilities()?.sampling?.tools` before passing tools.

## registerTool

Register a tool that the host can call. Uses Standard Schema (Zod, ArkType, Valibot) for input/output validation. Returns a handle with `enable()`, `disable()`, `remove()`, and `update()` methods.

```ts theme={null}
registerTool<OutputArgs, InputArgs>(
  name: string,
  config: {
    title?: string;
    description?: string;
    inputSchema?: InputArgs;
    outputSchema?: OutputArgs;
    annotations?: ToolAnnotations;
    _meta?: Record<string, unknown>;
  },
  cb: AppToolCallback<InputArgs, OutputArgs>,
): RegisteredAppTool
```

```ts theme={null}
const handle = app.registerTool("get_selection", {
  description: "Get the user's current selection",
}, () => ({
  content: [{ type: "text", text: selectedText }],
}));

// Later: update, disable, or remove
handle.update({ description: "Updated description" });
handle.disable();
handle.remove();
```

## sendToolListChanged

Notify the host that the app's tool list has changed. Call this after `registerTool`, `remove()`, `enable()`, or `disable()`.

```ts theme={null}
async sendToolListChanged(params?: {}): Promise<void>
```

```ts theme={null}
await app.sendToolListChanged();
```

## [sendSizeChanged](/mcp-apps/app/requests/send-size-changed)

Manually notify the host of a UI size change. If `autoResize` is enabled (default), this is called automatically.

```ts theme={null}
sendSizeChanged(params: { width?: number; height?: number }): Promise<void>
```

```ts theme={null}
app.sendSizeChanged({ width: 400, height: 600 });
```

<Tip>
  The [sunpeak framework](/quickstart) provides React convenience hooks that wrap these methods: [`useCallServerTool`](/app-framework/hooks/use-call-server-tool), [`useCreateSamplingMessage`](/app-framework/hooks/use-create-sampling-message), [`useDownloadFile`](/app-framework/hooks/use-download-file), [`useListServerResources`](/app-framework/hooks/use-list-server-resources), [`useReadServerResource`](/app-framework/hooks/use-read-server-resource), [`useRegisterTool`](/app-framework/hooks/use-register-tool), [`useSendMessage`](/app-framework/hooks/use-send-message), [`useSendToolListChanged`](/app-framework/hooks/use-send-tool-list-changed), [`useUpdateModelContext`](/app-framework/hooks/use-update-model-context), [`useOpenLink`](/app-framework/hooks/use-open-link), [`useRequestDisplayMode`](/app-framework/hooks/use-request-display-mode), [`useRequestTeardown`](/app-framework/hooks/use-request-teardown), and [`useSendLog`](/app-framework/hooks/use-send-log).
</Tip>
