Skip to main content
Glama
SocialMCP

SocialMCP

by SciTechDude

SocialMCP

A local-first personal CRM for your professional network, delivered entirely as an MCP server.

No scraping. No cookies. No browser automation. No web UI. You drop the data export that LinkedIn already owes you into a folder, and any MCP client becomes the interface to a searchable CRM that remembers how you met everyone.

You:        Who did I meet at Seattle AI Week, and who haven't I followed up with?

Assistant:  7 people, 13-15 Oct. Four are still cold:
            • Marcus Webb — Staff ML Engineer, Cascade Robotics
              "Great meeting you at Seattle AI Week today!"
            • Hana Suzuki — Director of Product, Elliott Bay Health
            ...
            Also: Priya Raghavan moved from Northwind AI to Aurora Labs as VP GTM
            since your last export.

(Illustrative — the names above come from the synthetic fixtures in tests/fixtures/, not from anyone's real network.)

Why this exists

Networking at a fast-paced event creates a memory gap: three days later you have forty new connections and no idea which conversation went with which name. LinkedIn used to let you tag and annotate connections privately — that is now behind Sales Navigator.

The workaround most people already use by hand:

  1. A QR code on your lock screen so people can scan instead of typing.

  2. A clipboard template in the connection invite — "Great meeting you at Seattle AI Week today!"

That second habit is the key. Your official LinkedIn data export contains Invitations.csv, and the Message column preserves exactly what you pasted, with a timestamp. That is a dated, venue-stamped note about how you met every single person — already written, just never surfaced. SocialMCP extracts it, clusters the connections into the events they came from, and makes the whole thing queryable in natural language.

Related MCP server: LinkedIn MCP Server

Principles

MCP-first, headless

No SPA, no REST API, no listening socket. The chat window is the UI.

Zero scraping, ToS-compliant

Only user-owned GDPR/CCPA export files. Never a session cookie, headless browser, or live API call against a social network.

Local-first privacy

Contacts and DM history live in one SQLite file on your machine. Embeddings default to local Ollama. Cloud LLM keys are strictly opt-in.

Contributor-extensible

New sources are one module implementing BaseAdapter. No schema changes.

Install

Requires Python 3.11+.

git clone https://github.com/SciTechDude/socialMCP.git
cd socialMCP
pip install -e ".[dev]"

Optional, for semantic search (recommended):

# sqlite-vec comes with the install; you also need a local embedding model
ollama pull nomic-embed-text

Without Ollama, SocialMCP runs fine — search falls back to SQLite FTS5 keyword matching plus exact filters.

Get your data

LinkedIn → Settings & Privacy → Data Privacy → Get a copy of your data → request the larger archive (it is the one with Invitations.csv and messages.csv). It arrives by email in minutes to 24 hours.

Drop the .zip — unopened, as downloaded — into:

drop-archive/linkedin/

That is the whole ingestion UX. The background harness notices the file, waits for the copy to finish, parses it, and files it away under ingested/linkedin/.

Connect it to any MCP client

SocialMCP is a standard stdio MCP server with no client-specific code, so anything that speaks MCP can drive it. The whole integration is one command:

python /absolute/path/to/socialMCP/run.py

Most clients configure that as JSON in the now-conventional mcpServers shape:

{
  "mcpServers": {
    "socialmcp": {
      "command": "python",
      "args": ["/absolute/path/to/socialMCP/run.py"]
    }
  }
}

Where that JSON goes, by client:

Client

Location

Claude Desktop

~/Library/Application Support/Claude/claude_desktop_config.json (macOS), %APPDATA%\Claude\claude_desktop_config.json (Windows)

Claude Code

claude mcp add socialmcp -- python /abs/path/run.py

Cursor

.cursor/mcp.json in the project, or ~/.cursor/mcp.json globally

VS Code (Copilot)

.vscode/mcp.json, under a servers key instead of mcpServers

Zed

context_servers in settings.json

Continue

mcpServers in ~/.continue/config.yaml

Windsurf, Cline, LibreChat, Goose, …

their own MCP config file, same command

Your own code

mcp SDK (Python/TS), Pydantic-AI, LangChain MCP adapters, …

Check your client's docs for the exact key name — the command and args never change. Restart the client afterwards, then ask it to call get_network_briefing.

If a client won't start the server, run python run.py stats first: that exercises the same database and config without the protocol in the way.

Verifying it from your own script

No AI client needed to confirm the server works — this is the reference client from the mcp SDK:

import asyncio
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client

async def main():
    params = StdioServerParameters(command="python", args=["/abs/path/to/socialMCP/run.py"])
    async with stdio_client(params) as (read, write):
        async with ClientSession(read, write) as session:
            await session.initialize()
            print([t.name for t in (await session.list_tools()).tools])
            print((await session.call_tool("network_stats", {})).content[0].text)

asyncio.run(main())

tests/test_mcp_protocol.py is this same flow as a test, so client-neutral compatibility is enforced on every CI run rather than assumed.

What any MCP client can do

Tool

Purpose

search_network

Hybrid semantic + keyword search with structured filters (company, title, status, event, date range).

get_event_cluster

Everyone connected during one event window, with their invite notes and who is still un-followed-up.

get_contact

One person's full record: invite note, DMs, your notes, job changes.

log_contact_note

Append a timestamped private note.

list_pending_invitations

Invites you sent that were never accepted — your follow-up list.

rename_event_cluster

Turn "13-15 Oct 2025 burst (7 connections)" into "Seattle AI Week 2025".

trigger_archive_ingest

Ingest on demand instead of waiting for the watcher.

list_event_clusters, network_stats, reindex_embeddings, get_network_briefing

Housekeeping and session priming.

Resources: socialmcp://briefing, socialmcp://stats, socialmcp://clusters, socialmcp://adapters.

Also usable without an AI client

python run.py ingest                                   # process the drop zone now
python run.py search "enterprise AI sales" --limit 5
python run.py search "" --event seattle-ai-week-2025
python run.py clusters
python run.py briefing
python run.py watch                                    # harness only, foreground
python run.py                                          # MCP server over stdio

How it works

drop-archive/linkedin/export.zip
        │
        ▼
  core/harness.py ──── watchdog + debounce (never reads a half-copied zip)
        │
        ▼
  core/ingest.py ───── safe extract → sha256 dedupe → adapter → merge → upsert
        │                                                  │
        │                                     adapters/linkedin/adapter.py
        │                                     (CSV quirks live here, only here)
        ▼
  SQLite: contacts, interactions, contact_history, event_clusters,
          contact_identities, harness_events, contacts_fts, vec_contacts
        │
        ├── core/clustering.py  deterministic event detection
        ├── core/search.py      FTS5 + sqlite-vec fused with RRF
        └── mcp_server/server.py  the only thing the model talks to

Repository layout

socialMCP/
├── run.py                        # entry point: MCP server, or CLI subcommands
├── drop-archive/linkedin/        # ← drop LinkedIn .zip exports here
├── ingested/                     # processed archives, structure mirrored
├── socialmcp/
│   ├── config.py                 # TOML + env config, drop-zone routing
│   ├── cli.py
│   ├── core/
│   │   ├── schema.sql            # the full data model, commented
│   │   ├── db.py                 # connection, migrations, sqlite-vec loading
│   │   ├── models.py             # the records every adapter emits
│   │   ├── normalize.py          # URL/date/name canonicalization
│   │   ├── ingest.py             # extract → merge → upsert → history
│   │   ├── clustering.py         # temporal event detection
│   │   ├── search.py             # hybrid search + RRF fusion
│   │   ├── embeddings.py         # Ollama / OpenAI / none
│   │   └── harness.py            # watcher, activity log, briefing
│   ├── adapters/
│   │   ├── base.py               # BaseAdapter + CSV header-detection helpers
│   │   └── linkedin/adapter.py   # v1 adapter
│   └── mcp_server/server.py      # FastMCP tools and resources
└── tests/                        # 63 tests, anonymized fixtures, no network

The LinkedIn quirks it handles

These are the things that break naive parsers, all covered by tests:

  • Multi-line preamble. Connections.csv opens with a human-readable "Notes:" disclaimer. The line count has changed between export vintages, so the parser scans for the first row containing the real headers instead of skipping a hard-coded three lines.

  • UTF-8 BOM glued to the first header cell.

  • Header drift. Direction is absent in older Invitations.csv; profile-URL columns appear in camelCase when they appear at all. Headers are normalized (SENDER PROFILE URL → sender_profile_url) rather than matched literally.

  • Invitations have no profile URL. The same person therefore arrives as in/jane-doe from Connections and name/jane-doe from Invitations. These are merged — otherwise your clipboard note ends up orphaned on a phantom "pending" duplicate. The merge refuses to fire when a name is ambiguous.

  • Opaque member URNs. messages.csv uses /in/ACoAAB… URLs that never match the vanity slugs in Connections.csv, so DMs are matched through an alias table (URL → email → unambiguous name).

  • Dates everywhere. "13 Oct 2025", "10/13/25, 6:42 PM", "2025-10-20 17:04:11 UTC", bare epochs. One bad date warns and keeps going.

  • Monthly re-drops. Archives are deduped by SHA-256; overlapping rows are idempotent; a changed company or title is written to contact_history so you can see who moved.

  • Untrusted zips. Extraction rejects path traversal (../) and absurd compression ratios. Exports are downloaded files, not trusted input.

Event clustering

A greedy pass over connection dates: extend a burst while each gap is ≤ max_gap_days (2) and the total span stays ≤ max_span_days (5); emit it if it holds ≥ min_contacts (5) people. Fifteen connections over a conference weekend becomes one cluster; one connection a week never does. Tunable in socialmcp.toml. Clusters you rename are never renamed back.

Keyword (FTS5, BM25-weighted toward names and invite notes) and semantic (sqlite-vec cosine) rankings are fused with Reciprocal Rank Fusion, so "GTM background" works without keyword overlap and "Stripe" still ranks the literal match first. Structured filters are plain SQL predicates applied before fusion — exact, never approximate.

Privacy

Everything is a local file. The server speaks stdio to its parent process and opens no network socket. The only outbound requests SocialMCP can make are to your configured embedding provider — 127.0.0.1:11434 by default — and nothing at all if you set provider = "none". .gitignore blocks exports, the database, and local config so you cannot accidentally commit your network.

Development

pip install -e ".[dev]"
pytest              # 63 tests, no network access required
ruff check .

Fixtures in tests/fixtures/linkedin/ are a hand-written, fully synthetic export that reproduces every quirk above. Use them instead of your real data.

Roadmap

  • Phase 1 & 2 (done): schema, hardened ingest pipeline, LinkedIn adapter, event clustering, harness, MCP tools and resources.

  • Phase 3: more adapters (Instagram/Facebook JSON, X archives, vCard/CSV), richer follow-up prompting, optional recurring reminders.

Adapters are the easiest place to contribute — one module, no schema change. See CONTRIBUTING.md.

License

Apache License 2.0 — see LICENSE.

Apache-2.0 rather than MIT for the explicit patent grant and because its contribution clause means contributors don't need to sign a separate CLA.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      Not graded
      quality
      D
      maintenance
      Local-first MCP server for research on AI-assisted browsing of a user-owned professional-network account (e.g., LinkedIn), providing read-focused tools such as profile, company, search, and feed reads.
      MIT
    • A
      license
      A
      quality
      D
      maintenance
      Enables searching and scraping of LinkedIn profiles, companies, jobs, and posts using natural language through MCP-compatible AI clients.
      13
      MIT
    • A
      license
      Not graded
      quality
      C
      maintenance
      MCP server for AI-native LinkedIn prospecting. It enables lead research, audience building, conversation management, and controlled outreach actions such as messaging and publishing through an OAuth-protected remote endpoint.
      2
      MIT