Skip to main content
Glama
vpndetection-io

VPNDetection MCP Server

Official

VPNDetection MCP Server

npm license

The official Model Context Protocol server for the VPNDetection API.

It gives an AI agent seven read-only tools for anonymity detection: whether an address belongs to a VPN, a residential, datacenter or mobile proxy, a Tor node, a public relay, a hosting provider or a CDN, plus the database catalog and where your organization's license for each one stands.

Getting Started

We host it at https://mcp.vpndetection.io/mcp, and you sign in to it with your VPNDetection account. It also runs on your own machine from npm.

In Claude

In claude.ai, the desktop app or Cowork, add https://mcp.vpndetection.io/mcp as a custom connector and choose Use Claude's published identity when asked. In Claude Code, install our plugin, which adds the server with skills:

/plugin marketplace add vpndetection-io/claude-plugin
/plugin install vpndetection@vpndetection

Either way you sign in with your VPNDetection account, and Claude never sees your API key. The steps, the skills and how to disconnect: docs.vpndetection.io/integrations/claude.

In any other MCP client

Point it at https://mcp.vpndetection.io/mcp. A client that supports MCP authorization signs in the same way. One that doesn't can send a key instead, as Authorization: Bearer your-key.

On your own machine

No API key needed to start. The free tier answers ip and is_vpn, and allows 1000 requests per day per source address.

Add this to your MCP client's config:

{
  "mcpServers": {
    "vpndetection": {
      "command": "npx",
      "args": ["-y", "vpndetection-mcp"]
    }
  }
}

Requires Node.js 22 or newer.

A key unlocks the provider name, the classification databases and the proxy families. Put it in the environment:

{
  "mcpServers": {
    "vpndetection": {
      "command": "npx",
      "args": ["-y", "vpndetection-mcp"],
      "env": { "VPNDETECTION_API_KEY": "your-key" }
    }
  }
}

VPNDETECTION_BASE_URL overrides the endpoint if you need to point somewhere else.

Related MCP server: hackmyip-mcp

Tools

Tool

What it answers

lookup_ip

Classify one address.

lookup_ips

Classify a whole list of addresses in one call, keyed by address. A long list is batched for you.

my_entitlement

What this key is entitled to and what it has spent: plan, field tier, requests so far, allowance, and when it resets.

list_databases

Every database we publish, with its standing for your organization: licensed, expired or unlicensed.

database_metadata

A database's columns, sample rows, row count, build date and file sizes.

database_checksum

The published digests for one database file.

list_downloads

Your organization's recent download attempts, refusals included.

Every tool is read-only. There is deliberately no download tool: the databases run to several GB, which is not something an agent should pull into a conversation. Fetch them with the client libraries or the API instead.

There is deliberately no my_ip tool, although every client library has one. Over a hosted transport the address our edge observes belongs to whatever proxied the call - Claude's infrastructure, not the person asking - so the tool would answer confidently and wrongly for the only reading anyone would put on it. my_entitlement has no such problem and is the same answer from any transport, because it describes the credential rather than the connection. A test pins the tool's absence so it cannot be added back by accident.

Usage counts against the anniversary of the subscription, not the calendar month and not the billing period. A null hard_limit means we never stop serving; it is not a limit of zero.

Reading a result

Each lookup comes back with a coverage block beside it:

{
  "result": { "ip": "45.83.91.1", "is_vpn": true },
  "coverage": {
    "included": ["ip", "is_vpn"],
    "not_included": ["is_hosting", "is_tor", "hosting", "tor", "..."],
    "note": "The fields in not_included were not returned, because this API key's plan does not include them. ..."
  }
}

This matters more here than in a normal client library. A field missing from a result means your plan doesn't include it, never "we checked and found nothing" - and a model reading the result on its own will otherwise treat the absence as a negative answer. coverage states the difference explicitly so it can't.

Other Libraries

There are official VPNDetection client libraries available for many languages including PHP, Python, Go, Java, Ruby, and many popular frameworks such as Django, Rails, and Laravel. See our GitHub at https://github.com/vpndetection-io for more.

About VPNDetection

VPN Detection API: Accurate anonymity detection identifying VPNs, residential proxies, hosting servers, Tor nodes, CDNs, relays and more.

License

This project is licensed under the MIT License.

Available Tools

7 tools
database_checksumGet a database's checksumsA
Read-onlyIdempotent

The published digests for one database file, for verifying a copy you already hold or deciding whether a build has changed since you last fetched it. It is licensed like the download itself, so a database this key cannot download is refused here too. Looking up a checksum is not a download and does not appear in list_downloads.

ParametersJSON Schema
NameRequiredDescriptionDefault
formatYesWhich published file to digest.
dataset_idYesA VERSIONED database id, from `versions[].id` in `list_databases` - `cdn_ip_v1`, not `cdn_ip`. The unversioned base id is a license reference and is not accepted here.

Output Schema

ParametersJSON Schema
NameRequiredDescription
checksumsYesThe published digests for one database file.

TDQS

