UNPKG

@kolbo/mcp

Version:

Kolbo AI MCP Server - Generate images, videos, music, speech, and sound effects from Claude Code

136 lines (125 loc) • 7.49 kB
/* ⛔ BACKWARD COMPATIBILITY: Tool names and arg names below are a PUBLIC * CONTRACT. Never rename, remove, or break an existing tool/arg — old cached * `npx @kolbo/mcp` installs in the wild will break silently. Add new tools or * new OPTIONAL args only. Full rules: ../index.js top-of-file and CLAUDE.md. */ const { z } = require('zod'); const { projectIdField } = require('./_shared'); const { listResult } = require('../apps'); const CONTENT_GUIDE = 'HTML body content. Use clean semantic HTML the in-app editor understands: <h1>-<h3>, <p>, <ul>/<ol>/<li>, <table>, <blockquote>, <strong>/<em>, <a>. No <script>/<style>/<iframe> (stripped server-side). Write the FULL document yourself — this is where you author the doc.'; function registerDocTools(server, client) { // ─── create_doc ──────────────────────────────────────────── server.tool( 'create_doc', 'Create an AI Doc (Magic Pad document) in the user\'s Kolbo workspace. YOU author the document: write complete, well-structured HTML content (plans, briefs, scripts, research summaries, meeting notes…) and save it here so the user can read and edit it in the Kolbo app. Docs are project-scoped — when the user is working in a named project, resolve it with `list_projects` and pass `project_id`. Returns the doc id; use `share_doc` afterwards if the user wants a public link.', { title: z.string().describe('Document title (shown in the app sidebar).'), content: z.string().describe(CONTENT_GUIDE), project_id: projectIdField }, async ({ title, content, project_id }) => { const body = { title, content }; if (project_id) body.project_id = project_id; const result = await client.post('/v1/docs', body); return { content: [{ type: 'text', text: JSON.stringify({ doc: result.doc, _hint: 'Doc created. The user can open it in the Kolbo app under the project\'s AI Docs. Call share_doc with shared:true to get a public link.' }, null, 2) }] }; } ); // ─── list_docs ───────────────────────────────────────────── server.tool( 'list_docs', 'List the user\'s AI Docs (Magic Pad documents) across all projects, most recently updated first. Pass `project_id` (from `list_projects`) to narrow to one project. Returns id, title, project_id, share state, and timestamps — use `get_doc` to read a doc\'s content.', { project_id: z.string().optional().describe('Restrict to docs in one project (ObjectId from `list_projects`). Omit to list across all projects.'), page: z.number().optional().describe('Page number, 1-indexed. Default: 1'), limit: z.number().optional().describe('Results per page, max 50. Default: 20') }, async ({ project_id, page, limit }) => { const params = new URLSearchParams(); if (project_id) params.set('project_id', project_id); if (page) params.set('page', String(page)); if (limit) params.set('limit', String(limit)); const qs = params.toString(); const result = await client.get(`/v1/docs${qs ? '?' + qs : ''}`); const docs = result.docs || []; const text = JSON.stringify({ docs, pagination: result.pagination || null }, null, 2); return listResult(text, { widget: 'list', title: 'Your AI Docs', items: docs.map(d => ({ id: d.id, title: d.title, subtitle: (d.updated_at ? String(d.updated_at).slice(0, 10) : '') + (d.is_shared ? ' · shared' : ''), open_url: d.share_url || null })), total: docs.length, page_tool: 'list_docs', next_args: docs.length >= (limit || 20) ? { project_id, page: (page || 1) + 1, limit } : undefined }); } ); // ─── get_doc ─────────────────────────────────────────────── server.tool( 'get_doc', 'Fetch one AI Doc including its full HTML content. Use before editing an existing doc (read → modify → `update_doc`).', { doc_id: z.string().describe('The doc ObjectId (from create_doc or list_docs).') }, async ({ doc_id }) => { const result = await client.get(`/v1/docs/${encodeURIComponent(doc_id)}`); return { content: [{ type: 'text', text: JSON.stringify(result.doc, null, 2) }] }; } ); // ─── update_doc ──────────────────────────────────────────── server.tool( 'update_doc', 'Edit an AI Doc in place: title and/or content. NEVER delete and recreate a doc to change its text — existing share links and the sidebar entry stay on this id. Content REPLACES the whole document — call `get_doc` first, apply the user\'s edits to the full HTML, and send the complete result back.', { doc_id: z.string().describe('The doc ObjectId to update.'), title: z.string().optional().describe('New title. Omit to keep the current one.'), content: z.string().optional().describe('Full replacement ' + CONTENT_GUIDE) }, async ({ doc_id, title, content }) => { const body = {}; if (title !== undefined) body.title = title; if (content !== undefined) body.content = content; const result = await client.put(`/v1/docs/${encodeURIComponent(doc_id)}`, body); return { content: [{ type: 'text', text: JSON.stringify(result.doc, null, 2) }] }; } ); // ─── share_doc ───────────────────────────────────────────── server.tool( 'share_doc', 'Enable or disable public sharing of an AI Doc. When enabling, returns a stable public link (app.kolbo.ai/shared/magicpad/…) anyone can open — give it to the user. Set `editable` to also let link visitors edit the doc.', { doc_id: z.string().describe('The doc ObjectId.'), shared: z.boolean().describe('true = publicly shared, false = private again (the link stops working).'), editable: z.boolean().optional().describe('Whether link visitors can edit. Default: unchanged (false for new shares).') }, async ({ doc_id, shared, editable }) => { const body = { shared }; if (editable !== undefined) body.editable = editable; const result = await client.patch(`/v1/docs/${encodeURIComponent(doc_id)}/share`, body); return { content: [{ type: 'text', text: JSON.stringify(result.doc, null, 2) }] }; } ); // ─── delete_doc ──────────────────────────────────────────── server.tool( 'delete_doc', 'Delete an AI Doc (soft delete — recoverable from the app\'s trash flow). Owner only. Confirm with the user before deleting anything they did not just create in this conversation.', { doc_id: z.string().describe('The doc ObjectId to delete.') }, async ({ doc_id }) => { const result = await client.delete(`/v1/docs/${encodeURIComponent(doc_id)}`); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } ); } module.exports = { registerDocTools };