mcp-google-docs 1.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.
@@ -0,0 +1,121 @@
1
+ import { getClients } from '../auth.js';
2
+
3
+ export async function readDocument(documentId: string): Promise<string> {
4
+ const { docs } = await getClients();
5
+
6
+ const response = await docs.documents.get({ documentId });
7
+ const document = response.data;
8
+
9
+ let text = '';
10
+ if (document.body?.content) {
11
+ for (const element of document.body.content) {
12
+ if (element.paragraph?.elements) {
13
+ for (const elem of element.paragraph.elements) {
14
+ if (elem.textRun?.content) {
15
+ text += elem.textRun.content;
16
+ }
17
+ }
18
+ }
19
+ if (element.table) {
20
+ text += '\n[TABLE]\n';
21
+ for (const row of element.table.tableRows || []) {
22
+ const cells: string[] = [];
23
+ for (const cell of row.tableCells || []) {
24
+ let cellText = '';
25
+ for (const content of cell.content || []) {
26
+ if (content.paragraph?.elements) {
27
+ for (const elem of content.paragraph.elements) {
28
+ if (elem.textRun?.content) {
29
+ cellText += elem.textRun.content.trim();
30
+ }
31
+ }
32
+ }
33
+ }
34
+ cells.push(cellText);
35
+ }
36
+ text += cells.join(' | ') + '\n';
37
+ }
38
+ text += '[/TABLE]\n';
39
+ }
40
+ }
41
+ }
42
+
43
+ return text;
44
+ }
45
+
46
+ export async function insertText(
47
+ documentId: string,
48
+ text: string,
49
+ index?: number
50
+ ): Promise<void> {
51
+ const { docs } = await getClients();
52
+
53
+ const insertIndex = index ?? 1;
54
+
55
+ await docs.documents.batchUpdate({
56
+ documentId,
57
+ requestBody: {
58
+ requests: [
59
+ {
60
+ insertText: {
61
+ location: { index: insertIndex },
62
+ text,
63
+ },
64
+ },
65
+ ],
66
+ },
67
+ });
68
+ }
69
+
70
+ export async function replaceText(
71
+ documentId: string,
72
+ searchText: string,
73
+ replaceText: string,
74
+ matchCase: boolean = false
75
+ ): Promise<number> {
76
+ const { docs } = await getClients();
77
+
78
+ const response = await docs.documents.batchUpdate({
79
+ documentId,
80
+ requestBody: {
81
+ requests: [
82
+ {
83
+ replaceAllText: {
84
+ containsText: {
85
+ text: searchText,
86
+ matchCase,
87
+ },
88
+ replaceText,
89
+ },
90
+ },
91
+ ],
92
+ },
93
+ });
94
+
95
+ const replies = response.data.replies || [];
96
+ return replies[0]?.replaceAllText?.occurrencesChanged || 0;
97
+ }
98
+
99
+ export async function appendText(
100
+ documentId: string,
101
+ text: string
102
+ ): Promise<void> {
103
+ const { docs } = await getClients();
104
+
105
+ const doc = await docs.documents.get({ documentId });
106
+ const endIndex = doc.data.body?.content?.slice(-1)[0]?.endIndex || 1;
107
+
108
+ await docs.documents.batchUpdate({
109
+ documentId,
110
+ requestBody: {
111
+ requests: [
112
+ {
113
+ insertText: {
114
+ location: { index: endIndex - 1 },
115
+ text,
116
+ },
117
+ },
118
+ ],
119
+ },
120
+ });
121
+ }
@@ -0,0 +1,80 @@
1
+ import { getClients } from '../auth.js';
2
+ import { DriveFile, MIME_TYPES } from '../types.js';
3
+
4
+ export async function listFiles(
5
+ folderId?: string,
6
+ pageSize: number = 20
7
+ ): Promise<DriveFile[]> {
8
+ const { drive } = await getClients();
9
+
10
+ let query = 'trashed = false';
11
+ if (folderId) {
12
+ query += ` and '${folderId}' in parents`;
13
+ }
14
+
15
+ const response = await drive.files.list({
16
+ q: query,
17
+ pageSize,
18
+ fields: 'files(id, name, mimeType, modifiedTime, webViewLink)',
19
+ orderBy: 'modifiedTime desc',
20
+ });
21
+
22
+ return (response.data.files || []).map(file => ({
23
+ id: file.id!,
24
+ name: file.name!,
25
+ mimeType: file.mimeType!,
26
+ modifiedTime: file.modifiedTime || undefined,
27
+ webViewLink: file.webViewLink || undefined,
28
+ }));
29
+ }
30
+
31
+ export async function searchFiles(
32
+ query: string,
33
+ fileType?: 'document' | 'spreadsheet' | 'folder',
34
+ pageSize: number = 20
35
+ ): Promise<DriveFile[]> {
36
+ const { drive } = await getClients();
37
+
38
+ let searchQuery = `name contains '${query}' and trashed = false`;
39
+
40
+ if (fileType) {
41
+ const mimeType = fileType === 'document'
42
+ ? MIME_TYPES.DOCUMENT
43
+ : fileType === 'spreadsheet'
44
+ ? MIME_TYPES.SPREADSHEET
45
+ : MIME_TYPES.FOLDER;
46
+ searchQuery += ` and mimeType = '${mimeType}'`;
47
+ }
48
+
49
+ const response = await drive.files.list({
50
+ q: searchQuery,
51
+ pageSize,
52
+ fields: 'files(id, name, mimeType, modifiedTime, webViewLink)',
53
+ orderBy: 'modifiedTime desc',
54
+ });
55
+
56
+ return (response.data.files || []).map(file => ({
57
+ id: file.id!,
58
+ name: file.name!,
59
+ mimeType: file.mimeType!,
60
+ modifiedTime: file.modifiedTime || undefined,
61
+ webViewLink: file.webViewLink || undefined,
62
+ }));
63
+ }
64
+
65
+ export async function getFileInfo(fileId: string): Promise<DriveFile> {
66
+ const { drive } = await getClients();
67
+
68
+ const response = await drive.files.get({
69
+ fileId,
70
+ fields: 'id, name, mimeType, modifiedTime, webViewLink',
71
+ });
72
+
73
+ return {
74
+ id: response.data.id!,
75
+ name: response.data.name!,
76
+ mimeType: response.data.mimeType!,
77
+ modifiedTime: response.data.modifiedTime || undefined,
78
+ webViewLink: response.data.webViewLink || undefined,
79
+ };
80
+ }
@@ -0,0 +1,121 @@
1
+ import { getClients } from '../auth.js';
2
+ import { CellUpdate } from '../types.js';
3
+
4
+ export async function readSheet(
5
+ spreadsheetId: string,
6
+ range: string
7
+ ): Promise<any[][]> {
8
+ const { sheets } = await getClients();
9
+
10
+ const response = await sheets.spreadsheets.values.get({
11
+ spreadsheetId,
12
+ range,
13
+ });
14
+
15
+ return response.data.values || [];
16
+ }
17
+
18
+ export async function getSheetInfo(spreadsheetId: string): Promise<{
19
+ title: string;
20
+ sheets: { id: number; title: string; rowCount: number; columnCount: number }[];
21
+ }> {
22
+ const { sheets } = await getClients();
23
+
24
+ const response = await sheets.spreadsheets.get({
25
+ spreadsheetId,
26
+ fields: 'properties.title,sheets.properties',
27
+ });
28
+
29
+ return {
30
+ title: response.data.properties?.title || '',
31
+ sheets: (response.data.sheets || []).map(sheet => ({
32
+ id: sheet.properties?.sheetId || 0,
33
+ title: sheet.properties?.title || '',
34
+ rowCount: sheet.properties?.gridProperties?.rowCount || 0,
35
+ columnCount: sheet.properties?.gridProperties?.columnCount || 0,
36
+ })),
37
+ };
38
+ }
39
+
40
+ export async function updateCell(
41
+ spreadsheetId: string,
42
+ range: string,
43
+ value: string | number | boolean
44
+ ): Promise<void> {
45
+ const { sheets } = await getClients();
46
+
47
+ await sheets.spreadsheets.values.update({
48
+ spreadsheetId,
49
+ range,
50
+ valueInputOption: 'USER_ENTERED',
51
+ requestBody: {
52
+ values: [[value]],
53
+ },
54
+ });
55
+ }
56
+
57
+ export async function updateRange(
58
+ spreadsheetId: string,
59
+ range: string,
60
+ values: any[][]
61
+ ): Promise<void> {
62
+ const { sheets } = await getClients();
63
+
64
+ await sheets.spreadsheets.values.update({
65
+ spreadsheetId,
66
+ range,
67
+ valueInputOption: 'USER_ENTERED',
68
+ requestBody: { values },
69
+ });
70
+ }
71
+
72
+ export async function batchUpdate(
73
+ spreadsheetId: string,
74
+ sheetName: string,
75
+ updates: CellUpdate[]
76
+ ): Promise<number> {
77
+ const { sheets } = await getClients();
78
+
79
+ const data = updates.map(update => ({
80
+ range: `${sheetName}!${columnToLetter(update.col)}${update.row}`,
81
+ values: [[update.value]],
82
+ }));
83
+
84
+ const response = await sheets.spreadsheets.values.batchUpdate({
85
+ spreadsheetId,
86
+ requestBody: {
87
+ valueInputOption: 'USER_ENTERED',
88
+ data,
89
+ },
90
+ });
91
+
92
+ return response.data.totalUpdatedCells || 0;
93
+ }
94
+
95
+ export async function appendRow(
96
+ spreadsheetId: string,
97
+ sheetName: string,
98
+ values: any[]
99
+ ): Promise<void> {
100
+ const { sheets } = await getClients();
101
+
102
+ await sheets.spreadsheets.values.append({
103
+ spreadsheetId,
104
+ range: `${sheetName}!A:A`,
105
+ valueInputOption: 'USER_ENTERED',
106
+ insertDataOption: 'INSERT_ROWS',
107
+ requestBody: {
108
+ values: [values],
109
+ },
110
+ });
111
+ }
112
+
113
+ function columnToLetter(col: number): string {
114
+ let letter = '';
115
+ while (col > 0) {
116
+ const mod = (col - 1) % 26;
117
+ letter = String.fromCharCode(65 + mod) + letter;
118
+ col = Math.floor((col - 1) / 26);
119
+ }
120
+ return letter;
121
+ }
package/src/types.ts ADDED
@@ -0,0 +1,45 @@
1
+ import { google, drive_v3, docs_v1, sheets_v4 } from 'googleapis';
2
+
3
+ export interface GoogleClients {
4
+ drive: drive_v3.Drive;
5
+ docs: docs_v1.Docs;
6
+ sheets: sheets_v4.Sheets;
7
+ }
8
+
9
+ export interface DriveFile {
10
+ id: string;
11
+ name: string;
12
+ mimeType: string;
13
+ modifiedTime?: string;
14
+ webViewLink?: string;
15
+ }
16
+
17
+ export interface SheetRange {
18
+ spreadsheetId: string;
19
+ range: string;
20
+ }
21
+
22
+ export interface CellUpdate {
23
+ row: number;
24
+ col: number;
25
+ value: string | number | boolean;
26
+ }
27
+
28
+ export interface BatchUpdateRequest {
29
+ spreadsheetId: string;
30
+ sheetName: string;
31
+ updates: CellUpdate[];
32
+ }
33
+
34
+ export const SCOPES = [
35
+ 'https://www.googleapis.com/auth/drive.readonly',
36
+ 'https://www.googleapis.com/auth/drive.file',
37
+ 'https://www.googleapis.com/auth/documents',
38
+ 'https://www.googleapis.com/auth/spreadsheets',
39
+ ];
40
+
41
+ export const MIME_TYPES = {
42
+ FOLDER: 'application/vnd.google-apps.folder',
43
+ DOCUMENT: 'application/vnd.google-apps.document',
44
+ SPREADSHEET: 'application/vnd.google-apps.spreadsheet',
45
+ } as const;
package/tsconfig.json ADDED
@@ -0,0 +1,17 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "NodeNext",
5
+ "moduleResolution": "NodeNext",
6
+ "outDir": "./dist",
7
+ "rootDir": "./src",
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true,
11
+ "forceConsistentCasingInFileNames": true,
12
+ "declaration": true,
13
+ "resolveJsonModule": true
14
+ },
15
+ "include": ["src/**/*"],
16
+ "exclude": ["node_modules", "dist"]
17
+ }