A4.3/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations cover the safety profile (readOnly, idempotent, openWorld), and the description adds genuinely useful non-annotation context: entitlement is enforced identically to the download, so unauthorized keys are refused, and the call has no write side effect on the downloads record. Return format and pagination are absent, but the output schema and annotations carry that load.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three tight sentences, front-loaded with what the tool returns before moving to the licensing and side-effect caveats. No sentence is redundant or restates the title.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With an output schema present and rich annotations, the description supplies exactly the missing pieces: the entitlement rule and the fact that checksum lookups are invisible to list_downloads. Nothing an agent needs to invoke it correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100% and both parameters are documented there, including the versioned-vs-unversioned dataset_id distinction and the format enum. The description adds no parameter-level detail beyond that, so the baseline of 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific resource (published digests for one database file) with two concrete purposes (verifying a held copy, detecting build changes). It also explicitly separates itself from the sibling list_downloads by noting a checksum lookup is not a download, so an agent can route without opening schemas.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives clear when-to-use conditions ('verifying a copy you already hold', 'deciding whether a build has changed') and a when-not ('is not a download'). It stops short of naming the alternative tool to reach for in each case, but the exclusions are strong enough that misuse is unlikely.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

database_metadataDescribe a databaseA
Read-onlyIdempotent

What is inside one database before you fetch it: the columns in each published format with their types, a few sample rows, the row count, the build date and the file sizes. Use this to answer questions about what a database contains without downloading it - the files reach several GB - and to budget a transfer before starting one.

ParametersJSON Schema
NameRequiredDescriptionDefault
dataset_idYesA VERSIONED database id, from `versions[].id` in `list_databases` - `cdn_ip_v1`, not `cdn_ip`. The unversioned base id is a license reference and is not accepted here.

Output Schema

ParametersJSON Schema
NameRequiredDescription
idYes
sizeNoBytes per format
sampleNoA few real rows, keyed by format
schemaYesColumns, keyed by format
entriesYesRow count in the current build
updatedYes
sample_sizeNoBytes per format of the evaluation sample, where one is published
update_freqNoHow often a new build is published
sample_entriesNoRow count in the evaluation sample

TDQS

A4.1/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already establish readOnly/idempotent/openWorld, so the safety profile is covered. The description adds real operational context: it lists what the response surfaces (sample rows, build date, sizes) and warns that the underlying files reach several GB — useful for deciding whether to proceed to a download.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two sentences, front-loaded with the returned content before the usage rationale. Slightly dense ('What is inside one database before you fetch it'), but no filler or repetition of the schema.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, so return-value explanation is not required, and the description covers purpose, usage and the size caution. The only residual gap is that no sibling tool is named as the explicit alternative, which is minor for this tool's complexity.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100% and the dataset_id description is unusually thorough (versioned id from versions[].id vs. the unversioned base id, which is rejected). The description adds no parameter guidance of its own, so the baseline 3 for schema-complete definitions applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States precisely what the tool returns — per-format columns with types, sample rows, row count, build date and file sizes — which is a specific resource description, not a restatement of the name. It is clearly distinguishable from list_databases (enumeration) and database_checksum (integrity) among the siblings.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly gives two use cases: answering questions about database contents without downloading, and budgeting a transfer before starting one. It implies the alternative (fetching/downloading the files) rather than naming a specific sibling tool, so it stops just short of full when/when-not routing.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

list_databasesList databasesA
Read-onlyIdempotent

The database catalog as this API key's organization may see it, one entry per database FAMILY, with standing saying where their license stands: licensed if the family is theirs today, expired if the term has ended, unlicensed if it is published but has never been bought. Each entry has a base id, which is what the license names, and a versions array whose id is what the other database tools take - pass versions[].id (cdn_ip_v1), never the base (cdn_ip). Ask again rather than holding on to this: it is answered per key and is not the same for everyone.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

Output Schema

ParametersJSON Schema
NameRequiredDescription
databasesYes

TDQS

A4.6/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Beyond the readOnly/idempotent/openWorld annotations, the description discloses that results are key-scoped and differ per caller, that entries are grouped by family not version, and what each `standing` value means (licensed/expired/unlicensed). The base-vs-version id hazard is a real behavioral trap that no annotation or schema could convey.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the resource and the FAMILY grouping, then the standing enumeration, then the id warning. Every clause is load-bearing, though the run-on structure and heavy inline backtick markup make it denser than it needs to be.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Despite an output schema existing, the description still supplies the interpretive context (standing semantics, base vs versions ids, per-key variance) that makes the output usable for chaining into other database tools. Nothing an agent needs to call or consume this correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool takes zero parameters, so per the baseline rule the schema carries no parameter burden to compensate for. The description's id guidance concerns output fields rather than inputs, so it neither adds nor detracts from parameter semantics.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('the database catalog ... one entry per database FAMILY') and immediately clarifies the counting unit, which is the key ambiguity an agent would otherwise guess wrong. It also implicitly separates itself from database_metadata and database_checksum by scoping this to the catalog listing rather than per-database detail.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Tells the agent how the output feeds the sibling database tools ('pass `versions[].id`, never the `base`') and warns not to cache the result ('Ask again rather than holding on to this'). No explicit when-not-to-use or named alternative tool, but the routing guidance is clear and actionable.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

