mdingest
Ingests Dev.to articles using the Forem API and converts them to clean Markdown with metadata such as title, author, date, reading time, and tags.
Ingests Medium articles via the Freedium mirror and converts them to clean Markdown with metadata such as title, author, date, reading time, and tags.
Ingests free Substack posts, including reader URLs, and converts them to clean Markdown with metadata such as title, author, date, reading time, and tags.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@mdingestingest this Medium article into clean markdown: https://medium.com/@user/article-id"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
mdingest
API that ingests blog/article/newsletter pages to clean Markdown for LLM consumption. Supports Medium (via Freedium), Dev.to (Forem API), and Substack (free posts via public API + HTML→Markdown). Four entry points: HTTP API, CLI, MCP server (stdio), and MCP server (HTTP) — all sharing the same ingestion logic.
Quick start
bun install
bun run dev # HTTP API + web UI on port 3000Related MCP server: cleanfetch
Usage
HTTP API
Deployed at https://mdingest.knightker.workers.dev:
curl "https://mdingest.knightker.workers.dev/v1/medium?url=https://medium.com/@user/article-id"
curl "https://mdingest.knightker.workers.dev/v1/devto?url=https://dev.to/user/article-slug"
curl "https://mdingest.knightker.workers.dev/v1/substack?url=https://pub.substack.com/p/article-slug"Substack reader URLs are also supported (resolved via 302 redirect):
curl "https://mdingest.knightker.workers.dev/v1/substack?url=https://substack.com/home/post/p-212696442"Default response is text/markdown with YAML frontmatter + clean body:
---
title: "Article Title"
author: "Author Name"
date: "2026-01-15"
reading_time: "5 min read"
free: true
source_url: "https://medium.com/@user/article-id"
provider: "medium"
tags:
- "Distributed Systems"
---
# Article content in clean Markdown...JSON response (metadata + markdown)
Add &format=json to get { metadata, markdown } as application/json instead of raw markdown:
curl "https://mdingest.knightker.workers.dev/v1/medium?url=https://medium.com/@user/article-id&format=json"{
"metadata": {
"title": "Article Title",
"author": "Author Name",
"date": "2026-01-15",
"reading_time": "5 min read",
"free": true,
"source_url": "https://medium.com/@user/article-id",
"provider": "medium",
"tags": ["Distributed Systems"]
},
"markdown": "---\ntitle: \"Article Title\"...\n"
}Local development
bun run dev
curl "http://localhost:3000/v1/medium?url=https://medium.com/@user/article-id"The web UI is at http://localhost:3000/ — paste a URL, auto-detects the provider,
and shows the output with md/json tabs and line numbers.
CLI
# Ingest any article URL → markdown to stdout (auto-detects provider)
bun run src/cli.ts https://dev.to/user/post > article.md
# JSON output (metadata + markdown)
bun run src/cli.ts https://dev.to/user/post --json
# Override provider auto-detection
bun run src/cli.ts https://example.com/post --provider medium
# List supported providers
bun run src/cli.ts providersPipe-friendly: mdingest https://dev.to/user/post > article.md gives you clean Markdown with no log noise.
MCP server
For AI tools (Claude, Cursor, etc.) — two ways to connect:
Remote (zero setup): Register the deployed endpoint directly:
{
"mcpServers": {
"mdingest": {
"url": "https://mdingest.knightker.workers.dev/v1/mcp"
}
}
}Local (stdio): Run the CLI as a local process:
{
"mcpServers": {
"mdingest": {
"command": "bun",
"args": ["run", "src/cli.ts", "mcp"]
}
}
}Both expose the same two tools:
Tool | Description |
| Ingest a URL into clean Markdown. Auto-detects provider. Returns markdown text by default; |
| List supported providers with source, example URL, and accepted domains. |
Configuration
Env var | Default | Purpose |
|
| Server port |
|
| Freedium mirror base URL |
|
| Cache entry TTL (seconds) |
|
| Max cache entries |
|
| Fetch timeout (ms) |
| Chrome 120 UA | User-Agent for upstream requests |
Architecture
src/
core/
config/ Zod-validated config (boot-time, frozen object)
cache/ In-memory LRU cache
integrations/
freedium/ HTTP client for Freedium mirror (download + data endpoints)
common/
types/ Shared types: ArticleMetadata schema, Provider interface
pipes/ ZodValidationPipe (validates controller input)
filters/ AllExceptionsFilter (shapes errors to { code, message, details?, traceId })
guards/ RateLimitGuard (30 req/min per IP, global via APP_GUARD)
llm-visibility.ts Fastify preHandler: Accept: text/markdown negotiation, Link headers, Vary, 406
errors/ shapeError() — shared error shaping (filter, CLI, MCP)
modules/
medium/ Medium feature: controller, service, DTOs, errors
devto/ Dev.to feature: controller, service, DTOs, errors
substack/ Substack feature: controller, service, DTOs, errors
mcp/ MCP server: server.ts (createMcpServer/startMcpServer/handleHttpRequest), tools.ts (ingest_article, list_providers)
app.module.ts Root module
main.ts Bootstrap (NestJS + Fastify + Bun, URI versioning, global filter)
ingest.ts Shared router: service registry + ingest(url) — used by CLI + MCP
cli.ts CLI entry: citty binary, `mdingest <url>` + `mdingest mcp` subcommand
mcp.controller.ts MCP HTTP endpoint at /v1/mcp (Streamable HTTP transport)
worker.ts Cloudflare Worker — routes requests to the Docker container
shared/
providers.ts Single source of truth: MEDIUM_DOMAINS, PROVIDERS, detectProvider(), error codes
web/ Astro static site (React islands)
src/
islands/ Ingestor.tsx — URL input, provider selector, output display
pages/ index.astro (landing), ingest.astro (ingest UI), docs.astro (API docs)
layouts/ Base.astro — shared header (sticky, backdrop-blur), footer, meta tags
styles/ global.css — design tokens, base styles, shared components
public/
icons/ Provider SVG logos (medium, devto, substack)
robots.txt Allow all crawlers + Content-Signal directive + sitemap reference
llms.txt Curated markdown index for AI-mediated conversations
llms-full.txt All 3 pages concatenated for single-fetch LLM ingestion
*.md Markdown twins of each HTML page (index.md, ingest.md, docs.md)
.well-known/
ai-catalog.json AI Catalog — domain-level agent discovery
api-catalog RFC 9727 API Catalog (linkset+json)
mcp/server-card.json MCP Server Card — pre-connection MCP client metadataFour entry points share the same ingestion logic via src/ingest.ts:
Entry point | File | How |
HTTP API |
| NestJS + Fastify, DI wires services, controller delegates to service |
CLI |
| citty binary: |
MCP server (stdio) |
| stdio JSON-RPC: |
MCP server (HTTP) |
| Streamable HTTP at |
Each provider implements a Provider interface (matches, convert). Adding a provider = new folder under modules/, no changes to core or common. Service classes work with direct new outside NestJS — CLI and MCP instantiate them via src/ingest.ts without booting NestJS.
URL detection is centralized in shared/providers.ts — detectProvider(url) is the single source of truth used by both the frontend (auto-detect) and all 3 backend DTOs (isValid*Url delegate to it).
Errors return { code, message, details?, traceId } with namespaced codes (MEDIUM.INVALID_URL, SUBSTACK.PAID_POST, VALIDATION.FAILED, RATE_LIMITED, etc.). Full contract in AGENTS.md. All endpoints are rate-limited at 30 req/min per IP.
Tech stack
Backend
Tool | Role |
Bun | Runtime |
NestJS + Fastify | Framework (modules, DI, controllers) |
Zod | Runtime validation (config, params, metadata) |
lru-cache | In-memory cache with TTL |
turndown | HTML→Markdown (Substack provider) |
oxlint | Linting |
@cloudflare/containers | Cloudflare Containers deployment |
citty | CLI arg parsing + |
@modelcontextprotocol/sdk | MCP server over stdio for AI tools |
Frontend
Tool | Role |
Astro | Static site generator with React island support |
@astrojs/sitemap | Sitemap generation (sitemap-index.xml + sitemap-0.xml) |
React | Interactive islands (Ingestor component) |
lucide-react + @lucide/astro | Icons |
Geist + Geist Mono | Self-hosted fonts |
UI quality
The verify script runs impeccable to scan the built frontend for UI anti-patterns
(WCAG AA contrast, line-height). Current state: 0 anti-patterns.
Roadmap
Feature | Status | How |
HTTP API | Deployed — runtime-verified |
|
Medium provider | Runtime-verified |
|
Dev.to provider | Runtime-verified |
|
Substack provider | Runtime-verified |
|
Web UI | Runtime-verified | Astro + React islands — landing page, ingest page with md/json tabs + line numbers, API docs |
CLI | Runtime-verified |
|
MCP server (stdio) | Runtime-verified |
|
MCP server (HTTP) | Runtime-verified |
|
Rate limiting | Runtime-verified | Global |
LLM visibility | Runtime-verified |
|
Agent discovery | Runtime-verified |
|
Development
bun run verify # typecheck + lint + impeccable (UI anti-pattern scan)
bun run dev # start backend dev server with hot reload (port 3000)
bun run dev:web # start Astro dev server (frontend only, port 4321)
bun run build:web # build Astro frontend to web/dist/
bun run test # run unit tests (vitest)
bun run cli # run CLI (bun run src/cli.ts <url>)
bun run mcp # start MCP server (bun run src/cli.ts mcp)Attribution
Medium articles fetched via Freedium.
This server cannot be deployed
Maintenance
Related MCP Connectors
Clean Markdown and AI-readability scoring for any URL. Built for AI agents.
Web scraping for AI agents. Converts URLs to clean, LLM-ready Markdown with anti-bot bypass.
Fetch any URL as clean Markdown or metadata, and buy digital goods via x402 — for AI agents.
Turn any URL into clean Markdown and structured data. Scrape, crawl, search and extract.
Related MCP Servers
- AlicenseAqualityDmaintenanceConverts URLs and raw HTML to clean Markdown, enabling AI assistants to read web pages for summarization, analysis, or ingestion.24 npm1MIT
- AlicenseAqualityCmaintenanceEnables AI agents to read web pages reliably, returning clean markdown content, hyperlinks, and metadata without navigation or ad noise.36 npmMIT
- AlicenseAqualityDmaintenanceEnables AI agents to fetch any web page as clean markdown or screenshot it, turning URLs into LLM-ready context.21 npmMIT
- AlicenseAqualityBmaintenanceEnables AI agents to read clean Markdown from any URL and assess source quality with AI-readability scores.649 npm1MIT