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 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
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"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"}
@@ -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,2 @@
1
+ export declare function htmlToMarkdown(html: string): string;
2
+ //# sourceMappingURL=html-to-md.d.ts.map
@@ -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![${alt}](${src})\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![${alt}](${src})\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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"}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=index.d.ts.map
@@ -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"}
@@ -0,0 +1,2 @@
1
+ export declare function startServer(): Promise<void>;
2
+ //# sourceMappingURL=server.d.ts.map
@@ -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":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAC5C,OAAO,EACL,YAAY,EACZ,UAAU,EACV,cAAc,GAEf,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEjD,SAAS,iBAAiB,CAAC,QAA2B;IACpD,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,oBAAoB,CAAC;IAEvD,OAAO,QAAQ;SACZ,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;QACZ,MAAM,IAAI,GAAG,CAAC,CAAC,QAAQ,KAAK,WAAW,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC;QACzD,MAAM,OAAO,GAAG,CAAC,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;QAC9D,MAAM,IAAI,GAAG,CAAC,CAAC,QAAQ;YACrB,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,kBAAkB,CAAC,OAAO,EAAE;gBAC/C,IAAI,EAAE,SAAS;gBACf,KAAK,EAAE,OAAO;gBACd,GAAG,EAAE,SAAS;aACf,CAAC;YACJ,CAAC,CAAC,EAAE,CAAC;QACP,MAAM,KAAK,GAAG,GAAG,CAAC,CAAC,KAAK,WAAW,CAAC,CAAC,QAAQ,WAAW,CAAC;QAEzD,OAAO,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,KAAK,KAAK,IAAI,GAAG,OAAO,QAAQ,IAAI,MAAM,KAAK,cAAc,CAAC,CAAC,IAAI,aAAa,CAAC,CAAC,YAAY,EAAE,CAAC;IAC3H,CAAC,CAAC;SACD,IAAI,CAAC,MAAM,CAAC,CAAC;AAClB,CAAC;AAED,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;IAC3B,IAAI,EAAE,sBAAsB;IAC5B,OAAO,EAAE,OAAO;CACjB,CAAC,CAAC;AAEH,oEAAoE;AAEpE,MAAM,CAAC,IAAI,CACT,sBAAsB,EACtB,mEAAmE,EACnE,EAAE,EACF,KAAK,IAAI,EAAE;IACT,MAAM,MAAM,GAAG,MAAM,eAAe,EAAE,CAAC;IAEvC,IAAI,CAAC,MAAM,CAAC,aAAa,EAAE,CAAC;QAC1B,OAAO;YACL,OAAO,EAAE;gBACP;oBACE,IAAI,EAAE,MAAe;oBACrB,IAAI,EAAE,wGAAwG;iBAC/G;aACF;SACF,CAAC;IACJ,CAAC;IAED,OAAO;QACL,OAAO,EAAE;YACP;gBACE,IAAI,EAAE,MAAe;gBACrB,IAAI,EAAE,gCAAgC,MAAM,CAAC,SAAS,gBAAgB,MAAM,CAAC,SAAS,IAAI,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC,IAAI,WAAW,EAAE;aAC7I;SACF;KACF,CAAC;AACJ,CAAC,CACF,CAAC;AAEF,mEAAmE;AAEnE,MAAM,CAAC,IAAI,CACT,eAAe,EACf,0IAA0I,EAC1I;IACE,KAAK,EAAE,CAAC;SACL,MAAM,EAAE;SACR,GAAG,EAAE;SACL,GAAG,CAAC,CAAC,CAAC;SACN,GAAG,CAAC,EAAE,CAAC;SACP,QAAQ,EAAE;SACV,QAAQ,CAAC,mDAAmD,CAAC;IAChE,MAAM,EAAE,CAAC;SACN,MAAM,EAAE;SACR,GAAG,EAAE;SACL,GAAG,CAAC,CAAC,CAAC;SACN,QAAQ,EAAE;SACV,QAAQ,CAAC,+BAA+B,CAAC;IAC5C,IAAI,EAAE,CAAC;SACJ,IAAI,CAAC,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;SACpB,QAAQ,EAAE;SACV,QAAQ,CAAC,oDAAoD,CAAC;CAClE,EACD,KAAK,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,EAAE;IAChC,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,YAAY,CAAC;YAClC,KAAK,EAAE,KAAK,IAAI,EAAE;YAClB,MAAM,EAAE,MAAM,IAAI,CAAC;YACnB,IAAI,EAAE,IAAI,IAAI,KAAK;SACpB,CAAC,CAAC;QAEH,MAAM,KAAK,GAAG,QAAQ,CAAC,MAAM,CAAC;QAC9B,MAAM,IAAI,GAAG,SAAS,KAAK,WAAW,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,QAAQ,iBAAiB,CAAC,QAAQ,CAAC,EAAE,CAAC;QAElG,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;IACxD,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO;YACL,OAAO,EAAE;gBACP;oBACE,IAAI,EAAE,MAAe;oBACrB,IAAI,EAAE,UAAU,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE;iBACzE;aACF;SACF,CAAC;IACJ,CAAC;AACH,CAAC,CACF,CAAC;AAEF,mEAAmE;AAEnE,MAAM,CAAC,IAAI,CACT,aAAa,EACb,0KAA0K,EAC1K;IACE,IAAI,EAAE,CAAC;SACJ,MAAM,EAAE;SACR,QAAQ,CACP,4FAA4F,CAC7F;CACJ,EACD,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE;IACjB,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,UAAU,CAAC,IAAI,CAAC,CAAC;QAEvC,MAAM,IAAI,GAAG,OAAO,CAAC,QAAQ,KAAK,WAAW,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC;QAC/D,MAAM,IAAI,GAAG,OAAO,CAAC,QAAQ;YAC3B,CAAC,CAAC,IAAI,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,kBAAkB,CAAC,OAAO,EAAE;gBACrD,IAAI,EAAE,SAAS;gBACf,KAAK,EAAE,MAAM;gBACb,GAAG,EAAE,SAAS;aACf,CAAC;YACJ,CAAC,CAAC,EAAE,CAAC;QAEP,IAAI,QAAQ,GAAG,EAAE,CAAC;QAClB,QAAQ,IAAI,KAAK,OAAO,CAAC,KAAK,GAAG,IAAI,MAAM,CAAC;QAC5C,IAAI,OAAO,CAAC,QAAQ;YAAE,QAAQ,IAAI,IAAI,OAAO,CAAC,QAAQ,OAAO,CAAC;QAC9D,IAAI,IAAI;YAAE,QAAQ,IAAI,cAAc,IAAI,IAAI,CAAC;QAC7C,QAAQ,IAAI,QAAQ,OAAO,CAAC,YAAY,IAAI,CAAC;QAC7C,QAAQ,IAAI,eAAe,OAAO,CAAC,KAAK,WAAW,OAAO,CAAC,QAAQ,cAAc,OAAO,CAAC,QAAQ,aAAa,CAAC;QAC/G,IAAI,OAAO,CAAC,SAAS;YAAE,QAAQ,IAAI,eAAe,OAAO,CAAC,SAAS,IAAI,CAAC;QACxE,QAAQ,IAAI,WAAW,CAAC;QAExB,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC;YACrB,QAAQ,IAAI,cAAc,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QAC/C,CAAC;aAAM,IAAI,OAAO,CAAC,iBAAiB,EAAE,CAAC;YACrC,QAAQ;gBACN,OAAO,CAAC,iBAAiB;oBACzB,mFAAmF,CAAC;QACxF,CAAC;aAAM,CAAC;YACN,QAAQ,IAAI,gCAAgC,CAAC;QAC/C,CAAC;QAED,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC;IAClE,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO;YACL,OAAO,EAAE;gBACP;oBACE,IAAI,EAAE,MAAe;oBACrB,IAAI,EAAE,UAAU,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE;iBACzE;aACF;SACF,CAAC;IACJ,CAAC;AACH,CAAC,CACF,CAAC;AAEF,mEAAmE;AAEnE,MAAM,CAAC,IAAI,CACT,iBAAiB,EACjB,8FAA8F,EAC9F;IACE,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,+BAA+B,CAAC;IAC3D,KAAK,EAAE,CAAC;SACL,MAAM,EAAE;SACR,GAAG,EAAE;SACL,GAAG,CAAC,CAAC,CAAC;SACN,GAAG,CAAC,EAAE,CAAC;SACP,QAAQ,EAAE;SACV,QAAQ,CAAC,oCAAoC,CAAC;CAClD,EACD,KAAK,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,EAAE;IACzB,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,cAAc,CAAC,KAAK,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC;QAC1D,MAAM,KAAK,GAAG,QAAQ,CAAC,MAAM,CAAC;QAE9B,IAAI,KAAK,KAAK,CAAC,EAAE,CAAC;YAChB,OAAO;gBACL,OAAO,EAAE;oBACP;wBACE,IAAI,EAAE,MAAe;wBACrB,IAAI,EAAE,+BAA+B,KAAK,IAAI;qBAC/C;iBACF;aACF,CAAC;QACJ,CAAC;QAED,MAAM,IAAI,GAAG,SAAS,KAAK,WAAW,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,cAAc,KAAK,SAAS,iBAAiB,CAAC,QAAQ,CAAC,EAAE,CAAC;QACtH,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;IACxD,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO;YACL,OAAO,EAAE;gBACP;oBACE,IAAI,EAAE,MAAe;oBACrB,IAAI,EAAE,UAAU,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE;iBACzE;aACF;SACF,CAAC;IACJ,CAAC;AACH,CAAC,CACF,CAAC;AAEF,oEAAoE;AAEpE,MAAM,CAAC,KAAK,UAAU,WAAW;IAC/B,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAChC,OAAO,CAAC,KAAK,CAAC,sCAAsC,CAAC,CAAC;AACxD,CAAC"}
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
+ }