Skip to content
The Massive - Business Search The Massive

Developer documentation

MCP server documentation

Everything an AI client, developer or operator needs to connect to The Massive - Business Search. Run paid Google Ads business searches, collect public contact details and deliver a CSV.

OAuth 2.1 Streamable HTTP JSON-RPC 2.0 Version 1.0.0 Paid searches

Endpoint

Streamable HTTP
https://themassive.com.au/mcp/search

No API key. The client discovers OAuth 2.1 metadata from the endpoint and opens the browser sign-in.

Connect a client
On this page

Getting started

Overview #

The Massive - Business Search is a remote Model Context Protocol (MCP) server for paid, on-demand business research. An AI client asks for one keyword, one country and one or more locations. The server runs a live Google Ads search, visits the businesses it finds to collect public contact details, and emails a CSV when the list is ready and paid for.

Live Google results

Every request runs a fresh Google Ads search, including paid and organic results.

Contact enrichment

Public emails, phone numbers and addresses are pulled from each business website.

Up to 100 locations

Run one keyword across many cities, states or regions in a single request.

CSV by email

A unique CSV download link is emailed after the search and payment are complete.

OAuth 2.1

Browser sign-in through Google, GitHub, Apple, Facebook, Microsoft or email.

Saved card or Checkout

Use an automatic saved-card charge or a Stripe Checkout payment link.

Server identity

Name
The Massive - Business Search
Version
1.0.0
Transport
Streamable HTTP
Authentication
OAuth 2.1 with dynamic client registration and PKCE
Scope
mcp:use
Rate limit
60 requests per minute per IP

What the server does not do

  • It does not return the CSV inside the MCP response. The download link is emailed to the address supplied with the search.
  • It does not expose a status-check or cancel tool. Keep the request ID and watch the email updates.
  • It does not run free searches or accept a price argument. Creating a search starts a paid search that cannot be cancelled.
  • It does not accept passwords in chat. OAuth sign-in happens on the browser login page.
  • It does not allow more than one unpaid search per email address.

Getting started

Endpoint and transport #

The server exposes one remote MCP endpoint. Use the Streamable HTTP transport and send JSON-RPC 2.0 requests to the same URL. Do not append a trailing slash.

Endpoint Streamable HTTP
https://themassive.com.au/mcp/search

Method

POST

Content type

application/json

Authentication

Bearer <token>

POST https://themassive.com.au/mcp/search
Content-Type: application/json
Authorization: Bearer <access-token>
Accept: application/json, text/event-stream

The same endpoint serves every client. OAuth metadata is discovered from the unauthenticated response, so there is no separate connection URL and no API key to paste into a chat.

Unauthenticated discovery response #

A request without a valid access token returns HTTP 401. The response includes a WWW-Authenticate header and JSON fields that point the client at the protected-resource metadata and the browser login page.

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="mcp", resource_metadata="https://themassive.com.au/.well-known/oauth-protected-resource/mcp/search"
{
  "message": "Authentication required. Complete the OAuth 2.1 flow from your MCP client before asking the user for a keyword, country, locations, or email.",
  "login_url": "https://themassive.com.au/login",
  "resource_metadata": "https://themassive.com.au/.well-known/oauth-protected-resource/mcp/search"
}

Getting started

Protocol and JSON-RPC methods #

The server speaks JSON-RPC 2.0. Newer MCP clients use the discovery handshake (protocol 2026-07-28); older clients use initialize (protocol 2025-11-25 or 2025-06-18). In both cases the server returns its capabilities and the full server instructions, which the client should make available to the model.

Method Purpose
server/discover Returns supported versions, capabilities and instructions to clients using MCP 2026-07-28.
initialize Negotiates the protocol version and returns capabilities and instructions to older clients.
notifications/initialized Notification sent after initialization. It has no response.
ping Health check and keepalive.
tools/list Lists the tools and their JSON schemas.
tools/call Invokes a tool by name with an arguments object.
prompts/list Lists the available prompt templates.
prompts/get Returns the assistant instructions and kickoff message for a prompt.

Initialize example #

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "initialize",
  "params": {
    "protocolVersion": "2025-06-18",
    "capabilities": {},
    "clientInfo": {
      "name": "my-client",
      "version": "1.0.0"
    }
  }
}
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "protocolVersion": "2025-06-18",
    "capabilities": {
      "tools": {},
      "resources": {},
      "prompts": {}
    },
    "serverInfo": {
      "name": "The Massive - Business Search",
      "version": "1.0.0"
    },
    "instructions": "Use this server to run paid Google Ads business searches..."
  }
}

The instructions are part of the protocol response, not just documentation. They tell the agent to authenticate first, never ask for a password in chat, show a full confirmation summary before creating a search and wait for explicit confirmation.

After initialization, send the negotiated protocol version in the MCP-Protocol-Version header on subsequent Streamable HTTP requests.

Getting started

Authentication with OAuth 2.1 #

The MCP endpoint is protected by OAuth 2.1. There is no API key. The client discovers the protected resource, registers itself dynamically, opens the browser login page and stores the resulting access token.

Never enter a password into an AI chat

Passwords are only entered on the browser login page. An agent should never ask for one, and a user should never paste one into a conversation.

OAuth endpoint URL
Protected resource metadata https://themassive.com.au/.well-known/oauth-protected-resource/mcp/search
Authorization server metadata https://themassive.com.au/.well-known/oauth-authorization-server
Dynamic client registration https://themassive.com.au/oauth/register
Authorization https://themassive.com.au/oauth/authorize
Token https://themassive.com.au/oauth/token

How the browser flow works #

  1. The client calls the endpoint without a token and receives 401 Unauthorized.
  2. The client reads the WWW-Authenticate header and loads the protected-resource metadata.
  3. The client discovers the authorization server and registers itself with dynamic client registration.
  4. The client generates a PKCE code verifier and opens the authorization URL in a browser.
  5. The user signs in with an existing email and password, or chooses Google, GitHub, Apple, Facebook or Microsoft.
  6. The user approves the mcp:use scope and the client exchanges the authorization code for an access token.
  7. The client calls get-account to confirm which account is connected before asking for any search details.

