gdocs-mcp 0.4.1 → 0.4.2
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/dist/index.js
CHANGED
|
@@ -29,7 +29,8 @@ import { CopyDocumentSchema, copyDocument } from './tools/copy-document.js';
|
|
|
29
29
|
import { DeleteDocumentSchema, deleteDocument } from './tools/delete-document.js';
|
|
30
30
|
import { GetCommentsSchema, getComments } from './tools/get-comments.js';
|
|
31
31
|
import { AddCommentSchema, addComment } from './tools/add-comment.js';
|
|
32
|
-
|
|
32
|
+
import { ReadDocumentStructureSchema, readDocumentStructure } from './tools/read-document-structure.js';
|
|
33
|
+
const VERSION = '0.4.2';
|
|
33
34
|
const QUIET = process.env.GDOCS_MCP_QUIET === '1';
|
|
34
35
|
const server = new McpServer({
|
|
35
36
|
name: 'gdocs-mcp',
|
|
@@ -125,6 +126,8 @@ registerTool('copy_document', 'Create a copy of a Google Doc with a new title. I
|
|
|
125
126
|
registerTool('delete_document', 'Move a Google Doc to trash. Requires confirmTitle matching the exact document title.', DeleteDocumentSchema, deleteDocument);
|
|
126
127
|
registerTool('get_comments', 'List comments on a document with author, content, resolved status, and replies.', GetCommentsSchema, getComments);
|
|
127
128
|
registerTool('add_comment', 'Add a comment anchored to specific text in the document. Anchors to first occurrence of quotedText.', AddCommentSchema, addComment);
|
|
129
|
+
// v0.4.2 structure tool
|
|
130
|
+
registerTool('read_document_structure', 'Return the structural outline of a Google Doc: paragraphs with indices, heading levels, text previews, tables with row/col counts, list metadata. Lightweight map for targeted edits.', ReadDocumentStructureSchema, readDocumentStructure);
|
|
128
131
|
async function main() {
|
|
129
132
|
const transport = new StdioServerTransport();
|
|
130
133
|
await server.connect(transport);
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const ReadDocumentStructureSchema: z.ZodObject<{
|
|
3
|
+
documentId: z.ZodString;
|
|
4
|
+
}, z.core.$strip>;
|
|
5
|
+
interface StructureElement {
|
|
6
|
+
type: 'paragraph' | 'table' | 'sectionBreak' | 'tableOfContents';
|
|
7
|
+
startIndex: number;
|
|
8
|
+
endIndex: number;
|
|
9
|
+
style?: string;
|
|
10
|
+
text?: string;
|
|
11
|
+
truncated?: boolean;
|
|
12
|
+
rows?: number;
|
|
13
|
+
cols?: number;
|
|
14
|
+
listId?: string;
|
|
15
|
+
nestingLevel?: number;
|
|
16
|
+
}
|
|
17
|
+
export declare function readDocumentStructure(args: z.infer<typeof ReadDocumentStructureSchema>): Promise<{
|
|
18
|
+
documentId: string;
|
|
19
|
+
title: string;
|
|
20
|
+
totalElements: number;
|
|
21
|
+
elements: StructureElement[];
|
|
22
|
+
_apiMs: number;
|
|
23
|
+
}>;
|
|
24
|
+
export {};
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { google } from 'googleapis';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { getAuthClient } from '../auth/google-auth.js';
|
|
4
|
+
const TEXT_PREVIEW_LENGTH = 80;
|
|
5
|
+
// Field mask: only structural data, no full text styling
|
|
6
|
+
const FIELD_MASK = [
|
|
7
|
+
'title',
|
|
8
|
+
'body.content.startIndex',
|
|
9
|
+
'body.content.endIndex',
|
|
10
|
+
'body.content.paragraph.paragraphStyle.namedStyleType',
|
|
11
|
+
'body.content.paragraph.bullet',
|
|
12
|
+
'body.content.paragraph.elements.startIndex',
|
|
13
|
+
'body.content.paragraph.elements.endIndex',
|
|
14
|
+
'body.content.paragraph.elements.textRun.content',
|
|
15
|
+
'body.content.paragraph.elements.inlineObjectElement',
|
|
16
|
+
'body.content.table.rows',
|
|
17
|
+
'body.content.table.columns',
|
|
18
|
+
'body.content.sectionBreak',
|
|
19
|
+
'body.content.tableOfContents',
|
|
20
|
+
].join(',');
|
|
21
|
+
export const ReadDocumentStructureSchema = z.object({
|
|
22
|
+
documentId: z.string().describe('The Google Doc document ID'),
|
|
23
|
+
});
|
|
24
|
+
export async function readDocumentStructure(args) {
|
|
25
|
+
const auth = getAuthClient();
|
|
26
|
+
const docs = google.docs({ version: 'v1', auth });
|
|
27
|
+
const apiStart = performance.now();
|
|
28
|
+
const res = await docs.documents.get({
|
|
29
|
+
documentId: args.documentId,
|
|
30
|
+
fields: FIELD_MASK,
|
|
31
|
+
});
|
|
32
|
+
const apiMs = Math.round(performance.now() - apiStart);
|
|
33
|
+
const content = res.data.body?.content || [];
|
|
34
|
+
const elements = [];
|
|
35
|
+
for (const element of content) {
|
|
36
|
+
const startIndex = element.startIndex ?? 0;
|
|
37
|
+
const endIndex = element.endIndex ?? 0;
|
|
38
|
+
if (element.sectionBreak !== undefined) {
|
|
39
|
+
elements.push({ type: 'sectionBreak', startIndex, endIndex });
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
if (element.tableOfContents !== undefined) {
|
|
43
|
+
elements.push({ type: 'tableOfContents', startIndex, endIndex });
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if (element.table) {
|
|
47
|
+
elements.push({
|
|
48
|
+
type: 'table',
|
|
49
|
+
startIndex,
|
|
50
|
+
endIndex,
|
|
51
|
+
rows: element.table.rows ?? 0,
|
|
52
|
+
cols: element.table.columns ?? 0,
|
|
53
|
+
});
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
if (element.paragraph) {
|
|
57
|
+
const style = element.paragraph.paragraphStyle?.namedStyleType || 'NORMAL_TEXT';
|
|
58
|
+
// Build text preview from elements
|
|
59
|
+
let hasInlineObject = false;
|
|
60
|
+
let fullText = '';
|
|
61
|
+
for (const el of element.paragraph.elements || []) {
|
|
62
|
+
if (el.inlineObjectElement) {
|
|
63
|
+
hasInlineObject = true;
|
|
64
|
+
}
|
|
65
|
+
if (el.textRun?.content) {
|
|
66
|
+
fullText += el.textRun.content;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
// Clean trailing newline
|
|
70
|
+
fullText = fullText.replace(/\n$/, '');
|
|
71
|
+
// Determine text preview
|
|
72
|
+
let text;
|
|
73
|
+
let truncated;
|
|
74
|
+
if (!fullText && hasInlineObject) {
|
|
75
|
+
text = '[image]';
|
|
76
|
+
truncated = false;
|
|
77
|
+
}
|
|
78
|
+
else if (fullText.length > TEXT_PREVIEW_LENGTH) {
|
|
79
|
+
text = fullText.substring(0, TEXT_PREVIEW_LENGTH) + '...';
|
|
80
|
+
truncated = true;
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
text = fullText;
|
|
84
|
+
truncated = false;
|
|
85
|
+
}
|
|
86
|
+
const entry = {
|
|
87
|
+
type: 'paragraph',
|
|
88
|
+
startIndex,
|
|
89
|
+
endIndex,
|
|
90
|
+
style,
|
|
91
|
+
text,
|
|
92
|
+
truncated,
|
|
93
|
+
};
|
|
94
|
+
// Add list metadata if present
|
|
95
|
+
const bullet = element.paragraph.bullet;
|
|
96
|
+
if (bullet) {
|
|
97
|
+
if (bullet.listId)
|
|
98
|
+
entry.listId = bullet.listId;
|
|
99
|
+
if (bullet.nestingLevel !== undefined && bullet.nestingLevel !== null) {
|
|
100
|
+
entry.nestingLevel = bullet.nestingLevel;
|
|
101
|
+
}
|
|
102
|
+
else if (bullet.listId) {
|
|
103
|
+
entry.nestingLevel = 0;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
elements.push(entry);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return {
|
|
110
|
+
documentId: args.documentId,
|
|
111
|
+
title: res.data.title || 'Untitled',
|
|
112
|
+
totalElements: elements.length,
|
|
113
|
+
elements,
|
|
114
|
+
_apiMs: apiMs,
|
|
115
|
+
};
|
|
116
|
+
}
|
package/package.json
CHANGED