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.
Files changed (51) hide show
  1. package/README.md +57 -0
  2. package/dist/cli.d.ts +2 -0
  3. package/dist/cli.js +94 -0
  4. package/dist/cli.js.map +1 -0
  5. package/dist/config/constants.d.ts +43 -0
  6. package/dist/config/constants.js +54 -0
  7. package/dist/config/constants.js.map +1 -0
  8. package/dist/core/local-file.d.ts +3 -0
  9. package/dist/core/local-file.js +50 -0
  10. package/dist/core/local-file.js.map +1 -0
  11. package/dist/core/result.d.ts +44 -0
  12. package/dist/core/result.js +104 -0
  13. package/dist/core/result.js.map +1 -0
  14. package/dist/core/tool.d.ts +61 -0
  15. package/dist/core/tool.js +63 -0
  16. package/dist/core/tool.js.map +1 -0
  17. package/dist/google/auth.d.ts +47 -0
  18. package/dist/google/auth.js +256 -0
  19. package/dist/google/auth.js.map +1 -0
  20. package/dist/google/client.d.ts +11 -0
  21. package/dist/google/client.js +14 -0
  22. package/dist/google/client.js.map +1 -0
  23. package/dist/google/generated/oauth-client.d.ts +5 -0
  24. package/dist/google/generated/oauth-client.js +2 -0
  25. package/dist/google/generated/oauth-client.js.map +1 -0
  26. package/dist/index.d.ts +2 -0
  27. package/dist/index.js +24 -0
  28. package/dist/index.js.map +1 -0
  29. package/dist/services/auth/tools.d.ts +2 -0
  30. package/dist/services/auth/tools.js +34 -0
  31. package/dist/services/auth/tools.js.map +1 -0
  32. package/dist/services/drive/adapter.d.ts +56 -0
  33. package/dist/services/drive/adapter.js +168 -0
  34. package/dist/services/drive/adapter.js.map +1 -0
  35. package/dist/services/drive/tools.d.ts +65 -0
  36. package/dist/services/drive/tools.js +324 -0
  37. package/dist/services/drive/tools.js.map +1 -0
  38. package/dist/services/registry.d.ts +24 -0
  39. package/dist/services/registry.js +57 -0
  40. package/dist/services/registry.js.map +1 -0
  41. package/dist/services/sheets/adapter.d.ts +112 -0
  42. package/dist/services/sheets/adapter.js +174 -0
  43. package/dist/services/sheets/adapter.js.map +1 -0
  44. package/dist/services/sheets/tools.d.ts +118 -0
  45. package/dist/services/sheets/tools.js +547 -0
  46. package/dist/services/sheets/tools.js.map +1 -0
  47. package/dist/setup/setup.d.ts +31 -0
  48. package/dist/setup/setup.js +179 -0
  49. package/dist/setup/setup.js.map +1 -0
  50. package/package.json +61 -0
  51. package/scripts/install.sh +16 -0