A new account is created automatically on the first social sign-in. If the provider returns a verified email that already belongs to an existing account, the social account is linked to it automatically.

Access tokens can expire or be revoked. If a tool reports that authentication is required, reconnect the server from the client MCP settings. Do not ask for a password and do not ask the user to create a second account.

Getting started

Rate limits #

Limit Value
MCP endpoint requests 60 per minute per IP address
Response header X-RateLimit-Limit: 60
When exceeded HTTP 429 Too Many Requests
Unpaid searches per email 1 at a time

The endpoint limiter is applied per IP address. Search creation also has a separate per-email limit: a new request is rejected while a previous request for the same email address is still unpaid or in progress.

Tools

Tools overview #

The server exposes three tools. All of them require a valid OAuth access token, even though get-account is read-only. Tool results are text content; failures are returned as tool results with isError set to true rather than as a transport failure.

Tool Purpose Read-only Destructive Idempotent Open world
get-account Confirm the connected account, linked sign-in methods and payment method. Yes No Yes No
create-search-request Create a paid business search and start processing it. No Yes No Yes
renew-payment-link Replace an expired Stripe payment link for an existing request. No Yes No Yes

The annotations are safety hints for the client. Read-only means the tool does not change server state. Destructive means it creates a paid or externally visible side effect. Idempotent means repeating the same call does not add another effect. Open world means the tool works with external systems such as Google, SerpApi and Stripe.

Listing and calling tools #

Call tools/call with the tool name and an arguments object. The arguments are validated against the JSON schema returned by tools/list.

{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tools/call",
  "params": {
    "name": "get-account",
    "arguments": {}
  }
}

Every tool description starts with the authentication requirement and tells the agent to call get-account before collecting search details. Clients that show tool descriptions to the model should surface this text.

Tools

get-account #

Call this tool first, before asking for a keyword, country, locations or email. It confirms the account that is connected to the OAuth token and shows whether automatic payments are available.

Arguments

None

Annotations

Read-only, idempotent

Authentication

Required

Response #

A successful call returns text similar to this:

Connected account

Name: Jane Doe
Email: [email protected]
Social sign-in methods: Google and GitHub
Payment method: Visa •••• 4242 (automatic payment enabled)

To use a different sign-in method, sign out and choose Google, GitHub, Apple, Facebook, or Microsoft on the login page.

If there is no saved card, the payment line says "No saved card — payment links will be emailed". If no social provider is linked, the sign-in line says "None linked".

Errors #

If the token is missing or expired, the tool returns an error that starts with "No account is connected". The agent must stop, ask the user to complete the OAuth sign-in in the MCP client, and avoid asking for any search details until the call succeeds.

Use get-account again after reconnecting, after switching accounts, and before a paid action if the connection may have expired.

Tools

create-search-request #

Creates a paid business search. This tool is destructive and not idempotent: each successful call creates a new request and starts background work. A request cannot be cancelled after it is created.

Confirmation is mandatory

Before calling this tool, the agent must show a full summary of the keyword, country, locations and email, and wait for the user to explicitly confirm. Do not call it while any detail is still uncertain.

Arguments #

Argument Type Rules Description
keyword string Required, 1–255 characters One search keyword, for example "plumber" or "emergency electrician". It is trimmed and lowercased.
country string Required, 1–255 characters Full country name or ISO 3166-1 alpha-2 code, for example "Australia" or "AU". The country is matched against the supported country list.
locations array of strings Required, 1–100 items, each 1–255 characters Specific places within the country. Every location is verified with SerpApi before the request is created.
email string Required, valid email, max 255 characters Receives the Stripe payment link, the retry link and the final CSV download link. Always read it back to the user before confirming.

What happens when it is called #

  1. The tool checks the OAuth token. Without a connected user it returns an authentication error and does not collect or store anything.
  2. The arguments are validated. Missing fields, an invalid email, an empty locations array or a value that is too long fail with a validation error.
  3. The country is resolved by full name or ISO alpha-2 code. An unknown country returns a clear error instead of guessing.
  4. Each location is checked through SerpApi. If any location cannot be verified, no request is created and the tool lists every invalid location.
  5. The keyword and email are normalized: the keyword is trimmed and lowercased, and the email is trimmed and lowercased.
  6. A short-lived cache lock prevents two simultaneous submissions for the same email address.
  7. The server rejects the request if the email already has a search in pending, processing, scraping, payment_pending or payment_expired. Only one unpaid search is allowed per email address.
  8. A new request is stored as pending and the background processing job is dispatched.
  9. The tool returns the request ID, a summary of the search, and the payment path that will be used.

Successful responses #

Without a saved card, the response explains that a payment link will be emailed when the search finishes:

Business search request created successfully! ID: 42

We will search Google Ads for "plumber" across 3 location(s) in Australia.

The search returns business details including website address, email address, phone number, and physical address where available.

Once the search and contact extraction are complete, you will receive a payment link at [email protected].
After payment, a CSV download link with the business details will be sent to the same address.

With a saved card, the response explains that the card will be charged automatically:

Business search request created successfully! ID: 42

We will search Google Ads for "plumber" across 3 location(s) in Australia.

Your saved card (Visa •••• 4242) will be charged automatically when the search completes. No payment email is needed; the CSV download link will be emailed to [email protected] as soon as the payment succeeds.

Errors #

