MCP /v3 endpoint
Connect to the /v3 MCP endpoint and see each tool's inputs, outputs, read or write classification, billing, errors and limits.
https://mcp.usestring.ai/v3/mcp serves six tools, each of which does one kind of thing: a search, a page read, a
request with a body, or one step of a site crawl. Every tool's MCP annotations describe exactly what a call can do, so
your client can let reads run and ask you before a call that spends your balance or sends data to another site. If you
need browser actions, site integrations, or to cancel or list crawl jobs, use /v2/mcp instead.
Connect
Add this URL to your MCP client as a remote server. It speaks Streamable HTTP, and there is nothing to install.
https://mcp.usestring.ai/v3/mcpThe client steps on Remote (hosted) MCP work here too, with this URL in place
of the /v2 one.
Authenticate
Sign in with String, or send an API key. Either way, each call spends one Web Access key.
Sign in with String (OAuth)
A request with no credential gets 401 and a WWW-Authenticate header that points the client at the protected
resource metadata:
https://mcp.usestring.ai/.well-known/oauth-protected-resource/v3/mcpThe client finds the authorization server there and runs the authorization code flow:
| Setting | Value |
|---|---|
| Scope | web_access. It is the only scope, and it grants all six tools. |
| PKCE | Required, with S256. |
| Client registration | A client ID metadata document: the client ID is an HTTPS URL that serves the client's metadata. There is no dynamic client registration endpoint. |
| Token endpoint auth | none for public clients, client_secret_basic or client_secret_post. |
On String's consent screen you choose which of your organization's Web Access keys the app may use, or create one for it. The app never sees the key. Sign in with String shows the screens, and Managing connected apps shows how to disconnect an app.
API key
Send the key as a bearer token on every request:
Authorization: Bearer YOUR_API_KEYCreate a key under Settings in the portal; see Getting a key.
Permissions
The key decides what a call may do. Calls are billed to the key's organization and follow that organization's
access rules, and solveCaptcha: true only has an effect when the key is
allowed to solve captchas. The web_access scope has no read-only
variant, so read-only access is something your client enforces, using the classifications below.
Smallest working example
Every request is a JSON-RPC message, and the server keeps no session, so one tools/call is a complete request. This
fetches example.com as Markdown:
curl https://mcp.usestring.ai/v3/mcp \
-H "Authorization: Bearer $STRING_API_KEY" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"web_access_fetch","arguments":{"url":"https://example.com"}}}'The answer is one server-sent event. Its data is the JSON-RPC result, trimmed here to the headers shown:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"content": [
{ "type": "text", "text": "HTTP 200, 174 bytes" },
{ "type": "text", "text": "{\"statusCode\":200,\"headers\":{…},\"body\":\"# Example Domain\\n\\n…\"}" }
],
"structuredContent": {
"statusCode": 200,
"headers": {
"Content-Type": ["text/markdown; charset=utf-8"],
"X-Billed-Request-Type": ["request_standard"],
"X-Final-Url": ["https://example.com/"],
"X-Status-Code": ["200"]
},
"body": "# Example Domain\n\nThis domain is for use in documentation examples without needing permission. This is not a service; avoid relying on it for testing and monitoring purposes."
}
}
}Every tool answers in this shape: a one-line summary, then the same JSON as text and as structuredContent, which
matches the tool's output schema.
Tools
| Tool | Classification | readOnlyHint | destructiveHint | idempotentHint | openWorldHint |
|---|---|---|---|---|---|
web_access_search | Read | true | false | false | true |
web_access_fetch | Read | true | false | false | true |
web_access_sitemap_quote | Write | false | false | false | true |
web_access_sitemap_start | Sensitive write | false | false | false | true |
web_access_sitemap_status | Read | true | false | true | false |
web_access_request | Sensitive write | false | true | false | true |
| Classification | What a call does |
|---|---|
| Read | Retrieves information and changes nothing, on String or on any other site. |
| Write | Creates data on String, here a crawl quote, and spends nothing. |
| Sensitive write | Spends your balance or sends data to another site. A client should ask before every call. |
Each call is billed as described under its tool, at the rates on Pricing.
web_access_search
Searches the web for a query and returns ranked results, along with the other parts of the Google results page, such as the knowledge panel, AI overview and local listings. It reads search results only and changes nothing.
| Field | Type | Default | Description |
|---|---|---|---|
query | string | — | Required. The search query. Quoted phrases and site: work. |
searchCount | integer | none | Organic results wanted, 1 to 300. Pages are collected until that many are in hand. Not with format: "raw" or aiMode. |
page | integer | 1 | Results page to start from, 1 to 30. shopping has page 1 only, images 3 pages and places 15. page and searchCount together stay within the first 300 results. |
country | string | US | ISO 3166-1 alpha-2 code the search runs from. |
language | string | none | Results language, such as en or pt-br. |
location | string | none | A place name such as London, 1 to 200 characters. Results are biased toward it. |
coordinates | object | none | { latitude, longitude, radius }: latitude -90 to 90, longitude -180 to 180, radius 1 to 1,000,000 meters (default 5000). Wins over location. |
searchType | string | web | web, images, videos, shopping, books, places or forums. |
dateRange | string or object | none | hour, day, week, month or year, or { "from": "2026-01-01", "to": "2026-06-30" }. With searchType web, videos or forums only. |
sortBy | string | relevance | relevance or date. With searchType web, videos or forums only. |
safeSearch | boolean | false | true removes explicit results. |
includeOmittedResults | boolean | false | true includes results Google hides as very similar. |
autocorrect | boolean | true | false searches the query as typed. |
restrictCountry | string | none | Two-letter code; keeps only pages from that country. |
verbatim | boolean | false | true matches the query's words exactly. With searchType web, videos or forums only. |
format | string | structured | structured for parsed results, or raw for the results page as HTML, at most 60,000 bytes per call. raw works with searchType: "web" only. |
aiMode | boolean | false | true returns Google AI Mode's answer and its sources instead of results. Not with searchCount, page, format, searchType, dateRange, sortBy or the filters. |
aiOverview | boolean | false | true fills an AI overview that loads after the results page. With searchType: "web" only, and not with aiMode. |
Any other field is rejected before anything is sent. web_access_search on MCP
tools explains each field and combination in full.
Returns: results, each with position, rank, title, url, snippet, displayUrl, displayText, and
source and timestamp when Google shows them; zeroResults, true when nothing matched; each other part of the
page Google showed, such as entity, places, overviews and peopleAlsoAsk; aiMode when you asked for it;
paging when you sent searchCount; and html, htmlBytes and htmlTruncated with format: "raw". The fields
are those of POST /search.
{ "query": "model context protocol specification" }{
"results": [
{
"position": 1,
"rank": 1,
"title": "Specification",
"url": "https://modelcontextprotocol.io/specification/2025-06-18",
"snippet": "Model Context Protocol (MCP) is an open protocol that enables seamless integration between LLM applications and external data sources and tools.",
"displayUrl": "https://modelcontextprotocol.io › specification",
"displayText": "https://modelcontextprotocol.io › specification",
"source": "Model Context Protocol",
"timestamp": "Jun 18, 2025"
}
],
"relatedSearches": ["…"],
"peopleAlsoAsk": [{ "question": "…" }],
"overviews": [{ "text": "…", "sources": [{ "title": "…", "url": "…" }] }]
}Side effects and billing: none outside String. Each results page fetched is billed as one search, so a
searchCount call is billed once per page in paging.pages. aiOverview: true is billed at the AI Overview rate on
Pricing.
Errors: a value out of range or a combination that doesn't apply fails before anything is sent, with a message
naming the field. If an AI Mode answer isn't available, the call fails; retry, or search without aiMode.
web_access_fetch
Fetches a URL with a GET and returns the page, as Markdown by default. It reads the page and changes nothing on the
destination. To send a body, use web_access_request.
| Field | Type | Default | Description |
|---|---|---|---|
url | string | — | Required. The full http or https URL. |
format | string | markdown | markdown, raw for the destination's body as it was sent, or json for the destination's own status and headers. |
executeJS | boolean | false | true renders JavaScript first, for pages that build their content in the browser. Not with headers. |
headers | object | none | Request headers to send, up to 50, in the order given. A name given twice is an error. Not with executeJS. |
countryCode | string | none | ISO 3166-1 alpha-2 code; the page is requested from that country. |
solveCaptcha | boolean | false | With false, a challenge page fails the call with an error. true has the request attempt the challenge; see Captcha solving. |
Returns: statusCode, the destination's HTTP status; headers; and body, the page in the requested format.
With markdown or raw, headers includes X-Status-Code and X-Billed-Request-Type; with json, headers are
the destination's own. The smallest working example shows a full response.
Side effects and billing: one GET to the destination, which changes nothing there. The request is billed at the
rate X-Billed-Request-Type names (see Billed request type),
including when the destination answers with an error status such as 404.
Errors: a destination error such as 404 is a successful call carrying that statusCode. A page that can't be
returned, including a challenge page with solveCaptcha: false, fails the call; see Errors.
web_access_sitemap_quote
Quotes a crawl of one site, starting from a URL and following same-domain links breadth-first. It creates a job waiting for approval and crawls nothing.
| Field | Type | Default | Description |
|---|---|---|---|
url | string | — | Required. The URL to start from. The crawl stays on its domain. |
maxPages | integer | 10 | Most pages to fetch, 1 to 10,000. |
maxDepth | integer | 2 | How many links deep to follow from the start URL, 1 to 100. |
pathPrefix | string | none | Only crawl URLs whose path starts with this, such as /docs. |
budgetUsd | number | quote | Spend cap in USD, at least 0.0001. The crawl stops as token_cap_exceeded before going over it. |
useSitemap | boolean | false | Also start from the site's /sitemap.xml, which is one more billed page. |
A value out of range is rejected; leave a field out to get its default.
Returns: jobId, status (awaiting_approval), estimatedPages and estimatedCostUsd, the most the crawl
will cost.
{ "url": "https://example.com", "maxPages": 500, "maxDepth": 3 }{
"jobId": "6f1c9f0a-…",
"status": "awaiting_approval",
"estimatedCostUsd": "0.0500000000",
"estimatedPages": 500
}Side effects and billing: creates one job record. Quoting is free, and each call creates a new job. A quote that
is not started within one hour expires: the job becomes failed with errorMessage: "quote_expired".
Errors: 403 when the site is not enabled for your organization; see Access control.
web_access_sitemap_start
Starts a quoted crawl. This is the step that spends money, so call it only after the user has approved the quote's
estimatedCostUsd.
| Field | Type | Default | Description |
|---|---|---|---|
jobId | string | — | Required. The jobId that web_access_sitemap_quote returned. |
Returns: jobId and its new status, running.
Side effects and billing: holds estimatedCostUsd, or budgetUsd when that is lower, against your balance, then
crawls the site with GET requests, up to maxPages pages. Pages are billed as they are fetched, and the hold is
settled against the pages actually crawled when the job ends. Calling it again with the same jobId never starts a
second crawl or holds the cost twice: a running job answers running, and a finished one answers with its final
status, such as failed for an expired quote.
Errors: 402 when your balance can't cover the hold; the job becomes failed, so quote again after adding funds.
409 with partial_state means the start was interrupted; call this tool again with the same jobId. 404 means
the job doesn't exist or belongs to another organization. A canceled or token_cap_exceeded job can't be
started; quote a new crawl.
web_access_sitemap_status
Reports a crawl job's status and, once the job has ended, returns a page of the URLs it found. It reads String's job record and changes nothing, so calling it again is always safe.
| Field | Type | Default | Description |
|---|---|---|---|
jobId | string | — | Required. The jobId that web_access_sitemap_quote returned. |
limit | integer | 1000 | URLs per page once the job has ended, 1 to 5,000. |
offset | integer | 0 | URLs to skip, for paging. |
Returns: jobId and status, plus whichever of estimatedCostUsd, pending, processed, pagesProcessed,
errorMessage and finishedAt the job reports. Once the job has ended it also returns total and urls. Each URL
has url, statusCode (0 if it was found but not fetched), depth, parentUrl, isSitemap, sourceType, and
error when fetching it failed. See Get discovered URLs.
{ "jobId": "6f1c9f0a-…", "status": "running", "pending": 42, "processed": 158 }Side effects and billing: none, and it is free.
Errors: 404 means the job doesn't exist or belongs to another organization. If a job has ended but its URLs
can't be read, the call fails instead of returning the status alone; call it again.
web_access_request
Sends a POST, PUT or PATCH with a body to a URL you give it, and returns the destination's response. Some of
these calls only read, such as a GraphQL query or a search form, but a call can create, change or delete data on the
destination, so treat every call as one that can.
| Field | Type | Default | Description |
|---|---|---|---|
url | string | — | Required. The full http or https URL. |
method | string | — | Required. POST, PUT or PATCH. GET is refused; read a page with web_access_fetch. |
body | string or object | none | A string is sent as it is; an object is sent as the JSON you wrote. |
format | string | json | json for the destination's status and headers, markdown, or raw. |
headers | object | none | Request headers to send, up to 50, in the order given. A name given twice is an error. |
countryCode | string | none | ISO 3166-1 alpha-2 code; the request is sent from that country. |
solveCaptcha | boolean | false | With false, a challenge page fails the call with an error. true has the request attempt the challenge. |
Returns: statusCode, headers and body, as for web_access_fetch.
{ "url": "https://httpbin.org/post", "method": "POST", "body": { "name": "widget" } }{
"statusCode": 200,
"headers": { "Content-Type": ["application/json"] },
"body": "{\"json\":{\"name\":\"widget\"},\"url\":\"https://httpbin.org/post\"}"
}Side effects and billing: sends the request and its body to the destination, which may act on it. A call is not
idempotent: sending it twice can act twice. A call that fails may still have reached the destination, so check the
destination's state before you send the same request again. Billed like web_access_fetch.
Errors: 403 with a reason when the method needs approval for that path, such as a sign-in path; see
Access control & KYC.
Crawl status
A crawl moves through these statuses, read with web_access_sitemap_status:
status | Meaning | Next step |
|---|---|---|
awaiting_approval | Quoted. Nothing is crawled or held. | Show the user estimatedCostUsd, then start it. |
partial_state | A start was interrupted. | Call web_access_sitemap_start again. |
running | Crawling. pending and processed count the URLs. | Poll again. |
completed | Finished. | Read urls, paging with offset to total. |
token_cap_exceeded | Stopped at the spend cap before maxPages. The URLs found so far are readable. | Read urls. |
failed | Ended without finishing; errorMessage says why, such as quote_expired. | Read urls, or quote again. |
canceled | Canceled. The pages already fetched stay billed and readable. | Read urls. |
So a crawl takes three tools in order:
web_access_sitemap_quotereturns ajobIdandestimatedCostUsd.- After the user approves that cost,
web_access_sitemap_startstarts the job. web_access_sitemap_statusreports progress. Poll every few seconds for a small crawl, and every few tens of seconds for a crawl of hundreds of pages. Once the job has ended, the same tool returns its URLs.
Errors
A request the endpoint refuses gets an HTTP error before any tool runs:
| Status | Meaning | What to do |
|---|---|---|
401 | No credential, or an OAuth token that is invalid or expired. WWW-Authenticate names the metadata and scope="web_access". | Sign in again. |
403 | The token lacks the web_access scope, or no key is connected for this app. | Sign in again and choose a key. |
413 | The request body is over 4 MiB. | Send a smaller body. |
A tool call that fails returns a result with isError: true and a text message. A message about your input names
the field and the rule, such as maxPages 0 is out of range 1-10000, and nothing was sent. A refusal from the Web
Access API names its HTTP status, such as status=402, with the API's error text:
| Status | Meaning | What to do |
|---|---|---|
400 | The API rejected a field. | Fix the field the message names. |
401 | The API key is invalid or revoked. | Use an active key. |
402 | Your organization's balance can't cover the call. | Add funds under Usage & Plans, then retry. |
403 | The destination isn't enabled for your organization, or the method needs approval for the path. | See Access control & KYC. |
404 | The crawl job doesn't exist or belongs to another organization. | Check the jobId. |
409 | partial_state: a crawl start was interrupted. | Call web_access_sitemap_start again. |
429 | Over the rate limit. | Wait, then retry with backoff. |
502 | The destination returned an error, or the request could not be completed. | Retry later; read errorType in the message when present. |
504 | The destination did not respond in time. | Retry later. |
A failed call is not billed, apart from the cases on Pricing. The general meaning of each
status and errorType is on Errors.
Rate limits
Tool calls are Web Access API requests made with your key, so they count toward your organization's
rate limit; a call over it fails with status=429. Each tool's own limits are in
its field table above.
See also
- Remote (hosted) MCP for client setup and connected apps.
- Sitemap for a crawl walkthrough.
- Pricing for search, request and crawl rates.