package/README.md ADDED
@@ -0,0 +1,57 @@
1
+ # terra-mcp — Google Drive + Sheets MCP server
2
+
3
+ Read/write access to your **Google Drive** and **Sheets**. OAuth + PKCE login — only the public
4
+ client ID ships in npm, consent happens in browser, token is cached and auto-refreshed.
5
+
6
+ ## Quick start
7
+
8
+ ```bash
9
+ npm install -g terra-mcp-google
10
+ terra-mcp auth login # browser consent, caches token
11
+ terra-mcp client codex # print safe (read-only) MCP config
12
+ ```
13
+
14
+ ## CLI
15
+
16
+ | Command | Does |
17
+ | --- | --- |
18
+ | `auth login` / `logout` / `status` | sign in / out / show account, scopes, expiry |
19
+ | `setup` | check config dir + auth, print MCP config for all clients |
20
+ | `client [codex\|claude\|copilot\|kiro\|all]` | print MCP config with **mutating tools disabled** (`--include-dangerous` keeps them) |
21
+ | *(no command)* | start the stdio server |
22
+
23
+ ## Tools
24
+
25
+ **Auth** — sign-in/out are CLI-only (`terra-mcp auth login`/`logout`).
26
+
27
+ | Tool | Does |
28
+ | --- | --- |
29
+ | `google_auth_status` | show signed-in account, granted scopes, token expiry |
30
+
31
+ **Drive**
32
+
33
+ | Tool | Does |
34
+ | --- | --- |
35
+ | `drive_list_files` | list / search files (Drive `q` query, pagination) |
36
+ | `drive_get_file` | get file metadata by ID |
37
+ | `drive_download_file` | download or export content (Google-native files → csv/txt/pdf/…) |
38
+ | `drive_create_folder` | create a folder |
39
+ | `drive_upload_file` | upload inline text or a local file |
40
+ | `drive_update_file` | rename, move, or replace content |
41
+ | `drive_copy_file` | duplicate a file |
42
+ | `drive_delete_file` | trash (default) or permanently delete |
43
+
44
+ **Sheets**
45
+
46
+ | Tool | Does |
47
+ | --- | --- |
48
+ | `sheets_create_spreadsheet` | create a spreadsheet |
49
+ | `sheets_get_spreadsheet` | list tabs and their dimensions |
50
+ | `sheets_read_range` | read one A1 range, or many at once (pass an array of ranges) |
51
+ | `sheets_write_range` | overwrite values in one A1 range, or many at once (pass `data`) |
52
+ | `sheets_append_rows` | append rows after the last row of a table |
53
+ | `sheets_clear_range` | clear values in a range |
54
+ | `sheets_add_sheet` / `sheets_delete_sheet` | add / remove a tab |
55
+ | `sheets_format_cells` | format a cell range (colors, bold, font size, alignment) |
56
+ | `sheets_set_data_validation` | set a dropdown (list) rule on a range |
57
+ | `sheets_batch_update` | escape hatch: raw `spreadsheets.batchUpdate` requests (merge, borders, sort, …) |
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,94 @@
1
+ #!/usr/bin/env node
2
+ import { Command, Option } from "commander";
3
+ import { clearToken, getAuthStatus, runLoginFlow } from "./google/auth.js";
4
+ import { SERVER_VERSION, TOKEN_PATH } from "./config/constants.js";
5
+ import { startServer } from "./index.js";
6
+ import { configReport, parseClient, runSetup } from "./setup/setup.js";
7
+ const CLIENT_CHOICES = ["codex", "claude", "copilot", "kiro", "all"];
8
+ async function login() {
9
+ console.error("Starting Google sign-in... a browser window will open.\n");
10
+ const result = await runLoginFlow({
11
+ openBrowser: true,
12
+ onUrl: (url) => {
13
+ console.error(`If the browser didn't open, visit:\n${url}\n`);
14
+ },
15
+ });
16
+ console.error(`\nSigned in${result.email ? ` as ${result.email}` : ""}.`);
17
+ console.error(`Token saved to ${TOKEN_PATH}`);
18
+ console.error(`Granted scopes: ${result.scopes.join(", ")}`);
19
+ }
20
+ async function logout() {
21
+ const removed = await clearToken();
22
+ console.error(removed ? `Signed out — cached token deleted (${TOKEN_PATH}).` : "No cached token to remove.");
23
+ }
24
+ async function status() {
25
+ const result = await getAuthStatus();
26
+ if (!result.authenticated) {
27
+ console.error("Not signed in. Run `terra-mcp auth login` to sign in.");
28
+ return;
29
+ }
30
+ console.error(`Signed in${result.email ? ` as ${result.email}` : ""}.`);
31
+ if (result.scopes?.length)
32
+ console.error(`Granted scopes: ${result.scopes.join(", ")}`);
33
+ if (result.expiryDate)
34
+ console.error(`Access token expires: ${new Date(result.expiryDate).toISOString()}`);
35
+ }
36
+ function buildProgram() {
37
+ const program = new Command();
38
+ program
39
+ .name("terra-mcp")
40
+ .description("Kozocom Google Drive & Sheets MCP server")
41
+ .version(SERVER_VERSION);
42
+ // Default action (`terra-mcp` with no subcommand) starts the server.
43
+ program
44
+ .command("start", { isDefault: true })
45
+ .alias("server")
46
+ .description("Start the MCP server over stdio")
47
+ .action(async () => {
48
+ await startServer();
49
+ });
50
+ // `auth` group: manage the cached Google OAuth credentials.
51
+ const auth = program.command("auth").description("Manage Google sign-in (login / logout / status)");
52
+ auth
53
+ .command("login")
54
+ .description("Sign in to Google and cache the OAuth token")
55
+ .action(async () => {
56
+ await login();
57
+ });
58
+ auth
59
+ .command("logout")
60
+ .description("Delete the cached Google OAuth token")
61
+ .action(async () => {
62
+ await logout();
63
+ });
64
+ auth
65
+ .command("status")
66
+ .description("Show the signed-in account, scopes, and token expiry")
67
+ .action(async () => {
68
+ await status();
69
+ });
70
+ program
71
+ .command("setup")
72
+ .description("Check setup and print MCP config (sign in with `auth login`)")
73
+ .addOption(new Option("-c, --client <client>", "MCP client to configure").choices(CLIENT_CHOICES))
74
+ .option("-y, --yes", "Accept defaults without prompting")
75
+ .action(async (opts) => {
76
+ await runSetup({ client: opts.client, yes: opts.yes });
77
+ });
78
+ program
79
+ .command("client [agent]")
80
+ .description("Print MCP config for a coding agent with dangerous tools disabled")
81
+ .option("--include-dangerous", "Keep destructive tools enabled (not recommended)", false)
82
+ .action((agent, opts) => {
83
+ const client = parseClient(agent) ?? "all";
84
+ console.log(configReport({ client, safeMode: !opts.includeDangerous }));
85
+ });
86
+ return program;
87
+ }
88
+ buildProgram()
89
+ .parseAsync(process.argv)
90
+ .catch((error) => {
91
+ console.error(error instanceof Error ? error.message : String(error));
92
+ process.exit(1);
93
+ });
94
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,43 @@
1
+ /** Server identity reported to MCP clients. */
2
+ export declare const SERVER_NAME = "kozocom-google-mcp-server";
3
+ export declare const SERVER_VERSION = "0.1.0";
4
+ /** Full read/write Drive scope — gates the `drive_*` tools. */
5
+ export declare const DRIVE_SCOPE = "https://www.googleapis.com/auth/drive";
6
+ /** Full read/write Sheets scope — gates the `sheets_*` tools. */
7
+ export declare const SHEETS_SCOPE = "https://www.googleapis.com/auth/spreadsheets";
8
+ /** Sign-in scopes, always requested. Let us show which account is signed in. */
9
+ export declare const IDENTITY_SCOPES: string[];
10
+ /**
11
+ * OAuth scopes requested at login. Full read/write for both Drive and Sheets.
12
+ * Google's granular consent screen lets the user grant only some of these; the
13
+ * server then registers only the tools whose scope was actually granted (see
14
+ * `selectGoogleTools`). Changing these requires re-running login (delete the
15
+ * cached token first).
16
+ */
17
+ export declare const SCOPES: string[];
18
+ /** Directory holding the optional OAuth client config and cached token. */
19
+ export declare const CONFIG_DIR: string;
20
+ /** Path to an optional downloaded Google OAuth client JSON. */
21
+ export declare const CLIENT_SECRET_PATH: string;
22
+ /** Path to the cached OAuth token (access + refresh). */
23
+ export declare const TOKEN_PATH: string;
24
+ /**
25
+ * OAuth token-exchange proxy. Google "Desktop" clients are confidential — the
26
+ * token endpoint requires `client_secret` even with PKCE. To keep the secret out
27
+ * of the published package, the CLI does the PKCE authorize step itself and posts
28
+ * the resulting `code` (and later, refresh tokens) to this Worker, which holds the
29
+ * secret and completes the exchange. See the `quang-mcp-auth-proxy` repo.
30
+ */
31
+ export declare const TOKEN_PROXY_URL: string;
32
+ /**
33
+ * Shared deterrent key sent to the proxy in `x-proxy-key`. This ships in the
34
+ * package, so it is NOT a secret — just a casual-abuse speed bump. The real
35
+ * protection is PKCE (binds each auth code to the CLI that started the flow).
36
+ */
37
+ export declare const PROXY_SHARED_KEY: string;
38
+ /** Maximum characters returned in a single tool response before truncation. */
39
+ export declare const CHARACTER_LIMIT = 25000;
40
+ /** Whether this process is running with dangerous tools disabled. */
41
+ export declare const SAFE_MODE: boolean;
42
+ /** Env var naming the only directory local_path/save_path may read/write. */
43
+ export declare const LOCAL_FILE_ROOT_ENV = "TERRA_MCP_LOCAL_FILE_ROOT";
@@ -0,0 +1,54 @@
1
+ import { homedir } from "node:os";
2
+ import { join } from "node:path";
3
+ /** Server identity reported to MCP clients. */
4
+ export const SERVER_NAME = "kozocom-google-mcp-server";
5
+ export const SERVER_VERSION = "0.1.0";
6
+ /** Full read/write Drive scope — gates the `drive_*` tools. */
7
+ export const DRIVE_SCOPE = "https://www.googleapis.com/auth/drive";
8
+ /** Full read/write Sheets scope — gates the `sheets_*` tools. */
9
+ export const SHEETS_SCOPE = "https://www.googleapis.com/auth/spreadsheets";
10
+ /** Sign-in scopes, always requested. Let us show which account is signed in. */
11
+ export const IDENTITY_SCOPES = ["https://www.googleapis.com/auth/userinfo.email", "openid"];
12
+ /**
13
+ * OAuth scopes requested at login. Full read/write for both Drive and Sheets.
14
+ * Google's granular consent screen lets the user grant only some of these; the
15
+ * server then registers only the tools whose scope was actually granted (see
16
+ * `selectGoogleTools`). Changing these requires re-running login (delete the
17
+ * cached token first).
18
+ */
19
+ export const SCOPES = [DRIVE_SCOPE, SHEETS_SCOPE, ...IDENTITY_SCOPES];
20
+ /** Directory holding the optional OAuth client config and cached token. */
21
+ export const CONFIG_DIR = process.env.TERRA_MCP_DIR ?? join(homedir(), ".terra-mcp");
22
+ /** Path to an optional downloaded Google OAuth client JSON. */
23
+ export const CLIENT_SECRET_PATH = process.env.GOOGLE_OAUTH_CREDENTIALS ?? join(CONFIG_DIR, "client_secret.json");
24
+ /** Path to the cached OAuth token (access + refresh). */
25
+ export const TOKEN_PATH = process.env.GOOGLE_OAUTH_TOKEN ?? join(CONFIG_DIR, "token.json");
26
+ /**
27
+ * OAuth token-exchange proxy. Google "Desktop" clients are confidential — the
28
+ * token endpoint requires `client_secret` even with PKCE. To keep the secret out
29
+ * of the published package, the CLI does the PKCE authorize step itself and posts
30
+ * the resulting `code` (and later, refresh tokens) to this Worker, which holds the
31
+ * secret and completes the exchange. See the `quang-mcp-auth-proxy` repo.
32
+ */
33
+ export const TOKEN_PROXY_URL = process.env.TERRA_MCP_TOKEN_PROXY_URL ??
34
+ "https://quang-mcp-auth-proxy.getting-started-worker.workers.dev/token";
35
+ /**
36
+ * Shared deterrent key sent to the proxy in `x-proxy-key`. This ships in the
37
+ * package, so it is NOT a secret — just a casual-abuse speed bump. The real
38
+ * protection is PKCE (binds each auth code to the CLI that started the flow).
39
+ */
40
+ export const PROXY_SHARED_KEY = process.env.TERRA_MCP_PROXY_KEY ??
41
+ "f80350f60e2c7950b72f3041c673d1194d45efa38217237ecc7bf87530f093d5";
42
+ /** Maximum characters returned in a single tool response before truncation. */
43
+ export const CHARACTER_LIMIT = 25000;
44
+ /**
45
+ * Env var that, when set to "1", runs the server in safe mode: irreversible,
46
+ * destructive tools (delete/clear) are not registered. The `config` CLI command
47
+ * emits this in the generated MCP config so AI clients can't destroy data.
48
+ */
49
+ const SAFE_MODE_ENV = "TERRA_MCP_SAFE_MODE";
50
+ /** Whether this process is running with dangerous tools disabled. */
51
+ export const SAFE_MODE = process.env[SAFE_MODE_ENV] === "1";
52
+ /** Env var naming the only directory local_path/save_path may read/write. */
53
+ export const LOCAL_FILE_ROOT_ENV = "TERRA_MCP_LOCAL_FILE_ROOT";
54
+ //# sourceMappingURL=constants.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.js","sourceRoot":"","sources":["../../src/config/constants.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,+CAA+C;AAC/C,MAAM,CAAC,MAAM,WAAW,GAAG,2BAA2B,CAAC;AACvD,MAAM,CAAC,MAAM,cAAc,GAAG,OAAO,CAAC;AAEtC,+DAA+D;AAC/D,MAAM,CAAC,MAAM,WAAW,GAAG,uCAAuC,CAAC;AACnE,iEAAiE;AACjE,MAAM,CAAC,MAAM,YAAY,GAAG,8CAA8C,CAAC;AAE3E,gFAAgF;AAChF,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,gDAAgD,EAAE,QAAQ,CAAC,CAAC;AAE5F;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,WAAW,EAAE,YAAY,EAAE,GAAG,eAAe,CAAC,CAAC;AAEtE,2EAA2E;AAC3E,MAAM,CAAC,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,aAAa,IAAI,IAAI,CAAC,OAAO,EAAE,EAAE,YAAY,CAAC,CAAC;AAErF,+DAA+D;AAC/D,MAAM,CAAC,MAAM,kBAAkB,GAC7B,OAAO,CAAC,GAAG,CAAC,wBAAwB,IAAI,IAAI,CAAC,UAAU,EAAE,oBAAoB,CAAC,CAAC;AAEjF,yDAAyD;AACzD,MAAM,CAAC,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,kBAAkB,IAAI,IAAI,CAAC,UAAU,EAAE,YAAY,CAAC,CAAC;AAE3F;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,eAAe,GAC1B,OAAO,CAAC,GAAG,CAAC,yBAAyB;IACrC,uEAAuE,CAAC;AAE1E;;;;GAIG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAC3B,OAAO,CAAC,GAAG,CAAC,mBAAmB;IAC/B,kEAAkE,CAAC;AAErE,+EAA+E;AAC/E,MAAM,CAAC,MAAM,eAAe,GAAG,KAAK,CAAC;AAErC;;;;GAIG;AACH,MAAM,aAAa,GAAG,qBAAqB,CAAC;AAE5C,qEAAqE;AACrE,MAAM,CAAC,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC,KAAK,GAAG,CAAC;AAE5D,6EAA6E;AAC7E,MAAM,CAAC,MAAM,mBAAmB,GAAG,2BAA2B,CAAC"}
@@ -0,0 +1,3 @@
1
+ export declare function isPathInsideRoot(path: string, root: string): boolean;
2
+ export declare function safeReadPath(path: string): Promise<string>;
3
+ export declare function safeWritePath(path: string): Promise<string>;
@@ -0,0 +1,50 @@
1
+ import { realpath } from "node:fs/promises";
2
+ import { basename, dirname, isAbsolute, join, relative, resolve } from "node:path";
3
+ import { LOCAL_FILE_ROOT_ENV } from "../config/constants.js";
4
+ const disabledMessage = `Local file access is disabled. Set ${LOCAL_FILE_ROOT_ENV} to a directory, ` +
5
+ "then keep local_path/save_path inside it.";
6
+ export function isPathInsideRoot(path, root) {
7
+ const rel = relative(root, path);
8
+ return rel === "" || (!!rel && !rel.startsWith("..") && !isAbsolute(rel));
9
+ }
10
+ async function localFileRoot() {
11
+ const root = process.env[LOCAL_FILE_ROOT_ENV];
12
+ if (!root)
13
+ throw new Error(disabledMessage);
14
+ return realpath(root);
15
+ }
16
+ function candidatePath(root, path) {
17
+ return isAbsolute(path) ? resolve(path) : resolve(join(root, path));
18
+ }
19
+ function assertInside(path, root) {
20
+ if (!isPathInsideRoot(path, root)) {
21
+ throw new Error(`Local file path is outside ${LOCAL_FILE_ROOT_ENV}: ${path}`);
22
+ }
23
+ }
24
+ function isNotFound(error) {
25
+ return !!error && typeof error === "object" && error.code === "ENOENT";
26
+ }
27
+ export async function safeReadPath(path) {
28
+ const root = await localFileRoot();
29
+ const resolved = await realpath(candidatePath(root, path));
30
+ assertInside(resolved, root);
31
+ return resolved;
32
+ }
33
+ export async function safeWritePath(path) {
34
+ const root = await localFileRoot();
35
+ const candidate = candidatePath(root, path);
36
+ try {
37
+ const existing = await realpath(candidate);
38
+ assertInside(existing, root);
39
+ return existing;
40
+ }
41
+ catch (error) {
42
+ if (!isNotFound(error))
43
+ throw error;
44
+ }
45
+ const parent = await realpath(dirname(candidate));
46
+ const resolved = resolve(parent, basename(candidate));
47
+ assertInside(resolved, root);
48
+ return resolved;
49
+ }
50
+ //# sourceMappingURL=local-file.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"local-file.js","sourceRoot":"","sources":["../../src/core/local-file.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACnF,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAE7D,MAAM,eAAe,GACnB,sCAAsC,mBAAmB,mBAAmB;IAC5E,2CAA2C,CAAC;AAE9C,MAAM,UAAU,gBAAgB,CAAC,IAAY,EAAE,IAAY;IACzD,MAAM,GAAG,GAAG,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IACjC,OAAO,GAAG,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC;AAC5E,CAAC;AAED,KAAK,UAAU,aAAa;IAC1B,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAC;IAC9C,IAAI,CAAC,IAAI;QAAE,MAAM,IAAI,KAAK,CAAC,eAAe,CAAC,CAAC;IAC5C,OAAO,QAAQ,CAAC,IAAI,CAAC,CAAC;AACxB,CAAC;AAED,SAAS,aAAa,CAAC,IAAY,EAAE,IAAY;IAC/C,OAAO,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;AACtE,CAAC;AAED,SAAS,YAAY,CAAC,IAAY,EAAE,IAAY;IAC9C,IAAI,CAAC,gBAAgB,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,CAAC;QAClC,MAAM,IAAI,KAAK,CAAC,8BAA8B,mBAAmB,KAAK,IAAI,EAAE,CAAC,CAAC;IAChF,CAAC;AACH,CAAC;AAED,SAAS,UAAU,CAAC,KAAc;IAChC,OAAO,CAAC,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAK,KAA+B,CAAC,IAAI,KAAK,QAAQ,CAAC;AACpG,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,IAAY;IAC7C,MAAM,IAAI,GAAG,MAAM,aAAa,EAAE,CAAC;IACnC,MAAM,QAAQ,GAAG,MAAM,QAAQ,CAAC,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;IAC3D,YAAY,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IAC7B,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,IAAY;IAC9C,MAAM,IAAI,GAAG,MAAM,aAAa,EAAE,CAAC;IACnC,MAAM,SAAS,GAAG,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IAC5C,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,QAAQ,CAAC,SAAS,CAAC,CAAC;QAC3C,YAAY,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;QAC7B,OAAO,QAAQ,CAAC;IAClB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC;YAAE,MAAM,KAAK,CAAC;IACtC,CAAC;IACD,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC;IAClD,MAAM,QAAQ,GAAG,OAAO,CAAC,MAAM,EAAE,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC;IACtD,YAAY,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IAC7B,OAAO,QAAQ,CAAC;AAClB,CAAC"}
@@ -0,0 +1,44 @@
1
+ import { z } from "zod";
2
+ /** Output format shared by all read tools. */
3
+ declare const ResponseFormat: {
4
+ readonly MARKDOWN: "markdown";
5
+ readonly JSON: "json";
6
+ };
7
+ export type ResponseFormatValue = (typeof ResponseFormat)[keyof typeof ResponseFormat];
8
+ /** Reusable Zod field: `response_format` defaulting to markdown. */
9
+ export declare const responseFormatSchema: z.ZodDefault<z.ZodEnum<{
10
+ markdown: "markdown";
11
+ json: "json";
12
+ }>>;
13
+ /** Thrown when a tool needs Google credentials but none are valid/cached. */
14
+ export declare class NotAuthenticatedError extends Error {
15
+ constructor(message?: string);
16
+ }
17
+ /** Standard MCP tool result shape (index signature satisfies the SDK's CallToolResult). */
18
+ export interface ToolResult {
19
+ [key: string]: unknown;
20
+ content: {
21
+ type: "text";
22
+ text: string;
23
+ }[];
24
+ structuredContent?: Record<string, unknown>;
25
+ isError?: boolean;
26
+ }
27
+ /** Build a plain text/structured success result, truncating oversized text. */
28
+ export declare function toolResult(text: string, structured?: Record<string, unknown>): ToolResult;
29
+ /** Build an error result with an actionable message. */
30
+ export declare function errorResult(message: string): ToolResult;
31
+ /**
32
+ * Pick the text representation for a read tool's response: pretty-printed JSON
33
+ * when `format` is "json", otherwise the lazily-built markdown. The markdown
34
+ * thunk is only invoked when needed.
35
+ */
36
+ export declare function formatResponse(format: ResponseFormatValue, json: unknown, markdown: () => string): string;
37
+ /** Truncate a string to CHARACTER_LIMIT with a clear marker. */
38
+ export declare function truncate(text: string, limit?: number): string;
39
+ /**
40
+ * Map any thrown error into a clear, actionable message for the agent.
41
+ * Recognizes auth failures, common HTTP statuses, and Zod validation errors.
42
+ */
43
+ export declare function handleGoogleError(error: unknown): string;
44
+ export {};
@@ -0,0 +1,104 @@
1
+ import { z } from "zod";
2
+ import { CHARACTER_LIMIT } from "../config/constants.js";
3
+ /** Output format shared by all read tools. */
4
+ const ResponseFormat = {
5
+ MARKDOWN: "markdown",
6
+ JSON: "json",
7
+ };
8
+ /** Reusable Zod field: `response_format` defaulting to markdown. */
9
+ export const responseFormatSchema = z
10
+ .enum(["markdown", "json"])
11
+ .default("markdown")
12
+ .describe("Output format: 'markdown' for human-readable or 'json' for machine-readable");
13
+ /** Thrown when a tool needs Google credentials but none are valid/cached. */
14
+ export class NotAuthenticatedError extends Error {
15
+ constructor(message = "Not authenticated with Google.") {
16
+ super(message);
17
+ this.name = "NotAuthenticatedError";
18
+ }
19
+ }
20
+ /** Build a plain text/structured success result, truncating oversized text. */
21
+ export function toolResult(text, structured) {
22
+ return {
23
+ content: [{ type: "text", text: truncate(text) }],
24
+ ...(structured ? { structuredContent: structured } : {}),
25
+ };
26
+ }
27
+ /** Build an error result with an actionable message. */
28
+ export function errorResult(message) {
29
+ return { content: [{ type: "text", text: message }], isError: true };
30
+ }
31
+ /**
32
+ * Pick the text representation for a read tool's response: pretty-printed JSON
33
+ * when `format` is "json", otherwise the lazily-built markdown. The markdown
34
+ * thunk is only invoked when needed.
35
+ */
36
+ export function formatResponse(format, json, markdown) {
37
+ return format === ResponseFormat.JSON ? JSON.stringify(json, null, 2) : markdown();
38
+ }
39
+ /** Truncate a string to CHARACTER_LIMIT with a clear marker. */
40
+ export function truncate(text, limit = CHARACTER_LIMIT) {
41
+ if (text.length <= limit)
42
+ return text;
43
+ return (text.slice(0, limit) +
44
+ `\n\n…[truncated ${text.length - limit} of ${text.length} characters. ` +
45
+ `Narrow your query, add filters, or use pagination to see more.]`);
46
+ }
47
+ /** Extract an HTTP status code from a googleapis/gaxios error, if any. */
48
+ function statusOf(error) {
49
+ if (error && typeof error === "object") {
50
+ const e = error;
51
+ const raw = e.response?.status ?? e.status ?? e.code;
52
+ if (typeof raw === "number")
53
+ return raw;
54
+ if (typeof raw === "string" && /^\d+$/.test(raw))
55
+ return Number(raw);
56
+ }
57
+ return undefined;
58
+ }
59
+ /** Best-effort human-readable message from an Error or a gaxios-like object. */
60
+ function messageOf(error) {
61
+ if (error instanceof Error)
62
+ return error.message;
63
+ if (error && typeof error === "object") {
64
+ const m = error.message;
65
+ if (typeof m === "string")
66
+ return m;
67
+ }
68
+ return String(error);
69
+ }
70
+ /**
71
+ * Map any thrown error into a clear, actionable message for the agent.
72
+ * Recognizes auth failures, common HTTP statuses, and Zod validation errors.
73
+ */
74
+ export function handleGoogleError(error) {
75
+ if (error instanceof NotAuthenticatedError) {
76
+ return `Error: ${error.message} Run \`terra-mcp auth login\` in a terminal to sign in, then retry.`;
77
+ }
78
+ if (error instanceof z.ZodError) {
79
+ const issues = error.issues.map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ");
80
+ return `Error: Invalid input — ${issues}`;
81
+ }
82
+ const status = statusOf(error);
83
+ const detail = messageOf(error);
84
+ switch (status) {
85
+ case 400:
86
+ return `Error: Bad request — ${detail}. Check IDs, ranges (e.g. 'Sheet1!A1:C10'), and parameters.`;
87
+ case 401:
88
+ return "Error: Authentication expired or revoked. Run `terra-mcp auth login` to sign in again.";
89
+ case 403:
90
+ return `Error: Permission denied — ${detail}. You may not have access to this file, or the required API/scope is not enabled.`;
91
+ case 404:
92
+ return (`Error: Not found — ${detail}. This means either the file/spreadsheet ID does not exist, ` +
93
+ `or it exists but you lack access (Google returns 404 instead of 403 to avoid revealing it). ` +
94
+ `Verify the ID and that your account can open it.`);
95
+ case 429:
96
+ return "Error: Rate limit exceeded. Wait a moment and retry, or reduce the request frequency.";
97
+ case 500:
98
+ case 503:
99
+ return "Error: Google API is temporarily unavailable. Please retry shortly.";
100
+ default:
101
+ return `Error: ${detail}`;
102
+ }
103
+ }
104
+ //# sourceMappingURL=result.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"result.js","sourceRoot":"","sources":["../../src/core/result.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,61 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { ToolAnnotations } from "@modelcontextprotocol/sdk/types.js";
3
+ import type { z } from "zod";
4
+ import type { drive_v3, sheets_v4 } from "googleapis";
5
+ import { type ToolResult } from "./result.js";
6
+ /**
7
+ * Args a handler receives: the parsed output of its Zod input shape, with
8
+ * defaults applied. Derived from the schema so the schema is the single source
9
+ * of truth — handlers never re-declare their argument types by hand.
10
+ */
11
+ export type ArgsOf<Shape extends z.ZodRawShape> = z.infer<z.ZodObject<Shape>>;
12
+ /**
13
+ * A self-registering tool. Defining a tool yields one of these; the registry
14
+ * just calls it with the server. Capturing registration in a closure (rather
15
+ * than an erased `{ definition, handler }` record) is what lets each tool stay
16
+ * fully type-checked against its own schema. The closure also carries the
17
+ * tool's `name` and `annotations` so registries can filter (e.g. drop
18
+ * dangerous tools in safe mode) without re-deriving them.
19
+ */
20
+ export interface ToolRegistration {
21
+ (server: McpServer): void;
22
+ readonly toolName: string;
23
+ readonly annotations: ToolAnnotations;
24
+ }
25
+ /**
26
+ * Whether a tool only reads — never mutates Drive/Sheets or local auth state.
27
+ * Driven by the honest `readOnlyHint` annotation each tool sets. Safe-mode
28
+ * configs expose only these; every mutating tool (create/write/delete, plus
29
+ * login/logout) is treated as dangerous and disabled.
30
+ */
31
+ export declare function isReadOnlyTool(registration: ToolRegistration): boolean;
32
+ interface ToolSpec<Shape extends z.ZodRawShape> {
33
+ name: string;
34
+ title: string;
35
+ description: string;
36
+ inputSchema: Shape;
37
+ annotations: ToolAnnotations;
38
+ }
39
+ /**
40
+ * Define a tool that needs no Google client (e.g. auth tools). The handler owns
41
+ * its own error handling when it must shape errors specially; otherwise thrown
42
+ * errors are caught and returned via {@link handleGoogleError}.
43
+ */
44
+ export declare function tool<Shape extends z.ZodRawShape>(spec: ToolSpec<Shape> & {
45
+ run: (args: ArgsOf<Shape>) => Promise<ToolResult>;
46
+ }): ToolRegistration;
47
+ /**
48
+ * Define a Drive tool. The authorized Drive client is fetched and injected per
49
+ * call; `NotAuthenticated`/API errors are mapped to actionable results. The
50
+ * pure `run` function is what unit tests call directly with a fake client.
51
+ */
52
+ export declare function driveTool<Shape extends z.ZodRawShape>(spec: ToolSpec<Shape> & {
53
+ run: (drive: drive_v3.Drive, args: ArgsOf<Shape>) => Promise<ToolResult>;
54
+ }): ToolRegistration;
55
+ /** Define a Sheets tool. See {@link driveTool}; injects the Sheets client. */
56
+ export declare function sheetsTool<Shape extends z.ZodRawShape>(spec: ToolSpec<Shape> & {
57
+ run: (sheets: sheets_v4.Sheets, args: ArgsOf<Shape>) => Promise<ToolResult>;
58
+ }): ToolRegistration;
59
+ /** Register every tool in the list with the server. */
60
+ export declare function registerAll(server: McpServer, tools: readonly ToolRegistration[]): void;
61
+ export {};
@@ -0,0 +1,63 @@
1
+ import { errorResult, handleGoogleError } from "./result.js";
2
+ import { getGoogleClients } from "../google/client.js";
3
+ /**
4
+ * Whether a tool only reads — never mutates Drive/Sheets or local auth state.
5
+ * Driven by the honest `readOnlyHint` annotation each tool sets. Safe-mode
6
+ * configs expose only these; every mutating tool (create/write/delete, plus
7
+ * login/logout) is treated as dangerous and disabled.
8
+ */
9
+ export function isReadOnlyTool(registration) {
10
+ return registration.annotations.readOnlyHint === true;
11
+ }
12
+ /** Register a tool whose handler is `run`, mapping any thrown error to a result. */
13
+ function register(spec, run) {
14
+ const { name, title, description, inputSchema, annotations } = spec;
15
+ const callback = async (args) => {
16
+ try {
17
+ return await run(args);
18
+ }
19
+ catch (error) {
20
+ return errorResult(handleGoogleError(error));
21
+ }
22
+ };
23
+ const registration = (server) => {
24
+ // The SDK's callback type is generic over its own zod-compat shapes and a
25
+ // wider CallToolResult content union than ToolResult. `callback` is fully
26
+ // checked against `Shape` above; this single cast bridges that boundary —
27
+ // the only erasure in the registration path.
28
+ server.registerTool(name, { title, description, inputSchema, annotations }, callback);
29
+ };
30
+ return Object.assign(registration, { toolName: name, annotations });
31
+ }
32
+ /**
33
+ * Define a tool that needs no Google client (e.g. auth tools). The handler owns
34
+ * its own error handling when it must shape errors specially; otherwise thrown
35
+ * errors are caught and returned via {@link handleGoogleError}.
36
+ */
37
+ export function tool(spec) {
38
+ return register(spec, spec.run);
39
+ }
40
+ /**
41
+ * Define a Drive tool. The authorized Drive client is fetched and injected per
42
+ * call; `NotAuthenticated`/API errors are mapped to actionable results. The
43
+ * pure `run` function is what unit tests call directly with a fake client.
44
+ */
45
+ export function driveTool(spec) {
46
+ return register(spec, async (args) => {
47
+ const { drive } = await getGoogleClients();
48
+ return spec.run(drive, args);
49
+ });
50
+ }
51
+ /** Define a Sheets tool. See {@link driveTool}; injects the Sheets client. */
52
+ export function sheetsTool(spec) {
53
+ return register(spec, async (args) => {
54
+ const { sheets } = await getGoogleClients();
55
+ return spec.run(sheets, args);
56
+ });
57
+ }
58
+ /** Register every tool in the list with the server. */
59
+ export function registerAll(server, tools) {
60
+ for (const registerTool of tools)
61
+ registerTool(server);
62
+ }
63
+ //# sourceMappingURL=tool.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool.js","sourceRoot":"","sources":["../../src/core/tool.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,WAAW,EAAE,iBAAiB,EAAmB,MAAM,aAAa,CAAC;AAC9E,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAuBvD;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAAC,YAA8B;IAC3D,OAAO,YAAY,CAAC,WAAW,CAAC,YAAY,KAAK,IAAI,CAAC;AACxD,CAAC;AAUD,oFAAoF;AACpF,SAAS,QAAQ,CACf,IAAqB,EACrB,GAAiD;IAEjD,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,WAAW,EAAE,GAAG,IAAI,CAAC;IACpE,MAAM,QAAQ,GAAG,KAAK,EAAE,IAAmB,EAAuB,EAAE;QAClE,IAAI,CAAC;YACH,OAAO,MAAM,GAAG,CAAC,IAAI,CAAC,CAAC;QACzB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,WAAW,CAAC,iBAAiB,CAAC,KAAK,CAAC,CAAC,CAAC;QAC/C,CAAC;IACH,CAAC,CAAC;IACF,MAAM,YAAY,GAAgC,CAAC,MAAM,EAAE,EAAE;QAC3D,0EAA0E;QAC1E,0EAA0E;QAC1E,0EAA0E;QAC1E,6CAA6C;QAC7C,MAAM,CAAC,YAAY,CACjB,IAAI,EACJ,EAAE,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,WAAW,EAAE,EAChD,QAA0C,CAC3C,CAAC;IACJ,CAAC,CAAC;IACF,OAAO,MAAM,CAAC,MAAM,CAAC,YAAY,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC,CAAC;AACtE,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,IAAI,CAClB,IAA6E;IAE7E,OAAO,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC;AAClC,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,SAAS,CACvB,IAEC;IAED,OAAO,QAAQ,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE;QACnC,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,gBAAgB,EAAE,CAAC;QAC3C,OAAO,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IAC/B,CAAC,CAAC,CAAC;AACL,CAAC;AAED,8EAA8E;AAC9E,MAAM,UAAU,UAAU,CACxB,IAEC;IAED,OAAO,QAAQ,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE;QACnC,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,gBAAgB,EAAE,CAAC;QAC5C,OAAO,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IAChC,CAAC,CAAC,CAAC;AACL,CAAC;AAED,uDAAuD;AACvD,MAAM,UAAU,WAAW,CAAC,MAAiB,EAAE,KAAkC;IAC/E,KAAK,MAAM,YAAY,IAAI,KAAK;QAAE,YAAY,CAAC,MAAM,CAAC,CAAC;AACzD,CAAC"}