Error Cause What to do
Authentication required No connected user, or the token expired. Reconnect OAuth and call get-account.
Validation failed A required field is missing or invalid. Fix the argument and call again after confirming.
Country "..." was not recognised The country is not in the supported list. Use a full name such as "United States" or an ISO code such as "US".
The following location(s) could not be verified by SerpApi A location is too broad, misspelled or not recognised. Use a more specific place such as "Sydney, New South Wales, Australia".
Another business search request for this email is being created right now Two submissions raced for the same email. Wait a moment and try once more.
You already have a business search ... awaiting payment A previous request is in payment_pending. Pay using the emailed Stripe link before creating another search.
... has an expired Stripe payment link A previous request is in payment_expired. Call renew-payment-link with that request ID or use the retry link in the email.
... is in progress A previous request is still pending, processing or scraping. Wait for the payment link, then pay, before creating another search.

JSON schema #

Show the input schema
{
  "type": "object",
  "properties": {
    "locations": {
      "type": "array",
      "description": "List of locations to search in.",
      "items": {
        "type": "string",
        "minLength": 1,
        "maxLength": 255
      },
      "minItems": 1,
      "maxItems": 100
    },
    "keyword": {
      "type": "string",
      "description": "The single search keyword.",
      "minLength": 1,
      "maxLength": 255
    },
    "country": {
      "type": "string",
      "description": "The country name to scope the search to.",
      "minLength": 1,
      "maxLength": 255
    },
    "email": {
      "type": "string",
      "description": "The address that receives the payment and download links.",
      "minLength": 1,
      "maxLength": 255
    }
  },
  "required": ["locations", "keyword", "country", "email"]
}

Example call #

{
  "jsonrpc": "2.0",
  "id": 3,
  "method": "tools/call",
  "params": {
    "name": "create-search-request",
    "arguments": {
      "locations": [
        "Sydney, New South Wales, Australia",
        "Melbourne, Victoria, Australia",
        "Brisbane, Queensland, Australia"
      ],
      "keyword": "plumber",
      "country": "Australia",
      "email": "[email protected]"
    }
  }
}

Prompt templates

Prompts overview #

Prompts are optional guided templates. They do not create searches or charge cards. They return assistant instructions plus a kickoff user message that the client can insert into the conversation. Tools remain the only way to perform actions.

Prompt Arguments Purpose
get-started Optional: keyword, country, locations, email Guided search intake, summary and confirmation before create-search-request is called.
connect-account None Connection details, client configuration and OAuth steps for another AI client.

Both prompts require authentication. Calling a prompt without a connected account returns an error and tells the user to complete the OAuth sign-in first.

Listing and getting prompts #

{
  "jsonrpc": "2.0",
  "id": 5,
  "method": "prompts/get",
  "params": {
    "name": "get-started",
    "arguments": {
      "keyword": "plumber",
      "country": "Australia",
      "locations": "Sydney, NSW; Melbourne, VIC",
      "email": "[email protected]"
    }
  }
}

Prompt templates

get-started #

A guided intake prompt. It starts by checking the account, asks only for the missing search details, shows a full confirmation summary and tells the assistant not to call create-search-request until the user explicitly confirms.

Argument Type Rules Description
keyword string Optional, max 255 The single search keyword. It will be asked for if omitted.
country string Optional, max 255 The country to scope the search to. It will be asked for if omitted.
locations string Optional, max 4000 A comma separated list of locations, for example "Sydney, NSW; Melbourne, VIC". The prompt converts this into the locations array for the tool.
email string Optional, valid email, max 255 The address that receives the payment and CSV links.

Mandatory confirmation summary #

The prompt instructs the assistant to present exactly this structure before creating a search:

Please confirm your business search details:

- Keyword: plumber
- Country: Australia
- Locations (3 total):
  1. Sydney, New South Wales, Australia
  2. Melbourne, Victoria, Australia
  3. Brisbane, Queensland, Australia
- Email: [email protected]

Type "yes" or "confirm" to submit, or let me know if anything needs to change.

A vague acknowledgement is not confirmation. If a detail changes, the assistant must show the full updated summary again and wait for a fresh confirmation. The prompt itself never creates the search.

How the conversation runs #

  1. Call get-account. If it fails, stop and ask for OAuth sign-in before collecting anything.
  2. Ask only for the missing details. Values already supplied as prompt arguments are echoed back and do not need to be asked again.
  3. When an email is supplied, read it back and ask the user to confirm it is correct.
  4. Show the confirmation summary with every location listed.
  5. Wait for explicit confirmation, then call create-search-request once.
  6. Explain what happens next: background search, payment email or automatic charge, then the CSV download email.

Prompt templates

connect-account #

A connection helper for users who have not signed in yet or who want to connect another AI client. It returns the endpoint, transport, OAuth details, client configuration examples and the rules the assistant must follow.

Arguments

None

Authentication

Required

Side effects

None

Use it before the first search when the user has not signed in, or when the user wants to add the same account to ChatGPT, Claude, Cursor, VS Code or another client. It returns two messages: assistant instructions and a kickoff user message asking for the exact configuration for that client.

{
  "jsonrpc": "2.0",
  "id": 6,
  "method": "prompts/get",
  "params": {
    "name": "connect-account",
    "arguments": {}
  }
}

How searches work

Search lifecycle #

A search runs in the background. The MCP tool returns quickly with a request ID; the work continues in queue jobs, and the user receives emails as the request moves through payment and delivery.

Status flow

pending → processing → scraping → payment_pending → paid

payment_pending → payment_expired → (renew-payment-link) → payment_pending → paid

  1. 1

    Request created (pending)

    create-search-request stores the keyword, country, locations and email, then dispatches the processing job. The response includes the request ID.

  2. 2

    Search setup (processing)

    The worker resolves the country and every location, creates one internal search per location and dispatches the Google Ads searches. The request moves to scraping.

  3. 3

    Live search and enrichment (scraping)

    Each internal search fetches Google Ads and organic results. The service then visits business websites to find public email addresses, phone numbers and addresses, with a fallback to /contact and /contact-us pages when needed.

  4. 4

    Finalize and price

    When every internal search has settled, the finalizer counts businesses with an email or phone, calculates the amount and either charges the saved card or creates a Stripe Checkout session. Without a saved card, the payment link is emailed and the request becomes payment_pending.

  5. 5

    Payment and delivery (paid)

    After a successful payment, a unique download token is generated and the CSV download link is emailed. The request becomes paid and is treated as downloadable.

