google-tools-mcp 1.2.12 → 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.
Files changed (97) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +537 -276
  3. package/dist/cachedToolsList.js +52 -52
  4. package/dist/googleDocsApiHelpers.js +76 -12
  5. package/dist/helpers.js +572 -252
  6. package/dist/httpAuth.js +296 -0
  7. package/dist/index.js +162 -11
  8. package/dist/logger.js +87 -87
  9. package/dist/markdown-transformer/docsToMarkdown.js +128 -0
  10. package/dist/markdown-transformer/index.js +17 -7
  11. package/dist/markdown-transformer/markdownToDocs.js +185 -27
  12. package/dist/readTracker.js +136 -17
  13. package/dist/sessionContext.js +37 -0
  14. package/dist/setup.js +187 -9
  15. package/dist/tools/calendar/getBusy.js +64 -64
  16. package/dist/tools/calendar/getEvents.js +141 -141
  17. package/dist/tools/calendar/getFree.js +225 -225
  18. package/dist/tools/calendar/index.js +19 -19
  19. package/dist/tools/calendar/listCalendars.js +38 -38
  20. package/dist/tools/calendar/listRecurringInstances.js +83 -83
  21. package/dist/tools/calendar/manageCalendar.js +121 -121
  22. package/dist/tools/calendar/manageEvent.js +258 -258
  23. package/dist/tools/calendar/moveEvent.js +66 -66
  24. package/dist/tools/docs/addTab.js +10 -12
  25. package/dist/tools/docs/appendToGoogleDoc.js +9 -4
  26. package/dist/tools/docs/deleteRange.js +9 -4
  27. package/dist/tools/docs/findAndReplace.js +9 -4
  28. package/dist/tools/docs/formatting/applyParagraphStyle.js +4 -1
  29. package/dist/tools/docs/formatting/getFormatting.js +171 -171
  30. package/dist/tools/docs/insertImage.js +15 -2
  31. package/dist/tools/docs/insertPageBreak.js +4 -1
  32. package/dist/tools/docs/insertTable.js +4 -1
  33. package/dist/tools/docs/insertTableWithData.js +4 -1
  34. package/dist/tools/docs/modifyText.js +14 -4
  35. package/dist/tools/docs/modifyText.test.js +84 -84
  36. package/dist/tools/docs/readGoogleDoc.js +71 -11
  37. package/dist/tools/docs/renameTab.js +12 -14
  38. package/dist/tools/drafts.js +165 -165
  39. package/dist/tools/drive/createDocument.js +12 -0
  40. package/dist/tools/drive/downloadFile.js +268 -268
  41. package/dist/tools/drive/getFileInfo.js +48 -48
  42. package/dist/tools/drive/getFilePath.js +50 -50
  43. package/dist/tools/drive/listDriveFiles.js +112 -112
  44. package/dist/tools/drive/listSharedDrives.js +52 -52
  45. package/dist/tools/drive/listSharedWithMe.js +97 -97
  46. package/dist/tools/drive/uploadFile.js +111 -111
  47. package/dist/tools/extras/index.js +9 -9
  48. package/dist/tools/extras/readDriveFile.js +197 -196
  49. package/dist/tools/extras/readFile.js +84 -84
  50. package/dist/tools/extras/searchFileContents.js +81 -81
  51. package/dist/tools/forms/batchUpdateForm.js +81 -81
  52. package/dist/tools/forms/createForm.js +54 -54
  53. package/dist/tools/forms/getForm.js +118 -118
  54. package/dist/tools/forms/getFormResponse.js +45 -45
  55. package/dist/tools/forms/index.js +15 -15
  56. package/dist/tools/forms/listFormResponses.js +56 -56
  57. package/dist/tools/forms/setPublishSettings.js +59 -59
  58. package/dist/tools/gmail/drafts.js +165 -165
  59. package/dist/tools/gmail/labels.js +54 -83
  60. package/dist/tools/gmail/messages.js +444 -441
  61. package/dist/tools/gmail/settings.js +358 -528
  62. package/dist/tools/gmail/threads.js +286 -164
  63. package/dist/tools/index.js +511 -473
  64. package/dist/tools/labels.js +103 -103
  65. package/dist/tools/legacyAliases.js +426 -0
  66. package/dist/tools/maps/directions.js +72 -0
  67. package/dist/tools/maps/geocode.js +16 -0
  68. package/dist/tools/maps/index.js +15 -0
  69. package/dist/tools/maps/mapsClient.js +83 -0
  70. package/dist/tools/maps/placeDetails.js +16 -0
  71. package/dist/tools/maps/reverseGeocode.js +19 -0
  72. package/dist/tools/maps/searchNearby.js +54 -0
  73. package/dist/tools/maps/searchPlaces.js +24 -0
  74. package/dist/tools/messages.js +448 -448
  75. package/dist/tools/settings.js +528 -528
  76. package/dist/tools/slides/createPresentation.js +104 -104
  77. package/dist/tools/slides/createShape.js +92 -92
  78. package/dist/tools/slides/createTextBox.js +82 -82
  79. package/dist/tools/slides/deleteSlide.js +30 -30
  80. package/dist/tools/slides/duplicateSlide.js +37 -37
  81. package/dist/tools/slides/exportThumbnail.js +42 -42
  82. package/dist/tools/slides/formatParagraph.js +72 -72
  83. package/dist/tools/slides/formatText.js +84 -84
  84. package/dist/tools/slides/getPresentation.js +87 -87
  85. package/dist/tools/slides/index.js +33 -33
  86. package/dist/tools/slides/reorderSlides.js +41 -41
  87. package/dist/tools/slides/replaceAllText.js +46 -46
  88. package/dist/tools/slides/setBackground.js +58 -58
  89. package/dist/tools/slides/speakerNotes.js +102 -102
  90. package/dist/tools/slides/styleShape.js +111 -111
  91. package/dist/tools/slides/updatePresentation.js +128 -128
  92. package/dist/tools/threads.js +145 -145
  93. package/dist/tools/utils/appendMarkdownToGoogleDoc.js +30 -6
  94. package/dist/tools/utils/replaceDocumentWithMarkdown.js +71 -8
  95. package/dist/updateCheck.js +202 -0
  96. package/dist/workspace.js +122 -0
  97. package/package.json +82 -81
@@ -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
+ }