Skip to main content
Glama

Update Workspace

update_workspace

Modify workspace settings in AFFiNE by updating workspace ID, public visibility, and AI feature status to customize and enhance workspace functionality.

Instructions

Update workspace settings

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
enableAiNoEnable AI features
idYesWorkspace ID
publicNoMake workspace public

Implementation Reference

  • The main handler function for the update_workspace tool. It constructs a GraphQL mutation to update the workspace's public status and AI enablement, executes it via the GraphQL client, and returns the result.
    const updateWorkspaceHandler = async ({ id, public: isPublic, enableAi }: { id: string; public?: boolean; enableAi?: boolean }) => {
        try {
          const mutation = `
            mutation UpdateWorkspace($input: UpdateWorkspaceInput!) {
              updateWorkspace(input: $input) {
                id
                public
              }
            }
          `;
          
          const input: any = { id };
          if (isPublic !== undefined) input.public = isPublic;
          
          const data = await gql.request<{ updateWorkspace: any }>(mutation, { input });
          
          return text(data.updateWorkspace);
        } catch (error: any) {
          return text({ error: error.message });
        }
      };
  • Primary registration of the 'update_workspace' tool with the MCP server, including input schema using Zod.
    server.registerTool(
      "update_workspace",
      {
        title: "Update Workspace",
        description: "Update workspace settings",
        inputSchema: {
          id: z.string().describe("Workspace ID"),
          public: z.boolean().optional().describe("Make workspace public"),
          enableAi: z.boolean().optional().describe("Enable AI features")
        }
      },
      updateWorkspaceHandler as any
    );
  • Secondary registration of the 'affine_update_workspace' tool alias using the same handler and schema.
    server.registerTool(
      "affine_update_workspace",
      {
        title: "Update Workspace",
        description: "Update workspace settings",
        inputSchema: {
          id: z.string().describe("Workspace ID"),
          public: z.boolean().optional().describe("Make workspace public"),
          enableAi: z.boolean().optional().describe("Enable AI features")
        }
      },
      updateWorkspaceHandler as any
    );
  • Zod-based input schema definition for the tool parameters: workspace ID (required), public and enableAi (optional booleans).
    inputSchema: {
      id: z.string().describe("Workspace ID"),
      public: z.boolean().optional().describe("Make workspace public"),
      enableAi: z.boolean().optional().describe("Enable AI features")
    }
  • The registerWorkspaceTools function that sets up all workspace-related tools, including the update_workspace handler and registrations.
    export function registerWorkspaceTools(server: McpServer, gql: GraphQLClient) {
      // LIST WORKSPACES
      const listWorkspacesHandler = async () => {
        try {
          const query = `query { workspaces { id public enableAi createdAt } }`;
          const data = await gql.request<{ workspaces: any[] }>(query);
          return text(data.workspaces || []);
        } catch (error: any) {
          return text({ error: error.message });
        }
      };
    
      server.registerTool(
        "list_workspaces",
        {
          title: "List Workspaces",
          description: "List all available AFFiNE workspaces"
        },
        listWorkspacesHandler as any
      );
      server.registerTool(
        "affine_list_workspaces",
        {
          title: "List Workspaces",
          description: "List all available AFFiNE workspaces"
        },
        listWorkspacesHandler
      );
    
      // GET WORKSPACE
      const getWorkspaceHandler = async ({ id }: { id: string }) => {
        try {
          const query = `query GetWorkspace($id: String!) { 
            workspace(id: $id) { 
              id 
              public 
              enableAi 
              createdAt
              permissions { 
                Workspace_Read 
                Workspace_CreateDoc 
              } 
            } 
          }`;
          const data = await gql.request<{ workspace: any }>(query, { id });
          return text(data.workspace);
        } catch (error: any) {
          return text({ error: error.message });
        }
      };
    
      server.registerTool(
        "get_workspace",
        {
          title: "Get Workspace",
          description: "Get details of a specific workspace",
          inputSchema: { 
            id: z.string().describe("Workspace ID") 
          }
        },
        getWorkspaceHandler as any
      );
      server.registerTool(
        "affine_get_workspace",
        {
          title: "Get Workspace",
          description: "Get details of a specific workspace",
          inputSchema: { 
            id: z.string().describe("Workspace ID") 
          }
        },
        getWorkspaceHandler as any
      );
    
      // CREATE WORKSPACE
      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" });
          }
        };
    
      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
      );
      server.registerTool(
        "affine_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
      );
      server.registerTool(
        "affine_create_workspace_fixed",
        {
          title: "Create Workspace (Fixed)",
          description: "Create a new workspace with initial document (backward compatible alias)",
          inputSchema: {
            name: z.string().describe("Workspace name"),
            avatar: z.string().optional().describe("Avatar emoji or URL")
          }
        },
        createWorkspaceHandler as any
      );
    
      // UPDATE WORKSPACE
      const updateWorkspaceHandler = async ({ id, public: isPublic, enableAi }: { id: string; public?: boolean; enableAi?: boolean }) => {
          try {
            const mutation = `
              mutation UpdateWorkspace($input: UpdateWorkspaceInput!) {
                updateWorkspace(input: $input) {
                  id
                  public
                }
              }
            `;
            
            const input: any = { id };
            if (isPublic !== undefined) input.public = isPublic;
            
            const data = await gql.request<{ updateWorkspace: any }>(mutation, { input });
            
            return text(data.updateWorkspace);
          } catch (error: any) {
            return text({ error: error.message });
          }
        };
      server.registerTool(
        "update_workspace",
        {
          title: "Update Workspace",
          description: "Update workspace settings",
          inputSchema: {
            id: z.string().describe("Workspace ID"),
            public: z.boolean().optional().describe("Make workspace public"),
            enableAi: z.boolean().optional().describe("Enable AI features")
          }
        },
        updateWorkspaceHandler as any
      );
      server.registerTool(
        "affine_update_workspace",
        {
          title: "Update Workspace",
          description: "Update workspace settings",
          inputSchema: {
            id: z.string().describe("Workspace ID"),
            public: z.boolean().optional().describe("Make workspace public"),
            enableAi: z.boolean().optional().describe("Enable AI features")
          }
        },
        updateWorkspaceHandler as any
      );
    
      // DELETE WORKSPACE
      const deleteWorkspaceHandler = async ({ id }: { id: string }) => {
          try {
            const mutation = `
              mutation DeleteWorkspace($id: String!) {
                deleteWorkspace(id: $id)
              }
            `;
            
            const data = await gql.request<{ deleteWorkspace: boolean }>(mutation, { id });
            
            return text({ success: data.deleteWorkspace, message: "Workspace deleted successfully" });
          } catch (error: any) {
            return text({ error: error.message });
          }
        };
      server.registerTool(
        "delete_workspace",
        {
          title: "Delete Workspace",
          description: "Delete a workspace permanently",
          inputSchema: {
            id: z.string().describe("Workspace ID")
          }
        },
        deleteWorkspaceHandler as any
      );
      server.registerTool(
        "affine_delete_workspace",
        {
          title: "Delete Workspace",
          description: "Delete a workspace permanently",
          inputSchema: {
            id: z.string().describe("Workspace ID")
          }
        },
        deleteWorkspaceHandler as any
      );
    }
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries full burden for behavioral disclosure. 'Update workspace settings' implies a mutation operation, but the description doesn't mention authentication requirements, permission levels needed, whether changes are reversible, error conditions, or what happens to unspecified settings. This is inadequate for a mutation tool with zero annotation coverage.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is extremely concise at just three words, with zero wasted language. It's front-loaded with the essential information and contains no unnecessary elaboration. This is an example of optimal conciseness for a simple tool.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a mutation tool with no annotations and no output schema, the description is insufficiently complete. It doesn't explain what 'workspace settings' encompasses, what the update operation returns, error handling, or authentication requirements. Given the complexity of updating workspace settings and the lack of structured metadata, the description should provide more contextual information.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The schema description coverage is 100%, with all parameters well-documented in the schema itself. The description doesn't add any additional parameter context beyond what's already in the schema, so it meets the baseline expectation but doesn't provide extra value. The description doesn't mention any of the specific parameters (id, enableAi, public).

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the verb ('update') and resource ('workspace settings'), making the purpose immediately understandable. However, it doesn't differentiate this tool from sibling tools like 'affine_update_settings' or 'affine_update_profile', which also update settings-related resources, so it doesn't reach the highest clarity level.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides no guidance on when to use this tool versus alternatives. There are multiple sibling tools that update various resources (affine_update_settings, affine_update_profile, affine_update_comment), but the description offers no comparison or context about when this specific workspace update tool is appropriate.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Related Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/DAWNCR0W/affine-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server