Skip to content

Commit 44b9205

Browse files
authored
Merge pull request #4 from aliwatters/fix/docs-batch-update-nil-requests
fix: construct actual Docs API request objects instead of passing nil
2 parents eb9a40a + ba29245 commit 44b9205

File tree

3 files changed

+273
-26
lines changed

3 files changed

+273
-26
lines changed

internal/docs/docs_content_testable.go

Lines changed: 53 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88

99
"github.com/aliwatters/gsuite-mcp/internal/common"
1010
"github.com/mark3labs/mcp-go/mcp"
11+
"google.golang.org/api/docs/v1"
1112
)
1213

1314
// docsEditURLFormat is the URL template for Google Docs edit links.
@@ -171,9 +172,13 @@ func TestableDocsAppendText(ctx context.Context, request mcp.CallToolRequest, de
171172
insertIndex = 1
172173
}
173174

174-
// For testable version, we don't need to actually construct requests
175-
// since we're using the mock service which tracks BatchUpdate calls
176-
_, err = srv.BatchUpdate(ctx, docID, nil)
175+
requests := []*docs.Request{{
176+
InsertText: &docs.InsertTextRequest{
177+
Text: text,
178+
Location: &docs.Location{Index: insertIndex},
179+
},
180+
}}
181+
_, err = srv.BatchUpdate(ctx, docID, requests)
177182
if err != nil {
178183
return mcp.NewToolResultError(fmt.Sprintf("Docs API error: %v", err)), nil
179184
}
@@ -216,7 +221,13 @@ func TestableDocsInsertText(ctx context.Context, request mcp.CallToolRequest, de
216221

217222
docID = common.ExtractGoogleResourceID(docID)
218223

219-
_, err := srv.BatchUpdate(ctx, docID, nil)
224+
requests := []*docs.Request{{
225+
InsertText: &docs.InsertTextRequest{
226+
Text: text,
227+
Location: &docs.Location{Index: index},
228+
},
229+
}}
230+
_, err := srv.BatchUpdate(ctx, docID, requests)
220231
if err != nil {
221232
return mcp.NewToolResultError(fmt.Sprintf("Docs API error: %v", err)), nil
222233
}
@@ -256,15 +267,29 @@ func TestableDocsReplaceText(ctx context.Context, request mcp.CallToolRequest, d
256267

257268
docID = common.ExtractGoogleResourceID(docID)
258269

259-
_, err := srv.BatchUpdate(ctx, docID, nil)
270+
replaceReq := &docs.ReplaceAllTextRequest{
271+
ContainsText: &docs.SubstringMatchCriteria{
272+
Text: findText,
273+
MatchCase: matchCase,
274+
},
275+
ReplaceText: replaceText,
276+
ForceSendFields: []string{"ReplaceText"},
277+
}
278+
requests := []*docs.Request{{ReplaceAllText: replaceReq}}
279+
resp, err := srv.BatchUpdate(ctx, docID, requests)
260280
if err != nil {
261281
return mcp.NewToolResultError(fmt.Sprintf("Docs API error: %v", err)), nil
262282
}
263283

284+
replacementsCount := int64(0)
285+
if resp != nil && resp.Replies != nil && len(resp.Replies) > 0 && resp.Replies[0].ReplaceAllText != nil {
286+
replacementsCount = resp.Replies[0].ReplaceAllText.OccurrencesChanged
287+
}
288+
264289
result := map[string]any{
265290
"success": true,
266291
"document_id": docID,
267-
"replacements_count": 1, // Mock assumes 1 replacement
292+
"replacements_count": replacementsCount,
268293
"match_case": matchCase,
269294
"message": fmt.Sprintf("Replaced occurrences of '%s' with '%s'", findText, replaceText),
270295
"url": fmt.Sprintf(docsEditURLFormat, docID),
@@ -292,7 +317,15 @@ func TestableDocsDeleteText(ctx context.Context, request mcp.CallToolRequest, de
292317

293318
docID = common.ExtractGoogleResourceID(docID)
294319

295-
_, err := srv.BatchUpdate(ctx, docID, nil)
320+
requests := []*docs.Request{{
321+
DeleteContentRange: &docs.DeleteContentRangeRequest{
322+
Range: &docs.Range{
323+
StartIndex: startIndex,
324+
EndIndex: endIndex,
325+
},
326+
},
327+
}}
328+
_, err := srv.BatchUpdate(ctx, docID, requests)
296329
if err != nil {
297330
return mcp.NewToolResultError(fmt.Sprintf("Docs API error: %v", err)), nil
298331
}
@@ -326,27 +359,32 @@ func TestableDocsBatchUpdate(ctx context.Context, request mcp.CallToolRequest, d
326359

327360
docID = common.ExtractGoogleResourceID(docID)
328361

329-
// Parse the JSON to validate it
330-
var requests []any
331-
if err := json.Unmarshal([]byte(requestsJSON), &requests); err != nil {
362+
// Parse the JSON into Docs API request objects
363+
var docsRequests []*docs.Request
364+
if err := json.Unmarshal([]byte(requestsJSON), &docsRequests); err != nil {
332365
return mcp.NewToolResultError(fmt.Sprintf("Failed to parse requests JSON: %v", err)), nil
333366
}
334367

335-
if len(requests) == 0 {
368+
if len(docsRequests) == 0 {
336369
return mcp.NewToolResultError("requests array cannot be empty"), nil
337370
}
338371

339-
_, err := srv.BatchUpdate(ctx, docID, nil)
372+
resp, err := srv.BatchUpdate(ctx, docID, docsRequests)
340373
if err != nil {
341374
return mcp.NewToolResultError(fmt.Sprintf("Docs API error: %v", err)), nil
342375
}
343376

377+
repliesCount := 0
378+
if resp != nil && resp.Replies != nil {
379+
repliesCount = len(resp.Replies)
380+
}
381+
344382
result := map[string]any{
345383
"success": true,
346384
"document_id": docID,
347-
"requests_count": len(requests),
348-
"replies_count": len(requests),
349-
"message": fmt.Sprintf("Successfully executed %d batch update request(s)", len(requests)),
385+
"requests_count": len(docsRequests),
386+
"replies_count": repliesCount,
387+
"message": fmt.Sprintf("Successfully executed %d batch update request(s)", len(docsRequests)),
350388
"url": fmt.Sprintf(docsEditURLFormat, docID),
351389
}
352390

internal/docs/docs_formatting_testable.go

Lines changed: 144 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77

88
"github.com/aliwatters/gsuite-mcp/internal/common"
99
"github.com/mark3labs/mcp-go/mcp"
10+
"google.golang.org/api/docs/v1"
1011
)
1112

1213
// textFormatFields defines the parameter-to-field mapping for text formatting.
@@ -144,7 +145,18 @@ func TestableDocsFormatText(ctx context.Context, request mcp.CallToolRequest, de
144145
return mcp.NewToolResultError("at least one formatting option must be specified"), nil
145146
}
146147

147-
_, err := srv.BatchUpdate(ctx, docID, nil)
148+
textStyle := buildTextStyle(request.Params.Arguments)
149+
requests := []*docs.Request{{
150+
UpdateTextStyle: &docs.UpdateTextStyleRequest{
151+
Range: &docs.Range{
152+
StartIndex: startIndex,
153+
EndIndex: endIndex,
154+
},
155+
TextStyle: textStyle,
156+
Fields: strings.Join(fields, ","),
157+
},
158+
}}
159+
_, err := srv.BatchUpdate(ctx, docID, requests)
148160
if err != nil {
149161
return mcp.NewToolResultError(fmt.Sprintf("Docs API error: %v", err)), nil
150162
}
@@ -179,7 +191,17 @@ func TestableDocsClearFormatting(ctx context.Context, request mcp.CallToolReques
179191

180192
docID = common.ExtractGoogleResourceID(docID)
181193

182-
_, err := srv.BatchUpdate(ctx, docID, nil)
194+
requests := []*docs.Request{{
195+
UpdateTextStyle: &docs.UpdateTextStyleRequest{
196+
Range: &docs.Range{
197+
StartIndex: startIndex,
198+
EndIndex: endIndex,
199+
},
200+
TextStyle: &docs.TextStyle{},
201+
Fields: "*",
202+
},
203+
}}
204+
_, err := srv.BatchUpdate(ctx, docID, requests)
183205
if err != nil {
184206
return mcp.NewToolResultError(fmt.Sprintf("Docs API error: %v", err)), nil
185207
}
@@ -221,7 +243,18 @@ func TestableDocsSetParagraphStyle(ctx context.Context, request mcp.CallToolRequ
221243
return mcp.NewToolResultError("at least one paragraph style option must be specified"), nil
222244
}
223245

224-
_, err := srv.BatchUpdate(ctx, docID, nil)
246+
paraStyle := buildParagraphStyle(request.Params.Arguments)
247+
requests := []*docs.Request{{
248+
UpdateParagraphStyle: &docs.UpdateParagraphStyleRequest{
249+
Range: &docs.Range{
250+
StartIndex: startIndex,
251+
EndIndex: endIndex,
252+
},
253+
ParagraphStyle: paraStyle,
254+
Fields: strings.Join(fields, ","),
255+
},
256+
}}
257+
_, err := srv.BatchUpdate(ctx, docID, requests)
225258
if err != nil {
226259
return mcp.NewToolResultError(fmt.Sprintf("Docs API error: %v", err)), nil
227260
}
@@ -281,7 +314,16 @@ func TestableDocsCreateList(ctx context.Context, request mcp.CallToolRequest, de
281314

282315
docID = common.ExtractGoogleResourceID(docID)
283316

284-
_, err := srv.BatchUpdate(ctx, docID, nil)
317+
requests := []*docs.Request{{
318+
CreateParagraphBullets: &docs.CreateParagraphBulletsRequest{
319+
Range: &docs.Range{
320+
StartIndex: startIndex,
321+
EndIndex: endIndex,
322+
},
323+
BulletPreset: bulletPreset,
324+
},
325+
}}
326+
_, err := srv.BatchUpdate(ctx, docID, requests)
285327
if err != nil {
286328
return mcp.NewToolResultError(fmt.Sprintf("Docs API error: %v", err)), nil
287329
}
@@ -322,7 +364,15 @@ func TestableDocsRemoveList(ctx context.Context, request mcp.CallToolRequest, de
322364

323365
docID = common.ExtractGoogleResourceID(docID)
324366

325-
_, err := srv.BatchUpdate(ctx, docID, nil)
367+
requests := []*docs.Request{{
368+
DeleteParagraphBullets: &docs.DeleteParagraphBulletsRequest{
369+
Range: &docs.Range{
370+
StartIndex: startIndex,
371+
EndIndex: endIndex,
372+
},
373+
},
374+
}}
375+
_, err := srv.BatchUpdate(ctx, docID, requests)
326376
if err != nil {
327377
return mcp.NewToolResultError(fmt.Sprintf("Docs API error: %v", err)), nil
328378
}
@@ -336,3 +386,92 @@ func TestableDocsRemoveList(ctx context.Context, request mcp.CallToolRequest, de
336386

337387
return common.MarshalToolResult(result)
338388
}
389+
390+
// buildTextStyle constructs a docs.TextStyle from the provided arguments.
391+
func buildTextStyle(args map[string]any) *docs.TextStyle {
392+
style := &docs.TextStyle{}
393+
if v, ok := args["bold"].(bool); ok {
394+
style.Bold = v
395+
if !v {
396+
style.ForceSendFields = append(style.ForceSendFields, "Bold")
397+
}
398+
}
399+
if v, ok := args["italic"].(bool); ok {
400+
style.Italic = v
401+
if !v {
402+
style.ForceSendFields = append(style.ForceSendFields, "Italic")
403+
}
404+
}
405+
if v, ok := args["underline"].(bool); ok {
406+
style.Underline = v
407+
if !v {
408+
style.ForceSendFields = append(style.ForceSendFields, "Underline")
409+
}
410+
}
411+
if v, ok := args["strikethrough"].(bool); ok {
412+
style.Strikethrough = v
413+
if !v {
414+
style.ForceSendFields = append(style.ForceSendFields, "Strikethrough")
415+
}
416+
}
417+
if v, ok := args["small_caps"].(bool); ok {
418+
style.SmallCaps = v
419+
if !v {
420+
style.ForceSendFields = append(style.ForceSendFields, "SmallCaps")
421+
}
422+
}
423+
if v, ok := args["font_family"].(string); ok && v != "" {
424+
style.WeightedFontFamily = &docs.WeightedFontFamily{FontFamily: v}
425+
}
426+
if v, ok := args["font_size"].(float64); ok && v > 0 {
427+
style.FontSize = &docs.Dimension{Magnitude: v, Unit: "PT"}
428+
}
429+
if v, ok := args["foreground_color"].(string); ok && v != "" {
430+
if r, g, b, err := parseColor(v); err == nil {
431+
style.ForegroundColor = &docs.OptionalColor{
432+
Color: &docs.Color{RgbColor: &docs.RgbColor{Red: r, Green: g, Blue: b}},
433+
}
434+
}
435+
}
436+
if v, ok := args["background_color"].(string); ok && v != "" {
437+
if r, g, b, err := parseColor(v); err == nil {
438+
style.BackgroundColor = &docs.OptionalColor{
439+
Color: &docs.Color{RgbColor: &docs.RgbColor{Red: r, Green: g, Blue: b}},
440+
}
441+
}
442+
}
443+
if v, ok := args["baseline_offset"].(string); ok && v != "" {
444+
style.BaselineOffset = v
445+
}
446+
return style
447+
}
448+
449+
// buildParagraphStyle constructs a docs.ParagraphStyle from the provided arguments.
450+
func buildParagraphStyle(args map[string]any) *docs.ParagraphStyle {
451+
style := &docs.ParagraphStyle{}
452+
if v, ok := args["alignment"].(string); ok && v != "" {
453+
style.Alignment = v
454+
}
455+
if v, ok := args["named_style_type"].(string); ok && v != "" {
456+
style.NamedStyleType = v
457+
}
458+
if v, ok := args["line_spacing"].(float64); ok && v > 0 {
459+
style.LineSpacing = v
460+
}
461+
if v, ok := args["indent_start"].(float64); ok {
462+
style.IndentStart = &docs.Dimension{Magnitude: v, Unit: "PT"}
463+
}
464+
if v, ok := args["indent_end"].(float64); ok {
465+
style.IndentEnd = &docs.Dimension{Magnitude: v, Unit: "PT"}
466+
}
467+
if v, ok := args["indent_first_line"].(float64); ok {
468+
style.IndentFirstLine = &docs.Dimension{Magnitude: v, Unit: "PT"}
469+
}
470+
if v, ok := args["space_above"].(float64); ok {
471+
style.SpaceAbove = &docs.Dimension{Magnitude: v, Unit: "PT"}
472+
}
473+
if v, ok := args["space_below"].(float64); ok {
474+
style.SpaceBelow = &docs.Dimension{Magnitude: v, Unit: "PT"}
475+
}
476+
return style
477+
}

0 commit comments

Comments
 (0)