list_downloadsList recent download attemptsA
Read-only

This organization's own recent download attempts, newest first, REFUSALS INCLUDED - a denial carries the outcome and http_status that answer "it stopped working", which nothing else here can. Use it to explain a failing fetch, to confirm a transfer ran, or to check whether a request was theirs. This is a bounded WINDOW of at most 200 rows, so a database missing from the answer means it is not in this window - never that it was never downloaded.

ParametersJSON Schema
NameRequiredDescriptionDefault
limitNoHow many attempts to return, newest first. At most 200; the API defaults to 50.

Output Schema

ParametersJSON Schema
NameRequiredDescription
downloadsYes

TDQS

A4.4/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations only establish read-only/idempotency, while the description adds genuinely new behavior: refusals are included, a denial carries outcome and http_status, results are newest-first, and the result set is a bounded 200-row window with a stated implication for absent records. That window caveat is the key behavioral disclosure an agent would otherwise get wrong.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three sentences, all load-bearing: capability with refusals, use cases, then the window caveat. It is front-loaded and well organized, though the ALL-CAPS emphasis and dense compound sentences make it slightly harder to scan than a strictly minimal version.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With an output schema present and annotations covering the safety profile, the description fills the remaining gaps an agent needs: what is included (refusals), ordering, window bound, how to interpret absences, and when to reach for it. Nothing material is missing for correct invocation.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100% and the single limit parameter's description already states 'newest first, at most 200, API defaults to 50'. The description's ordering and window notes largely restate that, so it adds only marginal meaning beyond the schema; baseline 3 for high coverage applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb-scope-resource: this organization's own recent download attempts, newest first, refusals included. It also implicitly distinguishes itself from siblings by asserting it answers the 'it stopped working' question that 'nothing else here can', none of which list download attempts.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives three concrete trigger scenarios (explain a failing fetch, confirm a transfer ran, check whether a request was theirs) and an explicit interpretation rule that a missing database means it is outside the 200-row window, not never downloaded. It stops short of naming an alternative tool or a when-not-to-use condition, so it is clear context without exclusions.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

lookup_ipLook up an IP addressA
Read-onlyIdempotent

Classify a single IPv4 or IPv6 address: whether it belongs to a VPN, a residential, datacenter or mobile proxy, a Tor node, a public relay, a hosting provider or a CDN, with the provider name where we have one. Private and reserved addresses are answered locally and cost nothing. Always read coverage.note before concluding anything from a field that is not in the result.

ParametersJSON Schema
NameRequiredDescriptionDefault
ipYesThe IPv4 or IPv6 address to classify.

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYesThe union of every plan's answer. Only `ip` and `is_vpn` are guaranteed; each remaining field is present when your plan includes it, and absent otherwise. A client that must work across plans should treat an absent flag as unknown rather than as `false`.
coverageYesWhich datasets this result covered. Read `note` before interpreting any absent field.

TDQS

A4.3/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already cover the safety profile (readOnlyHint, idempotentHint, openWorldHint), so the bar is lower, and the description adds real value: local free resolution for private/reserved ranges and the instruction to read `coverage.note` before drawing conclusions from absent fields. That warns the agent about silent result gaps, which the annotations do not convey. It does not mention auth/entitlement requirements, which the my_entitlement sibling hints may exist.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three sentences, front-loaded with the core purpose, then cost behavior, then the caveat. Every sentence carries new information and none is padding.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, so return-shape documentation is not needed, and the description still flags the one output subtlety that matters (`coverage.note`). For a single-parameter read-only lookup, it gives the agent everything required to call it correctly and interpret partial results.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

One parameter with 100% schema description coverage, so the schema already documents `ip` fully; baseline 3 applies. The description adds only the 'single IPv4 or IPv6' framing, which mildly constrains accepted input but adds no format, syntax, or validation detail beyond the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb ('Classify') and resource ('a single IPv4 or IPv6 address') and enumerates the exact classification categories returned (VPN, residential/datacenter/mobile proxy, Tor, public relay, hosting, CDN). The word 'single' plus the plural sibling lookup_ips makes the scope distinction obvious without opening either schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

'Classify a single ... address' clearly frames the use case and implicitly routes bulk work to the sibling lookup_ips. It also tells the agent when not to expect a lookup at all: private and reserved addresses are answered locally and cost nothing. It stops short of an explicit 'use lookup_ips for multiple addresses' statement, so no exclusions are spelled out.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

lookup_ipsLook up several IP addressesA
Read-onlyIdempotent

Classify a list of addresses in one call, returning a map keyed by address so duplicates collapse and the order you passed them stops mattering. Pass the whole list rather than splitting it yourself: a long one is batched for you. Prefer this over repeated lookup_ip calls when you already have the list, for example when triaging a log file. An address that fails carries its error in place of a result rather than failing the batch. Always read coverage.note before concluding anything from a field that is not in the result.

