AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL unreviewed MIT Self-run

Add Gmail

skill-crypdick-pynchy-add-gmail · by crypdick

Add Gmail integration to NanoClaw. Can be configured as a tool (agent reads/sends emails when triggered from WhatsApp) or as a full channel (emails can trigger the agent, schedule tasks, and receive replies). Guides through GCP OAuth setup and implements the integration.

No reviews yet
0 installs
23 views
0.0% view→install

Install

$ agentstack add skill-crypdick-pynchy-add-gmail

Open-source listing, not yet scanned by AgentStack. Follow the source repository for install instructions.

Security review

⚠ Flagged

1 finding(s); flagged for manual review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures
  • high Dangerous shell/eval execution.

What it can access

  • Network access No
  • Filesystem access No
  • Shell / process execution Used
  • Environment & secrets Used
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

View the full security report →

Reliability & compatibility

Not yet reviewed
0 installs to date
no reviews yet
3mo ago

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of Add Gmail? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

Add Gmail Integration

This skill adds Gmail capabilities to NanoClaw. It can be configured in two modes:

  1. Tool Mode - Agent can read/send emails, but only when triggered from WhatsApp
  2. Channel Mode - Emails can trigger the agent, schedule tasks, and receive email replies

Initial Questions

Ask the user:

> How do you want to use Gmail with NanoClaw? > > Option 1: Tool Mode > - Agent can read and send emails when you ask it to > - Triggered only from WhatsApp (e.g., "@Andy check my email" or "@Andy send an email to...") > - Simpler setup, no email polling > > Option 2: Channel Mode > - Everything in Tool Mode, plus: > - Emails to a specific address/label trigger the agent > - Agent replies via email (not WhatsApp) > - Can schedule tasks via email > - Requires email polling infrastructure

Store their choice and proceed to the appropriate section.


Prerequisites (Both Modes)

1. Check Existing Gmail Setup

First, check if Gmail is already configured:

ls -la ~/.gmail-mcp/ 2>/dev/null || echo "No Gmail config found"

If credentials.json exists, skip to "Verify Gmail Access" below.

2. Create Gmail Config Directory

mkdir -p ~/.gmail-mcp

3. GCP Project Setup

USER ACTION REQUIRED

Tell the user:

> I need you to set up Google Cloud OAuth credentials. I'll walk you through it: > > 1. Open https://console.cloud.google.com in your browser > 2. Create a new project (or select existing) - click the project dropdown at the top

Wait for user confirmation, then continue:

> 3. Now enable the Gmail API: > - In the left sidebar, go to APIs & Services → Library > - Search for "Gmail API" > - Click on it, then click Enable

Wait for user confirmation, then continue:

> 4. Now create OAuth credentials: > - Go to APIs & Services → Credentials (in the left sidebar) > - Click + CREATE CREDENTIALS at the top > - Select OAuth client ID > - If prompted for consent screen, choose "External", fill in app name (e.g., "NanoClaw"), your email, and save > - For Application type, select Desktop app > - Name it anything (e.g., "NanoClaw Gmail") > - Click Create

Wait for user confirmation, then continue:

> 5. Download the credentials: > - Click DOWNLOAD JSON on the popup (or find it in the credentials list and click the download icon) > - Save it as gcp-oauth.keys.json > > Where did you save the file? (Give me the full path, or just paste the file contents here)

If user provides a path, copy it:

cp "/path/user/provided/gcp-oauth.keys.json" ~/.gmail-mcp/gcp-oauth.keys.json

If user pastes the JSON content, write it directly:

cat > ~/.gmail-mcp/gcp-oauth.keys.json  I'm going to run the Gmail authorization. A browser window will open asking you to sign in to Google and grant access.
>
> **Important:** If you see a warning that the app isn't verified, click "Advanced" then "Go to [app name] (unsafe)" - this is normal for personal OAuth apps.

Run the authorization:

