AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
MCP verified MIT Self-run

Rowbound

mcp-eliasstravik-rowbound · by eliasstravik

Open-source CLI and MCP server for Google Sheets enrichment — waterfalls, conditions, HTTP API integrations

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

Install

$ agentstack add mcp-eliasstravik-rowbound

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

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

What it can access

  • Network access Used
  • Filesystem access Used
  • Shell / process execution No
  • 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 →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/mcp-eliasstravik-rowbound)

Reliability & compatibility

Security review passed
0 installs to date
no reviews yet
5mo ago

Declared compatibility

Claude CodeClaude DesktopCursorWindsurf

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 Rowbound? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

Rowbound

A CLI for GTM Engineering in Google Sheets with Claude Code.


Demo

Install

Prerequisites

  • Node.js 22+node --version must be >= 22.0.0
  • gws CLIGoogle Workspace CLI for Sheets access
npm install -g @googleworkspace/cli
gws auth setup   # first time: creates Cloud project, enables APIs, logs in
gws auth login   # subsequent logins

Quick install

npm install -g github:eliasstravik/rowbound

Build from source

git clone https://github.com/eliasstravik/rowbound.git
cd rowbound
npm install
npm run dev -- 

Quick Start

# 1. Initialize a sheet
rowbound init 

# 2. Add an action
rowbound config add-action  --json '{
  "id": "enrich_company",
  "type": "http",
  "target": "company_info",
  "method": "GET",
  "url": "https://api.example.com/company?domain={{row.domain}}",
  "headers": { "Authorization": "Bearer {{env.API_KEY}}" },
  "extract": "$.name"
}'

# 3. Store API keys and run
rowbound env set API_KEY=your_key_here
rowbound run 
rowbound run  --dry-run   # preview first

Column names are automatically resolved to stable IDs when you run rowbound sync.

Features

Sources — Create rows from external data

  • HTTP sources — fetch from any API, extract array from response, map columns via JSONPath
  • Exec sources — run shell commands, parse JSON output into rows
  • Script sources — run named scripts to generate rows from their output
  • Webhook sources — accept inbound POST payloads, create rows in real-time
  • Deduplication — skip or update existing rows based on a match column
  • Scheduling — run sources manually, hourly, daily, or weekly

Actions — Enrich existing rows

  • HTTP actions — call any REST API with templated URLs, headers, and bodies; extract values with JSONPath; ifEmpty fallback when extract returns nothing
  • Waterfall actions — try multiple providers in order until one returns a result (e.g., Clearbit → Apollo → Hunter)
  • Formula actions — compute derived values with JavaScript expressions using {{Column Name}} references
  • AI actions — run headless Claude or Codex per row with configurable model, max turns, tools, and bare mode for faster startup
  • Exec actions — run shell commands and capture stdout
  • Script actions — run reusable named scripts stored in config; supports bash, python3, and node runtimes
  • Lookup actions — pull data from other tabs (boolean, count, or full row JSON)
  • Write actions — push data to other tabs with column mapping; supports append, upsert, and array expansion via expandPath
  • Per-action environment variables — inject env vars per action (e.g., PLAYWRIGHT_HEADLESS=true)

Pipeline

  • Conditional execution — skip actions per-row with when expressions
  • Smart skip — automatically skips rows where the target cell already has a value
  • Watch mode — poll sheets on an interval or trigger runs via webhook
  • Column tracking — stable column IDs that survive header renames and reordering
  • --columns flag — target specific columns by letter (e.g., --columns A-C,E,AP)
  • --rows flag — flexible row specs (e.g., --rows 2-5,8,10-12)
  • Rate limiting — configurable seconds between requests (default: 1 per second)
  • Timeouts in seconds — all user-facing timeouts in seconds, not milliseconds
  • Retry with backoff — exponential, linear, or fixed backoff on failures
  • Structured error handling — per-action onError config maps status codes to actions (skip, write fallback)
  • MCP server — expose all operations as Model Context Protocol tools for Claude Desktop and other AI assistants
  • Run history — track pipeline executions with per-action summaries, durations, and error logs
  • Dry run — preview what would change without writing to the sheet
  • Per-tab stop/start — enable or disable processing per tab; stops mid-run if toggled during execution
  • Per-tab settings — override concurrency, rate limit, retries, and backoff per tab
  • BYOK — bring your own API keys, pay only for the APIs you use