ParametersJSON Schema
NameRequiredDescriptionDefault
ipsYesThe IPv4 or IPv6 addresses to classify.

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultsYesKeyed by address. A value is either a result or an error.
coverageYesWhich datasets this result covered. Read `note` before interpreting any absent field.

TDQS

A4.9/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Goes well beyond the annotations (readOnly/idempotent/openWorld) by disclosing batch-splitting behavior, duplicate collapsing, order-independence, and — most valuably — partial-failure semantics where a failing address carries its error in place rather than failing the whole batch. The coverage.note caveat is unusual but genuinely actionable behavioral context.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Four dense sentences, each earning its place: the return shape leads, the batching guidance follows, the sibling routing is named, and the failure/coverage caveats close. No filler or restatement of the title.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With an output schema present, return structure needn't be explained, yet the description still supplies the two facts an agent most needs for correct interpretation: errors are inlined per address, and coverage.note must be read before drawing conclusions from absent fields. Nothing material is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Only one parameter and the schema already documents it at 100% coverage, so the baseline is 3. The description adds real semantic value by clarifying that the entire list should be passed in one call rather than pre-split, which shapes how the agent populates the array.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Specific verb (classify) plus resource (a list of IP addresses) with scope made concrete: returns a map keyed by address with duplicates collapsed and order irrelevant. It also explicitly distinguishes itself from sibling lookup_ip, so an agent can route correctly without opening either schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly names the alternative ('Prefer this over repeated lookup_ip calls when you already have the list') and gives a concrete triggering scenario (triaging a log file). The 'pass the whole list rather than splitting it yourself' instruction further pins down when this tool is the right call.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

my_entitlementPlan and usage for this keyA
Read-only

What this API key is entitled to and how much of it has been used: the plan, the field tier that decides how much of a lookup answer comes back, the requests counted so far, the allowance, and when it resets. Use it to explain why a field is missing from a lookup, or before a large batch. Usage counts against the anniversary of the subscription rather than the calendar month, and can lag a few seconds behind. A null hard_limit means we never stop serving - it is NOT a limit of zero.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

Output Schema

ParametersJSON Schema
NameRequiredDescription
entitlementYes

TDQS

A4.6/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Beyond the readOnlyHint annotations, it discloses non-obvious behavior: usage counts against the subscription anniversary rather than the calendar month, counts can lag a few seconds, and a null hard_limit means unlimited serving rather than zero. That last point actively prevents a plausible misreading, which is exactly the value annotations cannot supply.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The definition is front-loaded with the return-field inventory and then moves to usage and caveats without filler. It is information-dense and slightly long, but every clause carries a distinct fact rather than restating the title.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, so return-format documentation is not required, yet the description usefully frames what the returned fields mean and how to interpret anomalies. Combined with the lag and anniversary caveats, an agent has everything needed to call and interpret this tool correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

