google-tools-mcp 1.2.11 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +555 -276
- package/dist/cachedToolsList.js +52 -52
- package/dist/googleDocsApiHelpers.js +76 -12
- package/dist/helpers.js +572 -252
- package/dist/httpAuth.js +296 -0
- package/dist/index.js +162 -11
- package/dist/logger.js +87 -87
- package/dist/markdown-transformer/docsToMarkdown.js +222 -21
- package/dist/markdown-transformer/index.js +19 -9
- package/dist/markdown-transformer/markdownToDocs.js +458 -28
- package/dist/readTracker.js +136 -17
- package/dist/sessionContext.js +37 -0
- package/dist/setup.js +187 -9
- package/dist/tools/calendar/getBusy.js +64 -64
- package/dist/tools/calendar/getEvents.js +141 -141
- package/dist/tools/calendar/getFree.js +225 -225
- package/dist/tools/calendar/index.js +19 -19
- package/dist/tools/calendar/listCalendars.js +38 -38
- package/dist/tools/calendar/listRecurringInstances.js +83 -83
- package/dist/tools/calendar/manageCalendar.js +121 -121
- package/dist/tools/calendar/manageEvent.js +258 -258
- package/dist/tools/calendar/moveEvent.js +66 -66
- package/dist/tools/docs/addTab.js +10 -12
- package/dist/tools/docs/appendToGoogleDoc.js +9 -4
- package/dist/tools/docs/deleteRange.js +9 -4
- package/dist/tools/docs/findAndReplace.js +9 -4
- package/dist/tools/docs/formatting/applyParagraphStyle.js +4 -1
- package/dist/tools/docs/formatting/getFormatting.js +171 -171
- package/dist/tools/docs/insertImage.js +15 -2
- package/dist/tools/docs/insertPageBreak.js +4 -1
- package/dist/tools/docs/insertTable.js +4 -1
- package/dist/tools/docs/insertTableWithData.js +4 -1
- package/dist/tools/docs/modifyText.js +14 -4
- package/dist/tools/docs/modifyText.test.js +84 -84
- package/dist/tools/docs/readGoogleDoc.js +71 -11
- package/dist/tools/docs/renameTab.js +12 -14
- package/dist/tools/drafts.js +165 -165
- package/dist/tools/drive/createDocument.js +13 -1
- package/dist/tools/drive/downloadFile.js +268 -268
- package/dist/tools/drive/getFileInfo.js +48 -48
- package/dist/tools/drive/getFilePath.js +50 -50
- package/dist/tools/drive/listDriveFiles.js +112 -112
- package/dist/tools/drive/listSharedDrives.js +52 -52
- package/dist/tools/drive/listSharedWithMe.js +97 -97
- package/dist/tools/drive/uploadFile.js +111 -111
- package/dist/tools/extras/index.js +9 -9
- package/dist/tools/extras/readDriveFile.js +212 -206
- package/dist/tools/extras/readFile.js +84 -84
- package/dist/tools/extras/searchFileContents.js +81 -81
- package/dist/tools/forms/batchUpdateForm.js +81 -81
- package/dist/tools/forms/createForm.js +54 -54
- package/dist/tools/forms/getForm.js +118 -118
- package/dist/tools/forms/getFormResponse.js +45 -45
- package/dist/tools/forms/index.js +15 -15
- package/dist/tools/forms/listFormResponses.js +56 -56
- package/dist/tools/forms/setPublishSettings.js +59 -59
- package/dist/tools/gmail/drafts.js +165 -165
- package/dist/tools/gmail/labels.js +54 -83
- package/dist/tools/gmail/messages.js +444 -441
- package/dist/tools/gmail/settings.js +358 -528
- package/dist/tools/gmail/threads.js +286 -164
- package/dist/tools/index.js +511 -473
- package/dist/tools/labels.js +103 -103
- package/dist/tools/legacyAliases.js +426 -0
- package/dist/tools/maps/directions.js +72 -0
- package/dist/tools/maps/geocode.js +16 -0
- package/dist/tools/maps/index.js +15 -0
- package/dist/tools/maps/mapsClient.js +83 -0
- package/dist/tools/maps/placeDetails.js +16 -0
- package/dist/tools/maps/reverseGeocode.js +19 -0
- package/dist/tools/maps/searchNearby.js +54 -0
- package/dist/tools/maps/searchPlaces.js +24 -0
- package/dist/tools/messages.js +448 -448
- package/dist/tools/settings.js +528 -528
- package/dist/tools/slides/createPresentation.js +104 -104
- package/dist/tools/slides/createShape.js +92 -92
- package/dist/tools/slides/createTextBox.js +82 -82
- package/dist/tools/slides/deleteSlide.js +30 -30
- package/dist/tools/slides/duplicateSlide.js +37 -37
- package/dist/tools/slides/exportThumbnail.js +42 -42
- package/dist/tools/slides/formatParagraph.js +72 -72
- package/dist/tools/slides/formatText.js +84 -84
- package/dist/tools/slides/getPresentation.js +87 -87
- package/dist/tools/slides/index.js +33 -33
- package/dist/tools/slides/reorderSlides.js +41 -41
- package/dist/tools/slides/replaceAllText.js +46 -46
- package/dist/tools/slides/setBackground.js +58 -58
- package/dist/tools/slides/speakerNotes.js +102 -102
- package/dist/tools/slides/styleShape.js +111 -111
- package/dist/tools/slides/updatePresentation.js +128 -128
- package/dist/tools/threads.js +145 -145
- package/dist/tools/utils/appendMarkdownToGoogleDoc.js +31 -7
- package/dist/tools/utils/replaceDocumentWithMarkdown.js +72 -9
- package/dist/updateCheck.js +202 -0
- package/dist/workspace.js +122 -0
- package/package.json +82 -80
|
@@ -1,37 +1,37 @@
|
|
|
1
|
-
import { UserError } from 'fastmcp';
|
|
2
|
-
import { z } from 'zod';
|
|
3
|
-
import { getSlidesClient } from '../../clients.js';
|
|
4
|
-
|
|
5
|
-
export function register(server) {
|
|
6
|
-
server.addTool({
|
|
7
|
-
name: 'duplicateSlide',
|
|
8
|
-
description: 'Duplicate a slide in a Google Slides presentation.',
|
|
9
|
-
parameters: z.object({
|
|
10
|
-
presentationId: z.string().describe('Presentation ID'),
|
|
11
|
-
slideObjectId: z.string().describe('Object ID of the slide to duplicate'),
|
|
12
|
-
}),
|
|
13
|
-
execute: async (args, { log }) => {
|
|
14
|
-
const slidesApi = await getSlidesClient();
|
|
15
|
-
|
|
16
|
-
try {
|
|
17
|
-
const response = await slidesApi.presentations.batchUpdate({
|
|
18
|
-
presentationId: args.presentationId,
|
|
19
|
-
requestBody: {
|
|
20
|
-
requests: [{ duplicateObject: { objectId: args.slideObjectId } }],
|
|
21
|
-
},
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
const newId = response.data.replies?.[0]?.duplicateObject?.objectId;
|
|
25
|
-
return JSON.stringify({
|
|
26
|
-
success: true,
|
|
27
|
-
originalId: args.slideObjectId,
|
|
28
|
-
duplicateId: newId || null,
|
|
29
|
-
message: `Duplicated slide ${args.slideObjectId}${newId ? ` -> ${newId}` : ''}`,
|
|
30
|
-
});
|
|
31
|
-
} catch (error) {
|
|
32
|
-
log.error(`Error duplicating slide: ${error.message || error}`);
|
|
33
|
-
throw new UserError(`Failed to duplicate slide: ${error.message || 'Unknown error'}`);
|
|
34
|
-
}
|
|
35
|
-
},
|
|
36
|
-
});
|
|
37
|
-
}
|
|
1
|
+
import { UserError } from 'fastmcp';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { getSlidesClient } from '../../clients.js';
|
|
4
|
+
|
|
5
|
+
export function register(server) {
|
|
6
|
+
server.addTool({
|
|
7
|
+
name: 'duplicateSlide',
|
|
8
|
+
description: 'Duplicate a slide in a Google Slides presentation.',
|
|
9
|
+
parameters: z.object({
|
|
10
|
+
presentationId: z.string().describe('Presentation ID'),
|
|
11
|
+
slideObjectId: z.string().describe('Object ID of the slide to duplicate'),
|
|
12
|
+
}),
|
|
13
|
+
execute: async (args, { log }) => {
|
|
14
|
+
const slidesApi = await getSlidesClient();
|
|
15
|
+
|
|
16
|
+
try {
|
|
17
|
+
const response = await slidesApi.presentations.batchUpdate({
|
|
18
|
+
presentationId: args.presentationId,
|
|
19
|
+
requestBody: {
|
|
20
|
+
requests: [{ duplicateObject: { objectId: args.slideObjectId } }],
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const newId = response.data.replies?.[0]?.duplicateObject?.objectId;
|
|
25
|
+
return JSON.stringify({
|
|
26
|
+
success: true,
|
|
27
|
+
originalId: args.slideObjectId,
|
|
28
|
+
duplicateId: newId || null,
|
|
29
|
+
message: `Duplicated slide ${args.slideObjectId}${newId ? ` -> ${newId}` : ''}`,
|
|
30
|
+
});
|
|
31
|
+
} catch (error) {
|
|
32
|
+
log.error(`Error duplicating slide: ${error.message || error}`);
|
|
33
|
+
throw new UserError(`Failed to duplicate slide: ${error.message || 'Unknown error'}`);
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
}
|
|
@@ -1,42 +1,42 @@
|
|
|
1
|
-
import { UserError } from 'fastmcp';
|
|
2
|
-
import { z } from 'zod';
|
|
3
|
-
import { getSlidesClient } from '../../clients.js';
|
|
4
|
-
|
|
5
|
-
export function register(server) {
|
|
6
|
-
server.addTool({
|
|
7
|
-
name: 'exportSlideThumbnail',
|
|
8
|
-
description: 'Export a slide as a thumbnail image URL (PNG or JPEG) from a Google Slides presentation.',
|
|
9
|
-
parameters: z.object({
|
|
10
|
-
presentationId: z.string().describe('Presentation ID'),
|
|
11
|
-
slideObjectId: z.string().describe('Slide object ID'),
|
|
12
|
-
mimeType: z.enum(['PNG', 'JPEG']).optional().default('PNG').describe('Image format'),
|
|
13
|
-
size: z.enum(['SMALL', 'MEDIUM', 'LARGE']).optional().default('LARGE').describe('Thumbnail size'),
|
|
14
|
-
}),
|
|
15
|
-
execute: async (args, { log }) => {
|
|
16
|
-
const slidesApi = await getSlidesClient();
|
|
17
|
-
|
|
18
|
-
try {
|
|
19
|
-
const response = await slidesApi.presentations.pages.getThumbnail({
|
|
20
|
-
presentationId: args.presentationId,
|
|
21
|
-
pageObjectId: args.slideObjectId,
|
|
22
|
-
'thumbnailProperties.mimeType': args.mimeType,
|
|
23
|
-
'thumbnailProperties.thumbnailSize': args.size,
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
const url = response.data?.contentUrl;
|
|
27
|
-
if (!url) throw new UserError('No thumbnail URL returned by Google Slides API.');
|
|
28
|
-
|
|
29
|
-
return JSON.stringify({
|
|
30
|
-
slideObjectId: args.slideObjectId,
|
|
31
|
-
mimeType: args.mimeType,
|
|
32
|
-
size: args.size,
|
|
33
|
-
thumbnailUrl: url,
|
|
34
|
-
});
|
|
35
|
-
} catch (error) {
|
|
36
|
-
if (error instanceof UserError) throw error;
|
|
37
|
-
log.error(`Error exporting thumbnail: ${error.message || error}`);
|
|
38
|
-
throw new UserError(`Failed to export thumbnail: ${error.message || 'Unknown error'}`);
|
|
39
|
-
}
|
|
40
|
-
},
|
|
41
|
-
});
|
|
42
|
-
}
|
|
1
|
+
import { UserError } from 'fastmcp';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { getSlidesClient } from '../../clients.js';
|
|
4
|
+
|
|
5
|
+
export function register(server) {
|
|
6
|
+
server.addTool({
|
|
7
|
+
name: 'exportSlideThumbnail',
|
|
8
|
+
description: 'Export a slide as a thumbnail image URL (PNG or JPEG) from a Google Slides presentation.',
|
|
9
|
+
parameters: z.object({
|
|
10
|
+
presentationId: z.string().describe('Presentation ID'),
|
|
11
|
+
slideObjectId: z.string().describe('Slide object ID'),
|
|
12
|
+
mimeType: z.enum(['PNG', 'JPEG']).optional().default('PNG').describe('Image format'),
|
|
13
|
+
size: z.enum(['SMALL', 'MEDIUM', 'LARGE']).optional().default('LARGE').describe('Thumbnail size'),
|
|
14
|
+
}),
|
|
15
|
+
execute: async (args, { log }) => {
|
|
16
|
+
const slidesApi = await getSlidesClient();
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
const response = await slidesApi.presentations.pages.getThumbnail({
|
|
20
|
+
presentationId: args.presentationId,
|
|
21
|
+
pageObjectId: args.slideObjectId,
|
|
22
|
+
'thumbnailProperties.mimeType': args.mimeType,
|
|
23
|
+
'thumbnailProperties.thumbnailSize': args.size,
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const url = response.data?.contentUrl;
|
|
27
|
+
if (!url) throw new UserError('No thumbnail URL returned by Google Slides API.');
|
|
28
|
+
|
|
29
|
+
return JSON.stringify({
|
|
30
|
+
slideObjectId: args.slideObjectId,
|
|
31
|
+
mimeType: args.mimeType,
|
|
32
|
+
size: args.size,
|
|
33
|
+
thumbnailUrl: url,
|
|
34
|
+
});
|
|
35
|
+
} catch (error) {
|
|
36
|
+
if (error instanceof UserError) throw error;
|
|
37
|
+
log.error(`Error exporting thumbnail: ${error.message || error}`);
|
|
38
|
+
throw new UserError(`Failed to export thumbnail: ${error.message || 'Unknown error'}`);
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
}
|
|
@@ -1,72 +1,72 @@
|
|
|
1
|
-
import { UserError } from 'fastmcp';
|
|
2
|
-
import { z } from 'zod';
|
|
3
|
-
import { getSlidesClient } from '../../clients.js';
|
|
4
|
-
|
|
5
|
-
export function register(server) {
|
|
6
|
-
server.addTool({
|
|
7
|
-
name: 'formatSlidesParagraph',
|
|
8
|
-
description:
|
|
9
|
-
'Apply paragraph formatting (alignment, line spacing, bullets) to a text element in Google Slides.',
|
|
10
|
-
parameters: z.object({
|
|
11
|
-
presentationId: z.string().describe('Presentation ID'),
|
|
12
|
-
objectId: z.string().describe('Object ID of the text element'),
|
|
13
|
-
alignment: z.enum(['START', 'CENTER', 'END', 'JUSTIFIED']).optional().describe('Text alignment'),
|
|
14
|
-
lineSpacing: z.number().optional().describe('Line spacing multiplier (e.g. 1.5)'),
|
|
15
|
-
bulletStyle: z
|
|
16
|
-
.enum(['NONE', 'DISC', 'ARROW', 'SQUARE', 'DIAMOND', 'STAR', 'NUMBERED'])
|
|
17
|
-
.optional()
|
|
18
|
-
.describe('Bullet style'),
|
|
19
|
-
}),
|
|
20
|
-
execute: async (args, { log }) => {
|
|
21
|
-
const slidesApi = await getSlidesClient();
|
|
22
|
-
const requests = [];
|
|
23
|
-
|
|
24
|
-
if (args.alignment) {
|
|
25
|
-
requests.push({
|
|
26
|
-
updateParagraphStyle: {
|
|
27
|
-
objectId: args.objectId,
|
|
28
|
-
style: { alignment: args.alignment },
|
|
29
|
-
fields: 'alignment',
|
|
30
|
-
},
|
|
31
|
-
});
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
if (args.lineSpacing !== undefined) {
|
|
35
|
-
requests.push({
|
|
36
|
-
updateParagraphStyle: {
|
|
37
|
-
objectId: args.objectId,
|
|
38
|
-
style: { lineSpacing: args.lineSpacing },
|
|
39
|
-
fields: 'lineSpacing',
|
|
40
|
-
},
|
|
41
|
-
});
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
if (args.bulletStyle) {
|
|
45
|
-
if (args.bulletStyle === 'NONE') {
|
|
46
|
-
requests.push({ deleteParagraphBullets: { objectId: args.objectId } });
|
|
47
|
-
} else if (args.bulletStyle === 'NUMBERED') {
|
|
48
|
-
requests.push({
|
|
49
|
-
createParagraphBullets: { objectId: args.objectId, bulletPreset: 'NUMBERED_DIGIT_ALPHA_ROMAN' },
|
|
50
|
-
});
|
|
51
|
-
} else {
|
|
52
|
-
requests.push({
|
|
53
|
-
createParagraphBullets: { objectId: args.objectId, bulletPreset: `BULLET_${args.bulletStyle}_CIRCLE_SQUARE` },
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
if (requests.length === 0) throw new UserError('No formatting options specified');
|
|
59
|
-
|
|
60
|
-
try {
|
|
61
|
-
await slidesApi.presentations.batchUpdate({
|
|
62
|
-
presentationId: args.presentationId,
|
|
63
|
-
requestBody: { requests },
|
|
64
|
-
});
|
|
65
|
-
return JSON.stringify({ success: true, message: `Applied paragraph formatting to ${args.objectId}` });
|
|
66
|
-
} catch (error) {
|
|
67
|
-
log.error(`Error formatting paragraph: ${error.message || error}`);
|
|
68
|
-
throw new UserError(`Failed to format paragraph: ${error.message || 'Unknown error'}`);
|
|
69
|
-
}
|
|
70
|
-
},
|
|
71
|
-
});
|
|
72
|
-
}
|
|
1
|
+
import { UserError } from 'fastmcp';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { getSlidesClient } from '../../clients.js';
|
|
4
|
+
|
|
5
|
+
export function register(server) {
|
|
6
|
+
server.addTool({
|
|
7
|
+
name: 'formatSlidesParagraph',
|
|
8
|
+
description:
|
|
9
|
+
'Apply paragraph formatting (alignment, line spacing, bullets) to a text element in Google Slides.',
|
|
10
|
+
parameters: z.object({
|
|
11
|
+
presentationId: z.string().describe('Presentation ID'),
|
|
12
|
+
objectId: z.string().describe('Object ID of the text element'),
|
|
13
|
+
alignment: z.enum(['START', 'CENTER', 'END', 'JUSTIFIED']).optional().describe('Text alignment'),
|
|
14
|
+
lineSpacing: z.number().optional().describe('Line spacing multiplier (e.g. 1.5)'),
|
|
15
|
+
bulletStyle: z
|
|
16
|
+
.enum(['NONE', 'DISC', 'ARROW', 'SQUARE', 'DIAMOND', 'STAR', 'NUMBERED'])
|
|
17
|
+
.optional()
|
|
18
|
+
.describe('Bullet style'),
|
|
19
|
+
}),
|
|
20
|
+
execute: async (args, { log }) => {
|
|
21
|
+
const slidesApi = await getSlidesClient();
|
|
22
|
+
const requests = [];
|
|
23
|
+
|
|
24
|
+
if (args.alignment) {
|
|
25
|
+
requests.push({
|
|
26
|
+
updateParagraphStyle: {
|
|
27
|
+
objectId: args.objectId,
|
|
28
|
+
style: { alignment: args.alignment },
|
|
29
|
+
fields: 'alignment',
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (args.lineSpacing !== undefined) {
|
|
35
|
+
requests.push({
|
|
36
|
+
updateParagraphStyle: {
|
|
37
|
+
objectId: args.objectId,
|
|
38
|
+
style: { lineSpacing: args.lineSpacing },
|
|
39
|
+
fields: 'lineSpacing',
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (args.bulletStyle) {
|
|
45
|
+
if (args.bulletStyle === 'NONE') {
|
|
46
|
+
requests.push({ deleteParagraphBullets: { objectId: args.objectId } });
|
|
47
|
+
} else if (args.bulletStyle === 'NUMBERED') {
|
|
48
|
+
requests.push({
|
|
49
|
+
createParagraphBullets: { objectId: args.objectId, bulletPreset: 'NUMBERED_DIGIT_ALPHA_ROMAN' },
|
|
50
|
+
});
|
|
51
|
+
} else {
|
|
52
|
+
requests.push({
|
|
53
|
+
createParagraphBullets: { objectId: args.objectId, bulletPreset: `BULLET_${args.bulletStyle}_CIRCLE_SQUARE` },
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (requests.length === 0) throw new UserError('No formatting options specified');
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
await slidesApi.presentations.batchUpdate({
|
|
62
|
+
presentationId: args.presentationId,
|
|
63
|
+
requestBody: { requests },
|
|
64
|
+
});
|
|
65
|
+
return JSON.stringify({ success: true, message: `Applied paragraph formatting to ${args.objectId}` });
|
|
66
|
+
} catch (error) {
|
|
67
|
+
log.error(`Error formatting paragraph: ${error.message || error}`);
|
|
68
|
+
throw new UserError(`Failed to format paragraph: ${error.message || 'Unknown error'}`);
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
}
|
|
@@ -1,84 +1,84 @@
|
|
|
1
|
-
import { UserError } from 'fastmcp';
|
|
2
|
-
import { z } from 'zod';
|
|
3
|
-
import { getSlidesClient } from '../../clients.js';
|
|
4
|
-
|
|
5
|
-
export function register(server) {
|
|
6
|
-
server.addTool({
|
|
7
|
-
name: 'formatSlidesText',
|
|
8
|
-
description:
|
|
9
|
-
'Apply text formatting (bold, italic, underline, font size, color, etc.) to a text element in Google Slides. Use getPresentation to find element objectIds first.',
|
|
10
|
-
parameters: z.object({
|
|
11
|
-
presentationId: z.string().describe('Presentation ID'),
|
|
12
|
-
objectId: z.string().describe('Object ID of the text element'),
|
|
13
|
-
startIndex: z.number().min(0).optional().describe('Start character index (0-based). Omit with endIndex to format all text.'),
|
|
14
|
-
endIndex: z.number().min(0).optional().describe('End character index (0-based)'),
|
|
15
|
-
bold: z.boolean().optional().describe('Make text bold'),
|
|
16
|
-
italic: z.boolean().optional().describe('Make text italic'),
|
|
17
|
-
underline: z.boolean().optional().describe('Underline text'),
|
|
18
|
-
strikethrough: z.boolean().optional().describe('Strikethrough text'),
|
|
19
|
-
fontSize: z.number().optional().describe('Font size in points'),
|
|
20
|
-
fontFamily: z.string().optional().describe('Font family name (e.g. "Arial", "Roboto")'),
|
|
21
|
-
foregroundColor: z
|
|
22
|
-
.object({
|
|
23
|
-
red: z.number().min(0).max(1).optional(),
|
|
24
|
-
green: z.number().min(0).max(1).optional(),
|
|
25
|
-
blue: z.number().min(0).max(1).optional(),
|
|
26
|
-
})
|
|
27
|
-
.optional()
|
|
28
|
-
.describe('Text color as RGB values (0-1)'),
|
|
29
|
-
}),
|
|
30
|
-
execute: async (args, { log }) => {
|
|
31
|
-
const slidesApi = await getSlidesClient();
|
|
32
|
-
|
|
33
|
-
const textStyle = {};
|
|
34
|
-
const fields = [];
|
|
35
|
-
|
|
36
|
-
if (args.bold !== undefined) { textStyle.bold = args.bold; fields.push('bold'); }
|
|
37
|
-
if (args.italic !== undefined) { textStyle.italic = args.italic; fields.push('italic'); }
|
|
38
|
-
if (args.underline !== undefined) { textStyle.underline = args.underline; fields.push('underline'); }
|
|
39
|
-
if (args.strikethrough !== undefined) { textStyle.strikethrough = args.strikethrough; fields.push('strikethrough'); }
|
|
40
|
-
if (args.fontSize !== undefined) {
|
|
41
|
-
textStyle.fontSize = { magnitude: args.fontSize, unit: 'PT' };
|
|
42
|
-
fields.push('fontSize');
|
|
43
|
-
}
|
|
44
|
-
if (args.fontFamily !== undefined) { textStyle.fontFamily = args.fontFamily; fields.push('fontFamily'); }
|
|
45
|
-
if (args.foregroundColor) {
|
|
46
|
-
textStyle.foregroundColor = {
|
|
47
|
-
opaqueColor: {
|
|
48
|
-
rgbColor: {
|
|
49
|
-
red: args.foregroundColor.red || 0,
|
|
50
|
-
green: args.foregroundColor.green || 0,
|
|
51
|
-
blue: args.foregroundColor.blue || 0,
|
|
52
|
-
},
|
|
53
|
-
},
|
|
54
|
-
};
|
|
55
|
-
fields.push('foregroundColor');
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
if (fields.length === 0) throw new UserError('No formatting options specified');
|
|
59
|
-
|
|
60
|
-
const request = {
|
|
61
|
-
updateTextStyle: {
|
|
62
|
-
objectId: args.objectId,
|
|
63
|
-
style: textStyle,
|
|
64
|
-
fields: fields.join(','),
|
|
65
|
-
textRange:
|
|
66
|
-
args.startIndex !== undefined && args.endIndex !== undefined
|
|
67
|
-
? { type: 'FIXED_RANGE', startIndex: args.startIndex, endIndex: args.endIndex }
|
|
68
|
-
: { type: 'ALL' },
|
|
69
|
-
},
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
try {
|
|
73
|
-
await slidesApi.presentations.batchUpdate({
|
|
74
|
-
presentationId: args.presentationId,
|
|
75
|
-
requestBody: { requests: [request] },
|
|
76
|
-
});
|
|
77
|
-
return JSON.stringify({ success: true, message: `Applied text formatting to ${args.objectId}` });
|
|
78
|
-
} catch (error) {
|
|
79
|
-
log.error(`Error formatting text: ${error.message || error}`);
|
|
80
|
-
throw new UserError(`Failed to format text: ${error.message || 'Unknown error'}`);
|
|
81
|
-
}
|
|
82
|
-
},
|
|
83
|
-
});
|
|
84
|
-
}
|
|
1
|
+
import { UserError } from 'fastmcp';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { getSlidesClient } from '../../clients.js';
|
|
4
|
+
|
|
5
|
+
export function register(server) {
|
|
6
|
+
server.addTool({
|
|
7
|
+
name: 'formatSlidesText',
|
|
8
|
+
description:
|
|
9
|
+
'Apply text formatting (bold, italic, underline, font size, color, etc.) to a text element in Google Slides. Use getPresentation to find element objectIds first.',
|
|
10
|
+
parameters: z.object({
|
|
11
|
+
presentationId: z.string().describe('Presentation ID'),
|
|
12
|
+
objectId: z.string().describe('Object ID of the text element'),
|
|
13
|
+
startIndex: z.number().min(0).optional().describe('Start character index (0-based). Omit with endIndex to format all text.'),
|
|
14
|
+
endIndex: z.number().min(0).optional().describe('End character index (0-based)'),
|
|
15
|
+
bold: z.boolean().optional().describe('Make text bold'),
|
|
16
|
+
italic: z.boolean().optional().describe('Make text italic'),
|
|
17
|
+
underline: z.boolean().optional().describe('Underline text'),
|
|
18
|
+
strikethrough: z.boolean().optional().describe('Strikethrough text'),
|
|
19
|
+
fontSize: z.number().optional().describe('Font size in points'),
|
|
20
|
+
fontFamily: z.string().optional().describe('Font family name (e.g. "Arial", "Roboto")'),
|
|
21
|
+
foregroundColor: z
|
|
22
|
+
.object({
|
|
23
|
+
red: z.number().min(0).max(1).optional(),
|
|
24
|
+
green: z.number().min(0).max(1).optional(),
|
|
25
|
+
blue: z.number().min(0).max(1).optional(),
|
|
26
|
+
})
|
|
27
|
+
.optional()
|
|
28
|
+
.describe('Text color as RGB values (0-1)'),
|
|
29
|
+
}),
|
|
30
|
+
execute: async (args, { log }) => {
|
|
31
|
+
const slidesApi = await getSlidesClient();
|
|
32
|
+
|
|
33
|
+
const textStyle = {};
|
|
34
|
+
const fields = [];
|
|
35
|
+
|
|
36
|
+
if (args.bold !== undefined) { textStyle.bold = args.bold; fields.push('bold'); }
|
|
37
|
+
if (args.italic !== undefined) { textStyle.italic = args.italic; fields.push('italic'); }
|
|
38
|
+
if (args.underline !== undefined) { textStyle.underline = args.underline; fields.push('underline'); }
|
|
39
|
+
if (args.strikethrough !== undefined) { textStyle.strikethrough = args.strikethrough; fields.push('strikethrough'); }
|
|
40
|
+
if (args.fontSize !== undefined) {
|
|
41
|
+
textStyle.fontSize = { magnitude: args.fontSize, unit: 'PT' };
|
|
42
|
+
fields.push('fontSize');
|
|
43
|
+
}
|
|
44
|
+
if (args.fontFamily !== undefined) { textStyle.fontFamily = args.fontFamily; fields.push('fontFamily'); }
|
|
45
|
+
if (args.foregroundColor) {
|
|
46
|
+
textStyle.foregroundColor = {
|
|
47
|
+
opaqueColor: {
|
|
48
|
+
rgbColor: {
|
|
49
|
+
red: args.foregroundColor.red || 0,
|
|
50
|
+
green: args.foregroundColor.green || 0,
|
|
51
|
+
blue: args.foregroundColor.blue || 0,
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
fields.push('foregroundColor');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (fields.length === 0) throw new UserError('No formatting options specified');
|
|
59
|
+
|
|
60
|
+
const request = {
|
|
61
|
+
updateTextStyle: {
|
|
62
|
+
objectId: args.objectId,
|
|
63
|
+
style: textStyle,
|
|
64
|
+
fields: fields.join(','),
|
|
65
|
+
textRange:
|
|
66
|
+
args.startIndex !== undefined && args.endIndex !== undefined
|
|
67
|
+
? { type: 'FIXED_RANGE', startIndex: args.startIndex, endIndex: args.endIndex }
|
|
68
|
+
: { type: 'ALL' },
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
await slidesApi.presentations.batchUpdate({
|
|
74
|
+
presentationId: args.presentationId,
|
|
75
|
+
requestBody: { requests: [request] },
|
|
76
|
+
});
|
|
77
|
+
return JSON.stringify({ success: true, message: `Applied text formatting to ${args.objectId}` });
|
|
78
|
+
} catch (error) {
|
|
79
|
+
log.error(`Error formatting text: ${error.message || error}`);
|
|
80
|
+
throw new UserError(`Failed to format text: ${error.message || 'Unknown error'}`);
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
}
|
|
@@ -1,87 +1,87 @@
|
|
|
1
|
-
import { UserError } from 'fastmcp';
|
|
2
|
-
import { z } from 'zod';
|
|
3
|
-
import { getSlidesClient } from '../../clients.js';
|
|
4
|
-
|
|
5
|
-
export function register(server) {
|
|
6
|
-
server.addTool({
|
|
7
|
-
name: 'getPresentation',
|
|
8
|
-
description:
|
|
9
|
-
'Get the content of a Google Slides presentation with element IDs for formatting. Optionally retrieve a single slide by index.',
|
|
10
|
-
parameters: z.object({
|
|
11
|
-
presentationId: z.string().describe('Presentation ID'),
|
|
12
|
-
slideIndex: z.number().min(0).optional().describe('Specific slide index (0-based). Omit to get all slides.'),
|
|
13
|
-
}),
|
|
14
|
-
execute: async (args, { log }) => {
|
|
15
|
-
const slidesApi = await getSlidesClient();
|
|
16
|
-
log.info(`Reading presentation: ${args.presentationId}`);
|
|
17
|
-
|
|
18
|
-
try {
|
|
19
|
-
const presentation = await slidesApi.presentations.get({
|
|
20
|
-
presentationId: args.presentationId,
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
if (!presentation.data.slides) {
|
|
24
|
-
throw new UserError('No slides found in presentation');
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const allSlides = presentation.data.slides;
|
|
28
|
-
const slides =
|
|
29
|
-
args.slideIndex !== undefined ? [allSlides[args.slideIndex]] : allSlides;
|
|
30
|
-
|
|
31
|
-
const result = {
|
|
32
|
-
title: presentation.data.title,
|
|
33
|
-
presentationId: args.presentationId,
|
|
34
|
-
totalSlides: allSlides.length,
|
|
35
|
-
slides: slides.map((slide, idx) => {
|
|
36
|
-
if (!slide?.objectId) return null;
|
|
37
|
-
const slideInfo = {
|
|
38
|
-
index: args.slideIndex ?? idx,
|
|
39
|
-
objectId: slide.objectId,
|
|
40
|
-
elements: [],
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
for (const el of slide.pageElements || []) {
|
|
44
|
-
if (!el.objectId) continue;
|
|
45
|
-
if (el.shape?.text) {
|
|
46
|
-
let text = '';
|
|
47
|
-
for (const te of el.shape.text.textElements || []) {
|
|
48
|
-
if (te.textRun?.content) text += te.textRun.content;
|
|
49
|
-
}
|
|
50
|
-
slideInfo.elements.push({
|
|
51
|
-
type: 'text',
|
|
52
|
-
objectId: el.objectId,
|
|
53
|
-
placeholderType: el.shape.placeholder?.type || null,
|
|
54
|
-
text: text.trim(),
|
|
55
|
-
});
|
|
56
|
-
} else if (el.shape) {
|
|
57
|
-
slideInfo.elements.push({
|
|
58
|
-
type: 'shape',
|
|
59
|
-
objectId: el.objectId,
|
|
60
|
-
shapeType: el.shape.shapeType || 'Unknown',
|
|
61
|
-
});
|
|
62
|
-
} else if (el.image) {
|
|
63
|
-
slideInfo.elements.push({ type: 'image', objectId: el.objectId });
|
|
64
|
-
} else if (el.video) {
|
|
65
|
-
slideInfo.elements.push({ type: 'video', objectId: el.objectId });
|
|
66
|
-
} else if (el.table) {
|
|
67
|
-
slideInfo.elements.push({
|
|
68
|
-
type: 'table',
|
|
69
|
-
objectId: el.objectId,
|
|
70
|
-
rows: el.table.rows,
|
|
71
|
-
columns: el.table.columns,
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
return slideInfo;
|
|
76
|
-
}).filter(Boolean),
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
return JSON.stringify(result, null, 2);
|
|
80
|
-
} catch (error) {
|
|
81
|
-
if (error instanceof UserError) throw error;
|
|
82
|
-
log.error(`Error reading presentation: ${error.message || error}`);
|
|
83
|
-
throw new UserError(`Failed to read presentation: ${error.message || 'Unknown error'}`);
|
|
84
|
-
}
|
|
85
|
-
},
|
|
86
|
-
});
|
|
87
|
-
}
|
|
1
|
+
import { UserError } from 'fastmcp';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { getSlidesClient } from '../../clients.js';
|
|
4
|
+
|
|
5
|
+
export function register(server) {
|
|
6
|
+
server.addTool({
|
|
7
|
+
name: 'getPresentation',
|
|
8
|
+
description:
|
|
9
|
+
'Get the content of a Google Slides presentation with element IDs for formatting. Optionally retrieve a single slide by index.',
|
|
10
|
+
parameters: z.object({
|
|
11
|
+
presentationId: z.string().describe('Presentation ID'),
|
|
12
|
+
slideIndex: z.number().min(0).optional().describe('Specific slide index (0-based). Omit to get all slides.'),
|
|
13
|
+
}),
|
|
14
|
+
execute: async (args, { log }) => {
|
|
15
|
+
const slidesApi = await getSlidesClient();
|
|
16
|
+
log.info(`Reading presentation: ${args.presentationId}`);
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
const presentation = await slidesApi.presentations.get({
|
|
20
|
+
presentationId: args.presentationId,
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
if (!presentation.data.slides) {
|
|
24
|
+
throw new UserError('No slides found in presentation');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const allSlides = presentation.data.slides;
|
|
28
|
+
const slides =
|
|
29
|
+
args.slideIndex !== undefined ? [allSlides[args.slideIndex]] : allSlides;
|
|
30
|
+
|
|
31
|
+
const result = {
|
|
32
|
+
title: presentation.data.title,
|
|
33
|
+
presentationId: args.presentationId,
|
|
34
|
+
totalSlides: allSlides.length,
|
|
35
|
+
slides: slides.map((slide, idx) => {
|
|
36
|
+
if (!slide?.objectId) return null;
|
|
37
|
+
const slideInfo = {
|
|
38
|
+
index: args.slideIndex ?? idx,
|
|
39
|
+
objectId: slide.objectId,
|
|
40
|
+
elements: [],
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
for (const el of slide.pageElements || []) {
|
|
44
|
+
if (!el.objectId) continue;
|
|
45
|
+
if (el.shape?.text) {
|
|
46
|
+
let text = '';
|
|
47
|
+
for (const te of el.shape.text.textElements || []) {
|
|
48
|
+
if (te.textRun?.content) text += te.textRun.content;
|
|
49
|
+
}
|
|
50
|
+
slideInfo.elements.push({
|
|
51
|
+
type: 'text',
|
|
52
|
+
objectId: el.objectId,
|
|
53
|
+
placeholderType: el.shape.placeholder?.type || null,
|
|
54
|
+
text: text.trim(),
|
|
55
|
+
});
|
|
56
|
+
} else if (el.shape) {
|
|
57
|
+
slideInfo.elements.push({
|
|
58
|
+
type: 'shape',
|
|
59
|
+
objectId: el.objectId,
|
|
60
|
+
shapeType: el.shape.shapeType || 'Unknown',
|
|
61
|
+
});
|
|
62
|
+
} else if (el.image) {
|
|
63
|
+
slideInfo.elements.push({ type: 'image', objectId: el.objectId });
|
|
64
|
+
} else if (el.video) {
|
|
65
|
+
slideInfo.elements.push({ type: 'video', objectId: el.objectId });
|
|
66
|
+
} else if (el.table) {
|
|
67
|
+
slideInfo.elements.push({
|
|
68
|
+
type: 'table',
|
|
69
|
+
objectId: el.objectId,
|
|
70
|
+
rows: el.table.rows,
|
|
71
|
+
columns: el.table.columns,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return slideInfo;
|
|
76
|
+
}).filter(Boolean),
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
return JSON.stringify(result, null, 2);
|
|
80
|
+
} catch (error) {
|
|
81
|
+
if (error instanceof UserError) throw error;
|
|
82
|
+
log.error(`Error reading presentation: ${error.message || error}`);
|
|
83
|
+
throw new UserError(`Failed to read presentation: ${error.message || 'Unknown error'}`);
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
}
|