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.
Endpoint
Streamable HTTPhttps://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 clientOn 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.
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 #
- The client calls the endpoint without a token and receives 401 Unauthorized.
- The client reads the WWW-Authenticate header and loads the protected-resource metadata.
- The client discovers the authorization server and registers itself with dynamic client registration.
- The client generates a PKCE code verifier and opens the authorization URL in a browser.
- The user signs in with an existing email and password, or chooses Google, GitHub, Apple, Facebook or Microsoft.
- The user approves the mcp:use scope and the client exchanges the authorization code for an access token.
- 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 #
- The tool checks the OAuth token. Without a connected user it returns an authentication error and does not collect or store anything.
- 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.
- The country is resolved by full name or ISO alpha-2 code. An unknown country returns a clear error instead of guessing.
- Each location is checked through SerpApi. If any location cannot be verified, no request is created and the tool lists every invalid location.
- The keyword and email are normalized: the keyword is trimmed and lowercased, and the email is trimmed and lowercased.
- A short-lived cache lock prevents two simultaneous submissions for the same email address.
- 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.
- A new request is stored as pending and the background processing job is dispatched.
- 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]"
}
}
}
Tools
renew-payment-link #
Renews an expired Stripe Checkout link for an existing business search. It never creates a new search and it cannot be used to skip payment for a completed search.
A saved card may be charged immediately
Before calling this tool, confirm the request ID with the user and explain that a saved card can be charged right away. If the automatic charge succeeds, the tool sends the CSV download link instead of returning a new payment URL.
Arguments #
| Argument | Type | Rules | Description |
|---|---|---|---|
request_id |
integer | Required, minimum 1 | The business search request ID returned by create-search-request. It must belong to the connected account email. |
Preconditions #
- The connected account email must match the email on the search request.
- The request status must be payment_pending or payment_expired.
- Stripe must be configured, and the request must have a stored Checkout session ID.
- Stripe must report the stored Checkout session as expired. An active link is never replaced.
What happens when it is called #
- The request is looked up by ID and connected account email. A request belonging to another account is treated as not found.
- The current Checkout session is retrieved from Stripe and checked against the stored session ID.
- If the session is still active, the tool refuses to create a second link and explains that the current link has not expired.
- If the account has a saved card, the tool attempts an automatic charge. On success it marks the request paid and emails the CSV download link.
- Otherwise it creates a new Stripe Checkout session, stores it on the request, sets the status back to payment_pending and emails the new payment link.
- The new payment URL is returned directly in the tool response so the user does not need to wait for the email.
Successful responses #
When the saved card is charged automatically:
Business search request 42 was paid automatically using the saved card on your account.
The CSV download link has been emailed to [email protected].
When a new Stripe link is created:
New Stripe payment link created for business search request 42.
Pay here: https://checkout.stripe.com/c/pay/...
Errors #
| Error | Cause | What to do |
|---|---|---|
| Authentication required | No connected user or expired token. | Reconnect OAuth and call get-account. |
| Validation failed | request_id is missing or invalid. | Use the numeric request ID returned by create-search-request. |
| That business search request was not found for the connected account | The ID does not exist or belongs to another email address. | Check the request ID and the connected account. |
| ... does not have an unpaid payment link that can be renewed | The request is already paid, failed or in another status. | No renewal is needed. Check the download email or contact support. |
| Stripe payments are not configured | Stripe is not configured on the server. | Contact the operator. The tool cannot create a payment link without Stripe. |
| The business search does not have a stored Stripe payment session to verify | The request has no stored Checkout session ID. | Contact support with the request ID. |
| The current Stripe payment link could not be verified | Stripe could not retrieve the session. | Try again later. If it persists, contact support. |
| ... does not have an expired payment link | Stripe still considers the current link active. | Use the existing link from the payment email. |
| A new Stripe payment link could not be created | Stripe rejected the request or the API is unavailable. | Try again later. If it persists, contact support. |
Example call #
{
"jsonrpc": "2.0",
"id": 4,
"method": "tools/call",
"params": {
"name": "renew-payment-link",
"arguments": {
"request_id": 42
}
}
}
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 #
- Call get-account. If it fails, stop and ask for OAuth sign-in before collecting anything.
- Ask only for the missing details. Values already supplied as prompt arguments are echoed back and do not need to be asked again.
- When an email is supplied, read it back and ask the user to confirm it is correct.
- Show the confirmation summary with every location listed.
- Wait for explicit confirmation, then call create-search-request once.
- 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
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
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
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
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
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 request | 1. Each extra keyword needs its own search. |
| Countries per request | 1. Run a separate request for each country. |
| Locations per request | 1–100 |
| Keyword length | 1–255 characters |
| Location length | 1–255 characters each |
| Email length | Valid email, max 255 characters |
| Unpaid searches per email | 1 at a time |
| Cancellation | Not 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 |
|---|---|
| Name | Business name. |
| Public email address, where one was found. | |
| Phone | Public phone number, where one was found. |
| Address | Public physical address, where one was found. |
| Website | Business website. |
| Country | Country used for the search. |
| Location | The 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 |
|---|---|
| URL | https://themassive.com.au/mcp/search |
| Transport | Streamable HTTP |
| Authentication | OAuth 2.1 discovery, handled by the client |
| Scope | mcp:use |
| API key | Not 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 #
- Open Settings → Connectors → Add custom connector.
- Name the connector The Massive and paste the endpoint URL.
- Save the connector and complete the Google, GitHub, Apple, Facebook, Microsoft or email sign-in.
- 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
-
Overview
#overview -
Endpoint
#endpoint -
Protocol
#protocol -
Authentication
#authentication -
Rate limits
#rate-limits
Tools
-
Tools overview
#tools -
get-account
#get-account -
create-search-request
#create-search-request -
renew-payment-link
#renew-payment-link
Prompt templates
-
Prompts overview
#prompts -
get-started
#get-started -
connect-account
#connect-account
How searches work
-
Search lifecycle
#search-lifecycle -
Search rules
#search-rules -
Payments
#payments -
Pricing
#pricing -
Request statuses
#statuses -
CSV and results
#csv-download
Client setup
-
Connection settings
#client-setup -
Claude Code
#claude-code -
Claude Desktop and Claude.ai
#claude-desktop -
Cursor
#cursor -
VS Code and Copilot
#vs-code -
Other clients
#other-clients
Reference and operations
-
Error reference
#errors -
Troubleshooting
#troubleshooting -
Security and privacy
#security -
Section index
#section-index
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.
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.