> ## Documentation Index
> Fetch the complete documentation index at: https://docs.hookfish.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Quickstart

> Scaffold Hookfish, connect a Gmail account through MCP, and read the user's inbox with a current access token.

Create a standalone Node.js broker, connect a user's Gmail account through the
remote MCP server at `https://gmail.run.tools`, and read their inbox from
trusted server code.

## Prerequisites

* Node.js 20 or later
* pnpm
* A Google account with Gmail enabled

You do not need to create a Google OAuth application. The MCP provider uses the
server's authorization metadata and dynamic client registration.

## Create your broker and read Gmail

<Steps>
  <Step title="Scaffold a Node.js project">
    Install the latest Hookfish CLI, run the initializer, and enter the
    generated directory.

    ```bash theme={null}
    npm i -g hookfish@latest
    hookfish init my-broker --backend node
    cd my-broker
    ```

    The initializer installs dependencies and creates a gitignored `.env` with
    unique `OAUTH_ENCRYPTION_KEY` and `HOOKFISH_API_KEY` values.
  </Step>

  <Step title="Install the clients">
    Add the first-party Hookfish SDK and the MCP TypeScript client to the
    generated Node.js project.

    ```bash theme={null}
    pnpm add @hookfish/sdk @modelcontextprotocol/client
    ```
  </Step>

  <Step title="Add an inbox endpoint">
    Replace `src/index.ts` with this Hono API. The `/gmail/inbox` endpoint keeps
    the broker access token and Gmail access token on the server.

    ```ts theme={null}
    import { serve } from '@hono/node-server'
    import { HookfishServer } from '@hookfish/api'
    import { Hookfish, HookfishError } from '@hookfish/sdk'
    import {
      Client as McpClient,
      StreamableHTTPClientTransport,
      UnauthorizedError,
    } from '@modelcontextprotocol/client'
    import { Hono } from 'hono'
    import config from '../hookfish.config'

    const app = new Hono()
    const broker = await HookfishServer.init(config)
    if (!process.env.HOOKFISH_API_KEY) throw new Error('HOOKFISH_API_KEY is required')

    const hookfish = new Hookfish({
      apiKey: process.env.HOOKFISH_API_KEY,
      baseUrl: 'http://local/api',
      fetch: async (input: RequestInfo | URL, init?: RequestInit) =>
        broker.fetch(new Request(input, init), process.env),
    })

    // Mount Hookfish's API and OAuth callback routes.
    app.all('/api/*', (ctx) =>
      broker.fetch(ctx.req.raw, process.env),
    )

    // Authorize Gmail when needed, then return the user's inbox.
    app.get('/gmail/inbox', async (ctx) => {
      const mcpUrl = new URL('https://gmail.run.tools')

      // Ensure the remote Gmail MCP server is configured as a provider.
      try {
        await hookfish.providers.get('gmail')
      } catch (error) {
        if (!(error instanceof HookfishError) || error.status !== 404) throw error
        await hookfish.providers.put('gmail', {
          template: 'mcp',
          configuration: { resource_url: mcpUrl.href, scopes: [] },
          credentials: { mode: 'register' },
        })
      }

      let accessToken: string

      // Get a current token or return a URL that starts authorization.
      try {
        accessToken = (
          await hookfish.oauth.getToken('personal/gmail')
        ).access_token
      } catch (error) {
        if (!(error instanceof HookfishError) || error.status !== 404) throw error
        const { authorize_url } = await hookfish.oauth.authorize('gmail', {
          connectionId: 'personal/gmail',
        })
        return ctx.json({ error: 'authorization_required', authorize_url }, 401)
      }

      const mcp = new McpClient(
        { name: 'gmail-inbox-quickstart', version: '1.0.0' },
        { versionNegotiation: { mode: 'auto' } },
      )

      // Call Gmail through MCP, restarting authorization if the token is rejected.
      try {
        await mcp.connect(new StreamableHTTPClientTransport(mcpUrl, {
          authProvider: { token: async () => accessToken },
        }))
        const inbox = await mcp.callTool({
          name: 'Gmail_SearchEmailsByQuery',
          arguments: {
            query: 'in:inbox',
            max_results: 10,
            result_detail: 'lightweight',
          },
        })
        return ctx.json(inbox)
      } catch (error) {
        if (!(error instanceof UnauthorizedError)) throw error
        const { authorize_url } = await hookfish.oauth.authorize('gmail', {
          connectionId: 'personal/gmail',
        })
        return ctx.json({ error: 'authorization_required', authorize_url }, 401)
      } finally {
        await mcp.close().catch(() => undefined)
      }
    })

    serve({ fetch: app.fetch, port: Number(process.env.PORT ?? 8787) })
    ```

    The `/api/*` route mounts Hookfish, including its API documentation at
    `/api/docs`.
  </Step>

  <Step title="Start the API">
    Start the generated backend and dashboard.

    ```bash theme={null}
    pnpm dev
    ```
  </Step>

  <Step title="Request the inbox">
    Call the application endpoint from another terminal.

    ```bash theme={null}
    curl --include http://127.0.0.1:8787/gmail/inbox
    ```

    The first request returns `401 Unauthorized` with an `authorize_url`. Open
    that URL, sign in with Google, and approve access. After Hookfish handles the
    callback, run the same command again. The endpoint now returns the MCP tool
    result for the user's inbox.
  </Step>
</Steps>

<Check>
  You now have one Hono endpoint that returns an authorization URL until Gmail
  is connected, then returns the user's inbox through MCP. Hookfish stores and
  refreshes the provider token behind that endpoint.
</Check>

<Warning>
  This quickstart leaves `/gmail/inbox` unauthenticated for local testing.
  Authenticate the application user and use a scoped broker token before you
  deploy it.
</Warning>

## Understand the generated project

The default development command runs two processes:

```text theme={null}
Browser
  │
  ▼
Hookfish dashboard :5173
  │ proxies /api
  ▼
Hookfish backend :8787
  │
  ▼
PGlite data/hookfish
```

The generated `hookfish.config.ts` contains provider factories, database
configuration, callback settings, and documentation visibility. The backend
mounts `HookfishServer.init(config).fetch` below `/api`.

## Next steps

* Read [How Hookfish works](/concepts/how-it-works) to understand the callback
  and token-refresh lifecycle.
* Choose a [deployment backend](/deployment/choose-backend).
* Replace the root key with a [scoped broker token](/authorization/token-scoping).
* Add [application authentication](/authentication/application-auth), then
  call Hookfish from your authorized server routes.
