Skip to main content
Glama
technicallypete

outreacher

Outreacher

An AI-powered lead qualification system with an MCP server for Claude Desktop and a Next.js SaaS frontend — built within 3 days.

All dev tooling runs via Docker. You do not need Go, Node, Bun, goose, or sqlc installed locally.

Developers: see CLAUDE.md for commands, conventions, and test instructions.

TIP

This project demonstrates and acts as a foundation boilerplate for 2-mode operation of an AI MCP as:

  1. Single-tenant Claude Desktop Go binary (individual use)

  2. Multi-tenant Next.js SaaS + chat LLM + MCP Go server (hosted environment)

The project architecture includes:

  • AI SDK + assistant-ui chat

  • Example MCP development

  • Shared backend code for both binary and server operation

  • Claude Code (and other development agent) usage and best practices

  • Docker best practices for development and production

  • Database security role/schema best practices (adopted early for scale)

📸 Demo & Walkthrough

See the full demo with screenshots and flows here:
👉 View Demo Docs


Related MCP server: atiendo24-mcp-server

Architecture

Claude Desktop
  └── bin/outreacher-mcp-0.1.0-linux-amd64  (Go stdio binary, WSL)
        └── localhost:5432  (standalone postgres, volume: outreacher_standalone_data)

Browser / Next.js frontend
  └── app (Next.js, :3000)
        └── mcp (:3001, Go SSE server, air hot reload)
              └── postgres (internal only, volume: outreacher_postgres-data)

Two runtime modes — standalone binary for Claude Desktop, Docker Compose for development. Important: use separate named volumes for each mode to avoid data directory conflicts.


Tech Stack

  • Next.js 15 (App Router, Bun)

  • Go MCP server (mcp/) — stdio + SSE transports, pgx/v5, sqlc, goose migrations

  • PostgreSQL 16 — app schema, named Docker volume

  • Docker Compose — dev stack with air hot reload on the Go server


DB Users

User

Password

Role

admin

admin

Owns schema, runs DDL and migrations

app

app

Next.js runtime — member of app_crud + direct nextauth grants

mcp

mcp

MCP server runtime — member of app_crud

reporter

reporter

Read-only access — member of app_read

Group roles (no LOGIN) — DEFAULT PRIVILEGES auto-cover new tables/sequences/types:

  • app_crud — SELECT/INSERT/UPDATE/DELETE on app schema

  • app_read — SELECT on app schema

DATABASE_URL → mcp user (MCP server) or app user (Next.js). DATABASE_ADMIN_URL → admin user (goose only).


Schema (app)

Multi-tenancy

Table

Key fields

organizations

id, name, slug (unique), is_system

campaigns

id, organization_id, name, slug, is_default

users

id, email†, slug (unique), name, is_system

organization_memberships

organization_id, user_id, role (owner|admin|member)

campaign_memberships

campaign_id, user_id, role (admin|member|viewer)

Domain (scoped to campaign_id)

Table

Key fields

companies

id, campaign_id, name, domain, industry, linkedin_url

signals

id, description — global, no campaign scope

signal_keywords

signal_id, keyword — global

company_signals

company_id, signal_id

leads

id, campaign_id, name, email†, linkedin_url†, company_id, title, status, score, location, phone

notes

id, lead_id, content, created_at

contact_identifiers

campaign_id, type, value — dedup key, PK is (campaign_id, type, value)

† nullable. Leads are deduplicated by contact_identifiers, not by email.

Lead status flow: new → contacted → qualified → disqualified → converted


MCP Tools

Domain tools (all accept optional campaign_id; defaults to startup campaign)

Tool

Description

search_leads

Filter by name, email, status, company, campaign_id

get_lead

Full lead detail with company and notes

update_lead_status

Advance a lead through the status flow

create_followup_note

Append a note to a lead

import_csv

Import from CSV text (auto-detects Gojiberry, Revli formats)

search_companies

Search companies by name or domain

get_company

Full company detail with signals

Campaign management (stdio binary only)

Tool

Description

list_campaigns

