create_workspace
Generate a new workspace with an initial document for collaborative content creation in AFFiNE, using the MCP server API. Define workspace name and optional avatar for easy organization.
Instructions
Create a new workspace with initial document (accessible in UI)
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| avatar | No | Avatar emoji or URL | |
| name | Yes | Workspace name |
Implementation Reference
- src/tools/workspaces.ts:209-342 (handler)Main handler function executing the create_workspace tool: generates initial Yjs workspace and document, sends GraphQL mutation via multipart form, and syncs document via WebSocket.const createWorkspaceHandler = async ({ name, avatar }: { name: string; avatar?: string }) => { try { // Get endpoint and headers from GraphQL client const endpoint = (gql as any).endpoint || process.env.AFFINE_BASE_URL + '/graphql'; const headers = (gql as any).headers || {}; const cookie = (gql as any).cookie || headers.Cookie || ''; // Create initial workspace data const { workspaceUpdate, firstDocId, docUpdate } = createInitialWorkspaceData(name); // Only send workspace update - document will be created separately const initData = Buffer.from(workspaceUpdate); // Create multipart form const form = new FormData(); // Add GraphQL operation form.append('operations', JSON.stringify({ name: 'createWorkspace', query: `mutation createWorkspace($init: Upload!) { createWorkspace(init: $init) { id public createdAt enableAi } }`, variables: { init: null } })); // Map file to variable form.append('map', JSON.stringify({ '0': ['variables.init'] })); // Add workspace init data form.append('0', initData, { filename: 'init.yjs', contentType: 'application/octet-stream' }); // Send request const response = await fetch(endpoint, { method: 'POST', headers: { ...headers, 'Cookie': cookie, ...form.getHeaders() }, body: form as any }); const result = await response.json() as any; if (result.errors) { throw new Error(result.errors[0].message); } const workspace = result.data.createWorkspace; // Now create the actual document via WebSocket const wsUrl = endpoint.replace('https://', 'wss://').replace('http://', 'ws://').replace('/graphql', ''); return new Promise((resolve) => { const socket = io(wsUrl, { transports: ['websocket'], path: '/socket.io/', extraHeaders: cookie ? { Cookie: cookie } : undefined }); socket.on('connect', () => { // Join the workspace socket.emit('space:join', { spaceType: 'workspace', spaceId: workspace.id }); // Send the document update setTimeout(() => { const docUpdateBase64 = Buffer.from(docUpdate).toString('base64'); socket.emit('space:push-doc-update', { spaceType: 'workspace', spaceId: workspace.id, docId: firstDocId, update: docUpdateBase64 }); // Wait longer for sync and disconnect setTimeout(() => { socket.disconnect(); resolve(text({ ...workspace, name: name, avatar: avatar, firstDocId: firstDocId, status: "success", message: "Workspace created successfully", url: `${process.env.AFFINE_BASE_URL}/workspace/${workspace.id}` })); }, 3000); }, 1000); }); socket.on('error', () => { socket.disconnect(); // Even if WebSocket fails, workspace was created resolve(text({ ...workspace, name: name, avatar: avatar, firstDocId: firstDocId, status: "partial", message: "Workspace created (document sync may be pending)", url: `${process.env.AFFINE_BASE_URL}/workspace/${workspace.id}` })); }); // Timeout setTimeout(() => { socket.disconnect(); resolve(text({ ...workspace, name: name, avatar: avatar, firstDocId: firstDocId, status: "success", message: "Workspace created", url: `${process.env.AFFINE_BASE_URL}/workspace/${workspace.id}` })); }, 10000); }); } catch (error: any) { return text({ error: error.message, status: "failed" }); } };
- src/tools/workspaces.ts:344-355 (registration)Registration of the 'create_workspace' MCP tool, including title, description, Zod input schema, and reference to the handler function.server.registerTool( "create_workspace", { title: "Create Workspace", description: "Create a new workspace with initial document (accessible in UI)", inputSchema: { name: z.string().describe("Workspace name"), avatar: z.string().optional().describe("Avatar emoji or URL") } }, createWorkspaceHandler as any );
- src/tools/workspaces.ts:350-352 (schema)Zod input schema defining parameters for the create_workspace tool: required 'name' string and optional 'avatar' string.name: z.string().describe("Workspace name"), avatar: z.string().optional().describe("Avatar emoji or URL") }
- src/tools/workspaces.ts:21-132 (helper)Helper function to generate initial Yjs updates for new workspace root and first document with AFFiNE block structure.function createInitialWorkspaceData(workspaceName: string = 'New Workspace') { // Create workspace root YDoc const rootDoc = new Y.Doc(); // Set workspace metadata const meta = rootDoc.getMap('meta'); meta.set('name', workspaceName); meta.set('avatar', ''); // Create pages array with initial document const pages = new Y.Array(); const firstDocId = generateDocId(); // Add first document metadata const pageMetadata = new Y.Map(); pageMetadata.set('id', firstDocId); pageMetadata.set('title', 'Welcome to ' + workspaceName); pageMetadata.set('createDate', Date.now()); pageMetadata.set('tags', new Y.Array()); pages.push([pageMetadata]); meta.set('pages', pages); // Create settings const setting = rootDoc.getMap('setting'); setting.set('collections', new Y.Array()); // Encode workspace update const workspaceUpdate = Y.encodeStateAsUpdate(rootDoc); // Create the actual document const docYDoc = new Y.Doc(); const blocks = docYDoc.getMap('blocks'); // Create page block with proper structure const pageId = generateDocId(); const pageBlock = new Y.Map(); pageBlock.set('sys:id', pageId); pageBlock.set('sys:flavour', 'affine:page'); // Title as Y.Text const titleText = new Y.Text(); titleText.insert(0, 'Welcome to ' + workspaceName); pageBlock.set('prop:title', titleText); // Children const pageChildren = new Y.Array(); pageBlock.set('sys:children', pageChildren); blocks.set(pageId, pageBlock); // Add surface block (required) const surfaceId = generateDocId(); const surfaceBlock = new Y.Map(); surfaceBlock.set('sys:id', surfaceId); surfaceBlock.set('sys:flavour', 'affine:surface'); surfaceBlock.set('sys:parent', pageId); surfaceBlock.set('sys:children', new Y.Array()); blocks.set(surfaceId, surfaceBlock); pageChildren.push([surfaceId]); // Add note block with xywh const noteId = generateDocId(); const noteBlock = new Y.Map(); noteBlock.set('sys:id', noteId); noteBlock.set('sys:flavour', 'affine:note'); noteBlock.set('sys:parent', pageId); noteBlock.set('prop:displayMode', 'DocAndEdgeless'); noteBlock.set('prop:xywh', '[0,0,800,600]'); noteBlock.set('prop:index', 'a0'); noteBlock.set('prop:lockedBySelf', false); const noteChildren = new Y.Array(); noteBlock.set('sys:children', noteChildren); blocks.set(noteId, noteBlock); pageChildren.push([noteId]); // Add initial paragraph const paragraphId = generateDocId(); const paragraphBlock = new Y.Map(); paragraphBlock.set('sys:id', paragraphId); paragraphBlock.set('sys:flavour', 'affine:paragraph'); paragraphBlock.set('sys:parent', noteId); paragraphBlock.set('sys:children', new Y.Array()); paragraphBlock.set('prop:type', 'text'); const paragraphText = new Y.Text(); paragraphText.insert(0, 'This workspace was created by AFFiNE MCP Server'); paragraphBlock.set('prop:text', paragraphText); blocks.set(paragraphId, paragraphBlock); noteChildren.push([paragraphId]); // Set document metadata const docMeta = docYDoc.getMap('meta'); docMeta.set('id', firstDocId); docMeta.set('title', 'Welcome to ' + workspaceName); docMeta.set('createDate', Date.now()); docMeta.set('tags', new Y.Array()); docMeta.set('version', 1); // Encode document update const docUpdate = Y.encodeStateAsUpdate(docYDoc); return { workspaceUpdate, firstDocId, docUpdate }; }
- src/tools/workspaces.ts:11-18 (helper)Utility function to generate AFFiNE-style 10-char document IDs used in workspace creation.function generateDocId(): string { const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-'; let id = ''; for (let i = 0; i < 10; i++) { id += chars.charAt(Math.floor(Math.random() * chars.length)); } return id; }