Create Workspace
create_workspaceCreate a new AFFiNE workspace with an initial document. Specify a workspace name and an optional avatar emoji or URL.
Instructions
Create a new workspace with initial document (accessible in UI)
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| name | Yes | Workspace name | |
| avatar | No | Avatar emoji or URL |
Implementation Reference
- src/tools/workspaces.ts:190-295 (handler)The main handler function for the create_workspace tool. It creates a new workspace by: (1) building initial Y.js workspace data with a document, (2) sending a GraphQL mutation with multipart form data to create the workspace, (3) connecting via WebSocket to sync the initial document, and (4) returning a receipt with workspace details.
const createWorkspaceHandler = async ({ name, avatar }: { name: string; avatar?: string }) => { try { // Get endpoint and headers from GraphQL client const endpoint = gql.endpoint; const headers = gql.headers; const cookie = gql.cookie; const bearer = gql.bearer; // Create initial workspace data const { workspaceUpdate, firstDocId, docUpdate } = createInitialWorkspaceData(name, avatar || ''); // 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; const wsUrl = wsUrlFromGraphQLEndpoint(endpoint); const baseUrl = process.env.AFFINE_BASE_URL || endpoint.replace(/\/graphql\/?$/, ''); try { const socket = await connectWorkspaceSocket(wsUrl, cookie, bearer); try { await joinWorkspace(socket, workspace.id); const docUpdateBase64 = Buffer.from(docUpdate).toString('base64'); await pushDocUpdate(socket, workspace.id, firstDocId, docUpdateBase64); } finally { socket.disconnect(); } } catch (_wsError) { // Keep workspace creation successful even if initial websocket sync fails. return receipt("workspace.create", { workspaceId: workspace.id, ...workspace, name, avatar, firstDocId, syncStatus: "partial", status: "partial", message: "Workspace created (document sync may be pending)", url: `${baseUrl}/workspace/${workspace.id}` }); } return receipt("workspace.create", { workspaceId: workspace.id, ...workspace, name, avatar, firstDocId, syncStatus: "success", status: "success", message: "Workspace created successfully", url: `${baseUrl}/workspace/${workspace.id}` }); } catch (error: any) { return text({ kind: "workspace.create", ok: false, status: "failed", error: error.message, }); } }; - src/tools/workspaces.ts:21-132 (helper)Helper function that builds the initial Y.js workspace data structures including workspace metadata, a root document with page blocks (page, surface, note, paragraph), and returns the encoded updates.
function createInitialWorkspaceData(workspaceName: string = 'New Workspace', avatar: string = '') { // Create workspace root YDoc const rootDoc = new Y.Doc(); // Set workspace metadata const meta = rootDoc.getMap('meta'); meta.set('name', workspaceName); meta.set('avatar', 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', null); 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', null); 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', null); 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)Helper function that generates a random 10-character document ID using URL-safe characters.
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; } - src/tools/workspaces.ts:297-308 (registration)Registers the 'create_workspace' tool on the MCP server with its title, description, and input schema, binding it to the handler.
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:302-305 (schema)Input schema for create_workspace: requires a 'name' string and an optional 'avatar' string.
inputSchema: { name: z.string().describe("Workspace name"), avatar: z.string().optional().describe("Avatar emoji or URL") }