Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
e4740a6
feat(slides): add slides.create, slides.batchUpdate, and slides.creat…
n0012 Apr 25, 2026
0b2da2c
feat(slides): add indent, fix vertical_align on shapes, sanitize temp…
n0012 Apr 25, 2026
ebb6a10
feat(slides): add theme system to slides.createFromJson
n0012 Apr 26, 2026
02d8da7
fix(slides): accurate Google brand colors, light theme as default
n0012 Apr 26, 2026
27de814
docs(slides): add layout principles to createFromJson tool description
n0012 Apr 26, 2026
fee88f3
docs(slides): reframe tool description — creativity over rigidity
n0012 Apr 26, 2026
4ea8474
feat(slides): add slides.getSpeakerNotes and slides.updateSpeakerNotes
n0012 Apr 26, 2026
3942629
fix(slides): make theme + speaker notes failures explicit in tool des…
n0012 Apr 26, 2026
696a45a
feat(slides): inline speaker_notes in createFromJson blueprint
n0012 Apr 26, 2026
42a29ae
fix(slides): make speaker_notes required in schema description
n0012 Apr 26, 2026
f6d65cf
fix(slides): full four-color Google palette, near-black headers
n0012 Apr 26, 2026
884058d
feat(slides): four-color Google brand bar in theme guidance
n0012 Apr 26, 2026
f9e67bc
fix(slides): fix dark theme monotone, add accent colors to all themes
n0012 Apr 26, 2026
94726d6
docs(slides): less is more — restrain color, embrace whitespace
n0012 Apr 26, 2026
364ac7b
feat(slides): nudge agent to write speaker notes in a second pass
n0012 Apr 26, 2026
fb60d86
docs(slides): speaker notes should be ~45 seconds per slide
n0012 Apr 26, 2026
544d276
fix(slides): auto-delete default blank slide after createFromJson
n0012 Apr 26, 2026
937b7a3
refactor(slides): remove dark theme, simplify to light + google
n0012 Apr 26, 2026
1c69a60
refactor(slides): single Google theme, remove theme parameter
n0012 Apr 26, 2026
84e5f7f
feat(slides): auto-add footer + four-color bar to every slide
n0012 Apr 26, 2026
8dfa3d8
refactor(slides): remove hardcoded footer + brand bar from server
n0012 Apr 26, 2026
8c34a39
feat(skills): add slides-creation template skill
n0012 Apr 26, 2026
02b3502
fix(docs): use explicit field masks to fix comment-field API error
n0012 Apr 29, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
151 changes: 151 additions & 0 deletions workspace-server/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,157 @@ async function main() {
slidesService.getSlideThumbnail,
);

