@jaingxyz/personal-gmail-mcp 0.1.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/.env.example +6 -0
- package/LICENSE +661 -0
- package/README.md +142 -0
- package/dist/auth.js +200 -0
- package/dist/auth.js.map +1 -0
- package/dist/config.js +90 -0
- package/dist/config.js.map +1 -0
- package/dist/google.js +27 -0
- package/dist/google.js.map +1 -0
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -0
- package/dist/scripts/whoami.js +16 -0
- package/dist/scripts/whoami.js.map +1 -0
- package/dist/server.js +136 -0
- package/dist/server.js.map +1 -0
- package/dist/tools/calendar.js +296 -0
- package/dist/tools/calendar.js.map +1 -0
- package/dist/tools/helpers.js +125 -0
- package/dist/tools/helpers.js.map +1 -0
- package/dist/tools/mutate.js +75 -0
- package/dist/tools/mutate.js.map +1 -0
- package/dist/tools/read.js +120 -0
- package/dist/tools/read.js.map +1 -0
- package/dist/tools/send.js +166 -0
- package/dist/tools/send.js.map +1 -0
- package/package.json +70 -0
package/README.md
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# personal-gmail-mcp
|
|
2
|
+
|
|
3
|
+
A [Model Context Protocol](https://modelcontextprotocol.io/) server that exposes a personal Gmail (consumer Google account) inbox **and calendar** to MCP clients like Claude Desktop. Talks to the Gmail and Google Calendar APIs over HTTPS, uses a local **loopback OAuth** flow, and stores tokens in the **OS keyring**.
|
|
4
|
+
|
|
5
|
+
Sibling to [personal-outlook-mcp](https://github.com/jaingxyz/personal-outlook-mcp); same design (local stdio, keyring-backed tokens, you bring your own OAuth client), different provider. Tools are prefixed `gmail_*`.
|
|
6
|
+
|
|
7
|
+
> **Status: functional.** Auth (loopback OAuth + keyring token cache), 12 mail tools, and 7 calendar tools are in place and verified against a live account. Not yet published to npm — install from source for now (see below).
|
|
8
|
+
|
|
9
|
+
## Why keyring + bring-your-own-client
|
|
10
|
+
|
|
11
|
+
Every Gmail MCP server we surveyed stores OAuth tokens as a **plaintext JSON file** in your home directory. This one stores them in the OS keyring (macOS Keychain / Windows Credential Manager / Linux Secret Service) via [`@napi-rs/keyring`](https://github.com/Brooooooklyn/keyring-node), and runs entirely locally — your mail never passes through anyone else's servers.
|
|
12
|
+
|
|
13
|
+
## Google setup (required, ~5 minutes, free)
|
|
14
|
+
|
|
15
|
+
You must create your own Google Cloud OAuth client.
|
|
16
|
+
|
|
17
|
+
1. Go to https://console.cloud.google.com → create a project.
|
|
18
|
+
2. **APIs & Services → Library** → enable both **Gmail API** and **Google Calendar API**.
|
|
19
|
+
3. **APIs & Services → OAuth consent screen**:
|
|
20
|
+
- User type: **External**.
|
|
21
|
+
- Add your own Google account as a **test user**.
|
|
22
|
+
- **Publish the app ("In production").** You can leave it **unverified** for personal use (you'll click through an "unverified app" warning at sign-in).
|
|
23
|
+
- ⚠️ **This step matters:** an app left in **"Testing"** status issues refresh tokens that **expire after 7 days** — you'd have to re-auth every week. Publishing to production (even unverified) gives long-lived refresh tokens.
|
|
24
|
+
4. **APIs & Services → Credentials → Create credentials → OAuth client ID** → application type **Desktop app**. Copy the **Client ID** and **Client secret**.
|
|
25
|
+
5. `cp .env.example .env` and fill in `GOOGLE_CLIENT_ID` and `GOOGLE_CLIENT_SECRET`.
|
|
26
|
+
|
|
27
|
+
The required scopes are requested at sign-in; you consent at the browser prompt:
|
|
28
|
+
|
|
29
|
+
| Scope | Used for |
|
|
30
|
+
| ----------------------------------------- | ------------------------------------------------------------ |
|
|
31
|
+
| `https://mail.google.com/` | full mailbox read/modify/send |
|
|
32
|
+
| `.../auth/calendar.events` | event read/create/update/cancel/respond |
|
|
33
|
+
| `.../auth/calendar.calendarlist.readonly` | enumerating your calendars (`gmail_calendar_list_calendars`) |
|
|
34
|
+
| `.../auth/userinfo.email` + `openid` | identifying the signed-in account |
|
|
35
|
+
|
|
36
|
+
> Changing the scope list invalidates the cached token — re-run `npm run whoami` to re-consent.
|
|
37
|
+
|
|
38
|
+
## First run (loopback auth)
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
npm install
|
|
42
|
+
npm run build
|
|
43
|
+
npm run whoami
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
`whoami` opens your browser to Google's consent page (the URL is also printed to **stderr** if the browser doesn't open). After you approve, a throwaway `127.0.0.1` listener captures the redirect, exchanges the code, and stores the token set in the OS keyring under service `personal-gmail-mcp`. It then prints your Gmail profile as JSON. Subsequent runs refresh silently.
|
|
47
|
+
|
|
48
|
+
To sign out (forget the cached token):
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
node -e "import('./dist/auth.js').then(m => m.signOut())"
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## MCP client integration
|
|
55
|
+
|
|
56
|
+
Point your MCP client (Claude Desktop or similar) at the built server. Until
|
|
57
|
+
this is published to npm, use the **from-source** form with an absolute path to
|
|
58
|
+
`dist/index.js`:
|
|
59
|
+
|
|
60
|
+
```json
|
|
61
|
+
{
|
|
62
|
+
"mcpServers": {
|
|
63
|
+
"personal-gmail": {
|
|
64
|
+
"command": "node",
|
|
65
|
+
"args": ["/absolute/path/to/personal-gmail-mcp/dist/index.js"],
|
|
66
|
+
"env": {
|
|
67
|
+
"GOOGLE_CLIENT_ID": "<YOUR-CLIENT-ID>",
|
|
68
|
+
"GOOGLE_CLIENT_SECRET": "<YOUR-CLIENT-SECRET>"
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
> If your `node` is managed by a version manager (nvm, mise, asdf), use the
|
|
76
|
+
> **absolute path** to the node binary (e.g. `which node`) — MCP clients launch
|
|
77
|
+
> with a minimal `PATH` and won't resolve a bare `node`.
|
|
78
|
+
|
|
79
|
+
Once published to npm, the simpler `npx` form works:
|
|
80
|
+
|
|
81
|
+
```json
|
|
82
|
+
{
|
|
83
|
+
"command": "npx",
|
|
84
|
+
"args": ["-y", "@jaingxyz/personal-gmail-mcp"],
|
|
85
|
+
"env": { "GOOGLE_CLIENT_ID": "...", "GOOGLE_CLIENT_SECRET": "..." }
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Run `npm run whoami` from a terminal **once** to seed the keyring before
|
|
90
|
+
launching the client — the browser/consent step can't be surfaced from inside
|
|
91
|
+
the app (stderr is swallowed), so the MCP server only does silent refresh and
|
|
92
|
+
will report `Re-authentication required` if the cache is empty or stale.
|
|
93
|
+
|
|
94
|
+
## Tools
|
|
95
|
+
|
|
96
|
+
All tools are prefixed `gmail_*` (mail) or `gmail_calendar_*` (calendar) so a
|
|
97
|
+
multi-account setup can coexist.
|
|
98
|
+
|
|
99
|
+
| Tool | Purpose |
|
|
100
|
+
| ---------------------------------- | -------------------------------------------------------------------------------------- |
|
|
101
|
+
| `gmail_list_labels` | List labels (system + custom) with unread/total counts. |
|
|
102
|
+
| `gmail_list_recent` | Newest-first messages in a label. Supports `unreadOnly`, cursor paging. |
|
|
103
|
+
| `gmail_search` | Search with Gmail query syntax (`from:`, `subject:`, `has:attachment`, `newer_than:`). |
|
|
104
|
+
| `gmail_read` | Fetch one message with decoded body (text preferred, html fallback). |
|
|
105
|
+
| `gmail_mark_read` | Mark read/unread (toggles the `UNREAD` label). |
|
|
106
|
+
| `gmail_modify_labels` | Add/remove labels (remove `INBOX` to archive, add `STARRED`, etc.). |
|
|
107
|
+
| `gmail_delete` | Trash (recoverable) by default; `hardDelete=true` is permanent. |
|
|
108
|
+
| `gmail_untrash` | Restore a message from Trash. |
|
|
109
|
+
| `gmail_send` | Send a new email immediately. |
|
|
110
|
+
| `gmail_reply` | Reply in-thread by message id; `replyAll=true` for all recipients. |
|
|
111
|
+
| `gmail_create_draft` | Create a draft without sending; returns a draftId. |
|
|
112
|
+
| `gmail_send_draft` | Send a previously created draft. |
|
|
113
|
+
| `gmail_calendar_list_calendars` | List calendars with editability. |
|
|
114
|
+
| `gmail_calendar_list_events` | Events in a time range (recurring series expanded). |
|
|
115
|
+
| `gmail_calendar_read_event` | Full event details incl. attendees and recurrence. |
|
|
116
|
+
| `gmail_calendar_create_event` | Create an event; optional Google Meet link and invites. |
|
|
117
|
+
| `gmail_calendar_update_event` | Update subject/time/location/description/attendees. |
|
|
118
|
+
| `gmail_calendar_cancel_event` | Delete an event; notifies attendees when you organize it. |
|
|
119
|
+
| `gmail_calendar_respond_to_invite` | accept / tentativelyAccept / decline an invite. |
|
|
120
|
+
|
|
121
|
+
Calendar event times use `{ dateTime, timeZone }` where `dateTime` is local
|
|
122
|
+
form (no offset) and `timeZone` is an IANA name. Output times default to
|
|
123
|
+
`America/Los_Angeles`; override with `PERSONAL_GMAIL_TZ`.
|
|
124
|
+
|
|
125
|
+
## How it differs from the Outlook server
|
|
126
|
+
|
|
127
|
+
| | Outlook (Graph) | Gmail |
|
|
128
|
+
| -------------- | --------------------- | ---------------------------------------------------------------- |
|
|
129
|
+
| Auth flow | MSAL device code | Loopback / installed-app (Google forbids device code for Gmail) |
|
|
130
|
+
| Mail container | Folders | **Labels** (`INBOX`, `SENT`, `DRAFT`, `TRASH`, custom) |
|
|
131
|
+
| Search | Graph `$search` (KQL) | Gmail query syntax (`from: subject: has:attachment newer_than:`) |
|
|
132
|
+
| Send | JSON message | base64url **RFC-2822 MIME** |
|
|
133
|
+
| Soft delete | move to Deleted Items | `trash` / `untrash` |
|
|
134
|
+
| Calendar | same Graph API | **separate** Google Calendar API + scope |
|
|
135
|
+
|
|
136
|
+
## License
|
|
137
|
+
|
|
138
|
+
AGPL-3.0-or-later. See [LICENSE](./LICENSE).
|
|
139
|
+
|
|
140
|
+
## Acknowledgements
|
|
141
|
+
|
|
142
|
+
Built with assistance from [Claude](https://www.anthropic.com/claude) (Anthropic). Architecture and final review remain the human author's responsibility.
|
package/dist/auth.js
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// Copyright (C) 2026 jaingxyz
|
|
3
|
+
import { createServer } from "node:http";
|
|
4
|
+
import { randomBytes } from "node:crypto";
|
|
5
|
+
import { execFile } from "node:child_process";
|
|
6
|
+
import { AsyncEntry } from "@napi-rs/keyring";
|
|
7
|
+
import { google } from "googleapis";
|
|
8
|
+
import { config } from "./config.js";
|
|
9
|
+
const keyringEntry = new AsyncEntry(config.keychainService, config.keychainAccount);
|
|
10
|
+
// Persist the OAuth token set (access + refresh + expiry) in the OS keyring.
|
|
11
|
+
// Same principle as the Outlook server: tokens never touch disk in plaintext.
|
|
12
|
+
async function loadCredentials() {
|
|
13
|
+
const data = await keyringEntry.getPassword();
|
|
14
|
+
if (!data)
|
|
15
|
+
return null;
|
|
16
|
+
try {
|
|
17
|
+
return JSON.parse(data);
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
async function saveCredentials(creds) {
|
|
24
|
+
// Merge: a refresh response often omits refresh_token, so don't clobber it.
|
|
25
|
+
const existing = (await loadCredentials()) ?? {};
|
|
26
|
+
const merged = { ...existing, ...creds };
|
|
27
|
+
if (!merged.refresh_token && existing.refresh_token) {
|
|
28
|
+
merged.refresh_token = existing.refresh_token;
|
|
29
|
+
}
|
|
30
|
+
await keyringEntry.setPassword(JSON.stringify(merged));
|
|
31
|
+
}
|
|
32
|
+
export class ReauthRequiredError extends Error {
|
|
33
|
+
constructor(reason) {
|
|
34
|
+
super(`Re-authentication required: ${reason}. Run \`npm run whoami\` from a terminal to refresh the token cache, then retry.`);
|
|
35
|
+
this.name = "ReauthRequiredError";
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function makeClient(redirectUri) {
|
|
39
|
+
return new google.auth.OAuth2(config.clientId, config.clientSecret, redirectUri);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Returns an authorized OAuth2 client. Silent path: load cached credentials;
|
|
43
|
+
* the googleapis client auto-refreshes using the refresh_token when the access
|
|
44
|
+
* token is expired, and we persist the refreshed token via the tokens event.
|
|
45
|
+
*/
|
|
46
|
+
export async function getAuthClient(opts = {}) {
|
|
47
|
+
const creds = await loadCredentials();
|
|
48
|
+
if (creds?.refresh_token) {
|
|
49
|
+
const client = makeClient();
|
|
50
|
+
client.setCredentials(creds);
|
|
51
|
+
client.on("tokens", (tokens) => {
|
|
52
|
+
void saveCredentials(tokens);
|
|
53
|
+
});
|
|
54
|
+
// Proactively ensure we have a live access token; this triggers a refresh
|
|
55
|
+
// if needed and throws if the refresh token is revoked/expired.
|
|
56
|
+
try {
|
|
57
|
+
await client.getAccessToken();
|
|
58
|
+
return client;
|
|
59
|
+
}
|
|
60
|
+
catch (err) {
|
|
61
|
+
if (!opts.interactive) {
|
|
62
|
+
throw new ReauthRequiredError(silentFailureReason(err));
|
|
63
|
+
}
|
|
64
|
+
// fall through to interactive
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
else if (!opts.interactive) {
|
|
68
|
+
throw new ReauthRequiredError("no cached refresh token");
|
|
69
|
+
}
|
|
70
|
+
return runLoopbackFlow();
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Interactive loopback (installed-app) OAuth flow. Google does NOT support
|
|
74
|
+
* device-code for Gmail, so we spin up a throwaway localhost listener, open
|
|
75
|
+
* the consent page in the browser, and capture the redirected auth code.
|
|
76
|
+
*/
|
|
77
|
+
async function runLoopbackFlow() {
|
|
78
|
+
return new Promise((resolvePromise, reject) => {
|
|
79
|
+
const state = randomBytes(16).toString("hex");
|
|
80
|
+
const server = createServer((req, res) => {
|
|
81
|
+
try {
|
|
82
|
+
const url = new URL(req.url ?? "", `http://${config.redirectHost}`);
|
|
83
|
+
if (!url.pathname.startsWith("/oauth2callback")) {
|
|
84
|
+
res.writeHead(404).end();
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const returnedState = url.searchParams.get("state");
|
|
88
|
+
const code = url.searchParams.get("code");
|
|
89
|
+
const error = url.searchParams.get("error");
|
|
90
|
+
if (error) {
|
|
91
|
+
finish(res, `Authorization failed: ${error}`);
|
|
92
|
+
cleanup();
|
|
93
|
+
reject(new Error(`OAuth error: ${error}`));
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
if (returnedState !== state) {
|
|
97
|
+
finish(res, "State mismatch — aborting.");
|
|
98
|
+
cleanup();
|
|
99
|
+
reject(new Error("OAuth state mismatch (possible CSRF)"));
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
if (!code) {
|
|
103
|
+
finish(res, "No authorization code received.");
|
|
104
|
+
cleanup();
|
|
105
|
+
reject(new Error("No authorization code in callback"));
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
const port = server.address().port;
|
|
109
|
+
const redirectUri = `http://${config.redirectHost}:${port}/oauth2callback`;
|
|
110
|
+
const client = makeClient(redirectUri);
|
|
111
|
+
client
|
|
112
|
+
.getToken(code)
|
|
113
|
+
.then(async ({ tokens }) => {
|
|
114
|
+
if (!tokens.refresh_token) {
|
|
115
|
+
// Without offline access we'd re-auth hourly. prompt=consent
|
|
116
|
+
// below forces a refresh token to be issued.
|
|
117
|
+
console.error("[auth] warning: no refresh_token returned; you may need to revoke the app's access and retry.");
|
|
118
|
+
}
|
|
119
|
+
client.setCredentials(tokens);
|
|
120
|
+
client.on("tokens", (t) => void saveCredentials(t));
|
|
121
|
+
await saveCredentials(tokens);
|
|
122
|
+
finish(res, "Authentication complete. You can close this tab and return to the terminal.");
|
|
123
|
+
cleanup();
|
|
124
|
+
resolvePromise(client);
|
|
125
|
+
})
|
|
126
|
+
.catch((err) => {
|
|
127
|
+
finish(res, "Token exchange failed.");
|
|
128
|
+
cleanup();
|
|
129
|
+
reject(err);
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
catch (err) {
|
|
133
|
+
cleanup();
|
|
134
|
+
reject(err);
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
function cleanup() {
|
|
138
|
+
server.close();
|
|
139
|
+
}
|
|
140
|
+
function finish(res, message) {
|
|
141
|
+
res
|
|
142
|
+
.writeHead(200, { "Content-Type": "text/html" })
|
|
143
|
+
.end(`<!doctype html><meta charset="utf-8"><p>${message}</p>`);
|
|
144
|
+
}
|
|
145
|
+
// Bind to an ephemeral port on the loopback interface, then build the auth
|
|
146
|
+
// URL with the now-known port.
|
|
147
|
+
server.listen(0, config.redirectHost, () => {
|
|
148
|
+
const port = server.address().port;
|
|
149
|
+
const redirectUri = `http://${config.redirectHost}:${port}/oauth2callback`;
|
|
150
|
+
const client = makeClient(redirectUri);
|
|
151
|
+
const authUrl = client.generateAuthUrl({
|
|
152
|
+
access_type: "offline", // gets us a refresh_token
|
|
153
|
+
prompt: "consent", // force refresh_token issuance even on re-auth
|
|
154
|
+
scope: config.scopes,
|
|
155
|
+
state,
|
|
156
|
+
});
|
|
157
|
+
// CLI script context: print to stderr so the MCP stdout stream is never
|
|
158
|
+
// corrupted if this is ever reached from the server.
|
|
159
|
+
console.error("\nOpen this URL to authorize personal-gmail-mcp:\n");
|
|
160
|
+
console.error(authUrl + "\n");
|
|
161
|
+
openBrowser(authUrl);
|
|
162
|
+
});
|
|
163
|
+
server.on("error", reject);
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
function openBrowser(url) {
|
|
167
|
+
// Pass the URL as an argv element (no shell), so it can't be interpreted as
|
|
168
|
+
// a shell command. Best-effort; if it fails the user copies the URL from
|
|
169
|
+
// stderr. On Windows, `cmd /c start "" <url>` ("" = empty window title).
|
|
170
|
+
const platform = process.platform;
|
|
171
|
+
if (platform === "darwin") {
|
|
172
|
+
execFile("open", [url], () => { });
|
|
173
|
+
}
|
|
174
|
+
else if (platform === "win32") {
|
|
175
|
+
execFile("cmd", ["/c", "start", "", url], () => { });
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
execFile("xdg-open", [url], () => { });
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
function silentFailureReason(err) {
|
|
182
|
+
if (err && typeof err === "object") {
|
|
183
|
+
const e = err;
|
|
184
|
+
if (e.message?.includes("invalid_grant")) {
|
|
185
|
+
return "refresh token expired or revoked (Google test-mode tokens expire after 7 days — publish the OAuth app)";
|
|
186
|
+
}
|
|
187
|
+
if (e.message)
|
|
188
|
+
return e.message.slice(0, 200);
|
|
189
|
+
}
|
|
190
|
+
return "silent token refresh failed";
|
|
191
|
+
}
|
|
192
|
+
export async function signOut() {
|
|
193
|
+
try {
|
|
194
|
+
await keyringEntry.deletePassword();
|
|
195
|
+
}
|
|
196
|
+
catch {
|
|
197
|
+
// Already signed out.
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
//# sourceMappingURL=auth.js.map
|
package/dist/auth.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"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"}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// Copyright (C) 2026 jaingxyz
|
|
3
|
+
import { readFileSync, existsSync } from "node:fs";
|
|
4
|
+
import { resolve, dirname } from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
function loadDotEnv() {
|
|
7
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
const candidates = [
|
|
9
|
+
resolve(here, "../../.env"),
|
|
10
|
+
resolve(process.cwd(), ".env"),
|
|
11
|
+
];
|
|
12
|
+
for (const path of candidates) {
|
|
13
|
+
if (!existsSync(path))
|
|
14
|
+
continue;
|
|
15
|
+
const text = readFileSync(path, "utf8");
|
|
16
|
+
for (const rawLine of text.split(/\r?\n/)) {
|
|
17
|
+
const line = rawLine.trim();
|
|
18
|
+
if (!line || line.startsWith("#"))
|
|
19
|
+
continue;
|
|
20
|
+
const eq = line.indexOf("=");
|
|
21
|
+
if (eq === -1)
|
|
22
|
+
continue;
|
|
23
|
+
const key = line.slice(0, eq).trim();
|
|
24
|
+
let val = line.slice(eq + 1).trim();
|
|
25
|
+
if ((val.startsWith('"') && val.endsWith('"')) ||
|
|
26
|
+
(val.startsWith("'") && val.endsWith("'"))) {
|
|
27
|
+
val = val.slice(1, -1);
|
|
28
|
+
}
|
|
29
|
+
if (process.env[key] === undefined)
|
|
30
|
+
process.env[key] = val;
|
|
31
|
+
}
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
loadDotEnv();
|
|
36
|
+
function readVersion() {
|
|
37
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
38
|
+
const candidates = [
|
|
39
|
+
resolve(here, "../package.json"),
|
|
40
|
+
resolve(here, "../../package.json"),
|
|
41
|
+
];
|
|
42
|
+
for (const path of candidates) {
|
|
43
|
+
if (!existsSync(path))
|
|
44
|
+
continue;
|
|
45
|
+
try {
|
|
46
|
+
const pkg = JSON.parse(readFileSync(path, "utf8"));
|
|
47
|
+
if (pkg.version)
|
|
48
|
+
return pkg.version;
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
// Malformed package.json — try the next candidate.
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return "0.0.0";
|
|
55
|
+
}
|
|
56
|
+
function required(name) {
|
|
57
|
+
const v = process.env[name];
|
|
58
|
+
if (!v) {
|
|
59
|
+
throw new Error(`Missing required env var ${name}. Copy .env.example to .env and fill it in (see README "Google setup").`);
|
|
60
|
+
}
|
|
61
|
+
return v;
|
|
62
|
+
}
|
|
63
|
+
export const config = {
|
|
64
|
+
version: readVersion(),
|
|
65
|
+
clientId: required("GOOGLE_CLIENT_ID"),
|
|
66
|
+
clientSecret: required("GOOGLE_CLIENT_SECRET"),
|
|
67
|
+
// Loopback redirect. Port 0 = OS picks a free port at auth time; the chosen
|
|
68
|
+
// port is appended before the redirect is registered with Google. A Desktop
|
|
69
|
+
// OAuth client accepts any http://127.0.0.1 / http://localhost port, so we
|
|
70
|
+
// don't have to pre-register a fixed one.
|
|
71
|
+
redirectHost: process.env.GOOGLE_REDIRECT_HOST || "127.0.0.1",
|
|
72
|
+
// Delegated scopes. mail.google.com gives full mailbox (read/modify/send);
|
|
73
|
+
// calendar.events covers event CRUD on a known calendar (incl. "primary");
|
|
74
|
+
// calendar.calendarlist.readonly is needed to ENUMERATE calendars
|
|
75
|
+
// (calendarList.list) — calendar.events alone returns 403 there.
|
|
76
|
+
// mail.google.com and the calendar scopes are "restricted" — the OAuth app
|
|
77
|
+
// must be published (not in Testing) for long-lived refresh tokens.
|
|
78
|
+
// NOTE: changing this list invalidates the cached token; re-run `whoami`.
|
|
79
|
+
scopes: [
|
|
80
|
+
"https://mail.google.com/",
|
|
81
|
+
"https://www.googleapis.com/auth/calendar.events",
|
|
82
|
+
"https://www.googleapis.com/auth/calendar.calendarlist.readonly",
|
|
83
|
+
"https://www.googleapis.com/auth/userinfo.email",
|
|
84
|
+
"openid",
|
|
85
|
+
],
|
|
86
|
+
defaultTimeZone: process.env.PERSONAL_GMAIL_TZ || "America/Los_Angeles",
|
|
87
|
+
keychainService: "personal-gmail-mcp",
|
|
88
|
+
keychainAccount: "oauth-token",
|
|
89
|
+
};
|
|
90
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
|
package/dist/google.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// Copyright (C) 2026 jaingxyz
|
|
3
|
+
import { google } from "googleapis";
|
|
4
|
+
import { getAuthClient } from "./auth.js";
|
|
5
|
+
export async function gmailClient(opts) {
|
|
6
|
+
const auth = await getAuthClient(opts);
|
|
7
|
+
return google.gmail({ version: "v1", auth });
|
|
8
|
+
}
|
|
9
|
+
export async function calendarClient(opts) {
|
|
10
|
+
const auth = await getAuthClient(opts);
|
|
11
|
+
return google.calendar({ version: "v3", auth });
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Smoke-test identity probe: returns the signed-in account's Gmail profile
|
|
15
|
+
* (email address, total messages/threads, history id).
|
|
16
|
+
*/
|
|
17
|
+
export async function getMe(opts) {
|
|
18
|
+
const gmail = await gmailClient(opts);
|
|
19
|
+
const res = await gmail.users.getProfile({ userId: "me" });
|
|
20
|
+
return {
|
|
21
|
+
emailAddress: res.data.emailAddress,
|
|
22
|
+
messagesTotal: res.data.messagesTotal,
|
|
23
|
+
threadsTotal: res.data.threadsTotal,
|
|
24
|
+
historyId: res.data.historyId,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=google.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"google.js","sourceRoot":"","sources":["../src/google.ts"],"names":[],"mappings":"AAAA,6CAA6C;AAC7C,8BAA8B;AAC9B,OAAO,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAEpC,OAAO,EAAE,aAAa,EAAyB,MAAM,WAAW,CAAC;AAEjE,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,IAAuB;IAEvB,MAAM,IAAI,GAAG,MAAM,aAAa,CAAC,IAAI,CAAC,CAAC;IACvC,OAAO,MAAM,CAAC,KAAK,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;AAC/C,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,IAAuB;IAEvB,MAAM,IAAI,GAAG,MAAM,aAAa,CAAC,IAAI,CAAC,CAAC;IACvC,OAAO,MAAM,CAAC,QAAQ,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;AAClD,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,KAAK,CAAC,IAAuB;IAMjD,MAAM,KAAK,GAAG,MAAM,WAAW,CAAC,IAAI,CAAC,CAAC;IACtC,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,KAAK,CAAC,UAAU,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;IAC3D,OAAO;QACL,YAAY,EAAE,GAAG,CAAC,IAAI,CAAC,YAAY;QACnC,aAAa,EAAE,GAAG,CAAC,IAAI,CAAC,aAAa;QACrC,YAAY,EAAE,GAAG,CAAC,IAAI,CAAC,YAAY;QACnC,SAAS,EAAE,GAAG,CAAC,IAAI,CAAC,SAAS;KAC9B,CAAC;AACJ,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
3
|
+
// Copyright (C) 2026 jaingxyz
|
|
4
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
5
|
+
import { buildServer } from "./server.js";
|
|
6
|
+
async function main() {
|
|
7
|
+
const server = buildServer();
|
|
8
|
+
const transport = new StdioServerTransport();
|
|
9
|
+
await server.connect(transport);
|
|
10
|
+
// Server runs until the client closes stdin.
|
|
11
|
+
}
|
|
12
|
+
main().catch((err) => {
|
|
13
|
+
console.error("[personal-gmail-mcp] fatal:", err);
|
|
14
|
+
process.exit(1);
|
|
15
|
+
});
|
|
16
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,6CAA6C;AAC7C,8BAA8B;AAC9B,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE1C,KAAK,UAAU,IAAI;IACjB,MAAM,MAAM,GAAG,WAAW,EAAE,CAAC;IAC7B,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAChC,6CAA6C;AAC/C,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,OAAO,CAAC,KAAK,CAAC,6BAA6B,EAAE,GAAG,CAAC,CAAC;IAClD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// Copyright (C) 2026 jaingxyz
|
|
3
|
+
import { getMe } from "../google.js";
|
|
4
|
+
async function main() {
|
|
5
|
+
// Force the interactive loopback flow if the keyring is empty or the refresh
|
|
6
|
+
// token is stale. Subsequent runs (and the MCP server) reuse the cached token.
|
|
7
|
+
const me = await getMe({ interactive: true });
|
|
8
|
+
// Intentionally writes to stdout: this is a CLI script, not the MCP server.
|
|
9
|
+
// eslint-disable-next-line no-console
|
|
10
|
+
console.log(JSON.stringify(me, null, 2));
|
|
11
|
+
}
|
|
12
|
+
main().catch((err) => {
|
|
13
|
+
console.error(err);
|
|
14
|
+
process.exit(1);
|
|
15
|
+
});
|
|
16
|
+
//# sourceMappingURL=whoami.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"whoami.js","sourceRoot":"","sources":["../../src/scripts/whoami.ts"],"names":[],"mappings":"AAAA,6CAA6C;AAC7C,8BAA8B;AAC9B,OAAO,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AAErC,KAAK,UAAU,IAAI;IACjB,6EAA6E;IAC7E,+EAA+E;IAC/E,MAAM,EAAE,GAAG,MAAM,KAAK,CAAC,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,CAAC;IAC9C,4EAA4E;IAC5E,sCAAsC;IACtC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;AAC3C,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACnB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|