CLI Commands

| Command | Description | |---------|-------------| | rowbound init | Initialize a sheet with a default pipeline config | | rowbound run | Run the enrichment pipeline (--dry-run, --rows, --columns) | | rowbound status | Show pipeline status and enrichment rates | | rowbound watch | Watch for changes and run continuously (--interval, --port) | | rowbound sync | Reconcile columns, validate config, fix issues | | rowbound config show | Display the pipeline config as JSON | | rowbound config add-action | Add an action to the pipeline | | rowbound config remove-action | Remove an action by ID | | rowbound config update-action | Update an action (merge partial JSON) | | rowbound config list-actions | List configured actions (--json) | | rowbound config add-source | Add a source to the pipeline | | rowbound config remove-source | Remove a source by ID | | rowbound config update-source | Update a source (merge partial JSON) | | rowbound config set | Update pipeline settings (--enabled, --disabled, --concurrency, --rate-limit, etc.) | | rowbound config add-script | Add a script to the pipeline config | | rowbound config remove-script | Remove a script by name | | rowbound config update-script | Update a script (merge partial JSON) | | rowbound config validate | Validate the pipeline config | | rowbound runs [runId] | List recent runs or view a specific run | | rowbound runs clear | Delete all run history | | rowbound source run | Run a source to create rows (--source, --dry-run) | | rowbound source list | List configured sources | | rowbound env set | Store an API key globally | | rowbound env remove | Remove a stored key | | rowbound env list | List stored keys (values masked) | | rowbound mcp | Start the MCP server (stdio) |

MCP Server

Rowbound exposes all pipeline operations as MCP tools. Add this to your Claude Desktop config:

{
  "mcpServers": {
    "rowbound": {
      "command": "rowbound",
      "args": ["mcp"]
    }
  }
}

| Tool | Description | |------|-------------| | init_pipeline | Initialize a sheet with a default pipeline config | | run_pipeline | Run the enrichment pipeline | | add_action / remove_action / update_action | Manage pipeline actions | | add_source / remove_source / update_source | Manage data sources | | run_source | Execute a source to create rows | | update_settings | Update pipeline settings (concurrency, rate limit, retry) | | sync_columns | Sync the column registry with the current sheet state | | get_config / validate_config | Read or validate the pipeline config | | get_status | Return pipeline status with enrichment rates | | dry_run | Run in dry mode (no writes) | | start_watch / stop_watch | Manage watch mode | | preview_rows | Read and display rows from the sheet | | list_runs / get_run | View pipeline run history |

Source Types

Sources create rows from external data. They run before actions in the pipeline — new rows are created first, then actions enrich them on the next run.

http source

Fetch from an API and create rows from the response.

{
  "id": "search_companies",
  "type": "http",
  "method": "POST",
  "url": "https://api.blitz-api.ai/v2/search/company",
  "headers": { "x-api-key": "{{env.BLITZ_API_KEY}}" },
  "body": { "industry": "restaurants", "country_code": ["SE"] },
  "extract": "$",
  "extractPath": "$.results",
  "columns": { "Title": "$.company_name", "Website": "$.website_url", "LinkedIn": "$.linkedin_url" },
  "dedup": "Website",
  "schedule": "daily"
}

exec source

Run a shell command and parse JSON output into rows.

{
  "id": "import_leads",
  "type": "exec",
  "command": "curl -s https://api.example.com/leads",
  "extract": "$.data",
  "columns": { "Name": "$.name", "Email": "$.email" },
  "dedup": "Email",
  "updateExisting": true
}

