Skip to main content
Glama
document-tools.ts11.1 kB
import { TOOL_CREATE_DOCUMENT, TOOL_CREATE_DOCUMENT_BLOCK, TOOL_CREATE_DOCUMENT_PASSWORD, TOOL_GET_DOCUMENT, TOOL_GET_DOCUMENT_RAW, TOOL_SET_DOCUMENT_PERMISSION, } from '@/consts/index.js'; import { DocumentService } from '@/services/documents/document-service.js'; import { FeiShuApiError } from '@/services/error.js'; /** * Document Tools * * Defines MCP tools for FeiShu document operations. */ import { z } from 'zod'; import type { ToolRegistryParams } from './index.js'; /** * Register document tools with the MCP server * * @param params - Tool registration parameters */ export function registerDocumentTools(params: ToolRegistryParams): void { const { server, services, logger } = params; // Get document raw content server.tool( TOOL_GET_DOCUMENT_RAW, 'Retrieve raw content from a FeiShu document by ID', { docId: z .string() .describe( 'The document ID of the FeiShu file to fetch, typically found in a URL like feishu.cn/wiki/<documentId>...', ), }, async ({ docId }) => { try { logger.info(`Reading FeiShu document ${docId}`); const content = await services.documents.getDocumentContent(docId); return { content: [{ type: 'text' as const, text: content }], }; } catch (error) { // Create appropriate error message based on error type let errorMessage: string; if (error instanceof FeiShuApiError) { // Handle specific FeiShu API errors logger.error( `FeiShu API Error (${error.code || 'unknown'}): ${error.message}`, ); errorMessage = `FeiShu API Error: ${error.message}`; } else { // Handle generic errors logger.error(`Failed to fetch document ${docId}:`, error); errorMessage = `Error fetching document: ${error instanceof Error ? error.message : String(error)}`; } return { content: [{ type: 'text' as const, text: errorMessage }], }; } }, ); // Get document metadata server.tool( TOOL_GET_DOCUMENT, 'Retrieve metadata for a FeiShu document', { docId: z.string().describe('The document ID to get information about'), }, async ({ docId }) => { try { logger.info(`Getting metadata for document ${docId}`); const info = await services.documents.getDocumentInfo(docId); return { content: [ { type: 'text' as const, text: JSON.stringify(info, null, 2) }, ], }; } catch (error) { const errorMessage = error instanceof FeiShuApiError ? `FeiShu API Error: ${error.message}` : `Error retrieving document info: ${error}`; logger.error(errorMessage); return { content: [{ type: 'text' as const, text: errorMessage }], }; } }, ); // Create a new document server.tool( TOOL_CREATE_DOCUMENT, 'Create a new FeiShu document with optional title and folder location', { title: z .string() .optional() .describe('Title of the document to create'), folderToken: z .string() .optional() .describe( 'Folder token to create the document in (optional, creates in root if not specified)', ), }, async ({ title, folderToken }) => { try { logger.info(`Creating FeiShu document: ${title || 'Untitled'}`); const result = await services.documents.createDocument(title, folderToken); return { content: [ { type: 'text' as const, text: JSON.stringify(result, null, 2) }, ], }; } catch (error) { const errorMessage = error instanceof FeiShuApiError ? `FeiShu API Error: ${error.message}` : `Error creating document: ${error}`; logger.error(errorMessage); return { content: [{ type: 'text' as const, text: errorMessage }], }; } }, ); // Create blocks in a document server.tool( TOOL_CREATE_DOCUMENT_BLOCK, 'Create content blocks in a FeiShu document. Supports text, headings, lists, code, quotes, todos, and dividers.', { documentId: z .string() .describe('The document ID to add blocks to'), blockId: z .string() .optional() .describe( 'Parent block ID to add children to (defaults to document root if not specified)', ), blocks: z .array( z.object({ type: z .enum([ 'text', 'heading1', 'heading2', 'heading3', 'heading4', 'heading5', 'heading6', 'bullet', 'ordered', 'code', 'quote', 'todo', 'divider', ]) .describe('Type of block to create'), content: z .string() .optional() .describe('Text content for the block (not needed for divider)'), done: z .boolean() .optional() .describe('For todo blocks: whether the task is done'), language: z .number() .optional() .describe('For code blocks: language identifier (1=PlainText, 2=ABAP, etc.)'), }), ) .describe('Array of blocks to create'), index: z .number() .optional() .describe('Index to insert blocks at (-1 or omit for end)'), }, async ({ documentId, blockId, blocks, index }) => { try { logger.info( `Creating ${blocks.length} blocks in document ${documentId}`, ); const blockRequests = blocks.map((block) => { switch (block.type) { case 'text': return DocumentService.createTextBlock(block.content || ''); case 'heading1': return DocumentService.createHeadingBlock(block.content || '', 1); case 'heading2': return DocumentService.createHeadingBlock(block.content || '', 2); case 'heading3': return DocumentService.createHeadingBlock(block.content || '', 3); case 'heading4': return DocumentService.createHeadingBlock(block.content || '', 4); case 'heading5': return DocumentService.createHeadingBlock(block.content || '', 5); case 'heading6': return DocumentService.createHeadingBlock(block.content || '', 6); case 'bullet': return DocumentService.createBulletBlock(block.content || ''); case 'ordered': return DocumentService.createOrderedBlock(block.content || ''); case 'code': return DocumentService.createCodeBlock( block.content || '', block.language || 1, ); case 'quote': return DocumentService.createQuoteBlock(block.content || ''); case 'todo': return DocumentService.createTodoBlock( block.content || '', block.done || false, ); case 'divider': return DocumentService.createDividerBlock(); default: return DocumentService.createTextBlock(block.content || ''); } }); const result = await services.documents.createBlocks( documentId, blockId || documentId, blockRequests, index ?? -1, ); return { content: [ { type: 'text' as const, text: JSON.stringify(result, null, 2) }, ], }; } catch (error) { const errorMessage = error instanceof FeiShuApiError ? `FeiShu API Error: ${error.message}` : `Error creating blocks: ${error}`; logger.error(errorMessage); return { content: [{ type: 'text' as const, text: errorMessage }], }; } }, ); // Set document permission server.tool( TOOL_SET_DOCUMENT_PERMISSION, 'Set public sharing permission for a FeiShu document. Use this to make a document accessible via link.', { documentId: z .string() .describe('The document ID to set permission for'), linkShareEntity: z .enum([ 'tenant_readable', 'tenant_editable', 'anyone_readable', 'anyone_editable', 'closed', ]) .default('anyone_editable') .describe( 'Link sharing permission: tenant_readable (org can view), tenant_editable (org can edit), anyone_readable (anyone can view), anyone_editable (anyone can edit), closed (disabled)', ), }, async ({ documentId, linkShareEntity }) => { try { logger.info( `Setting permission for document ${documentId} to ${linkShareEntity}`, ); const result = await services.documents.setPublicSharing( documentId, linkShareEntity, ); return { content: [ { type: 'text' as const, text: JSON.stringify(result, null, 2) }, ], }; } catch (error) { const errorMessage = error instanceof FeiShuApiError ? `FeiShu API Error: ${error.message}` : `Error setting permission: ${error}`; logger.error(errorMessage); return { content: [{ type: 'text' as const, text: errorMessage }], }; } }, ); // Create document password server.tool( TOOL_CREATE_DOCUMENT_PASSWORD, 'Create a password for document sharing. The document must have link sharing enabled first.', { documentId: z .string() .describe('The document ID to create password for'), }, async ({ documentId }) => { try { logger.info(`Creating password for document ${documentId}`); const result = await services.documents.createSharePassword(documentId); return { content: [ { type: 'text' as const, text: JSON.stringify( { success: true, password: result.password, message: `Password created. Share link: https://feishu.cn/docx/${documentId}`, }, null, 2, ), }, ], }; } catch (error) { const errorMessage = error instanceof FeiShuApiError ? `FeiShu API Error: ${error.message}` : `Error creating password: ${error}`; logger.error(errorMessage); return { content: [{ type: 'text' as const, text: errorMessage }], }; } }, ); }

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/opsworld30/feishu-mcp-server'

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