Spaces:
Sleeping
Sleeping
File size: 7,551 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 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 | import type { FastMCP } from 'fastmcp';
import { UserError } from 'fastmcp';
import { z } from 'zod';
import { docs_v1 } from 'googleapis';
import { getDocsClient } from '../../clients.js';
import { DocumentIdParameter } from '../../types.js';
import * as GDocsHelpers from '../../googleDocsApiHelpers.js';
export interface UpdateSectionStyleBuilderInput {
startIndex: number;
endIndex: number;
flipPageOrientation?: boolean;
sectionType?: 'SECTION_TYPE_UNSPECIFIED' | 'CONTINUOUS' | 'NEXT_PAGE';
marginTop?: number;
marginBottom?: number;
marginLeft?: number;
marginRight?: number;
pageNumberStart?: number;
tabId?: string;
}
export function buildUpdateSectionStyleRequest(
params: UpdateSectionStyleBuilderInput
): { request: docs_v1.Schema$Request; fields: string[] } | null {
const sectionStyle: docs_v1.Schema$SectionStyle = {};
const fields: string[] = [];
if (params.flipPageOrientation !== undefined) {
sectionStyle.flipPageOrientation = params.flipPageOrientation;
fields.push('flipPageOrientation');
}
if (params.sectionType !== undefined) {
sectionStyle.sectionType = params.sectionType;
fields.push('sectionType');
}
if (params.marginTop !== undefined) {
sectionStyle.marginTop = { magnitude: params.marginTop, unit: 'PT' };
fields.push('marginTop');
}
if (params.marginBottom !== undefined) {
sectionStyle.marginBottom = { magnitude: params.marginBottom, unit: 'PT' };
fields.push('marginBottom');
}
if (params.marginLeft !== undefined) {
sectionStyle.marginLeft = { magnitude: params.marginLeft, unit: 'PT' };
fields.push('marginLeft');
}
if (params.marginRight !== undefined) {
sectionStyle.marginRight = { magnitude: params.marginRight, unit: 'PT' };
fields.push('marginRight');
}
if (params.pageNumberStart !== undefined) {
sectionStyle.pageNumberStart = params.pageNumberStart;
fields.push('pageNumberStart');
}
if (fields.length === 0) {
return null;
}
const range: any = {
startIndex: params.startIndex,
endIndex: params.endIndex,
};
if (params.tabId) {
range.tabId = params.tabId;
}
return {
request: {
updateSectionStyle: {
range,
sectionStyle,
fields: fields.join(','),
},
},
fields,
};
}
export function register(server: FastMCP) {
server.addTool({
name: 'updateSectionStyle',
description:
'Updates the style of a section identified by a character range. The range must cover the section whose style is changing — typically from the character right after an inserted section break to any index inside that section. Supports flipping page orientation (landscape <-> portrait), setting margins, and changing the section type. Common workflow for a landscape page: call insertSectionBreak with sectionType="NEXT_PAGE" before the landscape content, call insertSectionBreak again after it, then call updateSectionStyle on the range between the two breaks with flipPageOrientation=true.',
parameters: DocumentIdParameter.extend({
startIndex: z
.number()
.int()
.min(1)
.describe(
'The starting index of the range covering the section to style (inclusive, 1-based). Typically the index immediately after the section break that opened this section.'
),
endIndex: z
.number()
.int()
.min(1)
.describe(
'The ending index of the range covering the section to style (exclusive). Any index inside the section works — the style applies to the whole section.'
),
flipPageOrientation: z
.boolean()
.optional()
.describe(
'If true, swaps the section page width and height (effectively toggling between portrait and landscape). If false, keeps the default document orientation.'
),
sectionType: z
.enum(['SECTION_TYPE_UNSPECIFIED', 'CONTINUOUS', 'NEXT_PAGE'])
.optional()
.describe(
'Changes the section type after the break. Usually set at insert time via insertSectionBreak, but can be updated here.'
),
marginTop: z
.number()
.nonnegative()
.optional()
.describe('Top margin of the section in points (1 inch = 72 points).'),
marginBottom: z
.number()
.nonnegative()
.optional()
.describe('Bottom margin of the section in points.'),
marginLeft: z
.number()
.nonnegative()
.optional()
.describe('Left margin of the section in points.'),
marginRight: z
.number()
.nonnegative()
.optional()
.describe('Right margin of the section in points.'),
pageNumberStart: z
.number()
.int()
.min(1)
.optional()
.describe(
'Page number to start the section from. If unset, numbering continues from the previous section.'
),
tabId: z
.string()
.optional()
.describe(
'The ID of the specific tab to update. Use listDocumentTabs to get tab IDs. If not specified, targets the first tab.'
),
}).refine((data) => data.endIndex > data.startIndex, {
message: 'endIndex must be greater than startIndex',
path: ['endIndex'],
}),
execute: async (args, { log }) => {
const docs = await getDocsClient();
log.info(
`Updating section style in doc ${args.documentId} for range ${args.startIndex}-${args.endIndex}${args.tabId ? ` (tab: ${args.tabId})` : ''}`
);
try {
if (args.tabId) {
const docInfo = await docs.documents.get({
documentId: args.documentId,
includeTabsContent: true,
fields: 'tabs(tabProperties,documentTab(body))',
});
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).`
);
}
}
const built = buildUpdateSectionStyleRequest({
startIndex: args.startIndex,
endIndex: args.endIndex,
flipPageOrientation: args.flipPageOrientation,
sectionType: args.sectionType,
marginTop: args.marginTop,
marginBottom: args.marginBottom,
marginLeft: args.marginLeft,
marginRight: args.marginRight,
pageNumberStart: args.pageNumberStart,
tabId: args.tabId,
});
if (!built) {
throw new UserError(
'No section style options were provided. Set at least one of: flipPageOrientation, sectionType, marginTop, marginBottom, marginLeft, marginRight, pageNumberStart.'
);
}
await GDocsHelpers.executeBatchUpdate(docs, args.documentId, [built.request]);
return `Successfully updated section style (${built.fields.join(', ')}) for range ${args.startIndex}-${args.endIndex}${args.tabId ? ` in tab ${args.tabId}` : ''}.`;
} catch (error: any) {
log.error(
`Error updating section style in doc ${args.documentId}: ${error.message || error}`
);
if (error instanceof UserError) throw error;
throw new UserError(`Failed to update section style: ${error.message || 'Unknown error'}`);
}
},
});
}
|