Skip to main content
Glama
geryslov

LinkedIn Ads MCP

by geryslov

LinkedIn Ads for Claude — product site

Thin front door for the standalone LinkedIn Ads MCP: LinkedIn SSO, one setup screen, and an admin panel. No ads dashboard — that's the separate linkedin-ads-buddy app.

npm install
cp .env.example .env    # fill in the anon key + your product MCP URL
npm run dev             # http://localhost:8080

How it fits together

Shares the Supabase project (bxoxefmenvlxiubynuay) with linkedin-ads-buddy, but not its tables. Two independent systems:

Legacy dashboard

This product

Table

mcp_api_keys

mcp_keys

MCP entrypoint

mcp-server/src/server.ts

mcp-server/src/server-product.ts

Railway service

linkedin-ads-buddy-production

separate service

Key → token

anon PostgREST select

resolve_mcp_key() RPC

Nothing here touches the legacy path. That's deliberate — the old dashboard is a working daily driver and must keep working unchanged.

Related MCP server: linkedin-mcp

Routes

Route

Auth

Purpose

/

Landing + Sign in with LinkedIn

/auth/callback

Supabase OIDC return; captures provider_token, calls link_mcp_key

/linkedin/callback

Server-side connect return; calls connect_linkedin

/setup

session

Connection status, MCP key, Claude setup instructions

/admin

admin

Suspend/activate users, revoke keys, toggle read-only

/privacy, /terms

Placeholders — replace before launch

Setup checklist

Do these in order. Steps 1–2 are the go/no-go.

1. LinkedIn developer app (reuse the existing one — a new app needs fresh Advertising API approval, which takes weeks and can be refused)

  • Products: "Sign In with LinkedIn using OpenID Connect" and "Advertising API". If OIDC is missing, openid is ungrantable and the whole combined-scope request fails. This is the single go/no-go.

  • Auth → Authorized redirect URLs, add (keep the existing ones):

    • https://bxoxefmenvlxiubynuay.supabase.co/auth/v1/callback — Supabase SSO

    • https://<this-site>/linkedin/callback — server-side connect flow

    • http://localhost:8080/linkedin/callback — local dev

2. Check for refresh tokens. Run one live connect and look at what LinkedIn returns. connect_linkedin reports hasRefreshToken. Access tokens last ~60 days; refresh tokens are partner-gated. This single fact decides whether reconnects can ever be made silent, so find out early.

3. Supabase Auth provider — NOT needed.

Sign-in deliberately does not use signInWithOAuth({provider: "linkedin_oidc"}). That would require enabling the LinkedIn provider in the Supabase dashboard, using credentials this project already holds as edge secrets. Instead the flow is:

/  → get_auth_url (scopeSet: "oidc")   → LinkedIn consent
   → /auth/callback?code=…
   → linkedin_signin  (edge, service role)
        exchange code → /v2/userinfo → find-or-create user
        → store ads token in mcp_keys
        → generateLink → one-time hashed_token
   → supabase.auth.verifyOtp({token_hash}) → session

The LinkedIn access token never reaches the browser, and linkedin_oidc can stay disabled. If you ever do enable it, nothing here needs to change.

4. Migrations — run manually in the SQL Editor, in order:

  1. 20260805120000_mcp_product_keys.sql

  2. 20260805120100_resolve_mcp_key_rpc.sql

5. Mint the resolver JWT. Sign {"role":"mcp_server"} with the project JWT secret. Set it as SUPABASE_MCP_JWT on the product Railway service.

6. Deploy the edge function (CI for this is broken — do it by hand):

SUPABASE_ACCESS_TOKEN=<token> npx supabase functions deploy linkedin-api --project-ref bxoxefmenvlxiubynuay

7. New Railway service from the same mcp-server/ folder: startCommand = npm run start:product, plus SUPABASE_MCP_JWT, SETUP_URL, OAUTH_CLIENT_ID, SUPABASE_ANON_KEY.

8. Grant yourself admin. ⚠️ Your existing admin row belongs to the email/password user. Signing in with LinkedIn creates a different auth.users row, and the signup trigger only ever grants 'user' — so you will not have /admin until you run this:

insert into user_roles (user_id, role)
select id, 'admin' from auth.users where email = 'you@example.com'
on conflict do nothing;

Known gaps

  • Legal pages are placeholders.

  • No email nudge before a token expires — the /setup banner is the only warning.

  • Access is self-serve (access_status defaults to 'active'). To require approval, change the column default to 'pending'; the "waiting for approval" screen already exists.

  • No Stripe. profiles.plan / stripe_customer_id are there for it, unused.

Related MCP Connectors

Related MCP Servers

  • A
    license
    A
    quality
    B
    maintenance
    Enables AI assistants to access and interact with LinkedIn data—profiles, messaging, jobs, companies, and more—via MCP, with remote or local deployment.
    2
    22
    52 npm
    9
    MIT
  • A
    license
    A
    quality
    B
    maintenance
    Enables LinkedIn API integration for managing profiles, posts, feed, connections, and sending messages through MCP-compatible clients.
    10
    8 npm
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    An MCP server that connects AI assistants to LinkedIn's Ad Library API for searching advertisers' sponsored content, job postings, and influencer partnerships.
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables management and analysis of LinkedIn ad campaigns, including listing accounts, campaigns, creatives, and retrieving analytics, through natural language queries.
    1 npm
    MIT