Timing depends on the number of locations, the external search provider and the queue workers. The finalizer re-queues itself if searches are still running, so a request can stay in scraping for a while. The MCP server does not expose a status tool; the request ID and the emails are the source of truth.

How searches work

Search rules #

Rule Value
Keywords per request1. Each extra keyword needs its own search.
Countries per request1. Run a separate request for each country.
Locations per request1–100
Keyword length1–255 characters
Location length1–255 characters each
Email lengthValid email, max 255 characters
Unpaid searches per email1 at a time
CancellationNot available after the request is created.

What the agent must do #

  • Call get-account before asking for any search details.
  • Collect one keyword, one country, between 1 and 100 specific locations, and an email address.
  • Read the email back and ask the user to confirm it is correct. A typo means no payment link and no CSV.
  • Show a full confirmation summary that lists every location.
  • Wait for an explicit "yes", "confirm" or equivalent. "OK" or silence is not confirmation.
  • Never invent locations, an email address or a confirmation.
  • Do not retry with a different email address to bypass the unpaid-search limit.

Location guidance #

Use a specific place, not a whole country or a vague region. SerpApi verifies each location before the request is created.

Good examples

  • Sydney, New South Wales, Australia
  • Melbourne, Victoria, Australia
  • Austin, Texas, United States
  • London, England, United Kingdom

Avoid

  • "Australia" as the only location
  • "all of California"
  • Misspelled suburbs or ambiguous names
  • Locations from more than one country in a single request

Search results are live and can vary between runs. The CSV is a snapshot of the search that was run for this request; it is not a permanent, shared database.

How searches work

Payments #

Payments are handled by Stripe. The MCP server never receives full card details. There are two paths: an automatic charge to a saved card, or a Stripe Checkout payment link sent by email.

Saved card (automatic payment) #

A user can save a card in Settings → Billing. When a search finishes, the server charges that card off-session for the calculated amount. If the charge succeeds, the request becomes paid and the CSV download link is emailed. No payment email is needed.

If the automatic charge fails, the server falls back to a Stripe Checkout link and emails it to the search address. The request becomes payment_pending. The tool response and get-account show whether automatic payment is enabled.

Stripe Checkout link #

Without a saved card, the server creates a Stripe Checkout session when the search finishes. Stripe collects the card and billing address and the payment link is emailed to the address supplied with the search. After payment, the download link is emailed to the same address.

  • The request is payment_pending while the link is active.
  • Stripe expires the session eventually. The webhook marks the request payment_expired.
  • renew-payment-link creates a fresh link, but only when Stripe confirms the old session expired.
  • A saved card may be charged immediately during renewal.
  • Only one unpaid search is allowed per email address. Pay or renew the previous request first.

Payment is not a subscription

Each search is charged once, based on the contacts found. There is no monthly fee and no seat count. See the pricing section and the Terms of Service for the applicable terms.

How searches work

Pricing #

The current configuration charges $0.50 per business contact with a $1.00 minimum per search. Only businesses with an email address or a phone number count toward the total.

Per contact

$0.50

Only contacts with an email or phone.

Minimum

$1.00

Per search, even when fewer contacts are found.

Subscription

$0

No monthly fee. Pay only for searches.

The amount is calculated after the search finishes, because the number of contacts is not known in advance. The MCP tool does not take a price argument and the agent should not promise a total before the results are ready. The Stripe Checkout page shows the exact amount before payment.

Example: 96 contacts at $0.50 each costs $48.00. A search that finds 4 contacts costs $2.00. A search that finds fewer than 2 contacts costs the $1.00 minimum.

Operators can change these values with MCP_SCRAPE_CENTS_PER_RESULT and MCP_SCRAPE_MINIMUM_CENTS. The values above reflect the current application configuration.

How searches work

Request statuses #

The request status is stored on the server and is used by emails, payments and downloads. The MCP server does not expose a tool to read it, so keep the request ID from create-search-request.

Status Meaning
pending Pending The request has been created and is waiting for a queue worker to start it.
processing Processing The server is resolving the country and locations and creating the internal searches.
scraping Scraping Live searches are running and business websites are being enriched with contact details.
payment_pending Payment pending Results and a Stripe payment link have been emailed. The request is waiting for payment.
payment_expired Payment expired The Stripe Checkout session expired. Use renew-payment-link or the retry link in the email.
paid Paid Payment succeeded, a download token was created and the CSV link was emailed.
completed Completed A terminal completed state. Like paid, it can still be used to download the CSV.
failed Failed The search could not finish, or Stripe is not configured. Contact support with the request ID.

pending, processing, scraping, payment_pending and payment_expired all count as unpaid or in progress for the per-email limit. A new search is rejected until the previous request is paid or failed.

How searches work

CSV and results #

Results are delivered as a CSV download link by email. There is no MCP tool that returns the CSV content directly, so the agent should tell the user to check the supplied email address after payment.

The file is named business-list-<keyword>-<request-id>.csv. The link is unique to the order and works while the request is paid or completed. A missing, unpaid or unknown token returns 404.

CSV columns #

Column Contents
NameBusiness name.
EmailPublic email address, where one was found.
PhonePublic phone number, where one was found.
AddressPublic physical address, where one was found.
WebsiteBusiness website.
CountryCountry used for the search.
LocationThe location that produced the row.

Deduplication and cleaning #

  • Rows are deduplicated by website domain, falling back to the email domain when there is no website.
  • When two rows share a domain, the row with more complete data is kept.
  • Domains blocked by the service are excluded from the CSV.
  • Rows with no domain are kept individually.
  • Fields that could not be found are left blank. A blank field is not an error.

Treat the download link as a secret

The link contains a unique token. Anyone with the link can download the file. Do not post it in public channels, and do not share it with people who should not see the contact list.