List all campaigns for the current org

get_campaign

Get campaign details by id

create_campaign

Create a new campaign under the current org

rename_campaign

Rename a campaign's display name


Mode 1 — Claude Desktop (Go binary via WSL)

Build the binary:

npm run build:mcp
# outputs bin/outreacher-mcp-0.1.0-linux-amd64   (stdio, WSL)
#         bin/outreacher-mcp-0.1.0-darwin-arm64   (stdio, macOS Apple Silicon)
#         bin/outreacher-mcp-0.1.0-darwin-amd64   (stdio, macOS Intel)
#         bin/outreacher-mcp-0.1.0-windows-amd64.exe
#         bin/outreacher-server-0.1.0             (SSE server)

Start standalone postgres:

docker run -d --name outreacher-pg --restart always \
  -p 5432:5432 \
  -e POSTGRES_DB=outreacher \
  -e POSTGRES_USER=admin \
  -e POSTGRES_PASSWORD=admin \
  -v outreacher_standalone_data:/var/lib/postgresql/data \
  -v "$(pwd)/postgres/init:/docker-entrypoint-initdb.d" \
  postgres:16-alpine

Run migrations:

PG_IP=$(docker inspect outreacher-pg --format '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}')
docker run --rm \
  -v "$(pwd)/mcp:/src" -w /src \
  --add-host=postgres:$PG_IP \
  golang:1.24-alpine \
  sh -c 'go run github.com/pressly/goose/v3/cmd/goose@latest \
    -dir migrations postgres \
    "postgresql://admin:admin@postgres:5432/outreacher" up'

Claude Desktop config (Settings → Developer → Edit Config):

{
  "mcpServers": {
    "Outreacher": {
      "command": "wsl.exe",
      "args": [
        "~/Code/outreacher/bin/outreacher-mcp-0.1.0-linux-amd64",
        "--database-url", "postgresql://mcp:mcp@localhost:5432/outreacher",
        "--openai-api-key", "sk-proj-..."
      ]
    }
  }
}

Replace <user> with your WSL username. ANTHROPIC_API_KEY enables LLM-based CSV extraction for unknown formats; omit to use the built-in parsers only. Restart Claude Desktop after saving — the hammer icon confirms tools are connected.

The binary idempotently bootstraps system_default_org, system_default_user, and the Default campaign on first run.


Mode 2 — Docker Compose (dev stack)

Copy .env.example to .env and fill in API keys, then:

docker compose up --build -d

Service

Host port

Notes

app

3000

Next.js dev server

mcp

3001

Go SSE server, air hot reload

postgres

—

Internal only

First-time migrations:

docker run --rm --network outreacher_default \
  -v "$(pwd)/mcp:/src" -w /src \
  golang:1.24-alpine \
  sh -c 'go run github.com/pressly/goose/v3/cmd/goose@latest \
    -dir migrations postgres \
    "postgresql://admin:admin@outreacher-postgres-1:5432/outreacher" up'

If the mcp container exited while waiting for migrations, restart it:

docker compose restart mcp

Imports

fixtures/imports/ is gitignored — it contains live personal data used for local development and debugging only. Supported formats: Gojiberry, Revli startup contacts, Revli investor contacts, Revli startup companies, Revli investor companies.

© 2026 Vitruvian Technology, Corp. All rights reserved.

This code is provided under a noncommercial license for evaluation and research purposes only.

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    A
    maintenance
    MCP server that enables AI agents to discover and qualify B2B leads from Leadbay's knowledge base, with tools for lead research, enrichment, and outreach logging.
    MIT
  • A
    license
    Not graded
    quality
    D
    maintenance
    MCP server that connects Claude Desktop to Atiendo24 platform, enabling management of appointments, customers, conversations, leads, analytics, and WhatsApp messaging through natural language.
    8 npm
    MIT
  • F
    license
    Not graded
    quality
    D
    maintenance
    A lightweight MCP server that connects Claude AI to Mautic marketing automation, enabling contact management, email sending, campaign operations, and analytics via natural language.
    1
    -