shieldapi-mcp 1.0.2 → 2.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/dist/index.js +53 -8
- package/package.json +2 -2
- package/src/index.ts +65 -8
package/dist/index.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* ShieldAPI MCP Server
|
|
3
|
+
* ShieldAPI MCP Server (v2.0.0 — Phase 2)
|
|
4
4
|
*
|
|
5
5
|
* Exposes ShieldAPI security intelligence as native MCP tools.
|
|
6
6
|
* Handles x402 USDC micropayments automatically, with demo fallback.
|
|
7
|
+
*
|
|
8
|
+
* Phase 2 adds: scan_skill, check_prompt
|
|
7
9
|
*/
|
|
8
10
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
9
11
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
@@ -64,13 +66,11 @@ async function initPaymentFetch() {
|
|
|
64
66
|
chain: base,
|
|
65
67
|
transport: http(),
|
|
66
68
|
}).extend(publicActions);
|
|
67
|
-
// Type assertion needed: viem walletClient.extend(publicActions) is structurally compatible
|
|
68
|
-
// but TypeScript can't prove it due to complex generic types in viem + @x402/evm
|
|
69
69
|
const signer = toClientEvmSigner(walletClient);
|
|
70
70
|
const client = new x402Client().register(`eip155:${base.id}`, new ExactEvmScheme(signer));
|
|
71
71
|
paymentFetch = wrapFetchWithPayment(fetch, client);
|
|
72
72
|
}
|
|
73
|
-
// --- API
|
|
73
|
+
// --- API callers ---
|
|
74
74
|
async function callShieldApi(endpoint, params) {
|
|
75
75
|
const url = new URL(`${SHIELDAPI_URL}/api/${endpoint}`);
|
|
76
76
|
for (const [key, value] of Object.entries(params)) {
|
|
@@ -86,6 +86,22 @@ async function callShieldApi(endpoint, params) {
|
|
|
86
86
|
}
|
|
87
87
|
return response.json();
|
|
88
88
|
}
|
|
89
|
+
async function callShieldApiPost(endpoint, body) {
|
|
90
|
+
const url = new URL(`${SHIELDAPI_URL}/api/${endpoint}`);
|
|
91
|
+
if (demoMode) {
|
|
92
|
+
url.searchParams.set('demo', 'true');
|
|
93
|
+
}
|
|
94
|
+
const response = await paymentFetch(url.toString(), {
|
|
95
|
+
method: 'POST',
|
|
96
|
+
headers: { 'Content-Type': 'application/json' },
|
|
97
|
+
body: JSON.stringify(body),
|
|
98
|
+
});
|
|
99
|
+
if (!response.ok) {
|
|
100
|
+
const body = await response.text();
|
|
101
|
+
throw new Error(`ShieldAPI ${endpoint} failed (${response.status}): ${body.substring(0, 200)}`);
|
|
102
|
+
}
|
|
103
|
+
return response.json();
|
|
104
|
+
}
|
|
89
105
|
function detectTargetType(target) {
|
|
90
106
|
if (target.includes('@'))
|
|
91
107
|
return { email: target };
|
|
@@ -103,20 +119,49 @@ function formatResult(data) {
|
|
|
103
119
|
// --- MCP Server ---
|
|
104
120
|
const server = new McpServer({
|
|
105
121
|
name: 'ShieldAPI',
|
|
106
|
-
version: '
|
|
122
|
+
version: '2.0.0',
|
|
107
123
|
});
|
|
108
|
-
// Register standard tools from config
|
|
124
|
+
// Register standard GET tools from config
|
|
109
125
|
for (const [name, def] of Object.entries(TOOLS)) {
|
|
110
126
|
server.tool(name, def.description, { [def.param]: z.string().describe(def.paramDesc) }, async (params) => formatResult(await callShieldApi(def.endpoint, params)));
|
|
111
127
|
}
|
|
112
|
-
// full_scan
|
|
128
|
+
// full_scan — single 'target' param mapped to the correct server param
|
|
113
129
|
server.tool('full_scan', 'Run all security checks on a target (URL, domain, IP, or email). Most comprehensive scan.', { target: z.string().describe('Target to scan — URL, domain, IP address, or email') }, async ({ target }) => formatResult(await callShieldApi('full-scan', detectTargetType(target))));
|
|
130
|
+
// ================================================================
|
|
131
|
+
// Phase 2 Tools (POST endpoints)
|
|
132
|
+
// ================================================================
|
|
133
|
+
// scan_skill — AI skill supply chain security scanner
|
|
134
|
+
server.tool('scan_skill', 'Scan an AI agent skill/plugin for security issues across 8 risk categories (Snyk ToxicSkills taxonomy). Checks for prompt injection, malicious code, suspicious downloads, credential handling, secret detection, third-party content, unverifiable dependencies, and financial access patterns. Static analysis only — no code execution. Returns risk score (0-100), severity-ranked findings with file locations, and human-readable summary.', {
|
|
135
|
+
skill: z.string().optional().describe('Raw SKILL.md content or skill name from ClawHub'),
|
|
136
|
+
files: z.array(z.object({
|
|
137
|
+
name: z.string().describe('Filename including extension'),
|
|
138
|
+
content: z.string().describe('File content as string'),
|
|
139
|
+
})).optional().describe('Additional code files to analyze (max 20 files)'),
|
|
140
|
+
}, async (params) => {
|
|
141
|
+
const body = {};
|
|
142
|
+
if (params.skill)
|
|
143
|
+
body.skill = params.skill;
|
|
144
|
+
if (params.files)
|
|
145
|
+
body.files = params.files;
|
|
146
|
+
return formatResult(await callShieldApiPost('scan-skill', body));
|
|
147
|
+
});
|
|
148
|
+
// check_prompt — Prompt injection detection
|
|
149
|
+
server.tool('check_prompt', 'Detect prompt injection in text. Analyzes across 4 categories (direct injection, encoding tricks, exfiltration, indirect injection) with 200+ detection patterns. Designed for real-time inline usage before processing untrusted user input. Returns boolean verdict, confidence score (0-1), matched patterns with evidence, and decoded content if encoding obfuscation was detected. Response time <100ms p95.', {
|
|
150
|
+
prompt: z.string().describe('The text to analyze for prompt injection'),
|
|
151
|
+
context: z.enum(['user-input', 'skill-prompt', 'system-prompt']).optional()
|
|
152
|
+
.describe('Context hint for sensitivity: user-input (default), skill-prompt (higher tolerance), system-prompt (highest sensitivity)'),
|
|
153
|
+
}, async (params) => {
|
|
154
|
+
const body = { prompt: params.prompt };
|
|
155
|
+
if (params.context)
|
|
156
|
+
body.context = params.context;
|
|
157
|
+
return formatResult(await callShieldApiPost('check-prompt', body));
|
|
158
|
+
});
|
|
114
159
|
// --- Start ---
|
|
115
160
|
async function main() {
|
|
116
161
|
await initPaymentFetch();
|
|
117
162
|
const transport = new StdioServerTransport();
|
|
118
163
|
await server.connect(transport);
|
|
119
|
-
console.error(`ShieldAPI MCP server running (${demoMode ? 'DEMO mode' : 'PAID mode'})`);
|
|
164
|
+
console.error(`ShieldAPI MCP server v2.0.0 running (${demoMode ? 'DEMO mode' : 'PAID mode'})`);
|
|
120
165
|
}
|
|
121
166
|
main().catch((err) => {
|
|
122
167
|
console.error('Fatal:', err);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "shieldapi-mcp",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "MCP server for ShieldAPI",
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "MCP server for ShieldAPI — URL scanning, breach detection, domain/IP reputation as AI agent tools. Pay-per-request with USDC micropayments via x402.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"shieldapi-mcp": "dist/index.js"
|
package/src/index.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* ShieldAPI MCP Server
|
|
3
|
+
* ShieldAPI MCP Server (v2.0.0 — Phase 2)
|
|
4
4
|
*
|
|
5
5
|
* Exposes ShieldAPI security intelligence as native MCP tools.
|
|
6
6
|
* Handles x402 USDC micropayments automatically, with demo fallback.
|
|
7
|
+
*
|
|
8
|
+
* Phase 2 adds: scan_skill, check_prompt
|
|
7
9
|
*/
|
|
8
10
|
|
|
9
11
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
@@ -85,8 +87,6 @@ async function initPaymentFetch(): Promise<void> {
|
|
|
85
87
|
transport: http(),
|
|
86
88
|
}).extend(publicActions);
|
|
87
89
|
|
|
88
|
-
// Type assertion needed: viem walletClient.extend(publicActions) is structurally compatible
|
|
89
|
-
// but TypeScript can't prove it due to complex generic types in viem + @x402/evm
|
|
90
90
|
const signer = toClientEvmSigner(walletClient as unknown as Parameters<typeof toClientEvmSigner>[0]);
|
|
91
91
|
const client = new x402Client().register(
|
|
92
92
|
`eip155:${base.id}`,
|
|
@@ -96,7 +96,7 @@ async function initPaymentFetch(): Promise<void> {
|
|
|
96
96
|
paymentFetch = wrapFetchWithPayment(fetch, client);
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
-
// --- API
|
|
99
|
+
// --- API callers ---
|
|
100
100
|
|
|
101
101
|
async function callShieldApi(endpoint: string, params: Record<string, string>): Promise<unknown> {
|
|
102
102
|
const url = new URL(`${SHIELDAPI_URL}/api/${endpoint}`);
|
|
@@ -115,6 +115,24 @@ async function callShieldApi(endpoint: string, params: Record<string, string>):
|
|
|
115
115
|
return response.json();
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
+
async function callShieldApiPost(endpoint: string, body: Record<string, unknown>): Promise<unknown> {
|
|
119
|
+
const url = new URL(`${SHIELDAPI_URL}/api/${endpoint}`);
|
|
120
|
+
if (demoMode) {
|
|
121
|
+
url.searchParams.set('demo', 'true');
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const response = await paymentFetch(url.toString(), {
|
|
125
|
+
method: 'POST',
|
|
126
|
+
headers: { 'Content-Type': 'application/json' },
|
|
127
|
+
body: JSON.stringify(body),
|
|
128
|
+
});
|
|
129
|
+
if (!response.ok) {
|
|
130
|
+
const body = await response.text();
|
|
131
|
+
throw new Error(`ShieldAPI ${endpoint} failed (${response.status}): ${body.substring(0, 200)}`);
|
|
132
|
+
}
|
|
133
|
+
return response.json();
|
|
134
|
+
}
|
|
135
|
+
|
|
118
136
|
function detectTargetType(target: string): Record<string, string> {
|
|
119
137
|
if (target.includes('@')) return { email: target };
|
|
120
138
|
if (/^\d+\.\d+\.\d+\.\d+$/.test(target)) return { ip: target };
|
|
@@ -132,10 +150,10 @@ function formatResult(data: unknown): { content: Array<{ type: 'text'; text: str
|
|
|
132
150
|
|
|
133
151
|
const server = new McpServer({
|
|
134
152
|
name: 'ShieldAPI',
|
|
135
|
-
version: '
|
|
153
|
+
version: '2.0.0',
|
|
136
154
|
});
|
|
137
155
|
|
|
138
|
-
// Register standard tools from config
|
|
156
|
+
// Register standard GET tools from config
|
|
139
157
|
for (const [name, def] of Object.entries(TOOLS)) {
|
|
140
158
|
server.tool(
|
|
141
159
|
name,
|
|
@@ -145,7 +163,7 @@ for (const [name, def] of Object.entries(TOOLS)) {
|
|
|
145
163
|
);
|
|
146
164
|
}
|
|
147
165
|
|
|
148
|
-
// full_scan
|
|
166
|
+
// full_scan — single 'target' param mapped to the correct server param
|
|
149
167
|
server.tool(
|
|
150
168
|
'full_scan',
|
|
151
169
|
'Run all security checks on a target (URL, domain, IP, or email). Most comprehensive scan.',
|
|
@@ -153,13 +171,52 @@ server.tool(
|
|
|
153
171
|
async ({ target }) => formatResult(await callShieldApi('full-scan', detectTargetType(target)))
|
|
154
172
|
);
|
|
155
173
|
|
|
174
|
+
// ================================================================
|
|
175
|
+
// Phase 2 Tools (POST endpoints)
|
|
176
|
+
// ================================================================
|
|
177
|
+
|
|
178
|
+
// scan_skill — AI skill supply chain security scanner
|
|
179
|
+
server.tool(
|
|
180
|
+
'scan_skill',
|
|
181
|
+
'Scan an AI agent skill/plugin for security issues across 8 risk categories (Snyk ToxicSkills taxonomy). Checks for prompt injection, malicious code, suspicious downloads, credential handling, secret detection, third-party content, unverifiable dependencies, and financial access patterns. Static analysis only — no code execution. Returns risk score (0-100), severity-ranked findings with file locations, and human-readable summary.',
|
|
182
|
+
{
|
|
183
|
+
skill: z.string().optional().describe('Raw SKILL.md content or skill name from ClawHub'),
|
|
184
|
+
files: z.array(z.object({
|
|
185
|
+
name: z.string().describe('Filename including extension'),
|
|
186
|
+
content: z.string().describe('File content as string'),
|
|
187
|
+
})).optional().describe('Additional code files to analyze (max 20 files)'),
|
|
188
|
+
},
|
|
189
|
+
async (params) => {
|
|
190
|
+
const body: Record<string, unknown> = {};
|
|
191
|
+
if (params.skill) body.skill = params.skill;
|
|
192
|
+
if (params.files) body.files = params.files;
|
|
193
|
+
return formatResult(await callShieldApiPost('scan-skill', body));
|
|
194
|
+
}
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
// check_prompt — Prompt injection detection
|
|
198
|
+
server.tool(
|
|
199
|
+
'check_prompt',
|
|
200
|
+
'Detect prompt injection in text. Analyzes across 4 categories (direct injection, encoding tricks, exfiltration, indirect injection) with 200+ detection patterns. Designed for real-time inline usage before processing untrusted user input. Returns boolean verdict, confidence score (0-1), matched patterns with evidence, and decoded content if encoding obfuscation was detected. Response time <100ms p95.',
|
|
201
|
+
{
|
|
202
|
+
prompt: z.string().describe('The text to analyze for prompt injection'),
|
|
203
|
+
context: z.enum(['user-input', 'skill-prompt', 'system-prompt']).optional()
|
|
204
|
+
.describe('Context hint for sensitivity: user-input (default), skill-prompt (higher tolerance), system-prompt (highest sensitivity)'),
|
|
205
|
+
},
|
|
206
|
+
async (params) => {
|
|
207
|
+
const body: Record<string, unknown> = { prompt: params.prompt };
|
|
208
|
+
if (params.context) body.context = params.context;
|
|
209
|
+
return formatResult(await callShieldApiPost('check-prompt', body));
|
|
210
|
+
}
|
|
211
|
+
);
|
|
212
|
+
|
|
156
213
|
// --- Start ---
|
|
157
214
|
|
|
158
215
|
async function main(): Promise<void> {
|
|
159
216
|
await initPaymentFetch();
|
|
160
217
|
const transport = new StdioServerTransport();
|
|
161
218
|
await server.connect(transport);
|
|
162
|
-
console.error(`ShieldAPI MCP server running (${demoMode ? 'DEMO mode' : 'PAID mode'})`);
|
|
219
|
+
console.error(`ShieldAPI MCP server v2.0.0 running (${demoMode ? 'DEMO mode' : 'PAID mode'})`);
|
|
163
220
|
}
|
|
164
221
|
|
|
165
222
|
main().catch((err) => {
|