server.registerTool(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The slides.create tool is being registered using server.registerTool directly, which bypasses the registerTool wrapper defined on line 154. This wrapper is responsible for checking if the tool is enabled via feature flags (WORKSPACE_FEATURE_OVERRIDES). Using the wrapper ensures consistency and allows users to disable these tools if needed.

Suggested change
server.registerTool(
registerTool(

'slides.create',
{
description:
'Creates a new blank Google Slides presentation. Returns the presentation ID and URL.',
inputSchema: {
title: z.string().describe('The title for the new presentation.'),
},
},
slidesService.create,
);

server.registerTool(
'slides.batchUpdate',
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Similar to slides.create, this tool should be registered using the registerTool wrapper to respect feature flags.

  registerTool(

{
description:
'Executes a batch of updates (create, modify, delete) on a Google Slides presentation. Takes an array of raw Slides API request objects.',
inputSchema: {
presentationId: z
.string()
.describe('The ID or URL of the presentation to modify.'),
requests: z
.string()
.describe(
'JSON string of an array of Slides API request objects (e.g., [{"createSlide":{}}, {"createShape":{...}}]). Will be parsed server-side.',
),
Comment on lines +518 to +522
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The requests field is restricted to a string, but the SlidesService.batchUpdate implementation (line 329) and most MCP clients support passing structured arrays directly. Allowing both a JSON string and an array of objects provides a better experience for AI agents.

Suggested change
requests: z
.string()
.describe(
'JSON string of an array of Slides API request objects (e.g., [{"createSlide":{}}, {"createShape":{...}}]). Will be parsed server-side.',
),
requests: z
.union([z.string(), z.array(z.any())])
.describe(
'An array of Slides API request objects or a JSON string of that array (e.g., [{"createSlide":{}}, {"createShape":{...}}]).',
),

},
},
slidesService.batchUpdate,
);

// Shared element schema for createFromJson
const slideElementSchema = z.object({
type: z.enum(['text', 'shape', 'image']).describe('Element type.'),
content: z
.string()
.optional()
.describe('Text content (for text elements).'),
shape_type: z
.string()
.optional()
.describe(
'Shape type (e.g., RECTANGLE, RIGHT_ARROW, TEXT_BOX). Default: RECTANGLE.',
),
url: z.string().optional().describe('Image URL (for image elements).'),
layer: z
.number()
.optional()
.describe(
'Z-index layer for rendering order. Lower layers render first.',
),
position: z
.object({
x: z.number().describe('X position in points.'),
y: z.number().describe('Y position in points.'),
w: z.number().describe('Width in points.'),
h: z.number().describe('Height in points.'),
})
.describe('Position and size on a 720x405 point grid.'),
style: z
.object({
size: z.number().optional().describe('Font size in points.'),
bold: z.boolean().optional().describe('Bold text.'),
italic: z.boolean().optional().describe('Italic text.'),
align: z
.enum(['START', 'CENTER', 'END'])
.optional()
.describe('Horizontal text alignment.'),
vertical_align: z
.enum(['TOP', 'MIDDLE', 'BOTTOM'])
.optional()
.describe('Vertical content alignment.'),
color: z
.object({
red: z.number(),
green: z.number(),
blue: z.number(),
})
.optional()
.describe('Text color (RGB 0-1).'),
bg_color: z
.object({
red: z.number(),
green: z.number(),
blue: z.number(),
})
.optional()
.describe('Shape background color (RGB 0-1).'),
border_color: z
.object({
red: z.number(),
green: z.number(),
blue: z.number(),
})
.optional()
.describe('Shape border color (RGB 0-1).'),
border_weight: z
.number()
.optional()
.describe('Border weight in points.'),
no_border: z
.boolean()
.optional()
.describe('Remove border from shape.'),
font_family: z
.string()
.optional()
.describe('Font family name (e.g. "Arial", "Roboto"). Defaults to "Arial".'),
underline: z.boolean().optional().describe('Underline text.'),
strikethrough: z.boolean().optional().describe('Strikethrough text.'),
bold_phrases: z
.array(z.string())
.optional()
.describe('Phrases within content to bold.'),
bold_until: z
.number()
.optional()
.describe('Bold text from start to this character index.'),
links: z
.array(
z.object({
text: z.string().describe('Link text to find in content.'),
url: z.string().describe('URL to link to.'),
}),
)
.optional()
.describe('Hyperlinks to apply to matching text.'),
})
.optional()
.describe('Styling options for the element.'),
});

server.registerTool(
'slides.createFromJson',
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

This tool should also use the registerTool wrapper to ensure it can be managed via feature flags.

  registerTool(

{
description:
'Creates one or more slides in a presentation from a JSON blueprint. Supports two formats: (1) { "slides": [{ "elements": [...] }, ...] } for multiple slides, or (2) { "elements": [...] } for a single slide. Elements are positioned on a 720x405 point grid. Each element has: type ("text"|"shape"|"image"), position ({x,y,w,h} in points), optional content (string), optional shape_type (e.g. "RECTANGLE","TEXT_BOX"), optional url (for images), optional layer (z-index), optional style ({size,bold,italic,underline,strikethrough,align,vertical_align,color,bg_color,border_color,border_weight,no_border,font_family,bold_phrases,bold_until,links}). Colors are RGB 0-1 objects ({red,green,blue}).',
inputSchema: {
presentationId: z
.string()
.describe('The ID or URL of the presentation to add slides to.'),
slideJson: z
.string()
.describe(
'JSON string of the slide blueprint. Use {"slides":[{"elements":[...]},...]} for multiple slides or {"elements":[...]} for one slide. Will be parsed server-side.',
),
Comment on lines +642 to +646
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The slideElementSchema defined on lines 493-591 is currently unused. It should be applied to the slideJson input schema to provide the AI agent with structured validation and clear documentation of the expected blueprint format. Additionally, allowing both objects and strings makes the tool more robust.

        slideJson: z
          .union([
            z.object({
              slides: z.array(z.object({ elements: z.array(slideElementSchema) })),
            }),
            z.object({
              elements: z.array(slideElementSchema),
            }),
            z.string(),
          ])
          .describe(
            'The slide blueprint. Use {"slides":[{"elements":[...]}]} for multiple slides or {"elements":[...]} for one slide. Can be a JSON string or object.',
          ),

},
},
slidesService.createFromJson,
);

// Sheets tools
registerTool(
'sheets.getText',
Expand Down
Loading