
Threat Research
/Security News
Searchjacked: Hidden Browser Extensions Hijack Searches for 13 Million Users
319 browser extensions installed by 13.4 million users hijack searches, capture queries, and collect referral payments from Yahoo and Bing.
@tekmidian/coogle
Advanced tools
Coogle — Google Workspace MCP multiplexer for Claude Code. One persistent daemon, no credential conflicts across multiple sessions.
Google Workspace MCP multiplexer for Claude Code. One persistent daemon holds the single coogle-mcp connection. Every Claude Code session goes through it. No more credential conflicts, no more 142 tools registering simultaneously.
Coogle is the MCP server that gives Claude access to Gmail, Calendar, Drive, Docs, Sheets, and the rest of Google Workspace — across multiple sessions simultaneously.
The naive setup — running Google Workspace MCP directly — works fine for one session. The moment you open a second Claude Code window, both sessions try to own the same Google OAuth credentials. They fight, one wins, the other gets errors or stale tokens.
Coogle solves this with a daemon pattern: one persistent process holds the Google Workspace connection and serializes all calls through a queue. Every Claude Code session runs a thin MCP shim that forwards tool calls to the daemon over a Unix Domain Socket. From Claude's perspective nothing changes — 142 tools are available. Under the hood, every call goes through a single, stable connection.
Claude Code (session 1)
Claude Code (session 2) ──> coogle daemon ──> coogle-mcp ──> Google APIs
Claude Code (session 3)
Tell Claude Code:
Clone https://github.com/mnott/Coogle and set it up for me
Claude clones the repo, finds the setup skill, and handles everything autonomously — prerequisites, build, config, daemon install, and Google OAuth. You just approve the permissions in your browser when prompted.
If you prefer a traditional install without cloning:
npx -y @tekmidian/coogle setup
The wizard handles everything: environment check, credentials, config, daemon install, launchd service, and Claude config patching.
uvx (from uv): brew install uv or pip install uvgit clone https://github.com/mnott/Coogle ~/dev/apps/coogle
cd ~/dev/apps/coogle
npm install
npm run build
node dist/index.js setup
The wizard walks you through eight steps:
uvx are available~/.claude.json (any MCP server entry with GOOGLE_OAUTH_CLIENT_ID in its env block) or prompts for them manually~/.config/coogle/config.jsondist/index.js existscom.pai.coogle as a macOS launch agent (auto-starts on login)~/.claude.json to point the coogle MCP server at the coogle shimAfter setup, restart Claude Code. The coogle shim starts automatically when Claude loads, discovers all tools from the daemon, and proxies every call.
Coogle has two runtime components:
Daemon (src/daemon.ts) — a long-running process that spawns and owns a single coogle-mcp child via stdio. It listens on a Unix Domain Socket (/tmp/coogle.sock) and serializes all incoming tool calls through a queue. If the child crashes, it auto-respawns with a 3-second cooldown.
MCP shim (src/mcp-server.ts) — a thin proxy started by Claude Code. On startup it connects to the daemon socket, dynamically discovers all available tools, registers them with the MCP SDK, and forwards every tools/call request to the daemon over IPC.
Claude Code (MCP stdio)
|
| JSON-RPC (stdio)
|
MCP shim (coogle mcp)
|
| NDJSON (Unix socket /tmp/coogle.sock)
|
Coogle daemon (coogle serve)
|
| MCP stdio
|
coogle-mcp backend
|
| HTTPS
|
Google APIs
# Start the daemon (normally managed by launchd)
node dist/index.js serve
# Start the MCP shim (used as the Claude MCP command)
node dist/index.js mcp
# Check daemon status
node dist/index.js status
# Restart the coogle-mcp child process
node dist/index.js restart
# Print the resolved configuration
node dist/index.js config
# Generate the launchd plist for this system (outputs to stdout)
node dist/index.js generate-plist
# Interactive first-time setup
node dist/index.js setup
Config lives at ~/.config/coogle/config.json. It is created automatically by setup or on first serve.
{
"socketPath": "/tmp/coogle.sock",
"mcp": {
"command": "uvx",
"args": ["workspace-mcp", "--tool-tier", "core"]
},
"credentials": {
"source": "claude-json",
"claudeJsonPath": "~/.claude.json",
"mcpServerName": "coogle"
},
"callTimeoutMs": 120000,
"logLevel": "info"
}
source | How credentials are loaded |
|---|---|
claude-json (default) | Read GOOGLE_OAUTH_CLIENT_ID and GOOGLE_OAUTH_CLIENT_SECRET from the named MCP server's env block in ~/.claude.json |
env | Read from GOOGLE_OAUTH_CLIENT_ID and GOOGLE_OAUTH_CLIENT_SECRET environment variables |
manual | Stored directly in config as clientId and clientSecret |
| Field | Default | Description |
|---|---|---|
socketPath | /tmp/coogle.sock | Unix Domain Socket path for IPC |
mcp.command | uvx | Command to run coogle-mcp |
mcp.args | ["workspace-mcp", "--tool-tier", "core"] | Arguments for the Google Workspace MCP backend |
credentials.source | claude-json | Where to load Google OAuth credentials from |
credentials.claudeJsonPath | ~/.claude.json | Path to Claude config (for claude-json source) |
credentials.mcpServerName | coogle | MCP server key to read credentials from |
callTimeoutMs | 120000 | Per-call timeout in milliseconds |
logLevel | info | Log verbosity: debug, info, warn, error |
defaultAccount | (unset) | Google account used when a call omits user_google_email |
autoOpenAuth | false | Allow the child to open a browser window when it wants authorization |
| Variable | Default | Description |
|---|---|---|
COOGLE_AUTO_OPEN_AUTH | unset (off) | Set to 1 to let the workspace-mcp child open a browser window on an authorization prompt. Overrides autoOpenAuth. |
The daemon normally runs under launchd, so set this in com.pai.coogle.plist
(EnvironmentVariables) rather than in your shell, then restart the daemon.
Coogle supports managing multiple Google accounts simultaneously. Each account needs a one-time OAuth authorization, after which all 142 tools work for any authorized account — just pass the appropriate user_google_email parameter.
OAuth tokens are stored per user in ~/.google_workspace_mcp/credentials/:
~/.google_workspace_mcp/credentials/
├── alice@example.com.json
├── bob@example.com.json
└── carol@example.com.json
The OAuth Client ID and Client Secret (app credentials) are shared across all accounts — they come from your Google Cloud project. Each user gets their own refresh token after completing the consent flow once.
manual source in ~/.config/coogle/config.json:{
"credentials": {
"source": "manual",
"clientId": "your-client-id.apps.googleusercontent.com",
"clientSecret": "GOCSPX-your-client-secret"
}
}
You can find these values in an existing token file at ~/.google_workspace_mcp/credentials/<email>.json (fields client_id and client_secret) or in your Google Cloud Console.
# Kill the daemon (launchd auto-restarts it)
launchctl kickstart -k gui/$(id -u)/com.pai.coogle
start_google_auth(service_name="people", user_google_email="newuser@example.com")
Or call any tool with the new email — workspace-mcp will return an authorization URL if no token exists.
Open the authorization URL in a browser, sign in as the target account, and approve the permissions. The callback goes to localhost:8000 (the OAuth server built into workspace-mcp).
Coogle does not open the browser for you — a tool call must never pop a window while you are working. Take the URL from the daemon log:
grep -A1 "Authorization URL for" /tmp/coogle.log | tail -2
To have the browser open automatically instead, set COOGLE_AUTO_OPEN_AUTH=1
for the daemon (see Environment variables).
Done. A token file is saved to ~/.google_workspace_mcp/credentials/newuser@example.com.json. All subsequent tool calls with that email work immediately.
If all accounts are on the same Google Workspace domain (e.g. @example.com) and you are the domain admin, you can authorize all accounts yourself — just sign in as each user in the browser when the consent screen appears. No need for each person to do it themselves.
Testing mode, Google expires every refresh token after 7 days, and each account then needs a fresh authorization. If accounts keep needing re-authorization about weekly, publish the consent screen in Google Cloud Console → APIs & Services → OAuth consent screen.gina@example.de for the account gina@example.org), the credential file is named after the alias while Google's ID token names the canonical account. workspace-mcp binds one account per stdio session and rejects the mismatch as a session rebind. Coogle logs a NOTE: line when it detects this; no action is needed, because keeping the token fresh avoids the child's refresh path entirely.workspace-mcp OAuth callback server listens on localhost:8000. If this port is occupied when you trigger an auth flow, restart the daemon first.If you prefer not to use the setup wizard, edit ~/.claude.json directly. Find the coogle entry under mcpServers and replace its command/args with the coogle shim:
{
"mcpServers": {
"coogle": {
"type": "stdio",
"command": "node",
"args": ["/path/to/coogle/dist/index.js", "mcp"]
}
}
}
The env block with GOOGLE_OAUTH_CLIENT_ID and GOOGLE_OAUTH_CLIENT_SECRET is no longer needed in the Claude config — the daemon reads credentials directly and injects them into the coogle-mcp child environment.
Keep a backup of the original config before editing:
cp ~/.claude.json ~/.claude.json.backup
The setup wizard creates this backup automatically.
The daemon is designed to run as a persistent macOS launch agent. The setup wizard installs it automatically. To manage it manually:
# Generate the plist (uses the correct node and index.js paths for your system)
node dist/index.js generate-plist > ~/Library/LaunchAgents/com.pai.coogle.plist
# Load (start)
launchctl load ~/Library/LaunchAgents/com.pai.coogle.plist
# Unload (stop)
launchctl unload ~/Library/LaunchAgents/com.pai.coogle.plist
# Restart
launchctl kickstart -k gui/$(id -u)/com.pai.coogle
Logs go to /tmp/coogle.log.
tail -f /tmp/coogle.log
The 142 available tools span 12 Google services. They are discovered dynamically from coogle-mcp on daemon startup — no hardcoded list.
| Service | Example tools |
|---|---|
| Gmail | search_gmail_messages, send_gmail_message, get_gmail_thread_content |
| Calendar | get_events, create_event, modify_event, query_freebusy |
| Drive | search_drive_files, list_drive_items, share_drive_file, copy_drive_file |
| Docs | get_doc_content, modify_doc_text, create_doc, insert_doc_elements |
| Sheets | read_sheet_values, modify_sheet_values, create_spreadsheet |
| Slides | create_presentation, get_presentation, batch_update_presentation |
| Forms | create_form, get_form, list_form_responses |
| Contacts | search_contacts, create_contact, list_contact_groups |
| Chat | send_message, get_messages, list_spaces |
| Tasks | list_tasks, create_task, update_task, move_task |
| Scripts | run_script_function, create_script_project |
| Search | search_custom, get_search_engine_info |
"Coogle daemon not running"
The shim cannot reach the daemon socket. Start the daemon:
node dist/index.js serve
Or check if the launchd service is loaded:
launchctl list | grep coogle
cat /tmp/coogle.log
"coogle-mcp child is not connected"
The daemon is running but coogle-mcp failed to start or crashed. Check the log:
tail -50 /tmp/coogle.log
Common cause: uvx not in PATH, or Google OAuth credentials are missing/expired. Try restarting the child:
node dist/index.js restart
No tools discovered / tool count is 0
coogle-mcp started but returned no tools. This usually means the Google OAuth token has expired or is missing.
workspace-mcp manages its own OAuth tokens independently — they are stored in ~/.google_workspace_mcp/credentials/. To re-authenticate, delete the cached token file and restart the daemon:
rm -r ~/.google_workspace_mcp/credentials/
node dist/index.js restart
workspace-mcp will prompt for a new OAuth flow on the next call.
Note: re-running node dist/index.js setup will not fix expired tokens. The setup wizard only handles the OAuth Client ID and Client Secret (app credentials). Token lifecycle is managed entirely by workspace-mcp.
"ACTION REQUIRED: Google Authentication Needed" — but nothing was revoked
Symptom: the first call for an account after an idle period fails with an authorization demand, a Google consent page opens by itself, and repeating the identical call immediately succeeds without doing anything.
Cause: workspace-mcp escalates to a full re-authorization whenever its own
token refresh fails, and one such failure is structural for a multi-account
daemon. The child binds a single Google account to its one stdio session. When
it refreshes an account whose credential file is named after an alias, the ID
token in the refresh response names the canonical account, the child treats that
as a session rebind and rejects it — discarding credentials it had just
refreshed successfully. It then opens a browser as a side effect of the failed
tool call. The retry works because the refreshed token is already on disk, so no
refresh is attempted the second time.
Coogle handles this in three layers:
No browser is opened at any point unless COOGLE_AUTO_OPEN_AUTH=1 is set.
Note: the child's own log line Opened auth URL in browser automatically is
inaccurate when auto-open is disabled. Coogle points the child's $BROWSER at a
no-op binary, so the call succeeds without launching anything.
Daemon keeps crashing
Check /tmp/coogle.log for error messages. The daemon has a 3-second respawn cooldown — launchd will restart it automatically but the ThrottleInterval in the plist prevents tight restart loops.
Multiple Claude sessions getting stale results
This is the condition coogle was built to prevent. Verify all sessions are using the coogle shim:
node dist/index.js status
Check that ~/.claude.json shows node .../coogle/dist/index.js mcp as the coogle MCP command.
Rollback to pre-coogle configuration
cp ~/.claude.json.backup ~/.claude.json
Restart Claude Code to apply.
~/.claude.json or ~/.config/coogle/config.json. OAuth tokens obtained after authentication are managed by workspace-mcp and stored in ~/.google_workspace_mcp/credentials/. All files are local and not transmitted anywhere by Coogle.coogle-mcp over stdio (same machine, same user)./tmp/coogle.sock is local only and accessible only to the current user.coogle-mcp.uvx (from uv) for running coogle-mcp# Install dependencies
npm install
# Build
npm run build
# Watch mode
npm run dev
# Run the daemon directly
npm start
# Run the MCP shim directly
npm run mcp
MIT — see LICENSE
Matthias Nott — github.com/mnott
Links: [[Ideaverse/AI/coogle/coogle|coogle]]
FAQs
Coogle — Google Workspace MCP multiplexer for Claude Code. One persistent daemon, no credential conflicts across multiple sessions.
The npm package @tekmidian/coogle receives a total of 0 weekly downloads. As such, @tekmidian/coogle popularity was classified as not popular.
We found that @tekmidian/coogle demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.

Threat Research
/Security News
319 browser extensions installed by 13.4 million users hijack searches, capture queries, and collect referral payments from Yahoo and Bing.

Threat Research
Malicious Firefox extensions mimic Phantom, Jupiter, and Bitget wallets to steal recovery phrases and private keys, with one fetching its payload remotely.

Product
Socket Threat Intel now shows which malware campaigns were blocked or detected in your organization, with links to scans and Firewall events.