@cnwhy/qqwry-mcp
Version:
MCP server for QQWry IP database query
269 lines (251 loc) • 7.94 kB
JavaScript
import { McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';
import libqqwry from 'lib-qqwry';
import { z } from 'zod';
import pkg from './package.json' with { type: "json" };
const myDataPath = process.env['QQWRY_DATA_PATH'];
let qqwry = libqqwry(true, myDataPath);
// Create an MCP server
const mcpServer = new McpServer({
name: pkg.name,
version: pkg.version,
});
// Add an query_ip tool
mcpServer.registerTool(
'query_ip',
{
title: '查询IP地址工具',
description: '通过IP检索纯真IP库,获取IP地址的归属地信息; 通过查询255.255.255.255,可以查询纯真IP库的版本信息;',
inputSchema: { ip: z.string() },
outputSchema: {
result: z.object({
int: z.number(),
ip: z.string(),
Country: z.string(),
Area: z.string()
})
}
},
async ({ ip }) => {
try {
const result = qqwry.searchIP(ip);
const output = {
result: {
int: result.int || 0,
ip: result.ip || ip,
Country: result.Country || '',
Area: result.Area || ''
}
};
return {
content: [{ type: 'text', text: JSON.stringify(output) }],
structuredContent: output
};
} catch (error) {
throw new Error(`Error querying IP: ${error.message}`);
}
}
);
mcpServer.registerTool(
'query_ip_scope',
{
title: '查询IP段的地址',
description: '通过IP检索纯真IP库,查询IP段内的所有IP地址的归属地信息,结果以IP段的形式返回',
inputSchema: {
beginIP: z.string(),
endIP: z.string(),
},
outputSchema: {
result: z.array(z.object({
begInt: z.number(),
endInt: z.number(),
begIP: z.string(),
endIP: z.string(),
Country: z.string(),
Area: z.string(),
}))
}
},
async ({ beginIP, endIP }) => {
try {
const result = qqwry.searchIPScope(beginIP, endIP);
// fix the begIP
const output = {
result: result.map(item => {
// item.beginIP = item.bginIP
// delete item.bginIP
return item;
})
};
return {
content: [{ type: 'text', text: JSON.stringify(output) }],
structuredContent: output
};
} catch (error) {
throw new Error(`Error querying IP: ${error.message}`);
}
}
);
mcpServer.registerTool(
'get_ip_use_keyword',
{
title: '通过关键字查询IP',
description: '通过检索纯真IP库,查询符合关键字的所有IP段',
inputSchema: {
keywords: z.array(z.string()),
mode: z.enum(['any', 'all']),
ignoreCase: z.boolean().default(true),
},
outputSchema: {
result: z.array(z.object({
begInt: z.number(),
endInt: z.number(),
begIP: z.string(),
endIP: z.string(),
Country: z.string(),
Area: z.string(),
}))
}
},
async ({ keywords, ignoreCase, mode }) => {
try {
const result = await new Promise((resolve, reject) => {
const result = [];
const isMatch = (function () {
try {
// 过滤掉 keywords 数组中非字符串的项,防止 .includes 出错
let validKeywords = keywords.filter(k => typeof k === 'string');
validKeywords = ignoreCase ? validKeywords.map(k => k.toLowerCase()) : validKeywords;
// 根据 mode 执行核心逻辑
if (mode === 'any') {
// "命中一个": 使用 Array.prototype.some()
return function (str) {
const targetStr = ignoreCase ? str.toLowerCase() : str;
return validKeywords.some((keyword) => targetStr.includes(keyword))
}
} else if (mode === 'all') {
// "全都要命中": 使用 Array.prototype.every()
return function (str) {
const targetStr = ignoreCase ? str.toLowerCase() : str;
return validKeywords.every((keyword) => targetStr.includes(keyword))
}
} else {
// 无效 mode 处理
console.error(`无效的 mode: "${mode}"。 mode 必须是 'any' 或 'all'。`);
return false;
}
} catch (error) {
reject(error);
}
// let _isMatch;
// if (ignoreCase) {
// keyword = keyword.map(v => v.toLocaleLowerCase());
// _isMatch = function (str, key) {
// return ~str.toLocaleLowerCase().indexOf(key);
// };
// } else {
// _isMatch = function (str, key) {
// return str.indexOf(key) != -1;
// };
// }
// return str => {
// for (let key of keyword) {
// if (_isMatch(str, key)) return true;
// }
// };
})();
qqwry.searchIPScopeStream(0, 0xffffffff, { format: 'object', outHeader: true })
.on('data', function (obj) {
if (isMatch(obj[4] + obj[5])) {
const [begInt, endInt, begIP, endIP, Country, Area] = obj;
result.push({ begInt, endInt, begIP, endIP, Country, Area });
return;
}
})
.on('end', function () {
resolve(result)
});
})
// const result = qqwry.searchIPScope(beginIP, endIP);
// // fix the begIP
const output = {
result: result.map(item => {
// item.beginIP = item.bginIP
// delete item.bginIP
return item;
})
};
return {
content: [{ type: 'text', text: JSON.stringify(output) }],
structuredContent: output
};
} catch (error) {
throw new Error(`Error querying IP: ${error.message}`);
}
}
);
mcpServer.registerTool(
'chinge_ip_data_path',
{
title: '切换本工具使用的纯真IP库文件',
description: '用户可以指定要切换的纯真IP库文件; 如果不指定文件路径, 则使用默认的纯真IP库文件',
inputSchema: {
dataPath: z.string().nullable(),
},
outputSchema: {
message: z.string(),
}
},
async ({ dataPath }) => {
const _oldQqwry = qqwry;
let msg = '切换成功!';
try {
qqwry = new libqqwry(true, dataPath);
const info = qqwry.searchIP('255.255.255.255');
msg += `当前IP库版本为: ${info.Area}`
} catch (error) {
qqwry = _oldQqwry;
msg = `切换失败!error: ${ error.message || '位置错误!'}; 请检查文件路径是否正确!只支持原始的纯真IP库文件,一般文件名为 qqwry.dat`;
}
return {
content: [{ type: 'text', text: msg }],
structuredContent: { message: msg }
};
}
);
// // Add a dynamic greeting resource
// mcpServer.registerResource(
// 'greeting',
// new ResourceTemplate('greeting://{name}', { list: undefined }),
// {
// title: 'Greeting Resource', // Display name for UI
// description: 'Dynamic greeting generator'
// },
// async (uri, { name }) => ({
// contents: [
// {
// uri: uri.href,
// text: `Hello, ${name}!`
// }
// ]
// })
// );
// mcpServer.registerPrompt(
// 'greeting',
// {
// title: 'Greeting Prompt',
// description: 'A simple greeting prompt',
// argsSchema: z.object({
// name: z.string()
// })
// },
// async ({ name }) => ({
// content: [
// {
// type: 'text',
// text: `Hello, ${name}!`
// }
// ]
// })
// )
export default mcpServer