@opentabs-dev/mcp-server 0.0.63 → 0.0.65
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browser-tools/click-element.js +3 -3
- package/dist/browser-tools/click-element.js.map +1 -1
- package/dist/browser-tools/press-key.js +5 -5
- package/dist/browser-tools/press-key.js.map +1 -1
- package/dist/dev-proxy.js +19 -7
- package/dist/dev-proxy.js.map +1 -1
- package/dist/extension-handlers.d.ts +11 -2
- package/dist/extension-handlers.d.ts.map +1 -1
- package/dist/extension-handlers.js +73 -7
- package/dist/extension-handlers.js.map +1 -1
- package/dist/extension-protocol.d.ts.map +1 -1
- package/dist/extension-protocol.js +10 -1
- package/dist/extension-protocol.js.map +1 -1
- package/dist/http-routes.d.ts.map +1 -1
- package/dist/http-routes.js +56 -0
- package/dist/http-routes.js.map +1 -1
- package/dist/loader.d.ts +2 -0
- package/dist/loader.d.ts.map +1 -1
- package/dist/loader.js +9 -0
- package/dist/loader.js.map +1 -1
- package/dist/mcp-prompts.d.ts +71 -0
- package/dist/mcp-prompts.d.ts.map +1 -0
- package/dist/mcp-prompts.js +248 -0
- package/dist/mcp-prompts.js.map +1 -0
- package/dist/mcp-resources.d.ts +53 -0
- package/dist/mcp-resources.d.ts.map +1 -0
- package/dist/mcp-resources.js +139 -0
- package/dist/mcp-resources.js.map +1 -0
- package/dist/mcp-setup.d.ts +13 -1
- package/dist/mcp-setup.d.ts.map +1 -1
- package/dist/mcp-setup.js +166 -2
- package/dist/mcp-setup.js.map +1 -1
- package/dist/plugin-management.d.ts +14 -1
- package/dist/plugin-management.d.ts.map +1 -1
- package/dist/plugin-management.js +55 -1
- package/dist/plugin-management.js.map +1 -1
- package/dist/prompts/audit-ai-docs.d.ts +6 -0
- package/dist/prompts/audit-ai-docs.d.ts.map +1 -0
- package/dist/prompts/audit-ai-docs.js +155 -0
- package/dist/prompts/audit-ai-docs.js.map +1 -0
- package/dist/prompts/build-plugin.d.ts +3 -0
- package/dist/prompts/build-plugin.d.ts.map +1 -0
- package/dist/prompts/build-plugin.js +455 -0
- package/dist/prompts/build-plugin.js.map +1 -0
- package/dist/prompts/contribute-learnings.d.ts +12 -0
- package/dist/prompts/contribute-learnings.d.ts.map +1 -0
- package/dist/prompts/contribute-learnings.js +107 -0
- package/dist/prompts/contribute-learnings.js.map +1 -0
- package/dist/prompts/plugin-icon.d.ts +5 -0
- package/dist/prompts/plugin-icon.d.ts.map +1 -0
- package/dist/prompts/plugin-icon.js +147 -0
- package/dist/prompts/plugin-icon.js.map +1 -0
- package/dist/prompts/setup-plugin.d.ts +3 -0
- package/dist/prompts/setup-plugin.d.ts.map +1 -0
- package/dist/prompts/setup-plugin.js +197 -0
- package/dist/prompts/setup-plugin.js.map +1 -0
- package/dist/prompts/troubleshoot.d.ts +3 -0
- package/dist/prompts/troubleshoot.d.ts.map +1 -0
- package/dist/prompts/troubleshoot.js +191 -0
- package/dist/prompts/troubleshoot.js.map +1 -0
- package/dist/reload.d.ts.map +1 -1
- package/dist/reload.js +9 -6
- package/dist/reload.js.map +1 -1
- package/dist/resources/browser-tools.d.ts +3 -0
- package/dist/resources/browser-tools.d.ts.map +1 -0
- package/dist/resources/browser-tools.js +100 -0
- package/dist/resources/browser-tools.js.map +1 -0
- package/dist/resources/cli.d.ts +3 -0
- package/dist/resources/cli.d.ts.map +1 -0
- package/dist/resources/cli.js +217 -0
- package/dist/resources/cli.js.map +1 -0
- package/dist/resources/plugin-development.d.ts +3 -0
- package/dist/resources/plugin-development.d.ts.map +1 -0
- package/dist/resources/plugin-development.js +596 -0
- package/dist/resources/plugin-development.js.map +1 -0
- package/dist/resources/quick-start.d.ts +3 -0
- package/dist/resources/quick-start.d.ts.map +1 -0
- package/dist/resources/quick-start.js +210 -0
- package/dist/resources/quick-start.js.map +1 -0
- package/dist/resources/sdk-api.d.ts +3 -0
- package/dist/resources/sdk-api.d.ts.map +1 -0
- package/dist/resources/sdk-api.js +199 -0
- package/dist/resources/sdk-api.js.map +1 -0
- package/dist/resources/self-improvement.d.ts +10 -0
- package/dist/resources/self-improvement.d.ts.map +1 -0
- package/dist/resources/self-improvement.js +91 -0
- package/dist/resources/self-improvement.js.map +1 -0
- package/dist/resources/status.d.ts +5 -0
- package/dist/resources/status.d.ts.map +1 -0
- package/dist/resources/status.js +27 -0
- package/dist/resources/status.js.map +1 -0
- package/dist/resources/troubleshooting.d.ts +3 -0
- package/dist/resources/troubleshooting.d.ts.map +1 -0
- package/dist/resources/troubleshooting.js +167 -0
- package/dist/resources/troubleshooting.js.map +1 -0
- package/dist/state.d.ts +6 -0
- package/dist/state.d.ts.map +1 -1
- package/dist/state.js +9 -3
- package/dist/state.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,455 @@
|
|
|
1
|
+
/** Prompt text for the `build_plugin` prompt — full plugin development workflow. */
|
|
2
|
+
export const buildPluginPromptText = (url, name) => {
|
|
3
|
+
const nameClause = name ? `The plugin name should be \`${name}\`.` : '';
|
|
4
|
+
return `Build a production-ready OpenTabs plugin for ${url}. ${nameClause}
|
|
5
|
+
|
|
6
|
+
Follow the complete workflow below. Each phase builds on the previous one — do not skip phases.
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Prerequisites
|
|
11
|
+
|
|
12
|
+
- The user has the target web app open in a browser tab at ${url}
|
|
13
|
+
- The MCP server is running (you are connected to it)
|
|
14
|
+
- You have access to the filesystem for creating plugin source files
|
|
15
|
+
|
|
16
|
+
### Browser Tool Permissions
|
|
17
|
+
|
|
18
|
+
Plugin development requires heavy use of browser tools (\`browser_execute_script\`, \`browser_navigate_tab\`, \`browser_get_tab_content\`, etc.). By default, tools have permission \`'off'\` (disabled) or \`'ask'\` (requires human approval).
|
|
19
|
+
|
|
20
|
+
Ask the user if they want to enable \`skipPermissions\` to bypass approval prompts during development. Set the env var: \`OPENTABS_DANGEROUSLY_SKIP_PERMISSIONS=1\`. Warn them this bypasses human approval and should only be used during active plugin development.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Core Principle: Use the Real APIs, Never the DOM
|
|
25
|
+
|
|
26
|
+
Every plugin tool must use the web app's own APIs — the same HTTP endpoints, WebSocket channels, or internal RPC methods that the web app's JavaScript calls. DOM scraping is never acceptable as a tool implementation strategy. It is fragile (breaks on any UI change), limited (cannot access data not rendered on screen), and slow (parsing HTML is orders of magnitude slower than a JSON API call).
|
|
27
|
+
|
|
28
|
+
When an API is hard to discover, spend time reverse-engineering it (network capture, XHR interception, source code reading). Do not fall back to DOM scraping because it is faster to implement.
|
|
29
|
+
|
|
30
|
+
**Only three uses of the DOM are acceptable:**
|
|
31
|
+
1. \`isReady()\` — checking authentication signals (meta tags, page globals, indicator cookies)
|
|
32
|
+
2. URL hash navigation — triggering client-side route changes
|
|
33
|
+
3. Last-resort compose flows — when the app has no API for creating content and the UI is the only path (rare)
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Phase 1: Research the Codebase
|
|
38
|
+
|
|
39
|
+
Before writing any code, study the existing plugin infrastructure using the filesystem:
|
|
40
|
+
|
|
41
|
+
1. **Study the Plugin SDK** — read \`platform/plugin-sdk/CLAUDE.md\` and key source files (\`src/index.ts\`, \`src/plugin.ts\`, \`src/tool.ts\`). Understand:
|
|
42
|
+
- \`OpenTabsPlugin\` abstract base class (name, displayName, description, urlPatterns, tools, isReady)
|
|
43
|
+
- \`defineTool({ name, displayName, description, icon, input, output, handle })\` factory
|
|
44
|
+
- \`ToolError\` static factories: \`.auth()\`, \`.notFound()\`, \`.rateLimited()\`, \`.timeout()\`, \`.validation()\`, \`.internal()\`
|
|
45
|
+
- SDK utilities: \`fetchJSON\`, \`postJSON\`, \`getLocalStorage\`, \`waitForSelector\`, \`retry\`, \`sleep\`, \`log\`
|
|
46
|
+
- All plugin code runs in the **browser page context** (not server-side)
|
|
47
|
+
|
|
48
|
+
2. **Study an existing plugin** (e.g., \`plugins/slack/\`) as the canonical reference:
|
|
49
|
+
- \`src/index.ts\` — plugin class, imports all tools
|
|
50
|
+
- \`src/slack-api.ts\` — API wrapper with auth extraction + error classification
|
|
51
|
+
- \`src/tools/\` — one file per tool, shared schemas
|
|
52
|
+
- \`package.json\` — the opentabs field, dependency versions, scripts
|
|
53
|
+
|
|
54
|
+
3. **Study \`plugins/CLAUDE.md\`** — plugin isolation rules and conventions
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## Phase 2: Explore the Target Web App
|
|
59
|
+
|
|
60
|
+
This is the most critical phase. Use browser tools to understand how the web app works.
|
|
61
|
+
|
|
62
|
+
### Step 1: Find the Tab
|
|
63
|
+
|
|
64
|
+
\`\`\`
|
|
65
|
+
plugin_list_tabs or browser_list_tabs → find the tab for ${url}
|
|
66
|
+
\`\`\`
|
|
67
|
+
|
|
68
|
+
### Step 2: Analyze the Site
|
|
69
|
+
|
|
70
|
+
\`\`\`
|
|
71
|
+
plugin_analyze_site(url: "${url}")
|
|
72
|
+
\`\`\`
|
|
73
|
+
|
|
74
|
+
This gives you a comprehensive report: auth methods, API endpoints, framework detection, storage keys, and concrete tool suggestions.
|
|
75
|
+
|
|
76
|
+
### Step 3: Enable Network Capture and Explore
|
|
77
|
+
|
|
78
|
+
\`\`\`
|
|
79
|
+
browser_enable_network_capture(tabId, urlFilter: "/api")
|
|
80
|
+
\`\`\`
|
|
81
|
+
|
|
82
|
+
Navigate around in the app to trigger API calls, then read them:
|
|
83
|
+
|
|
84
|
+
\`\`\`
|
|
85
|
+
browser_get_network_requests(tabId)
|
|
86
|
+
\`\`\`
|
|
87
|
+
|
|
88
|
+
Study the captured traffic to understand:
|
|
89
|
+
- API base URL
|
|
90
|
+
- Whether the API is same-origin or cross-origin (critical for CORS)
|
|
91
|
+
- Request format (JSON body vs form-encoded)
|
|
92
|
+
- Required headers (content-type, custom headers)
|
|
93
|
+
- Response shapes for each endpoint
|
|
94
|
+
- Error response format
|
|
95
|
+
|
|
96
|
+
### Step 4: Check CORS Policy (for Cross-Origin APIs)
|
|
97
|
+
|
|
98
|
+
If the API is on a different subdomain, verify CORS behavior:
|
|
99
|
+
|
|
100
|
+
\`\`\`bash
|
|
101
|
+
curl -sI -X OPTIONS https://api.example.com/endpoint \\
|
|
102
|
+
-H "Origin: ${url}" \\
|
|
103
|
+
-H "Access-Control-Request-Method: GET" \\
|
|
104
|
+
-H "Access-Control-Request-Headers: Authorization,Content-Type" \\
|
|
105
|
+
| grep -i "access-control"
|
|
106
|
+
\`\`\`
|
|
107
|
+
|
|
108
|
+
### Step 5: Discover Auth Token
|
|
109
|
+
|
|
110
|
+
**First, always check cookies with \`browser_get_cookies\`** to understand the auth model. Then probe the page:
|
|
111
|
+
|
|
112
|
+
- **localStorage**: Direct access or iframe fallback if the app deletes \`window.localStorage\`
|
|
113
|
+
- **Page globals**: \`window.__APP_STATE__\`, \`window.boot_data\`, \`window.__NEXT_DATA__\`
|
|
114
|
+
- **Webpack module stores**: For React/webpack SPAs
|
|
115
|
+
- **Cookies**: \`document.cookie\` for non-HttpOnly tokens
|
|
116
|
+
- **Script tags**: Inline \`<script>\` tags with embedded config
|
|
117
|
+
|
|
118
|
+
### Step 6: Test the API
|
|
119
|
+
|
|
120
|
+
Once you have the token, make a test API call with \`browser_execute_script\`:
|
|
121
|
+
|
|
122
|
+
\`\`\`javascript
|
|
123
|
+
const resp = await fetch('https://example.com/api/v2/me', {
|
|
124
|
+
headers: { Authorization: 'Bearer ' + token },
|
|
125
|
+
credentials: 'include',
|
|
126
|
+
});
|
|
127
|
+
const data = await resp.json();
|
|
128
|
+
return data;
|
|
129
|
+
\`\`\`
|
|
130
|
+
|
|
131
|
+
### Step 7: Intercept Internal API Traffic (for apps without clean REST APIs)
|
|
132
|
+
|
|
133
|
+
Some web apps do not expose clean REST or GraphQL APIs. Instead they use internal RPC endpoints, obfuscated paths, or proprietary protocols that are hard to discover via network capture alone. For these apps, monkey-patch \`XMLHttpRequest\` and \`fetch\` to intercept all API traffic and capture auth headers at runtime.
|
|
134
|
+
|
|
135
|
+
Install the interceptor at adapter load time to capture auth tokens from early boot requests. Store captured data on \`globalThis\` so it survives adapter re-injection.
|
|
136
|
+
|
|
137
|
+
\`\`\`javascript
|
|
138
|
+
// XHR interceptor — captures internal API requests and auth headers
|
|
139
|
+
const captured = { authHeader: null, requests: [] };
|
|
140
|
+
|
|
141
|
+
const origOpen = XMLHttpRequest.prototype.open;
|
|
142
|
+
const origSetHeader = XMLHttpRequest.prototype.setRequestHeader;
|
|
143
|
+
const origSend = XMLHttpRequest.prototype.send;
|
|
144
|
+
|
|
145
|
+
XMLHttpRequest.prototype.open = function (method, url) {
|
|
146
|
+
this._method = method;
|
|
147
|
+
this._url = url;
|
|
148
|
+
return origOpen.apply(this, arguments);
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
XMLHttpRequest.prototype.setRequestHeader = function (name, value) {
|
|
152
|
+
if (/auth|token|x-api|x-csrf/i.test(name)) {
|
|
153
|
+
captured.authHeader = { name, value };
|
|
154
|
+
}
|
|
155
|
+
return origSetHeader.apply(this, arguments);
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
XMLHttpRequest.prototype.send = function (body) {
|
|
159
|
+
captured.requests.push({ method: this._method, url: this._url });
|
|
160
|
+
return origSend.apply(this, arguments);
|
|
161
|
+
};
|
|
162
|
+
\`\`\`
|
|
163
|
+
|
|
164
|
+
Use this when:
|
|
165
|
+
- The app uses internal RPC endpoints not visible in standard network capture
|
|
166
|
+
- Auth tokens are computed by obfuscated JavaScript and cannot be extracted from storage
|
|
167
|
+
- You need to discover which headers the app sends on its own API calls
|
|
168
|
+
|
|
169
|
+
### Step 8: Map the API Surface
|
|
170
|
+
|
|
171
|
+
Discover the key endpoints: user/profile, list resources, get single resource, create/update/delete, search, messaging, reactions.
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## Phase 3: Scaffold the Plugin
|
|
176
|
+
|
|
177
|
+
\`\`\`bash
|
|
178
|
+
cd plugins/
|
|
179
|
+
opentabs plugin create <name> --domain <domain> --display <DisplayName> --description "OpenTabs plugin for <DisplayName>"
|
|
180
|
+
\`\`\`
|
|
181
|
+
|
|
182
|
+
After scaffolding, compare \`package.json\` with an existing plugin (e.g., \`plugins/slack/package.json\`) and align:
|
|
183
|
+
- Package name: \`@opentabs-dev/opentabs-plugin-<name>\` for official plugins
|
|
184
|
+
- Version: Match the current platform version
|
|
185
|
+
- Add: \`publishConfig\`, \`check\` script
|
|
186
|
+
- Dependency versions: Match \`@opentabs-dev/plugin-sdk\` and \`@opentabs-dev/plugin-tools\` versions
|
|
187
|
+
|
|
188
|
+
---
|
|
189
|
+
|
|
190
|
+
## Phase 4: Design the Tool Set
|
|
191
|
+
|
|
192
|
+
**Maximize API coverage.** Add as many tools as the API supports. A typical production plugin has 15-25+ tools across these categories:
|
|
193
|
+
|
|
194
|
+
- **Content**: send, edit, delete, read/list, search
|
|
195
|
+
- **Resources/Containers**: list, get info, create, update, delete
|
|
196
|
+
- **Users/Members**: list, get profile
|
|
197
|
+
- **Interactions**: reactions, pins, bookmarks
|
|
198
|
+
- **Platform-specific**: threads, DMs, file uploads, etc.
|
|
199
|
+
|
|
200
|
+
For each API resource, ask: can the user list it, get one, create one, update one, delete one, and search it? If the API supports it, add the tool.
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
## Phase 5: Implement
|
|
205
|
+
|
|
206
|
+
### File Structure
|
|
207
|
+
|
|
208
|
+
\`\`\`
|
|
209
|
+
src/
|
|
210
|
+
index.ts # Plugin class — imports all tools, implements isReady()
|
|
211
|
+
<name>-api.ts # API wrapper — auth extraction + error classification
|
|
212
|
+
tools/
|
|
213
|
+
schemas.ts # Shared Zod schemas + defensive mappers
|
|
214
|
+
send-message.ts # One file per tool
|
|
215
|
+
...
|
|
216
|
+
\`\`\`
|
|
217
|
+
|
|
218
|
+
### API Wrapper Pattern (\`<name>-api.ts\`)
|
|
219
|
+
|
|
220
|
+
The API wrapper handles auth extraction, request construction, and error classification:
|
|
221
|
+
|
|
222
|
+
\`\`\`typescript
|
|
223
|
+
import { ToolError } from '@opentabs-dev/plugin-sdk';
|
|
224
|
+
|
|
225
|
+
interface AppAuth {
|
|
226
|
+
token: string;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const getAuth = (): AppAuth | null => {
|
|
230
|
+
// Check globalThis persistence first (survives adapter re-injection)
|
|
231
|
+
// Then try localStorage, page globals, cookies
|
|
232
|
+
// Return null if not authenticated
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
export const isAuthenticated = (): boolean => getAuth() !== null;
|
|
236
|
+
|
|
237
|
+
export const waitForAuth = (): Promise<boolean> =>
|
|
238
|
+
new Promise((resolve) => {
|
|
239
|
+
let elapsed = 0;
|
|
240
|
+
const interval = 500;
|
|
241
|
+
const maxWait = 5000;
|
|
242
|
+
const timer = setInterval(() => {
|
|
243
|
+
elapsed += interval;
|
|
244
|
+
if (isAuthenticated()) { clearInterval(timer); resolve(true); return; }
|
|
245
|
+
if (elapsed >= maxWait) { clearInterval(timer); resolve(false); }
|
|
246
|
+
}, interval);
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
export const api = async <T extends Record<string, unknown>>(
|
|
250
|
+
endpoint: string,
|
|
251
|
+
options: { method?: string; body?: Record<string, unknown>; query?: Record<string, string | number | boolean | undefined> } = {},
|
|
252
|
+
): Promise<T> => {
|
|
253
|
+
const auth = getAuth();
|
|
254
|
+
if (!auth) throw ToolError.auth('Not authenticated — please log in.');
|
|
255
|
+
|
|
256
|
+
let url = \\\`https://example.com/api\\\${endpoint}\\\`;
|
|
257
|
+
if (options.query) {
|
|
258
|
+
const params = new URLSearchParams();
|
|
259
|
+
for (const [key, value] of Object.entries(options.query)) {
|
|
260
|
+
if (value !== undefined) params.append(key, String(value));
|
|
261
|
+
}
|
|
262
|
+
const qs = params.toString();
|
|
263
|
+
if (qs) url += \\\`?\\\${qs}\\\`;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const headers: Record<string, string> = { Authorization: \\\`Bearer \\\${auth.token}\\\` };
|
|
267
|
+
let fetchBody: string | undefined;
|
|
268
|
+
if (options.body) {
|
|
269
|
+
headers['Content-Type'] = 'application/json';
|
|
270
|
+
fetchBody = JSON.stringify(options.body);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
let response: Response;
|
|
274
|
+
try {
|
|
275
|
+
response = await fetch(url, {
|
|
276
|
+
method: options.method ?? 'GET', headers, body: fetchBody,
|
|
277
|
+
credentials: 'include', signal: AbortSignal.timeout(30_000),
|
|
278
|
+
});
|
|
279
|
+
} catch (err: unknown) {
|
|
280
|
+
if (err instanceof DOMException && err.name === 'TimeoutError')
|
|
281
|
+
throw ToolError.timeout(\\\`API request timed out: \\\${endpoint}\\\`);
|
|
282
|
+
throw new ToolError(
|
|
283
|
+
\\\`Network error: \\\${err instanceof Error ? err.message : String(err)}\\\`,
|
|
284
|
+
'network_error', { category: 'internal', retryable: true },
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
if (!response.ok) {
|
|
289
|
+
const errorBody = (await response.text().catch(() => '')).substring(0, 512);
|
|
290
|
+
if (response.status === 429) throw ToolError.rateLimited(\\\`Rate limited: \\\${endpoint}\\\`);
|
|
291
|
+
if (response.status === 401 || response.status === 403)
|
|
292
|
+
throw ToolError.auth(\\\`Auth error (\\\${response.status}): \\\${errorBody}\\\`);
|
|
293
|
+
if (response.status === 404) throw ToolError.notFound(\\\`Not found: \\\${endpoint}\\\`);
|
|
294
|
+
throw ToolError.internal(\\\`API error (\\\${response.status}): \\\${endpoint} — \\\${errorBody}\\\`);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
if (response.status === 204) return {} as T;
|
|
298
|
+
return (await response.json()) as T;
|
|
299
|
+
};
|
|
300
|
+
\`\`\`
|
|
301
|
+
|
|
302
|
+
### Tool Pattern (one file per tool)
|
|
303
|
+
|
|
304
|
+
\`\`\`typescript
|
|
305
|
+
import { defineTool } from '@opentabs-dev/plugin-sdk';
|
|
306
|
+
import { z } from 'zod';
|
|
307
|
+
import { api } from '../<name>-api.js';
|
|
308
|
+
|
|
309
|
+
export const sendMessage = defineTool({
|
|
310
|
+
name: 'send_message',
|
|
311
|
+
displayName: 'Send Message',
|
|
312
|
+
description: 'Send a message to a channel. Supports markdown formatting.',
|
|
313
|
+
summary: 'Send a message to a channel',
|
|
314
|
+
icon: 'send',
|
|
315
|
+
input: z.object({
|
|
316
|
+
channel: z.string().describe('Channel ID to send the message to'),
|
|
317
|
+
content: z.string().describe('Message text content'),
|
|
318
|
+
}),
|
|
319
|
+
output: z.object({
|
|
320
|
+
id: z.string().describe('Message ID'),
|
|
321
|
+
}),
|
|
322
|
+
handle: async (params) => {
|
|
323
|
+
const data = await api<Record<string, unknown>>(
|
|
324
|
+
'/channels/' + params.channel + '/messages',
|
|
325
|
+
{ method: 'POST', body: { content: params.content } },
|
|
326
|
+
);
|
|
327
|
+
return { id: (data.id as string) ?? '' };
|
|
328
|
+
},
|
|
329
|
+
});
|
|
330
|
+
\`\`\`
|
|
331
|
+
|
|
332
|
+
### Plugin Class Pattern (\`index.ts\`)
|
|
333
|
+
|
|
334
|
+
\`\`\`typescript
|
|
335
|
+
import { OpenTabsPlugin } from '@opentabs-dev/plugin-sdk';
|
|
336
|
+
import type { ToolDefinition } from '@opentabs-dev/plugin-sdk';
|
|
337
|
+
import { isAuthenticated, waitForAuth } from './<name>-api.js';
|
|
338
|
+
import { sendMessage } from './tools/send-message.js';
|
|
339
|
+
|
|
340
|
+
class MyPlugin extends OpenTabsPlugin {
|
|
341
|
+
readonly name = '<name>';
|
|
342
|
+
readonly description = 'OpenTabs plugin for <DisplayName>';
|
|
343
|
+
override readonly displayName = '<DisplayName>';
|
|
344
|
+
readonly urlPatterns = ['*://*.example.com/*'];
|
|
345
|
+
readonly tools: ToolDefinition[] = [sendMessage];
|
|
346
|
+
|
|
347
|
+
async isReady(): Promise<boolean> {
|
|
348
|
+
if (isAuthenticated()) return true;
|
|
349
|
+
return waitForAuth();
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
export default new MyPlugin();
|
|
354
|
+
\`\`\`
|
|
355
|
+
|
|
356
|
+
---
|
|
357
|
+
|
|
358
|
+
## Phase 6: Build and Test
|
|
359
|
+
|
|
360
|
+
### Build
|
|
361
|
+
|
|
362
|
+
\`\`\`bash
|
|
363
|
+
cd plugins/<name>
|
|
364
|
+
npm install
|
|
365
|
+
npm run build
|
|
366
|
+
\`\`\`
|
|
367
|
+
|
|
368
|
+
### Full Check Suite
|
|
369
|
+
|
|
370
|
+
\`\`\`bash
|
|
371
|
+
npm run check # build + type-check + lint + format:check
|
|
372
|
+
\`\`\`
|
|
373
|
+
|
|
374
|
+
**Every command must exit 0.** Fix any failures before proceeding.
|
|
375
|
+
|
|
376
|
+
### Mandatory Tool Verification
|
|
377
|
+
|
|
378
|
+
**The plugin is not done until every tool has been called against the live browser.** Tools that have not been verified may have wrong field mappings, broken endpoints, or incorrect response parsing.
|
|
379
|
+
|
|
380
|
+
1. **Verify plugin loaded**: \`plugin_list_tabs(plugin: "<name>")\` — must show \`state: "ready"\`
|
|
381
|
+
2. **Call every read-only tool** (list, get, search) — verify response contains real data with correct field mappings
|
|
382
|
+
3. **Call every write tool** with round-trip tests (create → verify → delete → verify)
|
|
383
|
+
4. **Test error classification** — call a tool with an invalid ID, verify \`ToolError.notFound\` is returned
|
|
384
|
+
5. **Fix every failure** — use \`browser_execute_script\` to inspect raw API responses and fix mappers
|
|
385
|
+
|
|
386
|
+
**A plugin with untested tools is worse than a plugin with fewer tools.** Remove tools you cannot verify rather than shipping them broken.
|
|
387
|
+
|
|
388
|
+
---
|
|
389
|
+
|
|
390
|
+
## Key Conventions
|
|
391
|
+
|
|
392
|
+
- **One file per tool** in \`src/tools/\`
|
|
393
|
+
- **Every Zod field gets \`.describe()\`** — this is what AI agents see in the tool schema
|
|
394
|
+
- **\`description\` is for AI clients** — detailed, informative. \`summary\` is for humans — short, under 80 chars
|
|
395
|
+
- **Defensive mapping** with fallback defaults (\`data.field ?? ''\`) — never trust API shapes
|
|
396
|
+
- **Error classification is critical** — use \`ToolError\` factories, never throw raw errors
|
|
397
|
+
- **\`credentials: 'include'\`** on all fetch calls
|
|
398
|
+
- **30-second timeout** via \`AbortSignal.timeout(30_000)\`
|
|
399
|
+
- **\`.js\` extension** on all imports (ESM requirement)
|
|
400
|
+
- **No \`.transform()\`/\`.pipe()\`/\`.preprocess()\`** in Zod schemas (breaks JSON Schema serialization)
|
|
401
|
+
|
|
402
|
+
---
|
|
403
|
+
|
|
404
|
+
## Common Gotchas
|
|
405
|
+
|
|
406
|
+
1. **All plugin code runs in the browser** — no Node.js APIs
|
|
407
|
+
2. **SPAs hydrate asynchronously** — \`isReady()\` must poll (500ms interval, 5s max)
|
|
408
|
+
3. **Some apps delete browser APIs** — use iframe fallback for \`localStorage\`
|
|
409
|
+
4. **Tokens must persist on \`globalThis.__openTabs.tokenCache.<pluginName>\`** — module-level variables reset on extension reload
|
|
410
|
+
5. **HttpOnly cookies are invisible to plugin code** — use \`credentials: 'include'\` for the browser to send them automatically, detect auth status from DOM signals
|
|
411
|
+
6. **Parse error response bodies before classifying by HTTP status** — many apps reuse 403 for both auth and permission errors
|
|
412
|
+
7. **Cross-origin API + cookies: check CORS before choosing fetch strategy**
|
|
413
|
+
8. **Always run \`npm run format\` after writing code** — Biome config uses single quotes
|
|
414
|
+
9. **Adapter injection timing** — adapters are injected at \`loading\` (before page JS runs) and \`complete\` (after full load). \`isReady()\` is called at both points. Cache tokens from localStorage at loading time before the host app deletes them.
|
|
415
|
+
10. **Token persistence on \`globalThis\` survives re-injection** — use \`globalThis.__openTabs.tokenCache.<pluginName>\` to persist auth tokens. Module-level variables reset when the extension reloads. Clear the persisted token on 401 responses to handle token rotation.
|
|
416
|
+
11. **Error classification: parse body before HTTP status** — many apps return JSON error codes in the response body that distinguish auth errors from permission errors. Parse the body first, then fall back to HTTP status classification.
|
|
417
|
+
12. **Cookie-based auth may require CSRF tokens for writes** — apps using HttpOnly session cookies often require a CSRF token header for non-GET requests. The CSRF token is typically in a non-HttpOnly cookie. Check \`window.__initialData.csrfCookieName\` or similar bootstrap globals to discover the cookie name.
|
|
418
|
+
13. **Check bootstrap globals for auth signals** — \`window.__initialData\`, \`window.__INITIAL_STATE__\`, \`window.boot_data\` are more reliable than DOM for auth detection. Inspect these early during exploration.
|
|
419
|
+
14. **Some apps use internal APIs instead of public REST** — the public API may require OAuth2, but the web client uses internal same-origin endpoints with cookie auth. Look for internal endpoints when public API rejects auth.
|
|
420
|
+
15. **Intercepted headers must survive adapter re-injection** — store captured tokens on \`globalThis.__<pluginName>CapturedTokens\`. Re-patch XHR on each adapter load. Avoid stale \`if (installed) return\` guards that skip re-patching after re-injection.
|
|
421
|
+
16. **Trusted Types CSP blocks \`innerHTML\`** — use regex \`html.replace(/<[^>]+>/g, '')\` for HTML-to-text conversion instead. Never use \`innerHTML\`, \`outerHTML\`, or \`insertAdjacentHTML\` in plugin code.
|
|
422
|
+
17. **Opaque auth headers can only be captured, not generated** — some apps use cryptographic tokens computed by obfuscated JS. Capture them from the XHR interceptor and implement a polling wait with timeout for the header to appear.
|
|
423
|
+
18. **When one API path is blocked, find another** — if a write operation requires an undocumented cryptographic payload, don't give up. Explore the web app's internal extension APIs, JavaScript-exposed programmatic interfaces, or other internal endpoints. Complex apps usually expose higher-level APIs for extensions/accessibility. Use \`browser_execute_script\` to enumerate non-standard page globals.
|
|
424
|
+
19. **Web apps expose programmatic extension APIs on the page** — complex web apps often expose internal scripting APIs on \`window\` that provide higher-level operations than raw XHR endpoints. Discovery: use \`browser_execute_script\` with \`Object.keys(window).filter(...)\` to find non-standard globals, then explore their methods.
|
|
425
|
+
20. **Internal API endpoints can be deprecated without warning** — when building plugins for web apps with multiple API generations, test each endpoint independently. If an endpoint returns 404 or 403, it may be deprecated for that account or region. Remove tools that depend on deprecated endpoints rather than shipping broken tools.
|
|
426
|
+
|
|
427
|
+
---
|
|
428
|
+
|
|
429
|
+
## Phase 7: Write Learnings Back (Mandatory)
|
|
430
|
+
|
|
431
|
+
Every plugin build surfaces new patterns, gotchas, and techniques. You **must** write them back so the next AI session benefits automatically.
|
|
432
|
+
|
|
433
|
+
### Contribution Checklist
|
|
434
|
+
|
|
435
|
+
Review your session and check each item:
|
|
436
|
+
|
|
437
|
+
- **Auth extraction pattern** — did you discover a new way to extract tokens (cookie, header interception, window globals, localStorage)? → Add to \`plugin-development.ts\` under "Auth Token Extraction" or "Advanced Auth Patterns"
|
|
438
|
+
- **New gotcha encountered** — did you hit an error not in the Common Gotchas list above? If you encountered an error that is not in the Common Gotchas list, ADD IT. → Number it sequentially after the last entry in the gotcha list in \`build-plugin.ts\`
|
|
439
|
+
- **API discovery technique** — did you find a new way to discover or enumerate API endpoints (network capture patterns, global inspection, extension APIs)? → Add to Phase 2 of this prompt
|
|
440
|
+
- **CORS workaround** — did you work around a CORS issue (proxy, same-origin internal endpoints, cookie forwarding)? → Add to \`troubleshooting.ts\`
|
|
441
|
+
- **Zod schema pattern** — did you use a Zod pattern worth documenting (discriminated unions, lazy schemas, transform pipelines, coercion)? → Add to \`plugin-development.ts\` under "Zod Schema Rules"
|
|
442
|
+
- **Error handling pattern** — did you handle a non-obvious error (HTML error responses, rate limiting, auth expiry mid-session)? → Add to \`troubleshooting.ts\` or the Common Gotchas list
|
|
443
|
+
|
|
444
|
+
### Invoke the Contribution Workflow
|
|
445
|
+
|
|
446
|
+
**Invoke the \`contribute_learnings\` prompt now** with the task description (e.g., \`contribute_learnings(task: "built a plugin for <DisplayName>")\`). That prompt provides the complete self-improvement workflow: which files to edit, the file-to-resource mapping, rules for deduplication, and verification steps.
|
|
447
|
+
|
|
448
|
+
The specific files to check when contributing:
|
|
449
|
+
- \`platform/mcp-server/src/prompts/build-plugin.ts\` — for workflow improvements and new gotchas
|
|
450
|
+
- \`platform/mcp-server/src/resources/plugin-development.ts\` — for patterns, auth techniques, and Zod rules
|
|
451
|
+
- \`platform/mcp-server/src/resources/troubleshooting.ts\` — for error resolutions and CORS workarounds
|
|
452
|
+
|
|
453
|
+
Do not skip this phase. The source files you edit compile into the MCP resources and prompts this server serves. You are reading this prompt because a previous session wrote or improved it — your additions reach the next agent the same way.`;
|
|
454
|
+
};
|
|
455
|
+
//# sourceMappingURL=build-plugin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"build-plugin.js","sourceRoot":"","sources":["../../src/prompts/build-plugin.ts"],"names":[],"mappings":"AAAA,oFAAoF;AAEpF,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,GAAW,EAAE,IAAY,EAAU,EAAE;IACzE,MAAM,UAAU,GAAG,IAAI,CAAC,CAAC,CAAC,+BAA+B,IAAI,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;IAExE,OAAO,gDAAgD,GAAG,KAAK,UAAU;;;;;;;;6DAQd,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;+DAqDD,GAAG;;;;;;4BAMtC,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gBA+Bf,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iPA+V8N,CAAC;AAClP,CAAC,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* contribute_learnings prompt — standalone workflow for writing learnings back
|
|
3
|
+
* into the MCP server source code after completing any task.
|
|
4
|
+
*
|
|
5
|
+
* This prompt enables the self-improvement loop: AI agents that complete tasks
|
|
6
|
+
* (plugin development, troubleshooting, setup, etc.) write their discoveries
|
|
7
|
+
* back into the source files that compile into resources and prompts served
|
|
8
|
+
* by this MCP server. Every future AI session then receives those learnings
|
|
9
|
+
* automatically.
|
|
10
|
+
*/
|
|
11
|
+
export declare const contributeLearningsPromptText: (task: string) => string;
|
|
12
|
+
//# sourceMappingURL=contribute-learnings.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contribute-learnings.d.ts","sourceRoot":"","sources":["../../src/prompts/contribute-learnings.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,eAAO,MAAM,6BAA6B,GAAI,MAAM,MAAM,KAAG,MAgG5D,CAAC"}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* contribute_learnings prompt — standalone workflow for writing learnings back
|
|
3
|
+
* into the MCP server source code after completing any task.
|
|
4
|
+
*
|
|
5
|
+
* This prompt enables the self-improvement loop: AI agents that complete tasks
|
|
6
|
+
* (plugin development, troubleshooting, setup, etc.) write their discoveries
|
|
7
|
+
* back into the source files that compile into resources and prompts served
|
|
8
|
+
* by this MCP server. Every future AI session then receives those learnings
|
|
9
|
+
* automatically.
|
|
10
|
+
*/
|
|
11
|
+
export const contributeLearningsPromptText = (task) => {
|
|
12
|
+
const taskClause = task
|
|
13
|
+
? `You just completed a task: "${task}"\n\nReview what you learned and write it back into the appropriate source files.`
|
|
14
|
+
: 'Review your recent session for new patterns, gotchas, and techniques, then write them back into the appropriate source files.';
|
|
15
|
+
return `${taskClause}
|
|
16
|
+
|
|
17
|
+
The embedded \`opentabs://guide/self-improvement\` resource below contains the complete file-to-content mapping, contribution rules, and architecture explanation. Read it first.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Step 1: Identify What You Learned
|
|
22
|
+
|
|
23
|
+
Check your tool call history in this session — failed calls, retries, and workarounds are the richest source of learnings.
|
|
24
|
+
|
|
25
|
+
Review your session for:
|
|
26
|
+
|
|
27
|
+
- **New patterns** — auth extraction techniques, API discovery methods, Zod schema tricks
|
|
28
|
+
- **Gotchas** — things that didn't work as expected, subtle bugs, non-obvious behaviors
|
|
29
|
+
- **Resolution steps** — how you fixed an error that isn't documented yet
|
|
30
|
+
- **Missing documentation** — things you had to figure out that should have been written down
|
|
31
|
+
- **Workflow improvements** — steps that should be added to existing prompts
|
|
32
|
+
|
|
33
|
+
### Session Review Checklist
|
|
34
|
+
|
|
35
|
+
- Check your tool call history for failed calls and how you fixed them
|
|
36
|
+
- Check for API endpoints you discovered through network capture
|
|
37
|
+
- Check for auth patterns that worked (or didn't)
|
|
38
|
+
- Check for Zod schema patterns you used
|
|
39
|
+
- Check for error messages you encountered and resolved
|
|
40
|
+
|
|
41
|
+
If you have not yet called contribute_learnings during this session, do it now — this is your opportunity.
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## Step 2: Choose the Right File
|
|
46
|
+
|
|
47
|
+
Consult the file-to-content mapping in the embedded \`opentabs://guide/self-improvement\` resource to find the correct source file for your learnings.
|
|
48
|
+
|
|
49
|
+
### Contribution Examples
|
|
50
|
+
|
|
51
|
+
**Good contributions** — specific, actionable, reusable:
|
|
52
|
+
|
|
53
|
+
\`\`\`
|
|
54
|
+
## Common Gotchas
|
|
55
|
+
|
|
56
|
+
19. **API returns HTML on 500 errors** — wrap response.json() in try-catch and parse the text body for error details when JSON parsing fails.
|
|
57
|
+
\`\`\`
|
|
58
|
+
|
|
59
|
+
\`\`\`
|
|
60
|
+
### Cookie-Based Auth with CSRF
|
|
61
|
+
|
|
62
|
+
Some apps store the CSRF cookie name in window.__initialData.csrfCookieName — check bootstrap globals before hardcoding cookie names.
|
|
63
|
+
\`\`\`
|
|
64
|
+
|
|
65
|
+
**Bad contributions** — too vague or too app-specific:
|
|
66
|
+
|
|
67
|
+
- "Added better error handling" — no actionable information, no one can use this
|
|
68
|
+
- "Notion uses space IDs in the format xxx-xxx" — too app-specific, not reusable across plugins
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## Step 3: Write the Learnings
|
|
73
|
+
|
|
74
|
+
**Rules:**
|
|
75
|
+
1. **Check for duplicates** — read the target file first; scan existing content before adding
|
|
76
|
+
2. **Keep learnings generic** — no app-specific details (e.g., no "Notion uses space IDs")
|
|
77
|
+
3. **Be concise and factual** — write for AI consumption, not human narratives
|
|
78
|
+
4. **Add to the right section** — place gotchas in gotcha lists, patterns in pattern sections
|
|
79
|
+
5. **Preserve existing structure** — match the formatting and style of surrounding content
|
|
80
|
+
|
|
81
|
+
When adding a gotcha, number it sequentially after the last existing entry. When adding a pattern, add it to the relevant subsection with a code example.
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## Step 4: Verify
|
|
86
|
+
|
|
87
|
+
After editing, verify the server builds:
|
|
88
|
+
|
|
89
|
+
\`\`\`bash
|
|
90
|
+
cd platform/mcp-server && npm run build
|
|
91
|
+
\`\`\`
|
|
92
|
+
|
|
93
|
+
The build must succeed. If it fails, fix the issue before committing.
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## Step 5: Do NOT Write to Local Files
|
|
98
|
+
|
|
99
|
+
Write learnings ONLY to the TypeScript source files listed in the self-improvement guide. Do NOT write to:
|
|
100
|
+
- Local markdown files or skill files
|
|
101
|
+
- \`CLAUDE.md\` files (those are for platform contributors, not MCP-served content)
|
|
102
|
+
- Documentation site (\`docs/\`) — that's for humans, not AI agents
|
|
103
|
+
- Temporary files or scratch notes
|
|
104
|
+
|
|
105
|
+
The reason: only the source files in \`platform/mcp-server/src/\` compile into MCP content. Anything written elsewhere is invisible to future AI sessions.`;
|
|
106
|
+
};
|
|
107
|
+
//# sourceMappingURL=contribute-learnings.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contribute-learnings.js","sourceRoot":"","sources":["../../src/prompts/contribute-learnings.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,MAAM,CAAC,MAAM,6BAA6B,GAAG,CAAC,IAAY,EAAU,EAAE;IACpE,MAAM,UAAU,GAAG,IAAI;QACrB,CAAC,CAAC,+BAA+B,IAAI,mFAAmF;QACxH,CAAC,CAAC,+HAA+H,CAAC;IAEpI,OAAO,GAAG,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2JA0FqI,CAAC;AAC5J,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin-icon.d.ts","sourceRoot":"","sources":["../../src/prompts/plugin-icon.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,eAAO,MAAM,oBAAoB,GAC/B,QAAQ,MAAM,KACb,MA8IwJ,CAAC"}
|