vpndetection-mcp 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +90 -0
- package/dist/coverage.d.ts +68 -0
- package/dist/coverage.js +85 -0
- package/dist/coverage.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/schema.gen.d.ts +12 -0
- package/dist/schema.gen.js +492 -0
- package/dist/schema.gen.js.map +1 -0
- package/dist/stdio.d.ts +2 -0
- package/dist/stdio.js +29 -0
- package/dist/stdio.js.map +1 -0
- package/dist/tools.d.ts +28 -0
- package/dist/tools.js +258 -0
- package/dist/tools.js.map +1 -0
- package/package.json +67 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Mslm Dev
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# [<img src="https://s3.vpndetection.io/vpndetection-public/brand/mark.svg" alt="VPNDetection" width="24"/>](https://vpndetection.io/) VPNDetection MCP Server
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/vpndetection-mcp)
|
|
4
|
+
[](LICENSE)
|
|
5
|
+
|
|
6
|
+
The official [Model Context Protocol](https://modelcontextprotocol.io) server for the [VPNDetection](https://vpndetection.io) API.
|
|
7
|
+
|
|
8
|
+
It gives an AI agent five 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 catalogue of datasets your organisation is licensed to download.
|
|
9
|
+
|
|
10
|
+
## Getting Started
|
|
11
|
+
|
|
12
|
+
**No API key needed to start.** The free tier answers `ip` and `is_vpn`, and allows 1000 requests per day per source address.
|
|
13
|
+
|
|
14
|
+
Add this to your MCP client's config:
|
|
15
|
+
|
|
16
|
+
```json
|
|
17
|
+
{
|
|
18
|
+
"mcpServers": {
|
|
19
|
+
"vpndetection": {
|
|
20
|
+
"command": "npx",
|
|
21
|
+
"args": ["-y", "vpndetection-mcp"]
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Then ask it something like *"is 45.83.91.1 a VPN?"*.
|
|
28
|
+
|
|
29
|
+
Requires Node.js 22 or newer.
|
|
30
|
+
|
|
31
|
+
### With an API key
|
|
32
|
+
|
|
33
|
+
A key unlocks the provider name, the classification datasets and the proxy families. Put it in the environment:
|
|
34
|
+
|
|
35
|
+
```json
|
|
36
|
+
{
|
|
37
|
+
"mcpServers": {
|
|
38
|
+
"vpndetection": {
|
|
39
|
+
"command": "npx",
|
|
40
|
+
"args": ["-y", "vpndetection-mcp"],
|
|
41
|
+
"env": { "VPNDETECTION_API_KEY": "your-key" }
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
`VPNDETECTION_BASE_URL` overrides the endpoint if you need to point somewhere else.
|
|
48
|
+
|
|
49
|
+
## Tools
|
|
50
|
+
|
|
51
|
+
| Tool | What it answers |
|
|
52
|
+
|---|---|
|
|
53
|
+
| `lookup_ip` | Classify one address. |
|
|
54
|
+
| `lookup_ips` | Classify up to 100 addresses in one call, keyed by address. |
|
|
55
|
+
| `list_databases` | The datasets your organisation is licensed for. |
|
|
56
|
+
| `database_metadata` | A dataset's columns, sample rows, row count, build date and file sizes. |
|
|
57
|
+
| `database_checksum` | The published digests for one dataset file. |
|
|
58
|
+
|
|
59
|
+
Every tool is read-only. There is deliberately no download tool: the datasets run to several GB, which is not something an agent should pull into a conversation. Fetch them with the [client libraries](https://github.com/vpndetection-io) or the API instead.
|
|
60
|
+
|
|
61
|
+
## Reading a result
|
|
62
|
+
|
|
63
|
+
Each lookup comes back with a `coverage` block beside it:
|
|
64
|
+
|
|
65
|
+
```json
|
|
66
|
+
{
|
|
67
|
+
"result": { "ip": "45.83.91.1", "is_vpn": true },
|
|
68
|
+
"coverage": {
|
|
69
|
+
"included": ["ip", "is_vpn"],
|
|
70
|
+
"not_included": ["is_hosting", "is_tor", "hosting", "tor", "..."],
|
|
71
|
+
"note": "The fields in not_included were not returned, because this API key's plan does not include them. ..."
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
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.
|
|
77
|
+
|
|
78
|
+
## Other Libraries
|
|
79
|
+
|
|
80
|
+
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.
|
|
81
|
+
|
|
82
|
+
## About VPNDetection
|
|
83
|
+
|
|
84
|
+
VPN Detection API: Accurate anonymity detection identifying VPNs, residential proxies, hosting servers, Tor nodes, CDNs, relays and more.
|
|
85
|
+
|
|
86
|
+
[<img src="https://s3.vpndetection.io/vpndetection-public/brand/mark.svg" alt="VPNDetection" width="96"/>](https://vpndetection.io/)
|
|
87
|
+
|
|
88
|
+
## License
|
|
89
|
+
|
|
90
|
+
This project is licensed under the [MIT License](LICENSE).
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { Result } from 'vpndetection';
|
|
2
|
+
/**
|
|
3
|
+
* What a lookup result did and did not cover.
|
|
4
|
+
*
|
|
5
|
+
* The API's contract is that a missing member means "not in this plan", never
|
|
6
|
+
* "we checked and found nothing". A model reading a served body cannot tell the
|
|
7
|
+
* two apart and will report an absent `is_tor` as a negative finding, so every
|
|
8
|
+
* result carries this alongside it.
|
|
9
|
+
*/
|
|
10
|
+
export interface Coverage {
|
|
11
|
+
/** Members the API returned, so they were actually checked. */
|
|
12
|
+
included: string[];
|
|
13
|
+
/** Members this plan does not buy. Absent, not false. */
|
|
14
|
+
not_included: string[];
|
|
15
|
+
note: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* The answer in the names the API publishes, which is what a model has read the
|
|
19
|
+
* docs in and what this server's `outputSchema` declares.
|
|
20
|
+
*
|
|
21
|
+
* A served answer already carries it as `raw`. A bogon does not: the SDK answers
|
|
22
|
+
* those locally onto its own camelCase members and leaves `raw` empty. Rebuilding
|
|
23
|
+
* it from the spec's own property types avoids a second copy of the wire names
|
|
24
|
+
* here, and lands on the documented bogon shape by construction - every flag
|
|
25
|
+
* present and false, every detail object present and empty.
|
|
26
|
+
*/
|
|
27
|
+
export declare function wireBody(result: Result): Record<string, unknown>;
|
|
28
|
+
/**
|
|
29
|
+
* Derives coverage from the result body alone.
|
|
30
|
+
*
|
|
31
|
+
* Deliberately no tier table: the members are read from the pinned spec and the
|
|
32
|
+
* absent ones are whatever the API left out, so a dataset added to `ip_api`
|
|
33
|
+
* starts appearing here as soon as `scripts/download-spec.sh` is re-run. The
|
|
34
|
+
* caller's tier is never named, because the plan's identity is not the useful
|
|
35
|
+
* fact - what it failed to check is.
|
|
36
|
+
*/
|
|
37
|
+
export declare function coverageOf(body: Record<string, unknown>): Coverage;
|
|
38
|
+
/**
|
|
39
|
+
* One coverage for a whole batch, taken from the first served answer.
|
|
40
|
+
*
|
|
41
|
+
* A bogon is synthesized locally in the full shape, so reading coverage off one
|
|
42
|
+
* would report every dataset as covered whatever the plan actually buys. Errors
|
|
43
|
+
* carry no shape at all. With neither available there is nothing honest to say,
|
|
44
|
+
* which is what `NOTE_UNKNOWN` says.
|
|
45
|
+
*/
|
|
46
|
+
export declare function batchCoverage(bodies: Record<string, unknown>[]): Coverage;
|
|
47
|
+
export declare const COVERAGE_SCHEMA: {
|
|
48
|
+
readonly type: "object";
|
|
49
|
+
readonly description: "Which datasets this result covered. Read `note` before interpreting any absent field.";
|
|
50
|
+
readonly properties: {
|
|
51
|
+
readonly included: {
|
|
52
|
+
readonly type: "array";
|
|
53
|
+
readonly items: {
|
|
54
|
+
readonly type: "string";
|
|
55
|
+
};
|
|
56
|
+
};
|
|
57
|
+
readonly not_included: {
|
|
58
|
+
readonly type: "array";
|
|
59
|
+
readonly items: {
|
|
60
|
+
readonly type: "string";
|
|
61
|
+
};
|
|
62
|
+
};
|
|
63
|
+
readonly note: {
|
|
64
|
+
readonly type: "string";
|
|
65
|
+
};
|
|
66
|
+
};
|
|
67
|
+
readonly required: readonly ["included", "not_included", "note"];
|
|
68
|
+
};
|
package/dist/coverage.js
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { LOOKUP_GATED_MEMBERS, LOOKUP_MEMBERS, LOOKUP_RESULT_SCHEMA } from './schema.gen.js';
|
|
2
|
+
const NOTE_PARTIAL = 'The fields in not_included were not returned, because this API key\'s plan does '
|
|
3
|
+
+ 'not include them. Their absence is not a negative result: the address was never checked '
|
|
4
|
+
+ 'against those datasets. Do not state or imply that the address is not hosting, a relay, Tor, '
|
|
5
|
+
+ 'a CDN or a proxy on the basis of a field missing from this result.';
|
|
6
|
+
const NOTE_FULL = 'This plan covers every dataset the API serves, so every field was checked. A '
|
|
7
|
+
+ 'false flag here is a real negative result.';
|
|
8
|
+
const NOTE_BOGON = 'This is a private, reserved or otherwise non-routable address. It was answered '
|
|
9
|
+
+ 'locally without calling the API, so every flag is false by definition rather than by lookup, '
|
|
10
|
+
+ 'and it cost nothing against the key\'s allowance.';
|
|
11
|
+
const NOTE_UNKNOWN = 'No served answer was available to derive coverage from, so this batch says '
|
|
12
|
+
+ 'nothing about which datasets the plan covers.';
|
|
13
|
+
/**
|
|
14
|
+
* The answer in the names the API publishes, which is what a model has read the
|
|
15
|
+
* docs in and what this server's `outputSchema` declares.
|
|
16
|
+
*
|
|
17
|
+
* A served answer already carries it as `raw`. A bogon does not: the SDK answers
|
|
18
|
+
* those locally onto its own camelCase members and leaves `raw` empty. Rebuilding
|
|
19
|
+
* it from the spec's own property types avoids a second copy of the wire names
|
|
20
|
+
* here, and lands on the documented bogon shape by construction - every flag
|
|
21
|
+
* present and false, every detail object present and empty.
|
|
22
|
+
*/
|
|
23
|
+
export function wireBody(result) {
|
|
24
|
+
if (!result.isBogon) {
|
|
25
|
+
return result.raw;
|
|
26
|
+
}
|
|
27
|
+
const out = { ip: result.ip, is_bogon: true };
|
|
28
|
+
for (const [name, schema] of Object.entries(LOOKUP_RESULT_SCHEMA.properties ?? {})) {
|
|
29
|
+
if (name === 'ip') {
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
const type = schema.type;
|
|
33
|
+
if (type === 'boolean') {
|
|
34
|
+
out[name] = false;
|
|
35
|
+
}
|
|
36
|
+
else if (type === 'object') {
|
|
37
|
+
out[name] = {};
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return out;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Derives coverage from the result body alone.
|
|
44
|
+
*
|
|
45
|
+
* Deliberately no tier table: the members are read from the pinned spec and the
|
|
46
|
+
* absent ones are whatever the API left out, so a dataset added to `ip_api`
|
|
47
|
+
* starts appearing here as soon as `scripts/download-spec.sh` is re-run. The
|
|
48
|
+
* caller's tier is never named, because the plan's identity is not the useful
|
|
49
|
+
* fact - what it failed to check is.
|
|
50
|
+
*/
|
|
51
|
+
export function coverageOf(body) {
|
|
52
|
+
const included = LOOKUP_MEMBERS.filter((m) => m in body);
|
|
53
|
+
const notIncluded = LOOKUP_GATED_MEMBERS.filter((m) => !(m in body));
|
|
54
|
+
let note = notIncluded.length === 0 ? NOTE_FULL : NOTE_PARTIAL;
|
|
55
|
+
if (body['is_bogon'] === true) {
|
|
56
|
+
note = NOTE_BOGON;
|
|
57
|
+
}
|
|
58
|
+
return { included: included, not_included: notIncluded, note: note };
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* One coverage for a whole batch, taken from the first served answer.
|
|
62
|
+
*
|
|
63
|
+
* A bogon is synthesized locally in the full shape, so reading coverage off one
|
|
64
|
+
* would report every dataset as covered whatever the plan actually buys. Errors
|
|
65
|
+
* carry no shape at all. With neither available there is nothing honest to say,
|
|
66
|
+
* which is what `NOTE_UNKNOWN` says.
|
|
67
|
+
*/
|
|
68
|
+
export function batchCoverage(bodies) {
|
|
69
|
+
const served = bodies.find((b) => b['is_bogon'] !== true);
|
|
70
|
+
if (served === undefined) {
|
|
71
|
+
return { included: [], not_included: [], note: NOTE_UNKNOWN };
|
|
72
|
+
}
|
|
73
|
+
return coverageOf(served);
|
|
74
|
+
}
|
|
75
|
+
export const COVERAGE_SCHEMA = {
|
|
76
|
+
type: 'object',
|
|
77
|
+
description: 'Which datasets this result covered. Read `note` before interpreting any absent field.',
|
|
78
|
+
properties: {
|
|
79
|
+
included: { type: 'array', items: { type: 'string' } },
|
|
80
|
+
not_included: { type: 'array', items: { type: 'string' } },
|
|
81
|
+
note: { type: 'string' },
|
|
82
|
+
},
|
|
83
|
+
required: ['included', 'not_included', 'note'],
|
|
84
|
+
};
|
|
85
|
+
//# sourceMappingURL=coverage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"coverage.js","sourceRoot":"","sources":["../src/coverage.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { createTools, registerTools, BATCH_LIMIT } from './tools.js';
|
|
2
|
+
export type { ToolContext, ToolDef } from './tools.js';
|
|
3
|
+
export { coverageOf, batchCoverage, COVERAGE_SCHEMA } from './coverage.js';
|
|
4
|
+
export type { Coverage } from './coverage.js';
|
|
5
|
+
export { LOOKUP_MEMBERS, LOOKUP_GATED_MEMBERS, SPEC_VERSION } from './schema.gen.js';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAErE,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAE3E,OAAO,EAAE,cAAc,EAAE,oBAAoB,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare const LOOKUP_MEMBERS: readonly string[];
|
|
2
|
+
export declare const LOOKUP_GATED_MEMBERS: readonly string[];
|
|
3
|
+
export declare const SPEC_VERSION = "2026.09.10";
|
|
4
|
+
export interface ObjectSchema {
|
|
5
|
+
type: 'object';
|
|
6
|
+
properties?: Record<string, object>;
|
|
7
|
+
required?: string[];
|
|
8
|
+
[k: string]: unknown;
|
|
9
|
+
}
|
|
10
|
+
export declare const LOOKUP_RESULT_SCHEMA: ObjectSchema;
|
|
11
|
+
export declare const LICENSED_DATASET_SCHEMA: ObjectSchema;
|
|
12
|
+
export declare const DATASET_METADATA_SCHEMA: ObjectSchema;
|
|
@@ -0,0 +1,492 @@
|
|
|
1
|
+
// Code generated by scripts/gen-schema.mjs from spec/openapi.json. DO NOT EDIT.
|
|
2
|
+
export const LOOKUP_MEMBERS = [
|
|
3
|
+
'ip',
|
|
4
|
+
'is_vpn',
|
|
5
|
+
'is_hosting',
|
|
6
|
+
'is_relay',
|
|
7
|
+
'is_tor',
|
|
8
|
+
'is_cdn',
|
|
9
|
+
'is_resproxy',
|
|
10
|
+
'is_dcproxy',
|
|
11
|
+
'is_mobproxy',
|
|
12
|
+
'vpn',
|
|
13
|
+
'hosting',
|
|
14
|
+
'relay',
|
|
15
|
+
'tor',
|
|
16
|
+
'cdn',
|
|
17
|
+
'resproxy',
|
|
18
|
+
'dcproxy',
|
|
19
|
+
'mobproxy',
|
|
20
|
+
];
|
|
21
|
+
export const LOOKUP_GATED_MEMBERS = [
|
|
22
|
+
'is_hosting',
|
|
23
|
+
'is_relay',
|
|
24
|
+
'is_tor',
|
|
25
|
+
'is_cdn',
|
|
26
|
+
'is_resproxy',
|
|
27
|
+
'is_dcproxy',
|
|
28
|
+
'is_mobproxy',
|
|
29
|
+
'vpn',
|
|
30
|
+
'hosting',
|
|
31
|
+
'relay',
|
|
32
|
+
'tor',
|
|
33
|
+
'cdn',
|
|
34
|
+
'resproxy',
|
|
35
|
+
'dcproxy',
|
|
36
|
+
'mobproxy',
|
|
37
|
+
];
|
|
38
|
+
export const SPEC_VERSION = '2026.09.10';
|
|
39
|
+
export const LOOKUP_RESULT_SCHEMA = {
|
|
40
|
+
"type": "object",
|
|
41
|
+
"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",
|
|
42
|
+
"required": [
|
|
43
|
+
"ip",
|
|
44
|
+
"is_vpn"
|
|
45
|
+
],
|
|
46
|
+
"properties": {
|
|
47
|
+
"ip": {
|
|
48
|
+
"type": "string",
|
|
49
|
+
"description": "The address that was looked up, normalized."
|
|
50
|
+
},
|
|
51
|
+
"is_vpn": {
|
|
52
|
+
"type": "boolean",
|
|
53
|
+
"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"
|
|
54
|
+
},
|
|
55
|
+
"is_hosting": {
|
|
56
|
+
"type": "boolean",
|
|
57
|
+
"description": "Whether the address belongs to a hosting or cloud provider. Starter and above."
|
|
58
|
+
},
|
|
59
|
+
"is_relay": {
|
|
60
|
+
"type": "boolean",
|
|
61
|
+
"description": "Whether the address is a privacy relay egress. Starter and above."
|
|
62
|
+
},
|
|
63
|
+
"is_tor": {
|
|
64
|
+
"type": "boolean",
|
|
65
|
+
"description": "Whether the address is a Tor node. Starter and above."
|
|
66
|
+
},
|
|
67
|
+
"is_cdn": {
|
|
68
|
+
"type": "boolean",
|
|
69
|
+
"description": "Whether the address belongs to a CDN. Starter and above."
|
|
70
|
+
},
|
|
71
|
+
"is_resproxy": {
|
|
72
|
+
"type": "boolean",
|
|
73
|
+
"description": "Whether the address was seen in a residential proxy pool. Scale and above."
|
|
74
|
+
},
|
|
75
|
+
"is_dcproxy": {
|
|
76
|
+
"type": "boolean",
|
|
77
|
+
"description": "Whether the address was seen in a datacenter proxy pool. Scale and above."
|
|
78
|
+
},
|
|
79
|
+
"is_mobproxy": {
|
|
80
|
+
"type": "boolean",
|
|
81
|
+
"description": "Whether the address was seen in a mobile proxy pool. Scale and above."
|
|
82
|
+
},
|
|
83
|
+
"vpn": {
|
|
84
|
+
"type": "object",
|
|
85
|
+
"description": "Detail for `is_vpn`. Empty when `is_vpn` is false. Starter and above.\n",
|
|
86
|
+
"properties": {
|
|
87
|
+
"provider": {
|
|
88
|
+
"type": "string",
|
|
89
|
+
"description": "The VPN provider, or an empty string for an unattributed range."
|
|
90
|
+
},
|
|
91
|
+
"last_seen": {
|
|
92
|
+
"type": "string",
|
|
93
|
+
"format": "date",
|
|
94
|
+
"description": "The most recent date this address was observed as VPN infrastructure."
|
|
95
|
+
},
|
|
96
|
+
"confidence": {
|
|
97
|
+
"type": "string",
|
|
98
|
+
"description": "How strongly the attribution is supported. Max only."
|
|
99
|
+
},
|
|
100
|
+
"method": {
|
|
101
|
+
"type": "string",
|
|
102
|
+
"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"
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
"hosting": {
|
|
107
|
+
"type": "object",
|
|
108
|
+
"description": "Detail for `is_hosting`. Empty when false. Scale and above.",
|
|
109
|
+
"properties": {
|
|
110
|
+
"provider": {
|
|
111
|
+
"type": "string",
|
|
112
|
+
"description": "The provider, or an empty string where the dataset has none."
|
|
113
|
+
},
|
|
114
|
+
"confidence": {
|
|
115
|
+
"type": "string",
|
|
116
|
+
"description": "How strongly the classification is supported."
|
|
117
|
+
},
|
|
118
|
+
"last_seen": {
|
|
119
|
+
"type": "string",
|
|
120
|
+
"format": "date",
|
|
121
|
+
"description": "The most recent date this address was observed in this dataset."
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
"relay": {
|
|
126
|
+
"type": "object",
|
|
127
|
+
"description": "Detail for `is_relay`. Empty when false. Scale and above.",
|
|
128
|
+
"properties": {
|
|
129
|
+
"provider": {
|
|
130
|
+
"type": "string",
|
|
131
|
+
"description": "The provider, or an empty string where the dataset has none."
|
|
132
|
+
},
|
|
133
|
+
"confidence": {
|
|
134
|
+
"type": "string",
|
|
135
|
+
"description": "How strongly the classification is supported."
|
|
136
|
+
},
|
|
137
|
+
"last_seen": {
|
|
138
|
+
"type": "string",
|
|
139
|
+
"format": "date",
|
|
140
|
+
"description": "The most recent date this address was observed in this dataset."
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
"tor": {
|
|
145
|
+
"type": "object",
|
|
146
|
+
"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",
|
|
147
|
+
"properties": {
|
|
148
|
+
"provider": {
|
|
149
|
+
"type": "string",
|
|
150
|
+
"description": "The provider, or an empty string where the dataset has none."
|
|
151
|
+
},
|
|
152
|
+
"confidence": {
|
|
153
|
+
"type": "string",
|
|
154
|
+
"description": "How strongly the classification is supported."
|
|
155
|
+
},
|
|
156
|
+
"last_seen": {
|
|
157
|
+
"type": "string",
|
|
158
|
+
"format": "date",
|
|
159
|
+
"description": "The most recent date this address was observed in this dataset."
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
},
|
|
163
|
+
"cdn": {
|
|
164
|
+
"type": "object",
|
|
165
|
+
"description": "Detail for `is_cdn`. Empty when false. Scale and above.",
|
|
166
|
+
"properties": {
|
|
167
|
+
"provider": {
|
|
168
|
+
"type": "string",
|
|
169
|
+
"description": "The provider, or an empty string where the dataset has none."
|
|
170
|
+
},
|
|
171
|
+
"confidence": {
|
|
172
|
+
"type": "string",
|
|
173
|
+
"description": "How strongly the classification is supported."
|
|
174
|
+
},
|
|
175
|
+
"last_seen": {
|
|
176
|
+
"type": "string",
|
|
177
|
+
"format": "date",
|
|
178
|
+
"description": "The most recent date this address was observed in this dataset."
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
},
|
|
182
|
+
"resproxy": {
|
|
183
|
+
"type": "object",
|
|
184
|
+
"description": "Detail for `is_resproxy`. Empty when false. Max only.",
|
|
185
|
+
"properties": {
|
|
186
|
+
"provider": {
|
|
187
|
+
"type": "string",
|
|
188
|
+
"description": "The proxy network, or an empty string where unattributed."
|
|
189
|
+
},
|
|
190
|
+
"first_seen": {
|
|
191
|
+
"type": "string",
|
|
192
|
+
"format": "date",
|
|
193
|
+
"description": "The earliest date within the window this address was seen in the pool."
|
|
194
|
+
},
|
|
195
|
+
"last_seen": {
|
|
196
|
+
"type": "string",
|
|
197
|
+
"format": "date",
|
|
198
|
+
"description": "The most recent date within the window this address was seen in the pool."
|
|
199
|
+
},
|
|
200
|
+
"hits": {
|
|
201
|
+
"type": "integer",
|
|
202
|
+
"description": "How many times the address was observed in the pool during the window."
|
|
203
|
+
},
|
|
204
|
+
"hits_days_pct": {
|
|
205
|
+
"type": "integer",
|
|
206
|
+
"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"
|
|
207
|
+
},
|
|
208
|
+
"providers_num": {
|
|
209
|
+
"type": "integer",
|
|
210
|
+
"description": "How many distinct proxy networks this address was seen in."
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
},
|
|
214
|
+
"dcproxy": {
|
|
215
|
+
"type": "object",
|
|
216
|
+
"description": "Detail for `is_dcproxy`. Empty when false. Max only.",
|
|
217
|
+
"properties": {
|
|
218
|
+
"provider": {
|
|
219
|
+
"type": "string",
|
|
220
|
+
"description": "The proxy network, or an empty string where unattributed."
|
|
221
|
+
},
|
|
222
|
+
"first_seen": {
|
|
223
|
+
"type": "string",
|
|
224
|
+
"format": "date",
|
|
225
|
+
"description": "The earliest date within the window this address was seen in the pool."
|
|
226
|
+
},
|
|
227
|
+
"last_seen": {
|
|
228
|
+
"type": "string",
|
|
229
|
+
"format": "date",
|
|
230
|
+
"description": "The most recent date within the window this address was seen in the pool."
|
|
231
|
+
},
|
|
232
|
+
"hits": {
|
|
233
|
+
"type": "integer",
|
|
234
|
+
"description": "How many times the address was observed in the pool during the window."
|
|
235
|
+
},
|
|
236
|
+
"hits_days_pct": {
|
|
237
|
+
"type": "integer",
|
|
238
|
+
"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"
|
|
239
|
+
},
|
|
240
|
+
"providers_num": {
|
|
241
|
+
"type": "integer",
|
|
242
|
+
"description": "How many distinct proxy networks this address was seen in."
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
},
|
|
246
|
+
"mobproxy": {
|
|
247
|
+
"type": "object",
|
|
248
|
+
"description": "Detail for `is_mobproxy`. Empty when false. Max only.",
|
|
249
|
+
"properties": {
|
|
250
|
+
"provider": {
|
|
251
|
+
"type": "string",
|
|
252
|
+
"description": "The proxy network, or an empty string where unattributed."
|
|
253
|
+
},
|
|
254
|
+
"first_seen": {
|
|
255
|
+
"type": "string",
|
|
256
|
+
"format": "date",
|
|
257
|
+
"description": "The earliest date within the window this address was seen in the pool."
|
|
258
|
+
},
|
|
259
|
+
"last_seen": {
|
|
260
|
+
"type": "string",
|
|
261
|
+
"format": "date",
|
|
262
|
+
"description": "The most recent date within the window this address was seen in the pool."
|
|
263
|
+
},
|
|
264
|
+
"hits": {
|
|
265
|
+
"type": "integer",
|
|
266
|
+
"description": "How many times the address was observed in the pool during the window."
|
|
267
|
+
},
|
|
268
|
+
"hits_days_pct": {
|
|
269
|
+
"type": "integer",
|
|
270
|
+
"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"
|
|
271
|
+
},
|
|
272
|
+
"providers_num": {
|
|
273
|
+
"type": "integer",
|
|
274
|
+
"description": "How many distinct proxy networks this address was seen in."
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
};
|
|
280
|
+
export const LICENSED_DATASET_SCHEMA = {
|
|
281
|
+
"type": "object",
|
|
282
|
+
"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",
|
|
283
|
+
"properties": {
|
|
284
|
+
"base": {
|
|
285
|
+
"type": "string",
|
|
286
|
+
"description": "The dataset family, e.g. `vpn_ip`. What the license is held against."
|
|
287
|
+
},
|
|
288
|
+
"name": {
|
|
289
|
+
"type": "string"
|
|
290
|
+
},
|
|
291
|
+
"summary": {
|
|
292
|
+
"type": "string"
|
|
293
|
+
},
|
|
294
|
+
"license_type": {
|
|
295
|
+
"type": "string",
|
|
296
|
+
"enum": [
|
|
297
|
+
"evaluation",
|
|
298
|
+
"standard",
|
|
299
|
+
"redistribute"
|
|
300
|
+
],
|
|
301
|
+
"description": "What your license permits you to do with the data."
|
|
302
|
+
},
|
|
303
|
+
"starts": {
|
|
304
|
+
"type": "string",
|
|
305
|
+
"format": "date-time",
|
|
306
|
+
"nullable": true
|
|
307
|
+
},
|
|
308
|
+
"expires": {
|
|
309
|
+
"type": "string",
|
|
310
|
+
"format": "date-time",
|
|
311
|
+
"nullable": true,
|
|
312
|
+
"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."
|
|
313
|
+
},
|
|
314
|
+
"renews_at": {
|
|
315
|
+
"type": "string",
|
|
316
|
+
"format": "date-time",
|
|
317
|
+
"nullable": true,
|
|
318
|
+
"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."
|
|
319
|
+
},
|
|
320
|
+
"notice_due_at": {
|
|
321
|
+
"type": "string",
|
|
322
|
+
"format": "date-time",
|
|
323
|
+
"nullable": true,
|
|
324
|
+
"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."
|
|
325
|
+
},
|
|
326
|
+
"in_term": {
|
|
327
|
+
"type": "boolean",
|
|
328
|
+
"description": "False when the license has lapsed; downloads are refused."
|
|
329
|
+
},
|
|
330
|
+
"standing": {
|
|
331
|
+
"type": "string",
|
|
332
|
+
"enum": [
|
|
333
|
+
"expired",
|
|
334
|
+
"licensed",
|
|
335
|
+
"unlicensed"
|
|
336
|
+
],
|
|
337
|
+
"description": "`licensed` is a live grant, `expired` one whose term has ended, and\n`unlicensed` a dataset published but never bought.\n"
|
|
338
|
+
},
|
|
339
|
+
"versions": {
|
|
340
|
+
"type": "array",
|
|
341
|
+
"description": "Every published version of this family. The `id` here is what the\ndownload and checksum endpoints take.\n",
|
|
342
|
+
"items": {
|
|
343
|
+
"type": "object",
|
|
344
|
+
"properties": {
|
|
345
|
+
"id": {
|
|
346
|
+
"type": "string",
|
|
347
|
+
"description": "The versioned dataset id, e.g. `vpn_ip_v1`. Pass this to download."
|
|
348
|
+
},
|
|
349
|
+
"version": {
|
|
350
|
+
"type": "integer"
|
|
351
|
+
},
|
|
352
|
+
"summary": {
|
|
353
|
+
"type": "string"
|
|
354
|
+
},
|
|
355
|
+
"formats": {
|
|
356
|
+
"type": "array",
|
|
357
|
+
"items": {
|
|
358
|
+
"type": "object",
|
|
359
|
+
"properties": {
|
|
360
|
+
"format": {
|
|
361
|
+
"type": "string",
|
|
362
|
+
"enum": [
|
|
363
|
+
"csvgz",
|
|
364
|
+
"mmdb"
|
|
365
|
+
]
|
|
366
|
+
},
|
|
367
|
+
"bytes": {
|
|
368
|
+
"type": "integer",
|
|
369
|
+
"nullable": true,
|
|
370
|
+
"description": "Size of the published file, or null when it has not been published yet"
|
|
371
|
+
}
|
|
372
|
+
},
|
|
373
|
+
"required": [
|
|
374
|
+
"format",
|
|
375
|
+
"bytes"
|
|
376
|
+
]
|
|
377
|
+
}
|
|
378
|
+
},
|
|
379
|
+
"sampleFormats": {
|
|
380
|
+
"type": "array",
|
|
381
|
+
"description": "The formats an evaluation sample is published in, if any.",
|
|
382
|
+
"items": {
|
|
383
|
+
"type": "string",
|
|
384
|
+
"enum": [
|
|
385
|
+
"csvgz",
|
|
386
|
+
"mmdb"
|
|
387
|
+
]
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
},
|
|
391
|
+
"required": [
|
|
392
|
+
"id",
|
|
393
|
+
"version",
|
|
394
|
+
"formats"
|
|
395
|
+
]
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
},
|
|
399
|
+
"required": [
|
|
400
|
+
"base",
|
|
401
|
+
"name",
|
|
402
|
+
"summary",
|
|
403
|
+
"license_type",
|
|
404
|
+
"starts",
|
|
405
|
+
"expires",
|
|
406
|
+
"renews_at",
|
|
407
|
+
"notice_due_at",
|
|
408
|
+
"in_term",
|
|
409
|
+
"standing",
|
|
410
|
+
"versions"
|
|
411
|
+
]
|
|
412
|
+
};
|
|
413
|
+
export const DATASET_METADATA_SCHEMA = {
|
|
414
|
+
"type": "object",
|
|
415
|
+
"properties": {
|
|
416
|
+
"id": {
|
|
417
|
+
"type": "string"
|
|
418
|
+
},
|
|
419
|
+
"update_freq": {
|
|
420
|
+
"type": "string",
|
|
421
|
+
"description": "How often a new build is published"
|
|
422
|
+
},
|
|
423
|
+
"updated": {
|
|
424
|
+
"type": "string",
|
|
425
|
+
"format": "date"
|
|
426
|
+
},
|
|
427
|
+
"entries": {
|
|
428
|
+
"type": "integer",
|
|
429
|
+
"description": "Row count in the current build"
|
|
430
|
+
},
|
|
431
|
+
"schema": {
|
|
432
|
+
"type": "object",
|
|
433
|
+
"additionalProperties": {
|
|
434
|
+
"type": "array",
|
|
435
|
+
"items": {
|
|
436
|
+
"type": "object",
|
|
437
|
+
"properties": {
|
|
438
|
+
"name": {
|
|
439
|
+
"type": "string"
|
|
440
|
+
},
|
|
441
|
+
"type": {
|
|
442
|
+
"type": "string"
|
|
443
|
+
},
|
|
444
|
+
"description": {
|
|
445
|
+
"type": "string"
|
|
446
|
+
}
|
|
447
|
+
},
|
|
448
|
+
"required": [
|
|
449
|
+
"name",
|
|
450
|
+
"type"
|
|
451
|
+
]
|
|
452
|
+
}
|
|
453
|
+
},
|
|
454
|
+
"description": "Columns, keyed by format"
|
|
455
|
+
},
|
|
456
|
+
"sample": {
|
|
457
|
+
"type": "object",
|
|
458
|
+
"additionalProperties": {
|
|
459
|
+
"type": "array",
|
|
460
|
+
"items": {
|
|
461
|
+
"type": "object"
|
|
462
|
+
}
|
|
463
|
+
},
|
|
464
|
+
"description": "A few real rows, keyed by format"
|
|
465
|
+
},
|
|
466
|
+
"size": {
|
|
467
|
+
"type": "object",
|
|
468
|
+
"additionalProperties": {
|
|
469
|
+
"type": "integer"
|
|
470
|
+
},
|
|
471
|
+
"description": "Bytes per format"
|
|
472
|
+
},
|
|
473
|
+
"sample_size": {
|
|
474
|
+
"type": "object",
|
|
475
|
+
"additionalProperties": {
|
|
476
|
+
"type": "integer"
|
|
477
|
+
},
|
|
478
|
+
"description": "Bytes per format of the evaluation sample, where one is published"
|
|
479
|
+
},
|
|
480
|
+
"sample_entries": {
|
|
481
|
+
"type": "integer",
|
|
482
|
+
"description": "Row count in the evaluation sample"
|
|
483
|
+
}
|
|
484
|
+
},
|
|
485
|
+
"required": [
|
|
486
|
+
"id",
|
|
487
|
+
"updated",
|
|
488
|
+
"entries",
|
|
489
|
+
"schema"
|
|
490
|
+
]
|
|
491
|
+
};
|
|
492
|
+
//# sourceMappingURL=schema.gen.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.gen.js","sourceRoot":"","sources":["../src/schema.gen.ts"],"names":[],"mappings":"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"}
|
package/dist/stdio.d.ts
ADDED
package/dist/stdio.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// The stdio entry point: the process an MCP client spawns.
|
|
3
|
+
//
|
|
4
|
+
// This is the one file that reads the environment. The MCP specification tells
|
|
5
|
+
// stdio servers to take credentials that way rather than through the protocol's
|
|
6
|
+
// OAuth profile, and everything below it takes its configuration as arguments.
|
|
7
|
+
import { createRequire } from 'node:module';
|
|
8
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
9
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
10
|
+
import { VPNDetection } from 'vpndetection';
|
|
11
|
+
import { createTools, registerTools } from './tools.js';
|
|
12
|
+
const version = createRequire(import.meta.url)('../package.json').version;
|
|
13
|
+
async function main() {
|
|
14
|
+
const apiKey = process.env['VPNDETECTION_API_KEY'];
|
|
15
|
+
const client = new VPNDetection({
|
|
16
|
+
...(apiKey === undefined || apiKey === '' ? {} : { apiKey: apiKey }),
|
|
17
|
+
...(process.env['VPNDETECTION_BASE_URL'] === undefined
|
|
18
|
+
? {} : { baseUrl: process.env['VPNDETECTION_BASE_URL'] }),
|
|
19
|
+
});
|
|
20
|
+
const server = new Server({ name: 'vpndetection', version: version }, { capabilities: { tools: {} } });
|
|
21
|
+
registerTools(server, createTools({ client: client }));
|
|
22
|
+
await server.connect(new StdioServerTransport());
|
|
23
|
+
}
|
|
24
|
+
main().catch((err) => {
|
|
25
|
+
// stdout carries the protocol, so a diagnostic can only go to stderr.
|
|
26
|
+
console.error(err);
|
|
27
|
+
process.exitCode = 1;
|
|
28
|
+
});
|
|
29
|
+
//# sourceMappingURL=stdio.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stdio.js","sourceRoot":"","sources":["../src/stdio.ts"],"names":[],"mappings":";AAEA,2DAA2D;AAC3D,EAAE;AACF,+EAA+E;AAC/E,gFAAgF;AAChF,+EAA+E;AAE/E,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AACnE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAE5C,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAExD,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,iBAAiB,CAAC,CAAC,OAAiB,CAAC;AAEpF,KAAK,UAAU,IAAI;IACf,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,sBAAsB,CAAC,CAAC;IACnD,MAAM,MAAM,GAAG,IAAI,YAAY,CAAC;QAC5B,GAAG,CAAC,MAAM,KAAK,SAAS,IAAI,MAAM,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;QACpE,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,uBAAuB,CAAC,KAAK,SAAS;YAClD,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,uBAAuB,CAAC,EAAE,CAAC;KAChE,CAAC,CAAC;IAEH,MAAM,MAAM,GAAG,IAAI,MAAM,CACrB,EAAE,IAAI,EAAE,cAAc,EAAE,OAAO,EAAE,OAAO,EAAE,EAC1C,EAAE,YAAY,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,CAClC,CAAC;IACF,aAAa,CAAC,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC;IAEvD,MAAM,MAAM,CAAC,OAAO,CAAC,IAAI,oBAAoB,EAAE,CAAC,CAAC;AACrD,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAY,EAAE,EAAE;IAC1B,sEAAsE;IACtE,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACnB,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;AACzB,CAAC,CAAC,CAAC"}
|
package/dist/tools.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
2
|
+
import type { CallToolResult, Tool } from '@modelcontextprotocol/sdk/types.js';
|
|
3
|
+
import type { VPNDetection } from 'vpndetection';
|
|
4
|
+
/** The most addresses one `lookup_ips` call may carry. */
|
|
5
|
+
export declare const BATCH_LIMIT = 100;
|
|
6
|
+
export interface ToolContext {
|
|
7
|
+
client: VPNDetection;
|
|
8
|
+
/**
|
|
9
|
+
* Whether to offer the dataset tools. On by default: a key without the
|
|
10
|
+
* `db.download` scope gets a plain refusal from the API, which reads better
|
|
11
|
+
* than a tool that silently does not exist, and deciding otherwise would
|
|
12
|
+
* mean re-validating the key here against a policy `db_dl_api` owns.
|
|
13
|
+
*/
|
|
14
|
+
database?: boolean;
|
|
15
|
+
}
|
|
16
|
+
export interface ToolDef {
|
|
17
|
+
tool: Tool;
|
|
18
|
+
handler: (args: Record<string, unknown>) => Promise<CallToolResult>;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* The tool manifest, and the single place either transport gets it from.
|
|
22
|
+
*
|
|
23
|
+
* Descriptions are read by a model rather than a developer, so they say what
|
|
24
|
+
* the tool answers and what it costs, not how it is implemented.
|
|
25
|
+
*/
|
|
26
|
+
export declare function createTools(ctx: ToolContext): ToolDef[];
|
|
27
|
+
/** Mounts a manifest on a server. Both transports use this and nothing else. */
|
|
28
|
+
export declare function registerTools(server: Server, defs: ToolDef[]): void;
|
package/dist/tools.js
ADDED
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
3
|
+
import { batchCoverage, COVERAGE_SCHEMA, coverageOf, wireBody } from './coverage.js';
|
|
4
|
+
import { DATASET_METADATA_SCHEMA, LICENSED_DATASET_SCHEMA, LOOKUP_RESULT_SCHEMA, } from './schema.gen.js';
|
|
5
|
+
/** The most addresses one `lookup_ips` call may carry. */
|
|
6
|
+
export const BATCH_LIMIT = 100;
|
|
7
|
+
/**
|
|
8
|
+
* The tool manifest, and the single place either transport gets it from.
|
|
9
|
+
*
|
|
10
|
+
* Descriptions are read by a model rather than a developer, so they say what
|
|
11
|
+
* the tool answers and what it costs, not how it is implemented.
|
|
12
|
+
*/
|
|
13
|
+
export function createTools(ctx) {
|
|
14
|
+
const defs = [
|
|
15
|
+
{
|
|
16
|
+
tool: {
|
|
17
|
+
name: 'lookup_ip',
|
|
18
|
+
title: 'Look up an IP address',
|
|
19
|
+
description: 'Classify a single IPv4 or IPv6 address: whether it belongs to a VPN, '
|
|
20
|
+
+ 'a residential, datacenter or mobile proxy, a Tor node, a public relay, a '
|
|
21
|
+
+ 'hosting provider or a CDN, with the provider name where we have one. '
|
|
22
|
+
+ 'Private and reserved addresses are answered locally and cost nothing. '
|
|
23
|
+
+ 'Always read `coverage.note` before concluding anything from a field that is '
|
|
24
|
+
+ 'not in the result.',
|
|
25
|
+
inputSchema: jsonSchema(z.object({
|
|
26
|
+
ip: z.string().describe('The IPv4 or IPv6 address to classify.'),
|
|
27
|
+
})),
|
|
28
|
+
outputSchema: objectSchema({
|
|
29
|
+
result: LOOKUP_RESULT_SCHEMA,
|
|
30
|
+
coverage: COVERAGE_SCHEMA,
|
|
31
|
+
}, ['result', 'coverage']),
|
|
32
|
+
annotations: {
|
|
33
|
+
readOnlyHint: true,
|
|
34
|
+
idempotentHint: true,
|
|
35
|
+
openWorldHint: true,
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
handler: (args) => lookupIp(ctx, args),
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
tool: {
|
|
42
|
+
name: 'lookup_ips',
|
|
43
|
+
title: 'Look up several IP addresses',
|
|
44
|
+
description: `Classify up to ${BATCH_LIMIT} addresses in one call, returning a map `
|
|
45
|
+
+ 'keyed by address so duplicates collapse and the order you passed them stops '
|
|
46
|
+
+ 'mattering. Prefer this over repeated `lookup_ip` calls when you already have '
|
|
47
|
+
+ 'the list, for example when triaging a log file. An address that fails '
|
|
48
|
+
+ 'carries its error in place of a result rather than failing the batch.',
|
|
49
|
+
inputSchema: jsonSchema(z.object({
|
|
50
|
+
ips: z.array(z.string()).min(1).max(BATCH_LIMIT)
|
|
51
|
+
.describe(`The addresses to classify, at most ${BATCH_LIMIT}.`),
|
|
52
|
+
})),
|
|
53
|
+
outputSchema: objectSchema({
|
|
54
|
+
results: {
|
|
55
|
+
type: 'object',
|
|
56
|
+
description: 'Keyed by address. A value is either a result or an error.',
|
|
57
|
+
additionalProperties: true,
|
|
58
|
+
},
|
|
59
|
+
coverage: COVERAGE_SCHEMA,
|
|
60
|
+
}, ['results', 'coverage']),
|
|
61
|
+
annotations: {
|
|
62
|
+
readOnlyHint: true,
|
|
63
|
+
idempotentHint: true,
|
|
64
|
+
openWorldHint: true,
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
handler: (args) => lookupIps(ctx, args),
|
|
68
|
+
},
|
|
69
|
+
];
|
|
70
|
+
if (ctx.database !== false) {
|
|
71
|
+
defs.push(...databaseTools(ctx));
|
|
72
|
+
}
|
|
73
|
+
// A handler NEVER throws: an upstream 404 or a rejected argument is a result
|
|
74
|
+
// the model can read and correct itself from, while a JSON-RPC protocol error
|
|
75
|
+
// is not. Wrapping here rather than in `registerTools` keeps that true for
|
|
76
|
+
// any consumer of a ToolDef, including a transport that mounts its own.
|
|
77
|
+
return defs.map((d) => ({ tool: d.tool, handler: guard(d.handler) }));
|
|
78
|
+
}
|
|
79
|
+
/** Mounts a manifest on a server. Both transports use this and nothing else. */
|
|
80
|
+
export function registerTools(server, defs) {
|
|
81
|
+
const byName = new Map(defs.map((d) => [d.tool.name, d]));
|
|
82
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
83
|
+
return { tools: defs.map((d) => d.tool) };
|
|
84
|
+
});
|
|
85
|
+
server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
86
|
+
const def = byName.get(req.params.name);
|
|
87
|
+
if (def === undefined) {
|
|
88
|
+
throw new Error(`Unknown tool: ${req.params.name}`);
|
|
89
|
+
}
|
|
90
|
+
return await def.handler(req.params.arguments ?? {});
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
function guard(handler) {
|
|
94
|
+
return async (args) => {
|
|
95
|
+
try {
|
|
96
|
+
return await handler(args);
|
|
97
|
+
}
|
|
98
|
+
catch (err) {
|
|
99
|
+
return toolError(err);
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
async function lookupIp(ctx, args) {
|
|
104
|
+
const { ip } = z.object({ ip: z.string() }).parse(args);
|
|
105
|
+
const body = wireBody(await ctx.client.lookup(ip));
|
|
106
|
+
return ok({ result: body, coverage: coverageOf(body) });
|
|
107
|
+
}
|
|
108
|
+
async function lookupIps(ctx, args) {
|
|
109
|
+
const { ips } = z.object({
|
|
110
|
+
ips: z.array(z.string()).min(1).max(BATCH_LIMIT),
|
|
111
|
+
}).parse(args);
|
|
112
|
+
const answers = await ctx.client.lookupBatch(ips);
|
|
113
|
+
const results = {};
|
|
114
|
+
const served = [];
|
|
115
|
+
for (const [addr, answer] of answers) {
|
|
116
|
+
const failed = asClientError(answer);
|
|
117
|
+
if (failed !== undefined) {
|
|
118
|
+
results[addr] = { error: { kind: failed.kind, message: failed.message } };
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
const body = wireBody(answer);
|
|
122
|
+
results[addr] = body;
|
|
123
|
+
served.push(body);
|
|
124
|
+
}
|
|
125
|
+
return ok({ results: results, coverage: batchCoverage(served) });
|
|
126
|
+
}
|
|
127
|
+
function databaseTools(ctx) {
|
|
128
|
+
const formats = z.enum(['csvgz', 'mmdb']);
|
|
129
|
+
return [
|
|
130
|
+
{
|
|
131
|
+
tool: {
|
|
132
|
+
name: 'list_databases',
|
|
133
|
+
title: 'List licensed datasets',
|
|
134
|
+
description: 'The datasets this API key\'s organisation is licensed to download, '
|
|
135
|
+
+ 'with the licence type and term. A dataset absent from this list is one the '
|
|
136
|
+
+ 'organisation does not hold.',
|
|
137
|
+
inputSchema: { type: 'object', additionalProperties: false },
|
|
138
|
+
outputSchema: objectSchema({
|
|
139
|
+
datasets: { type: 'array', items: LICENSED_DATASET_SCHEMA },
|
|
140
|
+
}, ['datasets']),
|
|
141
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true },
|
|
142
|
+
},
|
|
143
|
+
handler: async () => {
|
|
144
|
+
return ok({ datasets: await ctx.client.database.list() });
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
tool: {
|
|
149
|
+
name: 'database_metadata',
|
|
150
|
+
title: 'Describe a dataset',
|
|
151
|
+
description: 'The shape of a dataset before you fetch it: the columns in each '
|
|
152
|
+
+ 'published format with their types, a few sample rows, the row count, the '
|
|
153
|
+
+ 'build date and the file sizes. Use this to answer questions about what a '
|
|
154
|
+
+ 'dataset contains without downloading it - the files reach several GB.',
|
|
155
|
+
inputSchema: jsonSchema(z.object({
|
|
156
|
+
dataset_id: z.string().describe('The dataset id, as returned by `list_databases`.'),
|
|
157
|
+
})),
|
|
158
|
+
outputSchema: DATASET_METADATA_SCHEMA,
|
|
159
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true },
|
|
160
|
+
},
|
|
161
|
+
handler: async (args) => {
|
|
162
|
+
const { dataset_id } = z.object({ dataset_id: z.string() }).parse(args);
|
|
163
|
+
return ok(await ctx.client.database.metadata(dataset_id));
|
|
164
|
+
},
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
tool: {
|
|
168
|
+
name: 'database_checksum',
|
|
169
|
+
title: 'Get a dataset\'s checksums',
|
|
170
|
+
description: 'The published digests for one dataset file, for verifying a copy you '
|
|
171
|
+
+ 'already hold or deciding whether a build has changed since you last fetched it.',
|
|
172
|
+
inputSchema: jsonSchema(z.object({
|
|
173
|
+
dataset_id: z.string().describe('The dataset id, as returned by `list_databases`.'),
|
|
174
|
+
format: formats.describe('Which published file to digest.'),
|
|
175
|
+
})),
|
|
176
|
+
outputSchema: {
|
|
177
|
+
type: 'object',
|
|
178
|
+
properties: { checksums: { type: 'object', additionalProperties: true } },
|
|
179
|
+
required: ['checksums'],
|
|
180
|
+
},
|
|
181
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true },
|
|
182
|
+
},
|
|
183
|
+
handler: async (args) => {
|
|
184
|
+
const parsed = z.object({
|
|
185
|
+
dataset_id: z.string(),
|
|
186
|
+
format: formats,
|
|
187
|
+
}).parse(args);
|
|
188
|
+
const checksums = await ctx.client.database.checksums(parsed.dataset_id, parsed.format);
|
|
189
|
+
return ok({ checksums: checksums });
|
|
190
|
+
},
|
|
191
|
+
},
|
|
192
|
+
];
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* The serialized structured content IS the text block, rather than a prose
|
|
196
|
+
* summary beside it. The coverage note lives inside the structure, so writing
|
|
197
|
+
* it once puts it in front of both a client that reads `structuredContent` and
|
|
198
|
+
* one that only renders `content`.
|
|
199
|
+
*/
|
|
200
|
+
function ok(structured) {
|
|
201
|
+
return {
|
|
202
|
+
content: [{ type: 'text', text: JSON.stringify(structured, null, 2) }],
|
|
203
|
+
structuredContent: structured,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* An error carries no `structuredContent`, deliberately.
|
|
208
|
+
*
|
|
209
|
+
* A tool that declares an `outputSchema` has every structured result validated
|
|
210
|
+
* against it by the client, and an error is not shaped like a success - so
|
|
211
|
+
* attaching one turns a readable failure into a protocol error the model cannot
|
|
212
|
+
* act on. The text block is what it reads instead.
|
|
213
|
+
*/
|
|
214
|
+
function toolError(err) {
|
|
215
|
+
const known = asClientError(err);
|
|
216
|
+
const detail = known !== undefined
|
|
217
|
+
? { kind: known.kind, message: known.message, retryable: known.retryable === true }
|
|
218
|
+
: { kind: 'internal', message: err instanceof Error ? err.message : String(err) };
|
|
219
|
+
return {
|
|
220
|
+
content: [{ type: 'text', text: JSON.stringify({ error: detail }, null, 2) }],
|
|
221
|
+
isError: true,
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Recognises a `VPNDetectionError` by SHAPE, not by `instanceof`.
|
|
226
|
+
*
|
|
227
|
+
* This package and whatever mounts it can each end up with their own copy of the
|
|
228
|
+
* `vpndetection` module - the hosted service depends on both - and two copies of
|
|
229
|
+
* one class are two identities, so `instanceof` silently returns false and every
|
|
230
|
+
* upstream failure degrades to a generic `internal`. A caller then sees "internal"
|
|
231
|
+
* for what was really `unauthorized`, and loses the `retryable` flag that decides
|
|
232
|
+
* whether trying again is worth anything.
|
|
233
|
+
*/
|
|
234
|
+
function asClientError(err) {
|
|
235
|
+
if (err === null || typeof err !== 'object') {
|
|
236
|
+
return undefined;
|
|
237
|
+
}
|
|
238
|
+
const e = err;
|
|
239
|
+
if (e.name !== 'VPNDetectionError' || typeof e.kind !== 'string') {
|
|
240
|
+
return undefined;
|
|
241
|
+
}
|
|
242
|
+
return e;
|
|
243
|
+
}
|
|
244
|
+
// One zod declaration yields both the published schema and the runtime parse, so
|
|
245
|
+
// a cap advertised to a client is the same cap the handler enforces.
|
|
246
|
+
function jsonSchema(schema) {
|
|
247
|
+
const out = z.toJSONSchema(schema, { io: 'input' });
|
|
248
|
+
delete out['$schema'];
|
|
249
|
+
return { ...out, type: 'object' };
|
|
250
|
+
}
|
|
251
|
+
function objectSchema(properties, required) {
|
|
252
|
+
return {
|
|
253
|
+
type: 'object',
|
|
254
|
+
properties: properties,
|
|
255
|
+
required: required,
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
//# sourceMappingURL=tools.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tools.js","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "vpndetection-mcp",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Official MCP server for the VPNDetection API. Gives an LLM agent VPN, proxy, Tor, hosting and CDN detection for any IP address.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"homepage": "https://vpndetection.io",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/vpndetection-io/mcp.git"
|
|
11
|
+
},
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/vpndetection-io/mcp/issues"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"mcp",
|
|
17
|
+
"model-context-protocol",
|
|
18
|
+
"vpn",
|
|
19
|
+
"vpn-detection",
|
|
20
|
+
"proxy-detection",
|
|
21
|
+
"tor",
|
|
22
|
+
"ip-intelligence",
|
|
23
|
+
"fraud-prevention",
|
|
24
|
+
"llm",
|
|
25
|
+
"agent"
|
|
26
|
+
],
|
|
27
|
+
"main": "./dist/index.js",
|
|
28
|
+
"types": "./dist/index.d.ts",
|
|
29
|
+
"bin": {
|
|
30
|
+
"vpndetection-mcp": "./dist/stdio.js"
|
|
31
|
+
},
|
|
32
|
+
"exports": {
|
|
33
|
+
".": {
|
|
34
|
+
"types": "./dist/index.d.ts",
|
|
35
|
+
"default": "./dist/index.js"
|
|
36
|
+
},
|
|
37
|
+
"./tools": {
|
|
38
|
+
"types": "./dist/tools.d.ts",
|
|
39
|
+
"default": "./dist/tools.js"
|
|
40
|
+
},
|
|
41
|
+
"./package.json": "./package.json"
|
|
42
|
+
},
|
|
43
|
+
"files": [
|
|
44
|
+
"dist",
|
|
45
|
+
"README.md",
|
|
46
|
+
"LICENSE"
|
|
47
|
+
],
|
|
48
|
+
"engines": {
|
|
49
|
+
"node": ">=22"
|
|
50
|
+
},
|
|
51
|
+
"dependencies": {
|
|
52
|
+
"@modelcontextprotocol/sdk": "^1.20.2",
|
|
53
|
+
"vpndetection": "^1.7.0",
|
|
54
|
+
"zod": "^4.1.12"
|
|
55
|
+
},
|
|
56
|
+
"devDependencies": {
|
|
57
|
+
"@types/node": "^26.2.0",
|
|
58
|
+
"ajv": "^8.20.0",
|
|
59
|
+
"ajv-formats": "^3.0.1",
|
|
60
|
+
"typescript": "^5.9.3"
|
|
61
|
+
},
|
|
62
|
+
"scripts": {
|
|
63
|
+
"generate": "node scripts/gen-schema.mjs",
|
|
64
|
+
"build": "rm -rf dist && pnpm run generate && tsc",
|
|
65
|
+
"test": "pnpm run build && node --test test/*.test.mjs"
|
|
66
|
+
}
|
|
67
|
+
}
|