Google Workspace
Build an MCP server with Google Calendar and Drive tools using Keycard delegated access
Build an MCP server with Calendar and Drive tools that let AI agents manage a user’s Google Workspace on their behalf.
Google Setup
Section titled “Google Setup”-
Add Google from Resource Catalog
In Keycard Console, navigate to Resource Catalog and add Google Calendar and Google Drive. This automatically creates the Google OAuth provider and API resources.
-
Set up the Google OAuth App callback
- Go to Zone Settings in Keycard Console and copy the OAuth2 Redirect URL
- In Google Cloud Console → APIs & Services → Credentials, create or edit your OAuth 2.0 Client ID
- Add the Keycard redirect URL to Authorized redirect URIs
- Ensure the Calendar API and Drive API are enabled under APIs & Services → Enabled APIs
-
Register your MCP server resource
Navigate to Resources → Create Resource:
Field Value Resource Name Google Workspace MCP Server Resource Identifier http://localhost:8000/mcpCredential Provider Zone Provider -
Create an Application
Navigate to Applications → Create Application:
Field Value Provided Resource Google Workspace MCP Server (your MCP server) Dependencies Google Calendar API, Google Drive API After creating the application, generate client credentials and save them.
Implementation
Section titled “Implementation”The implementation follows the same pattern as the GitHub server — the only difference is the API URL you pass to grant() and the API calls you make with the token.
pip install keycardai-mcp-fastmcp fastmcp httpxThe server setup is identical to GitHub — just change the server name. The tools file uses the same grant pattern with https://www.googleapis.com:
GOOGLE_API = "https://www.googleapis.com"
def register_tools(mcp: FastMCP, auth_provider: AuthProvider): @mcp.tool() @auth_provider.grant(GOOGLE_API) async def list_calendar_events(ctx: Context, calendar_id: str = "primary") -> dict: """List events from a Google Calendar.""" access_context: AccessContext = await ctx.get_state("keycardai") token = access_context.access(GOOGLE_API).access_token
# Use token to call Google Calendar API async with httpx.AsyncClient() as client: response = await client.get( f"{GOOGLE_API}/calendar/v3/calendars/{calendar_id}/events", headers={"Authorization": f"Bearer {token}"}, params={"singleEvents": "true", "orderBy": "startTime"}, ) # ... process response
@mcp.tool() @auth_provider.grant(GOOGLE_API) async def list_drive_files(ctx: Context, q: str | None = None) -> dict: """List or search files in Google Drive.""" access_context: AccessContext = await ctx.get_state("keycardai") token = access_context.access(GOOGLE_API).access_token # Use token to call Google Drive APISee the full example with all tools, error handling, and Google Workspace file export logic: Python SDK examples
npm install @keycardai/mcp @modelcontextprotocol/sdk expressnpm install -D typescript @types/expressThe server setup is identical to GitHub — just change the server name. The tools file uses the same grant pattern with https://www.googleapis.com:
const GOOGLE_API = "https://www.googleapis.com";
export function registerGoogleRoutes(app: Express, authProvider: AuthProvider) { app.get("/api/calendar/events", authProvider.grant(GOOGLE_API), async (req, res) => { const { accessContext } = req as DelegatedRequest; if (accessContext.hasErrors()) { res.status(502).json({ error: "Token exchange failed" }); return; } const token = accessContext.access(GOOGLE_API).accessToken;
// Use token to call Google Calendar API const response = await fetch( `${GOOGLE_API}/calendar/v3/calendars/primary/events?singleEvents=true&orderBy=startTime`, { headers: { Authorization: `Bearer ${token}` } }, ); // ... process response });
app.get("/api/drive/files", authProvider.grant(GOOGLE_API), async (req, res) => { // Same pattern: grant → accessContext → token → API call });}See the full example with all tools, error handling, and Google Workspace file export logic: TypeScript SDK examples
go mod init google-mcp-servergo get github.com/keycardai/credentials-go/mcpThe server setup is identical to GitHub — just change the server name. The tools file uses the same grant pattern with https://www.googleapis.com:
const googleAPI = "https://www.googleapis.com"
func registerGoogleRoutes(mux *http.ServeMux, authProvider *mcp.AuthProvider) { listEvents := mcp.RequireBearerAuth( mcp.WithRequiredScopes("mcp:tools"), )(authProvider.Grant(googleAPI)( http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ac := mcp.AccessContextFromRequest(r) token, _ := ac.Access(googleAPI)
// Use token to call Google Calendar API req, _ := http.NewRequestWithContext(r.Context(), "GET", googleAPI+"/calendar/v3/calendars/primary/events?singleEvents=true&orderBy=startTime", nil) req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token.AccessToken))
resp, err := http.DefaultClient.Do(req) if err != nil { http.Error(w, err.Error(), http.StatusBadGateway) return } defer resp.Body.Close() // ... process response }), ))
listFiles := mcp.RequireBearerAuth( mcp.WithRequiredScopes("mcp:tools"), )(authProvider.Grant(googleAPI)( http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ac := mcp.AccessContextFromRequest(r) token, _ := ac.Access(googleAPI) // Use token to call Google Drive API _ = token }), ))
mux.Handle("GET /api/calendar/events", listEvents) mux.Handle("GET /api/drive/files", listFiles)}See the full example with all tools, error handling, and Google Workspace file export logic: Go SDK examples
Test It
Section titled “Test It”-
Start your server
-
Connect from Cursor and complete the OAuth flow — you will be prompted to authorize Google Calendar and Drive access
-
Try these prompts:
- “Show my calendar events for this week”
- “List my recent Drive files”
- “Get the content of the file named ‘Meeting Notes’”
-
Verify in Audit Logs
Check Keycard Console Audit Logs — you should see the same
users:authenticate,users:authorize, andcredentials:issueevents, with the identity chain showing your user and the Google application.