There are zero parameters, so the baseline is 4. The description correctly implies a no-argument call, but adds no parameter semantics because there are none to add.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description names a specific resource (this API key's entitlement) and enumerates exactly what comes back: plan, field tier, request count, allowance, and reset timing. That is unambiguous against siblings like lookup_ip and database_metadata, which are resource lookups rather than account-state reads.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It gives two concrete triggering contexts ('explain why a field is missing from a lookup' and 'before a large batch'), which is clear when-to-use guidance. It does not name an alternative tool or an explicit when-not-to-use condition, so it stops short of a 5.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 7 tool updatesv5.3.4
    • Changeddatabase_checksum5 fields changed
      • changedInput schema / properties / dataset_id / description
        Previous value: -"The dataset id, as returned by `list_databases`."New value: +"A VERSIONED database id, from `versions[].id` in `list_databases` - `cdn_ip_v1`, not `cdn_ip`. The unversioned base id is a license reference and is not accepted here."
      • removedOutput schema / properties / checksums / additionalProperties
        Removed value: -true
      • addedOutput schema / properties / checksums / description
        Added value: +"The published digests for one database file."
      • addedOutput schema / properties / checksums / properties
        Added value: +{
        +  "md5": {
        +    "type": "string"
        +  },
        +  "sha1": {
        +    "type": "string"
        +  },
        +  "sha256": {
        +    "type": "string"
        +  },
        +  "sha512": {
        +    "type": "string"
        +  }
        +}
      • addedOutput schema / properties / checksums / required
        Added value: +[
        +  "md5",
        +  "sha1",
        +  "sha256",
        +  "sha512"
        +]
    • Changeddatabase_metadata3 fields changed
      • changedInput schema / properties / dataset_id / description
        Previous value: -"The dataset id, as returned by `list_databases`."New value: +"A VERSIONED database id, from `versions[].id` in `list_databases` - `cdn_ip_v1`, not `cdn_ip`. The unversioned base id is a license reference and is not accepted here."
      • addedOutput schema / properties / sample_size / additionalProperties / format
        Added value: +"int64"
      • addedOutput schema / properties / size / additionalProperties / format
        Added value: +"int64"
    • Changedlist_databases3 fields changed
      • addedOutput schema / properties / databases
        Added value: +{
        +  "items": {
        +    "description": "One database FAMILY, with your organization's license beside it. A license covers\nthe family, while a download names a specific version, so the ids you\npass to the download and checksum endpoints come from `versions`.\n",
        +    "properties": {
        +      "base": {
        +        "description": "The database family, e.g. `vpn_ip`. What the license is held against.",
        +        "type": "string"
        +      },
        +      "expires": {
        +        "description": "A hard stop. Null when the license has no end date, which is the normal case for a rolling agreement, and when there is no license. A rolling license reports its turnover date in renews_at instead.",
        +        "format": "date-time",
        +        "type": [
        +          "string",
        +          "null"
        +        ]
        +      },
        +      "in_term": {
        +        "description": "False when the license has lapsed; downloads are refused.",
        +        "type": "boolean"
        +      },
        +      "license_type": {
        +        "description": "What a license permits you to do with the data. Null for a family\nyou hold no license for, which is every one with standing\n`unlicensed`.\n",
        +        "enum": [
        +          "evaluation",
        +          "standard",
        +          "redistribute",
        +          null
        +        ],
        +        "type": [
        +          "string",
        +          "null"
        +        ]
        +      },
        +      "name": {
        +        "type": "string"
        +      },
        +      "notice_due_at": {
        +        "description": "The last day notice of non-renewal can be given for the term ending at renews_at. Null whenever renews_at is, and when the agreement records no notice period.",
        +        "format": "date-time",
        +        "type": [
        +          "string",
        +          "null"
        +        ]
        +      },
        +      "renews_at": {
        +        "description": "When a rolling license next renews. Null when the license has no defined term, when expires sets a hard stop instead, and when there is no license.",
        +        "format": "date-time",
        +        "type": [
        +          "string",
        +          "null"
        +        ]
        +      },
        +      "standing": {
        +        "description": "Where your license for a database family stands today. `licensed` is a\nlive grant, `expired` one whose term has ended, and `unlicensed` a\ndatabase published but never bought.\n",
        +        "enum": [
        +          "expired",
        +          "licensed",
        +          "unlicensed"
        +        ],
        +        "type": "string"
        +      },
        +      "starts": {
        +        "format": "date-time",
        +        "type": [
        +          "string",
        +          "null"
        +        ]
        +      },
        +      "summary": {
        +        "type": "string"
        +      },
        +      "versions": {
        +        "description": "Every published version of this family. The `id` here is what the\ndownload and checksum endpoints take.\n",
        +        "items": {
        +          "properties": {
        +            "formats": {
        +              "items": {
        +                "properties": {
        +                  "bytes": {
        +                    "description": "Size of the published file, or null when it has not been published\nyet. int64 because it is not hypothetical: resproxy_ip_14d's MMDB is\n4.58 GB, so a 32-bit field cannot carry the catalogue and `list`\nthrows for every caller rather than for that one entry.\n",
        +                    "format": "int64",
        +                    "type": [
        +                      "integer",
        +                      "null"
        +                    ]
        +                  },
        +                  "format": {
        +                    "description": "A file format a database version is published in.",
        +                    "enum": [
        +                      "csvgz",
        +                      "mmdb"
        +                    ],
        +                    "type": "string"
        +                  }
        +                },
        +                "required": [
        +                  "format",
        +                  "bytes"
        +                ],
        +                "type": "object"
        +              },
        +              "type": "array"
        +            },
        +            "id": {
        +              "description": "The versioned database id, e.g. `vpn_ip_v1`. Pass this to download.",
        +              "type": "string"
        +            },
        +            "sample_formats": {
        +              "description": "The formats an evaluation sample is published in, if any.",
        +              "items": {
        +                "description": "A file format a database version is published in.",
        +                "enum": [
        +                  "csvgz",
        +                  "mmdb"
        +                ],
        +                "type": "string"
        +              },
        +              "type": "array"
        +            },
        +            "summary": {
        +              "type": "string"
        +            },
        +            "version": {
        +              "type": "integer"
        +            }
        +          },
        +          "required": [
        +            "id",
        +            "version",
        +            "formats"
        +          ],
        +          "type": "object"
        +        },
        +        "type": "array"
        +      }
        +    },
        +    "required": [
        +      "base",
        +      "name",
        +      "summary",
        +      "license_type",
        +      "starts",
        +      "expires",
        +      "renews_at",
        +      "notice_due_at",
        +      "in_term",
        +      "standing",
        +      "versions"
        +    ],
        +    "type": "object"
        +  },
        +  "type": "array"
        +}
      • removedOutput schema / properties / datasets
        Removed value: -{
        -  "items": {
        -    "description": "One dataset FAMILY your organization is licensed for. A license covers\nthe family, while a download names a specific version, so the ids you\npass to the download and checksum endpoints come from `versions`.\n",
        -    "properties": {
        -      "base": {
        -        "description": "The dataset family, e.g. `vpn_ip`. What the license is held against.",
        -        "type": "string"
        -      },
        -      "expires": {
        -        "description": "A hard stop. Null when the license has no end date, which is the normal case for a rolling agreement, and when there is no license. A rolling license reports its turnover date in renews_at instead.",
        -        "format": "date-time",
        -        "nullable": true,
        -        "type": "string"
        -      },
        -      "in_term": {
        -        "description": "False when the license has lapsed; downloads are refused.",
        -        "type": "boolean"
        -      },
        -      "license_type": {
        -        "description": "What your license permits you to do with the data.",
        -        "enum": [
        -          "evaluation",
        -          "standard",
        -          "redistribute"
        -        ],
        -        "type": "string"
        -      },
        -      "name": {
        -        "type": "string"
        -      },
        -      "notice_due_at": {
        -        "description": "The last day notice of non-renewal can be given for the term ending at renews_at. Null whenever renews_at is, and when the agreement records no notice period.",
        -        "format": "date-time",
        -        "nullable": true,
        -        "type": "string"
        -      },
        -      "renews_at": {
        -        "description": "When a rolling license next renews. Null when the license has no defined term, when expires sets a hard stop instead, and when there is no license.",
        -        "format": "date-time",
        -        "nullable": true,
        -        "type": "string"
        -      },
        -      "standing": {
        -        "description": "`licensed` is a live grant, `expired` one whose term has ended, and\n`unlicensed` a dataset published but never bought.\n",
        -        "enum": [
        -          "expired",
        -          "licensed",
        -          "unlicensed"
        -        ],
        -        "type": "string"
        -      },
        -      "starts": {
        -        "format": "date-time",
        -        "nullable": true,
        -        "type": "string"
        -      },
        -      "summary": {
        -        "type": "string"
        -      },
        -      "versions": {
        -        "description": "Every published version of this family. The `id` here is what the\ndownload and checksum endpoints take.\n",
        -        "items": {
        -          "properties": {
        -            "formats": {
        -              "items": {
        -                "properties": {
        -                  "bytes": {
        -                    "description": "Size of the published file, or null when it has not been published yet",
        -                    "nullable": true,
        -                    "type": "integer"
        -                  },
        -                  "format": {
        -                    "enum": [
        -                      "csvgz",
        -                      "mmdb"
        -                    ],
        -                    "type": "string"
        -                  }
        -                },
        -                "required": [
        -                  "format",
        -                  "bytes"
        -                ],
        -                "type": "object"
        -              },
        -              "type": "array"
        -            },
        -            "id": {
        -              "description": "The versioned dataset id, e.g. `vpn_ip_v1`. Pass this to download.",
        -              "type": "string"
        -            },
        -            "sampleFormats": {
        -              "description": "The formats an evaluation sample is published in, if any.",
        -              "items": {
        -                "enum": [
        -                  "csvgz",
        -                  "mmdb"
        -                ],
        -                "type": "string"
        -              },
        -              "type": "array"
        -            },
        -            "summary": {
        -              "type": "string"
        -            },
        -            "version": {
        -              "type": "integer"
        -            }
        -          },
        -          "required": [
        -            "id",
        -            "version",
        -            "formats"
        -          ],
        -          "type": "object"
        -        },
        -        "type": "array"
        -      }
        -    },
        -    "required": [
        -      "base",
        -      "name",
        -      "summary",
        -      "license_type",
        -      "starts",
        -      "expires",
        -      "renews_at",
        -      "notice_due_at",
        -      "in_term",
        -      "standing",
        -      "versions"
        -    ],
        -    "type": "object"
        -  },
        -  "type": "array"
        -}
      • changedOutput schema / required
        Previous value: -[
        -  "datasets"
        -]New value: +[
        +  "databases"
        +]
    • Addedlist_downloads
    • Changedlookup_ip1 field changed
      • addedOutput schema / properties / result / properties / is_bogon
        Added value: +{
        +  "description": "Present, and true, only for a private, reserved or otherwise non-routable address. Such an answer is given locally without calling the API, so every other flag in it is false by definition rather than by lookup. Absent from every served answer.",
        +  "type": "boolean"
        +}
    • Changedlookup_ips3 fields changed
      • changedInput schema / properties / ips / description
        Previous value: -"The addresses to classify, at most 100."New value: +"The IPv4 or IPv6 addresses to classify."
      • removedInput schema / properties / ips / maxItems
        Removed value: -100
      • changedOutput schema / properties / results / additionalProperties
        Previous value: -trueNew value: +{
        +  "anyOf": [
        +    {
        +      "description": "The union of every plan's answer. Only `ip` and `is_vpn` are guaranteed;\neach remaining field is present when your plan includes it, and absent\notherwise. A client that must work across plans should treat an absent\nflag as unknown rather than as `false`.\n",
        +      "properties": {
        +        "cdn": {
        +          "description": "Detail for `is_cdn`. Empty when false. Scale and above.",
        +          "properties": {
        +            "confidence": {
        +              "description": "How strongly the classification is supported.",
        +              "type": "string"
        +            },
        +            "last_seen": {
        +              "description": "The most recent date this address was observed in this dataset.",
        +              "format": "date",
        +              "type": "string"
        +            },
        +            "provider": {
        +              "description": "The provider, or an empty string where the dataset has none.",
        +              "type": "string"
        +            }
        +          },
        +          "type": "object"
        +        },
        +        "dcproxy": {
        +          "description": "Detail for `is_dcproxy`. Empty when false. Max only.",
        +          "properties": {
        +            "first_seen": {
        +              "description": "The earliest date within the window this address was seen in the pool.",
        +              "format": "date",
        +              "type": "string"
        +            },
        +            "hits": {
        +              "description": "How many times the address was observed in the pool during the window.",
        +              "type": "integer"
        +            },
        +            "hits_days_pct": {
        +              "description": "The share of days in the window on which the address was seen, as a\npercentage. A high value means a stable pool member rather than a\none-off sighting.\n",
        +              "type": "integer"
        +            },
        +            "last_seen": {
        +              "description": "The most recent date within the window this address was seen in the pool.",
        +              "format": "date",
        +              "type": "string"
        +            },
        +            "provider": {
        +              "description": "The proxy network, or an empty string where unattributed.",
        +              "type": "string"
        +            },
        +            "providers_num": {
        +              "description": "How many distinct proxy networks this address was seen in.",
        +              "type": "integer"
        +            }
        +          },
        +          "type": "object"
        +        },
        +        "hosting": {
        +          "description": "Detail for `is_hosting`. Empty when false. Scale and above.",
        +          "properties": {
        +            "confidence": {
        +              "description": "How strongly the classification is supported.",
        +              "type": "string"
        +            },
        +            "last_seen": {
        +              "description": "The most recent date this address was observed in this dataset.",
        +              "format": "date",
        +              "type": "string"
        +            },
        +            "provider": {
        +              "description": "The provider, or an empty string where the dataset has none.",
        +              "type": "string"
        +            }
        +          },
        +          "type": "object"
        +        },
        +        "ip": {
        +          "description": "The address that was looked up, normalized.",
        +          "type": "string"
        +        },
        +        "is_bogon": {
        +          "description": "Present, and true, only for a private, reserved or otherwise non-routable address. Such an answer is given locally without calling the API, so every other flag in it is false by definition rather than by lookup. Absent from every served answer.",
        +          "type": "boolean"
        +        },
        +        "is_cdn": {
        +          "description": "Whether the address belongs to a CDN. Starter and above.",
        +          "type": "boolean"
        +        },
        +        "is_dcproxy": {
        +          "description": "Whether the address was seen in a datacenter proxy pool. Scale and above.",
        +          "type": "boolean"
        +        },
        +        "is_hosting": {
        +          "description": "Whether the address belongs to a hosting or cloud provider. Starter and above.",
        +          "type": "boolean"
        +        },
        +        "is_mobproxy": {
        +          "description": "Whether the address was seen in a mobile proxy pool. Scale and above.",
        +          "type": "boolean"
        +        },
        +        "is_relay": {
        +          "description": "Whether the address is a privacy relay egress. Starter and above.",
        +          "type": "boolean"
        +        },
        +        "is_resproxy": {
        +          "description": "Whether the address was seen in a residential proxy pool. Scale and above.",
        +          "type": "boolean"
        +        },
        +        "is_tor": {
        +          "description": "Whether the address is a Tor node. Starter and above.",
        +          "type": "boolean"
        +        },
        +        "is_vpn": {
        +          "description": "Whether the address is VPN infrastructure. Keys off presence in the\nVPN dataset, so an unattributed range with no provider is still\n`true`.\n",
        +          "type": "boolean"
        +        },
        +        "mobproxy": {
        +          "description": "Detail for `is_mobproxy`. Empty when false. Max only.",
        +          "properties": {
        +            "first_seen": {
        +              "description": "The earliest date within the window this address was seen in the pool.",
        +              "format": "date",
        +              "type": "string"
        +            },
        +            "hits": {
        +              "description": "How many times the address was observed in the pool during the window.",
        +              "type": "integer"
        +            },
        +            "hits_days_pct": {
        +              "description": "The share of days in the window on which the address was seen, as a\npercentage. A high value means a stable pool member rather than a\none-off sighting.\n",
        +              "type": "integer"
        +            },
        +            "last_seen": {
        +              "description": "The most recent date within the window this address was seen in the pool.",
        +              "format": "date",
        +              "type": "string"
        +            },
        +            "provider": {
        +              "description": "The proxy network, or an empty string where unattributed.",
        +              "type": "string"
        +            },
        +            "providers_num": {
        +              "description": "How many distinct proxy networks this address was seen in.",
        +              "type": "integer"
        +            }
        +          },
        +          "type": "object"
        +        },
        +        "relay": {
        +          "description": "Detail for `is_relay`. Empty when false. Scale and above.",
        +          "properties": {
        +            "confidence": {
        +              "description": "How strongly the classification is supported.",
        +              "type": "string"
        +            },
        +            "last_seen": {
        +              "description": "The most recent date this address was observed in this dataset.",
        +              "format": "date",
        +              "type": "string"
        +            },
        +            "provider": {
        +              "description": "The provider, or an empty string where the dataset has none.",
        +              "type": "string"
        +            }
        +          },
        +          "type": "object"
        +        },
        +        "resproxy": {
        +          "description": "Detail for `is_resproxy`. Empty when false. Max only.",
        +          "properties": {
        +            "first_seen": {
        +              "description": "The earliest date within the window this address was seen in the pool.",
        +              "format": "date",
        +              "type": "string"
        +            },
        +            "hits": {
        +              "description": "How many times the address was observed in the pool during the window.",
        +              "type": "integer"
        +            },
        +            "hits_days_pct": {
        +              "description": "The share of days in the window on which the address was seen, as a\npercentage. A high value means a stable pool member rather than a\none-off sighting.\n",
        +              "type": "integer"
        +            },
        +            "last_seen": {
        +              "description": "The most recent date within the window this address was seen in the pool.",
        +              "format": "date",
        +              "type": "string"
        +            },
        +            "provider": {
        +              "description": "The proxy network, or an empty string where unattributed.",
        +              "type": "string"
        +            },
        +            "providers_num": {
        +              "description": "How many distinct proxy networks this address was seen in.",
        +              "type": "integer"
        +            }
        +          },
        +          "type": "object"
        +        },
        +        "tor": {
        +          "description": "Detail for `is_tor`. Empty when false. Scale and above. The tor\ndataset carries no provider, so `provider` is always an empty string.\n",
        +          "properties": {
        +            "confidence": {
        +              "description": "How strongly the classification is supported.",
        +              "type": "string"
        +            },
        +            "last_seen": {
        +              "description": "The most recent date this address was observed in this dataset.",
        +              "format": "date",
        +              "type": "string"
        +            },
        +            "provider": {
        +              "description": "The provider, or an empty string where the dataset has none.",
        +              "type": "string"
        +            }
        +          },
        +          "type": "object"
        +        },
        +        "vpn": {
        +          "description": "Detail for `is_vpn`. Empty when `is_vpn` is false. Starter and above.\n",
        +          "properties": {
        +            "confidence": {
        +              "description": "How strongly the attribution is supported. Max only.",
        +              "type": "string"
        +            },
        +            "last_seen": {
        +              "description": "The most recent date this address was observed as VPN infrastructure.",
        +              "format": "date",
        +              "type": "string"
        +            },
        +            "method": {
        +              "description": "The class of evidence the attribution rests on, one of four values.\nMax only.\n\n`scan` - we spoke the VPN protocol to the address ourselves and got a\nvalid server response. `scrape` - the operator published the address\nthrough its own API, client or configuration. `registry` - public\nregistration or naming records attribute it to the operator.\n`infer` - the address was extrapolated from confirmed neighbours in\nthe same block.\n",
        +              "type": "string"
        +            },
        +            "provider": {
        +              "description": "The VPN provider, or an empty string for an unattributed range.",
        +              "type": "string"
        +            }
        +          },
        +          "type": "object"
        +        }
        +      },
        +      "required": [
        +        "ip",
        +        "is_vpn"
        +      ],
        +      "type": "object"
        +    },
        +    {
        +      "properties": {
        +        "error": {
        +          "properties": {
        +            "kind": {
        +              "description": "How the client SDK classified the failure, such as `bad_request` for a string that is not an IP address.",
        +              "type": "string"
        +            },
        +            "message": {
        +              "type": "string"
        +            }
        +          },
        +          "required": [
        +            "kind",
        +            "message"
        +          ],
        +          "type": "object"
        +        }
        +      },
        +      "required": [
        +        "error"
        +      ],
        +      "type": "object"
        +    }
        +  ]
        +}
    • Addedmy_entitlement
  2. 5 tool updatesv1.0.0
    • First observeddatabase_checksum
    • First observeddatabase_metadata
    • First observedlist_databases
    • First observedlookup_ip
    • First observedlookup_ips

TDQS

A4.2/5.0

Scored across 7 tools

Disambiguation5/5

Each tool targets a clearly distinct action or resource. lookup_ip vs lookup_ips is the only near-pair, but the descriptions explicitly frame one as single-address and the other as batch with dedup/error handling, so selection is unambiguous.

Naming Consistency3/5

All names are snake_case and readable, but the convention is mixed: lookup_ip, lookup_ips, list_databases, list_downloads are verb-first while database_metadata, database_checksum, and my_entitlement are noun-first.

Tool Count5/5

Seven tools is well-scoped for IP classification plus licensed-database introspection, with no redundant or filler tools.

Completeness3/5

Covers single/batch lookup, entitlement, catalog, metadata, checksum, and download history, but there is no tool to actually initiate a database download even though checksum and download-history tools reference downloads—a notable dead end in the lifecycle.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    D
    maintenance
    Provides AI agents with 22 data tools across 9 domains, including IP geolocation, email risk scoring, postal codes, countries, timezones, user-agent parsing, cryptographic hashing, Bible search, and QR code generation.
    MIT
  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables checking any IP address for VPN, proxy, Tor, datacenter hosting, anonymity, and fraud risk signals, plus API service status, directly from MCP-capable clients like Claude and Cursor.
    503 npm
    2
    MIT
  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables IP address lookups and risk assessments for IPv4/IPv6, providing geolocation, ISP/ASN, and security flags such as VPN, proxy, Tor, datacenter, and mobile with a risk score.
    184 npm
    MIT