Client setup

Connection settings #

Use an MCP client that supports remote Streamable HTTP servers and OAuth 2.1 discovery. A client that only supports local stdio servers or the legacy SSE transport will not connect to this endpoint.

Setting Value
URLhttps://themassive.com.au/mcp/search
TransportStreamable HTTP
AuthenticationOAuth 2.1 discovery, handled by the client
Scopemcp:use
API keyNot used

In production the endpoint must be reachable over HTTPS. The client should open the browser login page automatically; if it does not, remove the connector and add it again.

Client setup

Claude Code #

Add the server from a terminal:

claude mcp add --transport http google-ads-search https://themassive.com.au/mcp/search

Then run /mcp in Claude Code, select google-ads-search and complete the browser sign-in. The client stores the OAuth token automatically.

If the connection expires, run /mcp again and authenticate. To start over, remove the server and add it again.

Client setup

Claude Desktop and Claude.ai #

  1. Open Settings → Connectors → Add custom connector.
  2. Name the connector The Massive and paste the endpoint URL.
  3. Save the connector and complete the Google, GitHub, Apple, Facebook, Microsoft or email sign-in.
  4. Start a conversation and ask the assistant to use get-account before running a search.

Custom connector availability depends on your Claude plan and app version. If the connector does not open a browser, remove it and add it again.

Client setup

Cursor #

Add this to .cursor/mcp.json in the project, or to ~/.cursor/mcp.json for a global server:

{
  "mcpServers": {
    "google-ads-search": {
      "url": "https://themassive.com.au/mcp/search"
    }
  }
}

Cursor prompts for authentication when it first connects. Choose Streamable HTTP and complete the browser flow. Do not use an SSE URL.

Client setup

VS Code and GitHub Copilot #

Add this to .vscode/mcp.json in the workspace:

{
  "servers": {
    "google-ads-search": {
      "type": "http",
      "url": "https://themassive.com.au/mcp/search"
    }
  }
}

Start the server from the MCP panel and complete the browser sign-in. In users or workspaces that share a VS Code configuration, each user still authenticates with their own account.

Client setup

Other MCP clients #

For any other remote MCP client, add a server with the Streamable HTTP transport and this URL. The client should discover OAuth automatically. If it asks for OAuth details manually, use the authorization server metadata URL from the authentication section.

  • ChatGPT: add a custom connector with the endpoint URL where custom connectors are supported, then complete the browser sign-in.
  • Generic client: transport Streamable HTTP, URL above, OAuth 2.1 discovery enabled, scope mcp:use.
  • Do not configure a static API key. The server does not accept one.
  • Do not use the legacy HTTP+SSE transport. This server uses Streamable HTTP.

If a client only supports local command-based servers, use a client-side MCP bridge that can proxy a remote Streamable HTTP server, or choose a client with native remote support.

Reference and operations

Error reference #

Connection-level failures use HTTP status codes. Tool-level failures are returned inside a successful JSON-RPC response as tool content with isError set to true. The agent should read the message and explain it instead of treating it as a transport crash.

HTTP errors #

Status Meaning What to do
401 No access token, or the token expired or was revoked. Reconnect the MCP server from the client settings and complete OAuth again.
403 The OAuth consent request failed, or the token lacks access. Restart the consent flow in the same browser context. Operators should check the OAuth session settings.
404 The endpoint URL is wrong, or a download token is unknown or unpaid. Use the exact endpoint with no trailing slash. Check the download link from the email.
429 The rate limit was exceeded. Wait and retry. Do not bypass the limit with another account.
500 An unexpected server error. Retry once. If it persists, contact support with the time and request ID.

Tool errors #

Message Tool Resolution
Authentication required All tools and prompts Complete OAuth, then call get-account.
No account is connected get-account Reconnect the MCP server and retry.
Validation failed create-search-request, renew-payment-link Correct the arguments and retry.
Country "..." was not recognised create-search-request Use a full country name or ISO code.
Location(s) could not be verified create-search-request Use a more specific place name.
Another request is being created right now create-search-request Wait briefly and retry once.
You already have a business search ... create-search-request Pay or renew the previous request. Do not switch email addresses to bypass the limit.
That business search request was not found for the connected account renew-payment-link Check the request ID and the connected account email.
... does not have an unpaid payment link renew-payment-link The request is already paid or failed. Check the email or contact support.
Stripe payments are not configured renew-payment-link Contact the operator; Stripe is not configured on the server.

Reference and operations

Troubleshooting #

The client keeps returning 401 #

The access token is missing, expired or revoked. Reconnect the MCP server from the client settings and complete OAuth again. Do not ask for a password in chat and do not use another account to bypass the limit. Check that the URL has no trailing slash.

OAuth consent returns 403 “The provided auth token for the request is different from the session auth token” #

Passport stores the pending authorization request in the Laravel session and compares the hidden auth_token from the consent screen with the session when the user approves. The check fails when the approval request does not see the same session that rendered the consent screen.

The application keeps a short-lived, token-keyed copy of the pending authorization request in the cache and restores it before Passport handles the approve or deny request. It still relies on a persistent, shared cache that is not the array store, APP_URL matching the public scheme and host exactly, SESSION_DOMAIN unset or set to the exact domain, and a shared session store when more than one app server is running. Close old consent tabs, click Connect again in the AI client and finish the flow in the tab that opens.

The browser flow completes but the tool says “No account is connected” #

Reconnect the MCP server so the client obtains a fresh access token, then call get-account again. If the client still reports no account, remove the connector and add it again.

A social sign-in fails or the login page has no social buttons #