```bash
npx -y @gongrzhe/server-gmail-autoauth-mcp auth

If that doesn't work (some versions don't have an auth subcommand), run it and let it prompt:

timeout 60 npx -y @gongrzhe/server-gmail-autoauth-mcp || true

Tell user: > Complete the authorization in your browser. The window should close automatically when done. Let me know when you've authorized.

5. Verify Gmail Access

Check that credentials were saved:

if [ -f ~/.gmail-mcp/credentials.json ]; then
  echo "Gmail authorization successful!"
  ls -la ~/.gmail-mcp/
else
  echo "ERROR: credentials.json not found - authorization may have failed"
fi

Test the connection by listing labels (quick sanity check):

echo '{"method": "tools/list"}' | timeout 10 npx -y @gongrzhe/server-gmail-autoauth-mcp 2>/dev/null | head -20 || echo "MCP responded (check output above)"

If everything works, proceed to implementation.


Tool Mode Implementation

For Tool Mode, integrate Gmail MCP into the agent runner. Execute these changes directly.

Step 1: Add Gmail MCP to Agent Runner

Read container/agent-runner/src/index.ts and find the mcpServers config in the query() call.

Add gmail to the mcpServers object:

gmail: { command: 'npx', args: ['-y', '@gongrzhe/server-gmail-autoauth-mcp'] }

Find the allowedTools array and add Gmail tools:

'mcp__gmail__*'

The result should look like:

mcpServers: {
  nanoclaw: ipcMcp,
  gmail: { command: 'npx', args: ['-y', '@gongrzhe/server-gmail-autoauth-mcp'] }
},
allowedTools: [
  'Bash',
  'Read', 'Write', 'Edit', 'Glob', 'Grep',
  'WebSearch', 'WebFetch',
  'mcp__nanoclaw__*',
  'mcp__gmail__*'
],

Step 2: Mount Gmail Credentials in Container

Read src/container-runner.ts and find the buildVolumeMounts function.

Add this mount block (after the .claude mount is a good location):

// Gmail credentials directory
const gmailDir = path.join(homeDir, '.gmail-mcp');
if (fs.existsSync(gmailDir)) {
  mounts.push({
    hostPath: gmailDir,
    containerPath: '/home/node/.gmail-mcp',
    readonly: false  // MCP may need to refresh tokens
  });
}

Step 3: Update Group Memory

Append to groups/CLAUDE.md (the global memory file):


## Email (Gmail)

You have access to Gmail via MCP tools:
- `mcp__gmail__search_emails` - Search emails with query
- `mcp__gmail__get_email` - Get full email content by ID
- `mcp__gmail__send_email` - Send an email
- `mcp__gmail__draft_email` - Create a draft
- `mcp__gmail__list_labels` - List available labels

Example: "Check my unread emails from today" or "Send an email to john@example.com about the meeting"

Also append the same section to groups/main/CLAUDE.md.

Step 4: Rebuild and Restart

Run these commands:

cd container && ./build.sh

Wait for container build to complete, then:

cd .. && npm run build

Wait for TypeScript compilation, then restart the service:

launchctl kickstart -k gui/$(id -u)/com.nanoclaw

Check that it started:

sleep 2 && launchctl list | grep nanoclaw

Step 5: Test Gmail Integration

Tell the user:

> Gmail integration is set up! Test it by sending this message in your WhatsApp god channel: > > @Andy check my recent emails > > Or: > > @Andy list my Gmail labels

Watch the logs for any errors:

tail -f logs/nanoclaw.log

Channel Mode Implementation

Channel Mode includes everything from Tool Mode, plus email polling and routing.

Additional Questions for Channel Mode

Ask the user:

> How should the agent be triggered from email? > > Option A: Specific Label > - Create a Gmail label (e.g., "NanoClaw") > - Emails with this label trigger the agent > - You manually label emails or set up Gmail filters > > Option B: Email Address Pattern > - Emails to a specific address pattern (e.g., andy+task@gmail.com) > - Uses Gmail's plus-addressing feature > > Option C: Subject Prefix > - Emails with a subject starting with a keyword (e.g., "[Andy]") > - Anyone can trigger the agent by using the prefix

Also ask:

> How should email conversations be grouped? > > Option A: Per Email Thread > - Each email thread gets its own conversation context > - Agent remembers the thread history > > Option B: Per Sender > - All emails from the same sender share context > - Agent remembers all interactions with that person > > Option C: Single Context > - All emails share the god group context > - Like an additional input to the god channel

Store their choices for implementation.

Step 1: Complete Tool Mode First

Complete all Tool Mode steps above before continuing. Verify Gmail tools work by having the user test @Andy check my recent emails.

Step 2: Add Email Polling Configuration

Read src/types.ts and add this interface:

export interface EmailChannelConfig {
  enabled: boolean;
  triggerMode: 'label' | 'address' | 'subject';
  triggerValue: string;  // Label name, address pattern, or subject prefix
  contextMode: 'thread' | 'sender' | 'single';
  pollIntervalMs: number;
  replyPrefix?: string;  // Optional prefix for replies
}

Read src/config.ts and add this configuration (customize values based on user's earlier answers):

export const EMAIL_CHANNEL: EmailChannelConfig = {
  enabled: true,
  triggerMode: 'label',  // or 'address' or 'subject'
  triggerValue: 'NanoClaw',  // the label name, address pattern, or prefix
  contextMode: 'thread',
  pollIntervalMs: 60000,  // Check every minute
  replyPrefix: '[Andy] '
};

Step 3: Add Email State Tracking

Read src/db.ts and add these functions for tracking processed emails:

// Track processed emails to avoid duplicates
export function initEmailTable(): void {
  db.exec(`
    CREATE TABLE IF NOT EXISTS processed_emails (
      message_id TEXT PRIMARY KEY,
      thread_id TEXT NOT NULL,
      sender TEXT NOT NULL,
      subject TEXT,
      processed_at TEXT NOT NULL,
      response_sent INTEGER DEFAULT 0
    )
  `);
}

export function isEmailProcessed(messageId: string): boolean {
  const row = db.prepare('SELECT 1 FROM processed_emails WHERE message_id = ?').get(messageId);
  return !!row;
}

export function markEmailProcessed(messageId: string, threadId: string, sender: string, subject: string): void {
  db.prepare(`
    INSERT OR REPLACE INTO processed_emails (message_id, thread_id, sender, subject, processed_at)
    VALUES (?, ?, ?, ?, ?)
  `).run(messageId, threadId, sender, subject, new Date().toISOString());
}

export function markEmailResponded(messageId: string): void {
  db.prepare('UPDATE processed_emails SET response_sent = 1 WHERE message_id = ?').run(messageId);
}

Also find the initDatabase() function in src/db.ts and add a call to initEmailTable().

Step 4: Create Email Channel Module

Create a new file src/email-channel.ts with this content:

import { EMAIL_CHANNEL } from './config.js';
import { isEmailProcessed, markEmailProcessed, markEmailResponded } from './db.js';
import pino from 'pino';

const logger = pino({
  level: process.env.LOG_LEVEL || 'info',
  transport: { target: 'pino-pretty', options: { colorize: true } }
});

interface EmailMessage {
  id: string;
  threadId: string;
  from: string;
  subject: string;
  body: string;
  date: string;
}

// Gmail MCP client functions (call via subprocess or import the MCP directly)
// These would invoke the Gmail MCP tools

export async function checkForNewEmails(): Promise {
  // Build query based on trigger mode
  let query: string;
  switch (EMAIL_CHANNEL.triggerMode) {
    case 'label':
      query = `label:${EMAIL_CHANNEL.triggerValue} is:unread`;
      break;
    case 'address':
      query = `to:${EMAIL_CHANNEL.triggerValue} is:unread`;
      break;
    case 'subject':
      query = `subject:${EMAIL_CHANNEL.triggerValue} is:unread`;
      break;
  }

  // This requires calling Gmail MCP's search_emails tool
  // Implementation depends on how you want to invoke MCP from Node
  // Option 1: Use @anthropic-ai/claude-agent-sdk with just gmail MCP
  // Option 2: Run npx gmail MCP as subprocess and parse output
  // Option 3: Import gmail-autoauth-mcp directly

  // Placeholder - implement based on preference
  return [];
}

export async function sendEmailReply(
  threadId: string,
  to: string,
  subject: string,
  body: string
): Promise {
  // Call Gmail MCP's send_email tool with in_reply_to for threading
  // Prefix subject with replyPrefix if configured
  const replySubject = subject.startsWith('Re:')
    ? subject
    : `Re: ${subject}`;

  const prefixedBody = EMAIL_CHANNEL.replyPrefix
    ? `${EMAIL_CHANNEL.replyPrefix}${body}`
    : body;

  // Implementation: invoke Gmail MCP send_email
}

export function getContextKey(email: EmailMessage): string {
  switch (EMAIL_CHANNEL.contextMode) {
    case 'thread':
      return `email-thread-${email.threadId}`;
    case 'sender':
      return `email-sender-${email.from.toLowerCase()}`;
    case 'single':
      return 'email-main';
  }
}

Step 5: Add Email Polling to Main Loop

Read src/index.ts and add the email polling infrastructure. First, add these imports at the top:

import { checkForNewEmails, sendEmailReply, getContextKey } from './email-channel.js';
import { EMAIL_CHANNEL } from './config.js';
import { isEmailProcessed, markEmailProcessed, markEmailResponded } from './db.js';

Then add the startEmailLoop function:

async function startEmailLoop(): Promise {
  if (!EMAIL_CHANNEL.enabled) {
    logger.info('Email channel disabled');
    return;
  }

  logger.info(`Email channel running (trigger: ${EMAIL_CHANNEL.triggerMode}:${EMAIL_CHANNEL.triggerValue})`);

  while (true) {
    try {
      const emails = await checkForNewEmails();

      for (const email of emails) {
        if (isEmailProcessed(email.id)) continue;

        logger.info({ from: email.from, subject: email.subject }, 'Processing email');
        markEmailProcessed(email.id, email.threadId, email.from, email.subject);

        // Determine which group/context to use
        const contextKey = getContextKey(email);

        // Build prompt with email content
        const prompt = `
