google-tools-mcp 1.2.11 → 2.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.
- package/LICENSE +21 -21
- package/README.md +555 -276
- package/dist/cachedToolsList.js +52 -52
- package/dist/googleDocsApiHelpers.js +76 -12
- package/dist/helpers.js +572 -252
- package/dist/httpAuth.js +296 -0
- package/dist/index.js +162 -11
- package/dist/logger.js +87 -87
- package/dist/markdown-transformer/docsToMarkdown.js +222 -21
- package/dist/markdown-transformer/index.js +19 -9
- package/dist/markdown-transformer/markdownToDocs.js +458 -28
- package/dist/readTracker.js +136 -17
- package/dist/sessionContext.js +37 -0
- package/dist/setup.js +187 -9
- package/dist/tools/calendar/getBusy.js +64 -64
- package/dist/tools/calendar/getEvents.js +141 -141
- package/dist/tools/calendar/getFree.js +225 -225
- package/dist/tools/calendar/index.js +19 -19
- package/dist/tools/calendar/listCalendars.js +38 -38
- package/dist/tools/calendar/listRecurringInstances.js +83 -83
- package/dist/tools/calendar/manageCalendar.js +121 -121
- package/dist/tools/calendar/manageEvent.js +258 -258
- package/dist/tools/calendar/moveEvent.js +66 -66
- package/dist/tools/docs/addTab.js +10 -12
- package/dist/tools/docs/appendToGoogleDoc.js +9 -4
- package/dist/tools/docs/deleteRange.js +9 -4
- package/dist/tools/docs/findAndReplace.js +9 -4
- package/dist/tools/docs/formatting/applyParagraphStyle.js +4 -1
- package/dist/tools/docs/formatting/getFormatting.js +171 -171
- package/dist/tools/docs/insertImage.js +15 -2
- package/dist/tools/docs/insertPageBreak.js +4 -1
- package/dist/tools/docs/insertTable.js +4 -1
- package/dist/tools/docs/insertTableWithData.js +4 -1
- package/dist/tools/docs/modifyText.js +14 -4
- package/dist/tools/docs/modifyText.test.js +84 -84
- package/dist/tools/docs/readGoogleDoc.js +71 -11
- package/dist/tools/docs/renameTab.js +12 -14
- package/dist/tools/drafts.js +165 -165
- package/dist/tools/drive/createDocument.js +13 -1
- package/dist/tools/drive/downloadFile.js +268 -268
- package/dist/tools/drive/getFileInfo.js +48 -48
- package/dist/tools/drive/getFilePath.js +50 -50
- package/dist/tools/drive/listDriveFiles.js +112 -112
- package/dist/tools/drive/listSharedDrives.js +52 -52
- package/dist/tools/drive/listSharedWithMe.js +97 -97
- package/dist/tools/drive/uploadFile.js +111 -111
- package/dist/tools/extras/index.js +9 -9
- package/dist/tools/extras/readDriveFile.js +212 -206
- package/dist/tools/extras/readFile.js +84 -84
- package/dist/tools/extras/searchFileContents.js +81 -81
- package/dist/tools/forms/batchUpdateForm.js +81 -81
- package/dist/tools/forms/createForm.js +54 -54
- package/dist/tools/forms/getForm.js +118 -118
- package/dist/tools/forms/getFormResponse.js +45 -45
- package/dist/tools/forms/index.js +15 -15
- package/dist/tools/forms/listFormResponses.js +56 -56
- package/dist/tools/forms/setPublishSettings.js +59 -59
- package/dist/tools/gmail/drafts.js +165 -165
- package/dist/tools/gmail/labels.js +54 -83
- package/dist/tools/gmail/messages.js +444 -441
- package/dist/tools/gmail/settings.js +358 -528
- package/dist/tools/gmail/threads.js +286 -164
- package/dist/tools/index.js +511 -473
- package/dist/tools/labels.js +103 -103
- package/dist/tools/legacyAliases.js +426 -0
- package/dist/tools/maps/directions.js +72 -0
- package/dist/tools/maps/geocode.js +16 -0
- package/dist/tools/maps/index.js +15 -0
- package/dist/tools/maps/mapsClient.js +83 -0
- package/dist/tools/maps/placeDetails.js +16 -0
- package/dist/tools/maps/reverseGeocode.js +19 -0
- package/dist/tools/maps/searchNearby.js +54 -0
- package/dist/tools/maps/searchPlaces.js +24 -0
- package/dist/tools/messages.js +448 -448
- package/dist/tools/settings.js +528 -528
- package/dist/tools/slides/createPresentation.js +104 -104
- package/dist/tools/slides/createShape.js +92 -92
- package/dist/tools/slides/createTextBox.js +82 -82
- package/dist/tools/slides/deleteSlide.js +30 -30
- package/dist/tools/slides/duplicateSlide.js +37 -37
- package/dist/tools/slides/exportThumbnail.js +42 -42
- package/dist/tools/slides/formatParagraph.js +72 -72
- package/dist/tools/slides/formatText.js +84 -84
- package/dist/tools/slides/getPresentation.js +87 -87
- package/dist/tools/slides/index.js +33 -33
- package/dist/tools/slides/reorderSlides.js +41 -41
- package/dist/tools/slides/replaceAllText.js +46 -46
- package/dist/tools/slides/setBackground.js +58 -58
- package/dist/tools/slides/speakerNotes.js +102 -102
- package/dist/tools/slides/styleShape.js +111 -111
- package/dist/tools/slides/updatePresentation.js +128 -128
- package/dist/tools/threads.js +145 -145
- package/dist/tools/utils/appendMarkdownToGoogleDoc.js +31 -7
- package/dist/tools/utils/replaceDocumentWithMarkdown.js +72 -9
- package/dist/updateCheck.js +202 -0
- package/dist/workspace.js +122 -0
- package/package.json +82 -80
|
@@ -1,81 +1,81 @@
|
|
|
1
|
-
import { UserError } from 'fastmcp';
|
|
2
|
-
import { z } from 'zod';
|
|
3
|
-
import { getDriveClient } from '../../clients.js';
|
|
4
|
-
import { isDocx, isPdf, downloadBuffer, extractText } from './readFile.js';
|
|
5
|
-
|
|
6
|
-
export function register(server) {
|
|
7
|
-
server.addTool({
|
|
8
|
-
name: 'searchFileContents',
|
|
9
|
-
description:
|
|
10
|
-
'Search Google Drive for files whose content or name matches a query. ' +
|
|
11
|
-
'For .docx and .pdf files, extracts and returns matching text snippets. ' +
|
|
12
|
-
'Use this to find information inside Office documents and PDFs.',
|
|
13
|
-
parameters: z.object({
|
|
14
|
-
query: z.string().describe('Text to search for'),
|
|
15
|
-
maxResults: z
|
|
16
|
-
.number()
|
|
17
|
-
.optional()
|
|
18
|
-
.default(5)
|
|
19
|
-
.describe('Max number of files to search (default 5)'),
|
|
20
|
-
folderId: z
|
|
21
|
-
.string()
|
|
22
|
-
.optional()
|
|
23
|
-
.describe('Restrict search to a specific folder ID'),
|
|
24
|
-
}),
|
|
25
|
-
execute: async ({ query, maxResults = 5, folderId }, { log }) => {
|
|
26
|
-
const drive = await getDriveClient();
|
|
27
|
-
log.info(`Searching file contents for: "${query}"`);
|
|
28
|
-
|
|
29
|
-
try {
|
|
30
|
-
const escaped = query.replace(/\\/g, '\\\\').replace(/'/g, "\\'");
|
|
31
|
-
let q = `fullText contains '${escaped}' and trashed = false`;
|
|
32
|
-
if (folderId) q += ` and '${folderId}' in parents`;
|
|
33
|
-
|
|
34
|
-
const response = await drive.files.list({
|
|
35
|
-
q,
|
|
36
|
-
pageSize: maxResults,
|
|
37
|
-
fields: 'files(id,name,mimeType)',
|
|
38
|
-
supportsAllDrives: true,
|
|
39
|
-
includeItemsFromAllDrives: true,
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
const files = response.data.files || [];
|
|
43
|
-
if (files.length === 0) return 'No files found matching that query.';
|
|
44
|
-
|
|
45
|
-
const results = [];
|
|
46
|
-
|
|
47
|
-
for (const file of files) {
|
|
48
|
-
const canRead = isDocx(file.mimeType, file.name) || isPdf(file.mimeType, file.name);
|
|
49
|
-
|
|
50
|
-
if (canRead) {
|
|
51
|
-
try {
|
|
52
|
-
const buffer = await downloadBuffer(drive, file.id);
|
|
53
|
-
const text = await extractText(buffer, file.mimeType, file.name);
|
|
54
|
-
|
|
55
|
-
const lowerText = text.toLowerCase();
|
|
56
|
-
const lowerQuery = query.toLowerCase();
|
|
57
|
-
const idx = lowerText.indexOf(lowerQuery);
|
|
58
|
-
const snippet =
|
|
59
|
-
idx !== -1
|
|
60
|
-
? '...' + text.slice(Math.max(0, idx - 150), idx + 300).trim() + '...'
|
|
61
|
-
: text.slice(0, 400).trim() + '...';
|
|
62
|
-
|
|
63
|
-
results.push(`**${file.name}** (ID: \`${file.id}\`)\n\n${snippet}`);
|
|
64
|
-
} catch (e) {
|
|
65
|
-
results.push(`**${file.name}** (ID: \`${file.id}\`)\n\n[Could not read: ${e.message}]`);
|
|
66
|
-
}
|
|
67
|
-
} else {
|
|
68
|
-
results.push(`**${file.name}** (ID: \`${file.id}\`, type: ${file.mimeType})`);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
return results.join('\n\n---\n\n');
|
|
73
|
-
} catch (error) {
|
|
74
|
-
log.error(`Error searching files: ${error.message}`);
|
|
75
|
-
if (error.code === 403)
|
|
76
|
-
throw new UserError('Permission denied. Check Drive access.');
|
|
77
|
-
throw new UserError(`Failed to search files: ${error.message}`);
|
|
78
|
-
}
|
|
79
|
-
},
|
|
80
|
-
});
|
|
81
|
-
}
|
|
1
|
+
import { UserError } from 'fastmcp';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { getDriveClient } from '../../clients.js';
|
|
4
|
+
import { isDocx, isPdf, downloadBuffer, extractText } from './readFile.js';
|
|
5
|
+
|
|
6
|
+
export function register(server) {
|
|
7
|
+
server.addTool({
|
|
8
|
+
name: 'searchFileContents',
|
|
9
|
+
description:
|
|
10
|
+
'Search Google Drive for files whose content or name matches a query. ' +
|
|
11
|
+
'For .docx and .pdf files, extracts and returns matching text snippets. ' +
|
|
12
|
+
'Use this to find information inside Office documents and PDFs.',
|
|
13
|
+
parameters: z.object({
|
|
14
|
+
query: z.string().describe('Text to search for'),
|
|
15
|
+
maxResults: z
|
|
16
|
+
.number()
|
|
17
|
+
.optional()
|
|
18
|
+
.default(5)
|
|
19
|
+
.describe('Max number of files to search (default 5)'),
|
|
20
|
+
folderId: z
|
|
21
|
+
.string()
|
|
22
|
+
.optional()
|
|
23
|
+
.describe('Restrict search to a specific folder ID'),
|
|
24
|
+
}),
|
|
25
|
+
execute: async ({ query, maxResults = 5, folderId }, { log }) => {
|
|
26
|
+
const drive = await getDriveClient();
|
|
27
|
+
log.info(`Searching file contents for: "${query}"`);
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
const escaped = query.replace(/\\/g, '\\\\').replace(/'/g, "\\'");
|
|
31
|
+
let q = `fullText contains '${escaped}' and trashed = false`;
|
|
32
|
+
if (folderId) q += ` and '${folderId}' in parents`;
|
|
33
|
+
|
|
34
|
+
const response = await drive.files.list({
|
|
35
|
+
q,
|
|
36
|
+
pageSize: maxResults,
|
|
37
|
+
fields: 'files(id,name,mimeType)',
|
|
38
|
+
supportsAllDrives: true,
|
|
39
|
+
includeItemsFromAllDrives: true,
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const files = response.data.files || [];
|
|
43
|
+
if (files.length === 0) return 'No files found matching that query.';
|
|
44
|
+
|
|
45
|
+
const results = [];
|
|
46
|
+
|
|
47
|
+
for (const file of files) {
|
|
48
|
+
const canRead = isDocx(file.mimeType, file.name) || isPdf(file.mimeType, file.name);
|
|
49
|
+
|
|
50
|
+
if (canRead) {
|
|
51
|
+
try {
|
|
52
|
+
const buffer = await downloadBuffer(drive, file.id);
|
|
53
|
+
const text = await extractText(buffer, file.mimeType, file.name);
|
|
54
|
+
|
|
55
|
+
const lowerText = text.toLowerCase();
|
|
56
|
+
const lowerQuery = query.toLowerCase();
|
|
57
|
+
const idx = lowerText.indexOf(lowerQuery);
|
|
58
|
+
const snippet =
|
|
59
|
+
idx !== -1
|
|
60
|
+
? '...' + text.slice(Math.max(0, idx - 150), idx + 300).trim() + '...'
|
|
61
|
+
: text.slice(0, 400).trim() + '...';
|
|
62
|
+
|
|
63
|
+
results.push(`**${file.name}** (ID: \`${file.id}\`)\n\n${snippet}`);
|
|
64
|
+
} catch (e) {
|
|
65
|
+
results.push(`**${file.name}** (ID: \`${file.id}\`)\n\n[Could not read: ${e.message}]`);
|
|
66
|
+
}
|
|
67
|
+
} else {
|
|
68
|
+
results.push(`**${file.name}** (ID: \`${file.id}\`, type: ${file.mimeType})`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return results.join('\n\n---\n\n');
|
|
73
|
+
} catch (error) {
|
|
74
|
+
log.error(`Error searching files: ${error.message}`);
|
|
75
|
+
if (error.code === 403)
|
|
76
|
+
throw new UserError('Permission denied. Check Drive access.');
|
|
77
|
+
throw new UserError(`Failed to search files: ${error.message}`);
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
}
|
|
@@ -1,81 +1,81 @@
|
|
|
1
|
-
import { UserError } from 'fastmcp';
|
|
2
|
-
import { z } from 'zod';
|
|
3
|
-
import { getFormsClient } from '../../clients.js';
|
|
4
|
-
|
|
5
|
-
export function register(server) {
|
|
6
|
-
server.addTool({
|
|
7
|
-
name: 'batch_update_form',
|
|
8
|
-
description:
|
|
9
|
-
'Applies one or more updates to a Google Form. Supports creating, updating, deleting, and moving items, as well as updating form info and settings. ' +
|
|
10
|
-
'Each request in the array should be an object with exactly one key: createItem, updateItem, deleteItem, moveItem, updateFormInfo, or updateSettings. ' +
|
|
11
|
-
'See the Google Forms API batchUpdate documentation for the full request schema.',
|
|
12
|
-
parameters: z.object({
|
|
13
|
-
formId: z.string().describe('The ID of the Google Form to update'),
|
|
14
|
-
requests: z
|
|
15
|
-
.array(z.record(z.any()))
|
|
16
|
-
.min(1)
|
|
17
|
-
.describe(
|
|
18
|
-
'Array of update requests. Each object should have one key: createItem, updateItem, deleteItem, moveItem, updateFormInfo, or updateSettings',
|
|
19
|
-
),
|
|
20
|
-
}),
|
|
21
|
-
execute: async (args, { log }) => {
|
|
22
|
-
const forms = await getFormsClient();
|
|
23
|
-
log.info(`Batch updating form ${args.formId} with ${args.requests.length} request(s)`);
|
|
24
|
-
|
|
25
|
-
const validKeys = new Set([
|
|
26
|
-
'createItem',
|
|
27
|
-
'updateItem',
|
|
28
|
-
'deleteItem',
|
|
29
|
-
'moveItem',
|
|
30
|
-
'updateFormInfo',
|
|
31
|
-
'updateSettings',
|
|
32
|
-
]);
|
|
33
|
-
|
|
34
|
-
for (const req of args.requests) {
|
|
35
|
-
const keys = Object.keys(req);
|
|
36
|
-
if (keys.length !== 1 || !validKeys.has(keys[0])) {
|
|
37
|
-
throw new UserError(
|
|
38
|
-
`Invalid request object. Each request must have exactly one key from: ${[...validKeys].join(', ')}. Got: ${keys.join(', ')}`,
|
|
39
|
-
);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
try {
|
|
44
|
-
const response = await forms.forms.batchUpdate({
|
|
45
|
-
formId: args.formId,
|
|
46
|
-
requestBody: { requests: args.requests },
|
|
47
|
-
});
|
|
48
|
-
const data = response.data;
|
|
49
|
-
const replies = data.replies || [];
|
|
50
|
-
|
|
51
|
-
const result = {
|
|
52
|
-
url: `https://docs.google.com/forms/d/${args.formId}/edit`,
|
|
53
|
-
requestsApplied: args.requests.length,
|
|
54
|
-
repliesReceived: replies.length,
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
const createdItems = [];
|
|
58
|
-
for (const reply of replies) {
|
|
59
|
-
if (reply.createItem) {
|
|
60
|
-
const ci = reply.createItem;
|
|
61
|
-
createdItems.push({
|
|
62
|
-
itemId: ci.itemId,
|
|
63
|
-
questionId: ci.questionId?.[0] || null,
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
if (createdItems.length > 0) {
|
|
68
|
-
result.createdItems = createdItems;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
return JSON.stringify(result, null, 2);
|
|
72
|
-
} catch (error) {
|
|
73
|
-
log.error(`Error batch updating form: ${error.message || error}`);
|
|
74
|
-
if (error.code === 401)
|
|
75
|
-
throw new UserError('Authentication failed. Try logging out and re-authenticating.');
|
|
76
|
-
if (error.code === 404) throw new UserError(`Form not found: ${args.formId}`);
|
|
77
|
-
throw new UserError(`Failed to batch update form: ${error.message || 'Unknown error'}`);
|
|
78
|
-
}
|
|
79
|
-
},
|
|
80
|
-
});
|
|
81
|
-
}
|
|
1
|
+
import { UserError } from 'fastmcp';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { getFormsClient } from '../../clients.js';
|
|
4
|
+
|
|
5
|
+
export function register(server) {
|
|
6
|
+
server.addTool({
|
|
7
|
+
name: 'batch_update_form',
|
|
8
|
+
description:
|
|
9
|
+
'Applies one or more updates to a Google Form. Supports creating, updating, deleting, and moving items, as well as updating form info and settings. ' +
|
|
10
|
+
'Each request in the array should be an object with exactly one key: createItem, updateItem, deleteItem, moveItem, updateFormInfo, or updateSettings. ' +
|
|
11
|
+
'See the Google Forms API batchUpdate documentation for the full request schema.',
|
|
12
|
+
parameters: z.object({
|
|
13
|
+
formId: z.string().describe('The ID of the Google Form to update'),
|
|
14
|
+
requests: z
|
|
15
|
+
.array(z.record(z.any()))
|
|
16
|
+
.min(1)
|
|
17
|
+
.describe(
|
|
18
|
+
'Array of update requests. Each object should have one key: createItem, updateItem, deleteItem, moveItem, updateFormInfo, or updateSettings',
|
|
19
|
+
),
|
|
20
|
+
}),
|
|
21
|
+
execute: async (args, { log }) => {
|
|
22
|
+
const forms = await getFormsClient();
|
|
23
|
+
log.info(`Batch updating form ${args.formId} with ${args.requests.length} request(s)`);
|
|
24
|
+
|
|
25
|
+
const validKeys = new Set([
|
|
26
|
+
'createItem',
|
|
27
|
+
'updateItem',
|
|
28
|
+
'deleteItem',
|
|
29
|
+
'moveItem',
|
|
30
|
+
'updateFormInfo',
|
|
31
|
+
'updateSettings',
|
|
32
|
+
]);
|
|
33
|
+
|
|
34
|
+
for (const req of args.requests) {
|
|
35
|
+
const keys = Object.keys(req);
|
|
36
|
+
if (keys.length !== 1 || !validKeys.has(keys[0])) {
|
|
37
|
+
throw new UserError(
|
|
38
|
+
`Invalid request object. Each request must have exactly one key from: ${[...validKeys].join(', ')}. Got: ${keys.join(', ')}`,
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
const response = await forms.forms.batchUpdate({
|
|
45
|
+
formId: args.formId,
|
|
46
|
+
requestBody: { requests: args.requests },
|
|
47
|
+
});
|
|
48
|
+
const data = response.data;
|
|
49
|
+
const replies = data.replies || [];
|
|
50
|
+
|
|
51
|
+
const result = {
|
|
52
|
+
url: `https://docs.google.com/forms/d/${args.formId}/edit`,
|
|
53
|
+
requestsApplied: args.requests.length,
|
|
54
|
+
repliesReceived: replies.length,
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const createdItems = [];
|
|
58
|
+
for (const reply of replies) {
|
|
59
|
+
if (reply.createItem) {
|
|
60
|
+
const ci = reply.createItem;
|
|
61
|
+
createdItems.push({
|
|
62
|
+
itemId: ci.itemId,
|
|
63
|
+
questionId: ci.questionId?.[0] || null,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (createdItems.length > 0) {
|
|
68
|
+
result.createdItems = createdItems;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return JSON.stringify(result, null, 2);
|
|
72
|
+
} catch (error) {
|
|
73
|
+
log.error(`Error batch updating form: ${error.message || error}`);
|
|
74
|
+
if (error.code === 401)
|
|
75
|
+
throw new UserError('Authentication failed. Try logging out and re-authenticating.');
|
|
76
|
+
if (error.code === 404) throw new UserError(`Form not found: ${args.formId}`);
|
|
77
|
+
throw new UserError(`Failed to batch update form: ${error.message || 'Unknown error'}`);
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
}
|
|
@@ -1,54 +1,54 @@
|
|
|
1
|
-
import { UserError } from 'fastmcp';
|
|
2
|
-
import { z } from 'zod';
|
|
3
|
-
import { getFormsClient } from '../../clients.js';
|
|
4
|
-
|
|
5
|
-
export function register(server) {
|
|
6
|
-
server.addTool({
|
|
7
|
-
name: 'create_form',
|
|
8
|
-
description:
|
|
9
|
-
'Creates a new Google Form with a title and optional description. Returns the form ID, edit URL, and responder URL.',
|
|
10
|
-
parameters: z.object({
|
|
11
|
-
title: z.string().describe('The title of the form (shown to respondents)'),
|
|
12
|
-
description: z.string().optional().describe('A description displayed at the top of the form'),
|
|
13
|
-
documentTitle: z
|
|
14
|
-
.string()
|
|
15
|
-
.optional()
|
|
16
|
-
.describe('The document title (shown in Drive). Defaults to the form title if not provided'),
|
|
17
|
-
}),
|
|
18
|
-
execute: async (args, { log }) => {
|
|
19
|
-
const forms = await getFormsClient();
|
|
20
|
-
log.info(`Creating form: ${args.title}`);
|
|
21
|
-
|
|
22
|
-
const body = {
|
|
23
|
-
info: {
|
|
24
|
-
title: args.title,
|
|
25
|
-
documentTitle: args.documentTitle || args.title,
|
|
26
|
-
},
|
|
27
|
-
};
|
|
28
|
-
if (args.description) {
|
|
29
|
-
body.info.description = args.description;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
try {
|
|
33
|
-
const response = await forms.forms.create({ requestBody: body });
|
|
34
|
-
const form = response.data;
|
|
35
|
-
|
|
36
|
-
return JSON.stringify(
|
|
37
|
-
{
|
|
38
|
-
formId: form.formId,
|
|
39
|
-
title: form.info?.title,
|
|
40
|
-
editUrl: `https://docs.google.com/forms/d/${form.formId}/edit`,
|
|
41
|
-
responderUrl: form.responderUri || `https://docs.google.com/forms/d/${form.formId}/viewform`,
|
|
42
|
-
},
|
|
43
|
-
null,
|
|
44
|
-
2,
|
|
45
|
-
);
|
|
46
|
-
} catch (error) {
|
|
47
|
-
log.error(`Error creating form: ${error.message || error}`);
|
|
48
|
-
if (error.code === 401)
|
|
49
|
-
throw new UserError('Authentication failed. Try logging out and re-authenticating.');
|
|
50
|
-
throw new UserError(`Failed to create form: ${error.message || 'Unknown error'}`);
|
|
51
|
-
}
|
|
52
|
-
},
|
|
53
|
-
});
|
|
54
|
-
}
|
|
1
|
+
import { UserError } from 'fastmcp';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { getFormsClient } from '../../clients.js';
|
|
4
|
+
|
|
5
|
+
export function register(server) {
|
|
6
|
+
server.addTool({
|
|
7
|
+
name: 'create_form',
|
|
8
|
+
description:
|
|
9
|
+
'Creates a new Google Form with a title and optional description. Returns the form ID, edit URL, and responder URL.',
|
|
10
|
+
parameters: z.object({
|
|
11
|
+
title: z.string().describe('The title of the form (shown to respondents)'),
|
|
12
|
+
description: z.string().optional().describe('A description displayed at the top of the form'),
|
|
13
|
+
documentTitle: z
|
|
14
|
+
.string()
|
|
15
|
+
.optional()
|
|
16
|
+
.describe('The document title (shown in Drive). Defaults to the form title if not provided'),
|
|
17
|
+
}),
|
|
18
|
+
execute: async (args, { log }) => {
|
|
19
|
+
const forms = await getFormsClient();
|
|
20
|
+
log.info(`Creating form: ${args.title}`);
|
|
21
|
+
|
|
22
|
+
const body = {
|
|
23
|
+
info: {
|
|
24
|
+
title: args.title,
|
|
25
|
+
documentTitle: args.documentTitle || args.title,
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
if (args.description) {
|
|
29
|
+
body.info.description = args.description;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
const response = await forms.forms.create({ requestBody: body });
|
|
34
|
+
const form = response.data;
|
|
35
|
+
|
|
36
|
+
return JSON.stringify(
|
|
37
|
+
{
|
|
38
|
+
formId: form.formId,
|
|
39
|
+
title: form.info?.title,
|
|
40
|
+
editUrl: `https://docs.google.com/forms/d/${form.formId}/edit`,
|
|
41
|
+
responderUrl: form.responderUri || `https://docs.google.com/forms/d/${form.formId}/viewform`,
|
|
42
|
+
},
|
|
43
|
+
null,
|
|
44
|
+
2,
|
|
45
|
+
);
|
|
46
|
+
} catch (error) {
|
|
47
|
+
log.error(`Error creating form: ${error.message || error}`);
|
|
48
|
+
if (error.code === 401)
|
|
49
|
+
throw new UserError('Authentication failed. Try logging out and re-authenticating.');
|
|
50
|
+
throw new UserError(`Failed to create form: ${error.message || 'Unknown error'}`);
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
}
|