Spaces:
Sleeping
Sleeping
File size: 6,784 Bytes
7dc28be | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 | import type { FastMCP } from 'fastmcp';
import { UserError } from 'fastmcp';
import { z } from 'zod';
import { getDocsClient, getDriveClient, getScriptClient } from '../../clients.js';
import { DocumentIdParameter } from '../../types.js';
import * as GDocsHelpers from '../../googleDocsApiHelpers.js';
import { logger } from '../../logger.js';
export function register(server: FastMCP) {
server.addTool({
name: 'insertImage',
description:
'Inserts an inline image into a Google Document. Provide either a publicly accessible URL or a local file path. Local files are automatically uploaded to Google Drive before insertion.',
parameters: DocumentIdParameter.extend({
imageUrl: z
.string()
.url()
.optional()
.describe('Publicly accessible URL to the image (http:// or https://).'),
localImagePath: z
.string()
.optional()
.describe(
'Absolute path to a local image file (supports .jpg, .jpeg, .png, .gif, .bmp, .webp, .svg). The file will be uploaded to Google Drive.'
),
index: z
.number()
.int()
.min(1)
.describe(
"1-based character index in the document body where the image should be inserted. Use readDocument with format='json' to inspect indices."
),
width: z.number().min(1).optional().describe('Width of the image in points.'),
height: z.number().min(1).optional().describe('Height of the image in points.'),
tabId: z
.string()
.optional()
.describe(
'The ID of the specific tab to insert into. Use listDocumentTabs to get tab IDs. If not specified, inserts into the first tab.'
),
})
.refine((data) => data.imageUrl || data.localImagePath, {
message: 'Either imageUrl or localImagePath must be provided.',
})
.refine((data) => !(data.imageUrl && data.localImagePath), {
message: 'Provide only one of imageUrl or localImagePath, not both.',
}),
execute: async (args, { log }) => {
const docs = await getDocsClient();
const appsScriptDeploymentId = process.env.APPS_SCRIPT_DEPLOYMENT_ID;
try {
if (args.tabId) {
const docInfo = await docs.documents.get({
documentId: args.documentId,
includeTabsContent: true,
fields: 'tabs(tabProperties,documentTab(body(content(endIndex))))',
});
const targetTab = GDocsHelpers.findTabById(docInfo.data, args.tabId);
if (!targetTab) {
throw new UserError(`Tab with ID "${args.tabId}" not found in document.`);
}
if (!targetTab.documentTab) {
throw new UserError(
`Tab "${args.tabId}" does not have content (may not be a document tab).`
);
}
}
// --- Apps Script path: local files when APPS_SCRIPT_DEPLOYMENT_ID is set ---
if (args.localImagePath && appsScriptDeploymentId) {
const drive = await getDriveClient();
const scriptClient = await getScriptClient();
log.info(`[AppsScript] Uploading ${args.localImagePath} to Drive (no public sharing)`);
let parentFolderId: string | undefined;
try {
const docInfo = await drive.files.get({
fileId: args.documentId,
fields: 'parents',
supportsAllDrives: true,
});
if (docInfo.data.parents && docInfo.data.parents.length > 0) {
parentFolderId = docInfo.data.parents[0];
}
} catch (folderError) {
log.warn(
`Could not determine document's parent folder, using Drive root: ${folderError}`
);
}
const driveFileId = await GDocsHelpers.uploadImageToDrive(
drive,
args.localImagePath,
parentFolderId,
true // skipPublicSharing
);
log.info(
`[AppsScript] Inserting image via marker at index ${args.index} (fileId: ${driveFileId})`
);
await GDocsHelpers.insertImageViaAppsScript(
docs,
scriptClient,
appsScriptDeploymentId,
args.documentId,
driveFileId,
args.index,
args.tabId
);
return `Successfully inserted local image at index ${args.index} via Apps Script${args.tabId ? ` in tab ${args.tabId}` : ''}.`;
}
// --- Standard path: public URL insertion via Docs API ---
let resolvedUrl: string;
if (args.localImagePath) {
const drive = await getDriveClient();
log.info(
`Uploading local image ${args.localImagePath} and inserting at index ${args.index} in doc ${args.documentId}${args.tabId ? ` (tab: ${args.tabId})` : ''}`
);
let parentFolderId: string | undefined;
try {
const docInfo = await drive.files.get({
fileId: args.documentId,
fields: 'parents',
supportsAllDrives: true,
});
if (docInfo.data.parents && docInfo.data.parents.length > 0) {
parentFolderId = docInfo.data.parents[0];
}
} catch (folderError) {
log.warn(
`Could not determine document's parent folder, using Drive root: ${folderError}`
);
}
resolvedUrl = await GDocsHelpers.uploadImageToDrive(
drive,
args.localImagePath,
parentFolderId,
false // explicit: needs public URL for Docs API insertion
);
log.info(`Image uploaded successfully, URL: ${resolvedUrl}`);
} else {
resolvedUrl = args.imageUrl!;
log.info(
`Inserting image from URL ${resolvedUrl} at index ${args.index} in doc ${args.documentId}${args.tabId ? ` (tab: ${args.tabId})` : ''}`
);
}
await GDocsHelpers.insertInlineImage(
docs,
args.documentId,
resolvedUrl,
args.index,
args.width,
args.height,
args.tabId
);
let sizeInfo = '';
if (args.width && args.height) {
sizeInfo = ` with size ${args.width}x${args.height}pt`;
}
return `Successfully inserted image at index ${args.index}${sizeInfo}${args.tabId ? ` in tab ${args.tabId}` : ''}.`;
} catch (error: any) {
log.error(`Error inserting image in doc ${args.documentId}: ${error.message || error}`);
if (error instanceof UserError) throw error;
throw new UserError(`Failed to insert image: ${error.message || 'Unknown error'}`);
}
},
});
}
|