Spaces:
Sleeping
Sleeping
| import type { FastMCP } from 'fastmcp'; | |
| import { UserError } from 'fastmcp'; | |
| import { z } from 'zod'; | |
| import { drive_v3 } from 'googleapis'; | |
| import { getDriveClient, getDocsClient } from '../../clients.js'; | |
| import { insertMarkdown, formatInsertResult } from '../../markdown-transformer/index.js'; | |
| export function register(server: FastMCP) { | |
| server.addTool({ | |
| name: 'createDocument', | |
| description: | |
| 'Creates a new empty Google Document. Optionally places it in a specific folder and adds initial text content.', | |
| parameters: z.strictObject({ | |
| title: z.string().min(1).describe('Title for the new document.'), | |
| parentFolderId: z | |
| .string() | |
| .optional() | |
| .describe( | |
| 'ID of folder where document should be created. If not provided, creates in Drive root.' | |
| ), | |
| initialContent: z | |
| .string() | |
| .optional() | |
| .describe( | |
| 'Initial content to add to the document. By default, markdown syntax is converted to formatted Google Docs content (headings, bold, italic, links, lists, etc.).' | |
| ), | |
| contentFormat: z | |
| .enum(['markdown', 'raw']) | |
| .optional() | |
| .default('markdown') | |
| .describe( | |
| "How to interpret initialContent. 'markdown' (default) converts markdown to formatted Google Docs content. 'raw' inserts the text as-is without any conversion." | |
| ), | |
| }), | |
| execute: async (args, { log }) => { | |
| const drive = await getDriveClient(); | |
| log.info(`Creating new document "${args.title}"`); | |
| try { | |
| const documentMetadata: drive_v3.Schema$File = { | |
| name: args.title, | |
| mimeType: 'application/vnd.google-apps.document', | |
| }; | |
| if (args.parentFolderId) { | |
| documentMetadata.parents = [args.parentFolderId]; | |
| } | |
| const response = await drive.files.create({ | |
| requestBody: documentMetadata, | |
| fields: 'id,name,webViewLink', | |
| supportsAllDrives: true, | |
| }); | |
| const document = response.data; | |
| // Add initial content if provided | |
| if (args.initialContent) { | |
| try { | |
| const docs = await getDocsClient(); | |
| if (args.contentFormat === 'raw') { | |
| await docs.documents.batchUpdate({ | |
| documentId: document.id!, | |
| requestBody: { | |
| requests: [ | |
| { | |
| insertText: { | |
| location: { index: 1 }, | |
| text: args.initialContent, | |
| }, | |
| }, | |
| ], | |
| }, | |
| }); | |
| } else { | |
| const result = await insertMarkdown(docs, document.id!, args.initialContent, { | |
| startIndex: 1, | |
| firstHeadingAsTitle: true, | |
| }); | |
| log.info(formatInsertResult(result)); | |
| } | |
| } catch (contentError: any) { | |
| log.warn(`Document created but failed to add initial content: ${contentError.message}`); | |
| } | |
| } | |
| return JSON.stringify( | |
| { | |
| id: document.id, | |
| name: document.name, | |
| url: document.webViewLink, | |
| }, | |
| null, | |
| 2 | |
| ); | |
| } catch (error: any) { | |
| log.error(`Error creating document: ${error.message || error}`); | |
| if (error.code === 404) | |
| throw new UserError('Parent folder not found. Check the folder ID.'); | |
| if (error.code === 403) | |
| throw new UserError( | |
| 'Permission denied. Make sure you have write access to the destination folder.' | |
| ); | |
| throw new UserError(`Failed to create document: ${error.message || 'Unknown error'}`); | |
| } | |
| }, | |
| }); | |
| } | |