A provider button only appears when its client ID and secret are configured. Apple also needs a client secret or a private key with the team and key IDs. After changing environment values, run php artisan config:clear and rebuild the frontend assets.

  • Google: the authorized redirect URI must be https://your-domain.com/auth/google/callback.
  • GitHub: the authorization callback URL must be https://your-domain.com/auth/github/callback.
  • Apple: use the Services ID as the client ID and the same Services ID in the return URL configuration. Apple posts the callback instead of using GET; the app handles that POST and excludes it from CSRF protection.
  • Facebook: the app must have Facebook Login enabled and a matching redirect URI. While the app is in Development mode, only admins, developers and testers can sign in.
  • Microsoft: the Entra app registration needs the Web platform and the /auth/microsoft/callback redirect URI. MICROSOFT_TENANT_ID=common allows personal and work accounts.

Google shows “Access blocked” #

The Google OAuth app is probably still in Testing mode. In Google Cloud Console, open APIs & Services → OAuth consent screen (or Google Auth Platform → Audience), add every Google account that will test the sign-in under Test users, save and try again. Test users may see an unverified-app warning and can continue through Advanced. Publishing the app removes the test-user restriction for the openid, email and profile scopes. A Google Workspace admin may also need to allow the app.

OAuth authorize fails with “Invalid key supplied” #

Passport cannot load the RSA key pair used to sign OAuth tokens. This is a server-side key problem. The usual causes are missing storage/oauth-private.key and storage/oauth-public.key files, or PASSPORT_PRIVATE_KEY and PASSPORT_PUBLIC_KEY values with collapsed newlines.

php artisan passport:keys --force
chmod 600 storage/oauth-private.key
chmod 640 storage/oauth-public.key
php artisan optimize:clear

If the environment variables are set, remove them before regenerating files, because they take precedence. Regenerating the keys invalidates every existing access token, so every client must complete OAuth again.

A payment link or download link is missing #

  • The payment email is sent only after the search and contact extraction finish. Wait for the search to complete.
  • Check the spam or promotions folder and confirm the email address supplied with the search.
  • If the Stripe session expired, the request is payment_expired. Call renew-payment-link with the request ID or use the retry link in the payment email.
  • If the link is still active but the email is lost, the tool will refuse to replace it. Contact support with the request ID.
  • A download link only works for a paid or completed request. A 404 usually means the token is unknown, unpaid or mistyped.

A second search is rejected #

Only one unpaid search is allowed per email address. The agent must explain which request is blocking the new one and whether it is awaiting payment, expired or still in progress. Pay the existing request or renew its link; do not submit the new search with a different email address on the user behalf.

Reference and operations

Security and privacy #

The MCP server is designed around delegated browser authentication. The AI client never sees a password, and the server never asks for one in a tool call.

Tokens and sessions #

  • OAuth 2.1 uses the authorization code flow with PKCE and the mcp:use scope.
  • The access token is stored by the MCP client, not by the model or the chat history.
  • Disconnecting the server or revoking the token removes the client access. Complete OAuth again to reconnect.
  • Never paste an access token into a chat, a prompt or a public repository.
  • The MCP endpoint is rate limited per IP address to limit abuse.

Download links #

The CSV download link contains a unique token. It works without a login, so treat it like a password: anyone who has the link can download the file. Forward only to the intended recipient, and do not publish it.

Payment data #

Stripe Checkout collects card details and billing addresses. The application stores transaction identifiers, amounts, status and, when a card is saved, the card brand, expiry date, last four digits and Stripe identifiers. It does not receive or store full card numbers.

Search data and privacy #

The service stores the account, the search parameters, the email address, the results and the payment records needed to operate the request. Results contain publicly available business information, but you are responsible for using it lawfully, including marketing and privacy rules in your jurisdiction.

Read the Privacy Policy and Terms of Service for the full data-handling and acceptable-use terms.

Reference and operations

Section index #

Every section and subsection on this page has a stable anchor and a visible # link. Use the grouped list for a quick overview, the anchor directory for a complete machine-friendly list, or the JSON index to resolve every feature programmatically.

Getting started

Tools

Prompt templates

How searches work

Client setup

Reference and operations

Machine-readable index #

The JSON object below is embedded in the page and describes the server, its transport, authentication, tools, prompts, pricing and every anchor. Bots and integrations can read it without parsing the layout. A JSON-LD TechArticle block is also embedded for search engines.

