Appearance
Creating Tools
Learn how to create embed tools that return interactive web content.
Overview
An embed tool is an MCP tool that returns a toqanEmbed JSON structure. When your agent calls this tool, the frontend automatically renders the content as an iframe in the conversation.
Tool Structure
Every embed tool follows this pattern:
javascript
server.registerTool(
"tool_name",
{
description: "Tool description for the agent",
inputSchema: {},
},
async () => {
const embedData = {
toqanEmbed: {
type: "iframe",
url: "https://example.com",
height: 700,
},
};
return {
content: [{ type: "text", text: JSON.stringify(embedData) }],
structuredContent: { type: "iframe" },
};
},
);Step-by-Step: Create a New Tool
Step 1: Create Tool File
Create a new file in src/mcp/tools/ (e.g., my-tool.js):
javascript
/**
* Registers your custom tool on the MCP server
* @param {import("@modelcontextprotocol/sdk/server/mcp.js").McpServer} server - The MCP server instance
*/
export function registerMyTool(server) {
server.registerTool(
"get_my_website",
{
description:
"Returns my website. Use this tool when the user wants to view my website or learn more about me.",
inputSchema: {},
},
async () => {
const embedData = {
toqanEmbed: {
type: "iframe",
url: "https://example.com",
height: 700,
},
};
return {
content: [{ type: "text", text: JSON.stringify(embedData) }],
structuredContent: { type: "iframe" },
};
},
);
}Step 2: Register the Tool
Add your tool to src/mcp/tools/index.js:
javascript
import { registerMyTool } from "./my-tool.js";
export function registerAllTools(server) {
// ... existing tools
registerMyTool(server);
}Step 3: Test Your Tool
- Restart your server:
npm start - Call the tool from your agent
- Verify the iframe renders in the conversation
Embed JSON Schema
The toqanEmbed object must follow this structure:
typescript
{
toqanEmbed: {
type: "iframe"; // Only "iframe" is currently supported
url: string; // URL to embed (required)
// OR
src?: string; // Alternative field name (also supported)
height?: number; // Height in pixels (default: 400)
}
}Example: External Website
Embed an external website directly:
javascript
server.registerTool(
"get_ifood_website",
{
description:
"Returns the iFood website. Use this tool when the user wants to order food, browse restaurants, or access iFood services.",
inputSchema: {},
},
async () => {
const embedData = {
toqanEmbed: {
type: "iframe",
url: "https://www.ifood.com.br",
height: 700,
},
};
return {
content: [{ type: "text", text: JSON.stringify(embedData) }],
structuredContent: { type: "iframe" },
};
},
);Example: Self-Hosted Widget
Serve your own HTML widget and embed it:
- Create HTML widget (e.g.,
website.html) - Serve it via route (already configured at
GET /website.html) - Reference it in your tool:
javascript
server.registerTool(
"get_my_widget",
{
description: "Returns my custom widget",
inputSchema: {},
},
async () => {
const embedData = {
toqanEmbed: {
type: "iframe",
url: "https://your-mcp-server.com/website.html",
height: 700,
},
};
return {
content: [{ type: "text", text: JSON.stringify(embedData) }],
structuredContent: { type: "iframe" },
};
},
);Best Practices
- Use descriptive tool names - Prefix with
get_for clarity - Write clear descriptions - Help the agent understand when to use the tool
- Return URLs, not HTML - For performance, return URLs instead of inline HTML
- Set appropriate heights - Default is 400px, adjust based on content needs
- Use HTTPS - Required for iframe embedding in most browsers
Common Patterns
Dynamic URLs
javascript
async (args) => {
const url = `https://example.com/widget?id=${args.id}`;
return {
content: [{
type: "text",
text: JSON.stringify({
toqanEmbed: { type: "iframe", url, height: 700 }
})
}],
structuredContent: { type: "iframe" },
};
}Conditional Embeds
javascript
async (args) => {
const url = args.type === "preview"
? "https://preview.example.com"
: "https://example.com";
return {
content: [{
type: "text",
text: JSON.stringify({
toqanEmbed: { type: "iframe", url, height: 700 }
})
}],
structuredContent: { type: "iframe" },
};
}