Building a Remote MCP Server for Google Workspace (Sheets, Docs and Presentation)

Building a Remote MCP Server for Google Workspace: From Zero to Production
Want to connect your AI assistant to Google Sheets, Docs, and Slides? Our MCP server gives Claude, Cursor, and other AI clients full access to your Google Workspace — read spreadsheets, create documents, edit presentations, and more. No API keys, no local setup. Just add a URL and start working.
Try it now at gwk-mcp.abilioazevedo.com.br — $5/month for 24 tools across Sheets, Docs, and Slides.
What is MCP?
The Model Context Protocol (MCP) is an open standard that lets AI assistants — like Claude, ChatGPT, and others — connect to external tools and data sources through a unified interface. Think of it as a USB-C port for AI: one protocol, many tools.
Instead of each AI client implementing custom integrations for every service, MCP defines a common language. A server exposes tools (functions the AI can call), and any MCP-compatible client can discover and use them automatically.
This means you can build one MCP server and it works everywhere — Claude Code, Claude.ai, Cursor, VS Code, and any other client that speaks the protocol.
Why Google Workspace?
Google Workspace — Sheets, Docs, and Slides — is at the core of how teams manage data, documents, and presentations. Connecting these tools to AI assistants opens up powerful workflows:
- Ask Claude to "read the Q1 sales data from my spreadsheet and summarize trends"
- Have an AI append rows to a tracking sheet as part of an automated workflow
- Create new documents, presentations, or spreadsheets without leaving your terminal
- Search and replace text across an entire presentation in seconds
- Read and summarize the content of a Google Doc
The challenge? Google Workspace requires OAuth authentication, and most MCP servers run locally. We wanted something different: a remote MCP server that anyone can use by just adding a URL — no API keys, no local setup, no configuration files.
Architecture Overview
The server is built with Next.js deployed on Vercel, using Neon Postgres for session storage. The key insight is a two-layer OAuth flow:
┌─────────────┐ MCP OAuth ┌──────────────────┐ Google OAuth ┌─────────────┐
│ MCP Client │ ◄──────────────────► │ MCP Server │ ◄─────────────────► │ Google APIs │
│ (Claude, │ Bearer tokens, │ (Next.js on │ Access/refresh │ (Sheets, │
│ Cursor) │ PKCE, dynamic │ Vercel) │ tokens, consent │ Docs, │
│ │ client registration│ │ screen │ Slides, │
│ │ │ │ │ Drive) │
└─────────────┘ └──────────────────┘ └─────────────┘
│
▼
┌──────────────┐
│ Neon Postgres │
│ (sessions, │
│ auth codes, │
│ clients) │
└──────────────┘
Layer 1: MCP OAuth (Client ↔ Server)
When an MCP client connects, it discovers the auth requirements via standard well-known endpoints:
/.well-known/oauth-protected-resource— tells the client this resource is OAuth-protected/.well-known/oauth-authorization-server— provides the authorization server metadata (endpoints, supported flows)/oauth/register— dynamic client registration (the client registers itself automatically)
This follows the MCP spec so any compliant client handles it seamlessly. PKCE is supported for security.
Layer 2: Google OAuth (Server ↔ Google)
When the user needs to authenticate:
/oauth/authorizereceives the MCP client's auth request and redirects to Google's consent screen- The user grants access to their Google Sheets, Docs, Slides, and Drive
/oauth/callbackreceives Google's tokens, stores them in Postgres, and redirects back to the MCP client with an authorization code/oauth/tokenexchanges the code for a session access token
The end result: the MCP client gets a Bearer token, and the server holds the Google credentials securely.
The MCP Endpoint
The core of the server is a single Next.js Route Handler at POST /mcp. Every request creates a fresh McpServer instance with a WebStandardStreamableHTTPServerTransport:
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
export async function POST(request: NextRequest) {
const token = request.headers.get("authorization")?.replace("Bearer ", "");
if (!token) return unauthorizedResponse();
const server = new McpServer({
name: "google-workspace",
version: "0.2.0",
});
registerTools(server, () => resolveGoogleAuth(token));
const transport = new WebStandardStreamableHTTPServerTransport({
sessionIdGenerator: undefined,
});
await server.connect(transport);
return await transport.handleRequest(request);
}
The WebStandardStreamableHTTPServerTransport is the key — it works with the Web Standard Request/Response APIs that Next.js Route Handlers use, making deployment on serverless platforms like Vercel straightforward.
Tools We Expose
The server registers 24 tools across three Google Workspace products:
Google Sheets (8 tools)
| Tool | Description |
|---|---|
list_spreadsheets |
Search and list spreadsheets from Google Drive |
get_spreadsheet |
Get spreadsheet metadata (sheet names, dimensions) |
read_sheet |
Read data from a range (returns formatted table) |
write_sheet |
Write data to a specific range (overwrites) |
append_rows |
Append rows to the end of a sheet |
update_cells |
Update individual cells at specific locations |
create_spreadsheet |
Create a new spreadsheet |
batch_get |
Read multiple ranges in one request |
Google Docs (7 tools)
| Tool | Description |
|---|---|
list_documents |
Search and list Google Docs from Drive |
get_document |
Get document metadata and structure |
read_document |
Read the full text content of a document |
create_document |
Create a new Google Doc |
append_text_to_document |
Append text at the end of a document |
insert_text_in_document |
Insert text at a specific position |
replace_text_in_document |
Find and replace text in a document |
Google Slides (9 tools)
| Tool | Description |
|---|---|
list_presentations |
Search and list presentations from Drive |
get_presentation |
Get presentation metadata (slide count, IDs) |
read_slide |
Read text and elements from a specific slide |
read_all_slides |
Read text from all slides in a presentation |
create_presentation |
Create a new presentation |
add_slide |
Add a new slide with a predefined layout |
add_text_to_slide |
Insert text into a shape/text box on a slide |
replace_text_in_presentation |
Find and replace text across all slides |
delete_slide |
Delete a slide from a presentation |
Each tool uses Zod schemas for input validation and includes descriptions that help AI clients understand when and how to use them:
server.registerTool(
"read_sheet",
{
description: "Read data from a spreadsheet range. Returns a formatted table.",
inputSchema: {
spreadsheet_id: z.string().describe("The spreadsheet ID"),
range: z.string().describe("A1 notation range, e.g. 'Sheet1!A1:D10'"),
},
},
async ({ spreadsheet_id, range }) => {
const auth = await getAuth();
const data = await sheetsLib.readSheet(auth, spreadsheet_id, range);
// Format as readable table...
return { content: [{ type: "text", text }] };
}
);
The server also includes a prompt (google_workspace_guide) that provides best-practice guidance to AI assistants for using the tools efficiently — like preferring narrow ranges, using batch_get for multiple reads, and exploring structure before reading full content.
The Tech Stack
- Next.js 15 — App Router with Route Handlers for all endpoints
- Vercel — Serverless deployment with git-push deploys
- Neon Postgres — Serverless PostgreSQL for session/token storage
- Prisma — ORM with three tables:
oauth_clients,auth_codes,sessions - @modelcontextprotocol/sdk — Official MCP TypeScript SDK
- googleapis — Google's official Node.js client for Sheets, Docs, Slides, and Drive APIs
- Zod — Schema validation for tool inputs
Deployment
The project deploys automatically on every push to master. The build command handles everything:
prisma generate && prisma migrate deploy && next build
Prisma migrations run automatically during build, so database schema changes deploy seamlessly alongside code changes.
How to Use It
Adding to Claude Code
Add the server URL to your MCP client configuration:
{
"mcpServers": {
"google-workspace": {
"url": "https://gwk-mcp.abilioazevedo.com.br/mcp"
}
}
}
That's it. The first time you use a tool, the OAuth flow kicks in — your browser opens, you grant access to Google Workspace, and you're connected.
Adding to Claude.ai
Go to Settings > MCP Servers > Add Server and paste the URL:
https://gwk-mcp.abilioazevedo.com.br/mcp
Claude will handle the authentication flow automatically.
Inspecting and Debugging with MCP Inspector
The MCP Inspector is an essential tool for developing and debugging MCP servers. It provides a web UI where you can see the server's capabilities, test tools interactively, and inspect the protocol messages.
Running the Inspector
To inspect our remote MCP server, run:
npx @modelcontextprotocol/inspector https://gwk-mcp.abilioazevedo.com.br/mcp
This launches a local web interface (usually at http://localhost:6274) that connects to the remote server.
What You Can Do with the Inspector
-
Authenticate — The Inspector supports the full MCP OAuth flow. Click "Connect" and it will walk you through the Google authentication, just like a real MCP client would.
-
List Tools — See all 24 tools the server exposes, along with their descriptions and input schemas. This is great for verifying your tool definitions look correct.
-
Call Tools — Fill in parameters and execute tools directly. For example, you can call
list_spreadsheetsto see your Google Sheets, then callread_sheetwith a specific spreadsheet ID and range. -
Inspect Protocol Messages — See the raw JSON-RPC messages exchanged between client and server. This is invaluable for debugging issues with tool calls, authentication, or transport.
-
Test Error Handling — Try calling tools with invalid parameters to verify your error responses are sensible.
Inspector Workflow Example
1. Run: npx @modelcontextprotocol/inspector https://gwk-mcp.abilioazevedo.com.br/mcp
2. Open http://localhost:6274 in your browser
3. Click "Connect" → Complete Google OAuth in the popup
4. Navigate to "Tools" tab → See all 24 registered tools
5. Select "list_spreadsheets" → Click "Run" → See your spreadsheets
6. Select "read_document" → Enter a document_id → See the document content
7. Select "get_presentation" → Enter a presentation_id → See the slide structure
The Inspector is also useful for local development:
# Start the dev server
pnpm dev
# In another terminal, point the inspector at localhost
npx @modelcontextprotocol/inspector http://localhost:3100/mcp
Lessons Learned
1. Accept Header Normalization
Some MCP clients send Accept: */* instead of the specific content types the SDK expects (application/json, text/event-stream). We had to normalize the header on incoming requests to prevent the transport from rejecting valid requests.
2. Don't Close the Server Too Early
The WebStandardStreamableHTTPServerTransport returns an SSE (Server-Sent Events) stream. The response body is populated asynchronously — if you call server.close() immediately after getting the response, it kills the stream before the client reads it. Let the serverless function's natural lifecycle handle cleanup.
3. CORS Matters for Remote Servers
Unlike local MCP servers, a remote server needs proper CORS headers. MCP clients running in browsers (like Claude.ai) need to make cross-origin requests to your server. Every endpoint — including OPTIONS preflight — needs the right headers.
4. Token Refresh is Critical
Google access tokens expire after ~1 hour. The server checks token expiry on each request and transparently refreshes using the stored refresh token. Without this, users would need to re-authenticate constantly.
5. Scaling to Multiple Google APIs
Starting with Sheets and then expanding to Docs and Slides taught us the value of keeping a clean separation between the MCP tool registration layer and the Google API wrappers. Each Google service (sheets.ts, docs.ts, slides.ts) exports pure functions that take a GoogleOAuth2Client and return structured data. The MCP route handler just orchestrates — register tools, resolve auth, format output. Adding a new Google service is straightforward: write the API wrapper, register the tools.
Conclusion
Building a remote MCP server is a powerful way to give AI assistants access to external services. The MCP protocol handles the complexity of tool discovery and authentication, while platforms like Vercel and Neon make deployment and data storage effortless.
The key advantage of a remote server over a local one is zero configuration for end users — they add a URL and authenticate once. No API keys to manage, no local processes to run, no config files to maintain.
By expanding from Google Sheets to the full Google Workspace suite — Sheets, Docs, and Slides — a single MCP server becomes a comprehensive productivity bridge between AI and the tools teams use every day.