webhook source

Accept inbound POST payloads and create rows. Used with rowbound watch.

{
  "id": "form_submissions",
  "type": "webhook",
  "columns": { "Name": "$.name", "Email": "$.email", "Company": "$.company" },
  "dedup": "Email"
}

Source options

| Field | Description | |-------|-------------| | columns | Maps sheet column headers to JSONPath per item: { "Name": "$.name" }. Use $.nested.field for nested data, or literal strings for static values. | | extract / extractPath | JSONPath to locate the array in the response. extractPath drills into a nested object first (e.g., $.results extracts from {"results": [...]}). | | dedup | Column header to deduplicate on. Existing rows with the same value are skipped. | | updateExisting | When true and dedup is set, update matched rows instead of skipping (default: false). | | schedule | "manual" (default), "hourly", "daily", or "weekly". Watch mode checks schedules automatically. |

script source

Run a named script (defined in the scripts config section) and parse its output into rows.

{
  "id": "import_from_script",
  "type": "script",
  "script": "fetch_leads",
  "args": ["--format", "json"],
  "extract": "$.leads",
  "columns": { "Name": "$.name", "Email": "$.email" },
  "dedup": "Email"
}

Scripts

Scripts are reusable code blocks stored in your pipeline config. Define a script once, then reference it from multiple actions or sources by name. Each script has a runtime (the interpreter) and code (the script body).

Config section

Scripts are stored under the scripts key in your config (global or per-tab):

{
  "scripts": {
    "claude_json": {
      "runtime": "bash",
      "code": "#!/bin/bash\ncurl -s https://api.anthropic.com/v1/messages \\\n  -H \"x-api-key: $ANTHROPIC_API_KEY\" \\\n  -H \"content-type: application/json\" \\\n  -d \"$1\""
    },
    "parse_csv": {
      "runtime": "python3",
      "code": "import csv, json, sys\nwith open(sys.argv[1]) as f:\n    print(json.dumps(list(csv.DictReader(f))))"
    }
  }
}

Supported runtimes: bash, python3, node.

Referencing scripts from actions

Use "type": "script" in an action to run a named script per row. The script receives row data via template-expanded arguments and its stdout is captured as the result.

{
  "id": "enrich_with_claude",
  "type": "script",
  "target": "ai_summary",
  "script": "claude_json",
  "args": ["{\"model\":\"claude-sonnet-4-20250514\",\"max_tokens\":256,\"messages\":[{\"role\":\"user\",\"content\":\"Summarize: {{row.company}}\"}]}"],
  "extract": "$.content[0].text",
  "timeout": 60000
}

Referencing scripts from sources

Use "type": "script" in a source to run a named script and create rows from its output.

{
  "id": "load_leads",
  "type": "script",
  "script": "parse_csv",
  "args": ["/tmp/leads.csv"],
  "columns": { "Name": "$.name", "Email": "$.email" },
  "dedup": "Email"
}

CLI commands

| Command | Description | |---------|-------------| | rowbound config add-script | Add a script to the config | | rowbound config remove-script | Remove a script by name | | rowbound config update-script | Update a script (merge partial JSON) |

Action Types

Templates use {{row.column}} for row data and {{env.KEY}} for environment variables. Actions support conditional execution with when expressions and structured error handling with onError.

http

Call a REST API and extract a value with JSONPath.

{
  "id": "get_company",
  "type": "http",
  "target": "company_name",
  "when": "row.domain !== ''",
  "method": "GET",
  "url": "https://api.clearbit.com/v2/companies/find?domain={{row.domain}}",
  "headers": { "Authorization": "Bearer {{env.CLEARBIT_API_KEY}}" },
  "extract": "$.name",
  "ifEmpty": "❌",
  "onError": { "404": "skip", "429": "skip", "default": { "write": "ERROR" } }
}

