Available for Paper partners. Contact us if you'd like to discuss.
- Chrome 150+ is required
- You must opt into the WebMCP origin trial
- Paper must allow your origin to use our WebMCP tools
- Paper must allow your origin to access our auth domain
- Embed Paper using an iframe with the required permissions
<iframe
src="https://app.paper.design"
allow="tools; clipboard-read; clipboard-write; local-fonts"
></iframe>- WebMCP tools are available after it's successfully loaded
const tools = await document.modelContext.getTools({
fromOrigins: ["https://app.paper.design"],
});
const tool = tools.find((t) => t.name === "get_basic_info");
ctx.executeTool(tool, "{}");- Wire up the tools to your agent library of choice
"use client";
import { useChat } from "@ai-sdk/react";
import {
DefaultChatTransport,
lastAssistantMessageIsCompleteWithToolCalls,
} from "ai";
const tools = await document.modelContext.getTools({
fromOrigins: ["https://app.paper.design"],
});
const toolsByName = new Map(tools.map((t) => [t.name, t]));
const { messages, sendMessage, addToolResult } = useChat({
transport: new DefaultChatTransport({
api: "/api/chat",
// 4. Send tool schemas to the server with each request
prepareSendMessagesRequest: ({ body, messages }) => ({
body: {
...body,
messages,
tools: tools.map(({ name, description, inputSchema }) => ({
name,
description,
inputSchema,
})),
},
}),
}),
sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithToolCalls,
// 6. Tool calls come back down and run here, in the browser
async onToolCall({ toolCall }) {
const output = await document.modelContext.executeTool(
toolsByName.get(toolCall.toolName),
JSON.stringify(toolCall.input ?? {}),
);
addToolResult({
tool: toolCall.toolName,
toolCallId: toolCall.toolCallId,
output,
});
},
});import { anthropic } from "@ai-sdk/anthropic";
import { streamText, tool, jsonSchema, convertToModelMessages } from "ai";
export async function POST(req: Request) {
const { messages, tools } = await req.json();
const result = streamText({
model: anthropic("claude-haiku-4-5"),
messages: convertToModelMessages(messages),
// 5. No `execute` → calls are streamed back to the browser's onToolCall
tools: Object.fromEntries(
tools.map((t) => [t.name, tool({ description: t.description, inputSchema: jsonSchema(t.inputSchema) })]),
),
});
return result.toUIMessageStreamResponse();
}