Skip to main content
Glama

mdingest

CI License: MIT Ask DeepWiki Last Commit

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 3000

Related MCP server: urltomarkdown-mcp

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 providers

Pipe-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_article

Ingest a URL into clean Markdown. Auto-detects provider. Returns markdown text by default; json: true for {metadata, markdown}.

list_providers

List supported providers with source, example URL, and accepted domains.

Configuration

Env var

Default

Purpose

PORT

3000

Server port

FREEDIUM_BASE_URL

https://freedium-mirror.cfd

Freedium mirror base URL

CACHE_TTL_SECONDS

300

Cache entry TTL (seconds)

CACHE_MAX_ENTRIES

200

Max cache entries

FETCH_TIMEOUT_MS

20000

Fetch timeout (ms)

USER_AGENT

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 metadata

Four entry points share the same ingestion logic via src/ingest.ts:

Entry point

File

How

HTTP API

src/main.ts

NestJS + Fastify, DI wires services, controller delegates to service

CLI

src/cli.ts

citty binary: mdingest <url> → markdown to stdout, --json for structured, --provider override

MCP server (stdio)

src/cli.ts mcp

stdio JSON-RPC: ingest_article + list_providers tools

MCP server (HTTP)

src/mcp.controller.ts

Streamable HTTP at /v1/mcp — same tools, remote, zero local setup

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.tsdetectProvider(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 + --help generation

@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

https://mdingest.knightker.workers.dev/v1/medium?url=... — Cloudflare Containers

Medium provider

Runtime-verified

GET /v1/medium?url=... — Freedium dual-source, 48 unit tests

Dev.to provider

Runtime-verified

GET /v1/devto?url=... — Forem API, liquid tag transform, 38 unit tests

Substack provider

Runtime-verified

GET /v1/substack?url=... — public API + turndown HTML→Markdown, free posts only, home URL redirect resolution, 32 unit tests

Web UI

Runtime-verified

Astro + React islands — landing page, ingest page with md/json tabs + line numbers, API docs

CLI

Runtime-verified

bun run src/cli.ts <url> → markdown to stdout; --json for structured; --provider override; mdingest providers lists sources

MCP server (stdio)

Runtime-verified

bun run src/cli.ts mcp → stdio JSON-RPC; ingest_article + list_providers tools; all 3 providers verified

MCP server (HTTP)

Runtime-verified

POST /v1/mcp → Streamable HTTP transport; same tools; initialize handshake + session ID; all 3 providers verified

Rate limiting

Runtime-verified

Global RateLimitGuard via APP_GUARD — 30 req/min per IP; 31st request returns 429 { code: "RATE_LIMITED", details: { retryAfter } }

LLM visibility

Runtime-verified

robots.txt + llms.txt + llms-full.txt + .md routes for all 3 pages + Accept: text/markdown content negotiation + Link headers + Vary: Accept + sitemap + FAQ on landing page. 6 Evil Martians techniques implemented.

Agent discovery

Runtime-verified

.well-known/mcp/server-card.json (MCP Server Card) + .well-known/ai-catalog.json (AI Catalog) + .well-known/api-catalog (RFC 9727 linkset). isitagentready.com: Agent-Readable L3.

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.

A
license - permissive license
Not graded
quality - not tested
B
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Servers

View all related MCP servers

Related MCP Connectors

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/LOsioChico/mdingest'

If you have feedback or need assistance with the MCP directory API, please join our Discord server