skim-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 +137 -0
- package/dist/index.js +102 -0
- package/package.json +58 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Skim
|
|
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,137 @@
|
|
|
1
|
+
# skim-mcp
|
|
2
|
+
|
|
3
|
+
MCP (Model Context Protocol) server for [Skim](https://skim402.com) — clean web
|
|
4
|
+
reader for AI agents. Pays **$0.002 per call in USDC on Base** over the
|
|
5
|
+
[x402](https://x402.org) protocol. No signup. No API keys. No monthly bills.
|
|
6
|
+
|
|
7
|
+
Give your agent the ability to read any URL and get back clean, agent-ready
|
|
8
|
+
Markdown (no nav, no ads, no boilerplate) plus structured metadata (title,
|
|
9
|
+
byline, published date, language, excerpt).
|
|
10
|
+
|
|
11
|
+
## What you need
|
|
12
|
+
|
|
13
|
+
1. **Node.js 18+**
|
|
14
|
+
2. A **Base wallet private key** with a small amount of **USDC** in it. A dollar
|
|
15
|
+
funds ~500 reads. Use a fresh wallet — not your personal one.
|
|
16
|
+
|
|
17
|
+
> Don't have one? Create a key with `cast wallet new` (Foundry) or any EVM
|
|
18
|
+
> wallet, then send USDC to it on Base mainnet from Coinbase, an exchange, or
|
|
19
|
+
> another wallet.
|
|
20
|
+
|
|
21
|
+
## Install in Claude Desktop
|
|
22
|
+
|
|
23
|
+
Edit `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS)
|
|
24
|
+
or `%APPDATA%\Claude\claude_desktop_config.json` (Windows):
|
|
25
|
+
|
|
26
|
+
```json
|
|
27
|
+
{
|
|
28
|
+
"mcpServers": {
|
|
29
|
+
"skim": {
|
|
30
|
+
"command": "npx",
|
|
31
|
+
"args": ["-y", "skim-mcp"],
|
|
32
|
+
"env": {
|
|
33
|
+
"SKIM_WALLET_PRIVATE_KEY": "0xYOUR_BASE_WALLET_PRIVATE_KEY"
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Restart Claude Desktop. You'll see a new `read_url` tool. Ask Claude to read
|
|
41
|
+
any article and it'll fetch it through Skim and pay automatically.
|
|
42
|
+
|
|
43
|
+
## Install in Cursor
|
|
44
|
+
|
|
45
|
+
Edit `~/.cursor/mcp.json` (or the in-app **Settings → MCP** panel):
|
|
46
|
+
|
|
47
|
+
```json
|
|
48
|
+
{
|
|
49
|
+
"mcpServers": {
|
|
50
|
+
"skim": {
|
|
51
|
+
"command": "npx",
|
|
52
|
+
"args": ["-y", "skim-mcp"],
|
|
53
|
+
"env": {
|
|
54
|
+
"SKIM_WALLET_PRIVATE_KEY": "0xYOUR_BASE_WALLET_PRIVATE_KEY"
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Install in Cline / Continue / Zed
|
|
62
|
+
|
|
63
|
+
All MCP-compatible clients use the same shape. Run the binary as:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
npx skim-mcp
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
with the same `SKIM_WALLET_PRIVATE_KEY` env var.
|
|
70
|
+
|
|
71
|
+
## Tools
|
|
72
|
+
|
|
73
|
+
### `read_url`
|
|
74
|
+
|
|
75
|
+
Reads any URL and returns clean Markdown plus a YAML frontmatter block with
|
|
76
|
+
metadata.
|
|
77
|
+
|
|
78
|
+
**Input:**
|
|
79
|
+
|
|
80
|
+
```json
|
|
81
|
+
{ "url": "https://example.com/article" }
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
**Output (text):**
|
|
85
|
+
|
|
86
|
+
```
|
|
87
|
+
---
|
|
88
|
+
title: Example article
|
|
89
|
+
byline: Jane Doe
|
|
90
|
+
published: 2025-01-15
|
|
91
|
+
lang: en
|
|
92
|
+
excerpt: A short summary...
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
# Example article
|
|
96
|
+
|
|
97
|
+
The cleaned article body in Markdown...
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Environment variables
|
|
101
|
+
|
|
102
|
+
| Variable | Required | Default | Notes |
|
|
103
|
+
| -------------------------- | -------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
|
|
104
|
+
| `SKIM_WALLET_PRIVATE_KEY` | **yes** | — | Hex private key for the Base wallet that pays for reads. With or without `0x` prefix. |
|
|
105
|
+
| `SKIM_MAX_PRICE_USD` | no | `0.01` | Maximum USD per call. Caps how much the wallet will sign for in a single read. Skim is currently $0.002/call, well under this. |
|
|
106
|
+
| `SKIM_API_URL` | no | `https://skim402.com` | Override the API base URL. Mostly for development. |
|
|
107
|
+
|
|
108
|
+
## Security
|
|
109
|
+
|
|
110
|
+
- Use a **dedicated wallet**, not your personal one. Fund it with only as much
|
|
111
|
+
USDC as you're willing to spend.
|
|
112
|
+
- The private key never leaves your machine. It's only used locally to sign
|
|
113
|
+
EIP-3009 payment authorizations consumed by the Skim API.
|
|
114
|
+
- Sweep the receive side periodically if you're running Skim yourself.
|
|
115
|
+
|
|
116
|
+
## How it works
|
|
117
|
+
|
|
118
|
+
1. The MCP server receives a `read_url` call from your AI agent.
|
|
119
|
+
2. It POSTs to `https://skim402.com/api/v1/read`.
|
|
120
|
+
3. The server replies `402 Payment Required` with x402 payment details.
|
|
121
|
+
4. [`x402-fetch`](https://www.npmjs.com/package/x402-fetch) signs an EIP-3009
|
|
122
|
+
USDC transfer authorization using your wallet, attaches it as the
|
|
123
|
+
`X-PAYMENT` header, and retries.
|
|
124
|
+
5. Skim verifies + settles via the Coinbase CDP facilitator and returns the
|
|
125
|
+
cleaned content.
|
|
126
|
+
|
|
127
|
+
End-to-end latency is typically <2 seconds.
|
|
128
|
+
|
|
129
|
+
## Links
|
|
130
|
+
|
|
131
|
+
- Skim — https://skim402.com
|
|
132
|
+
- x402 protocol — https://x402.org
|
|
133
|
+
- Model Context Protocol — https://modelcontextprotocol.io
|
|
134
|
+
|
|
135
|
+
## License
|
|
136
|
+
|
|
137
|
+
MIT
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { privateKeyToAccount } from "viem/accounts";
|
|
5
|
+
import { wrapFetchWithPayment } from "x402-fetch";
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
const BASE_URL = (process.env.SKIM_API_URL ?? "https://skim402.com").replace(/\/+$/, "");
|
|
8
|
+
const PRIVATE_KEY = process.env.SKIM_WALLET_PRIVATE_KEY;
|
|
9
|
+
const MAX_PRICE_USD = process.env.SKIM_MAX_PRICE_USD ?? "0.01";
|
|
10
|
+
let payFetch = fetch;
|
|
11
|
+
let walletAddress = null;
|
|
12
|
+
if (PRIVATE_KEY) {
|
|
13
|
+
const normalized = PRIVATE_KEY.startsWith("0x")
|
|
14
|
+
? PRIVATE_KEY.slice(2)
|
|
15
|
+
: PRIVATE_KEY;
|
|
16
|
+
if (!/^[0-9a-fA-F]{64}$/.test(normalized)) {
|
|
17
|
+
process.stderr.write("skim-mcp: SKIM_WALLET_PRIVATE_KEY must be a 64-character hex string (with or without 0x prefix). Refusing to start.\n");
|
|
18
|
+
process.exit(1);
|
|
19
|
+
}
|
|
20
|
+
const account = privateKeyToAccount(`0x${normalized}`);
|
|
21
|
+
walletAddress = account.address;
|
|
22
|
+
payFetch = wrapFetchWithPayment(fetch, account, BigInt(Math.round(Number(MAX_PRICE_USD) * 1_000_000)));
|
|
23
|
+
}
|
|
24
|
+
const server = new McpServer({
|
|
25
|
+
name: "skim-mcp",
|
|
26
|
+
version: "0.1.0",
|
|
27
|
+
});
|
|
28
|
+
server.tool("read_url", "Fetch any URL and return clean, agent-ready Markdown via Skim (skim402.com). Strips nav, ads, and boilerplate; preserves the article body plus structured metadata (title, byline, published date, language, excerpt). Pays $0.002 per call in USDC on Base over the x402 protocol — no API keys, no signup. Use this whenever you need to read web content: articles, docs, blog posts, GitHub READMEs, research papers, etc.", {
|
|
29
|
+
url: z
|
|
30
|
+
.string()
|
|
31
|
+
.url()
|
|
32
|
+
.describe("The fully-qualified URL to fetch and clean (https://...)."),
|
|
33
|
+
}, async ({ url }) => {
|
|
34
|
+
if (!PRIVATE_KEY) {
|
|
35
|
+
return {
|
|
36
|
+
isError: true,
|
|
37
|
+
content: [
|
|
38
|
+
{
|
|
39
|
+
type: "text",
|
|
40
|
+
text: "Skim requires payment via x402. Set the SKIM_WALLET_PRIVATE_KEY environment variable to a Base wallet funded with USDC. See https://skim402.com for details.",
|
|
41
|
+
},
|
|
42
|
+
],
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
let res;
|
|
46
|
+
try {
|
|
47
|
+
res = await payFetch(`${BASE_URL}/api/v1/read`, {
|
|
48
|
+
method: "POST",
|
|
49
|
+
headers: { "Content-Type": "application/json" },
|
|
50
|
+
body: JSON.stringify({ url, mode: "basic" }),
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
catch (err) {
|
|
54
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
55
|
+
return {
|
|
56
|
+
isError: true,
|
|
57
|
+
content: [
|
|
58
|
+
{
|
|
59
|
+
type: "text",
|
|
60
|
+
text: `Skim request failed: ${msg}. Common causes: wallet has no USDC on Base, or the price exceeds SKIM_MAX_PRICE_USD (${MAX_PRICE_USD}).`,
|
|
61
|
+
},
|
|
62
|
+
],
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
if (!res.ok) {
|
|
66
|
+
const body = await res.text().catch(() => "");
|
|
67
|
+
return {
|
|
68
|
+
isError: true,
|
|
69
|
+
content: [
|
|
70
|
+
{
|
|
71
|
+
type: "text",
|
|
72
|
+
text: `Skim returned ${res.status} ${res.statusText}: ${body || "(no body)"}`,
|
|
73
|
+
},
|
|
74
|
+
],
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
const data = (await res.json());
|
|
78
|
+
const metaLines = data.metadata
|
|
79
|
+
? Object.entries(data.metadata)
|
|
80
|
+
.filter(([, v]) => v != null && v !== "")
|
|
81
|
+
.map(([k, v]) => `${k}: ${typeof v === "string" ? v : JSON.stringify(v)}`)
|
|
82
|
+
: [];
|
|
83
|
+
const frontmatter = metaLines.length > 0 ? `---\n${metaLines.join("\n")}\n---\n\n` : "";
|
|
84
|
+
return {
|
|
85
|
+
content: [
|
|
86
|
+
{
|
|
87
|
+
type: "text",
|
|
88
|
+
text: frontmatter + (data.markdown ?? data.text ?? ""),
|
|
89
|
+
},
|
|
90
|
+
],
|
|
91
|
+
};
|
|
92
|
+
});
|
|
93
|
+
const transport = new StdioServerTransport();
|
|
94
|
+
await server.connect(transport);
|
|
95
|
+
// Surface startup status on stderr so MCP clients can show it in their logs
|
|
96
|
+
// without interfering with the stdio JSON-RPC stream.
|
|
97
|
+
if (walletAddress) {
|
|
98
|
+
process.stderr.write(`skim-mcp ready — paying from ${walletAddress} (max $${MAX_PRICE_USD}/call) → ${BASE_URL}\n`);
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
process.stderr.write(`skim-mcp ready (NO WALLET) — set SKIM_WALLET_PRIVATE_KEY to enable paid reads → ${BASE_URL}\n`);
|
|
102
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "skim-mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "MCP server for Skim — clean web reader for AI agents. Pays $0.002 per call in USDC over x402. No signup, no API keys.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"skim-mcp": "dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"main": "dist/index.js",
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"README.md",
|
|
13
|
+
"LICENSE"
|
|
14
|
+
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsc -p tsconfig.build.json && node -e \"require('fs').chmodSync('dist/index.js', 0o755)\"",
|
|
17
|
+
"typecheck": "tsc --noEmit",
|
|
18
|
+
"dev": "tsx src/index.ts",
|
|
19
|
+
"prepublishOnly": "pnpm build"
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
23
|
+
"viem": "^2.21.0",
|
|
24
|
+
"x402-fetch": "^0.6.0",
|
|
25
|
+
"zod": "catalog:"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@types/node": "catalog:",
|
|
29
|
+
"tsx": "catalog:",
|
|
30
|
+
"typescript": "^5.9.0"
|
|
31
|
+
},
|
|
32
|
+
"publishConfig": {
|
|
33
|
+
"access": "public",
|
|
34
|
+
"registry": "https://registry.npmjs.org/"
|
|
35
|
+
},
|
|
36
|
+
"keywords": [
|
|
37
|
+
"mcp",
|
|
38
|
+
"model-context-protocol",
|
|
39
|
+
"x402",
|
|
40
|
+
"skim",
|
|
41
|
+
"ai",
|
|
42
|
+
"agent",
|
|
43
|
+
"reader",
|
|
44
|
+
"markdown",
|
|
45
|
+
"web-scraping",
|
|
46
|
+
"claude",
|
|
47
|
+
"cursor"
|
|
48
|
+
],
|
|
49
|
+
"homepage": "https://skim402.com",
|
|
50
|
+
"repository": {
|
|
51
|
+
"type": "git",
|
|
52
|
+
"url": "https://github.com/skim402/skim-mcp"
|
|
53
|
+
},
|
|
54
|
+
"license": "MIT",
|
|
55
|
+
"engines": {
|
|
56
|
+
"node": ">=18"
|
|
57
|
+
}
|
|
58
|
+
}
|