shieldapi-mcp 1.0.3 → 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 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 caller ---
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: '1.0.2',
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 is special — single 'target' param mapped to the correct server param
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,6 +1,6 @@
1
1
  {
2
2
  "name": "shieldapi-mcp",
3
- "version": "1.0.3",
3
+ "version": "2.0.0",
4
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": {
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 caller ---
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: '1.0.2',
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 is special — single 'target' param mapped to the correct server param
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) => {