substack-article-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/README.md +124 -0
- package/dist/auth.d.ts +19 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +179 -0
- package/dist/auth.js.map +1 -0
- package/dist/client.d.ts +31 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +69 -0
- package/dist/client.js.map +1 -0
- package/dist/html-to-md.d.ts +2 -0
- package/dist/html-to-md.d.ts.map +1 -0
- package/dist/html-to-md.js +111 -0
- package/dist/html-to-md.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +91 -0
- package/dist/index.js.map +1 -0
- package/dist/server.d.ts +2 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +188 -0
- package/dist/server.js.map +1 -0
- package/package.json +54 -0
package/README.md
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# substack-article-mcp
|
|
2
|
+
|
|
3
|
+
MCP server for Substack — access your articles, notes, and engagement data from Claude, Cursor, Claude Desktop, or any MCP client.
|
|
4
|
+
|
|
5
|
+
**Authenticated access** means you get full content for premium/paywalled articles, not just the free preview.
|
|
6
|
+
|
|
7
|
+
## Quick Start
|
|
8
|
+
|
|
9
|
+
### 1. Authenticate
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npx substack-article-mcp login
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
This opens Chrome, you log into Substack, and the session cookie is saved automatically. One-time setup that lasts ~2-4 weeks.
|
|
16
|
+
|
|
17
|
+
**Alternative** (manual cookie paste):
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npx substack-article-mcp login --manual "your-substack-sid-cookie-value"
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### 2. Configure Your MCP Client
|
|
24
|
+
|
|
25
|
+
Add to your MCP configuration:
|
|
26
|
+
|
|
27
|
+
**Cursor** (`~/.cursor/mcp.json`):
|
|
28
|
+
|
|
29
|
+
```json
|
|
30
|
+
{
|
|
31
|
+
"mcpServers": {
|
|
32
|
+
"substack": {
|
|
33
|
+
"command": "npx",
|
|
34
|
+
"args": ["-y", "substack-article-mcp"],
|
|
35
|
+
"env": {
|
|
36
|
+
"SUBSTACK_SUBDOMAIN": "your-subdomain"
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
**Claude Desktop** (`~/Library/Application Support/Claude/claude_desktop_config.json`):
|
|
44
|
+
|
|
45
|
+
```json
|
|
46
|
+
{
|
|
47
|
+
"mcpServers": {
|
|
48
|
+
"substack": {
|
|
49
|
+
"command": "npx",
|
|
50
|
+
"args": ["-y", "substack-article-mcp"],
|
|
51
|
+
"env": {
|
|
52
|
+
"SUBSTACK_SUBDOMAIN": "your-subdomain"
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
**Claude Code**:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
claude mcp add substack -- npx -y substack-article-mcp
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Then set the environment variable `SUBSTACK_SUBDOMAIN` in your shell or MCP config.
|
|
66
|
+
|
|
67
|
+
Your **subdomain** is the part before `.substack.com` in your newsletter URL. For example: `buildtolaunch.substack.com` → subdomain is `buildtolaunch`.
|
|
68
|
+
|
|
69
|
+
### 3. Use It
|
|
70
|
+
|
|
71
|
+
Just talk to your AI assistant:
|
|
72
|
+
|
|
73
|
+
- "List my recent Substack articles"
|
|
74
|
+
- "Get the full content of my article about Claude Code"
|
|
75
|
+
- "Search my articles for posts about vibe coding"
|
|
76
|
+
- "Check my Substack auth status"
|
|
77
|
+
|
|
78
|
+
## Available Tools
|
|
79
|
+
|
|
80
|
+
| Tool | Description |
|
|
81
|
+
|------|-------------|
|
|
82
|
+
| `substack_auth_status` | Check if authentication is valid, show cookie age |
|
|
83
|
+
| `list_articles` | List published articles with metadata (title, date, slug, engagement, paid/free) |
|
|
84
|
+
| `get_article` | Get full article content as markdown (includes premium content when authenticated) |
|
|
85
|
+
| `search_articles` | Search published articles by keyword |
|
|
86
|
+
|
|
87
|
+
## CLI Commands
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
substack-article-mcp # Start the MCP server
|
|
91
|
+
substack-article-mcp login # Authenticate via Chrome (automated)
|
|
92
|
+
substack-article-mcp login --manual <sid> # Authenticate with a pasted cookie value
|
|
93
|
+
substack-article-mcp login --check # Check authentication status
|
|
94
|
+
substack-article-mcp --help # Show help
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## How Authentication Works
|
|
98
|
+
|
|
99
|
+
Substack doesn't have a public API. This MCP uses the same internal API that the Substack website uses, authenticated with your browser session cookie.
|
|
100
|
+
|
|
101
|
+
1. **`substack-article-mcp login`** launches Chrome with a dedicated profile
|
|
102
|
+
2. You log into Substack in the browser window
|
|
103
|
+
3. The `substack.sid` cookie is automatically extracted via Chrome DevTools Protocol
|
|
104
|
+
4. The cookie is stored locally at `~/.substack-article-mcp/auth.json`
|
|
105
|
+
5. The MCP server reads this cookie to make authenticated API requests
|
|
106
|
+
|
|
107
|
+
**Security notes:**
|
|
108
|
+
- Your cookie is stored locally on your machine only (`~/.substack-article-mcp/auth.json`)
|
|
109
|
+
- The cookie is never sent anywhere except to `substack.com` API endpoints
|
|
110
|
+
- Chrome profile data stays in `~/.substack-article-mcp/chrome-profile/`
|
|
111
|
+
- Re-authenticate by running `substack-article-mcp login` again when the cookie expires (~2-4 weeks)
|
|
112
|
+
|
|
113
|
+
## Requirements
|
|
114
|
+
|
|
115
|
+
- **Node.js** 18 or later
|
|
116
|
+
- **Google Chrome** (for the `login` command — the MCP server itself doesn't need Chrome)
|
|
117
|
+
|
|
118
|
+
## Disclaimer
|
|
119
|
+
|
|
120
|
+
This MCP uses Substack's internal APIs which are undocumented and may change without notice. Use for personal/experimental purposes. Not affiliated with Substack.
|
|
121
|
+
|
|
122
|
+
## License
|
|
123
|
+
|
|
124
|
+
MIT
|
package/dist/auth.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
interface StoredAuth {
|
|
2
|
+
substackSid: string;
|
|
3
|
+
extractedAt: string;
|
|
4
|
+
subdomain?: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function getAuthDir(): string;
|
|
7
|
+
export declare function loadAuth(): StoredAuth | null;
|
|
8
|
+
export declare function saveAuth(sid: string, subdomain?: string): void;
|
|
9
|
+
export declare function getCookieHeaders(): Record<string, string>;
|
|
10
|
+
export declare function runLogin(): Promise<void>;
|
|
11
|
+
export declare function runManualLogin(sid: string): Promise<void>;
|
|
12
|
+
export declare function validateStoredAuth(): Promise<boolean>;
|
|
13
|
+
export declare function checkAuthStatus(): Promise<{
|
|
14
|
+
authenticated: boolean;
|
|
15
|
+
cookieAge?: string;
|
|
16
|
+
subdomain?: string;
|
|
17
|
+
}>;
|
|
18
|
+
export {};
|
|
19
|
+
//# sourceMappingURL=auth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAQA,UAAU,UAAU;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,UAAU,IAAI,MAAM,CAKnC;AAED,wBAAgB,QAAQ,IAAI,UAAU,GAAG,IAAI,CAU5C;AAED,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAQ9D;AAED,wBAAgB,gBAAgB,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAYzD;AAyCD,wBAAsB,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CAqE9C;AA+BD,wBAAsB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAY/D;AAED,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,OAAO,CAAC,CAiB3D;AAED,wBAAsB,eAAe,IAAI,OAAO,CAAC;IAC/C,aAAa,EAAE,OAAO,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC,CAmBD"}
|
package/dist/auth.js
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
const AUTH_DIR = join(homedir(), ".substack-article-mcp");
|
|
5
|
+
const AUTH_FILE = join(AUTH_DIR, "auth.json");
|
|
6
|
+
const CHROME_PROFILE_DIR = join(AUTH_DIR, "chrome-profile");
|
|
7
|
+
export function getAuthDir() {
|
|
8
|
+
if (!existsSync(AUTH_DIR)) {
|
|
9
|
+
mkdirSync(AUTH_DIR, { recursive: true });
|
|
10
|
+
}
|
|
11
|
+
return AUTH_DIR;
|
|
12
|
+
}
|
|
13
|
+
export function loadAuth() {
|
|
14
|
+
try {
|
|
15
|
+
if (!existsSync(AUTH_FILE))
|
|
16
|
+
return null;
|
|
17
|
+
const raw = readFileSync(AUTH_FILE, "utf-8");
|
|
18
|
+
const data = JSON.parse(raw);
|
|
19
|
+
if (!data.substackSid)
|
|
20
|
+
return null;
|
|
21
|
+
return data;
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export function saveAuth(sid, subdomain) {
|
|
28
|
+
getAuthDir();
|
|
29
|
+
const data = {
|
|
30
|
+
substackSid: sid,
|
|
31
|
+
extractedAt: new Date().toISOString(),
|
|
32
|
+
subdomain,
|
|
33
|
+
};
|
|
34
|
+
writeFileSync(AUTH_FILE, JSON.stringify(data, null, 2), "utf-8");
|
|
35
|
+
}
|
|
36
|
+
export function getCookieHeaders() {
|
|
37
|
+
const auth = loadAuth();
|
|
38
|
+
if (!auth) {
|
|
39
|
+
throw new Error("Not authenticated. Run `substack-article-mcp login` first to connect your Substack account.");
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
Cookie: `substack.sid=${auth.substackSid}; substack.lli=1`,
|
|
43
|
+
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
function findChrome() {
|
|
47
|
+
const { platform } = process;
|
|
48
|
+
const candidates = [];
|
|
49
|
+
if (platform === "darwin") {
|
|
50
|
+
candidates.push("/Applications/Google Chrome.app/Contents/MacOS/Google Chrome", "/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary", "/Applications/Chromium.app/Contents/MacOS/Chromium");
|
|
51
|
+
}
|
|
52
|
+
else if (platform === "linux") {
|
|
53
|
+
candidates.push("/usr/bin/google-chrome", "/usr/bin/google-chrome-stable", "/usr/bin/chromium-browser", "/usr/bin/chromium", "/snap/bin/chromium");
|
|
54
|
+
}
|
|
55
|
+
else if (platform === "win32") {
|
|
56
|
+
const programFiles = process.env["PROGRAMFILES"] || "C:\\Program Files";
|
|
57
|
+
const programFilesX86 = process.env["PROGRAMFILES(X86)"] || "C:\\Program Files (x86)";
|
|
58
|
+
const localAppData = process.env["LOCALAPPDATA"] || join(homedir(), "AppData", "Local");
|
|
59
|
+
candidates.push(join(programFiles, "Google", "Chrome", "Application", "chrome.exe"), join(programFilesX86, "Google", "Chrome", "Application", "chrome.exe"), join(localAppData, "Google", "Chrome", "Application", "chrome.exe"));
|
|
60
|
+
}
|
|
61
|
+
for (const path of candidates) {
|
|
62
|
+
if (existsSync(path))
|
|
63
|
+
return path;
|
|
64
|
+
}
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
export async function runLogin() {
|
|
68
|
+
const chromePath = findChrome();
|
|
69
|
+
if (!chromePath) {
|
|
70
|
+
console.error("Could not find Chrome/Chromium on your system.");
|
|
71
|
+
console.error("Install Google Chrome and try again.");
|
|
72
|
+
console.error("\nAlternative: run `substack-article-mcp login --manual` to paste your cookie directly.");
|
|
73
|
+
process.exit(1);
|
|
74
|
+
}
|
|
75
|
+
console.log("Launching Chrome for Substack login...");
|
|
76
|
+
console.log(`Chrome: ${chromePath}`);
|
|
77
|
+
// Dynamic import to avoid loading puppeteer when running the MCP server
|
|
78
|
+
const puppeteer = await import("puppeteer-core");
|
|
79
|
+
getAuthDir();
|
|
80
|
+
const browser = await puppeteer.launch({
|
|
81
|
+
executablePath: chromePath,
|
|
82
|
+
headless: false,
|
|
83
|
+
userDataDir: CHROME_PROFILE_DIR,
|
|
84
|
+
args: [
|
|
85
|
+
"--no-first-run",
|
|
86
|
+
"--no-default-browser-check",
|
|
87
|
+
"--window-size=1200,800",
|
|
88
|
+
],
|
|
89
|
+
defaultViewport: null,
|
|
90
|
+
});
|
|
91
|
+
const pages = await browser.pages();
|
|
92
|
+
const page = pages[0] || (await browser.newPage());
|
|
93
|
+
await page.goto("https://substack.com/sign-in", {
|
|
94
|
+
waitUntil: "networkidle2",
|
|
95
|
+
});
|
|
96
|
+
console.log("\n┌─────────────────────────────────────────────┐");
|
|
97
|
+
console.log("│ Log in to Substack in the browser window. │");
|
|
98
|
+
console.log("│ The window will close automatically once │");
|
|
99
|
+
console.log("│ your session cookie is detected. │");
|
|
100
|
+
console.log("└─────────────────────────────────────────────┘\n");
|
|
101
|
+
const sid = await waitForCookie(page, "substack.sid", 300_000);
|
|
102
|
+
if (!sid) {
|
|
103
|
+
console.error("Timed out waiting for login. Please try again.");
|
|
104
|
+
await browser.close();
|
|
105
|
+
process.exit(1);
|
|
106
|
+
}
|
|
107
|
+
saveAuth(sid);
|
|
108
|
+
console.log("Cookies saved. Validating...");
|
|
109
|
+
const valid = await validateStoredAuth();
|
|
110
|
+
await browser.close();
|
|
111
|
+
if (valid) {
|
|
112
|
+
console.log("\n✅ Authenticated successfully!");
|
|
113
|
+
console.log(` Cookies stored in ${AUTH_FILE}`);
|
|
114
|
+
console.log(" You can now use the Substack Article MCP server in Cursor, Claude Code, etc.");
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
console.error("\n⚠️ Cookie was saved but validation failed. You may need to try again.");
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
async function waitForCookie(page, cookieName, timeoutMs) {
|
|
121
|
+
const start = Date.now();
|
|
122
|
+
while (Date.now() - start < timeoutMs) {
|
|
123
|
+
try {
|
|
124
|
+
const client = await page.createCDPSession();
|
|
125
|
+
const result = (await client.send("Network.getAllCookies"));
|
|
126
|
+
await client.detach();
|
|
127
|
+
const match = result.cookies.find((c) => c.name === cookieName);
|
|
128
|
+
if (match && match.value) {
|
|
129
|
+
return match.value;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
// Page may have navigated or CDP session failed — retry
|
|
134
|
+
}
|
|
135
|
+
await new Promise((r) => setTimeout(r, 2000));
|
|
136
|
+
}
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
export async function runManualLogin(sid) {
|
|
140
|
+
saveAuth(sid);
|
|
141
|
+
console.log("Cookie saved. Validating...");
|
|
142
|
+
const valid = await validateStoredAuth();
|
|
143
|
+
if (valid) {
|
|
144
|
+
console.log("\n✅ Authenticated successfully!");
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
console.error("\n⚠️ Cookie saved but validation failed. The cookie may be expired or invalid.");
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
export async function validateStoredAuth() {
|
|
151
|
+
try {
|
|
152
|
+
const headers = getCookieHeaders();
|
|
153
|
+
const subdomain = process.env["SUBSTACK_SUBDOMAIN"] ||
|
|
154
|
+
loadAuth()?.subdomain ||
|
|
155
|
+
"substack";
|
|
156
|
+
const res = await fetch(`https://${subdomain}.substack.com/api/v1/archive?limit=1`, { headers });
|
|
157
|
+
return res.ok;
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
export async function checkAuthStatus() {
|
|
164
|
+
const auth = loadAuth();
|
|
165
|
+
if (!auth) {
|
|
166
|
+
return { authenticated: false };
|
|
167
|
+
}
|
|
168
|
+
const valid = await validateStoredAuth();
|
|
169
|
+
const extractedDate = new Date(auth.extractedAt);
|
|
170
|
+
const ageMs = Date.now() - extractedDate.getTime();
|
|
171
|
+
const ageDays = Math.floor(ageMs / (1000 * 60 * 60 * 24));
|
|
172
|
+
const ageHours = Math.floor((ageMs % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
|
|
173
|
+
return {
|
|
174
|
+
authenticated: valid,
|
|
175
|
+
cookieAge: `${ageDays}d ${ageHours}h (extracted ${auth.extractedAt})`,
|
|
176
|
+
subdomain: auth.subdomain,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
//# sourceMappingURL=auth.js.map
|
package/dist/auth.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC7E,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,uBAAuB,CAAC,CAAC;AAC1D,MAAM,SAAS,GAAG,IAAI,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;AAC9C,MAAM,kBAAkB,GAAG,IAAI,CAAC,QAAQ,EAAE,gBAAgB,CAAC,CAAC;AAQ5D,MAAM,UAAU,UAAU;IACxB,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC1B,SAAS,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC3C,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,MAAM,UAAU,QAAQ;IACtB,IAAI,CAAC;QACH,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC;YAAE,OAAO,IAAI,CAAC;QACxC,MAAM,GAAG,GAAG,YAAY,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;QAC7C,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAe,CAAC;QAC3C,IAAI,CAAC,IAAI,CAAC,WAAW;YAAE,OAAO,IAAI,CAAC;QACnC,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,GAAW,EAAE,SAAkB;IACtD,UAAU,EAAE,CAAC;IACb,MAAM,IAAI,GAAe;QACvB,WAAW,EAAE,GAAG;QAChB,WAAW,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QACrC,SAAS;KACV,CAAC;IACF,aAAa,CAAC,SAAS,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;AACnE,CAAC;AAED,MAAM,UAAU,gBAAgB;IAC9B,MAAM,IAAI,GAAG,QAAQ,EAAE,CAAC;IACxB,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,MAAM,IAAI,KAAK,CACb,6FAA6F,CAC9F,CAAC;IACJ,CAAC;IACD,OAAO;QACL,MAAM,EAAE,gBAAgB,IAAI,CAAC,WAAW,kBAAkB;QAC1D,YAAY,EACV,uHAAuH;KAC1H,CAAC;AACJ,CAAC;AAED,SAAS,UAAU;IACjB,MAAM,EAAE,QAAQ,EAAE,GAAG,OAAO,CAAC;IAE7B,MAAM,UAAU,GAAa,EAAE,CAAC;IAEhC,IAAI,QAAQ,KAAK,QAAQ,EAAE,CAAC;QAC1B,UAAU,CAAC,IAAI,CACb,8DAA8D,EAC9D,4EAA4E,EAC5E,oDAAoD,CACrD,CAAC;IACJ,CAAC;SAAM,IAAI,QAAQ,KAAK,OAAO,EAAE,CAAC;QAChC,UAAU,CAAC,IAAI,CACb,wBAAwB,EACxB,+BAA+B,EAC/B,2BAA2B,EAC3B,mBAAmB,EACnB,oBAAoB,CACrB,CAAC;IACJ,CAAC;SAAM,IAAI,QAAQ,KAAK,OAAO,EAAE,CAAC;QAChC,MAAM,YAAY,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,IAAI,mBAAmB,CAAC;QACxE,MAAM,eAAe,GACnB,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC,IAAI,yBAAyB,CAAC;QAChE,MAAM,YAAY,GAChB,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,IAAI,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC;QAErE,UAAU,CAAC,IAAI,CACb,IAAI,CAAC,YAAY,EAAE,QAAQ,EAAE,QAAQ,EAAE,aAAa,EAAE,YAAY,CAAC,EACnE,IAAI,CAAC,eAAe,EAAE,QAAQ,EAAE,QAAQ,EAAE,aAAa,EAAE,YAAY,CAAC,EACtE,IAAI,CAAC,YAAY,EAAE,QAAQ,EAAE,QAAQ,EAAE,aAAa,EAAE,YAAY,CAAC,CACpE,CAAC;IACJ,CAAC;IAED,KAAK,MAAM,IAAI,IAAI,UAAU,EAAE,CAAC;QAC9B,IAAI,UAAU,CAAC,IAAI,CAAC;YAAE,OAAO,IAAI,CAAC;IACpC,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,QAAQ;IAC5B,MAAM,UAAU,GAAG,UAAU,EAAE,CAAC;IAChC,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,OAAO,CAAC,KAAK,CAAC,gDAAgD,CAAC,CAAC;QAChE,OAAO,CAAC,KAAK,CAAC,sCAAsC,CAAC,CAAC;QACtD,OAAO,CAAC,KAAK,CACX,yFAAyF,CAC1F,CAAC;QACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,wCAAwC,CAAC,CAAC;IACtD,OAAO,CAAC,GAAG,CAAC,WAAW,UAAU,EAAE,CAAC,CAAC;IAErC,wEAAwE;IACxE,MAAM,SAAS,GAAG,MAAM,MAAM,CAAC,gBAAgB,CAAC,CAAC;IAEjD,UAAU,EAAE,CAAC;IAEb,MAAM,OAAO,GAAG,MAAM,SAAS,CAAC,MAAM,CAAC;QACrC,cAAc,EAAE,UAAU;QAC1B,QAAQ,EAAE,KAAK;QACf,WAAW,EAAE,kBAAkB;QAC/B,IAAI,EAAE;YACJ,gBAAgB;YAChB,4BAA4B;YAC5B,wBAAwB;SACzB;QACD,eAAe,EAAE,IAAI;KACtB,CAAC,CAAC;IAEH,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;IACpC,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;IAEnD,MAAM,IAAI,CAAC,IAAI,CAAC,8BAA8B,EAAE;QAC9C,SAAS,EAAE,cAAc;KAC1B,CAAC,CAAC;IAEH,OAAO,CAAC,GAAG,CAAC,mDAAmD,CAAC,CAAC;IACjE,OAAO,CAAC,GAAG,CAAC,iDAAiD,CAAC,CAAC;IAC/D,OAAO,CAAC,GAAG,CAAC,kDAAkD,CAAC,CAAC;IAChE,OAAO,CAAC,GAAG,CAAC,kDAAkD,CAAC,CAAC;IAChE,OAAO,CAAC,GAAG,CAAC,mDAAmD,CAAC,CAAC;IAEjE,MAAM,GAAG,GAAG,MAAM,aAAa,CAAC,IAAI,EAAE,cAAc,EAAE,OAAO,CAAC,CAAC;IAE/D,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,OAAO,CAAC,KAAK,CAAC,gDAAgD,CAAC,CAAC;QAChE,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;QACtB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,QAAQ,CAAC,GAAG,CAAC,CAAC;IACd,OAAO,CAAC,GAAG,CAAC,8BAA8B,CAAC,CAAC;IAE5C,MAAM,KAAK,GAAG,MAAM,kBAAkB,EAAE,CAAC;IACzC,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;IAEtB,IAAI,KAAK,EAAE,CAAC;QACV,OAAO,CAAC,GAAG,CAAC,iCAAiC,CAAC,CAAC;QAC/C,OAAO,CAAC,GAAG,CAAC,wBAAwB,SAAS,EAAE,CAAC,CAAC;QACjD,OAAO,CAAC,GAAG,CACT,iFAAiF,CAClF,CAAC;IACJ,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,KAAK,CACX,0EAA0E,CAC3E,CAAC;IACJ,CAAC;AACH,CAAC;AAED,KAAK,UAAU,aAAa,CAC1B,IAA0G,EAC1G,UAAkB,EAClB,SAAiB;IAEjB,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAEzB,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,GAAG,SAAS,EAAE,CAAC;QACtC,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,gBAAgB,EAAE,CAAC;YAC7C,MAAM,MAAM,GAAG,CAAC,MAAM,MAAM,CAAC,IAAI,CAAC,uBAAuB,CAAC,CAEzD,CAAC;YACF,MAAM,MAAM,CAAC,MAAM,EAAE,CAAC;YAEtB,MAAM,KAAK,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,UAAU,CAAC,CAAC;YAChE,IAAI,KAAK,IAAI,KAAK,CAAC,KAAK,EAAE,CAAC;gBACzB,OAAO,KAAK,CAAC,KAAK,CAAC;YACrB,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,wDAAwD;QAC1D,CAAC;QAED,MAAM,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC;IAChD,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,GAAW;IAC9C,QAAQ,CAAC,GAAG,CAAC,CAAC;IACd,OAAO,CAAC,GAAG,CAAC,6BAA6B,CAAC,CAAC;IAE3C,MAAM,KAAK,GAAG,MAAM,kBAAkB,EAAE,CAAC;IACzC,IAAI,KAAK,EAAE,CAAC;QACV,OAAO,CAAC,GAAG,CAAC,iCAAiC,CAAC,CAAC;IACjD,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,KAAK,CACX,iFAAiF,CAClF,CAAC;IACJ,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,kBAAkB;IACtC,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,gBAAgB,EAAE,CAAC;QACnC,MAAM,SAAS,GACb,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC;YACjC,QAAQ,EAAE,EAAE,SAAS;YACrB,UAAU,CAAC;QAEb,MAAM,GAAG,GAAG,MAAM,KAAK,CACrB,WAAW,SAAS,sCAAsC,EAC1D,EAAE,OAAO,EAAE,CACZ,CAAC;QAEF,OAAO,GAAG,CAAC,EAAE,CAAC;IAChB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,eAAe;IAKnC,MAAM,IAAI,GAAG,QAAQ,EAAE,CAAC;IACxB,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,OAAO,EAAE,aAAa,EAAE,KAAK,EAAE,CAAC;IAClC,CAAC;IAED,MAAM,KAAK,GAAG,MAAM,kBAAkB,EAAE,CAAC;IACzC,MAAM,aAAa,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IACjD,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,aAAa,CAAC,OAAO,EAAE,CAAC;IACnD,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,IAAI,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC;IAC1D,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CACzB,CAAC,KAAK,GAAG,CAAC,IAAI,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,CAAC,IAAI,GAAG,EAAE,GAAG,EAAE,CAAC,CACnD,CAAC;IAEF,OAAO;QACL,aAAa,EAAE,KAAK;QACpB,SAAS,EAAE,GAAG,OAAO,KAAK,QAAQ,gBAAgB,IAAI,CAAC,WAAW,GAAG;QACrE,SAAS,EAAE,IAAI,CAAC,SAAS;KAC1B,CAAC;AACJ,CAAC"}
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export interface SubstackArticle {
|
|
2
|
+
id: number;
|
|
3
|
+
title: string;
|
|
4
|
+
slug: string;
|
|
5
|
+
subtitle: string;
|
|
6
|
+
description: string;
|
|
7
|
+
publishedAt: string;
|
|
8
|
+
canonicalUrl: string;
|
|
9
|
+
audience: string;
|
|
10
|
+
section?: {
|
|
11
|
+
name: string;
|
|
12
|
+
};
|
|
13
|
+
wordCount?: number;
|
|
14
|
+
postDate: string;
|
|
15
|
+
likes: number;
|
|
16
|
+
comments: number;
|
|
17
|
+
restacks: number;
|
|
18
|
+
}
|
|
19
|
+
export interface SubstackArticleFull extends SubstackArticle {
|
|
20
|
+
bodyHtml: string;
|
|
21
|
+
truncatedBodyText?: string;
|
|
22
|
+
}
|
|
23
|
+
export declare function listArticles(options: {
|
|
24
|
+
limit?: number;
|
|
25
|
+
offset?: number;
|
|
26
|
+
sort?: "new" | "top";
|
|
27
|
+
search?: string;
|
|
28
|
+
}): Promise<SubstackArticle[]>;
|
|
29
|
+
export declare function getArticle(slugOrId: string): Promise<SubstackArticleFull>;
|
|
30
|
+
export declare function searchArticles(query: string, limit?: number): Promise<SubstackArticle[]>;
|
|
31
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IAC3B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,mBAAoB,SAAQ,eAAe;IAC1D,QAAQ,EAAE,MAAM,CAAC;IACjB,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AA2DD,wBAAsB,YAAY,CAAC,OAAO,EAAE;IAC1C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,KAAK,GAAG,KAAK,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC,CAgB7B;AAED,wBAAsB,UAAU,CAC9B,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,mBAAmB,CAAC,CAW9B;AAED,wBAAsB,cAAc,CAClC,KAAK,EAAE,MAAM,EACb,KAAK,GAAE,MAAW,GACjB,OAAO,CAAC,eAAe,EAAE,CAAC,CAE5B"}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { getCookieHeaders, loadAuth } from "./auth.js";
|
|
2
|
+
function getBaseUrl() {
|
|
3
|
+
const subdomain = process.env["SUBSTACK_SUBDOMAIN"] || loadAuth()?.subdomain;
|
|
4
|
+
if (!subdomain) {
|
|
5
|
+
throw new Error("SUBSTACK_SUBDOMAIN environment variable is required. Set it in your MCP config.");
|
|
6
|
+
}
|
|
7
|
+
return `https://${subdomain}.substack.com`;
|
|
8
|
+
}
|
|
9
|
+
async function apiGet(endpoint) {
|
|
10
|
+
const url = `${getBaseUrl()}${endpoint}`;
|
|
11
|
+
const headers = getCookieHeaders();
|
|
12
|
+
const res = await fetch(url, {
|
|
13
|
+
headers: {
|
|
14
|
+
...headers,
|
|
15
|
+
Accept: "application/json",
|
|
16
|
+
},
|
|
17
|
+
});
|
|
18
|
+
if (res.status === 401 || res.status === 403) {
|
|
19
|
+
throw new Error("Authentication failed. Your cookie may have expired. Run `substack-article-mcp login` to re-authenticate.");
|
|
20
|
+
}
|
|
21
|
+
if (!res.ok) {
|
|
22
|
+
const text = await res.text();
|
|
23
|
+
throw new Error(`Substack API error ${res.status}: ${text}`);
|
|
24
|
+
}
|
|
25
|
+
return res.json();
|
|
26
|
+
}
|
|
27
|
+
function normalizePost(raw) {
|
|
28
|
+
return {
|
|
29
|
+
id: raw["id"],
|
|
30
|
+
title: raw["title"] || "",
|
|
31
|
+
slug: raw["slug"] || "",
|
|
32
|
+
subtitle: raw["subtitle"] || "",
|
|
33
|
+
description: raw["description"] || "",
|
|
34
|
+
publishedAt: raw["post_date"] || "",
|
|
35
|
+
canonicalUrl: raw["canonical_url"] || "",
|
|
36
|
+
audience: raw["audience"] || "everyone",
|
|
37
|
+
section: raw["publishedBylines"]
|
|
38
|
+
? undefined
|
|
39
|
+
: (raw["section"] || undefined),
|
|
40
|
+
wordCount: raw["wordcount"],
|
|
41
|
+
postDate: raw["post_date"] || "",
|
|
42
|
+
likes: raw["reaction_count"] || 0,
|
|
43
|
+
comments: raw["comment_count"] || 0,
|
|
44
|
+
restacks: raw["restacks"] || 0,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
export async function listArticles(options) {
|
|
48
|
+
const { limit = 12, offset = 0, sort = "new", search = "" } = options;
|
|
49
|
+
const params = new URLSearchParams({
|
|
50
|
+
sort,
|
|
51
|
+
search,
|
|
52
|
+
offset: String(offset),
|
|
53
|
+
limit: String(limit),
|
|
54
|
+
});
|
|
55
|
+
const data = (await apiGet(`/api/v1/archive?${params}`));
|
|
56
|
+
return data.map(normalizePost);
|
|
57
|
+
}
|
|
58
|
+
export async function getArticle(slugOrId) {
|
|
59
|
+
const raw = (await apiGet(`/api/v1/posts/${slugOrId}`));
|
|
60
|
+
return {
|
|
61
|
+
...normalizePost(raw),
|
|
62
|
+
bodyHtml: raw["body_html"] || "",
|
|
63
|
+
truncatedBodyText: raw["truncated_body_text"],
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
export async function searchArticles(query, limit = 12) {
|
|
67
|
+
return listArticles({ search: query, limit, sort: "new" });
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"html-to-md.d.ts","sourceRoot":"","sources":["../src/html-to-md.ts"],"names":[],"mappings":"AAGA,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAoHnD"}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { load } from "cheerio";
|
|
2
|
+
export function htmlToMarkdown(html) {
|
|
3
|
+
const $ = load(html);
|
|
4
|
+
$("script, style, .subscription-widget, .subscribe-widget, .paywall, .paywall-jump, .post-footer, .share-dialog, .button-wrapper, .captioned-button-wrap, .footer-wrap, .like-button-container").remove();
|
|
5
|
+
function processNode(el) {
|
|
6
|
+
if (el.type === "text") {
|
|
7
|
+
return el.data || "";
|
|
8
|
+
}
|
|
9
|
+
if (el.type !== "tag")
|
|
10
|
+
return "";
|
|
11
|
+
const $el = $(el);
|
|
12
|
+
const tag = el.tagName.toLowerCase();
|
|
13
|
+
const children = $el.contents().toArray();
|
|
14
|
+
const innerText = children.map(processNode).join("");
|
|
15
|
+
switch (tag) {
|
|
16
|
+
case "h1":
|
|
17
|
+
return `\n# ${innerText.trim()}\n\n`;
|
|
18
|
+
case "h2":
|
|
19
|
+
return `\n## ${innerText.trim()}\n\n`;
|
|
20
|
+
case "h3":
|
|
21
|
+
return `\n### ${innerText.trim()}\n\n`;
|
|
22
|
+
case "h4":
|
|
23
|
+
return `\n#### ${innerText.trim()}\n\n`;
|
|
24
|
+
case "h5":
|
|
25
|
+
case "h6":
|
|
26
|
+
return `\n##### ${innerText.trim()}\n\n`;
|
|
27
|
+
case "p":
|
|
28
|
+
return `\n${innerText.trim()}\n\n`;
|
|
29
|
+
case "br":
|
|
30
|
+
return "\n";
|
|
31
|
+
case "hr":
|
|
32
|
+
return "\n---\n\n";
|
|
33
|
+
case "strong":
|
|
34
|
+
case "b":
|
|
35
|
+
return `**${innerText}**`;
|
|
36
|
+
case "em":
|
|
37
|
+
case "i":
|
|
38
|
+
return `*${innerText}*`;
|
|
39
|
+
case "code": {
|
|
40
|
+
const parent = $el.parent();
|
|
41
|
+
if (parent.length && parent[0] && parent[0].tagName === "pre") {
|
|
42
|
+
return innerText;
|
|
43
|
+
}
|
|
44
|
+
return `\`${innerText}\``;
|
|
45
|
+
}
|
|
46
|
+
case "pre": {
|
|
47
|
+
const codeEl = $el.find("code");
|
|
48
|
+
const content = codeEl.length ? codeEl.text() : innerText;
|
|
49
|
+
return `\n\`\`\`\n${content.trim()}\n\`\`\`\n\n`;
|
|
50
|
+
}
|
|
51
|
+
case "blockquote": {
|
|
52
|
+
const lines = innerText.trim().split("\n");
|
|
53
|
+
return "\n" + lines.map((l) => `> ${l}`).join("\n") + "\n\n";
|
|
54
|
+
}
|
|
55
|
+
case "a": {
|
|
56
|
+
const href = $el.attr("href") || "";
|
|
57
|
+
if (href && !href.startsWith("javascript:")) {
|
|
58
|
+
return `[${innerText}](${href})`;
|
|
59
|
+
}
|
|
60
|
+
return innerText;
|
|
61
|
+
}
|
|
62
|
+
case "img": {
|
|
63
|
+
const src = $el.attr("src") || $el.data("src") || "";
|
|
64
|
+
const alt = $el.attr("alt") || "";
|
|
65
|
+
if (src)
|
|
66
|
+
return `\n\n\n`;
|
|
67
|
+
return "";
|
|
68
|
+
}
|
|
69
|
+
case "figure": {
|
|
70
|
+
const img = $el.find("img");
|
|
71
|
+
const caption = $el.find("figcaption");
|
|
72
|
+
if (img.length) {
|
|
73
|
+
const src = img.attr("src") || img.data("src") || "";
|
|
74
|
+
const alt = img.attr("alt") || "";
|
|
75
|
+
let result = `\n\n`;
|
|
76
|
+
if (caption.length) {
|
|
77
|
+
result += `*${caption.text().trim()}*\n`;
|
|
78
|
+
}
|
|
79
|
+
return result + "\n";
|
|
80
|
+
}
|
|
81
|
+
return innerText;
|
|
82
|
+
}
|
|
83
|
+
case "ul":
|
|
84
|
+
case "ol":
|
|
85
|
+
return "\n" + innerText + "\n";
|
|
86
|
+
case "li": {
|
|
87
|
+
const parent = $el.parent();
|
|
88
|
+
if (parent.length &&
|
|
89
|
+
parent[0] &&
|
|
90
|
+
parent[0].tagName === "ol") {
|
|
91
|
+
const idx = $el.index() + 1;
|
|
92
|
+
return `${idx}. ${innerText.trim()}\n`;
|
|
93
|
+
}
|
|
94
|
+
return `- ${innerText.trim()}\n`;
|
|
95
|
+
}
|
|
96
|
+
case "div":
|
|
97
|
+
case "span":
|
|
98
|
+
case "section":
|
|
99
|
+
case "article":
|
|
100
|
+
case "main":
|
|
101
|
+
return innerText;
|
|
102
|
+
default:
|
|
103
|
+
return innerText;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
const body = $("body").contents().toArray();
|
|
107
|
+
let md = body.map(processNode).join("");
|
|
108
|
+
md = md.replace(/\n{3,}/g, "\n\n").trim();
|
|
109
|
+
return md;
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=html-to-md.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"html-to-md.js","sourceRoot":"","sources":["../src/html-to-md.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const args = process.argv.slice(2);
|
|
3
|
+
const command = args[0];
|
|
4
|
+
async function main() {
|
|
5
|
+
if (command === "login") {
|
|
6
|
+
const flag = args[1];
|
|
7
|
+
if (flag === "--check" || flag === "--status") {
|
|
8
|
+
const { checkAuthStatus } = await import("./auth.js");
|
|
9
|
+
const status = await checkAuthStatus();
|
|
10
|
+
if (status.authenticated) {
|
|
11
|
+
console.log("✅ Authenticated");
|
|
12
|
+
console.log(` Cookie age: ${status.cookieAge}`);
|
|
13
|
+
console.log(` Subdomain: ${status.subdomain || process.env["SUBSTACK_SUBDOMAIN"] || "(not set)"}`);
|
|
14
|
+
}
|
|
15
|
+
else {
|
|
16
|
+
console.log("❌ Not authenticated");
|
|
17
|
+
console.log(' Run "substack-article-mcp login" to connect your account.');
|
|
18
|
+
}
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
if (flag === "--manual") {
|
|
22
|
+
const sid = args[2];
|
|
23
|
+
if (!sid) {
|
|
24
|
+
console.error("Usage: substack-article-mcp login --manual <substack.sid value>");
|
|
25
|
+
console.error("\nTo get your cookie value:\n1. Open substack.com in Chrome\n2. Open DevTools (F12) → Application → Cookies\n3. Copy the value of 'substack.sid'");
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
const { runManualLogin } = await import("./auth.js");
|
|
29
|
+
await runManualLogin(sid);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
const { runLogin } = await import("./auth.js");
|
|
33
|
+
await runLogin();
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
if (command === "--help" || command === "-h") {
|
|
37
|
+
printHelp();
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
if (command === "--version" || command === "-v") {
|
|
41
|
+
console.log("substack-article-mcp v0.1.0");
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
// Default: start MCP server
|
|
45
|
+
const { startServer } = await import("./server.js");
|
|
46
|
+
await startServer();
|
|
47
|
+
}
|
|
48
|
+
function printHelp() {
|
|
49
|
+
console.log(`
|
|
50
|
+
substack-article-mcp — Substack MCP Server
|
|
51
|
+
|
|
52
|
+
USAGE
|
|
53
|
+
substack-article-mcp Start the MCP server (stdio transport)
|
|
54
|
+
substack-article-mcp login Launch Chrome to authenticate with Substack
|
|
55
|
+
substack-article-mcp login --manual <sid> Manually provide your substack.sid cookie
|
|
56
|
+
substack-article-mcp login --check Check current authentication status
|
|
57
|
+
|
|
58
|
+
MCP CLIENT CONFIGURATION
|
|
59
|
+
|
|
60
|
+
Cursor / Claude Desktop / Claude Code:
|
|
61
|
+
{
|
|
62
|
+
"mcpServers": {
|
|
63
|
+
"substack": {
|
|
64
|
+
"command": "npx",
|
|
65
|
+
"args": ["-y", "substack-article-mcp"],
|
|
66
|
+
"env": {
|
|
67
|
+
"SUBSTACK_SUBDOMAIN": "your-subdomain"
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
Your subdomain is the part before .substack.com in your newsletter URL.
|
|
74
|
+
For example: buildtolaunch.substack.com → subdomain is "buildtolaunch"
|
|
75
|
+
|
|
76
|
+
ENVIRONMENT VARIABLES
|
|
77
|
+
SUBSTACK_SUBDOMAIN Your Substack subdomain (required)
|
|
78
|
+
|
|
79
|
+
MCP TOOLS
|
|
80
|
+
substack_auth_status Check authentication status
|
|
81
|
+
list_articles List published articles with metadata
|
|
82
|
+
get_article Get full article content as markdown
|
|
83
|
+
search_articles Search articles by keyword
|
|
84
|
+
`);
|
|
85
|
+
}
|
|
86
|
+
main().catch((error) => {
|
|
87
|
+
console.error("Fatal error:", error);
|
|
88
|
+
process.exit(1);
|
|
89
|
+
});
|
|
90
|
+
export {};
|
|
91
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AACnC,MAAM,OAAO,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;AAExB,KAAK,UAAU,IAAI;IACjB,IAAI,OAAO,KAAK,OAAO,EAAE,CAAC;QACxB,MAAM,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QAErB,IAAI,IAAI,KAAK,SAAS,IAAI,IAAI,KAAK,UAAU,EAAE,CAAC;YAC9C,MAAM,EAAE,eAAe,EAAE,GAAG,MAAM,MAAM,CAAC,WAAW,CAAC,CAAC;YACtD,MAAM,MAAM,GAAG,MAAM,eAAe,EAAE,CAAC;YAEvC,IAAI,MAAM,CAAC,aAAa,EAAE,CAAC;gBACzB,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC;gBAC/B,OAAO,CAAC,GAAG,CAAC,kBAAkB,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC;gBAClD,OAAO,CAAC,GAAG,CACT,iBAAiB,MAAM,CAAC,SAAS,IAAI,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC,IAAI,WAAW,EAAE,CACxF,CAAC;YACJ,CAAC;iBAAM,CAAC;gBACN,OAAO,CAAC,GAAG,CAAC,qBAAqB,CAAC,CAAC;gBACnC,OAAO,CAAC,GAAG,CAAC,8DAA8D,CAAC,CAAC;YAC9E,CAAC;YACD,OAAO;QACT,CAAC;QAED,IAAI,IAAI,KAAK,UAAU,EAAE,CAAC;YACxB,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;YACpB,IAAI,CAAC,GAAG,EAAE,CAAC;gBACT,OAAO,CAAC,KAAK,CAAC,iEAAiE,CAAC,CAAC;gBACjF,OAAO,CAAC,KAAK,CACX,kJAAkJ,CACnJ,CAAC;gBACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAClB,CAAC;YACD,MAAM,EAAE,cAAc,EAAE,GAAG,MAAM,MAAM,CAAC,WAAW,CAAC,CAAC;YACrD,MAAM,cAAc,CAAC,GAAG,CAAC,CAAC;YAC1B,OAAO;QACT,CAAC;QAED,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,MAAM,CAAC,WAAW,CAAC,CAAC;QAC/C,MAAM,QAAQ,EAAE,CAAC;QACjB,OAAO;IACT,CAAC;IAED,IAAI,OAAO,KAAK,QAAQ,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;QAC7C,SAAS,EAAE,CAAC;QACZ,OAAO;IACT,CAAC;IAED,IAAI,OAAO,KAAK,WAAW,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;QAChD,OAAO,CAAC,GAAG,CAAC,6BAA6B,CAAC,CAAC;QAC3C,OAAO;IACT,CAAC;IAED,4BAA4B;IAC5B,MAAM,EAAE,WAAW,EAAE,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC,CAAC;IACpD,MAAM,WAAW,EAAE,CAAC;AACtB,CAAC;AAED,SAAS,SAAS;IAChB,OAAO,CAAC,GAAG,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmCb,CAAC,CAAC;AACH,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;IACrB,OAAO,CAAC,KAAK,CAAC,cAAc,EAAE,KAAK,CAAC,CAAC;IACrC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAgOA,wBAAsB,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC,CAIjD"}
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { checkAuthStatus } from "./auth.js";
|
|
5
|
+
import { listArticles, getArticle, searchArticles, } from "./client.js";
|
|
6
|
+
import { htmlToMarkdown } from "./html-to-md.js";
|
|
7
|
+
function formatArticleList(articles) {
|
|
8
|
+
if (articles.length === 0)
|
|
9
|
+
return "No articles found.";
|
|
10
|
+
return articles
|
|
11
|
+
.map((a, i) => {
|
|
12
|
+
const paid = a.audience === "only_paid" ? " [PAID]" : "";
|
|
13
|
+
const section = a.section?.name ? ` (${a.section.name})` : "";
|
|
14
|
+
const date = a.postDate
|
|
15
|
+
? new Date(a.postDate).toLocaleDateString("en-US", {
|
|
16
|
+
year: "numeric",
|
|
17
|
+
month: "short",
|
|
18
|
+
day: "numeric",
|
|
19
|
+
})
|
|
20
|
+
: "";
|
|
21
|
+
const stats = `${a.likes} likes, ${a.comments} comments`;
|
|
22
|
+
return `${i + 1}. **${a.title}**${paid}${section}\n ${date} | ${stats}\n Slug: ${a.slug}\n URL: ${a.canonicalUrl}`;
|
|
23
|
+
})
|
|
24
|
+
.join("\n\n");
|
|
25
|
+
}
|
|
26
|
+
const server = new McpServer({
|
|
27
|
+
name: "substack-article-mcp",
|
|
28
|
+
version: "0.1.0",
|
|
29
|
+
});
|
|
30
|
+
// ─── Tool: substack_auth_status ──────────────────────────────────
|
|
31
|
+
server.tool("substack_auth_status", "Check if the Substack authentication is valid and show cookie age", {}, async () => {
|
|
32
|
+
const status = await checkAuthStatus();
|
|
33
|
+
if (!status.authenticated) {
|
|
34
|
+
return {
|
|
35
|
+
content: [
|
|
36
|
+
{
|
|
37
|
+
type: "text",
|
|
38
|
+
text: "Not authenticated. Run `substack-article-mcp login` in your terminal to connect your Substack account.",
|
|
39
|
+
},
|
|
40
|
+
],
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
content: [
|
|
45
|
+
{
|
|
46
|
+
type: "text",
|
|
47
|
+
text: `Authenticated ✓\nCookie age: ${status.cookieAge}\nSubdomain: ${status.subdomain || process.env["SUBSTACK_SUBDOMAIN"] || "(not set)"}`,
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
};
|
|
51
|
+
});
|
|
52
|
+
// ─── Tool: list_articles ────────────────────────────────────────
|
|
53
|
+
server.tool("list_articles", "List published Substack articles with metadata (title, date, slug, engagement stats, paid/free status). Returns newest first by default.", {
|
|
54
|
+
limit: z
|
|
55
|
+
.number()
|
|
56
|
+
.int()
|
|
57
|
+
.min(1)
|
|
58
|
+
.max(50)
|
|
59
|
+
.optional()
|
|
60
|
+
.describe("Number of articles to return (default 12, max 50)"),
|
|
61
|
+
offset: z
|
|
62
|
+
.number()
|
|
63
|
+
.int()
|
|
64
|
+
.min(0)
|
|
65
|
+
.optional()
|
|
66
|
+
.describe("Pagination offset (default 0)"),
|
|
67
|
+
sort: z
|
|
68
|
+
.enum(["new", "top"])
|
|
69
|
+
.optional()
|
|
70
|
+
.describe("Sort order: 'new' (default) or 'top' by engagement"),
|
|
71
|
+
}, async ({ limit, offset, sort }) => {
|
|
72
|
+
try {
|
|
73
|
+
const articles = await listArticles({
|
|
74
|
+
limit: limit ?? 12,
|
|
75
|
+
offset: offset ?? 0,
|
|
76
|
+
sort: sort ?? "new",
|
|
77
|
+
});
|
|
78
|
+
const total = articles.length;
|
|
79
|
+
const text = `Found ${total} article${total === 1 ? "" : "s"}:\n\n${formatArticleList(articles)}`;
|
|
80
|
+
return { content: [{ type: "text", text }] };
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
return {
|
|
84
|
+
content: [
|
|
85
|
+
{
|
|
86
|
+
type: "text",
|
|
87
|
+
text: `Error: ${error instanceof Error ? error.message : String(error)}`,
|
|
88
|
+
},
|
|
89
|
+
],
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
// ─── Tool: get_article ──────────────────────────────────────────
|
|
94
|
+
server.tool("get_article", "Get full content of a Substack article as markdown. Requires the article slug (the URL path segment after /p/). Authenticated access includes premium/paywalled content.", {
|
|
95
|
+
slug: z
|
|
96
|
+
.string()
|
|
97
|
+
.describe("Article slug from the URL (the part after /p/ in the article URL, e.g. 'my-article-title')"),
|
|
98
|
+
}, async ({ slug }) => {
|
|
99
|
+
try {
|
|
100
|
+
const article = await getArticle(slug);
|
|
101
|
+
const paid = article.audience === "only_paid" ? " [PAID]" : "";
|
|
102
|
+
const date = article.postDate
|
|
103
|
+
? new Date(article.postDate).toLocaleDateString("en-US", {
|
|
104
|
+
year: "numeric",
|
|
105
|
+
month: "long",
|
|
106
|
+
day: "numeric",
|
|
107
|
+
})
|
|
108
|
+
: "";
|
|
109
|
+
let markdown = "";
|
|
110
|
+
markdown += `# ${article.title}${paid}\n\n`;
|
|
111
|
+
if (article.subtitle)
|
|
112
|
+
markdown += `*${article.subtitle}*\n\n`;
|
|
113
|
+
if (date)
|
|
114
|
+
markdown += `Published: ${date}\n`;
|
|
115
|
+
markdown += `URL: ${article.canonicalUrl}\n`;
|
|
116
|
+
markdown += `Engagement: ${article.likes} likes, ${article.comments} comments, ${article.restacks} restacks\n`;
|
|
117
|
+
if (article.wordCount)
|
|
118
|
+
markdown += `Word count: ${article.wordCount}\n`;
|
|
119
|
+
markdown += "\n---\n\n";
|
|
120
|
+
if (article.bodyHtml) {
|
|
121
|
+
markdown += htmlToMarkdown(article.bodyHtml);
|
|
122
|
+
}
|
|
123
|
+
else if (article.truncatedBodyText) {
|
|
124
|
+
markdown +=
|
|
125
|
+
article.truncatedBodyText +
|
|
126
|
+
"\n\n[Content truncated — authentication may have failed for this premium article]";
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
markdown += "(No article content available)";
|
|
130
|
+
}
|
|
131
|
+
return { content: [{ type: "text", text: markdown }] };
|
|
132
|
+
}
|
|
133
|
+
catch (error) {
|
|
134
|
+
return {
|
|
135
|
+
content: [
|
|
136
|
+
{
|
|
137
|
+
type: "text",
|
|
138
|
+
text: `Error: ${error instanceof Error ? error.message : String(error)}`,
|
|
139
|
+
},
|
|
140
|
+
],
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
// ─── Tool: search_articles ──────────────────────────────────────
|
|
145
|
+
server.tool("search_articles", "Search your published Substack articles by keyword. Returns matching articles with metadata.", {
|
|
146
|
+
query: z.string().describe("Search query to find articles"),
|
|
147
|
+
limit: z
|
|
148
|
+
.number()
|
|
149
|
+
.int()
|
|
150
|
+
.min(1)
|
|
151
|
+
.max(50)
|
|
152
|
+
.optional()
|
|
153
|
+
.describe("Max results to return (default 12)"),
|
|
154
|
+
}, async ({ query, limit }) => {
|
|
155
|
+
try {
|
|
156
|
+
const articles = await searchArticles(query, limit ?? 12);
|
|
157
|
+
const total = articles.length;
|
|
158
|
+
if (total === 0) {
|
|
159
|
+
return {
|
|
160
|
+
content: [
|
|
161
|
+
{
|
|
162
|
+
type: "text",
|
|
163
|
+
text: `No articles found matching "${query}".`,
|
|
164
|
+
},
|
|
165
|
+
],
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
const text = `Found ${total} article${total === 1 ? "" : "s"} matching "${query}":\n\n${formatArticleList(articles)}`;
|
|
169
|
+
return { content: [{ type: "text", text }] };
|
|
170
|
+
}
|
|
171
|
+
catch (error) {
|
|
172
|
+
return {
|
|
173
|
+
content: [
|
|
174
|
+
{
|
|
175
|
+
type: "text",
|
|
176
|
+
text: `Error: ${error instanceof Error ? error.message : String(error)}`,
|
|
177
|
+
},
|
|
178
|
+
],
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
// ─── Start Server ────────────────────────────────────────────────
|
|
183
|
+
export async function startServer() {
|
|
184
|
+
const transport = new StdioServerTransport();
|
|
185
|
+
await server.connect(transport);
|
|
186
|
+
console.error("Substack MCP server running on stdio");
|
|
187
|
+
}
|
|
188
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "substack-article-mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "MCP server for Substack — access your articles, notes, and engagement data from Claude, Cursor, or any MCP client. Authenticated access to premium/paywalled content.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"substack-article-mcp": "dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"main": "./dist/index.js",
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"README.md"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsc",
|
|
16
|
+
"dev": "tsx src/index.ts",
|
|
17
|
+
"prepublishOnly": "npm run build"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"mcp",
|
|
21
|
+
"model-context-protocol",
|
|
22
|
+
"substack",
|
|
23
|
+
"newsletter",
|
|
24
|
+
"articles",
|
|
25
|
+
"notes",
|
|
26
|
+
"claude",
|
|
27
|
+
"cursor",
|
|
28
|
+
"claude-desktop",
|
|
29
|
+
"claude-code",
|
|
30
|
+
"content",
|
|
31
|
+
"authenticated",
|
|
32
|
+
"premium"
|
|
33
|
+
],
|
|
34
|
+
"author": "Jenny Ouyang",
|
|
35
|
+
"license": "MIT",
|
|
36
|
+
"repository": {
|
|
37
|
+
"type": "git",
|
|
38
|
+
"url": "git+https://github.com/jenny-ouyang/substack-article-mcp.git"
|
|
39
|
+
},
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
42
|
+
"cheerio": "^1.0.0",
|
|
43
|
+
"puppeteer-core": "^24.0.0",
|
|
44
|
+
"zod": "^3.23.0"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"@types/node": "^20.0.0",
|
|
48
|
+
"tsx": "^4.0.0",
|
|
49
|
+
"typescript": "^5.5.0"
|
|
50
|
+
},
|
|
51
|
+
"engines": {
|
|
52
|
+
"node": ">=18"
|
|
53
|
+
}
|
|
54
|
+
}
|