dsh-gmail
Verifieddsh-gmail Β· v0.3.1 Β· MIT
Connect DeepSeek Harness to Gmail and let the agent search and read mail with read-only OAuth access
Install
dsh plugin add dsh-gmail Confirm the layer applied with dsh --profile default --dump-config β see the install guide.
Source
Published to npm without a public repository. Inspect the package contents before installing.
Tags
Readme
dsh-gmail
Connect DeepSeek Harness (DSH) to Gmail and let the agent search your mailbox with Gmail query syntax and read individual messages. OAuth requests only the gmail.readonly scope, so the plugin can never send, edit, label, archive, or delete mail.
Features
- Read-only by design β only
https://www.googleapis.com/auth/gmail.readonlyis requested. - Four agent tools β connect, check status, search, and read messages.
- Gmail query syntax β
from:,subject:,after:,before:,has:attachment,is:unread, and any other Gmail filter. - No client secret in the package β Google Web credentials live in a small Cloudflare Worker broker, never in your install.
- PKCE + signed OAuth state β authorization codes are bound to the local machine and expire in 10 minutes.
- Tokens stay local β access/refresh tokens are stored only on your machine; message content never passes through the broker.
- Optional settings card β with
dsh-authorize-appinstalled, a Gmail card appears under Settings β Connected Apps.
Requirements
- Node.js β₯ 22
- DeepSeek Harness with the web profile running (
dsh web)
Install
dsh plugin --profile web add dsh-gmail
Then restart dsh web. For local development:
npm install
npm test
dsh plugin --profile web add file:/absolute/path/to/dsh-gmail
Connect Gmail
- Ask the agent to connect Gmail. It calls
gmail_connect, which returns an authorization URL. - Open that URL, sign in to Google, and approve read-only access.
- Google redirects back to the local DSH callback, which exchanges the code and stores the token.
If dsh-authorize-app is installed, you can instead open Settings β Connected Apps β Connect Gmail to get a fresh authorization URL directly.
Tools
| Tool | Description |
|---|---|
gmail_connect |
Starts a read-only Gmail OAuth connection and returns an authorization URL. |
gmail_connection_status |
Reports the connected account (and whether a reconnect is needed) without exposing tokens. |
gmail_search |
Searches keywords or Gmail filters and returns message metadata (id, sender, subject, date, snippet). |
gmail_read_message |
Reads one message body (bounded) and lists attachment filenames without downloading them. |
Configuration
The plugin reads its configuration from the profile patch (cordis.patch.yml):
| Key | Default | Description |
|---|---|---|
enabled |
true |
Disable to turn the plugin off. |
callbackUrl |
http://127.0.0.1:3080/auth/gmail/callback |
Local URL Google redirects back to. |
tokenFile |
~/.config/deepseek-harness/dsh-gmail.json |
Where tokens are stored (mode 0600). |
maxResultChars |
24000 |
Cap on the JSON size returned per tool call. |
oauthBrokerUrl |
'' |
Origin of the OAuth broker Worker. Required before connecting. |
Set the deployed broker origin in the patch before publishing:
- insert:
- id: gmail
name: 'dsh-gmail'
config:
enabled: true
callbackUrl: 'http://127.0.0.1:3080/auth/gmail/callback'
oauthBrokerUrl: 'https://auth.example.com'
For local testing, the DSH_GMAIL_OAUTH_BROKER_URL environment variable overrides oauthBrokerUrl.
Architecture
The npm package contains no Google client ID or client secret. A small publisher-hosted Cloudflare Worker holds the Google Web application credentials, validates a signed OAuth state, and exchanges or refreshes tokens:
βββββββββββββββββββββ ββββββββββββββββββββββββββββββ
β DSH web (agent) β PKCE verifier + β OAuth broker (Worker) β
β dsh-gmail pluginβ β signed state, code βββΊ β holds client_id/secret β
β β ββββ access/refresh ββββ β /oauth/google/* β
βββββββββββ¬ββββββββββ ββββββββββββββββ¬ββββββββββββββ
β β
β Gmail API β gmail.readonly only β token exchange
β (tokens stay on your machine) βΌ
ββββββββββββββββββββββββββββββββββββββββΊ Google OAuth / Gmail API
Flow:
- The plugin generates a PKCE verifier/challenge and a random local state.
- It redirects to the broker's
/oauth/google/start, which signs the state and redirects to Google. - Google sends the user back to the broker's
/oauth/google/callbackwith an authorization code. - The broker verifies its signature and forwards the code to the local
callbackUrl. - The plugin exchanges the code (plus verifier) at
/oauth/google/token, and refreshes later at/oauth/google/refresh.
PKCE is generated by the local plugin and verified by the Worker. Access and refresh tokens are returned over HTTPS and stored only on the user's machine; Gmail API requests and message content do not pass through the Worker.
Deploy your own OAuth broker
The repo ships a complete Worker under worker/. Deploy it if you are self-hosting or publishing your own package:
Create a Google Cloud project, enable the Gmail API, and configure the OAuth consent screen.
gmail.readonlyis a restricted user-data scope, so public use requires Google verification.Deploy
worker/to Cloudflare and attach a custom domain you own, such ashttps://auth.example.com.Create a Google OAuth Web application client with this exact authorized redirect URI:
https://auth.example.com/oauth/google/callbackFrom
worker/, set these three Worker secrets using the interactive prompt; never pass their values on the command line:wrangler secret put GOOGLE_CLIENT_ID wrangler secret put GOOGLE_CLIENT_SECRET wrangler secret put STATE_SIGNING_KEYSTATE_SIGNING_KEYmust contain at least 32 random characters.Set the same Worker origin as
oauthBrokerUrl, publish the plugin, and submit the Google production app for verification. The public homepage and privacy policy should use a domain you own.
Worker development and validation:
cd worker
npm install
cp .dev.vars.example .dev.vars # fill in the three values for local testing
npm run check # typecheck + unit tests + dry-run deploy
npm run deploy
The Worker exposes these endpoints:
| Method | Path | Purpose |
|---|---|---|
GET |
/health |
Reports {"ok":true} when all three secrets are configured. |
GET |
/oauth/google/start |
Signs state and redirects to Google. |
GET |
/oauth/google/callback |
Validates state and forwards the code to the local callback. |
POST |
/oauth/google/token |
Exchanges the authorization code for tokens. |
POST |
/oauth/google/refresh |
Refreshes an expired access token. |
Security & privacy
- Only the
gmail.readonlyscope is ever requested. - The plugin cannot send, edit, label, archive, or delete mail.
- The Worker sees OAuth authorization codes and token responses, but never persists them.
- Tokens are stored locally with mode
0600. - Gmail API requests and message content stay on your machine; the broker is only used for the OAuth dance.
Token storage
Tokens are stored at ~/.config/deepseek-harness/dsh-gmail.json by default ($XDG_CONFIG_HOME is honored). Override tokenFile in the profile patch when needed.
- Disconnect locally β delete the token file.
- Revoke the remote grant β remove the app in the Google Account security page.
Limits
- One Google account per configured
tokenFile. - Search returns at most 20 messages per call.
- Message bodies default to 20,000 characters (max 50,000) and never include downloaded attachment contents.
- OAuth state and PKCE expire after 10 minutes.
- Google OAuth verification is an external release requirement; installing this package cannot grant API approval.
Development
npm install
npm run build # tsc
npm test # build + node --test tests/*.test.mjs
npm run typecheck # tsc --noEmit
Worker tests run in the Cloudflare vitest pool:
cd worker
npm install
npm run check # tsc + vitest + wrangler deploy --dry-run
License
MIT Β© 2026 extension-hunter