terra-mcp-google 0.1.11
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 +57 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +94 -0
- package/dist/cli.js.map +1 -0
- package/dist/config/constants.d.ts +43 -0
- package/dist/config/constants.js +54 -0
- package/dist/config/constants.js.map +1 -0
- package/dist/core/local-file.d.ts +3 -0
- package/dist/core/local-file.js +50 -0
- package/dist/core/local-file.js.map +1 -0
- package/dist/core/result.d.ts +44 -0
- package/dist/core/result.js +104 -0
- package/dist/core/result.js.map +1 -0
- package/dist/core/tool.d.ts +61 -0
- package/dist/core/tool.js +63 -0
- package/dist/core/tool.js.map +1 -0
- package/dist/google/auth.d.ts +47 -0
- package/dist/google/auth.js +256 -0
- package/dist/google/auth.js.map +1 -0
- package/dist/google/client.d.ts +11 -0
- package/dist/google/client.js +14 -0
- package/dist/google/client.js.map +1 -0
- package/dist/google/generated/oauth-client.d.ts +5 -0
- package/dist/google/generated/oauth-client.js +2 -0
- package/dist/google/generated/oauth-client.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -0
- package/dist/services/auth/tools.d.ts +2 -0
- package/dist/services/auth/tools.js +34 -0
- package/dist/services/auth/tools.js.map +1 -0
- package/dist/services/drive/adapter.d.ts +56 -0
- package/dist/services/drive/adapter.js +168 -0
- package/dist/services/drive/adapter.js.map +1 -0
- package/dist/services/drive/tools.d.ts +65 -0
- package/dist/services/drive/tools.js +324 -0
- package/dist/services/drive/tools.js.map +1 -0
- package/dist/services/registry.d.ts +24 -0
- package/dist/services/registry.js +57 -0
- package/dist/services/registry.js.map +1 -0
- package/dist/services/sheets/adapter.d.ts +112 -0
- package/dist/services/sheets/adapter.js +174 -0
- package/dist/services/sheets/adapter.js.map +1 -0
- package/dist/services/sheets/tools.d.ts +118 -0
- package/dist/services/sheets/tools.js +547 -0
- package/dist/services/sheets/tools.js.map +1 -0
- package/dist/setup/setup.d.ts +31 -0
- package/dist/setup/setup.js +179 -0
- package/dist/setup/setup.js.map +1 -0
- package/package.json +61 -0
- package/scripts/install.sh +16 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { google } from "googleapis";
|
|
2
|
+
export type OAuth2Client = InstanceType<typeof google.auth.OAuth2>;
|
|
3
|
+
type Credentials = Parameters<OAuth2Client["setCredentials"]>[0];
|
|
4
|
+
interface ClientSecret {
|
|
5
|
+
client_id: string;
|
|
6
|
+
client_secret?: string;
|
|
7
|
+
}
|
|
8
|
+
export interface AuthStatus {
|
|
9
|
+
authenticated: boolean;
|
|
10
|
+
email?: string;
|
|
11
|
+
scopes?: string[];
|
|
12
|
+
expiryDate?: number;
|
|
13
|
+
}
|
|
14
|
+
export interface LoginOptions {
|
|
15
|
+
/** Open the system browser automatically (default true). */
|
|
16
|
+
openBrowser?: boolean;
|
|
17
|
+
/** Called with the consent URL (e.g. to print it for manual opening). */
|
|
18
|
+
onUrl?: (url: string) => void;
|
|
19
|
+
/** Abort the flow after this many ms (default 5 minutes). */
|
|
20
|
+
timeoutMs?: number;
|
|
21
|
+
}
|
|
22
|
+
export interface LoginResult {
|
|
23
|
+
email?: string;
|
|
24
|
+
scopes: string[];
|
|
25
|
+
}
|
|
26
|
+
/** Read and normalize the OAuth client config (embedded client or local JSON). */
|
|
27
|
+
export declare function readClientSecret(): Promise<ClientSecret>;
|
|
28
|
+
/** Load cached token credentials, or null if none saved. */
|
|
29
|
+
export declare function loadToken(): Promise<Credentials | null>;
|
|
30
|
+
/** Persist token credentials to disk (creates the config dir if needed). */
|
|
31
|
+
export declare function saveToken(credentials: Credentials): Promise<void>;
|
|
32
|
+
/** Delete the cached token. Returns true if a token existed. */
|
|
33
|
+
export declare function clearToken(): Promise<boolean>;
|
|
34
|
+
/**
|
|
35
|
+
* Build an authorized OAuth2 client from the cached token. Refreshes the access
|
|
36
|
+
* token automatically and persists any refreshed credentials.
|
|
37
|
+
* @throws NotAuthenticatedError if no token is cached.
|
|
38
|
+
*/
|
|
39
|
+
export declare function getAuthenticatedClient(): Promise<OAuth2Client>;
|
|
40
|
+
/** Report whether we have working credentials and for which account. */
|
|
41
|
+
export declare function getAuthStatus(): Promise<AuthStatus>;
|
|
42
|
+
/**
|
|
43
|
+
* Run the interactive OAuth loopback flow: start a localhost listener, open the
|
|
44
|
+
* consent page, capture the redirect, exchange the code, and cache the token.
|
|
45
|
+
*/
|
|
46
|
+
export declare function runLoginFlow(options?: LoginOptions): Promise<LoginResult>;
|
|
47
|
+
export {};
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import { createServer } from "node:http";
|
|
2
|
+
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
3
|
+
import open from "open";
|
|
4
|
+
import { Auth, google } from "googleapis";
|
|
5
|
+
import { CLIENT_SECRET_PATH, CONFIG_DIR, PROXY_SHARED_KEY, SCOPES, TOKEN_PATH, TOKEN_PROXY_URL, } from "../config/constants.js";
|
|
6
|
+
import { NotAuthenticatedError } from "../core/result.js";
|
|
7
|
+
import { EMBEDDED_OAUTH_CLIENT } from "./generated/oauth-client.js";
|
|
8
|
+
const SUCCESS_HTML = (email) => `<!doctype html><html><head><meta charset="utf-8"></head><body style="font-family:system-ui;text-align:center;padding:3rem">
|
|
9
|
+
<h2>✅ Signed in${email ? ` as ${email}` : ""}</h2>
|
|
10
|
+
<p>You can close this tab and return to your terminal / MCP client.</p>
|
|
11
|
+
</body></html>`;
|
|
12
|
+
// ── Token & OAuth client persistence ─────────────────────────────────────────
|
|
13
|
+
/** Read and normalize the OAuth client config (embedded client or local JSON). */
|
|
14
|
+
export async function readClientSecret() {
|
|
15
|
+
const explicitCredentialsPath = process.env.GOOGLE_OAUTH_CREDENTIALS !== undefined;
|
|
16
|
+
if (!explicitCredentialsPath && EMBEDDED_OAUTH_CLIENT) {
|
|
17
|
+
return EMBEDDED_OAUTH_CLIENT;
|
|
18
|
+
}
|
|
19
|
+
let raw;
|
|
20
|
+
try {
|
|
21
|
+
raw = await readFile(CLIENT_SECRET_PATH, "utf8");
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
throw new NotAuthenticatedError(`No OAuth client config found at ${CLIENT_SECRET_PATH}. ` +
|
|
25
|
+
`Use a package built with the embedded OAuth client, place one there, ` +
|
|
26
|
+
`or set GOOGLE_OAUTH_CREDENTIALS to its path.`);
|
|
27
|
+
}
|
|
28
|
+
const parsed = JSON.parse(raw);
|
|
29
|
+
const node = parsed.installed ?? parsed.web;
|
|
30
|
+
if (!node?.client_id) {
|
|
31
|
+
throw new NotAuthenticatedError(`OAuth client config at ${CLIENT_SECRET_PATH} is missing client_id. ` +
|
|
32
|
+
`Download a fresh OAuth client from Google Cloud Console.`);
|
|
33
|
+
}
|
|
34
|
+
return { client_id: node.client_id, client_secret: node.client_secret };
|
|
35
|
+
}
|
|
36
|
+
/** Load cached token credentials, or null if none saved. */
|
|
37
|
+
export async function loadToken() {
|
|
38
|
+
try {
|
|
39
|
+
return JSON.parse(await readFile(TOKEN_PATH, "utf8"));
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/** Persist token credentials to disk (creates the config dir if needed). */
|
|
46
|
+
export async function saveToken(credentials) {
|
|
47
|
+
await mkdir(CONFIG_DIR, { recursive: true });
|
|
48
|
+
await writeFile(TOKEN_PATH, JSON.stringify(credentials, null, 2), { mode: 0o600 });
|
|
49
|
+
}
|
|
50
|
+
/** Delete the cached token. Returns true if a token existed. */
|
|
51
|
+
export async function clearToken() {
|
|
52
|
+
try {
|
|
53
|
+
await rm(TOKEN_PATH);
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
// ── Authorized clients & status ───────────────────────────────────────────────
|
|
61
|
+
function createBaseClient(secret, redirectUri) {
|
|
62
|
+
if (secret.client_secret) {
|
|
63
|
+
return new google.auth.OAuth2(secret.client_id, secret.client_secret, redirectUri);
|
|
64
|
+
}
|
|
65
|
+
return new google.auth.OAuth2({
|
|
66
|
+
clientId: secret.client_id,
|
|
67
|
+
redirectUri,
|
|
68
|
+
clientAuthentication: Auth.ClientAuthentication.None,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Exchange an auth code or refresh token for credentials via the token proxy.
|
|
73
|
+
* The proxy holds the Google client_secret (Desktop clients require it even with
|
|
74
|
+
* PKCE); we send only the grant + PKCE params, never a secret.
|
|
75
|
+
*/
|
|
76
|
+
async function proxyTokenExchange(body) {
|
|
77
|
+
let res;
|
|
78
|
+
try {
|
|
79
|
+
res = await fetch(TOKEN_PROXY_URL, {
|
|
80
|
+
method: "POST",
|
|
81
|
+
headers: { "content-type": "application/json", "x-proxy-key": PROXY_SHARED_KEY },
|
|
82
|
+
body: JSON.stringify(body),
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
catch (cause) {
|
|
86
|
+
throw new Error(`Could not reach the OAuth token proxy at ${TOKEN_PROXY_URL}.`, { cause });
|
|
87
|
+
}
|
|
88
|
+
const data = (await res.json().catch(() => ({})));
|
|
89
|
+
if (!res.ok || data.error || !data.access_token) {
|
|
90
|
+
const detail = data.error_description ?? data.error ?? `HTTP ${res.status}`;
|
|
91
|
+
throw new Error(`Token exchange failed: ${detail}`);
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
access_token: data.access_token,
|
|
95
|
+
refresh_token: data.refresh_token,
|
|
96
|
+
scope: data.scope,
|
|
97
|
+
token_type: data.token_type,
|
|
98
|
+
id_token: data.id_token,
|
|
99
|
+
expiry_date: data.expires_in ? Date.now() + data.expires_in * 1000 : undefined,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
/** Fetch the signed-in user's email, best-effort. */
|
|
103
|
+
async function fetchEmail(client) {
|
|
104
|
+
try {
|
|
105
|
+
const oauth2 = google.oauth2({ version: "v2", auth: client });
|
|
106
|
+
const { data } = await oauth2.userinfo.get();
|
|
107
|
+
return data.email ?? undefined;
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
return undefined;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Build an authorized OAuth2 client from the cached token. Refreshes the access
|
|
115
|
+
* token automatically and persists any refreshed credentials.
|
|
116
|
+
* @throws NotAuthenticatedError if no token is cached.
|
|
117
|
+
*/
|
|
118
|
+
export async function getAuthenticatedClient() {
|
|
119
|
+
const secret = await readClientSecret();
|
|
120
|
+
const token = await loadToken();
|
|
121
|
+
if (!token) {
|
|
122
|
+
throw new NotAuthenticatedError("No saved Google credentials.");
|
|
123
|
+
}
|
|
124
|
+
const client = createBaseClient(secret);
|
|
125
|
+
client.setCredentials(token);
|
|
126
|
+
// Refresh through the proxy: Google needs the client_secret to refresh a
|
|
127
|
+
// confidential (Desktop) client, and we don't hold it. Setting refreshHandler
|
|
128
|
+
// makes google-auth-library call us instead of doing its own secret-based
|
|
129
|
+
// refresh. Persist the refreshed access token so it survives restarts.
|
|
130
|
+
client.refreshHandler = async () => {
|
|
131
|
+
if (!token.refresh_token) {
|
|
132
|
+
throw new NotAuthenticatedError("No refresh token cached — run `terra-mcp auth login` again.");
|
|
133
|
+
}
|
|
134
|
+
const fresh = await proxyTokenExchange({
|
|
135
|
+
grant_type: "refresh_token",
|
|
136
|
+
refresh_token: token.refresh_token,
|
|
137
|
+
});
|
|
138
|
+
await saveToken({ ...token, ...fresh });
|
|
139
|
+
if (!fresh.access_token || fresh.expiry_date == null) {
|
|
140
|
+
throw new Error("Token proxy returned no access token on refresh.");
|
|
141
|
+
}
|
|
142
|
+
return { access_token: fresh.access_token, expiry_date: fresh.expiry_date };
|
|
143
|
+
};
|
|
144
|
+
return client;
|
|
145
|
+
}
|
|
146
|
+
/** Report whether we have working credentials and for which account. */
|
|
147
|
+
export async function getAuthStatus() {
|
|
148
|
+
const token = await loadToken();
|
|
149
|
+
if (!token)
|
|
150
|
+
return { authenticated: false };
|
|
151
|
+
let email;
|
|
152
|
+
try {
|
|
153
|
+
email = await fetchEmail(await getAuthenticatedClient());
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
// token present but OAuth client config unreadable; still report as cached
|
|
157
|
+
}
|
|
158
|
+
return {
|
|
159
|
+
authenticated: true,
|
|
160
|
+
email,
|
|
161
|
+
scopes: token.scope?.split(" "),
|
|
162
|
+
expiryDate: token.expiry_date ?? undefined,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
// ── Interactive login ─────────────────────────────────────────────────────────
|
|
166
|
+
/**
|
|
167
|
+
* Run the interactive OAuth loopback flow: start a localhost listener, open the
|
|
168
|
+
* consent page, capture the redirect, exchange the code, and cache the token.
|
|
169
|
+
*/
|
|
170
|
+
export async function runLoginFlow(options = {}) {
|
|
171
|
+
const { openBrowser = true, onUrl, timeoutMs = 300_000 } = options;
|
|
172
|
+
const secret = await readClientSecret();
|
|
173
|
+
return await new Promise((resolve, reject) => {
|
|
174
|
+
let client;
|
|
175
|
+
let redirectUri = "";
|
|
176
|
+
let codeVerifier = "";
|
|
177
|
+
const server = createServer((req, res) => {
|
|
178
|
+
void (async () => {
|
|
179
|
+
const url = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
180
|
+
if (!url.pathname.startsWith("/oauth2callback")) {
|
|
181
|
+
res.writeHead(404).end("Not found");
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
const authError = url.searchParams.get("error");
|
|
185
|
+
const code = url.searchParams.get("code");
|
|
186
|
+
try {
|
|
187
|
+
if (authError)
|
|
188
|
+
throw new Error(`Authorization denied: ${authError}`);
|
|
189
|
+
if (!code || !client)
|
|
190
|
+
throw new Error("Missing authorization code in callback.");
|
|
191
|
+
const tokens = await proxyTokenExchange({
|
|
192
|
+
grant_type: "authorization_code",
|
|
193
|
+
code,
|
|
194
|
+
code_verifier: codeVerifier,
|
|
195
|
+
redirect_uri: redirectUri,
|
|
196
|
+
});
|
|
197
|
+
client.setCredentials(tokens);
|
|
198
|
+
await saveToken(tokens);
|
|
199
|
+
const email = await fetchEmail(client);
|
|
200
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" }).end(SUCCESS_HTML(email));
|
|
201
|
+
finish();
|
|
202
|
+
resolve({ email, scopes: tokens.scope?.split(" ") ?? SCOPES });
|
|
203
|
+
}
|
|
204
|
+
catch (err) {
|
|
205
|
+
res.writeHead(500, { "Content-Type": "text/plain" }).end("Login failed. Check the terminal.");
|
|
206
|
+
finish();
|
|
207
|
+
reject(err);
|
|
208
|
+
}
|
|
209
|
+
})();
|
|
210
|
+
});
|
|
211
|
+
const timer = setTimeout(() => {
|
|
212
|
+
finish();
|
|
213
|
+
reject(new Error("Login timed out — no response within the allotted time."));
|
|
214
|
+
}, timeoutMs);
|
|
215
|
+
function finish() {
|
|
216
|
+
clearTimeout(timer);
|
|
217
|
+
server.close();
|
|
218
|
+
}
|
|
219
|
+
server.on("error", (err) => {
|
|
220
|
+
finish();
|
|
221
|
+
reject(err);
|
|
222
|
+
});
|
|
223
|
+
server.listen(0, "127.0.0.1", () => {
|
|
224
|
+
const port = server.address().port;
|
|
225
|
+
redirectUri = `http://127.0.0.1:${port}/oauth2callback`;
|
|
226
|
+
const oauthClient = createBaseClient(secret, redirectUri);
|
|
227
|
+
client = oauthClient;
|
|
228
|
+
void oauthClient.generateCodeVerifierAsync().then(({ codeVerifier: verifier, codeChallenge }) => {
|
|
229
|
+
if (!codeChallenge)
|
|
230
|
+
throw new Error("Failed to generate OAuth PKCE challenge.");
|
|
231
|
+
codeVerifier = verifier;
|
|
232
|
+
const authUrl = oauthClient.generateAuthUrl({
|
|
233
|
+
access_type: "offline",
|
|
234
|
+
prompt: "consent",
|
|
235
|
+
scope: SCOPES,
|
|
236
|
+
code_challenge: codeChallenge,
|
|
237
|
+
code_challenge_method: Auth.CodeChallengeMethod.S256,
|
|
238
|
+
// Force per-scope checkboxes so the user can grant Drive and Sheets
|
|
239
|
+
// independently. Desktop/pre-2019 clients don't get them by default;
|
|
240
|
+
// a no-op once Google has auto-enabled granular consent for the client.
|
|
241
|
+
enable_granular_consent: true,
|
|
242
|
+
});
|
|
243
|
+
onUrl?.(authUrl);
|
|
244
|
+
if (openBrowser) {
|
|
245
|
+
void open(authUrl).catch(() => {
|
|
246
|
+
/* user can open the URL manually via onUrl */
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
}).catch((err) => {
|
|
250
|
+
finish();
|
|
251
|
+
reject(err);
|
|
252
|
+
});
|
|
253
|
+
});
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
//# sourceMappingURL=auth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/google/auth.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type drive_v3, type sheets_v4 } from "googleapis";
|
|
2
|
+
export interface GoogleClients {
|
|
3
|
+
drive: drive_v3.Drive;
|
|
4
|
+
sheets: sheets_v4.Sheets;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Build authorized Drive + Sheets API clients from the cached OAuth token.
|
|
8
|
+
* @throws NotAuthenticatedError if the user has not logged in.
|
|
9
|
+
*/
|
|
10
|
+
export declare function getGoogleClients(): Promise<GoogleClients>;
|
|
11
|
+
export type { drive_v3, sheets_v4 };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { google } from "googleapis";
|
|
2
|
+
import { getAuthenticatedClient } from "./auth.js";
|
|
3
|
+
/**
|
|
4
|
+
* Build authorized Drive + Sheets API clients from the cached OAuth token.
|
|
5
|
+
* @throws NotAuthenticatedError if the user has not logged in.
|
|
6
|
+
*/
|
|
7
|
+
export async function getGoogleClients() {
|
|
8
|
+
const auth = await getAuthenticatedClient();
|
|
9
|
+
return {
|
|
10
|
+
drive: google.drive({ version: "v3", auth }),
|
|
11
|
+
sheets: google.sheets({ version: "v4", auth }),
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/google/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAiC,MAAM,YAAY,CAAC;AACnE,OAAO,EAAE,sBAAsB,EAAE,MAAM,WAAW,CAAC;AAOnD;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB;IACpC,MAAM,IAAI,GAAG,MAAM,sBAAsB,EAAE,CAAC;IAC5C,OAAO;QACL,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;QAC5C,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;KAC/C,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauth-client.js","sourceRoot":"","sources":["../../../src/google/generated/oauth-client.ts"],"names":[],"mappings":"AAKA,MAAM,CAAC,MAAM,qBAAqB,GAA+B,IAAI,CAAC"}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
4
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
5
|
+
import { SAFE_MODE, SERVER_NAME, SERVER_VERSION } from "./config/constants.js";
|
|
6
|
+
import { registerGoogleTools } from "./services/registry.js";
|
|
7
|
+
export async function startServer() {
|
|
8
|
+
const server = new McpServer({
|
|
9
|
+
name: SERVER_NAME,
|
|
10
|
+
version: SERVER_VERSION,
|
|
11
|
+
});
|
|
12
|
+
await registerGoogleTools(server, { safeMode: SAFE_MODE });
|
|
13
|
+
const transport = new StdioServerTransport();
|
|
14
|
+
await server.connect(transport);
|
|
15
|
+
// Log to stderr so it doesn't corrupt the stdio JSON-RPC stream.
|
|
16
|
+
console.error(`${SERVER_NAME} v${SERVER_VERSION} running on stdio${SAFE_MODE ? " (safe mode: dangerous tools disabled)" : ""}`);
|
|
17
|
+
}
|
|
18
|
+
if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) {
|
|
19
|
+
startServer().catch((error) => {
|
|
20
|
+
console.error("Fatal error starting kozocom-google MCP server:", error);
|
|
21
|
+
process.exit(1);
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAC/E,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAE7D,MAAM,CAAC,KAAK,UAAU,WAAW;IAC/B,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;QAC3B,IAAI,EAAE,WAAW;QACjB,OAAO,EAAE,cAAc;KACxB,CAAC,CAAC;IACH,MAAM,mBAAmB,CAAC,MAAM,EAAE,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC,CAAC;IAE3D,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAChC,iEAAiE;IACjE,OAAO,CAAC,KAAK,CACX,GAAG,WAAW,KAAK,cAAc,oBAAoB,SAAS,CAAC,CAAC,CAAC,wCAAwC,CAAC,CAAC,CAAC,EAAE,EAAE,CACjH,CAAC;AACJ,CAAC;AAED,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;IAC1E,WAAW,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;QAC5B,OAAO,CAAC,KAAK,CAAC,iDAAiD,EAAE,KAAK,CAAC,CAAC;QACxE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { getAuthStatus } from "../../google/auth.js";
|
|
2
|
+
import { toolResult } from "../../core/result.js";
|
|
3
|
+
import { tool } from "../../core/tool.js";
|
|
4
|
+
const authStatusTool = tool({
|
|
5
|
+
name: "google_auth_status",
|
|
6
|
+
title: "Check Google sign-in status",
|
|
7
|
+
description: `Report whether the server has working Google credentials and for which account.
|
|
8
|
+
|
|
9
|
+
Args: none.
|
|
10
|
+
|
|
11
|
+
Returns:
|
|
12
|
+
{
|
|
13
|
+
"authenticated": boolean,
|
|
14
|
+
"email": string|null, // signed-in account
|
|
15
|
+
"scopes": string[]|null, // granted OAuth scopes
|
|
16
|
+
"expiry_date": number|null // access-token expiry (epoch ms); auto-refreshed when valid
|
|
17
|
+
}`,
|
|
18
|
+
inputSchema: {},
|
|
19
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
20
|
+
run: async () => {
|
|
21
|
+
const status = await getAuthStatus();
|
|
22
|
+
const text = status.authenticated
|
|
23
|
+
? `Authenticated${status.email ? ` as ${status.email}` : ""}.`
|
|
24
|
+
: "Not authenticated. Run `terra-mcp auth login` in a terminal to sign in.";
|
|
25
|
+
return toolResult(text, {
|
|
26
|
+
authenticated: status.authenticated,
|
|
27
|
+
email: status.email ?? null,
|
|
28
|
+
scopes: status.scopes ?? null,
|
|
29
|
+
expiry_date: status.expiryDate ?? null,
|
|
30
|
+
});
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
export const authTools = [authStatusTool];
|
|
34
|
+
//# sourceMappingURL=tools.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tools.js","sourceRoot":"","sources":["../../../src/services/auth/tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAClD,OAAO,EAAE,IAAI,EAAyB,MAAM,oBAAoB,CAAC;AAEjE,MAAM,cAAc,GAAG,IAAI,CAAC;IAC1B,IAAI,EAAE,oBAAoB;IAC1B,KAAK,EAAE,6BAA6B;IACpC,WAAW,EAAE;;;;;;;;;;IAUX;IACF,WAAW,EAAE,EAAE;IACf,WAAW,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE,eAAe,EAAE,KAAK,EAAE,cAAc,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE;IACtG,GAAG,EAAE,KAAK,IAAI,EAAE;QACd,MAAM,MAAM,GAAG,MAAM,aAAa,EAAE,CAAC;QACrC,MAAM,IAAI,GAAG,MAAM,CAAC,aAAa;YAC/B,CAAC,CAAC,gBAAgB,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG;YAC9D,CAAC,CAAC,yEAAyE,CAAC;QAC9E,OAAO,UAAU,CAAC,IAAI,EAAE;YACtB,aAAa,EAAE,MAAM,CAAC,aAAa;YACnC,KAAK,EAAE,MAAM,CAAC,KAAK,IAAI,IAAI;YAC3B,MAAM,EAAE,MAAM,CAAC,MAAM,IAAI,IAAI;YAC7B,WAAW,EAAE,MAAM,CAAC,UAAU,IAAI,IAAI;SACvC,CAAC,CAAC;IACL,CAAC;CACF,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,SAAS,GAAgC,CAAC,cAAc,CAAC,CAAC"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { drive_v3 } from "googleapis";
|
|
2
|
+
export interface DriveDownloadResult {
|
|
3
|
+
fileId: string;
|
|
4
|
+
name?: string | null;
|
|
5
|
+
mimeType: string;
|
|
6
|
+
bytes: number;
|
|
7
|
+
content?: string;
|
|
8
|
+
savedTo?: string;
|
|
9
|
+
binaryRequiresSavePath: boolean;
|
|
10
|
+
}
|
|
11
|
+
export declare class DriveFileAdapter {
|
|
12
|
+
private readonly drive;
|
|
13
|
+
constructor(drive: drive_v3.Drive);
|
|
14
|
+
listFiles(args: {
|
|
15
|
+
query?: string;
|
|
16
|
+
pageSize: number;
|
|
17
|
+
pageToken?: string;
|
|
18
|
+
orderBy?: string;
|
|
19
|
+
includeTrashed: boolean;
|
|
20
|
+
}): Promise<{
|
|
21
|
+
files: drive_v3.Schema$File[];
|
|
22
|
+
nextPageToken: string | null;
|
|
23
|
+
}>;
|
|
24
|
+
getFile(fileId: string): Promise<drive_v3.Schema$File>;
|
|
25
|
+
downloadFile(args: {
|
|
26
|
+
fileId: string;
|
|
27
|
+
exportMimeType?: string;
|
|
28
|
+
savePath?: string;
|
|
29
|
+
}): Promise<DriveDownloadResult>;
|
|
30
|
+
createFolder(args: {
|
|
31
|
+
name: string;
|
|
32
|
+
parentId?: string;
|
|
33
|
+
}): Promise<drive_v3.Schema$File>;
|
|
34
|
+
uploadFile(args: {
|
|
35
|
+
name: string;
|
|
36
|
+
content?: string;
|
|
37
|
+
localPath?: string;
|
|
38
|
+
mimeType?: string;
|
|
39
|
+
parentId?: string;
|
|
40
|
+
}): Promise<drive_v3.Schema$File>;
|
|
41
|
+
updateFile(args: {
|
|
42
|
+
fileId: string;
|
|
43
|
+
newName?: string;
|
|
44
|
+
addParents?: string;
|
|
45
|
+
removeParents?: string;
|
|
46
|
+
content?: string;
|
|
47
|
+
mimeType?: string;
|
|
48
|
+
}): Promise<drive_v3.Schema$File>;
|
|
49
|
+
copyFile(args: {
|
|
50
|
+
fileId: string;
|
|
51
|
+
name?: string;
|
|
52
|
+
parentId?: string;
|
|
53
|
+
}): Promise<drive_v3.Schema$File>;
|
|
54
|
+
deleteFile(fileId: string): Promise<void>;
|
|
55
|
+
trashFile(fileId: string): Promise<drive_v3.Schema$File>;
|
|
56
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { createReadStream } from "node:fs";
|
|
2
|
+
import { writeFile } from "node:fs/promises";
|
|
3
|
+
import { Readable } from "node:stream";
|
|
4
|
+
import { safeReadPath, safeWritePath } from "../../core/local-file.js";
|
|
5
|
+
const DRIVE_FILE_FIELDS = "id, name, mimeType, modifiedTime, createdTime, size, parents, trashed, webViewLink, iconLink, owners(displayName, emailAddress)";
|
|
6
|
+
const DRIVE_LIST_FIELDS = `nextPageToken, files(${DRIVE_FILE_FIELDS})`;
|
|
7
|
+
export class DriveFileAdapter {
|
|
8
|
+
drive;
|
|
9
|
+
constructor(drive) {
|
|
10
|
+
this.drive = drive;
|
|
11
|
+
}
|
|
12
|
+
async listFiles(args) {
|
|
13
|
+
const q = [args.query, args.includeTrashed ? undefined : "trashed = false"]
|
|
14
|
+
.filter(Boolean)
|
|
15
|
+
.join(" and ");
|
|
16
|
+
const { data } = await this.drive.files.list({
|
|
17
|
+
q: q || undefined,
|
|
18
|
+
pageSize: args.pageSize,
|
|
19
|
+
pageToken: args.pageToken,
|
|
20
|
+
orderBy: args.orderBy,
|
|
21
|
+
fields: DRIVE_LIST_FIELDS,
|
|
22
|
+
spaces: "drive",
|
|
23
|
+
supportsAllDrives: true,
|
|
24
|
+
includeItemsFromAllDrives: true,
|
|
25
|
+
});
|
|
26
|
+
return { files: data.files ?? [], nextPageToken: data.nextPageToken ?? null };
|
|
27
|
+
}
|
|
28
|
+
async getFile(fileId) {
|
|
29
|
+
const { data } = await this.drive.files.get({
|
|
30
|
+
fileId,
|
|
31
|
+
fields: DRIVE_FILE_FIELDS,
|
|
32
|
+
supportsAllDrives: true,
|
|
33
|
+
});
|
|
34
|
+
return data;
|
|
35
|
+
}
|
|
36
|
+
async downloadFile(args) {
|
|
37
|
+
const savePath = args.savePath ? await safeWritePath(args.savePath) : undefined;
|
|
38
|
+
const meta = await this.drive.files.get({
|
|
39
|
+
fileId: args.fileId,
|
|
40
|
+
fields: "id, name, mimeType, size",
|
|
41
|
+
supportsAllDrives: true,
|
|
42
|
+
});
|
|
43
|
+
const native = isGoogleNative(meta.data.mimeType);
|
|
44
|
+
const exportMime = native
|
|
45
|
+
? (args.exportMimeType ?? defaultExportMime(meta.data.mimeType ?? ""))
|
|
46
|
+
: undefined;
|
|
47
|
+
const res = native
|
|
48
|
+
? await this.drive.files.export({ fileId: args.fileId, mimeType: exportMime }, { responseType: "arraybuffer" })
|
|
49
|
+
: await this.drive.files.get({ fileId: args.fileId, alt: "media", supportsAllDrives: true }, { responseType: "arraybuffer" });
|
|
50
|
+
const buffer = Buffer.from(res.data);
|
|
51
|
+
const mimeType = native ? exportMime : (meta.data.mimeType ?? "application/octet-stream");
|
|
52
|
+
if (savePath) {
|
|
53
|
+
await writeFile(savePath, buffer);
|
|
54
|
+
return {
|
|
55
|
+
fileId: args.fileId,
|
|
56
|
+
name: meta.data.name,
|
|
57
|
+
mimeType,
|
|
58
|
+
bytes: buffer.byteLength,
|
|
59
|
+
savedTo: savePath,
|
|
60
|
+
binaryRequiresSavePath: false,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
if (!isTextMime(mimeType)) {
|
|
64
|
+
return {
|
|
65
|
+
fileId: args.fileId,
|
|
66
|
+
name: meta.data.name,
|
|
67
|
+
mimeType,
|
|
68
|
+
bytes: buffer.byteLength,
|
|
69
|
+
binaryRequiresSavePath: true,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
return {
|
|
73
|
+
fileId: args.fileId,
|
|
74
|
+
name: meta.data.name,
|
|
75
|
+
mimeType,
|
|
76
|
+
bytes: buffer.byteLength,
|
|
77
|
+
content: buffer.toString("utf8"),
|
|
78
|
+
binaryRequiresSavePath: false,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
async createFolder(args) {
|
|
82
|
+
const { data } = await this.drive.files.create({
|
|
83
|
+
requestBody: {
|
|
84
|
+
name: args.name,
|
|
85
|
+
mimeType: "application/vnd.google-apps.folder",
|
|
86
|
+
...(args.parentId ? { parents: [args.parentId] } : {}),
|
|
87
|
+
},
|
|
88
|
+
fields: DRIVE_FILE_FIELDS,
|
|
89
|
+
supportsAllDrives: true,
|
|
90
|
+
});
|
|
91
|
+
return data;
|
|
92
|
+
}
|
|
93
|
+
async uploadFile(args) {
|
|
94
|
+
const body = args.localPath
|
|
95
|
+
? createReadStream(await safeReadPath(args.localPath))
|
|
96
|
+
: Readable.from(args.content ?? "");
|
|
97
|
+
const { data } = await this.drive.files.create({
|
|
98
|
+
requestBody: {
|
|
99
|
+
name: args.name,
|
|
100
|
+
...(args.parentId ? { parents: [args.parentId] } : {}),
|
|
101
|
+
},
|
|
102
|
+
media: {
|
|
103
|
+
mimeType: args.mimeType ?? "application/octet-stream",
|
|
104
|
+
body,
|
|
105
|
+
},
|
|
106
|
+
fields: DRIVE_FILE_FIELDS,
|
|
107
|
+
supportsAllDrives: true,
|
|
108
|
+
});
|
|
109
|
+
return data;
|
|
110
|
+
}
|
|
111
|
+
async updateFile(args) {
|
|
112
|
+
const { data } = await this.drive.files.update({
|
|
113
|
+
fileId: args.fileId,
|
|
114
|
+
addParents: args.addParents,
|
|
115
|
+
removeParents: args.removeParents,
|
|
116
|
+
requestBody: args.newName ? { name: args.newName } : {},
|
|
117
|
+
...(args.content !== undefined
|
|
118
|
+
? { media: { mimeType: args.mimeType ?? "text/plain", body: Readable.from(args.content) } }
|
|
119
|
+
: {}),
|
|
120
|
+
fields: DRIVE_FILE_FIELDS,
|
|
121
|
+
supportsAllDrives: true,
|
|
122
|
+
});
|
|
123
|
+
return data;
|
|
124
|
+
}
|
|
125
|
+
async copyFile(args) {
|
|
126
|
+
const { data } = await this.drive.files.copy({
|
|
127
|
+
fileId: args.fileId,
|
|
128
|
+
requestBody: {
|
|
129
|
+
...(args.name ? { name: args.name } : {}),
|
|
130
|
+
...(args.parentId ? { parents: [args.parentId] } : {}),
|
|
131
|
+
},
|
|
132
|
+
fields: DRIVE_FILE_FIELDS,
|
|
133
|
+
supportsAllDrives: true,
|
|
134
|
+
});
|
|
135
|
+
return data;
|
|
136
|
+
}
|
|
137
|
+
async deleteFile(fileId) {
|
|
138
|
+
await this.drive.files.delete({ fileId, supportsAllDrives: true });
|
|
139
|
+
}
|
|
140
|
+
async trashFile(fileId) {
|
|
141
|
+
const { data } = await this.drive.files.update({
|
|
142
|
+
fileId,
|
|
143
|
+
requestBody: { trashed: true },
|
|
144
|
+
fields: "id, name, trashed",
|
|
145
|
+
supportsAllDrives: true,
|
|
146
|
+
});
|
|
147
|
+
return data;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
function defaultExportMime(mimeType) {
|
|
151
|
+
switch (mimeType) {
|
|
152
|
+
case "application/vnd.google-apps.spreadsheet":
|
|
153
|
+
return "text/csv";
|
|
154
|
+
case "application/vnd.google-apps.document":
|
|
155
|
+
return "text/plain";
|
|
156
|
+
case "application/vnd.google-apps.presentation":
|
|
157
|
+
return "application/pdf";
|
|
158
|
+
default:
|
|
159
|
+
return "application/pdf";
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
function isGoogleNative(mimeType) {
|
|
163
|
+
return !!mimeType && mimeType.startsWith("application/vnd.google-apps.");
|
|
164
|
+
}
|
|
165
|
+
function isTextMime(mimeType) {
|
|
166
|
+
return /^(text\/|application\/(json|xml|csv))/.test(mimeType);
|
|
167
|
+
}
|
|
168
|
+
//# sourceMappingURL=adapter.js.map
|