Skip to content

Google OAuth

Full OAuth flow with URL elicitation. The agent is directed to Google's authorization page, and a callback route completes the flow.

Install

sh
pnpm add @lynq/google
sh
npm install @lynq/google
sh
yarn add @lynq/google
sh
bun add @lynq/google

Import

ts
import { google, handleCallback } from "@lynq/google";

Usage

ts
server.tool("files", google({
  clientId: CLIENT_ID,
  clientSecret: CLIENT_SECRET,
  redirectUri: CALLBACK_URL,
}), config, handler);

Options

OptionTypeDefaultDescription
namestring"google"Middleware name
clientIdstring(required)Google OAuth client ID
clientSecretstring(required)Google OAuth client secret
redirectUristring(required)Your callback URL
scopesstring[]["openid", "profile", "email"]OAuth scopes
sessionKeystring"user"Session key for user data
messagestring"Please sign in with Google to continue."Elicitation message
timeoutnumber300000Timeout in ms

Google OAuth defaults to ["openid", "profile", "email"] if no scopes are specified.

Example

A full Hono example with MCP server, Google OAuth middleware, and callback route.

ts
import { createMCPServer } from "@lynq/lynq";
import { google, handleCallback } from "@lynq/google";
import { Hono } from "hono";
import { z } from "zod";

const CLIENT_ID = process.env.GOOGLE_CLIENT_ID!;
const CLIENT_SECRET = process.env.GOOGLE_CLIENT_SECRET!;
const CALLBACK_URL = "http://localhost:3000/auth/google/callback";

const mcp = createMCPServer({ name: "demo", version: "1.0.0" });

mcp.tool(
  "drive_files",
  google({
    clientId: CLIENT_ID,
    clientSecret: CLIENT_SECRET,
    redirectUri: CALLBACK_URL,
  }),
  { description: "List Google Drive files", input: z.object({}) },
  async (_args, c) => c.json(c.session.get("user")),
);

const app = new Hono();
const handler = mcp.http();
app.all("/mcp", (c) => handler(c.req.raw));

app.get("/auth/google/callback", async (c) => {
  const result = await handleCallback(
    mcp,
    { code: c.req.query("code")!, state: c.req.query("state")! },
    {
      clientId: CLIENT_ID,
      clientSecret: CLIENT_SECRET,
      redirectUri: CALLBACK_URL,
    },
  );
  if (!result.success) return c.text(`Error: ${result.error}`, 400);
  return c.html("<p>Signed in! You can close this tab.</p>");
});

export default { port: 3000, fetch: app.fetch };

Under the hood

google() wraps oauth() which wraps urlAction(). Same flow as GitHub OAuth: URL elicitation directs the agent to Google's authorization page. The state parameter encodes sessionId:elicitationId. When the user authorizes, Google redirects to your callback. handleCallback() exchanges the authorization code for tokens, fetches user info from Google's userinfo endpoint, stores it in the session under sessionKey, and calls server.completeElicitation() to resolve the pending elicitation.