There is an unfamiliar IP address in your access logs. You paste it into Claude and ask the obvious question: where is this, and is it a VPN? Until recently the best you could get back was a confident guess, which is the worst kind of wrong answer, because it looks exactly like a right one. With the ipstack MCP server connected, the assistant stops guessing and goes and looks.
The ipstack MCP server connects AI assistants such as Claude, Cursor, and ChatGPT to ipstack’s real-time IP geolocation API through the Model Context Protocol (MCP). Once configured, the assistant can look up any IPv4 or IPv6 address and receive location, timezone, currency, ASN, and threat intelligence data as structured tool results. It uses your existing ipstack API key, and MCP usage is billed against your normal ipstack plan with no additional charge.
Key Takeaways
- The ipstack MCP server gives AI assistants live IP geolocation through four tools: Standard Lookup, Bulk Lookup (up to 50 IPs), Threat Intelligence, and ASN and Connection Data.
- It works in Claude Desktop, Cursor, VS Code Copilot, Windsurf, Cline, and ChatGPT.
- You can use the hosted server at mcp.apilayer.com/mcp or run it locally with npx @apilayer/mcp-server.
- Every lookup returns 100+ data fields covering 2M+ locations and 200K+ cities, over IPv4 and IPv6.
- MCP usage costs nothing beyond your existing ipstack plan, and a free API key needs no credit card.
MCP in 60 Seconds
The Model Context Protocol is an open standard for letting AI applications call external tools and data sources in a uniform way. Three roles do the work. The host is the application you actually type into, such as Claude Desktop or Cursor. The client is the connector living inside it. The server is the piece that exposes tools, which in this case means ipstack’s lookups.
Why this matters for IP addresses specifically: a language model cannot know where an address is located. Not because the model is weak, but because the answer keeps changing. IP assignments move daily, and anything a model absorbed during training was true on a date that has since passed. The only correct answer is a live lookup at the moment you ask, which is exactly what an IP geolocation API is for. MCP is the plumbing that gets that lookup into the conversation instead of leaving you to switch tabs.
What the ipstack MCP Server Actually Does
Four tools show up in the assistant once the server is connected. You never call them by name; you ask a question in plain English and the assistant works out which one it needs.
|
Tool |
What it does |
Typical use |
|---|---|---|
|
Standard Lookup |
Full 100+ field lookup of one IPv4 or IPv6 address |
“Where is this IP and who owns it?” |
|
Bulk Lookup |
Up to 50 IP addresses in a single request |
Triage a log excerpt or alert batch |
|
Threat Intelligence |
Flags proxies, VPNs, Tor exit nodes, and known threats |
Fraud review, security triage |
|
ASN and Connection Data |
ISP, carrier, ASN, and connection type |
Spot hosting providers and scrapers |
Here is the shape of it in practice. Ask this:
Look up 134.201.250.155 and tell me the city, timezone, and whether it is a proxy.
And the assistant comes back with something built from this, abridged here from the full 100+ field response:
{
"ip": "134.201.250.155",
"type": "ipv4",
"city": "Los Angeles",
"region_name": "California",
"country_name": "United States",
"zip": "90013",
"latitude": 34.0453,
"longitude": -118.2413,
"time_zone": { "id": "America/Los_Angeles" },
"currency": { "code": "USD", "symbol": "$" },
"connection": { "asn": 25876, "isp": "Los Angeles Department of Water and Power" },
"security": { "is_proxy": false, "is_tor": false, "threat_level": "low" }
} What makes this useful is not any single field. It is that one lookup answers several unrelated questions at once, depending on which part of the response you care about.
You don’t need to overthink the math here. A simple rule works well: increase the wait between attempts, add some randomness, and cap the delay at a reasonable maximum, such as 30 seconds. That prevents a long chain of failures from leaving a user waiting indefinitely.
Backoff only makes sense for retryable errors, though. Before retrying anything, it helps to understand why GET requests are generally safe to retry in the first place.
Idempotency: Why GET Lookups Are Generally Safe to Retry
An idempotent request is one where sending the same request multiple times has the same intended effect as sending it once. A GET request, like an IP lookup, is a good example. Asking “what’s the location data for this IP?” ten times doesn’t change the resource on the server. It simply returns the requested data each time, network hiccups aside.
This matters because automatic retries are safest when repeating a request doesn’t create an unwanted side effect. POST requests that create a new record, charge a payment, or send an email aren’t necessarily safe to retry, since a retry could duplicate the action.
Before adding automatic retries to any API call in your app, check whether the operation is idempotent. If it is, like a typical read-only lookup, retrying is generally low-risk. If it isn’t, you need extra protection, such as an idempotency key, before retrying it.
For read-only lookups, retries are usually straightforward. But what happens when you’ve exhausted your retries and the API still isn’t responding? That’s where your fallback plan matters.
Graceful Degradation Patterns: Cached Fallback, Feature-Off, Default Value
Retries buy you time, not guarantees. At some point, you have to accept that the call failed and decide what your app should show the user instead of an error screen.
A few patterns work well here:
Cached fallback. If you looked up the same IP or resource recently, serve the cached result instead of failing outright. Geolocation data for a given IP doesn’t usually change quickly, so a cache that’s a few hours or even a day old may be acceptable for many use cases. The right freshness window depends on how accurate the feature needs to be.
Feature-off. If the failing call powers a non-critical feature, such as showing a visitor’s country flag next to their comment, hide that feature for this request instead of blocking the whole page. The user still gets everything else.
Default value. If an approximate answer is good enough, fall back to a sensible default. For location data, that might mean showing “Unknown region” instead of leaving the field blank, or using a general currency and language instead of a localized one.
Pick the pattern that fits the feature. A checkout page might need a cached fallback or another carefully designed recovery path. A “Welcome, visitor” banner can happily fall back to a generic greeting.
Now let’s put all three pieces (classify, retry, degrade) into one small, reusable wrapper.
A Complete Resilient Client Wrapper
Instead of scattering this logic across your codebase, wrap it once. Here’s a compact example using an IPstack lookup. It classifies the response, avoids retrying errors that won’t resolve on their own, retries temporary failures with backoff, and calls a fallback when it gives up.
JavaScript:
async function resilientLookup(ip, accessKey, fallback, maxAttempts = 4) {
const url = `https://api.ipstack.com/ip?accesskey={accessKey}`;
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
try {
const res = await fetch(url);
const data = await res.json();
if (data.success === false) {
const code = data.error.code;
// These errors won't be fixed by retrying.
if (code === 101 || code === 104) {
return fallback(data.error);
}
return fallback(data.error);
}
return data;
} catch (error) {
// Network error, retry below.
}
if (attempt < maxAttempts) {
const base = Math.pow(2, attempt) * 1000;
const jitter = Math.random() * base * 0.5;
await new Promise(resolve =>
setTimeout(resolve, base + jitter)
);
}
}
return fallback({ type: "max_attempts_reached" });
} Python:
import time
import random
import requests
def resilient_lookup(ip, access_key, fallback, max_attempts=4):
url = f""
params = {"access_key": access_key}
for attempt in range(1, max_attempts + 1):
try:
res = requests.get(url, params=params, timeout=5)
data = res.json()
if data.get("success") is False:
code = data["error"]["code"]
# These errors won't be fixed by retrying.
if code in (101, 104):
return fallback(data["error"])
return fallback(data["error"])
return data
except requests.RequestException:
# Network error, retry below.
pass
if attempt < max_attempts:
base = 2 ** attempt
jitter = random.uniform(0, base * 0.5)
time.sleep(base + jitter)
return fallback({"type": "max_attempts_reached"}) Both versions follow the same pattern: make the request, classify the failure, retry when appropriate, and fall back when attempts are exhausted. The fallback function is where your cached value, default, or feature-off logic lives.
The example uses IPstack because a geolocation lookup is a straightforward way to see these patterns in practice. The same wrapper approach can be adapted to other HTTP APIs, with the specific errors and retry rules adjusted to match each API’s behaviour.
💡For the complete list of IPstack API error codes and what they mean, the IPstack API documentation is a useful reference.
Conclusion
API error handling best practices aren’t about preventing every failure. They’re about building a client that knows the difference between “try again” and “stop and adjust,” waits sensibly between retries, and always has a fallback so one failed call doesn’t take down the whole experience for your users.
Start small: classify the errors your app already sees, add backoff to the ones worth retrying, and choose one fallback pattern for your most important feature. That alone puts you ahead of an integration with no error handling at all.
If you’re working with IPstack, the IP API tutorial hub is a useful next step for setting up the API and understanding the error codes it can return. The same error-handling principles apply whether you’re building around IPstack or another HTTP API.
Frequently asked questions
Do I need to retry every failed API call?
No. Only retry errors that are likely to succeed on another attempt, such as timeouts, dropped connections, or temporary server errors. Retrying a client error, such as an invalid access key, usually just wastes time and quota.
How many retry attempts should I use?
Three to five attempts is a reasonable starting point for many applications. The right number depends on how long your users can wait and how important the request is. More retries can simply delay your fallback without adding much benefit.
What's the difference between backoff and jitter?
Backoff is the increasing wait time between retries. Jitter adds a small random delay to that wait, helping prevent multiple clients from retrying at the same time.
Is it safe to retry a POST request the same way as a GET request?
Not always. GET requests are generally safe to repeat because they’re intended to retrieve data without changing the resource. POST requests can create duplicates or trigger an action more than once, so check whether the operation is idempotent before retrying it.
What should my app show the user if the API stays down?
Whatever keeps the experience usable: a cached value, a sensible default, or a non-critical feature temporarily turned off. Avoid exposing raw API errors to the end user.