#!/usr/bin/env node /** * gws-mcp-server — MCP server for Google Workspace CLI * * Exposes curated Google Workspace tools via Model Context Protocol, * wrapping the gws CLI (https://github.com/googleworkspace/cli). * * Usage: * gws-mcp-server [--services drive,sheets,calendar,docs,slides,gmail,tasks] [--gws-path /path/to/gws] * * In .mcp.json: * { * "mcpServers": { * "google-workspace": { * "command": "node", * "args": ["path/to/gws-mcp-server/build/index.js", "--services", "drive,sheets,calendar,docs,slides,gmail,tasks"] * } * } * } */ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { z } from "zod"; import { type ToolDef } from "./services.js"; /** * The version advertised to MCP clients, read from package.json at runtime. * * Read rather than imported. `import pkg from "../package.json"` needs * `resolveJsonModule`, and package.json sits outside `rootDir: ./src` — tsc * would widen the output root to include it and emit `build/src/index.js`, * breaking the `bin` path in package.json. Resolving from `import.meta.url` * keeps the emit layout intact and works in both trees: `src/../package.json` * when running from source, `build/../package.json` once installed, since npm * always ships package.json at the tarball root. */ export declare const SERVER_VERSION: string; export declare function buildZodSchema(tool: ToolDef): Record; /** * The tools `createServer` registers by hand, and the service that gates each. * * These are not in the `services.ts` registry, so `getToolsForServices()` does * not count them. `main()` logged `tools.length` before the server was built * and reported 37 while a client listing tools saw 39. */ export declare const CUSTOM_TOOLS: ReadonlyArray<{ name: string; service: string; readOnly: boolean; }>; /** * Pure builders for the two Drive ownership-transfer flows Google documents * separately (developers.google.com/workspace/drive/api/guides/transfer-file): * same-organization Workspace transfer (immediate) vs. propose-and-accept * (consumer accounts). role/type/pendingOwner/ * transferOwnership are protocol constants, not tool inputs — there is no * `fileId`/`emailAddress`/`opts` path that can reach them. This is the fix * for the #60 review finding: the first version declared `pendingOwner` as * an ordinary boolean ToolDef param, and a caller passing `pendingOwner: * false` got silently granted plain writer access instead of an ownership * proposal, despite the tool's name. Declarative ToolDef params/bodyParams * can only express caller-supplied fields — there's no way to hardcode a * body field without exposing it to the caller — so these two tools are * hand-written, the same escape hatch drive_files_download and * gmail_drafts_create already use for shapes the standard ToolDef can't * express. */ export declare function buildTransferOwnershipRequest(fileId: string, emailAddress: string, opts?: { moveToNewOwnersRoot?: boolean; fields?: string; }): { params: Record; body: Record; }; export declare function buildProposeOwnershipTransferRequest(fileId: string, emailAddress: string, opts?: { fields?: string; }): { params: Record; body: Record; }; /** * How many tools a client will actually see: registry tools plus whichever * custom tools have their gating service enabled. * * This still restates the guards inside `createServer` rather than driving * them, so the binding constraint is the e2e test that compares this against a * real `tools/list` for several service subsets. A count derived from the same * data it is meant to check would agree with itself and prove nothing. */ export declare function countRegisteredTools(tools: ToolDef[], services: readonly string[], readOnly?: boolean): number; /** * The registry tools to register, narrowed to reads under `--read-only`. * * Enforced here, at the server boundary, rather than in the README's prose: * under `--read-only` a write tool is never registered, so it is not in * `tools/list` and there is nothing for a client to call. This constrains the * agent, not the credential — the token on disk can still write. For an MCP * server the agent is the threat model, but that is the limit of the claim. */ export declare function selectTools(tools: ToolDef[], readOnly: boolean): ToolDef[]; /** * Generate a CWD-relative temp file name for gws CLI output. * * Must be relative: the gws CLI rejects --output paths that resolve outside * the current working directory. On macOS, os.tmpdir() returns /var/folders/... * which canonicalizes to /private/var/... (kernel symlink) and fails the * CLI's validate_safe_file_path() check. A bare relative name stays in CWD * on every platform. See issue #3. */ export declare function makeTmpFileName(prefix: string): string; /** * Build the MCP server and register every tool on it. * * Split out of `main()` so tests can inspect what a client actually sees. * The registry tools and the two hand-written tools are registered by * different code paths, and only an assertion against the assembled server * covers both — a unit test over `buildAnnotations` misses the custom ones, * which is how `gmail_drafts_create` shipped without a `destructiveHint`. */ export declare function createServer(tools: ToolDef[], services: readonly string[], gwsBinary: string, gwsAvailable: boolean, readOnly?: boolean): McpServer;