agentsapi-sec-filings 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 +85 -0
- package/package.json +40 -0
- package/src/index.js +189 -0
- package/test/client.mjs +43 -0
- package/test/client_installed.mjs +38 -0
package/README.md
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# agentsapi-sec-filings
|
|
2
|
+
|
|
3
|
+
MCP server exposing **SEC EDGAR filings** as agent-callable tools — pay-per-call in USDC on Base via [x402](https://x402.org). No API key, no subscription: every call settles a micropayment on-chain and returns data immediately.
|
|
4
|
+
|
|
5
|
+
## Tools
|
|
6
|
+
|
|
7
|
+
| Tool | Description | Price |
|
|
8
|
+
|---|---|---|
|
|
9
|
+
| `sec_filings_today` | Latest 10-K / 10-Q / 8-K filings across US markets, newest first (max 40) | **$0.01 USDC / call** |
|
|
10
|
+
| `sec_filings_8k` | Real-time **8-K material events** stream (M&A, executive changes, earnings, delisting, bankruptcy) | **$0.005 USDC / call** |
|
|
11
|
+
| `sec_filings_service_info` | Free discovery: endpoints, prices, payment network, wallet | free |
|
|
12
|
+
|
|
13
|
+
Every paid response carries a `receipt` block (`paid`, `payer`, `amount`, `transaction`) so the caller can audit settlement.
|
|
14
|
+
|
|
15
|
+
## Requirements
|
|
16
|
+
|
|
17
|
+
- Node.js >= 18
|
|
18
|
+
- An EVM wallet holding **USDC on Base** (mainnet, `eip155:8453`) plus a small amount of ETH for gas on the first transfer
|
|
19
|
+
- The private key is used **locally** to sign the x402 payment payload; it is never sent to the server
|
|
20
|
+
|
|
21
|
+
## Install
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npx agentsapi-sec-filings
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Configuration
|
|
28
|
+
|
|
29
|
+
| Env var | Required | Default | Purpose |
|
|
30
|
+
|---|---|---|---|
|
|
31
|
+
| `X402_PRIVATE_KEY` | yes | — | EVM private key (`0x...`) used to sign payments |
|
|
32
|
+
| `X402_RPC_URL` | no | `https://mainnet.base.org` | Base RPC endpoint |
|
|
33
|
+
| `AGENTSAPI_BASE` | no | `https://agentsapi.top` | API base URL |
|
|
34
|
+
|
|
35
|
+
### Claude Desktop / Claude Code
|
|
36
|
+
|
|
37
|
+
```json
|
|
38
|
+
{
|
|
39
|
+
"mcpServers": {
|
|
40
|
+
"sec-filings": {
|
|
41
|
+
"command": "npx",
|
|
42
|
+
"args": ["-y", "agentsapi-sec-filings"],
|
|
43
|
+
"env": {
|
|
44
|
+
"X402_PRIVATE_KEY": "0x<your-base-wallet-key>"
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Cursor / Windsurf / any stdio MCP client
|
|
52
|
+
|
|
53
|
+
```json
|
|
54
|
+
{
|
|
55
|
+
"mcpServers": {
|
|
56
|
+
"sec-filings": {
|
|
57
|
+
"command": "npx",
|
|
58
|
+
"args": ["-y", "agentsapi-sec-filings"],
|
|
59
|
+
"env": { "X402_PRIVATE_KEY": "0x<your-base-wallet-key>" }
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Example agent usage
|
|
66
|
+
|
|
67
|
+
> "Any 8-K filings in the last few hours that look like M&A activity?"
|
|
68
|
+
|
|
69
|
+
The agent calls `sec_filings_8k`, the client answers the HTTP 402 challenge with a signed USDC transfer, and the filing list comes back with EDGAR links — $0.005 settled, no signup.
|
|
70
|
+
|
|
71
|
+
## Payment flow
|
|
72
|
+
|
|
73
|
+
1. Tool call issues `GET https://agentsapi.top/api/filings/8k`
|
|
74
|
+
2. Server replies `402 Payment Required` with x402 terms (USDC, Base, amount, payTo)
|
|
75
|
+
3. Client signs an EIP-3009 USDC authorization and retries with `X-PAYMENT`
|
|
76
|
+
4. Facilitator settles on-chain; server returns data + `X-PAYMENT-RESPONSE` receipt
|
|
77
|
+
|
|
78
|
+
## Pricing rationale
|
|
79
|
+
|
|
80
|
+
- Data is refreshed every 10 minutes; each response contains up to 40 filings.
|
|
81
|
+
- No rate limits, no API keys — cost scales exactly with usage, which keeps agents free to call as needed.
|
|
82
|
+
|
|
83
|
+
## License
|
|
84
|
+
|
|
85
|
+
MIT
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "agentsapi-sec-filings",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"agentsapi-sec-filings": "src/index.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"start": "node src/index.js",
|
|
11
|
+
"test": "node test/client.mjs"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"mcp",
|
|
15
|
+
"x402",
|
|
16
|
+
"sec",
|
|
17
|
+
"edgar",
|
|
18
|
+
"filings",
|
|
19
|
+
"usdc",
|
|
20
|
+
"base"
|
|
21
|
+
],
|
|
22
|
+
"author": "",
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"engines": {
|
|
25
|
+
"node": ">=18"
|
|
26
|
+
},
|
|
27
|
+
"description": "MCP server for SEC EDGAR filings (10-K / 10-Q / 8-K), pay-per-call in USDC on Base via x402.",
|
|
28
|
+
"files": [
|
|
29
|
+
"src",
|
|
30
|
+
"test",
|
|
31
|
+
"README.md"
|
|
32
|
+
],
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
35
|
+
"@x402/core": "^2.25.0",
|
|
36
|
+
"@x402/evm": "^2.25.0",
|
|
37
|
+
"viem": "^2.56.3",
|
|
38
|
+
"zod": "^4.6.1"
|
|
39
|
+
}
|
|
40
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* agentsapi-sec-filings — MCP server for SEC EDGAR filings (x402 pay-per-call)
|
|
4
|
+
*
|
|
5
|
+
* Exposes two paid data tools to any MCP-capable agent:
|
|
6
|
+
* - sec_filings_today ($0.01 USDC per call) latest 10-K / 10-Q / 8-K filings
|
|
7
|
+
* - sec_filings_8k ($0.005 USDC per call) 8-K material-event stream
|
|
8
|
+
*
|
|
9
|
+
* Payment is handled automatically in-protocol via x402 (HTTP 402 -> USDC on Base).
|
|
10
|
+
* The calling side supplies its own wallet key through X402_PRIVATE_KEY.
|
|
11
|
+
*/
|
|
12
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
13
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
14
|
+
import { z } from "zod";
|
|
15
|
+
import { x402Client } from "@x402/core/client";
|
|
16
|
+
import { x402HTTPClient } from "@x402/core/http";
|
|
17
|
+
import { registerExactEvmScheme } from "@x402/evm/exact/client";
|
|
18
|
+
import { privateKeyToAccount } from "viem/accounts";
|
|
19
|
+
|
|
20
|
+
const BASE_URL = (process.env.AGENTSAPI_BASE || "https://agentsapi.top").replace(/\/$/, "");
|
|
21
|
+
const PRIVATE_KEY = (process.env.X402_PRIVATE_KEY || "").trim();
|
|
22
|
+
const RPC_URL = process.env.X402_RPC_URL || "https://mainnet.base.org";
|
|
23
|
+
|
|
24
|
+
const PAY_TO = "0x381cdbb664608bf7b1dd4f9403a572c1c57332c2";
|
|
25
|
+
const NETWORK = "eip155:8453 (Base mainnet)";
|
|
26
|
+
|
|
27
|
+
let cachedClient = null;
|
|
28
|
+
|
|
29
|
+
function buildClient() {
|
|
30
|
+
if (!PRIVATE_KEY) {
|
|
31
|
+
throw new Error(
|
|
32
|
+
"X402_PRIVATE_KEY is not set. Provide an EVM wallet private key (0x...) that holds USDC on Base; " +
|
|
33
|
+
"payment is signed locally and sent in-protocol, the key is never transmitted."
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
if (cachedClient) return cachedClient;
|
|
37
|
+
const account = privateKeyToAccount(PRIVATE_KEY);
|
|
38
|
+
const core = new x402Client();
|
|
39
|
+
registerExactEvmScheme(core, {
|
|
40
|
+
signer: account,
|
|
41
|
+
schemeOptions: { rpcUrl: RPC_URL },
|
|
42
|
+
});
|
|
43
|
+
cachedClient = { account, client: new x402HTTPClient(core) };
|
|
44
|
+
return cachedClient;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Call a paid endpoint, completing the x402 handshake when a 402 is returned.
|
|
49
|
+
*/
|
|
50
|
+
async function callPaidEndpoint(path) {
|
|
51
|
+
const { account, client } = buildClient();
|
|
52
|
+
const url = `${BASE_URL}${path}`;
|
|
53
|
+
|
|
54
|
+
let res = await fetch(url, { headers: { accept: "application/json" } });
|
|
55
|
+
|
|
56
|
+
let required = null;
|
|
57
|
+
if (res.status === 402) {
|
|
58
|
+
const body = await res.json().catch(() => ({}));
|
|
59
|
+
required = client.getPaymentRequiredResponse((name) => res.headers.get(name), body);
|
|
60
|
+
const payload = await client.createPaymentPayload(required);
|
|
61
|
+
res = await fetch(url, {
|
|
62
|
+
headers: { accept: "application/json", ...client.encodePaymentSignatureHeader(payload) },
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const text = await res.text();
|
|
67
|
+
if (res.status !== 200) {
|
|
68
|
+
throw new Error(`Upstream returned HTTP ${res.status}: ${text.slice(0, 300)}`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
let data;
|
|
72
|
+
try {
|
|
73
|
+
data = JSON.parse(text);
|
|
74
|
+
} catch {
|
|
75
|
+
throw new Error(`Upstream returned non-JSON payload: ${text.slice(0, 300)}`);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
let settlement = null;
|
|
79
|
+
try {
|
|
80
|
+
settlement = client.getPaymentSettleResponse((name) => res.headers.get(name));
|
|
81
|
+
} catch {
|
|
82
|
+
settlement = null;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const accepted = required?.accepts?.[0];
|
|
86
|
+
const receipt = {
|
|
87
|
+
paid: Boolean(settlement?.success ?? true),
|
|
88
|
+
payer: account.address,
|
|
89
|
+
payTo: PAY_TO,
|
|
90
|
+
network: NETWORK,
|
|
91
|
+
amount: accepted?.amount ? `${Number(accepted.amount) / 1e6} USDC` : undefined,
|
|
92
|
+
transaction: settlement?.transaction ?? null,
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
return { data, receipt, source: `${BASE_URL}${path}` };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function asText(payload) {
|
|
99
|
+
return {
|
|
100
|
+
content: [{ type: "text", text: JSON.stringify(payload, null, 2) }],
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function asError(err) {
|
|
105
|
+
return {
|
|
106
|
+
content: [{ type: "text", text: `Error: ${err?.message || String(err)}` }],
|
|
107
|
+
isError: true,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const server = new McpServer({
|
|
112
|
+
name: "agentsapi-sec-filings",
|
|
113
|
+
version: "1.0.0",
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
server.registerTool(
|
|
117
|
+
"sec_filings_today",
|
|
118
|
+
{
|
|
119
|
+
title: "Latest SEC filings (10-K / 10-Q / 8-K)",
|
|
120
|
+
description:
|
|
121
|
+
"Latest SEC EDGAR filings across 10-K, 10-Q and 8-K form types, sorted by filing time descending " +
|
|
122
|
+
"(max 40 per call, refreshed every 10 minutes). Each item: form type, company name + CIK, filing time (UTC), " +
|
|
123
|
+
"EDGAR archive URL. Paid per call: $0.01 USDC on Base, settled automatically over x402.",
|
|
124
|
+
inputSchema: {},
|
|
125
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
126
|
+
},
|
|
127
|
+
async () => {
|
|
128
|
+
try {
|
|
129
|
+
const { data, receipt, source } = await callPaidEndpoint("/api/filings/today");
|
|
130
|
+
return asText({ source, receipt, ...data });
|
|
131
|
+
} catch (err) {
|
|
132
|
+
return asError(err);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
server.registerTool(
|
|
138
|
+
"sec_filings_8k",
|
|
139
|
+
{
|
|
140
|
+
title: "SEC 8-K material events (real-time)",
|
|
141
|
+
description:
|
|
142
|
+
"Real-time stream of SEC EDGAR 8-K current reports — material corporate events such as M&A, officer changes, " +
|
|
143
|
+
"earnings releases, delisting and bankruptcy. Sorted by filing time descending, max 40 per call. " +
|
|
144
|
+
"Paid per call: $0.005 USDC on Base, settled automatically over x402.",
|
|
145
|
+
inputSchema: {},
|
|
146
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
147
|
+
},
|
|
148
|
+
async () => {
|
|
149
|
+
try {
|
|
150
|
+
const { data, receipt, source } = await callPaidEndpoint("/api/filings/8k");
|
|
151
|
+
return asText({ source, receipt, ...data });
|
|
152
|
+
} catch (err) {
|
|
153
|
+
return asError(err);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
server.registerTool(
|
|
159
|
+
"sec_filings_service_info",
|
|
160
|
+
{
|
|
161
|
+
title: "SEC filings API service info (free)",
|
|
162
|
+
description:
|
|
163
|
+
"Free discovery endpoint: describes the available SEC EDGAR data endpoints, their prices, payment network " +
|
|
164
|
+
"and wallet address. Use this first to check pricing before calling a paid tool.",
|
|
165
|
+
inputSchema: {},
|
|
166
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
167
|
+
},
|
|
168
|
+
async () => {
|
|
169
|
+
try {
|
|
170
|
+
const res = await fetch(`${BASE_URL}/openapi.json`, { headers: { accept: "application/json" } });
|
|
171
|
+
const spec = await res.json();
|
|
172
|
+
const paths = Object.keys(spec?.paths || {});
|
|
173
|
+
return asText({
|
|
174
|
+
service: "agentsapi.top",
|
|
175
|
+
payment: { protocol: "x402", network: NETWORK, asset: "USDC", payTo: PAY_TO },
|
|
176
|
+
endpoints: paths,
|
|
177
|
+
tools: {
|
|
178
|
+
sec_filings_today: "$0.01 USDC / call",
|
|
179
|
+
sec_filings_8k: "$0.005 USDC / call",
|
|
180
|
+
},
|
|
181
|
+
});
|
|
182
|
+
} catch (err) {
|
|
183
|
+
return asError(err);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
const transport = new StdioServerTransport();
|
|
189
|
+
await server.connect(transport);
|
package/test/client.mjs
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local end-to-end test: spawn the MCP server over stdio, list tools,
|
|
3
|
+
* then call the paid 8-K tool with a real $0.005 USDC payment on Base.
|
|
4
|
+
*/
|
|
5
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
6
|
+
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
7
|
+
|
|
8
|
+
const PRIVATE_KEY = process.env.TEST_X402_PRIVATE_KEY || process.argv[2];
|
|
9
|
+
if (!PRIVATE_KEY) {
|
|
10
|
+
console.error("usage: node test/client.mjs 0x<privateKey>");
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const transport = new StdioClientTransport({
|
|
15
|
+
command: "node",
|
|
16
|
+
args: ["src/index.js"],
|
|
17
|
+
env: {
|
|
18
|
+
...process.env,
|
|
19
|
+
X402_PRIVATE_KEY: PRIVATE_KEY,
|
|
20
|
+
X402_RPC_URL: process.env.X402_RPC_URL || "https://mainnet.base.org",
|
|
21
|
+
},
|
|
22
|
+
stderr: "inherit",
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const client = new Client({ name: "mcp-e2e-test", version: "1.0.0" });
|
|
26
|
+
await client.connect(transport);
|
|
27
|
+
console.log("[1] MCP 连接成功");
|
|
28
|
+
|
|
29
|
+
const tools = await client.listTools();
|
|
30
|
+
console.log("[2] 工具清单:", tools.tools.map((t) => t.name).join(", "));
|
|
31
|
+
|
|
32
|
+
console.log("[3] 调用 sec_filings_service_info(免费)...");
|
|
33
|
+
const info = await client.callTool({ name: "sec_filings_service_info", arguments: {} });
|
|
34
|
+
console.log(info.content[0].text.slice(0, 600));
|
|
35
|
+
|
|
36
|
+
console.log("[4] 调用 sec_filings_8k(真钱 $0.005)...");
|
|
37
|
+
const paid = await client.callTool({ name: "sec_filings_8k", arguments: {} });
|
|
38
|
+
const text = paid.content[0].text;
|
|
39
|
+
console.log("[5] 返回前 900 字符:\n" + text.slice(0, 900));
|
|
40
|
+
console.log("[6] isError:", paid.isError === true);
|
|
41
|
+
|
|
42
|
+
await client.close();
|
|
43
|
+
console.log("=== MCP 端到端测试结束 ===");
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simulate an external user: install the published tarball into a clean dir,
|
|
3
|
+
* spawn it over stdio as an MCP server, and call tools through the MCP protocol.
|
|
4
|
+
* Usage: node test/client_installed.mjs 0x<privateKey>
|
|
5
|
+
*/
|
|
6
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
7
|
+
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
8
|
+
|
|
9
|
+
const PRIVATE_KEY = process.env.TEST_X402_PRIVATE_KEY || process.argv[2];
|
|
10
|
+
|
|
11
|
+
const transport = new StdioClientTransport({
|
|
12
|
+
command: "npx",
|
|
13
|
+
args: ["--no-install", "agentsapi-sec-filings"],
|
|
14
|
+
env: { ...process.env, X402_PRIVATE_KEY: PRIVATE_KEY || "" },
|
|
15
|
+
stderr: "inherit",
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
const client = new Client({ name: "external-user-sim", version: "1.0.0" });
|
|
19
|
+
await client.connect(transport);
|
|
20
|
+
console.log("[1] 通过 npx 启动已安装的 MCP server 成功");
|
|
21
|
+
|
|
22
|
+
const tools = await client.listTools();
|
|
23
|
+
console.log("[2] 工具:", tools.tools.map((t) => t.name).join(", "));
|
|
24
|
+
|
|
25
|
+
const info = await client.callTool({ name: "sec_filings_service_info", arguments: {} });
|
|
26
|
+
console.log("[3] 免费工具返回:", info.content[0].text.replace(/\s+/g, " ").slice(0, 300));
|
|
27
|
+
|
|
28
|
+
if (PRIVATE_KEY) {
|
|
29
|
+
const paid = await client.callTool({ name: "sec_filings_8k", arguments: {} });
|
|
30
|
+
const t = paid.content[0].text;
|
|
31
|
+
console.log("[4] 付费工具 isError:", paid.isError === true);
|
|
32
|
+
console.log("[5] 付费返回片段:", t.replace(/\s+/g, " ").slice(0, 700));
|
|
33
|
+
} else {
|
|
34
|
+
console.log("[4] 未提供私钥,跳过付费调用");
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
await client.close();
|
|
38
|
+
console.log("=== 安装分发链路验证结束 ===");
|