toeverything/AFFiNE · error · Error

-32001

-32001

Error message

Title cannot be empty

What it means

Error('Title cannot be empty') thrown by the MCP doc-creation tool after it collapses all \r\n runs to spaces and trims: if nothing remains, the title is unusable. The MCP controller catches tool execution errors and returns them as JSON-RPC code -32001 'Error executing tool: Title cannot be empty'. It fires after the Workspace.CreateDoc permission check, so authorized callers with junk titles still hit it.

Source

Thrown at packages/backend/server/src/plugins/copilot/mcp/provider.ts:244

              type: 'string',
              description: 'The markdown content for the document body',
            },
          },
          required: ['title', 'content'],
          additionalProperties: false,
        },
        execute: async ({ title, content }, options) => {
          try {
            await this.ac
              .user(userId)
              .workspace(workspaceId)
              .assert('Workspace.CreateDoc');

            const abortedAfterPermission = abortIfNeeded(options.signal);
            if (abortedAfterPermission) return abortedAfterPermission;

            const sanitizedTitle = title.replace(/[\r\n]+/g, ' ').trim();
            if (!sanitizedTitle) throw new Error('Title cannot be empty');
            const strippedContent = content.replace(
              /^[ \t]{0,3}#\s+[^\n]*#*\s*\n*/,
              ''
            );
            const result = await this.writer.createDoc(
              workspaceId,
              sanitizedTitle,
              strippedContent,
              userId
            );

            return toolText(
              JSON.stringify({
                success: true,
                docId: result.docId,
                message: `Document "${title}" created successfully`,
              })
            );

View on GitHub (pinned to b4c8548c09)

Solutions

  1. Validate/trim the title client- (or agent-)side and require at least one non-whitespace character
  2. Fall back to a generated default title (e.g. 'Untitled' or a date-based name) when the derived title is blank
  3. Sanitize earlier: strip markdown '#' prefixes before sending, since the tool also strips them from content

Example fix

// before
tools.call('create_doc', { title: '\n  \n', content });

// after
const title = rawTitle.replace(/[\r\n]+/g, ' ').trim() || 'Untitled';
tools.call('create_doc', { title, content });
Defensive patterns

Strategy: validation

Validate before calling

const sanitizedTitle = title.replace(/[\r\n]+/g, ' ').trim();
if (!sanitizedTitle) throw new Error('Derived title is empty — supply a default');
await tools.call('create_doc', { title: sanitizedTitle, content });

Type guard

const isNonEmptyTitle = (t: unknown): t is string =>
  typeof t === 'string' && t.replace(/[\r\n]+/g, ' ').trim().length > 0;

Try / catch

try {
  return await tools.call('create_doc', { title, content });
} catch (e) {
  if (e.code === -32001 && /Title cannot be empty/.test(e.message)) {
    return tools.call('create_doc', { title: 'Untitled', content });
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling the create-doc MCP tool with a title of only newlines/spaces; a title that is empty after stripping markdown heading noise; an LLM passing a whitespace-only title argument.

Common situations: Agent derives the title from a document whose first line is blank; title string built from a template with empty interpolation; prompt-injection or parsing bug yields '\n\n'.

Related errors


AI-assisted analysis of toeverything/AFFiNE@b4c8548c09 (2026-08-18). Data as JSON: /api/errors/d496c22f9d465e96. Report an issue: GitHub.