Show the machine-readable index
{
    "@context": "https://schema.org",
    "@type": "TechArticle",
    "name": "MCP server documentation",
    "headline": "MCP server documentation",
    "description": "Complete documentation for The Massive - Business Search MCP server: endpoint, OAuth 2.1, tools, prompts, search lifecycle, payments, CSV delivery, client setup and troubleshooting.",
    "url": "https://themassive.com.au/docs/mcp",
    "inLanguage": "en",
    "about": {
        "@type": "SoftwareApplication",
        "name": "The Massive - Business Search",
        "applicationCategory": "BusinessApplication",
        "softwareVersion": "1.0.0",
        "mcpEndpoint": "https://themassive.com.au/mcp/search",
        "transport": "Streamable HTTP",
        "protocol": "JSON-RPC 2.0",
        "authentication": "OAuth 2.1 authorization code flow with PKCE, scope mcp:use",
        "rateLimit": "60 requests per minute per IP address"
    },
    "tools": [
        {
            "name": "get-account",
            "readOnly": true,
            "destructive": false,
            "idempotent": true,
            "anchor": "#get-account"
        },
        {
            "name": "create-search-request",
            "readOnly": false,
            "destructive": true,
            "idempotent": false,
            "anchor": "#create-search-request"
        },
        {
            "name": "renew-payment-link",
            "readOnly": false,
            "destructive": true,
            "idempotent": false,
            "anchor": "#renew-payment-link"
        }
    ],
    "prompts": [
        {
            "name": "get-started",
            "arguments": [
                "keyword",
                "country",
                "locations",
                "email"
            ],
            "anchor": "#get-started"
        },
        {
            "name": "connect-account",
            "arguments": [],
            "anchor": "#connect-account"
        }
    ],
    "pricing": {
        "currency": "USD",
        "perContact": 0.5,
        "minimumPerSearch": 1,
        "subscription": false,
        "anchor": "#pricing"
    },
    "sections": [
        {
            "title": "Overview",
            "anchor": "#overview"
        },
        {
            "title": "Endpoint",
            "anchor": "#endpoint"
        },
        {
            "title": "Protocol",
            "anchor": "#protocol"
        },
        {
            "title": "Authentication",
            "anchor": "#authentication"
        },
        {
            "title": "Rate limits",
            "anchor": "#rate-limits"
        },
        {
            "title": "Tools overview",
            "anchor": "#tools"
        },
        {
            "title": "get-account",
            "anchor": "#get-account"
        },
        {
            "title": "create-search-request",
            "anchor": "#create-search-request"
        },
        {
            "title": "renew-payment-link",
            "anchor": "#renew-payment-link"
        },
        {
            "title": "Prompts overview",
            "anchor": "#prompts"
        },
        {
            "title": "get-started",
            "anchor": "#get-started"
        },
        {
            "title": "connect-account",
            "anchor": "#connect-account"
        },
        {
            "title": "Search lifecycle",
            "anchor": "#search-lifecycle"
        },
        {
            "title": "Search rules",
            "anchor": "#search-rules"
        },
        {
            "title": "Payments",
            "anchor": "#payments"
        },
        {
            "title": "Pricing",
            "anchor": "#pricing"
        },
        {
            "title": "Request statuses",
            "anchor": "#statuses"
        },
        {
            "title": "CSV and results",
            "anchor": "#csv-download"
        },
        {
            "title": "Connection settings",
            "anchor": "#client-setup"
        },
        {
            "title": "Claude Code",
            "anchor": "#claude-code"
        },
        {
            "title": "Claude Desktop and Claude.ai",
            "anchor": "#claude-desktop"
        },
        {
            "title": "Cursor",
            "anchor": "#cursor"
        },
        {
            "title": "VS Code and Copilot",
            "anchor": "#vs-code"
        },
        {
            "title": "Other clients",
            "anchor": "#other-clients"
        },
        {
            "title": "Error reference",
            "anchor": "#errors"
        },
        {
            "title": "Troubleshooting",
            "anchor": "#troubleshooting"
        },
        {
            "title": "Security and privacy",
            "anchor": "#security"
        },
        {
            "title": "Section index",
            "anchor": "#section-index"
        }
    ],
    "anchors": [
        {
            "title": "Overview",
            "anchor": "#overview"
        },
        {
            "title": "Endpoint and transport",
            "anchor": "#endpoint"
        },
        {
            "title": "Unauthenticated discovery response",
            "anchor": "#endpoint-unauthorized"
        },
        {
            "title": "Protocol and JSON-RPC methods",
            "anchor": "#protocol"
        },
        {
            "title": "Initialize example",
            "anchor": "#protocol-initialize"
        },
        {
            "title": "Authentication with OAuth 2.1",
            "anchor": "#authentication"
        },
        {
            "title": "How the browser flow works",
            "anchor": "#authentication-flow"
        },
        {
            "title": "Rate limits",
            "anchor": "#rate-limits"
        },
        {
            "title": "Tools overview",
            "anchor": "#tools"
        },
        {
            "title": "Listing and calling tools",
            "anchor": "#tools-list-call"
        },
        {
            "title": "get-account",
            "anchor": "#get-account"
        },
        {
            "title": "get-account response",
            "anchor": "#get-account-response"
        },
        {
            "title": "get-account errors",
            "anchor": "#get-account-errors"
        },
        {
            "title": "create-search-request",
            "anchor": "#create-search-request"
        },
        {
            "title": "create-search-request arguments",
            "anchor": "#create-search-request-arguments"
        },
        {
            "title": "create-search-request behaviour",
            "anchor": "#create-search-request-behaviour"
        },
        {
            "title": "create-search-request responses",
            "anchor": "#create-search-request-responses"
        },
        {
            "title": "create-search-request errors",
            "anchor": "#create-search-request-errors"
        },
        {
            "title": "create-search-request JSON schema",
            "anchor": "#create-search-request-schema"
        },
        {
            "title": "create-search-request example",
            "anchor": "#create-search-request-example"
        },
        {
            "title": "renew-payment-link",
            "anchor": "#renew-payment-link"
        },
        {
            "title": "renew-payment-link arguments",
            "anchor": "#renew-payment-link-arguments"
        },
        {
            "title": "renew-payment-link preconditions",
            "anchor": "#renew-payment-link-preconditions"
        },
        {
            "title": "renew-payment-link behaviour",
            "anchor": "#renew-payment-link-behaviour"
        },
        {
            "title": "renew-payment-link responses",
            "anchor": "#renew-payment-link-responses"
        },
        {
            "title": "renew-payment-link errors",
            "anchor": "#renew-payment-link-errors"
        },
        {
            "title": "renew-payment-link example",
            "anchor": "#renew-payment-link-example"
        },
        {
            "title": "Prompts overview",
            "anchor": "#prompts"
        },
        {
            "title": "Listing and getting prompts",
            "anchor": "#prompts-list-get"
        },
        {
            "title": "get-started",
            "anchor": "#get-started"
        },
        {
            "title": "get-started confirmation summary",
            "anchor": "#get-started-confirmation"
        },
        {
            "title": "get-started conversation flow",
            "anchor": "#get-started-behaviour"
        },
        {
            "title": "connect-account",
            "anchor": "#connect-account"
        },
        {
            "title": "Search lifecycle",
            "anchor": "#search-lifecycle"
        },
        {
            "title": "Search rules",
            "anchor": "#search-rules"
        },
        {
            "title": "What the agent must do",
            "anchor": "#search-rules-confirmation"
        },
        {
            "title": "Location guidance",
            "anchor": "#search-rules-locations"
        },
        {
            "title": "Payments",
            "anchor": "#payments"
        },
        {
            "title": "Saved card (automatic payment)",
            "anchor": "#payments-saved-card"
        },
        {
            "title": "Stripe Checkout link",
            "anchor": "#payments-checkout"
        },
        {
            "title": "Pricing",
            "anchor": "#pricing"
        },
        {
            "title": "Request statuses",
            "anchor": "#statuses"
        },
        {
            "title": "CSV and results",
            "anchor": "#csv-download"
        },
        {
            "title": "CSV columns",
            "anchor": "#csv-download-columns"
        },
        {
            "title": "Deduplication and cleaning",
            "anchor": "#csv-download-deduplication"
        },
        {
            "title": "Connection settings",
            "anchor": "#client-setup"
        },
        {
            "title": "Claude Code",
            "anchor": "#claude-code"
        },
        {
            "title": "Claude Desktop and Claude.ai",
            "anchor": "#claude-desktop"
        },
        {
            "title": "Cursor",
            "anchor": "#cursor"
        },
        {
            "title": "VS Code and GitHub Copilot",
            "anchor": "#vs-code"
        },
        {
            "title": "Other MCP clients",
            "anchor": "#other-clients"
        },
        {
            "title": "Error reference",
            "anchor": "#errors"
        },
        {
            "title": "HTTP errors",
            "anchor": "#errors-http"
        },
        {
            "title": "Tool errors",
            "anchor": "#errors-tools"
        },
        {
            "title": "Troubleshooting",
            "anchor": "#troubleshooting"
        },
        {
            "title": "The client keeps returning 401",
            "anchor": "#troubleshooting-401"
        },
        {
            "title": "OAuth consent returns 403",
            "anchor": "#troubleshooting-oauth-403"
        },
        {
            "title": "The browser flow completes but the tool says no account is connected",
            "anchor": "#troubleshooting-no-account"
        },
        {
            "title": "A social sign-in fails or the login page has no social buttons",
            "anchor": "#troubleshooting-social"
        },
        {
            "title": "Google shows Access blocked",
            "anchor": "#troubleshooting-google"
        },
        {
            "title": "OAuth authorize fails with Invalid key supplied",
            "anchor": "#troubleshooting-passport-key"
        },
        {
            "title": "A payment link or download link is missing",
            "anchor": "#troubleshooting-payment"
        },
        {
            "title": "A second search is rejected",
            "anchor": "#troubleshooting-unpaid"
        },
        {
            "title": "Security and privacy",
            "anchor": "#security"
        },
        {
            "title": "Tokens and sessions",
            "anchor": "#security-tokens"
        },
        {
            "title": "Download links",
            "anchor": "#security-download"
        },
        {
            "title": "Payment data",
            "anchor": "#security-payments"
        },
        {
            "title": "Search data and privacy",
            "anchor": "#security-data"
        },
        {
            "title": "Section index",
            "anchor": "#section-index"
        },
        {
            "title": "Machine-readable index",
            "anchor": "#section-index-machine-readable"
        },
        {
            "title": "Anchor directory",
            "anchor": "#section-index-anchor-directory"
        }
    ]
}

