@elytrasec/mcp 0.1.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/LICENSE +21 -0
- package/README.md +56 -0
- package/dist/index.js +219 -0
- package/package.json +53 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 ElytraSec
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# @elytrasec/mcp
|
|
2
|
+
|
|
3
|
+
Elytra Security as a Model Context Protocol server. Give your AI coding agent the ability to scan smart contracts and code, replay famous exploit patterns, and look up onchain attestations — without leaving the IDE.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
### Claude Desktop
|
|
8
|
+
|
|
9
|
+
Add to `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows):
|
|
10
|
+
|
|
11
|
+
```json
|
|
12
|
+
{
|
|
13
|
+
"mcpServers": {
|
|
14
|
+
"elytra": {
|
|
15
|
+
"command": "npx",
|
|
16
|
+
"args": ["-y", "@elytrasec/mcp"]
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Restart Claude Desktop. The 4 Elytra tools appear in the MCP indicator.
|
|
23
|
+
|
|
24
|
+
### Cursor
|
|
25
|
+
|
|
26
|
+
Settings → MCP → Add server:
|
|
27
|
+
|
|
28
|
+
```json
|
|
29
|
+
{ "command": "npx", "args": ["-y", "@elytrasec/mcp"] }
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### Cline / Continue / any MCP-compatible client
|
|
33
|
+
|
|
34
|
+
Same one-liner — install as a stdio server with the npx command above.
|
|
35
|
+
|
|
36
|
+
## Tools
|
|
37
|
+
|
|
38
|
+
| Tool | What it does |
|
|
39
|
+
|---|---|
|
|
40
|
+
| `elytra_scan` | Scan a code snippet for security vulnerabilities |
|
|
41
|
+
| `elytra_scan_address` | Scan a deployed contract by 0x address (Ethereum / Base / Arbitrum / Optimism / Polygon) |
|
|
42
|
+
| `elytra_replay_hacks` | Test code against 8 famous-exploit patterns (Bybit, Ronin, Euler, Beanstalk, Multichain, Curve, Radiant, zkSync) |
|
|
43
|
+
| `elytra_agent_identity` | Return Elytra's onchain agent card (ERC-8004, pricing, capabilities) |
|
|
44
|
+
|
|
45
|
+
## Optional env vars
|
|
46
|
+
|
|
47
|
+
- `ELYTRA_API_KEY` — Bearer key for the paid `/api/v1/scan` endpoint (bypasses x402 micropayment for higher throughput). Contact hello@elytrasec.io.
|
|
48
|
+
- `ELYTRA_BASE_URL` — Override the default `https://elytrasec.io` (for self-hosting).
|
|
49
|
+
|
|
50
|
+
## Pricing
|
|
51
|
+
|
|
52
|
+
All tools above hit Elytra's free public endpoints. For higher rate limits or AI-powered deep review, the underlying API supports x402 pay-per-call in USDC on Base or Solana (1¢ per scan, 2¢ per review).
|
|
53
|
+
|
|
54
|
+
## License
|
|
55
|
+
|
|
56
|
+
MIT
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
5
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
6
|
+
import {
|
|
7
|
+
CallToolRequestSchema,
|
|
8
|
+
ListToolsRequestSchema
|
|
9
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
10
|
+
var BASE_URL = process.env.ELYTRA_BASE_URL ?? "https://elytrasec.io";
|
|
11
|
+
var API_KEY = process.env.ELYTRA_API_KEY ?? "";
|
|
12
|
+
async function postJSON(path, body, opts = {}) {
|
|
13
|
+
const headers = { "Content-Type": "application/json" };
|
|
14
|
+
if (opts.auth && API_KEY) headers.Authorization = `Bearer ${API_KEY}`;
|
|
15
|
+
let res;
|
|
16
|
+
try {
|
|
17
|
+
res = await fetch(`${BASE_URL}${path}`, { method: "POST", headers, body: JSON.stringify(body) });
|
|
18
|
+
} catch (e) {
|
|
19
|
+
return { error: `Network error reaching Elytra: ${e instanceof Error ? e.message : "unknown"}`, status: 0 };
|
|
20
|
+
}
|
|
21
|
+
let data;
|
|
22
|
+
try {
|
|
23
|
+
data = await res.json();
|
|
24
|
+
} catch {
|
|
25
|
+
data = null;
|
|
26
|
+
}
|
|
27
|
+
if (!res.ok) {
|
|
28
|
+
const errMsg = data?.error ?? `HTTP ${res.status}`;
|
|
29
|
+
return { error: errMsg, status: res.status };
|
|
30
|
+
}
|
|
31
|
+
return { ok: true, data };
|
|
32
|
+
}
|
|
33
|
+
async function getJSON(path) {
|
|
34
|
+
let res;
|
|
35
|
+
try {
|
|
36
|
+
res = await fetch(`${BASE_URL}${path}`);
|
|
37
|
+
} catch (e) {
|
|
38
|
+
return { error: `Network error reaching Elytra: ${e instanceof Error ? e.message : "unknown"}`, status: 0 };
|
|
39
|
+
}
|
|
40
|
+
let data;
|
|
41
|
+
try {
|
|
42
|
+
data = await res.json();
|
|
43
|
+
} catch {
|
|
44
|
+
data = null;
|
|
45
|
+
}
|
|
46
|
+
if (!res.ok) {
|
|
47
|
+
const errMsg = data?.error ?? `HTTP ${res.status}`;
|
|
48
|
+
return { error: errMsg, status: res.status };
|
|
49
|
+
}
|
|
50
|
+
return { ok: true, data };
|
|
51
|
+
}
|
|
52
|
+
function formatScan(r, label) {
|
|
53
|
+
const lines = [];
|
|
54
|
+
if (r.contract) {
|
|
55
|
+
lines.push(`# ${label}: ${r.contract.name} on ${r.contract.chainName}`);
|
|
56
|
+
lines.push(`Compiler: ${r.contract.compiler.replace(/^v/, "")} \xB7 Explorer: ${r.contract.explorerUrl}`);
|
|
57
|
+
} else {
|
|
58
|
+
lines.push(`# ${label}`);
|
|
59
|
+
}
|
|
60
|
+
if (r.warning) {
|
|
61
|
+
lines.push("");
|
|
62
|
+
lines.push(`\u26A0 ${r.warning}`);
|
|
63
|
+
return lines.join("\n");
|
|
64
|
+
}
|
|
65
|
+
lines.push("");
|
|
66
|
+
lines.push(`Score: ${r.score ?? "n/a"}/100 \xB7 Grade: ${r.grade ?? "n/a"}`);
|
|
67
|
+
lines.push(`Findings: ${r.findings.length}`);
|
|
68
|
+
if (r.findings.length === 0) {
|
|
69
|
+
lines.push("");
|
|
70
|
+
lines.push("No issues found.");
|
|
71
|
+
return lines.join("\n");
|
|
72
|
+
}
|
|
73
|
+
const order = ["critical", "high", "medium", "low", "info"];
|
|
74
|
+
const grouped = {};
|
|
75
|
+
for (const f of r.findings) (grouped[f.severity.toLowerCase()] ||= []).push(f);
|
|
76
|
+
for (const sev of order) {
|
|
77
|
+
const group = grouped[sev];
|
|
78
|
+
if (!group?.length) continue;
|
|
79
|
+
lines.push("");
|
|
80
|
+
lines.push(`## ${sev.toUpperCase()} (${group.length})`);
|
|
81
|
+
for (const f of group) {
|
|
82
|
+
const where = f.filePath ? `${f.filePath}:${f.startLine}` : `L${f.startLine}`;
|
|
83
|
+
lines.push(`- [${f.ruleId}] ${f.title.replace(/^\[Pattern\] /, "")} \xB7 ${where}`);
|
|
84
|
+
if (f.suggestion) lines.push(` \u2192 ${f.suggestion}`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return lines.join("\n");
|
|
88
|
+
}
|
|
89
|
+
var TOOLS = [
|
|
90
|
+
{
|
|
91
|
+
name: "elytra_scan",
|
|
92
|
+
description: "Scan a code snippet for security vulnerabilities. Supports Solidity, Vyper, JS/TS, Python, Go, Rust, Java, Ruby, PHP, plus IaC (Terraform, Kubernetes, Dockerfile, GitHub Actions). Returns findings with severity, fix suggestions, and a 0-100 score. Use this for security-focused code review.",
|
|
93
|
+
inputSchema: {
|
|
94
|
+
type: "object",
|
|
95
|
+
properties: {
|
|
96
|
+
code: { type: "string", description: "The source code to scan" },
|
|
97
|
+
language: { type: "string", description: "Language hint: javascript | typescript | python | go | java | ruby | php | solidity | rust", default: "javascript" }
|
|
98
|
+
},
|
|
99
|
+
required: ["code"]
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
name: "elytra_scan_address",
|
|
104
|
+
description: "Scan a DEPLOYED smart contract by its onchain address. Elytra fetches the verified source from the block explorer (Etherscan / Basescan / etc.) and scans every source file. Use this when the user gives you a 0x... address and asks about its security.",
|
|
105
|
+
inputSchema: {
|
|
106
|
+
type: "object",
|
|
107
|
+
properties: {
|
|
108
|
+
address: { type: "string", description: "Contract address (0x... 40 hex chars)" },
|
|
109
|
+
chain: { type: "string", description: "ethereum | base | arbitrum | optimism | polygon", default: "ethereum" }
|
|
110
|
+
},
|
|
111
|
+
required: ["address"]
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
name: "elytra_replay_hacks",
|
|
116
|
+
description: "Run the 8 famous-exploit pattern detectors against submitted source code. Encodes patterns from $3B+ in losses: Bybit ($1.46B), Ronin ($625M), Euler ($197M), Beanstalk ($182M), Multichain ($126M), Curve ($73M), Radiant ($53M), zkSync ($5M). Returns only matches against these specific historic attack vectors. Use this when you want to check 'have I made any of the famous mistakes?'",
|
|
117
|
+
inputSchema: {
|
|
118
|
+
type: "object",
|
|
119
|
+
properties: {
|
|
120
|
+
code: { type: "string", description: "The source code to test against hack-replay patterns" },
|
|
121
|
+
language: { type: "string", description: "Language hint", default: "solidity" }
|
|
122
|
+
},
|
|
123
|
+
required: ["code"]
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
name: "elytra_agent_identity",
|
|
128
|
+
description: "Return Elytra's onchain agent identity card \u2014 ERC-8004 registry data, capabilities, pricing on Base and Solana, attestation count, and discovery endpoints. Use this when the user wants to verify or learn about the Elytra agent itself, integrate it programmatically, or compare it to other agents.",
|
|
129
|
+
inputSchema: { type: "object", properties: {} }
|
|
130
|
+
}
|
|
131
|
+
];
|
|
132
|
+
function errOut(msg) {
|
|
133
|
+
return { content: [{ type: "text", text: `Error: ${msg}` }], isError: true };
|
|
134
|
+
}
|
|
135
|
+
async function runScan(args) {
|
|
136
|
+
if (!args.code) return errOut("Missing required field: code");
|
|
137
|
+
const r = await postJSON("/api/playground", {
|
|
138
|
+
code: args.code,
|
|
139
|
+
language: args.language ?? "javascript"
|
|
140
|
+
});
|
|
141
|
+
if (!("ok" in r)) return errOut(r.error);
|
|
142
|
+
return { content: [{ type: "text", text: formatScan(r.data, "Scan result") }] };
|
|
143
|
+
}
|
|
144
|
+
async function runScanAddress(args) {
|
|
145
|
+
if (!args.address) return errOut("Missing required field: address");
|
|
146
|
+
const r = await postJSON("/api/playground/scan-address", {
|
|
147
|
+
address: args.address,
|
|
148
|
+
chain: args.chain ?? "ethereum"
|
|
149
|
+
});
|
|
150
|
+
if (!("ok" in r)) return errOut(r.error);
|
|
151
|
+
return { content: [{ type: "text", text: formatScan(r.data, "Contract scan") }] };
|
|
152
|
+
}
|
|
153
|
+
async function runReplayHacks(args) {
|
|
154
|
+
if (!args.code) return errOut("Missing required field: code");
|
|
155
|
+
const r = await postJSON("/api/playground", {
|
|
156
|
+
code: args.code,
|
|
157
|
+
language: args.language ?? "solidity"
|
|
158
|
+
});
|
|
159
|
+
if (!("ok" in r)) return errOut(r.error);
|
|
160
|
+
const onlyHacks = {
|
|
161
|
+
...r.data,
|
|
162
|
+
findings: r.data.findings.filter((f) => f.ruleId.startsWith("cp-hack-"))
|
|
163
|
+
};
|
|
164
|
+
const text = onlyHacks.findings.length === 0 ? "No famous-hack patterns matched against this code. \u2713\n\nThis means none of: Bybit \xB7 Ronin \xB7 Euler \xB7 Beanstalk \xB7 Multichain \xB7 Curve \xB7 Radiant \xB7 zkSync fingerprints fired.\n\nNote: hack-replay is a narrow pattern check \u2014 run elytra_scan for the full 130+ rule set." : formatScan(onlyHacks, "Famous-hack pattern matches");
|
|
165
|
+
return { content: [{ type: "text", text }] };
|
|
166
|
+
}
|
|
167
|
+
async function runAgentIdentity() {
|
|
168
|
+
const r = await getJSON("/.well-known/agent-card.json");
|
|
169
|
+
if (!("ok" in r)) return errOut(r.error);
|
|
170
|
+
const c = r.data;
|
|
171
|
+
const lines = [];
|
|
172
|
+
lines.push(`# ${c.name}`);
|
|
173
|
+
lines.push("");
|
|
174
|
+
lines.push(c.description);
|
|
175
|
+
lines.push("");
|
|
176
|
+
lines.push(`## Identity`);
|
|
177
|
+
lines.push(`- ERC-8004 agent ID: ${c.identity.erc8004.agentId ?? "(pending registration)"}`);
|
|
178
|
+
lines.push(`- Registry: ${c.identity.erc8004.registryExplorer}`);
|
|
179
|
+
lines.push(`- Wallet: ${c.identity.wallet ?? "(unconfigured)"}`);
|
|
180
|
+
lines.push(`- Attestation service: ${c.identity.attestation.service}`);
|
|
181
|
+
lines.push(`- Confirmed attestations onchain: ${c.attestation_count}`);
|
|
182
|
+
lines.push("");
|
|
183
|
+
lines.push(`## Capabilities`);
|
|
184
|
+
for (const cap of c.capabilities) {
|
|
185
|
+
const priceLines = cap.pricing.filter((p) => p.available).map((p) => `${p.human} on ${p.chain}`).join(" \xB7 ");
|
|
186
|
+
lines.push(`- **${cap.name}** \u2192 ${cap.endpoint}`);
|
|
187
|
+
if (priceLines) lines.push(` Pricing: ${priceLines}`);
|
|
188
|
+
}
|
|
189
|
+
lines.push("");
|
|
190
|
+
lines.push(`## Discovery`);
|
|
191
|
+
for (const [k, v] of Object.entries(c.discovery)) lines.push(`- ${k}: ${v}`);
|
|
192
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
193
|
+
}
|
|
194
|
+
var server = new Server(
|
|
195
|
+
{ name: "elytrasec", version: "0.1.0" },
|
|
196
|
+
{ capabilities: { tools: {} } }
|
|
197
|
+
);
|
|
198
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }));
|
|
199
|
+
server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
200
|
+
const { name, arguments: args = {} } = req.params;
|
|
201
|
+
try {
|
|
202
|
+
switch (name) {
|
|
203
|
+
case "elytra_scan":
|
|
204
|
+
return await runScan(args);
|
|
205
|
+
case "elytra_scan_address":
|
|
206
|
+
return await runScanAddress(args);
|
|
207
|
+
case "elytra_replay_hacks":
|
|
208
|
+
return await runReplayHacks(args);
|
|
209
|
+
case "elytra_agent_identity":
|
|
210
|
+
return await runAgentIdentity();
|
|
211
|
+
default:
|
|
212
|
+
return errOut(`Unknown tool: ${name}`);
|
|
213
|
+
}
|
|
214
|
+
} catch (e) {
|
|
215
|
+
return errOut(e instanceof Error ? e.message : "Internal MCP server error");
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
var transport = new StdioServerTransport();
|
|
219
|
+
await server.connect(transport);
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@elytrasec/mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Elytra Security as a Model Context Protocol server — give your AI agent (Claude Desktop, Cursor, Cline) the ability to scan smart contracts and code, replay famous exploit patterns, and post onchain attestations.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "ElytraSec <hello@elytrasec.io>",
|
|
7
|
+
"homepage": "https://elytrasec.io/agents",
|
|
8
|
+
"bugs": "https://github.com/ElytraSec/elytrasec/issues",
|
|
9
|
+
"keywords": [
|
|
10
|
+
"mcp",
|
|
11
|
+
"model-context-protocol",
|
|
12
|
+
"security",
|
|
13
|
+
"scanner",
|
|
14
|
+
"solidity",
|
|
15
|
+
"defi",
|
|
16
|
+
"ai-agent",
|
|
17
|
+
"claude",
|
|
18
|
+
"cursor"
|
|
19
|
+
],
|
|
20
|
+
"engines": {
|
|
21
|
+
"node": ">=20"
|
|
22
|
+
},
|
|
23
|
+
"repository": {
|
|
24
|
+
"type": "git",
|
|
25
|
+
"url": "https://github.com/ElytraSec/elytrasec.git",
|
|
26
|
+
"directory": "packages/mcp"
|
|
27
|
+
},
|
|
28
|
+
"publishConfig": {
|
|
29
|
+
"access": "public"
|
|
30
|
+
},
|
|
31
|
+
"type": "module",
|
|
32
|
+
"bin": {
|
|
33
|
+
"elytrasec-mcp": "dist/index.js"
|
|
34
|
+
},
|
|
35
|
+
"files": [
|
|
36
|
+
"dist",
|
|
37
|
+
"README.md"
|
|
38
|
+
],
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"@modelcontextprotocol/sdk": "^1.0.4",
|
|
41
|
+
"zod": "^3.23.8"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@types/node": "^22.13.4",
|
|
45
|
+
"tsup": "^8.5.1",
|
|
46
|
+
"typescript": "^5.7.3"
|
|
47
|
+
},
|
|
48
|
+
"scripts": {
|
|
49
|
+
"build": "tsup",
|
|
50
|
+
"dev": "tsup src/index.ts --format esm --watch",
|
|
51
|
+
"start": "node dist/index.js"
|
|
52
|
+
}
|
|
53
|
+
}
|