waterfall

Try multiple providers in order. First non-empty result wins.

{
  "id": "find_email",
  "type": "waterfall",
  "target": "email",
  "providers": [
    {
      "name": "hunter",
      "method": "GET",
      "url": "https://api.hunter.io/v2/email-finder?domain={{row.domain}}&first_name={{row.first_name}}&last_name={{row.last_name}}&api_key={{env.HUNTER_API_KEY}}",
      "extract": "$.data.email"
    },
    {
      "name": "apollo",
      "method": "POST",
      "url": "https://api.apollo.io/api/v1/people/match",
      "headers": { "Content-Type": "application/json", "X-Api-Key": "{{env.APOLLO_API_KEY}}" },
      "body": { "email": "{{row.personal_email}}", "domain": "{{row.domain}}" },
      "extract": "$.person.email"
    }
  ]
}

formula

Compute a value with a sandboxed JavaScript expression.

{
  "id": "full_name",
  "type": "formula",
  "target": "full_name",
  "expression": "`${row.first_name} ${row.last_name}`"
}

exec

Run a shell command and capture stdout. Template values are shell-escaped.

{
  "id": "whois_lookup",
  "type": "exec",
  "target": "registrar",
  "command": "whois {{row.domain}} | grep 'Registrar:' | head -1 | cut -d: -f2",
  "timeout": 10000,
  "onError": { "default": "skip" }
}

lookup

Pull data from another tab by matching a column value. Source tab data is cached per pipeline run for performance.

{
  "id": "get_company_info",
  "type": "lookup",
  "target": "company_name",
  "sourceTab": "Companies",
  "matchColumn": "Domain",
  "matchValue": "{{row.domain}}",
  "matchOperator": "equals",
  "returnColumn": "Name",
  "matchMode": "first"
}

Use "matchMode": "all" to return all matches as a JSON array. Use "matchOperator": "contains" for substring matching.

write

Push data to another tab with column mapping. Supports append, upsert, and array expansion.

{
  "id": "export_contacts",
  "type": "write",
  "target": "export_status",
  "destTab": "Contacts",
  "columns": {
    "Company": "{{row.company}}",
    "Name": "{{item.name}}",
    "Title": "{{item.title}}",
    "Email": "{{item.email}}"
  },
  "expand": "{{row.contacts_json}}",
  "expandPath": "$.contacts"
}
  • append (default) — always create new rows
  • upsert — update existing rows if upsertMatch column matches, otherwise append
  • expand + expandPath — expand a JSON array into multiple rows; use {{item.field}} in column templates to access element data

script

Run a named script and capture its output. Scripts are defined in the scripts config section and referenced by name.

{
  "id": "ai_summary",
  "type": "script",
  "target": "summary",
  "script": "claude_json",
  "args": ["{\"prompt\":\"Summarize {{row.company}}\"}"],
  "extract": "$.content[0].text",
  "timeout": 60000
}

Error handling

Actions can define onError to map HTTP status codes (or exit codes for exec) to behaviors:

| Action | Effect | |--------|--------| | "skip" | Skip this action for the current row | | "stop_provider" | Stop the current waterfall provider, try the next | | {"write": "value"} | Write a fallback value to the target cell |

Google Sheets Sidebar

Rowbound includes an Apps Script sidebar that lets you configure actions directly in Google Sheets — no CLI needed. Click a column, edit the action config in a sidebar UI, and save. The sidebar reads and writes the same Developer Metadata config as the CLI, so both stay in sync.

Setup

  1. Open your Google Sheet → Extensions → Apps Script
  2. Replace the contents of Code.gs with [apps-script/Code.gs](apps-script/Code.gs)
  3. Click + next to Files → HTML → name it Sidebar → paste [apps-script/Sidebar.html](apps-script/Sidebar.html)
  4. In the left panel, click + next to Services → select Google Sheets API → set Ident

Source & license

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

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.