Anchor directory #

A complete list of every feature anchor on this page. Append any of these fragments to the page URL to link directly to that feature.

#overview Overview #endpoint Endpoint and transport #endpoint-unauthorized Unauthenticated discovery response #protocol Protocol and JSON-RPC methods #protocol-initialize Initialize example #authentication Authentication with OAuth 2.1 #authentication-flow How the browser flow works #rate-limits Rate limits #tools Tools overview #tools-list-call Listing and calling tools #get-account get-account #get-account-response get-account response #get-account-errors get-account errors #create-search-request create-search-request #create-search-request-arguments create-search-request arguments #create-search-request-behaviour create-search-request behaviour #create-search-request-responses create-search-request responses #create-search-request-errors create-search-request errors #create-search-request-schema create-search-request JSON schema #create-search-request-example create-search-request example #renew-payment-link renew-payment-link #renew-payment-link-arguments renew-payment-link arguments #renew-payment-link-preconditions renew-payment-link preconditions #renew-payment-link-behaviour renew-payment-link behaviour #renew-payment-link-responses renew-payment-link responses #renew-payment-link-errors renew-payment-link errors #renew-payment-link-example renew-payment-link example #prompts Prompts overview #prompts-list-get Listing and getting prompts #get-started get-started #get-started-confirmation get-started confirmation summary #get-started-behaviour get-started conversation flow #connect-account connect-account #search-lifecycle Search lifecycle #search-rules Search rules #search-rules-confirmation What the agent must do #search-rules-locations Location guidance #payments Payments #payments-saved-card Saved card (automatic payment) #payments-checkout Stripe Checkout link #pricing Pricing #statuses Request statuses #csv-download CSV and results #csv-download-columns CSV columns #csv-download-deduplication Deduplication and cleaning #client-setup Connection settings #claude-code Claude Code #claude-desktop Claude Desktop and Claude.ai #cursor Cursor #vs-code VS Code and GitHub Copilot #other-clients Other MCP clients #errors Error reference #errors-http HTTP errors #errors-tools Tool errors #troubleshooting Troubleshooting #troubleshooting-401 The client keeps returning 401 #troubleshooting-oauth-403 OAuth consent returns 403 #troubleshooting-no-account The browser flow completes but the tool says no account is connected #troubleshooting-social A social sign-in fails or the login page has no social buttons #troubleshooting-google Google shows Access blocked #troubleshooting-passport-key OAuth authorize fails with Invalid key supplied #troubleshooting-payment A payment link or download link is missing #troubleshooting-unpaid A second search is rejected #security Security and privacy #security-tokens Tokens and sessions #security-download Download links #security-payments Payment data #security-data Search data and privacy #section-index Section index #section-index-machine-readable Machine-readable index #section-index-anchor-directory Anchor directory

Linking guidance

Use the section anchor when you cite a feature, for example #create-search-request for the tool reference or #csv-download-columns for the CSV schema. Anchors are stable between releases; new sections add new anchors rather than renaming existing ones.