Your First Plugin
This is the part I'm most excited about.
You're going to build a working plugin from scratch. By the end, your AI agent will be able to call a tool that runs inside a real browser tab — using document and window directly, the same way the page's own JavaScript does. It takes about 10 minutes.
Prerequisites
- OpenTabs running (server + extension + MCP client)
- Node.js v22+ installed
1. Scaffold the project
opentabs plugin create my-app --domain .example.com --display "My App"
cd my-app
npm installThis creates a standalone npm package with everything wired up. The plugin gets registered with the MCP server on first build.
2. Define a tool
Open src/tools/example.ts. Replace the contents with a tool that reads the page title:
import { defineTool } from '@opentabs-dev/plugin-sdk';
import { z } from 'zod';
export const getPageTitle = defineTool({
name: 'get_page_title',
description: 'Returns the current page title and URL.',
input: z.object({}),
output: z.object({
title: z.string(),
url: z.string(),
}),
async handle() {
return {
title: document.title,
url: window.location.href,
};
},
});Notice what's happening in handle(): you're calling document.title and window.location.href directly. That's not a simulation or a proxy — your code runs in the actual page context, same as if you typed it in the DevTools console. You have the real DOM, the real window, the real everything. That's how plugins access internal APIs too: same fetch() calls the web app makes, with the same cookies, the same auth headers, the same session.
3. Register the tool
Open src/index.ts and update the imports and tools array:
import { OpenTabsPlugin } from '@opentabs-dev/plugin-sdk';
import { getPageTitle } from './tools/example.js';
import type { ToolDefinition } from '@opentabs-dev/plugin-sdk';
class MyAppPlugin extends OpenTabsPlugin {
readonly name = 'my-app';
readonly description = 'My first OpenTabs plugin';
readonly displayName = 'My App';
readonly urlPatterns = ['*://*.example.com/*'];
readonly tools: ToolDefinition[] = [getPageTitle];
async isReady(): Promise<boolean> {
// For production plugins, check if the user is authenticated.
// Example: return document.querySelector('.logged-in-indicator') !== null;
return true;
}
}
export default new MyAppPlugin();4. Build
npm run buildThis compiles your plugin, generates the adapter bundle and tool manifest, and notifies the running MCP server. That last part is important — you don't need to restart anything. The server picks up the new plugin immediately.
5. Test it
Open a tab matching your URL pattern (e.g., example.com), then ask your AI agent:
What's the title of the current example.com page?
The agent will call my-app_get_page_title and return the page title and URL.
Verify the plugin is loaded:
opentabs statusWhat just happened?
Here's the full chain, because it's worth understanding:
npm run buildcompiled your TypeScript and bundled an adapter IIFE — a self-contained script that runs in the page context- The build auto-registered your plugin in
~/.opentabs/config.jsonand calledPOST /reloadto notify the server - The server sent the plugin definition to the Chrome extension via WebSocket
- The extension injected the adapter into matching tabs
- When the AI agent called the tool, the request flowed: MCP client → server → extension → adapter → page → back
That's the whole platform in five steps. Your tool code ran inside the actual web page, with full access to the page's DOM, APIs, and authenticated session. The result traveled back through the extension, the server, and into your AI's context.
Fun fact: most of the plugins in this repo were built by AI in minutes. Point your agent at a website, tell it to build a plugin, and it handles the API discovery, scaffolding, and testing. Your agent wrote it, you can read every line, and it runs in your browser with your session. That's a pretty good trust model.
Next steps
- Plugin Development — the complete guide covering SDK utilities, error handling, logging, Zod schemas, hot reload, and publishing
- Plugin Class — API reference for
OpenTabsPlugin - Tools — API reference for
defineTool
Last Updated: 10 Mar, 2026