gmcp 0.2.1 → 0.4.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/README.md +23 -8
- package/dist/auth-cli.js +19 -24
- package/dist/cli.js +42 -47
- package/dist/index.js +190 -93
- package/package.json +9 -6
package/README.md
CHANGED
|
@@ -10,7 +10,6 @@
|
|
|
10
10
|
<p align="center">
|
|
11
11
|
<a href="https://www.npmjs.com/package/gmcp" rel="nofollow"><img src="https://img.shields.io/npm/v/gmcp.svg" alt="npm"></a>
|
|
12
12
|
<a href="https://hub.docker.com/r/johnie/gmcp" rel="nofollow"><img src="https://img.shields.io/docker/pulls/johnie/gmcp" alt="Docker Pulls"></a>
|
|
13
|
-
<a href="https://opensource.org/licenses/MIT" rel="nofollow"><img src="https://img.shields.io/github/license/johnie/gmcp" alt="License"></a>
|
|
14
13
|
<a href="https://github.com/johnie/gmcp" rel="nofollow"><img src="https://img.shields.io/github/stars/johnie/gmcp" alt="stars"></a>
|
|
15
14
|
</p>
|
|
16
15
|
|
|
@@ -61,10 +60,10 @@ bun install
|
|
|
61
60
|
|
|
62
61
|
```bash
|
|
63
62
|
# If installed globally
|
|
64
|
-
gmcp
|
|
63
|
+
gmcp auth
|
|
65
64
|
|
|
66
65
|
# Or with npx
|
|
67
|
-
npx gmcp
|
|
66
|
+
npx gmcp auth
|
|
68
67
|
|
|
69
68
|
# Or from source
|
|
70
69
|
bun run auth
|
|
@@ -76,10 +75,11 @@ Follow the prompts to authorize. The browser will show "connection refused" afte
|
|
|
76
75
|
|
|
77
76
|
```bash
|
|
78
77
|
# Globally installed
|
|
79
|
-
gmcp
|
|
78
|
+
gmcp start
|
|
79
|
+
# or just: gmcp
|
|
80
80
|
|
|
81
81
|
# With npx
|
|
82
|
-
npx gmcp
|
|
82
|
+
npx gmcp start
|
|
83
83
|
|
|
84
84
|
# From source
|
|
85
85
|
bun run start
|
|
@@ -135,7 +135,7 @@ Add to `~/Library/Application Support/Claude/claude_desktop_config.json`:
|
|
|
135
135
|
"mcpServers": {
|
|
136
136
|
"gmcp": {
|
|
137
137
|
"command": "bun",
|
|
138
|
-
"args": ["run", "/path/to/gmcp/src/
|
|
138
|
+
"args": ["run", "/path/to/gmcp/src/cli.ts"],
|
|
139
139
|
"env": {
|
|
140
140
|
"GOOGLE_CREDENTIALS_PATH": "/path/to/credentials.json",
|
|
141
141
|
"GOOGLE_TOKEN_PATH": "/path/to/token.json",
|
|
@@ -148,7 +148,7 @@ Add to `~/Library/Application Support/Claude/claude_desktop_config.json`:
|
|
|
148
148
|
|
|
149
149
|
## Tools
|
|
150
150
|
|
|
151
|
-
### Gmail (
|
|
151
|
+
### Gmail (16 tools)
|
|
152
152
|
|
|
153
153
|
| Tool | Description |
|
|
154
154
|
|------|-------------|
|
|
@@ -160,6 +160,7 @@ Add to `~/Library/Application Support/Claude/claude_desktop_config.json`:
|
|
|
160
160
|
| `gmcp_gmail_send_email` | Send new email |
|
|
161
161
|
| `gmcp_gmail_reply` | Reply to email in thread |
|
|
162
162
|
| `gmcp_gmail_create_draft` | Create draft message |
|
|
163
|
+
| `gmcp_gmail_delete_email` | Permanently delete email (bypasses trash) |
|
|
163
164
|
| `gmcp_gmail_list_labels` | List all labels |
|
|
164
165
|
| `gmcp_gmail_get_label` | Get label details |
|
|
165
166
|
| `gmcp_gmail_create_label` | Create custom label |
|
|
@@ -193,7 +194,7 @@ Add to `~/Library/Application Support/Claude/claude_desktop_config.json`:
|
|
|
193
194
|
|-------|--------|
|
|
194
195
|
| `gmail.readonly` | Read emails and labels |
|
|
195
196
|
| `gmail.send` | Send emails |
|
|
196
|
-
| `gmail.modify` | Read, modify labels |
|
|
197
|
+
| `gmail.modify` | Read, modify labels, delete emails |
|
|
197
198
|
| `gmail.labels` | Manage labels |
|
|
198
199
|
| `gmail.compose` | Create drafts and send |
|
|
199
200
|
| `calendar.readonly` | Read calendars and events |
|
|
@@ -210,6 +211,20 @@ GOOGLE_SCOPES=gmail.readonly,calendar.readonly
|
|
|
210
211
|
GOOGLE_SCOPES=gmail.readonly,gmail.modify,gmail.send,calendar.events
|
|
211
212
|
```
|
|
212
213
|
|
|
214
|
+
## CLI Reference
|
|
215
|
+
|
|
216
|
+
```
|
|
217
|
+
gmcp [command]
|
|
218
|
+
|
|
219
|
+
Commands:
|
|
220
|
+
start Start MCP server (default)
|
|
221
|
+
auth Run OAuth2 authentication flow
|
|
222
|
+
|
|
223
|
+
Options:
|
|
224
|
+
--help, -h Show usage
|
|
225
|
+
--version, -v Show version
|
|
226
|
+
```
|
|
227
|
+
|
|
213
228
|
## Testing
|
|
214
229
|
|
|
215
230
|
```bash
|
package/dist/auth-cli.js
CHANGED
|
@@ -10,19 +10,8 @@ import {
|
|
|
10
10
|
import"./shared/chunk-3s189drz.js";
|
|
11
11
|
|
|
12
12
|
// src/auth-cli.ts
|
|
13
|
-
import {
|
|
14
|
-
|
|
15
|
-
const rl = createInterface({
|
|
16
|
-
input: process.stdin,
|
|
17
|
-
output: process.stdout
|
|
18
|
-
});
|
|
19
|
-
return new Promise((resolve) => {
|
|
20
|
-
rl.on("line", (line) => {
|
|
21
|
-
rl.close();
|
|
22
|
-
resolve(line.trim());
|
|
23
|
-
});
|
|
24
|
-
});
|
|
25
|
-
}
|
|
13
|
+
import { input } from "@inquirer/prompts";
|
|
14
|
+
import kleur from "kleur";
|
|
26
15
|
async function runAuth() {
|
|
27
16
|
console.log(`GMCP Server - OAuth2 Authentication
|
|
28
17
|
`);
|
|
@@ -39,7 +28,7 @@ async function runAuth() {
|
|
|
39
28
|
========================================`);
|
|
40
29
|
console.log("STEP 1: Visit this URL to authorize:");
|
|
41
30
|
console.log("========================================");
|
|
42
|
-
console.log(
|
|
31
|
+
console.log(kleur.cyan(authUrl));
|
|
43
32
|
console.log(`
|
|
44
33
|
========================================`);
|
|
45
34
|
console.log("STEP 2: After authorizing:");
|
|
@@ -54,24 +43,30 @@ Copy the entire code after "code=" (the long string before "&scope")`);
|
|
|
54
43
|
console.log(`
|
|
55
44
|
========================================`);
|
|
56
45
|
console.log("STEP 3: Paste the authorization code below:");
|
|
57
|
-
console.log(
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
46
|
+
console.log(`========================================
|
|
47
|
+
`);
|
|
48
|
+
const code = await input({
|
|
49
|
+
message: "Authorization code:",
|
|
50
|
+
required: true,
|
|
51
|
+
validate: (value) => {
|
|
52
|
+
if (value.length < 10) {
|
|
53
|
+
return "Authorization code appears too short";
|
|
54
|
+
}
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
});
|
|
63
58
|
console.log(`
|
|
64
59
|
Exchanging authorization code for tokens...`);
|
|
65
60
|
try {
|
|
66
61
|
const tokens = await getTokensFromCode(oauth2Client, code);
|
|
67
62
|
await saveTokens(tokenPath, tokens);
|
|
68
|
-
console.log(
|
|
69
|
-
Success! Tokens saved to
|
|
63
|
+
console.log(kleur.green(`
|
|
64
|
+
Success! Tokens saved to ${tokenPath}`));
|
|
70
65
|
console.log(`
|
|
71
66
|
You can now run the MCP server with: npx gmcp`);
|
|
72
67
|
} catch (error) {
|
|
73
|
-
console.error(
|
|
74
|
-
Error exchanging code for tokens:`);
|
|
68
|
+
console.error(kleur.red(`
|
|
69
|
+
Error exchanging code for tokens:`));
|
|
75
70
|
console.error(error);
|
|
76
71
|
process.exit(1);
|
|
77
72
|
}
|
package/dist/cli.js
CHANGED
|
@@ -7,56 +7,51 @@ import {
|
|
|
7
7
|
} from "./shared/chunk-3s189drz.js";
|
|
8
8
|
|
|
9
9
|
// src/cli.ts
|
|
10
|
-
|
|
11
|
-
Usage: gmcp [command]
|
|
10
|
+
import { run } from "@stricli/core";
|
|
12
11
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
auth Run OAuth2 authentication flow
|
|
12
|
+
// src/cli/app.ts
|
|
13
|
+
import { buildApplication, buildRouteMap } from "@stricli/core";
|
|
16
14
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
15
|
+
// src/cli/commands/auth.ts
|
|
16
|
+
import { buildCommand } from "@stricli/core";
|
|
17
|
+
var authCommand = buildCommand({
|
|
18
|
+
func: async () => {
|
|
19
|
+
const { runAuth } = await import("./auth-cli.js");
|
|
20
|
+
await runAuth();
|
|
21
|
+
},
|
|
22
|
+
parameters: {},
|
|
23
|
+
docs: { brief: "Run OAuth2 authentication flow" }
|
|
24
|
+
});
|
|
20
25
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
`.trim();
|
|
26
|
-
function showHelp() {
|
|
27
|
-
console.log(USAGE);
|
|
28
|
-
}
|
|
29
|
-
async function showVersion() {
|
|
30
|
-
const version = await getVersion();
|
|
31
|
-
console.log(`gmcp v${version}`);
|
|
32
|
-
}
|
|
33
|
-
async function main() {
|
|
34
|
-
const args = process.argv.slice(2);
|
|
35
|
-
const command = args[0];
|
|
36
|
-
if (command === "--help" || command === "-h") {
|
|
37
|
-
showHelp();
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
if (command === "--version" || command === "-v") {
|
|
41
|
-
await showVersion();
|
|
42
|
-
return;
|
|
43
|
-
}
|
|
44
|
-
if (!command || command === "start") {
|
|
26
|
+
// src/cli/commands/start.ts
|
|
27
|
+
import { buildCommand as buildCommand2 } from "@stricli/core";
|
|
28
|
+
var startCommand = buildCommand2({
|
|
29
|
+
func: async () => {
|
|
45
30
|
const { startServer } = await import("./index.js");
|
|
46
31
|
await startServer();
|
|
47
|
-
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
}
|
|
59
|
-
main().catch((error) => {
|
|
60
|
-
console.error("Fatal error:", error);
|
|
61
|
-
process.exit(1);
|
|
32
|
+
},
|
|
33
|
+
parameters: {},
|
|
34
|
+
docs: { brief: "Start the MCP server" }
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// src/cli/app.ts
|
|
38
|
+
var routes = buildRouteMap({
|
|
39
|
+
routes: {
|
|
40
|
+
start: startCommand,
|
|
41
|
+
auth: authCommand
|
|
42
|
+
},
|
|
43
|
+
docs: { brief: "GMCP - Gmail and Calendar MCP Server" }
|
|
62
44
|
});
|
|
45
|
+
async function createApp() {
|
|
46
|
+
const version = await getVersion();
|
|
47
|
+
return buildApplication(routes, {
|
|
48
|
+
name: "gmcp",
|
|
49
|
+
versionInfo: { currentVersion: version }
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// src/cli.ts
|
|
54
|
+
var app = await createApp();
|
|
55
|
+
var args = process.argv.slice(2);
|
|
56
|
+
var effectiveArgs = args.length === 0 ? ["start"] : args;
|
|
57
|
+
await run(app, effectiveArgs, { process });
|
package/dist/index.js
CHANGED
|
@@ -647,6 +647,16 @@ function createGmailClient(auth, logger) {
|
|
|
647
647
|
} catch (error) {
|
|
648
648
|
throw new Error(`Failed to delete label ${labelId}: ${error}`);
|
|
649
649
|
}
|
|
650
|
+
},
|
|
651
|
+
async deleteEmail(messageId) {
|
|
652
|
+
try {
|
|
653
|
+
await gmail.users.messages.delete({
|
|
654
|
+
userId: "me",
|
|
655
|
+
id: messageId
|
|
656
|
+
});
|
|
657
|
+
} catch (error) {
|
|
658
|
+
throw new Error(`Failed to delete message ${messageId}: ${error}`);
|
|
659
|
+
}
|
|
650
660
|
}
|
|
651
661
|
};
|
|
652
662
|
}
|
|
@@ -1760,9 +1770,88 @@ This tool creates a new label with customizable visibility and color settings. L
|
|
|
1760
1770
|
**Scope Requirements**:
|
|
1761
1771
|
- Requires \`gmail.labels\` or \`gmail.modify\` scope`;
|
|
1762
1772
|
|
|
1763
|
-
// src/tools/delete-
|
|
1773
|
+
// src/tools/delete-email.ts
|
|
1764
1774
|
import json2md5 from "json2md";
|
|
1765
1775
|
import { z as z8 } from "zod";
|
|
1776
|
+
var DeleteEmailInputSchema = z8.object({
|
|
1777
|
+
message_id: z8.string().min(1, "Message ID cannot be empty").describe("The Gmail message ID to permanently delete"),
|
|
1778
|
+
output_format: z8.enum(["markdown", "json"]).default("markdown").describe("Output format: markdown (default) or json")
|
|
1779
|
+
});
|
|
1780
|
+
function deletionToMarkdown(messageId) {
|
|
1781
|
+
const sections = [
|
|
1782
|
+
{ h1: "Email Deleted Successfully" },
|
|
1783
|
+
{
|
|
1784
|
+
p: `Message with ID **${messageId}** has been permanently deleted from your Gmail account.`
|
|
1785
|
+
},
|
|
1786
|
+
{ h2: "Important Notes" },
|
|
1787
|
+
{
|
|
1788
|
+
ul: [
|
|
1789
|
+
"The email has been permanently deleted (not moved to trash)",
|
|
1790
|
+
"This action cannot be undone",
|
|
1791
|
+
"The email cannot be recovered"
|
|
1792
|
+
]
|
|
1793
|
+
}
|
|
1794
|
+
];
|
|
1795
|
+
return json2md5(sections);
|
|
1796
|
+
}
|
|
1797
|
+
async function deleteEmailTool(gmailClient, params) {
|
|
1798
|
+
try {
|
|
1799
|
+
await gmailClient.deleteEmail(params.message_id);
|
|
1800
|
+
const output = {
|
|
1801
|
+
message_id: params.message_id,
|
|
1802
|
+
deleted: true,
|
|
1803
|
+
message: "Email permanently deleted. This action cannot be undone."
|
|
1804
|
+
};
|
|
1805
|
+
const textContent = params.output_format === "json" ? JSON.stringify(output, null, 2) : deletionToMarkdown(params.message_id);
|
|
1806
|
+
return {
|
|
1807
|
+
content: [{ type: "text", text: textContent }],
|
|
1808
|
+
structuredContent: output
|
|
1809
|
+
};
|
|
1810
|
+
} catch (error) {
|
|
1811
|
+
return createErrorResponse("deleting email", error);
|
|
1812
|
+
}
|
|
1813
|
+
}
|
|
1814
|
+
var DELETE_EMAIL_DESCRIPTION = `Permanently delete a Gmail email message.
|
|
1815
|
+
|
|
1816
|
+
**WARNING: This is a destructive operation that CANNOT be undone. The email is permanently deleted and bypasses the trash folder.**
|
|
1817
|
+
|
|
1818
|
+
This tool permanently deletes an email message from your Gmail account. Unlike moving to trash, this operation immediately and permanently removes the email.
|
|
1819
|
+
|
|
1820
|
+
**Parameters**:
|
|
1821
|
+
- \`message_id\` (string, required): The Gmail message ID to delete
|
|
1822
|
+
- \`output_format\` (string, optional): Output format: "markdown" (default) or "json"
|
|
1823
|
+
|
|
1824
|
+
**Returns**:
|
|
1825
|
+
- \`message_id\`: The deleted message ID
|
|
1826
|
+
- \`deleted\`: Always true on success
|
|
1827
|
+
- \`message\`: Confirmation message
|
|
1828
|
+
|
|
1829
|
+
**Examples**:
|
|
1830
|
+
- Delete email: \`{ "message_id": "18f3c5d4e8a2b1c0" }\`
|
|
1831
|
+
- Delete with JSON output: \`{ "message_id": "18f3c5d4e8a2b1c0", "output_format": "json" }\`
|
|
1832
|
+
|
|
1833
|
+
**Safety Considerations**:
|
|
1834
|
+
- This is a destructive operation marked with \`destructiveHint: true\`
|
|
1835
|
+
- The email is permanently deleted, not moved to trash
|
|
1836
|
+
- This action cannot be undone - the email cannot be recovered
|
|
1837
|
+
- Consider using \`modify_labels\` with \`add_labels: ["TRASH"]\` if you want recoverable deletion
|
|
1838
|
+
- Use \`get_email\` first to verify which email you're deleting
|
|
1839
|
+
|
|
1840
|
+
**Use Cases**:
|
|
1841
|
+
- Permanently remove sensitive emails
|
|
1842
|
+
- Clean up emails that should not be recoverable
|
|
1843
|
+
- Remove emails that are already in trash permanently
|
|
1844
|
+
|
|
1845
|
+
**Error Handling**:
|
|
1846
|
+
- Returns error if message ID doesn't exist
|
|
1847
|
+
- Returns error if authentication lacks sufficient permissions
|
|
1848
|
+
|
|
1849
|
+
**Scope Requirements**:
|
|
1850
|
+
- Requires \`gmail.modify\` or full \`mail.google.com\` scope`;
|
|
1851
|
+
|
|
1852
|
+
// src/tools/delete-label.ts
|
|
1853
|
+
import json2md6 from "json2md";
|
|
1854
|
+
import { z as z9 } from "zod";
|
|
1766
1855
|
var SYSTEM_LABELS = [
|
|
1767
1856
|
"INBOX",
|
|
1768
1857
|
"SENT",
|
|
@@ -1778,11 +1867,11 @@ var SYSTEM_LABELS = [
|
|
|
1778
1867
|
"CATEGORY_UPDATES",
|
|
1779
1868
|
"CATEGORY_FORUMS"
|
|
1780
1869
|
];
|
|
1781
|
-
var DeleteLabelInputSchema =
|
|
1782
|
-
label_id:
|
|
1783
|
-
output_format:
|
|
1870
|
+
var DeleteLabelInputSchema = z9.object({
|
|
1871
|
+
label_id: z9.string().min(1, "Label ID cannot be empty").describe("The label ID to delete (e.g., 'Label_123'). Cannot delete system labels."),
|
|
1872
|
+
output_format: z9.enum(["markdown", "json"]).default("markdown").describe("Output format: markdown (default) or json")
|
|
1784
1873
|
});
|
|
1785
|
-
function
|
|
1874
|
+
function deletionToMarkdown2(labelId) {
|
|
1786
1875
|
const sections = [
|
|
1787
1876
|
{ h1: "Label Deleted Successfully" },
|
|
1788
1877
|
{
|
|
@@ -1798,7 +1887,7 @@ function deletionToMarkdown(labelId) {
|
|
|
1798
1887
|
]
|
|
1799
1888
|
}
|
|
1800
1889
|
];
|
|
1801
|
-
return
|
|
1890
|
+
return json2md6(sections);
|
|
1802
1891
|
}
|
|
1803
1892
|
async function deleteLabelTool(gmailClient, params) {
|
|
1804
1893
|
try {
|
|
@@ -1830,7 +1919,7 @@ async function deleteLabelTool(gmailClient, params) {
|
|
|
1830
1919
|
deleted: true,
|
|
1831
1920
|
message: "Label deleted successfully. The label has been removed from all messages."
|
|
1832
1921
|
};
|
|
1833
|
-
const textContent = params.output_format === "json" ? JSON.stringify(output, null, 2) :
|
|
1922
|
+
const textContent = params.output_format === "json" ? JSON.stringify(output, null, 2) : deletionToMarkdown2(params.label_id);
|
|
1834
1923
|
return {
|
|
1835
1924
|
content: [{ type: "text", text: textContent }],
|
|
1836
1925
|
structuredContent: output
|
|
@@ -1920,11 +2009,11 @@ This tool permanently deletes a user-created label from your Gmail account. Syst
|
|
|
1920
2009
|
If you want to hide a label instead of deleting it, consider using the \`update_label\` tool to set \`label_list_visibility\` to "labelHide" instead.`;
|
|
1921
2010
|
|
|
1922
2011
|
// src/tools/get-attachment.ts
|
|
1923
|
-
import { z as
|
|
1924
|
-
var GetAttachmentInputSchema =
|
|
1925
|
-
message_id:
|
|
1926
|
-
attachment_id:
|
|
1927
|
-
output_format:
|
|
2012
|
+
import { z as z10 } from "zod";
|
|
2013
|
+
var GetAttachmentInputSchema = z10.object({
|
|
2014
|
+
message_id: z10.string().min(1, "Message ID cannot be empty").describe("The Gmail message ID containing the attachment"),
|
|
2015
|
+
attachment_id: z10.string().min(1, "Attachment ID cannot be empty").describe("The attachment ID to download (from list_attachments)"),
|
|
2016
|
+
output_format: z10.enum(["base64", "json"]).default("base64").describe("Output format: base64 (default, returns raw base64url string) or json (returns structured object)")
|
|
1928
2017
|
});
|
|
1929
2018
|
async function getAttachmentTool(gmailClient, params) {
|
|
1930
2019
|
try {
|
|
@@ -1991,12 +2080,12 @@ To decode:
|
|
|
1991
2080
|
3. Decode base64url data to get original file content`;
|
|
1992
2081
|
|
|
1993
2082
|
// src/tools/get-email.ts
|
|
1994
|
-
import
|
|
1995
|
-
import { z as
|
|
1996
|
-
var GetEmailInputSchema =
|
|
1997
|
-
message_id:
|
|
1998
|
-
include_body:
|
|
1999
|
-
output_format:
|
|
2083
|
+
import json2md7 from "json2md";
|
|
2084
|
+
import { z as z11 } from "zod";
|
|
2085
|
+
var GetEmailInputSchema = z11.object({
|
|
2086
|
+
message_id: z11.string().min(1, "Message ID cannot be empty").describe("The Gmail message ID to retrieve"),
|
|
2087
|
+
include_body: z11.boolean().default(true).describe("Whether to include full email body in results (default: true)"),
|
|
2088
|
+
output_format: z11.enum(["markdown", "json"]).default("markdown").describe("Output format: markdown (default) or json")
|
|
2000
2089
|
});
|
|
2001
2090
|
function emailToMarkdown(email) {
|
|
2002
2091
|
const sections = [
|
|
@@ -2023,7 +2112,7 @@ function emailToMarkdown(email) {
|
|
|
2023
2112
|
sections.push({ h2: "Snippet" });
|
|
2024
2113
|
sections.push({ p: email.snippet });
|
|
2025
2114
|
}
|
|
2026
|
-
return
|
|
2115
|
+
return json2md7(sections);
|
|
2027
2116
|
}
|
|
2028
2117
|
async function getEmailTool(gmailClient, params) {
|
|
2029
2118
|
try {
|
|
@@ -2074,11 +2163,11 @@ This tool retrieves a specific email message from Gmail using its unique message
|
|
|
2074
2163
|
- Retrieve email for further processing or analysis`;
|
|
2075
2164
|
|
|
2076
2165
|
// src/tools/get-label.ts
|
|
2077
|
-
import
|
|
2078
|
-
import { z as
|
|
2079
|
-
var GetLabelInputSchema =
|
|
2080
|
-
label_id:
|
|
2081
|
-
output_format:
|
|
2166
|
+
import json2md8 from "json2md";
|
|
2167
|
+
import { z as z12 } from "zod";
|
|
2168
|
+
var GetLabelInputSchema = z12.object({
|
|
2169
|
+
label_id: z12.string().min(1, "Label ID cannot be empty").describe("The label ID to retrieve (e.g., 'INBOX', 'Label_123')"),
|
|
2170
|
+
output_format: z12.enum(["markdown", "json"]).default("markdown").describe("Output format: markdown (default) or json")
|
|
2082
2171
|
});
|
|
2083
2172
|
function labelToMarkdown(label) {
|
|
2084
2173
|
const sections = [
|
|
@@ -2113,7 +2202,7 @@ function labelToMarkdown(label) {
|
|
|
2113
2202
|
]
|
|
2114
2203
|
});
|
|
2115
2204
|
}
|
|
2116
|
-
return
|
|
2205
|
+
return json2md8(sections);
|
|
2117
2206
|
}
|
|
2118
2207
|
async function getLabelTool(gmailClient, params) {
|
|
2119
2208
|
try {
|
|
@@ -2180,12 +2269,12 @@ This tool retrieves comprehensive details about a label, including its name, typ
|
|
|
2180
2269
|
- System labels and custom labels are both supported`;
|
|
2181
2270
|
|
|
2182
2271
|
// src/tools/get-thread.ts
|
|
2183
|
-
import
|
|
2184
|
-
import { z as
|
|
2185
|
-
var GetThreadInputSchema =
|
|
2186
|
-
thread_id:
|
|
2187
|
-
include_body:
|
|
2188
|
-
output_format:
|
|
2272
|
+
import json2md9 from "json2md";
|
|
2273
|
+
import { z as z13 } from "zod";
|
|
2274
|
+
var GetThreadInputSchema = z13.object({
|
|
2275
|
+
thread_id: z13.string().min(1, "Thread ID cannot be empty").describe("The Gmail thread ID to retrieve"),
|
|
2276
|
+
include_body: z13.boolean().default(false).describe("Whether to include full email body for all messages (default: false)"),
|
|
2277
|
+
output_format: z13.enum(["markdown", "json"]).default("markdown").describe("Output format: markdown (default) or json")
|
|
2189
2278
|
});
|
|
2190
2279
|
function threadToMarkdown(threadId, messages) {
|
|
2191
2280
|
const sections = [
|
|
@@ -2220,7 +2309,7 @@ function threadToMarkdown(threadId, messages) {
|
|
|
2220
2309
|
sections.push({ p: "---" });
|
|
2221
2310
|
}
|
|
2222
2311
|
}
|
|
2223
|
-
return
|
|
2312
|
+
return json2md9(sections);
|
|
2224
2313
|
}
|
|
2225
2314
|
async function getThreadTool(gmailClient, params) {
|
|
2226
2315
|
try {
|
|
@@ -2278,11 +2367,11 @@ This tool retrieves all messages in a Gmail conversation thread (email chain). T
|
|
|
2278
2367
|
- Extract all messages from a discussion thread`;
|
|
2279
2368
|
|
|
2280
2369
|
// src/tools/list-attachments.ts
|
|
2281
|
-
import
|
|
2282
|
-
import { z as
|
|
2283
|
-
var ListAttachmentsInputSchema =
|
|
2284
|
-
message_id:
|
|
2285
|
-
output_format:
|
|
2370
|
+
import json2md10 from "json2md";
|
|
2371
|
+
import { z as z14 } from "zod";
|
|
2372
|
+
var ListAttachmentsInputSchema = z14.object({
|
|
2373
|
+
message_id: z14.string().min(1, "Message ID cannot be empty").describe("The Gmail message ID to list attachments from"),
|
|
2374
|
+
output_format: z14.enum(["markdown", "json"]).default("markdown").describe("Output format: markdown (default) or json")
|
|
2286
2375
|
});
|
|
2287
2376
|
function formatBytes(bytes) {
|
|
2288
2377
|
if (bytes === 0) {
|
|
@@ -2314,7 +2403,7 @@ function attachmentsToMarkdown(messageId, attachments) {
|
|
|
2314
2403
|
});
|
|
2315
2404
|
}
|
|
2316
2405
|
}
|
|
2317
|
-
return
|
|
2406
|
+
return json2md10(sections);
|
|
2318
2407
|
}
|
|
2319
2408
|
async function listAttachmentsTool(gmailClient, params) {
|
|
2320
2409
|
try {
|
|
@@ -2372,10 +2461,10 @@ This tool retrieves metadata about all attachments in a specific email message,
|
|
|
2372
2461
|
- List attachment IDs for use with get_attachment tool`;
|
|
2373
2462
|
|
|
2374
2463
|
// src/tools/list-labels.ts
|
|
2375
|
-
import
|
|
2376
|
-
import { z as
|
|
2377
|
-
var ListLabelsInputSchema =
|
|
2378
|
-
output_format:
|
|
2464
|
+
import json2md11 from "json2md";
|
|
2465
|
+
import { z as z15 } from "zod";
|
|
2466
|
+
var ListLabelsInputSchema = z15.object({
|
|
2467
|
+
output_format: z15.enum(["markdown", "json"]).default("markdown").describe("Output format: markdown (default) or json")
|
|
2379
2468
|
});
|
|
2380
2469
|
function labelsToMarkdown(labels) {
|
|
2381
2470
|
const sections = [{ h1: "Gmail Labels" }];
|
|
@@ -2392,7 +2481,7 @@ function labelsToMarkdown(labels) {
|
|
|
2392
2481
|
if (labels.system.length === 0 && labels.user.length === 0) {
|
|
2393
2482
|
sections.push({ p: "*No labels found*" });
|
|
2394
2483
|
}
|
|
2395
|
-
return
|
|
2484
|
+
return json2md11(sections);
|
|
2396
2485
|
}
|
|
2397
2486
|
async function listLabelsTool(gmailClient, params) {
|
|
2398
2487
|
try {
|
|
@@ -2465,13 +2554,13 @@ Custom labels are created by users for organizing emails. They have IDs like \`L
|
|
|
2465
2554
|
- Returns error if Gmail API is unreachable`;
|
|
2466
2555
|
|
|
2467
2556
|
// src/tools/modify-labels.ts
|
|
2468
|
-
import
|
|
2469
|
-
import { z as
|
|
2470
|
-
var ModifyLabelsInputSchema =
|
|
2471
|
-
message_id:
|
|
2472
|
-
add_labels:
|
|
2473
|
-
remove_labels:
|
|
2474
|
-
output_format:
|
|
2557
|
+
import json2md12 from "json2md";
|
|
2558
|
+
import { z as z16 } from "zod";
|
|
2559
|
+
var ModifyLabelsInputSchema = z16.object({
|
|
2560
|
+
message_id: z16.string().min(1, "Message ID cannot be empty").describe("The Gmail message ID to modify labels for"),
|
|
2561
|
+
add_labels: z16.array(z16.string()).optional().describe("Label IDs to add (e.g., ['STARRED', 'INBOX', 'UNREAD'] or custom label IDs)"),
|
|
2562
|
+
remove_labels: z16.array(z16.string()).optional().describe("Label IDs to remove (e.g., ['UNREAD', 'INBOX'] to mark read and archive)"),
|
|
2563
|
+
output_format: z16.enum(["markdown", "json"]).default("markdown").describe("Output format: markdown (default) or json")
|
|
2475
2564
|
});
|
|
2476
2565
|
function labelModificationToMarkdown(email, addedLabels, removedLabels) {
|
|
2477
2566
|
const sections = [
|
|
@@ -2497,7 +2586,7 @@ function labelModificationToMarkdown(email, addedLabels, removedLabels) {
|
|
|
2497
2586
|
sections.push({
|
|
2498
2587
|
p: email.labels.length > 0 ? email.labels.join(", ") : "*No labels on this message*"
|
|
2499
2588
|
});
|
|
2500
|
-
return
|
|
2589
|
+
return json2md12(sections);
|
|
2501
2590
|
}
|
|
2502
2591
|
async function modifyLabelsTool(gmailClient, params) {
|
|
2503
2592
|
try {
|
|
@@ -2588,15 +2677,15 @@ This tool allows you to add or remove labels from a Gmail message. Labels are us
|
|
|
2588
2677
|
- Move messages to trash or spam`;
|
|
2589
2678
|
|
|
2590
2679
|
// src/tools/reply.ts
|
|
2591
|
-
import
|
|
2592
|
-
import { z as
|
|
2593
|
-
var ReplyInputSchema =
|
|
2594
|
-
message_id:
|
|
2595
|
-
body:
|
|
2596
|
-
content_type:
|
|
2597
|
-
cc:
|
|
2598
|
-
confirm:
|
|
2599
|
-
output_format:
|
|
2680
|
+
import json2md13 from "json2md";
|
|
2681
|
+
import { z as z17 } from "zod";
|
|
2682
|
+
var ReplyInputSchema = z17.object({
|
|
2683
|
+
message_id: z17.string().min(1, "Message ID cannot be empty").describe("The Gmail message ID to reply to"),
|
|
2684
|
+
body: z17.string().min(1, "Reply body cannot be empty").describe("Reply message body"),
|
|
2685
|
+
content_type: z17.enum(["text/plain", "text/html"]).default("text/plain").describe("Content type: text/plain (default) or text/html for HTML replies"),
|
|
2686
|
+
cc: z17.email("CC must be a valid email address").optional().describe("CC (carbon copy) email address"),
|
|
2687
|
+
confirm: z17.boolean().default(false).describe("Set to true to confirm and send the reply. If false, returns preview only."),
|
|
2688
|
+
output_format: z17.enum(["markdown", "json"]).default("markdown").describe("Output format: markdown (default) or json")
|
|
2600
2689
|
});
|
|
2601
2690
|
function replyPreviewToMarkdown(originalEmail, replyBody, contentType, cc) {
|
|
2602
2691
|
const sections = [
|
|
@@ -2624,7 +2713,7 @@ function replyPreviewToMarkdown(originalEmail, replyBody, contentType, cc) {
|
|
|
2624
2713
|
{ h2: "Reply Body" },
|
|
2625
2714
|
{ p: replyBody }
|
|
2626
2715
|
];
|
|
2627
|
-
return
|
|
2716
|
+
return json2md13(sections);
|
|
2628
2717
|
}
|
|
2629
2718
|
function replySentToMarkdown(originalEmail, result, cc) {
|
|
2630
2719
|
const sections = [
|
|
@@ -2643,7 +2732,7 @@ function replySentToMarkdown(originalEmail, result, cc) {
|
|
|
2643
2732
|
p: "Your reply has been sent and added to the conversation thread."
|
|
2644
2733
|
}
|
|
2645
2734
|
];
|
|
2646
|
-
return
|
|
2735
|
+
return json2md13(sections);
|
|
2647
2736
|
}
|
|
2648
2737
|
async function replyTool(gmailClient, params) {
|
|
2649
2738
|
try {
|
|
@@ -2767,13 +2856,13 @@ This tool sends a reply to an existing email, automatically threading it in the
|
|
|
2767
2856
|
4. Call with \`confirm: true\` to send`;
|
|
2768
2857
|
|
|
2769
2858
|
// src/tools/search.ts
|
|
2770
|
-
import { z as
|
|
2771
|
-
var SearchEmailsInputSchema =
|
|
2772
|
-
query:
|
|
2773
|
-
max_results:
|
|
2774
|
-
include_body:
|
|
2775
|
-
page_token:
|
|
2776
|
-
output_format:
|
|
2859
|
+
import { z as z18 } from "zod";
|
|
2860
|
+
var SearchEmailsInputSchema = z18.object({
|
|
2861
|
+
query: z18.string().min(1, "Query cannot be empty").describe('Gmail search query using Gmail search syntax (e.g., "from:user@example.com subject:test is:unread")'),
|
|
2862
|
+
max_results: z18.number().int().min(1).max(100).default(10).describe("Maximum number of emails to return (default: 10, max: 100)"),
|
|
2863
|
+
include_body: z18.boolean().default(false).describe("Whether to include full email body in results (default: false)"),
|
|
2864
|
+
page_token: z18.string().optional().describe("Token for pagination to fetch next page of results"),
|
|
2865
|
+
output_format: z18.enum(["markdown", "json"]).default("markdown").describe("Output format: markdown (default) or json")
|
|
2777
2866
|
});
|
|
2778
2867
|
async function searchEmailsTool(gmailClient, params) {
|
|
2779
2868
|
try {
|
|
@@ -2843,17 +2932,17 @@ This tool allows you to search through your Gmail messages using the same query
|
|
|
2843
2932
|
- Empty results if no emails match the query`;
|
|
2844
2933
|
|
|
2845
2934
|
// src/tools/send-email.ts
|
|
2846
|
-
import
|
|
2847
|
-
import { z as
|
|
2848
|
-
var SendEmailInputSchema =
|
|
2849
|
-
to:
|
|
2850
|
-
subject:
|
|
2851
|
-
body:
|
|
2852
|
-
content_type:
|
|
2853
|
-
cc:
|
|
2854
|
-
bcc:
|
|
2855
|
-
confirm:
|
|
2856
|
-
output_format:
|
|
2935
|
+
import json2md14 from "json2md";
|
|
2936
|
+
import { z as z19 } from "zod";
|
|
2937
|
+
var SendEmailInputSchema = z19.object({
|
|
2938
|
+
to: z19.email("Must be a valid email address").describe("Recipient email address"),
|
|
2939
|
+
subject: z19.string().min(1, "Subject cannot be empty").describe("Email subject"),
|
|
2940
|
+
body: z19.string().min(1, "Body cannot be empty").describe("Email body content"),
|
|
2941
|
+
content_type: z19.enum(["text/plain", "text/html"]).default("text/plain").describe("Content type: text/plain (default) or text/html for HTML emails"),
|
|
2942
|
+
cc: z19.email("CC must be a valid email address").optional().describe("CC (carbon copy) email address"),
|
|
2943
|
+
bcc: z19.email("BCC must be a valid email address").optional().describe("BCC (blind carbon copy) email address"),
|
|
2944
|
+
confirm: z19.boolean().default(false).describe("Set to true to confirm and send the email. If false, returns preview only."),
|
|
2945
|
+
output_format: z19.enum(["markdown", "json"]).default("markdown").describe("Output format: markdown (default) or json")
|
|
2857
2946
|
});
|
|
2858
2947
|
function emailPreviewToMarkdown(params) {
|
|
2859
2948
|
const sections = [
|
|
@@ -2874,7 +2963,7 @@ function emailPreviewToMarkdown(params) {
|
|
|
2874
2963
|
{ h2: "Body" },
|
|
2875
2964
|
{ p: params.body }
|
|
2876
2965
|
];
|
|
2877
|
-
return
|
|
2966
|
+
return json2md14(sections);
|
|
2878
2967
|
}
|
|
2879
2968
|
function emailSentToMarkdown(params, result) {
|
|
2880
2969
|
const sections = [
|
|
@@ -2894,7 +2983,7 @@ function emailSentToMarkdown(params, result) {
|
|
|
2894
2983
|
p: "The email has been sent and will appear in your Sent folder."
|
|
2895
2984
|
}
|
|
2896
2985
|
];
|
|
2897
|
-
return
|
|
2986
|
+
return json2md14(sections);
|
|
2898
2987
|
}
|
|
2899
2988
|
async function sendEmailTool(gmailClient, params) {
|
|
2900
2989
|
try {
|
|
@@ -3000,8 +3089,8 @@ This tool sends email through your Gmail account. It includes a safety feature:
|
|
|
3000
3089
|
3. Call again with \`confirm: true\` to send`;
|
|
3001
3090
|
|
|
3002
3091
|
// src/tools/update-label.ts
|
|
3003
|
-
import
|
|
3004
|
-
import { z as
|
|
3092
|
+
import json2md15 from "json2md";
|
|
3093
|
+
import { z as z20 } from "zod";
|
|
3005
3094
|
var SYSTEM_LABELS2 = [
|
|
3006
3095
|
"INBOX",
|
|
3007
3096
|
"SENT",
|
|
@@ -3012,14 +3101,14 @@ var SYSTEM_LABELS2 = [
|
|
|
3012
3101
|
"IMPORTANT",
|
|
3013
3102
|
"UNREAD"
|
|
3014
3103
|
];
|
|
3015
|
-
var UpdateLabelInputSchema =
|
|
3016
|
-
label_id:
|
|
3017
|
-
name:
|
|
3018
|
-
message_list_visibility:
|
|
3019
|
-
label_list_visibility:
|
|
3020
|
-
background_color:
|
|
3021
|
-
text_color:
|
|
3022
|
-
output_format:
|
|
3104
|
+
var UpdateLabelInputSchema = z20.object({
|
|
3105
|
+
label_id: z20.string().min(1, "Label ID cannot be empty").describe("The label ID to update (e.g., 'Label_123' or 'INBOX')"),
|
|
3106
|
+
name: z20.string().optional().describe("New label name. Cannot rename system labels. Use '/' for nested labels."),
|
|
3107
|
+
message_list_visibility: z20.enum(["show", "hide"]).optional().describe("How label appears in message list"),
|
|
3108
|
+
label_list_visibility: z20.enum(["labelShow", "labelShowIfUnread", "labelHide"]).optional().describe("How label appears in label list (sidebar)"),
|
|
3109
|
+
background_color: z20.string().optional().describe("Background color in hex format (e.g., '#ff0000'). Must provide both background and text color together."),
|
|
3110
|
+
text_color: z20.string().optional().describe("Text color in hex format (e.g., '#ffffff'). Must provide both background and text color together."),
|
|
3111
|
+
output_format: z20.enum(["markdown", "json"]).default("markdown").describe("Output format: markdown (default) or json")
|
|
3023
3112
|
});
|
|
3024
3113
|
function updatedLabelToMarkdown(label, updates) {
|
|
3025
3114
|
const sections = [
|
|
@@ -3067,7 +3156,7 @@ function updatedLabelToMarkdown(label, updates) {
|
|
|
3067
3156
|
]
|
|
3068
3157
|
});
|
|
3069
3158
|
}
|
|
3070
|
-
return
|
|
3159
|
+
return json2md15(sections);
|
|
3071
3160
|
}
|
|
3072
3161
|
async function updateLabelTool(gmailClient, params) {
|
|
3073
3162
|
try {
|
|
@@ -3337,6 +3426,14 @@ async function startServer() {
|
|
|
3337
3426
|
inputSchema: DeleteLabelInputSchema,
|
|
3338
3427
|
annotations: DESTRUCTIVE_ANNOTATIONS,
|
|
3339
3428
|
handler: deleteLabelTool
|
|
3429
|
+
},
|
|
3430
|
+
{
|
|
3431
|
+
name: "gmcp_gmail_delete_email",
|
|
3432
|
+
title: "Delete Gmail Email",
|
|
3433
|
+
description: DELETE_EMAIL_DESCRIPTION,
|
|
3434
|
+
inputSchema: DeleteEmailInputSchema,
|
|
3435
|
+
annotations: DESTRUCTIVE_ANNOTATIONS,
|
|
3436
|
+
handler: deleteEmailTool
|
|
3340
3437
|
}
|
|
3341
3438
|
];
|
|
3342
3439
|
const calendarTools = [
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gmcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "GMCP Server for Google Workspace with OAuth2 authentication",
|
|
5
5
|
"module": "src/index.ts",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -44,22 +44,25 @@
|
|
|
44
44
|
"anthropic"
|
|
45
45
|
],
|
|
46
46
|
"dependencies": {
|
|
47
|
+
"@inquirer/prompts": "^8.2.0",
|
|
47
48
|
"@modelcontextprotocol/sdk": "^1.25.3",
|
|
49
|
+
"@stricli/core": "^1.2.5",
|
|
48
50
|
"googleapis": "^170.1.0",
|
|
49
51
|
"json2md": "^2.0.3",
|
|
50
|
-
"
|
|
51
|
-
"pino
|
|
52
|
+
"kleur": "^4.1.5",
|
|
53
|
+
"pino": "^10.3.0",
|
|
54
|
+
"pino-pretty": "^13.1.3",
|
|
52
55
|
"zod": "^4.3.6"
|
|
53
56
|
},
|
|
54
57
|
"devDependencies": {
|
|
55
|
-
"@biomejs/biome": "2.3.
|
|
58
|
+
"@biomejs/biome": "2.3.13",
|
|
56
59
|
"@changesets/changelog-github": "^0.5.2",
|
|
57
60
|
"@changesets/cli": "^2.29.8",
|
|
58
61
|
"@types/bun": "latest",
|
|
59
62
|
"@types/json2md": "^1.5.4",
|
|
60
63
|
"bunup": "^0.16.20",
|
|
61
|
-
"lefthook": "^2.0.
|
|
62
|
-
"ultracite": "7.
|
|
64
|
+
"lefthook": "^2.0.16",
|
|
65
|
+
"ultracite": "7.1.1",
|
|
63
66
|
"vitest": "^4.0.18"
|
|
64
67
|
},
|
|
65
68
|
"peerDependencies": {
|