@voidly/mcp-server 1.0.0 → 1.1.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/README.md +49 -46
- package/dist/index.js +90 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,26 +1,20 @@
|
|
|
1
1
|
# Voidly MCP Server
|
|
2
2
|
|
|
3
|
-
Model Context Protocol (MCP) server for the **Voidly Global Censorship Index**. Enables AI systems
|
|
3
|
+
Model Context Protocol (MCP) server for the **Voidly Global Censorship Index**. Enables AI systems to query real-time internet censorship data.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Data Scale
|
|
6
6
|
|
|
7
|
-
- **
|
|
8
|
-
- **
|
|
9
|
-
- **
|
|
10
|
-
-
|
|
11
|
-
|
|
12
|
-
## Tools
|
|
13
|
-
|
|
14
|
-
| Tool | Description |
|
|
15
|
-
|------|-------------|
|
|
16
|
-
| `get_censorship_index` | Global overview of all monitored countries |
|
|
17
|
-
| `get_country_status` | Detailed status for a specific country |
|
|
18
|
-
| `check_domain_blocked` | Check if a domain is blocked in a country |
|
|
19
|
-
| `get_most_censored` | Ranked list of most censored countries |
|
|
20
|
-
| `get_active_incidents` | Currently active censorship incidents |
|
|
7
|
+
- **11.7M** live OONI measurements
|
|
8
|
+
- **1B+** historical measurements (10-year archive)
|
|
9
|
+
- **120+** countries monitored
|
|
10
|
+
- Updated every 5 minutes
|
|
21
11
|
|
|
22
12
|
## Installation
|
|
23
13
|
|
|
14
|
+
```bash
|
|
15
|
+
npx @voidly/mcp-server
|
|
16
|
+
```
|
|
17
|
+
|
|
24
18
|
### Claude Desktop
|
|
25
19
|
|
|
26
20
|
Add to `~/Library/Application Support/Claude/claude_desktop_config.json`:
|
|
@@ -29,37 +23,41 @@ Add to `~/Library/Application Support/Claude/claude_desktop_config.json`:
|
|
|
29
23
|
{
|
|
30
24
|
"mcpServers": {
|
|
31
25
|
"voidly": {
|
|
32
|
-
"command": "
|
|
33
|
-
"args": ["
|
|
26
|
+
"command": "npx",
|
|
27
|
+
"args": ["@voidly/mcp-server"]
|
|
34
28
|
}
|
|
35
29
|
}
|
|
36
30
|
}
|
|
37
31
|
```
|
|
38
32
|
|
|
39
|
-
###
|
|
33
|
+
### Cursor / Windsurf / Cline
|
|
40
34
|
|
|
41
|
-
Add to your
|
|
35
|
+
Add to your MCP config:
|
|
42
36
|
|
|
43
37
|
```json
|
|
44
38
|
{
|
|
45
39
|
"mcpServers": {
|
|
46
40
|
"voidly": {
|
|
47
|
-
"command": "
|
|
48
|
-
"args": ["
|
|
41
|
+
"command": "npx",
|
|
42
|
+
"args": ["@voidly/mcp-server"]
|
|
49
43
|
}
|
|
50
44
|
}
|
|
51
45
|
}
|
|
52
46
|
```
|
|
53
47
|
|
|
54
|
-
|
|
48
|
+
## Tools
|
|
55
49
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
50
|
+
| Tool | Description |
|
|
51
|
+
|------|-------------|
|
|
52
|
+
| `get_censorship_index` | Global overview of all monitored countries |
|
|
53
|
+
| `get_country_status` | Detailed status for a specific country |
|
|
54
|
+
| `check_domain_blocked` | Check if a domain is blocked in a country |
|
|
55
|
+
| `get_most_censored` | Ranked list of most censored countries |
|
|
56
|
+
| `get_active_incidents` | Currently active censorship incidents |
|
|
59
57
|
|
|
60
58
|
## Usage Examples
|
|
61
59
|
|
|
62
|
-
Once configured, you can ask
|
|
60
|
+
Once configured, you can ask:
|
|
63
61
|
|
|
64
62
|
- "What countries have the most internet censorship?"
|
|
65
63
|
- "Is the internet censored in China?"
|
|
@@ -67,38 +65,43 @@ Once configured, you can ask Claude:
|
|
|
67
65
|
- "Are there any active internet shutdowns?"
|
|
68
66
|
- "Is Twitter blocked in Russia?"
|
|
69
67
|
|
|
68
|
+
## Other AI Platforms
|
|
69
|
+
|
|
70
|
+
### OpenAI / ChatGPT
|
|
71
|
+
|
|
72
|
+
MCP isn't supported by OpenAI yet. Use our **OpenAI Action** instead:
|
|
73
|
+
|
|
74
|
+
1. Go to ChatGPT → Create GPT → Actions
|
|
75
|
+
2. Import [`openai-action/openapi.yaml`](../openai-action/openapi.yaml)
|
|
76
|
+
3. Your GPT can now query Voidly data
|
|
77
|
+
|
|
78
|
+
### Direct API
|
|
79
|
+
|
|
80
|
+
Public endpoints (no auth):
|
|
81
|
+
- `https://api.voidly.ai/data/censorship-index.json`
|
|
82
|
+
- `https://api.voidly.ai/data/country/{code}`
|
|
83
|
+
- `https://api.voidly.ai/data/methodology`
|
|
84
|
+
|
|
70
85
|
## Data Sources
|
|
71
86
|
|
|
72
87
|
- **Primary**: OONI (Open Observatory of Network Interference)
|
|
73
|
-
- **
|
|
74
|
-
- **Countries Monitored**: 50+
|
|
88
|
+
- **Secondary**: Voidly probe network
|
|
75
89
|
- **License**: CC BY 4.0
|
|
76
90
|
|
|
77
|
-
## API Endpoints Used
|
|
78
|
-
|
|
79
|
-
- `https://censorship.voidly.ai/v1/censorship-index`
|
|
80
|
-
- `https://censorship.voidly.ai/v1/censorship-index/:country`
|
|
81
|
-
- `https://censorship.voidly.ai/v1/censorship-index/incidents`
|
|
82
|
-
|
|
83
91
|
## Development
|
|
84
92
|
|
|
85
93
|
```bash
|
|
86
|
-
# Install dependencies
|
|
87
94
|
npm install
|
|
88
|
-
|
|
89
|
-
# Build
|
|
90
95
|
npm run build
|
|
91
|
-
|
|
92
|
-
# Run in development
|
|
93
96
|
npm run dev
|
|
94
97
|
```
|
|
95
98
|
|
|
96
|
-
##
|
|
99
|
+
## Links
|
|
97
100
|
|
|
98
|
-
|
|
101
|
+
- [Voidly Censorship Index](https://voidly.ai/censorship-index)
|
|
102
|
+
- [npm Package](https://www.npmjs.com/package/@voidly/mcp-server)
|
|
103
|
+
- [OpenAI Action Spec](../openai-action/)
|
|
99
104
|
|
|
100
|
-
##
|
|
105
|
+
## License
|
|
101
106
|
|
|
102
|
-
|
|
103
|
-
- [API Documentation](https://voidly.ai/api-docs)
|
|
104
|
-
- [Hugging Face Dataset](https://huggingface.co/datasets/emperor-mew/global-censorship-index)
|
|
107
|
+
MIT
|
package/dist/index.js
CHANGED
|
@@ -187,6 +187,72 @@ async function getActiveIncidents() {
|
|
|
187
187
|
result += `URL: https://voidly.ai/censorship-index\n`;
|
|
188
188
|
return result;
|
|
189
189
|
}
|
|
190
|
+
async function verifyClaim(claim, requireEvidence = false) {
|
|
191
|
+
const data = await fetchJson(`${VOIDLY_API}/v1/verify-claim?claim=${encodeURIComponent(claim)}&require_evidence=${requireEvidence}`);
|
|
192
|
+
let result = `# Claim Verification\n\n`;
|
|
193
|
+
result += `**Claim:** "${data.claim}"\n\n`;
|
|
194
|
+
// Verdict with emoji
|
|
195
|
+
const verdictEmoji = {
|
|
196
|
+
confirmed: '✅',
|
|
197
|
+
likely: '🟡',
|
|
198
|
+
unconfirmed: '❓',
|
|
199
|
+
no_data: '⚪',
|
|
200
|
+
insufficient_data: '⚠️',
|
|
201
|
+
};
|
|
202
|
+
result += `## Verdict: ${verdictEmoji[data.verdict] || ''} ${data.verdict.toUpperCase()}\n\n`;
|
|
203
|
+
result += `**Confidence:** ${(data.confidence * 100).toFixed(0)}%\n`;
|
|
204
|
+
result += `**Reason:** ${data.reason}\n\n`;
|
|
205
|
+
// Parsed components
|
|
206
|
+
result += `## Parsed Claim\n`;
|
|
207
|
+
if (data.parsed.country) {
|
|
208
|
+
result += `- Country: ${data.parsed.country} (${data.parsed.country_code})\n`;
|
|
209
|
+
}
|
|
210
|
+
if (data.parsed.service) {
|
|
211
|
+
result += `- Service: ${data.parsed.service}\n`;
|
|
212
|
+
}
|
|
213
|
+
if (data.parsed.date) {
|
|
214
|
+
result += `- Date: ${data.parsed.date}\n`;
|
|
215
|
+
}
|
|
216
|
+
if (data.parsed.date_range) {
|
|
217
|
+
result += `- Date Range: ${data.parsed.date_range.start} to ${data.parsed.date_range.end}\n`;
|
|
218
|
+
}
|
|
219
|
+
result += '\n';
|
|
220
|
+
// Matching incidents
|
|
221
|
+
if (data.incidents && data.incidents.length > 0) {
|
|
222
|
+
result += `## Supporting Incidents\n\n`;
|
|
223
|
+
data.incidents.forEach((inc, i) => {
|
|
224
|
+
result += `### ${i + 1}. ${inc.title}\n`;
|
|
225
|
+
result += `- ID: ${inc.id}\n`;
|
|
226
|
+
result += `- Status: ${inc.status}\n`;
|
|
227
|
+
result += `- Severity: ${inc.severity}\n`;
|
|
228
|
+
result += `- Confidence: ${(inc.confidence * 100).toFixed(0)}%\n`;
|
|
229
|
+
result += `- Started: ${inc.startTime.slice(0, 10)}\n`;
|
|
230
|
+
result += `- Permalink: ${inc.permalink}\n\n`;
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
// Evidence if requested
|
|
234
|
+
if (data.evidence && data.evidence.length > 0) {
|
|
235
|
+
result += `## Evidence Chain\n\n`;
|
|
236
|
+
data.evidence.forEach((ev, i) => {
|
|
237
|
+
result += `${i + 1}. **${ev.source.toUpperCase()}** (${ev.kind})\n`;
|
|
238
|
+
result += ` - Observed: ${ev.observedAt.slice(0, 10)}\n`;
|
|
239
|
+
result += ` - Confidence: ${(ev.confidence * 100).toFixed(0)}%\n`;
|
|
240
|
+
if (ev.permalink) {
|
|
241
|
+
result += ` - Verify: ${ev.permalink}\n`;
|
|
242
|
+
}
|
|
243
|
+
result += '\n';
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
// Citation
|
|
247
|
+
if (data.citation) {
|
|
248
|
+
result += `## Citation\n\n`;
|
|
249
|
+
result += `${data.citation}\n\n`;
|
|
250
|
+
}
|
|
251
|
+
result += `## Source\n`;
|
|
252
|
+
result += `Data: Voidly Research Claim Verification API\n`;
|
|
253
|
+
result += `License: CC BY 4.0\n`;
|
|
254
|
+
return result;
|
|
255
|
+
}
|
|
190
256
|
// Create MCP server
|
|
191
257
|
const server = new Server({
|
|
192
258
|
name: 'voidly-censorship-index',
|
|
@@ -264,6 +330,24 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
264
330
|
required: [],
|
|
265
331
|
},
|
|
266
332
|
},
|
|
333
|
+
{
|
|
334
|
+
name: 'verify_claim',
|
|
335
|
+
description: 'Verify a censorship claim with evidence. Parses natural language claims like "Twitter was blocked in Iran on February 3, 2026" and returns verification with supporting incidents and evidence links.',
|
|
336
|
+
inputSchema: {
|
|
337
|
+
type: 'object',
|
|
338
|
+
properties: {
|
|
339
|
+
claim: {
|
|
340
|
+
type: 'string',
|
|
341
|
+
description: 'Natural language censorship claim to verify (e.g., "Is YouTube blocked in China?", "Twitter was blocked in Iran on February 3, 2026")',
|
|
342
|
+
},
|
|
343
|
+
require_evidence: {
|
|
344
|
+
type: 'boolean',
|
|
345
|
+
description: 'Whether to include detailed evidence chain with source links (default: false)',
|
|
346
|
+
},
|
|
347
|
+
},
|
|
348
|
+
required: ['claim'],
|
|
349
|
+
},
|
|
350
|
+
},
|
|
267
351
|
],
|
|
268
352
|
}));
|
|
269
353
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
@@ -293,6 +377,12 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
293
377
|
case 'get_active_incidents':
|
|
294
378
|
result = await getActiveIncidents();
|
|
295
379
|
break;
|
|
380
|
+
case 'verify_claim':
|
|
381
|
+
if (!args?.claim) {
|
|
382
|
+
throw new Error('claim is required');
|
|
383
|
+
}
|
|
384
|
+
result = await verifyClaim(args.claim, args?.require_evidence || false);
|
|
385
|
+
break;
|
|
296
386
|
default:
|
|
297
387
|
throw new Error(`Unknown tool: ${name}`);
|
|
298
388
|
}
|
package/package.json
CHANGED