arl-next-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/README.md +36 -0
- package/index.js +183 -0
- package/package.json +25 -0
package/README.md
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# ARL-Next MCP Server
|
|
2
|
+
|
|
3
|
+
这是 [ARL-Next](https://github.com/owl234/ARL-Next) 的官方 MCP (Model Context Protocol) 服务代理。
|
|
4
|
+
|
|
5
|
+
该模块作为一个中间代理,允许 AI 助手(如 Cursor, Claude Desktop 等)安全地通过你的本地环境与远端的 ARL-Next 服务进行交互,**无需在公网服务端暴露额外端口**。
|
|
6
|
+
|
|
7
|
+
## 🚀 快速接入
|
|
8
|
+
|
|
9
|
+
**完全无需本地安装!** 只需在你的 AI 客户端(如 Cursor 或 Claude)的 MCP 配置文件中添加以下内容:
|
|
10
|
+
|
|
11
|
+
```json
|
|
12
|
+
{
|
|
13
|
+
"mcpServers": {
|
|
14
|
+
"ARL-Next": {
|
|
15
|
+
"command": "npx",
|
|
16
|
+
"args": [
|
|
17
|
+
"-y",
|
|
18
|
+
"@arl-next/mcp"
|
|
19
|
+
],
|
|
20
|
+
"env": {
|
|
21
|
+
"ARL_HOST": "https://你的服务器IP",
|
|
22
|
+
"ARL_TOKEN": "你的API_TOKEN"
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
3. **重启加载**:
|
|
29
|
+
保存文件后,重启您的 Antigravity IDE 即可。当您在侧边栏或代码助手对话时,就可以直接让它去“搜索 ARL 最新资产”了。
|
|
30
|
+
|
|
31
|
+
## 🛠️ 当前支持的 Tools
|
|
32
|
+
|
|
33
|
+
1. `search_assets`:从资产库中智能查询资产(支持 `site`, `ip`, `domain`, `cert`, `service` 类型)。
|
|
34
|
+
2. `get_tasks`:获取最近的扫描任务状态。
|
|
35
|
+
|
|
36
|
+
更多自动化漏洞分析和下发任务功能持续更新中...
|
package/index.js
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { Server } = require("@modelcontextprotocol/sdk/server/index.js");
|
|
4
|
+
const { StdioServerTransport } = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
5
|
+
const {
|
|
6
|
+
CallToolRequestSchema,
|
|
7
|
+
ListToolsRequestSchema,
|
|
8
|
+
} = require("@modelcontextprotocol/sdk/types.js");
|
|
9
|
+
const axios = require("axios");
|
|
10
|
+
const { program } = require("commander");
|
|
11
|
+
|
|
12
|
+
program
|
|
13
|
+
.name("arl-mcp")
|
|
14
|
+
.description("MCP Server for ARL-Next")
|
|
15
|
+
.option("--host <host>", "ARL-Next server host URL (e.g. https://1.1.1.1:5173)")
|
|
16
|
+
.option("--token <token>", "ARL-Next API token")
|
|
17
|
+
.parse(process.argv);
|
|
18
|
+
|
|
19
|
+
const options = program.opts();
|
|
20
|
+
const host = options.host || process.env.ARL_HOST;
|
|
21
|
+
const token = options.token || process.env.ARL_TOKEN;
|
|
22
|
+
|
|
23
|
+
if (!host || !token) {
|
|
24
|
+
console.error("Error: --host and --token are required. Provide them via CLI or environment variables (ARL_HOST, ARL_TOKEN).");
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const https = require("https");
|
|
29
|
+
|
|
30
|
+
const apiClient = axios.create({
|
|
31
|
+
baseURL: `${host}/api`,
|
|
32
|
+
headers: {
|
|
33
|
+
"Token": token
|
|
34
|
+
},
|
|
35
|
+
timeout: 10000,
|
|
36
|
+
httpsAgent: new https.Agent({
|
|
37
|
+
rejectUnauthorized: false
|
|
38
|
+
})
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const server = new Server(
|
|
42
|
+
{
|
|
43
|
+
name: "arl-next-mcp",
|
|
44
|
+
version: "1.0.0",
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
capabilities: {
|
|
48
|
+
tools: {},
|
|
49
|
+
},
|
|
50
|
+
}
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
// Define tools
|
|
54
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
55
|
+
return {
|
|
56
|
+
tools: [
|
|
57
|
+
{
|
|
58
|
+
name: "search_assets",
|
|
59
|
+
description: "Search for discovered assets (sites, IPs, domains) in ARL-Next.",
|
|
60
|
+
inputSchema: {
|
|
61
|
+
type: "object",
|
|
62
|
+
properties: {
|
|
63
|
+
assetType: {
|
|
64
|
+
type: "string",
|
|
65
|
+
description: "Type of asset to search: 'site', 'ip', 'domain', 'cert', 'service'",
|
|
66
|
+
enum: ["site", "ip", "domain", "cert", "service"]
|
|
67
|
+
},
|
|
68
|
+
query: {
|
|
69
|
+
type: "string",
|
|
70
|
+
description: "Search keyword (e.g. 'nginx', '1.1.1.1', 'example.com')"
|
|
71
|
+
},
|
|
72
|
+
page: {
|
|
73
|
+
type: "number",
|
|
74
|
+
description: "Page number (default 1)"
|
|
75
|
+
},
|
|
76
|
+
size: {
|
|
77
|
+
type: "number",
|
|
78
|
+
description: "Page size (default 10, max 50 for safety)"
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
required: ["assetType"]
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
name: "get_tasks",
|
|
86
|
+
description: "Get recent scanning tasks and their status.",
|
|
87
|
+
inputSchema: {
|
|
88
|
+
type: "object",
|
|
89
|
+
properties: {
|
|
90
|
+
page: { type: "number", description: "Page number (default 1)" },
|
|
91
|
+
size: { type: "number", description: "Page size (default 10)" }
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
],
|
|
96
|
+
};
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// Handle tool execution
|
|
100
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
101
|
+
if (request.params.name === "search_assets") {
|
|
102
|
+
const { assetType, query, page = 1, size = 10 } = request.params.arguments;
|
|
103
|
+
|
|
104
|
+
// Security Fix: Validate assetType to prevent path traversal
|
|
105
|
+
const allowedAssets = ["site", "ip", "domain", "cert", "service"];
|
|
106
|
+
if (!allowedAssets.includes(assetType)) {
|
|
107
|
+
return {
|
|
108
|
+
content: [{ type: "text", text: `Error: Invalid assetType '${assetType}'` }],
|
|
109
|
+
isError: true
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const safeSize = Math.min(size, 50); // Enforce max 50 to prevent OOM
|
|
114
|
+
|
|
115
|
+
try {
|
|
116
|
+
const response = await apiClient.get(`/${assetType}/`, {
|
|
117
|
+
params: {
|
|
118
|
+
page: page,
|
|
119
|
+
size: safeSize,
|
|
120
|
+
query: query
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
return {
|
|
124
|
+
content: [
|
|
125
|
+
{
|
|
126
|
+
type: "text",
|
|
127
|
+
text: JSON.stringify(response.data, null, 2)
|
|
128
|
+
}
|
|
129
|
+
]
|
|
130
|
+
};
|
|
131
|
+
} catch (error) {
|
|
132
|
+
return {
|
|
133
|
+
content: [
|
|
134
|
+
{
|
|
135
|
+
type: "text",
|
|
136
|
+
text: `API Request Failed: ${error.message}\n${error.response?.data ? JSON.stringify(error.response.data) : ''}`
|
|
137
|
+
}
|
|
138
|
+
],
|
|
139
|
+
isError: true
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (request.params.name === "get_tasks") {
|
|
145
|
+
const { page = 1, size = 10 } = request.params.arguments;
|
|
146
|
+
try {
|
|
147
|
+
const response = await apiClient.get(`/task/`, {
|
|
148
|
+
params: { page, size }
|
|
149
|
+
});
|
|
150
|
+
return {
|
|
151
|
+
content: [
|
|
152
|
+
{
|
|
153
|
+
type: "text",
|
|
154
|
+
text: JSON.stringify(response.data, null, 2)
|
|
155
|
+
}
|
|
156
|
+
]
|
|
157
|
+
};
|
|
158
|
+
} catch (error) {
|
|
159
|
+
return {
|
|
160
|
+
content: [
|
|
161
|
+
{
|
|
162
|
+
type: "text",
|
|
163
|
+
text: `API Request Failed: ${error.message}\n${error.response?.data ? JSON.stringify(error.response.data) : ''}`
|
|
164
|
+
}
|
|
165
|
+
],
|
|
166
|
+
isError: true
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
throw new Error("Unknown tool");
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
async function run() {
|
|
175
|
+
const transport = new StdioServerTransport();
|
|
176
|
+
await server.connect(transport);
|
|
177
|
+
console.error("ARL-Next MCP Server running on stdio");
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
run().catch((error) => {
|
|
181
|
+
console.error("Fatal error running MCP server:", error);
|
|
182
|
+
process.exit(1);
|
|
183
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "arl-next-mcp",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "ARL-Next MCP Server",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"arl-next-mcp": "index.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"mcp",
|
|
14
|
+
"arl-next",
|
|
15
|
+
"security"
|
|
16
|
+
],
|
|
17
|
+
"author": "ARL-Next",
|
|
18
|
+
"license": "ISC",
|
|
19
|
+
"type": "commonjs",
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
22
|
+
"axios": "^1.18.1",
|
|
23
|
+
"commander": "^15.0.0"
|
|
24
|
+
}
|
|
25
|
+
}
|