google-services-mcp 0.1.0-beta.100
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 -0
- package/README.md +303 -0
- package/dist/auth/config.d.ts +43 -0
- package/dist/auth/config.d.ts.map +1 -0
- package/dist/auth/config.js +112 -0
- package/dist/auth/config.js.map +1 -0
- package/dist/auth/manager.d.ts +50 -0
- package/dist/auth/manager.d.ts.map +1 -0
- package/dist/auth/manager.js +141 -0
- package/dist/auth/manager.js.map +1 -0
- package/dist/auth/oauth.d.ts +57 -0
- package/dist/auth/oauth.d.ts.map +1 -0
- package/dist/auth/oauth.js +223 -0
- package/dist/auth/oauth.js.map +1 -0
- package/dist/auth/store.d.ts +18 -0
- package/dist/auth/store.d.ts.map +1 -0
- package/dist/auth/store.js +50 -0
- package/dist/auth/store.js.map +1 -0
- package/dist/cli.d.ts +7 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +137 -0
- package/dist/cli.js.map +1 -0
- package/dist/main.d.ts +3 -0
- package/dist/main.d.ts.map +1 -0
- package/dist/main.js +21 -0
- package/dist/main.js.map +1 -0
- package/dist/server.d.ts +3 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +28 -0
- package/dist/server.js.map +1 -0
- package/dist/services/calendar.d.ts +77 -0
- package/dist/services/calendar.d.ts.map +1 -0
- package/dist/services/calendar.js +141 -0
- package/dist/services/calendar.js.map +1 -0
- package/dist/services/contacts-tasks.d.ts +61 -0
- package/dist/services/contacts-tasks.d.ts.map +1 -0
- package/dist/services/contacts-tasks.js +106 -0
- package/dist/services/contacts-tasks.js.map +1 -0
- package/dist/services/docs.d.ts +32 -0
- package/dist/services/docs.d.ts.map +1 -0
- package/dist/services/docs.js +58 -0
- package/dist/services/docs.js.map +1 -0
- package/dist/services/drive.d.ts +72 -0
- package/dist/services/drive.d.ts.map +1 -0
- package/dist/services/drive.js +131 -0
- package/dist/services/drive.js.map +1 -0
- package/dist/services/forms.d.ts +30 -0
- package/dist/services/forms.d.ts.map +1 -0
- package/dist/services/forms.js +54 -0
- package/dist/services/forms.js.map +1 -0
- package/dist/services/gmail.d.ts +159 -0
- package/dist/services/gmail.d.ts.map +1 -0
- package/dist/services/gmail.js +499 -0
- package/dist/services/gmail.js.map +1 -0
- package/dist/services/sheets.d.ts +44 -0
- package/dist/services/sheets.d.ts.map +1 -0
- package/dist/services/sheets.js +53 -0
- package/dist/services/sheets.js.map +1 -0
- package/dist/services/slides.d.ts +30 -0
- package/dist/services/slides.d.ts.map +1 -0
- package/dist/services/slides.js +52 -0
- package/dist/services/slides.js.map +1 -0
- package/dist/services/youtube.d.ts +45 -0
- package/dist/services/youtube.d.ts.map +1 -0
- package/dist/services/youtube.js +97 -0
- package/dist/services/youtube.js.map +1 -0
- package/dist/tools/index.d.ts +3 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +953 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/util/result.d.ts +29 -0
- package/dist/util/result.d.ts.map +1 -0
- package/dist/util/result.js +21 -0
- package/dist/util/result.js.map +1 -0
- package/package.json +60 -0
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { google } from "googleapis";
|
|
2
|
+
import { getDataDir, hasCredentials, loadConfig, saveClientCredentials, saveConfig, } from "./config.js";
|
|
3
|
+
import { buildAuthUrl, DEFAULT_AUTH_TIMEOUT_MS, exchangeCode, fetchUserInfo, generatePkce, generateState, openBrowser, refreshAccessToken, waitForOAuthCallback, } from "./oauth.js";
|
|
4
|
+
import { deleteAccount, loadAccount, loadAllAccounts, saveAccount, } from "./store.js";
|
|
5
|
+
/**
|
|
6
|
+
* Coordinates credentials, per-account token storage, the interactive OAuth
|
|
7
|
+
* flow, and refresh. All service modules resolve auth through here.
|
|
8
|
+
*/
|
|
9
|
+
class AuthManager {
|
|
10
|
+
async getConfig() {
|
|
11
|
+
return loadConfig();
|
|
12
|
+
}
|
|
13
|
+
async listAccounts() {
|
|
14
|
+
return loadAllAccounts();
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Resolve an account to use for a tool call.
|
|
18
|
+
* Priority: explicit name -> configured default -> sole account.
|
|
19
|
+
*/
|
|
20
|
+
async resolveAccount(name) {
|
|
21
|
+
const accounts = await loadAllAccounts();
|
|
22
|
+
if (name) {
|
|
23
|
+
const found = accounts.find((a) => a.name === name);
|
|
24
|
+
if (!found) {
|
|
25
|
+
const available = accounts.length ? accounts.map((a) => a.name).join(", ") : "none";
|
|
26
|
+
throw new Error(`Account "${name}" not found. Available accounts: ${available}`);
|
|
27
|
+
}
|
|
28
|
+
return found;
|
|
29
|
+
}
|
|
30
|
+
if (accounts.length === 0) {
|
|
31
|
+
throw new Error("No Google accounts configured. Add one with the account_add tool or run: google-services-mcp add <name>");
|
|
32
|
+
}
|
|
33
|
+
const config = await this.getConfig();
|
|
34
|
+
if (config.defaultAccount) {
|
|
35
|
+
const found = accounts.find((a) => a.name === config.defaultAccount);
|
|
36
|
+
if (found)
|
|
37
|
+
return found;
|
|
38
|
+
}
|
|
39
|
+
if (accounts.length === 1)
|
|
40
|
+
return accounts[0];
|
|
41
|
+
throw new Error(`Multiple accounts configured but no default set. Pass account="..." or run: google-services-mcp set-default <name>`);
|
|
42
|
+
}
|
|
43
|
+
/** Get an authenticated googleapis OAuth2 client, refreshing first if needed. */
|
|
44
|
+
async getClient(name) {
|
|
45
|
+
const config = await this.getConfig();
|
|
46
|
+
let account = await this.resolveAccount(name);
|
|
47
|
+
if (account.expiryDate && Date.now() > account.expiryDate - 60_000) {
|
|
48
|
+
const tokens = await refreshAccessToken(config, account.refreshToken);
|
|
49
|
+
account = {
|
|
50
|
+
...account,
|
|
51
|
+
accessToken: tokens.accessToken,
|
|
52
|
+
refreshToken: tokens.refreshToken ?? account.refreshToken,
|
|
53
|
+
expiryDate: tokens.expiryDate,
|
|
54
|
+
};
|
|
55
|
+
await saveAccount(account);
|
|
56
|
+
}
|
|
57
|
+
const client = new google.auth.OAuth2(config.clientId, config.clientSecret);
|
|
58
|
+
client.setCredentials({
|
|
59
|
+
access_token: account.accessToken,
|
|
60
|
+
refresh_token: account.refreshToken,
|
|
61
|
+
expiry_date: account.expiryDate,
|
|
62
|
+
});
|
|
63
|
+
return client;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Run the interactive OAuth flow to add a new account: opens the consent
|
|
67
|
+
* screen, waits for the loopback callback, exchanges the code, and persists
|
|
68
|
+
* the account alongside the signed-in email.
|
|
69
|
+
*/
|
|
70
|
+
async addAccount(name, options = {}) {
|
|
71
|
+
const config = await this.getConfig();
|
|
72
|
+
if (!hasCredentials(config)) {
|
|
73
|
+
throw new Error("Google OAuth credentials are not configured. Set GOOGLE_MCP_CLIENT_ID and " +
|
|
74
|
+
"GOOGLE_MCP_CLIENT_SECRET (or add them to config.json) first.");
|
|
75
|
+
}
|
|
76
|
+
const state = generateState();
|
|
77
|
+
const { codeVerifier, codeChallenge } = generatePkce();
|
|
78
|
+
const authUrl = buildAuthUrl(config, state, codeChallenge);
|
|
79
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_AUTH_TIMEOUT_MS;
|
|
80
|
+
// The callback owns its timeout so an aborted flow always closes the
|
|
81
|
+
// loopback listener (no stale port left behind).
|
|
82
|
+
const callbackPromise = waitForOAuthCallback(config, state, { timeoutMs });
|
|
83
|
+
if (options.openBrowser !== false) {
|
|
84
|
+
try {
|
|
85
|
+
await openBrowser(authUrl);
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
// Browser unavailable (headless server) — the error message carries the URL.
|
|
89
|
+
throw new Error(`Could not open a browser. Open this URL manually to authorize "${name}": ${authUrl}`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
const callback = await callbackPromise;
|
|
93
|
+
const tokens = await exchangeCode(config, callback.code, codeVerifier);
|
|
94
|
+
const user = await fetchUserInfo(tokens.accessToken);
|
|
95
|
+
const account = {
|
|
96
|
+
name,
|
|
97
|
+
email: user.email,
|
|
98
|
+
accessToken: tokens.accessToken,
|
|
99
|
+
refreshToken: tokens.refreshToken ?? "",
|
|
100
|
+
expiryDate: tokens.expiryDate,
|
|
101
|
+
scopes: config.scopes ?? [],
|
|
102
|
+
createdAt: new Date().toISOString(),
|
|
103
|
+
};
|
|
104
|
+
await saveAccount(account);
|
|
105
|
+
return account;
|
|
106
|
+
}
|
|
107
|
+
async removeAccount(name) {
|
|
108
|
+
return deleteAccount(name);
|
|
109
|
+
}
|
|
110
|
+
async setDefaultAccount(name) {
|
|
111
|
+
const config = await this.getConfig();
|
|
112
|
+
if (name) {
|
|
113
|
+
const exists = await loadAccount(name);
|
|
114
|
+
if (!exists)
|
|
115
|
+
throw new Error(`Cannot set default: account "${name}" does not exist.`);
|
|
116
|
+
}
|
|
117
|
+
config.defaultAccount = name || undefined;
|
|
118
|
+
await saveConfig(config);
|
|
119
|
+
}
|
|
120
|
+
async saveCredentials(clientId, clientSecret) {
|
|
121
|
+
await saveClientCredentials(clientId, clientSecret);
|
|
122
|
+
}
|
|
123
|
+
async getStatus() {
|
|
124
|
+
const config = await this.getConfig();
|
|
125
|
+
const accounts = await loadAllAccounts();
|
|
126
|
+
return {
|
|
127
|
+
credentialsConfigured: hasCredentials(config),
|
|
128
|
+
dataDir: getDataDir(),
|
|
129
|
+
defaultAccount: config.defaultAccount,
|
|
130
|
+
accounts: accounts.map((a) => ({
|
|
131
|
+
name: a.name,
|
|
132
|
+
email: a.email,
|
|
133
|
+
expiresAt: a.expiryDate,
|
|
134
|
+
tokenHealthy: !a.expiryDate || Date.now() < a.expiryDate,
|
|
135
|
+
scopes: a.scopes,
|
|
136
|
+
})),
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
export const authManager = new AuthManager();
|
|
141
|
+
//# sourceMappingURL=manager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manager.js","sourceRoot":"","sources":["../../src/auth/manager.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { type Config } from "./config.js";
|
|
2
|
+
export interface TokenSet {
|
|
3
|
+
accessToken: string;
|
|
4
|
+
refreshToken?: string;
|
|
5
|
+
/** Epoch milliseconds at which the access token expires. */
|
|
6
|
+
expiryDate?: number;
|
|
7
|
+
}
|
|
8
|
+
/** How long the loopback callback server waits for Google's redirect. */
|
|
9
|
+
export declare const DEFAULT_AUTH_TIMEOUT_MS: number;
|
|
10
|
+
/**
|
|
11
|
+
* Resolve the redirect URI this server registers with Google. A full
|
|
12
|
+
* `redirectUri` override wins; otherwise the URI is derived from the loopback
|
|
13
|
+
* port. The value must exactly match a redirect URI registered for the OAuth
|
|
14
|
+
* client in the Google Cloud Console.
|
|
15
|
+
*/
|
|
16
|
+
export declare function resolveRedirectUri(config: Config): string;
|
|
17
|
+
/** Build the Google consent-screen URL. */
|
|
18
|
+
export declare function buildAuthUrl(config: Config, state: string, codeChallenge: string): string;
|
|
19
|
+
/** Open a URL in the default browser on the current platform. */
|
|
20
|
+
export declare function openBrowser(url: string): Promise<void>;
|
|
21
|
+
export interface AuthCallback {
|
|
22
|
+
code: string;
|
|
23
|
+
state: string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Start a loopback HTTP server on the configured redirect URI and wait for
|
|
27
|
+
* Google to redirect back with an authorization code. Resolves with the code
|
|
28
|
+
* and state; rejects on CSRF state mismatch, a missing code, a bind failure,
|
|
29
|
+
* or when `timeoutMs` elapses.
|
|
30
|
+
*
|
|
31
|
+
* The listener is always closed once the promise settles, so an aborted or
|
|
32
|
+
* timed-out flow never leaves a stale port occupied (which would otherwise
|
|
33
|
+
* cause EADDRINUSE on the next attempt).
|
|
34
|
+
*/
|
|
35
|
+
export declare function waitForOAuthCallback(config: Config, expectedState: string, options?: {
|
|
36
|
+
timeoutMs?: number;
|
|
37
|
+
}): Promise<AuthCallback>;
|
|
38
|
+
/** Exchange an authorization code for tokens. */
|
|
39
|
+
export declare function exchangeCode(config: Config, code: string, codeVerifier: string): Promise<TokenSet>;
|
|
40
|
+
/** Refresh an expired access token. Keeps the existing refresh token if the provider does not rotate it. */
|
|
41
|
+
export declare function refreshAccessToken(config: Config, refreshToken: string): Promise<TokenSet>;
|
|
42
|
+
/** Best-effort fetch of the signed-in user's profile (email/name). Never throws. */
|
|
43
|
+
export declare function fetchUserInfo(accessToken: string): Promise<{
|
|
44
|
+
email?: string;
|
|
45
|
+
name?: string;
|
|
46
|
+
}>;
|
|
47
|
+
/** Generate a random state value for CSRF protection. */
|
|
48
|
+
export declare function generateState(): string;
|
|
49
|
+
export interface PkcePair {
|
|
50
|
+
/** High-entropy secret kept client-side; sent at token exchange. */
|
|
51
|
+
codeVerifier: string;
|
|
52
|
+
/** S256 hash of the verifier; sent in the authorization URL. */
|
|
53
|
+
codeChallenge: string;
|
|
54
|
+
}
|
|
55
|
+
/** Generate a PKCE (RFC 7636) verifier/challenge pair with S256. */
|
|
56
|
+
export declare function generatePkce(): PkcePair;
|
|
57
|
+
//# sourceMappingURL=oauth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauth.d.ts","sourceRoot":"","sources":["../../src/auth/oauth.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,MAAM,EAAyC,MAAM,aAAa,CAAC;AAIjF,MAAM,WAAW,QAAQ;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,4DAA4D;IAC5D,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAMD,yEAAyE;AACzE,eAAO,MAAM,uBAAuB,QAAgB,CAAC;AAErD;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAIzD;AAoBD,2CAA2C;AAC3C,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,MAAM,CAczF;AAED,iEAAiE;AACjE,wBAAsB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAQ5D;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,MAAM,EACd,aAAa,EAAE,MAAM,EACrB,OAAO,GAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAO,GACnC,OAAO,CAAC,YAAY,CAAC,CA0FvB;AA+BD,iDAAiD;AACjD,wBAAsB,YAAY,CAChC,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,QAAQ,CAAC,CAOnB;AAED,4GAA4G;AAC5G,wBAAsB,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,CAKhG;AAED,oFAAoF;AACpF,wBAAsB,aAAa,CACjC,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAa5C;AAED,yDAAyD;AACzD,wBAAgB,aAAa,IAAI,MAAM,CAEtC;AAED,MAAM,WAAW,QAAQ;IACvB,oEAAoE;IACpE,YAAY,EAAE,MAAM,CAAC;IACrB,gEAAgE;IAChE,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,oEAAoE;AACpE,wBAAgB,YAAY,IAAI,QAAQ,CAOvC"}
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
3
|
+
import { createServer } from "node:http";
|
|
4
|
+
import { promisify } from "node:util";
|
|
5
|
+
import { DEFAULT_REDIRECT_PORT, DEFAULT_SCOPES } from "./config.js";
|
|
6
|
+
const execFileAsync = promisify(execFile);
|
|
7
|
+
const TOKEN_ENDPOINT = "https://oauth2.googleapis.com/token";
|
|
8
|
+
const AUTH_ENDPOINT = "https://accounts.google.com/o/oauth2/v2/auth";
|
|
9
|
+
const USERINFO_ENDPOINT = "https://www.googleapis.com/oauth2/v2/userinfo";
|
|
10
|
+
/** How long the loopback callback server waits for Google's redirect. */
|
|
11
|
+
export const DEFAULT_AUTH_TIMEOUT_MS = 5 * 60 * 1000;
|
|
12
|
+
/**
|
|
13
|
+
* Resolve the redirect URI this server registers with Google. A full
|
|
14
|
+
* `redirectUri` override wins; otherwise the URI is derived from the loopback
|
|
15
|
+
* port. The value must exactly match a redirect URI registered for the OAuth
|
|
16
|
+
* client in the Google Cloud Console.
|
|
17
|
+
*/
|
|
18
|
+
export function resolveRedirectUri(config) {
|
|
19
|
+
if (config.redirectUri)
|
|
20
|
+
return config.redirectUri;
|
|
21
|
+
const port = config.redirectPort ?? DEFAULT_REDIRECT_PORT;
|
|
22
|
+
return `http://127.0.0.1:${port}`;
|
|
23
|
+
}
|
|
24
|
+
/** Parse the resolved redirect URI into bind host/port and accepted paths. */
|
|
25
|
+
function parseCallbackTarget(config) {
|
|
26
|
+
const uri = new URL(resolveRedirectUri(config));
|
|
27
|
+
const hostname = uri.hostname || "127.0.0.1";
|
|
28
|
+
const host = hostname === "localhost" ? "127.0.0.1" : hostname;
|
|
29
|
+
const port = uri.port ? Number(uri.port) : DEFAULT_REDIRECT_PORT;
|
|
30
|
+
const configuredPath = uri.pathname && uri.pathname !== "/" ? uri.pathname : "/";
|
|
31
|
+
return { host, port, paths: new Set([configuredPath, "/", "/oauth2callback"]) };
|
|
32
|
+
}
|
|
33
|
+
/** Build the Google consent-screen URL. */
|
|
34
|
+
export function buildAuthUrl(config, state, codeChallenge) {
|
|
35
|
+
const scopes = config.scopes?.length ? config.scopes : DEFAULT_SCOPES;
|
|
36
|
+
const url = new URL(AUTH_ENDPOINT);
|
|
37
|
+
url.searchParams.set("client_id", config.clientId);
|
|
38
|
+
url.searchParams.set("redirect_uri", resolveRedirectUri(config));
|
|
39
|
+
url.searchParams.set("response_type", "code");
|
|
40
|
+
url.searchParams.set("scope", scopes.join(" "));
|
|
41
|
+
url.searchParams.set("access_type", "offline");
|
|
42
|
+
url.searchParams.set("prompt", "consent");
|
|
43
|
+
url.searchParams.set("state", state);
|
|
44
|
+
url.searchParams.set("code_challenge", codeChallenge);
|
|
45
|
+
url.searchParams.set("code_challenge_method", "S256");
|
|
46
|
+
url.searchParams.set("include_granted_scopes", "true");
|
|
47
|
+
return url.toString();
|
|
48
|
+
}
|
|
49
|
+
/** Open a URL in the default browser on the current platform. */
|
|
50
|
+
export async function openBrowser(url) {
|
|
51
|
+
if (process.platform === "darwin") {
|
|
52
|
+
await execFileAsync("open", [url]);
|
|
53
|
+
}
|
|
54
|
+
else if (process.platform === "win32") {
|
|
55
|
+
await execFileAsync("cmd", ["/c", "start", "", url]);
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
await execFileAsync("xdg-open", [url]);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Start a loopback HTTP server on the configured redirect URI and wait for
|
|
63
|
+
* Google to redirect back with an authorization code. Resolves with the code
|
|
64
|
+
* and state; rejects on CSRF state mismatch, a missing code, a bind failure,
|
|
65
|
+
* or when `timeoutMs` elapses.
|
|
66
|
+
*
|
|
67
|
+
* The listener is always closed once the promise settles, so an aborted or
|
|
68
|
+
* timed-out flow never leaves a stale port occupied (which would otherwise
|
|
69
|
+
* cause EADDRINUSE on the next attempt).
|
|
70
|
+
*/
|
|
71
|
+
export function waitForOAuthCallback(config, expectedState, options = {}) {
|
|
72
|
+
return new Promise((resolve, reject) => {
|
|
73
|
+
const target = parseCallbackTarget(config);
|
|
74
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_AUTH_TIMEOUT_MS;
|
|
75
|
+
let timer;
|
|
76
|
+
let server;
|
|
77
|
+
let closed = false;
|
|
78
|
+
/** Close the listener (optionally force-dropping active connections) and stop the timer. */
|
|
79
|
+
const finish = (force) => {
|
|
80
|
+
if (closed)
|
|
81
|
+
return;
|
|
82
|
+
closed = true;
|
|
83
|
+
if (timer)
|
|
84
|
+
clearTimeout(timer);
|
|
85
|
+
timer = undefined;
|
|
86
|
+
if (server) {
|
|
87
|
+
if (force)
|
|
88
|
+
server.closeAllConnections?.();
|
|
89
|
+
server.close();
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
const callbackHtml = "<html><body><h3>Google Service MCP: authorization received.</h3>" +
|
|
93
|
+
"<p>You can close this window and return to your terminal.</p></body></html>";
|
|
94
|
+
server = createServer((req, res) => {
|
|
95
|
+
let url;
|
|
96
|
+
try {
|
|
97
|
+
url = new URL(req.url ?? "/", resolveRedirectUri(config));
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
res.writeHead(400).end();
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
// Accept the configured redirect path plus the legacy root paths
|
|
104
|
+
// (`/` for `http://127.0.0.1:<port>`, `/oauth2callback` for older configs).
|
|
105
|
+
if (!target.paths.has(url.pathname)) {
|
|
106
|
+
res.writeHead(404);
|
|
107
|
+
res.end("Not found");
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
const code = url.searchParams.get("code") ?? "";
|
|
111
|
+
const state = url.searchParams.get("state") ?? "";
|
|
112
|
+
if (state !== expectedState) {
|
|
113
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
114
|
+
res.end(callbackHtml);
|
|
115
|
+
finish(false);
|
|
116
|
+
reject(new Error("OAuth state mismatch — possible CSRF attack. Aborting authorization."));
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
if (!code) {
|
|
120
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
121
|
+
res.end(callbackHtml);
|
|
122
|
+
finish(false);
|
|
123
|
+
reject(new Error("No authorization code in callback URL."));
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
127
|
+
res.end(callbackHtml);
|
|
128
|
+
finish(false);
|
|
129
|
+
resolve({ code, state });
|
|
130
|
+
});
|
|
131
|
+
server.on("error", (e) => {
|
|
132
|
+
finish(true);
|
|
133
|
+
if (e.code === "EADDRINUSE") {
|
|
134
|
+
reject(new Error(`Could not start the OAuth callback server: port ${target.port} (host ${target.host}) is already in use. ` +
|
|
135
|
+
`Set GOOGLE_MCP_REDIRECT_URI (or redirectUri in config.json, GOOGLE_MCP_REDIRECT_PORT, ` +
|
|
136
|
+
`or redirectPort) to a free loopback endpoint and retry.`));
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
reject(new Error(`OAuth callback server failed: ${e.message}`));
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
timer = setTimeout(() => {
|
|
143
|
+
finish(true);
|
|
144
|
+
reject(new Error(`Timed out waiting for the OAuth callback after ${Math.round(timeoutMs / 1000)}s. ` +
|
|
145
|
+
`Open the authorization URL in a browser and approve the prompt.`));
|
|
146
|
+
}, timeoutMs);
|
|
147
|
+
timer.unref?.();
|
|
148
|
+
server.listen(target.port, target.host);
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
async function postTokenRequest(config, body) {
|
|
152
|
+
const params = new URLSearchParams({
|
|
153
|
+
client_id: config.clientId,
|
|
154
|
+
});
|
|
155
|
+
params.set("client_secret", config.clientSecret);
|
|
156
|
+
for (const [key, value] of Object.entries(body)) {
|
|
157
|
+
params.set(key, value);
|
|
158
|
+
}
|
|
159
|
+
const response = await fetch(TOKEN_ENDPOINT, {
|
|
160
|
+
method: "POST",
|
|
161
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
162
|
+
body: params,
|
|
163
|
+
});
|
|
164
|
+
const data = await response.json().catch(() => ({}));
|
|
165
|
+
if (!response.ok || typeof data.access_token !== "string") {
|
|
166
|
+
throw new Error(`Token request failed: ${JSON.stringify(data)}`);
|
|
167
|
+
}
|
|
168
|
+
const expiresIn = typeof data.expires_in === "number" ? data.expires_in : 3600;
|
|
169
|
+
const refresh = typeof data.refresh_token === "string" && data.refresh_token
|
|
170
|
+
? data.refresh_token
|
|
171
|
+
: (body.refresh_token ?? undefined);
|
|
172
|
+
return {
|
|
173
|
+
accessToken: data.access_token,
|
|
174
|
+
refreshToken: refresh,
|
|
175
|
+
expiryDate: Date.now() + expiresIn * 1000,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
/** Exchange an authorization code for tokens. */
|
|
179
|
+
export async function exchangeCode(config, code, codeVerifier) {
|
|
180
|
+
return postTokenRequest(config, {
|
|
181
|
+
code,
|
|
182
|
+
code_verifier: codeVerifier,
|
|
183
|
+
redirect_uri: resolveRedirectUri(config),
|
|
184
|
+
grant_type: "authorization_code",
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
/** Refresh an expired access token. Keeps the existing refresh token if the provider does not rotate it. */
|
|
188
|
+
export async function refreshAccessToken(config, refreshToken) {
|
|
189
|
+
return postTokenRequest(config, {
|
|
190
|
+
refresh_token: refreshToken,
|
|
191
|
+
grant_type: "refresh_token",
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
/** Best-effort fetch of the signed-in user's profile (email/name). Never throws. */
|
|
195
|
+
export async function fetchUserInfo(accessToken) {
|
|
196
|
+
try {
|
|
197
|
+
const response = await fetch(USERINFO_ENDPOINT, {
|
|
198
|
+
headers: { Authorization: `Bearer ${accessToken}` },
|
|
199
|
+
});
|
|
200
|
+
if (response.ok) {
|
|
201
|
+
const data = await response.json();
|
|
202
|
+
return { email: data.email, name: data.name };
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
catch {
|
|
206
|
+
// ignore network failures
|
|
207
|
+
}
|
|
208
|
+
return {};
|
|
209
|
+
}
|
|
210
|
+
/** Generate a random state value for CSRF protection. */
|
|
211
|
+
export function generateState() {
|
|
212
|
+
return randomBytes(16).toString("hex");
|
|
213
|
+
}
|
|
214
|
+
/** Generate a PKCE (RFC 7636) verifier/challenge pair with S256. */
|
|
215
|
+
export function generatePkce() {
|
|
216
|
+
const codeVerifier = randomBytes(32)
|
|
217
|
+
.toString("base64url")
|
|
218
|
+
.replace(/[^A-Za-z0-9\-._~]/g, "")
|
|
219
|
+
.slice(0, 128);
|
|
220
|
+
const codeChallenge = createHash("sha256").update(codeVerifier).digest("base64url");
|
|
221
|
+
return { codeVerifier, codeChallenge };
|
|
222
|
+
}
|
|
223
|
+
//# sourceMappingURL=oauth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauth.js","sourceRoot":"","sources":["../../src/auth/oauth.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/** A fully-authenticated account record persisted to disk. */
|
|
2
|
+
export interface StoredAccount {
|
|
3
|
+
/** Sanitized account name (the identifier used in tool calls). */
|
|
4
|
+
name: string;
|
|
5
|
+
/** Email address returned by Google at authorization time, if available. */
|
|
6
|
+
email?: string;
|
|
7
|
+
accessToken: string;
|
|
8
|
+
refreshToken: string;
|
|
9
|
+
/** Epoch milliseconds after which accessToken is no longer valid. */
|
|
10
|
+
expiryDate?: number;
|
|
11
|
+
scopes: string[];
|
|
12
|
+
createdAt: string;
|
|
13
|
+
}
|
|
14
|
+
export declare function saveAccount(account: StoredAccount): Promise<void>;
|
|
15
|
+
export declare function loadAccount(name: string): Promise<StoredAccount | null>;
|
|
16
|
+
export declare function loadAllAccounts(): Promise<StoredAccount[]>;
|
|
17
|
+
export declare function deleteAccount(name: string): Promise<boolean>;
|
|
18
|
+
//# sourceMappingURL=store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/auth/store.ts"],"names":[],"mappings":"AAIA,8DAA8D;AAC9D,MAAM,WAAW,aAAa;IAC5B,kEAAkE;IAClE,IAAI,EAAE,MAAM,CAAC;IACb,4EAA4E;IAC5E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,qEAAqE;IACrE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,wBAAsB,WAAW,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAUvE;AAED,wBAAsB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAO7E;AAED,wBAAsB,eAAe,IAAI,OAAO,CAAC,aAAa,EAAE,CAAC,CAchE;AAED,wBAAsB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAOlE"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { ensureDirs, getAccountPath, getAccountsDir, sanitizeName } from "./config.js";
|
|
4
|
+
export async function saveAccount(account) {
|
|
5
|
+
await ensureDirs();
|
|
6
|
+
const safeName = sanitizeName(account.name);
|
|
7
|
+
const toWrite = { ...account, name: safeName };
|
|
8
|
+
await fs.writeFile(getAccountPath(safeName), JSON.stringify(toWrite, null, 2), {
|
|
9
|
+
encoding: "utf8",
|
|
10
|
+
mode: 0o600,
|
|
11
|
+
});
|
|
12
|
+
// writeFile's mode only applies on create; harden pre-existing files.
|
|
13
|
+
await fs.chmod(getAccountPath(safeName), 0o600).catch(() => { });
|
|
14
|
+
}
|
|
15
|
+
export async function loadAccount(name) {
|
|
16
|
+
try {
|
|
17
|
+
const raw = await fs.readFile(getAccountPath(name), "utf8");
|
|
18
|
+
return JSON.parse(raw);
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
export async function loadAllAccounts() {
|
|
25
|
+
await ensureDirs();
|
|
26
|
+
const files = await fs.readdir(getAccountsDir());
|
|
27
|
+
const accounts = [];
|
|
28
|
+
for (const file of files) {
|
|
29
|
+
if (!file.endsWith(".json"))
|
|
30
|
+
continue;
|
|
31
|
+
try {
|
|
32
|
+
const raw = await fs.readFile(path.join(getAccountsDir(), file), "utf8");
|
|
33
|
+
accounts.push(JSON.parse(raw));
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
// Corrupt or unreadable account file — skip it rather than crash the server.
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return accounts.sort((a, b) => a.name.localeCompare(b.name));
|
|
40
|
+
}
|
|
41
|
+
export async function deleteAccount(name) {
|
|
42
|
+
try {
|
|
43
|
+
await fs.unlink(getAccountPath(name));
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/auth/store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAClC,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAgBvF,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,OAAsB;IACtD,MAAM,UAAU,EAAE,CAAC;IACnB,MAAM,QAAQ,GAAG,YAAY,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5C,MAAM,OAAO,GAAkB,EAAE,GAAG,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;IAC9D,MAAM,EAAE,CAAC,SAAS,CAAC,cAAc,CAAC,QAAQ,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE;QAC7E,QAAQ,EAAE,MAAM;QAChB,IAAI,EAAE,KAAK;KACZ,CAAC,CAAC;IACH,sEAAsE;IACtE,MAAM,EAAE,CAAC,KAAK,CAAC,cAAc,CAAC,QAAQ,CAAC,EAAE,KAAK,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;AAClE,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,IAAY;IAC5C,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,cAAc,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,CAAC;QAC5D,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAkB,CAAC;IAC1C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,eAAe;IACnC,MAAM,UAAU,EAAE,CAAC;IACnB,MAAM,KAAK,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,cAAc,EAAE,CAAC,CAAC;IACjD,MAAM,QAAQ,GAAoB,EAAE,CAAC;IACrC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC;YAAE,SAAS;QACtC,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,CAAC;YACzE,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAkB,CAAC,CAAC;QAClD,CAAC;QAAC,MAAM,CAAC;YACP,6EAA6E;QAC/E,CAAC;IACH,CAAC;IACD,OAAO,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;AAC/D,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,IAAY;IAC9C,IAAI,CAAC;QACH,MAAM,EAAE,CAAC,MAAM,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC;QACtC,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC"}
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB;AAyCD,sEAAsE;AACtE,wBAAsB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,SAAS,CAAC,CA0FxE"}
|