Build your own App Block with an AI agent over MCP — the sandbox model, the infa:* postMessage protocol, the connector bridge for live integration data, and the publish lifecycle.
Anyone can build an App Block: it is one HTML file plus a handful of MCP tool calls. The fastest way is to hand the work to an AI agent — Infa gives you a ready-to-paste prompt, and the MCP server exposes the full lifecycle from register to publish. This page documents the same contract the server hands to agents, so you can also build a block by hand.
Copy link
Build with an AI Agent
The quickest path is to copy Infa's ready-made agent prompt, which includes connection steps, this contract, and your board ID:
In the App Block picker (insert menu → App Block → Community tab), the Build your own block panel has a Copy prompt for your agent button. The On this board tab has the same action in its footer.
Paste the prompt into Claude Code, Cursor, or any MCP-enabled agent and describe what the block should do.
To set an agent up manually instead:
Connect it to the Infa MCP server (HTTP transport, OAuth login on first call — no API key):
Claude Code
Copy code
Copy code
claude mcp add --transport http Infa https://infa.ai/api/mcp
Other MCP clients: add an HTTP server with the URL https://infa.ai/api/mcp. For stdio-only clients, use npx -y mcp-remote https://infa.ai/api/mcp. See MCP Server for details.
Have the agent call the infa_appBlockGuide tool. It returns the server's current version of the contract below — always fetch it before building or updating a block.
Copy link
The Sandbox Model
A block is a single, self-contained HTML document — inline CSS and JavaScript, no external scripts, stylesheets, or CDNs. Infa renders it in a sandboxed iframe with allow-scripts only: the block has an opaque origin and no direct network access. It communicates with the host page exclusively via postMessage.
Copy link
Host Protocol (postMessage)
Messages your block receives (listen for message events on window):
Message
Shape
Meaning
infa:props
{ type: "infa:props", props }
The block's runtime props. Sent on load and again whenever the user edits props — re-render on every message.
if (event.data?.type === "infa:props") render(event.data.props ?? {});
});
</script>
Copy link
Live Integration Data (the Connector Bridge)
Blocks cannot fetch directly — provider data flows through the host, which proxies each request to the provider's official API with the viewing user's session. Tokens never reach the block.
Send a request:
connector request
Copy code
Copy code
window.parent.postMessage(
{
type: "infa:connector-request",
requestId: "req-1", // any unique string, echoed back in the response
connectorId: props.connectorId, // always from a prop — never hardcoded
request: {
path: "/repos/owner/repo/pulls?state=open",
method: "GET", // optional; GET unless the provider needs POST
body: undefined, // optional JSON body for POST requests
},
},
"*"
);
The host replies with one of:
{ type: "infa:connector-response", requestId, ok: true, data } on success
{ type: "infa:connector-response", requestId, ok: false, error } on failure
Supported provider APIs: GitHub, Figma, and Slack (GET); Notion (GET and POST); Linear (POST /graphql). Requests outside the provider's official API are rejected.
Copy link
Connector Rules
Never hardcode a connectorId or credential. Take the connection as a prop, and declare that prop in inputSchema with "format": "infa-connector-id" — Infa renders it as a dropdown of the board's connections in the props editor:
inputSchema excerpt
Copy code
Copy code
{
"type": "object",
"properties": {
"connectorId": {
"type": "string",
"title": "GitHub connection",
"format": "infa-connector-id"
}
},
"required": ["connectorId"]
}
Declare the providers you use in contextHints.connections (provider slugs, e.g. ["github"]) so listings badge the block as needing a connection.
Copy link
Lifecycle (Infa MCP Tools)
Step
Tool
Fetch this contract
infa_appBlockGuide — returns the server's current version of this guide; call it before building or updating
Register
infa_registerAppBlock{ boardId, name, description, icon, category, sourceHtml, inputSchema?, defaultProps?, contextHints? } — returns a blockTypeId and auto-installs the block on the board
Preview
infa_appBlockPreview — render-check the block before and after changes
Iterate
infa_updateAppBlock{ blockTypeId, ...changedFields } — update in place; never register duplicates
Insert
infa_insertAppBlock — embed the block into a document or canvas paper
List connections
infa_listConnections{ boardId } — the board's integration connections; users bind one to your block through the props editor
Publish
infa_publishAppBlock{ blockTypeId, visibility: "published" } — share the block with the community; "board" retracts it
Updates apply immediately
There is no version pinning yet — every existing install of a block renders the new sourceHtml the moment you update it, including installs on other users' boards if the block is published. Always preview before updating a widely-installed block.
Building App Blocks
Anyone can build an App Block: it is one HTML file plus a handful of MCP tool calls. The fastest way is to hand the work to an AI agent — Infa gives you a ready-to-paste prompt, and the MCP server exposes the full lifecycle from register to publish. This page documents the same contract the server hands to agents, so you can also build a block by hand.
The quickest path is to copy Infa's ready-made agent prompt, which includes connection steps, this contract, and your board ID:
Paste the prompt into Claude Code, Cursor, or any MCP-enabled agent and describe what the block should do.
To set an agent up manually instead:
Connect it to the Infa MCP server (HTTP transport, OAuth login on first call — no API key):
Other MCP clients: add an HTTP server with the URL
https://infa.ai/api/mcp. For stdio-only clients, usenpx -y mcp-remote https://infa.ai/api/mcp. See MCP Server for details.Have the agent call the
infa_appBlockGuidetool. It returns the server's current version of the contract below — always fetch it before building or updating a block.A block is a single, self-contained HTML document — inline CSS and JavaScript, no external scripts, stylesheets, or CDNs. Infa renders it in a sandboxed iframe with
allow-scriptsonly: the block has an opaque origin and no direct network access. It communicates with the host page exclusively viapostMessage.Messages your block receives (listen for
messageevents onwindow):infa:props{ type: "infa:props", props }infa:context{ type: "infa:context", context: { surface, blockId, hints, version } }surfaceis"document"or"canvas".infa:storage{ type: "infa:storage", storage }Messages your block sends (via
window.parent.postMessage(message, "*")):infa:resize{ type: "infa:resize", height }infa:storage-update{ type: "infa:storage-update", detail: { key, value } }detail: { replace: { ... } }to replace all storage.A minimal skeleton that handles props, escaping, and resize:
Blocks cannot fetch directly — provider data flows through the host, which proxies each request to the provider's official API with the viewing user's session. Tokens never reach the block.
Send a request:
The host replies with one of:
{ type: "infa:connector-response", requestId, ok: true, data }on success{ type: "infa:connector-response", requestId, ok: false, error }on failureSupported provider APIs: GitHub, Figma, and Slack (GET); Notion (GET and POST); Linear (POST
/graphql). Requests outside the provider's official API are rejected.Never hardcode a
connectorIdor credential. Take the connection as a prop, and declare that prop ininputSchemawith"format": "infa-connector-id"— Infa renders it as a dropdown of the board's connections in the props editor:Declare the providers you use in
contextHints.connections(provider slugs, e.g.["github"]) so listings badge the block as needing a connection.infa_appBlockGuide— returns the server's current version of this guide; call it before building or updatinginfa_registerAppBlock{ boardId, name, description, icon, category, sourceHtml, inputSchema?, defaultProps?, contextHints? }— returns ablockTypeIdand auto-installs the block on the boardinfa_appBlockPreview— render-check the block before and after changesinfa_updateAppBlock{ blockTypeId, ...changedFields }— update in place; never register duplicatesinfa_insertAppBlock— embed the block into a document or canvas paperinfa_listConnections{ boardId }— the board's integration connections; users bind one to your block through the props editorinfa_publishAppBlock{ blockTypeId, visibility: "published" }— share the block with the community;"board"retracts itOwners manage published blocks at infa.ai/profile?tab=blocks.
Before publishing to the community, make sure your block clears the same bar Infa's own blocks do:
textContentor anesc()helper) — props and API responses are untrusted.@media (prefers-color-scheme: dark).inputSchema.required.sourceHtml— published blocks are public.App Blocks Overview
MCP Server