@voidly/mcp-server 1.0.1 → 1.1.1
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/dist/index.js +105 -3
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -16,8 +16,8 @@ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
|
16
16
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
17
17
|
import { CallToolRequestSchema, ListToolsRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
18
18
|
// Voidly API endpoints
|
|
19
|
-
const VOIDLY_API = 'https://
|
|
20
|
-
const VOIDLY_DATA_API = 'https://voidly.ai/
|
|
19
|
+
const VOIDLY_API = 'https://api.voidly.ai';
|
|
20
|
+
const VOIDLY_DATA_API = 'https://api.voidly.ai/data';
|
|
21
21
|
// Country metadata for enriching responses
|
|
22
22
|
const COUNTRY_NAMES = {
|
|
23
23
|
CN: 'China', IR: 'Iran', RU: 'Russia', VE: 'Venezuela', CU: 'Cuba',
|
|
@@ -161,7 +161,7 @@ async function getMostCensored(limit = 10) {
|
|
|
161
161
|
return result;
|
|
162
162
|
}
|
|
163
163
|
async function getActiveIncidents() {
|
|
164
|
-
const data = await fetchJson(`${
|
|
164
|
+
const data = await fetchJson(`${VOIDLY_DATA_API}/incidents?status=active&limit=50`);
|
|
165
165
|
let result = `# Active Censorship Incidents\n\n`;
|
|
166
166
|
result += `Total: ${data.count} incidents\n\n`;
|
|
167
167
|
if (data.incidents.length === 0) {
|
|
@@ -187,6 +187,84 @@ 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
|
+
// Use POST for verify-claim
|
|
192
|
+
const response = await fetch(`${VOIDLY_API}/verify-claim`, {
|
|
193
|
+
method: 'POST',
|
|
194
|
+
headers: {
|
|
195
|
+
'Content-Type': 'application/json',
|
|
196
|
+
'User-Agent': 'Voidly-MCP-Server/1.0',
|
|
197
|
+
},
|
|
198
|
+
body: JSON.stringify({ claim, require_evidence: requireEvidence }),
|
|
199
|
+
});
|
|
200
|
+
if (!response.ok) {
|
|
201
|
+
throw new Error(`API request failed: ${response.status} ${response.statusText}`);
|
|
202
|
+
}
|
|
203
|
+
const data = await response.json();
|
|
204
|
+
let result = `# Claim Verification\n\n`;
|
|
205
|
+
result += `**Claim:** "${data.claim}"\n\n`;
|
|
206
|
+
// Verdict with emoji
|
|
207
|
+
const verdictEmoji = {
|
|
208
|
+
confirmed: '✅',
|
|
209
|
+
likely: '🟡',
|
|
210
|
+
unconfirmed: '❓',
|
|
211
|
+
no_data: '⚪',
|
|
212
|
+
insufficient_data: '⚠️',
|
|
213
|
+
};
|
|
214
|
+
result += `## Verdict: ${verdictEmoji[data.verdict] || ''} ${data.verdict.toUpperCase()}\n\n`;
|
|
215
|
+
result += `**Confidence:** ${(data.confidence * 100).toFixed(0)}%\n`;
|
|
216
|
+
result += `**Reason:** ${data.reason}\n\n`;
|
|
217
|
+
// Parsed components
|
|
218
|
+
result += `## Parsed Claim\n`;
|
|
219
|
+
if (data.parsed.country) {
|
|
220
|
+
result += `- Country: ${data.parsed.country} (${data.parsed.country_code})\n`;
|
|
221
|
+
}
|
|
222
|
+
if (data.parsed.service) {
|
|
223
|
+
result += `- Service: ${data.parsed.service}\n`;
|
|
224
|
+
}
|
|
225
|
+
if (data.parsed.date) {
|
|
226
|
+
result += `- Date: ${data.parsed.date}\n`;
|
|
227
|
+
}
|
|
228
|
+
if (data.parsed.date_range) {
|
|
229
|
+
result += `- Date Range: ${data.parsed.date_range.start} to ${data.parsed.date_range.end}\n`;
|
|
230
|
+
}
|
|
231
|
+
result += '\n';
|
|
232
|
+
// Matching incidents
|
|
233
|
+
if (data.incidents && data.incidents.length > 0) {
|
|
234
|
+
result += `## Supporting Incidents\n\n`;
|
|
235
|
+
data.incidents.forEach((inc, i) => {
|
|
236
|
+
result += `### ${i + 1}. ${inc.title}\n`;
|
|
237
|
+
result += `- ID: ${inc.id}\n`;
|
|
238
|
+
result += `- Status: ${inc.status}\n`;
|
|
239
|
+
result += `- Severity: ${inc.severity}\n`;
|
|
240
|
+
result += `- Confidence: ${(inc.confidence * 100).toFixed(0)}%\n`;
|
|
241
|
+
result += `- Started: ${inc.startTime.slice(0, 10)}\n`;
|
|
242
|
+
result += `- Permalink: ${inc.permalink}\n\n`;
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
// Evidence if requested
|
|
246
|
+
if (data.evidence && data.evidence.length > 0) {
|
|
247
|
+
result += `## Evidence Chain\n\n`;
|
|
248
|
+
data.evidence.forEach((ev, i) => {
|
|
249
|
+
result += `${i + 1}. **${ev.source.toUpperCase()}** (${ev.kind})\n`;
|
|
250
|
+
result += ` - Observed: ${ev.observedAt.slice(0, 10)}\n`;
|
|
251
|
+
result += ` - Confidence: ${(ev.confidence * 100).toFixed(0)}%\n`;
|
|
252
|
+
if (ev.permalink) {
|
|
253
|
+
result += ` - Verify: ${ev.permalink}\n`;
|
|
254
|
+
}
|
|
255
|
+
result += '\n';
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
// Citation
|
|
259
|
+
if (data.citation) {
|
|
260
|
+
result += `## Citation\n\n`;
|
|
261
|
+
result += `${data.citation}\n\n`;
|
|
262
|
+
}
|
|
263
|
+
result += `## Source\n`;
|
|
264
|
+
result += `Data: Voidly Research Claim Verification API\n`;
|
|
265
|
+
result += `License: CC BY 4.0\n`;
|
|
266
|
+
return result;
|
|
267
|
+
}
|
|
190
268
|
// Create MCP server
|
|
191
269
|
const server = new Server({
|
|
192
270
|
name: 'voidly-censorship-index',
|
|
@@ -264,6 +342,24 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
264
342
|
required: [],
|
|
265
343
|
},
|
|
266
344
|
},
|
|
345
|
+
{
|
|
346
|
+
name: 'verify_claim',
|
|
347
|
+
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.',
|
|
348
|
+
inputSchema: {
|
|
349
|
+
type: 'object',
|
|
350
|
+
properties: {
|
|
351
|
+
claim: {
|
|
352
|
+
type: 'string',
|
|
353
|
+
description: 'Natural language censorship claim to verify (e.g., "Is YouTube blocked in China?", "Twitter was blocked in Iran on February 3, 2026")',
|
|
354
|
+
},
|
|
355
|
+
require_evidence: {
|
|
356
|
+
type: 'boolean',
|
|
357
|
+
description: 'Whether to include detailed evidence chain with source links (default: false)',
|
|
358
|
+
},
|
|
359
|
+
},
|
|
360
|
+
required: ['claim'],
|
|
361
|
+
},
|
|
362
|
+
},
|
|
267
363
|
],
|
|
268
364
|
}));
|
|
269
365
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
@@ -293,6 +389,12 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
293
389
|
case 'get_active_incidents':
|
|
294
390
|
result = await getActiveIncidents();
|
|
295
391
|
break;
|
|
392
|
+
case 'verify_claim':
|
|
393
|
+
if (!args?.claim) {
|
|
394
|
+
throw new Error('claim is required');
|
|
395
|
+
}
|
|
396
|
+
result = await verifyClaim(args.claim, args?.require_evidence || false);
|
|
397
|
+
break;
|
|
296
398
|
default:
|
|
297
399
|
throw new Error(`Unknown tool: ${name}`);
|
|
298
400
|
}
|
package/package.json
CHANGED