${email.from}
${email.subject}
${email.body}

Respond to this email. Your response will be sent as an email reply.`;

        // Run agent with email context
        // You'll need to create a registered group for email or use a special handler
        const response = await runEmailAgent(contextKey, prompt, email);

        if (response) {
          await sendEmailReply(email.threadId, email.from, email.subject, response);
          markEmailResponded(email.id);
          logger.info({ to: email.from }, 'Email reply sent');
        }
      }
    } catch (err) {
      logger.error({ err }, 'Error in email loop');
    }

    await new Promise(resolve => setTimeout(resolve, EMAIL_CHANNEL.pollIntervalMs));
  }
}

Then add startEmailLoop() in the main() function, after startMessageLoop():

// In main(), after startMessageLoop():
startEmailLoop();

Step 6: Implement Email Agent Runner

Add this function to src/index.ts (or create a separate src/email-agent.ts if preferred):

async function runEmailAgent(
  contextKey: string,
  prompt: string,
  email: EmailMessage
): Promise {
  // Email uses either:
  // 1. A dedicated "email" group folder
  // 2. Or dynamic folders per thread/sender

  const groupFolder = EMAIL_CHANNEL.contextMode === 'single'
    ? 'main'  // Use god group context
    : `email/${contextKey}`;  // Isolated email context

  // Ensure folder exists
  const groupDir = path.join(GROUPS_DIR, groupFolder);
  fs.mkdirSync(groupDir, { recursive: true });

  // Create minimal registered group for email
  const emailGroup: RegisteredGroup = {
    name: contextKey,
    folder: groupFolder,
    trigger: '',  // No trigger for email
    added_at: new Date().toISOString()
  };

  // Use existing runContainerAgent
  const output = await runContainerAgent(emailGroup, {
    prompt,
    sessionId: sessions[groupFolder],
    groupFolder,
    chatJid: `email:${email.from}`,  // Use email: prefix for JID
    isMain: false,
    isScheduledTask: false
  });

  if (output.newSessionId) {
    sessions[groupFolder] = output.newSessionId;
    setSession(groupFolder, output.newSessionId);
  }

  return output.status === 'success' ? output.result : null;
}

Step 7: Update IPC for Email Responses (Optional)

If you want the agent to be able to send emails proactively from within a session, read container/agent-runner/src/ipc-mcp.ts and add this tool:

// Add to the MCP tools
{
  name: 'send_email_reply',
  description: 'Send an email reply in the current thread',
  inputSchema: {
    type: 'object',
    p

…

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [crypdick](https://github.com/crypdick)
- **Source:** [crypdick/pynchy](https://github.com/crypdick/pynchy)
- **License:** MIT
- **Homepage:** https://pynchy.ricardodecal.com/

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.