Back to MCP Servers

Mdma

MCP server for MDMA (interactive Markdown applications with mounted components). Exposes the MDMA spec, authoring prompts, package metadata, and live docs to AI assistants. Tools: `get-spec`, `get-prompt`, `build-system-prompt`, `validate-prompt`, `list-packages`, `list-docs`, `…

developer-toolsai
By MobileReality
232Updated 2 weeks agoJavaScriptMIT

Installation

npx @mobile-reality/mdma-mcp

Configuration

{
  "mcpServers": {
    "mdma": {
      "command": "npx",
      "args": ["-y", "mdma"]
    }
  }
}

How to use

  1. Run the installation command above (if needed)
  2. Open your Claude Code settings file (~/.claude/settings.json)
  3. Add the configuration to the mcpServers section
  4. Restart Claude Code to apply changes
<p align="center"> <img src="assets/logo.svg" alt="MDMA Logo" width="160" /> </p> <h1 align="center">MDMA</h1> <p align="center">Markdown Document with Mounted Applications</p> <p align="center">Interactive documents from Markdown. Built for next gen-apps</p> <p align="center"> <a href="https://mobilereality.github.io/mdma/"><b>🚀 Live Demo</b></a> &nbsp;&nbsp;·&nbsp;&nbsp; <a href="https://mobilereality.github.io/mdma/#/docs"><b>📖 Docs</b></a> &nbsp;&nbsp;·&nbsp;&nbsp; <a href="https://discord.gg/etGSuCuR7B"><b>💬 Discord</b></a> </p>

Why MDMA?

AI conversations today are plain text — the user reads a response and manually acts on it. MDMA changes that. When an LLM knows the MDMA spec, it can respond with interactive components (forms, tables, approval gates) instead of just text. The conversation becomes actionable: the user fills out a form, approves a step, or reviews structured data — all inline, with a predictable schema that your app already knows how to render and process.

No custom UI per use case. No parsing free-form text. The AI generates structured, validated components and your frontend renders them instantly.

<p align="center"> <img src="assets/mdma-3.gif" alt="MDMA Demo" width="800" /> </p>

What is MDMA?

MDMA extends Markdown with interactive components defined in fenced mdma code blocks. A regular Markdown file becomes an interactive application:

# Patient Intake

```mdma
type: form
id: intake-form
fields:
  - name: patient-name
    type: text
    label: "Full Name"
    required: true
    sensitive: true
  - name: email
    type: email
    label: "Email"
    required: true
    sensitive: true
  - name: reason
    type: textarea
    label: "Reason for Visit"
    required: true
```

```mdma
type: button
id: submit-btn
text: "Submit Intake Form"
variant: primary
onAction: submit
```

MDMA_AUTHOR prompt matrix

Each cell shows the pass rate of the model-specialized MDMA_AUTHOR prompt variant on the listed eval suite.

✅ 100% on the suite.

🟡 Scoring between 80–99% on the suite.

🔴 Scoring below 80% on the suite.

Variantone-shotone-shot with custom promptconversationspecific flow of conversation
OpenAI
gpt-5.5
gpt-5.4✅ †✅ †✅ †
gpt-5.4-mini✅ *✅ *
gpt-5.4-nano✅ *✅ *
gpt-5.2
gpt-5.1
gpt-5 [i]
gpt-5-mini [i]✅ *✅ *
gpt-5-nano [i]🟡 *🟡 *
gpt-4.1
gpt-4.1-mini✅ *✅ *
gpt-4.1-nano✅ *🟡 *
Anthropic
claude-opus-4.7
claude-opus-4.6
claude-sonnet-4.6
claude-haiku-4.5✅ *✅ *
Google
gemini-3.1-pro-preview🟡 ‡
gemini-3.1-pro-preview-customtools
gemini-3.1-flash-lite-preview✅ *✅ *
gemini-3-flash-preview✅ *✅ *
gemini-2.5-pro
gemini-2.5-flash✅ *✅ *
gemini-2.5-flash-lite✅ *✅ *
xAI
grok-4.3 [i]🟡🔴🔴🔴
grok-4.20
Zhipu (z.ai)
glm-4-plusTBDTBDTBDTBD
Moonshot
kimi-k2TBDTBDTBDTBD
Alibaba
qwen3-maxTBDTBDTBDTBD
MiniMax
minimax-m1TBDTBDTBDTBD
Other
model

Don't see your model? Add a prompt variant under packages/prompt-pack/src/prompts/mdma-author/<vendor>/ and open a PR — we'll run the eval suite and add it to this table.

gpt-5.4 intermittent duplication buggpt-5.4 passes one-shot evals reliably but shows a non-deterministic output duplication in multi-turn, custom-prompt, and flow evals (~7–15% of runs). The model generates a complete, correct response and then immediately re-emits the entire output verbatim, causing [duplicate-ids] validation errors. This is a known model-level issue unrelated to the prompt variant. See the OpenAI community thread for details. If this affects your use case, prefer gpt-5.5 or gpt-5.2.

gemini-3.1-pro-preview stochastic preamble loop — on ~7–15% of flow-eval runs, the model emits a chain-of-thought as visible Markdown prose (e.g. **Investigating Production Errors** repeated 3–5 times) instead of opening a ```mdma block, producing either [yaml-correctness: outside fenced block] or [duplicate-ids] errors. Per Google's official Gemini 3 prompting guide, this is a model-level behavior driven by temperature/sampling — prompt-level fixes shift which test loops rather than eliminating the loops. If deterministic flow output matters, prefer gemini-2.5-pro for production multi-step flows.

* Smaller / lower-tier models from any lab (OpenAI mini · nano, Anthropic Haiku, Google Gemini Flash, etc.) pass our eval suites, which exercise short, structured test cases. In longer real-world conversations they tend to hallucinate, forget earlier turns, or drift from the spec. For production use that involves multi-turn dialogue or stateful flows, prefer the flagship-tier model from the same family.

[i] Noticeably slow response times — single-turn responses commonly take tens of seconds and full eval runs measure in minutes.

MDMA_FIXER prompt matrix

Each cell shows the pass rate of the model-specialized MDMA_FIXER prompt variant on the single-block fixer eval (15 tests covering structural fixes, bindings, PII, forms, tables/charts, approvals). The fixer is what powers automatic repair of LLM output that fails validate() — every supported model lands at ✅ via model-tailored inline guards (no-leading-separator, preserve-input-structure, table-key-direction, replace-all-placeholders, fix-all-listed-errors, etc.).

✅ 100% on the single-block fixer eval (15/15).

Variantsingle-block fixernotes
OpenAI
gpt-5.5
gpt-5.4
gpt-5.4-mini
gpt-5.4-nano
gpt-5.2
gpt-5.1
gpt-5
gpt-5-mini
gpt-5-nano
gpt-4.1
gpt-4.1-mini
gpt-4.1-nano
Anthropic
claude-opus-4.7
claude-opus-4.6
claude-sonnetcatch-all variant — matches claude-sonnet-4-5, claude-sonnet-4-6, etc.
claude-haiku
Google
gemini-3.1-pro-preview✅ ‡requires OpenRouter reasoning.exclude: true (already wired in evals/promptfooconfig.fixer.js)
gemini-3.1-pro-preview-customtools✅ ‡same reasoning.exclude requirement
gemini-3.1-flash-lite-preview
gemini-3-flash-preview
gemini-2.5-pro✅ ‡same reasoning.exclude requirement
gemini-2.5-flash
gemini-2.5-flash-lite
xAI
grok-4.3✅ ‡minimal prompt + reasoning.exclude: true — extra framing regresses Grok 4.3
grok-4.20

‡ Reasoning-token leak suppression — for reasoning-flavoured Gemini Pro variants and Grok 4.3, the fixer would otherwise see visible "Thinking: Topic" prose prepended to every response. The eval config sets passthrough.reasoning.exclude: true (and the demo's usePreviewValidation does the same per-provider) to strip reasoning tokens from the response body at the API layer rather than at the prompt layer.

Components

9 built-in component types, all rendered out of the box by @mobile-reality/mdma-renderer-react:

ComponentType keyDescription
FormformMulti-field forms with text, email, number, select, textarea, checkbox, datetime, and file fields. Supports validation, required fields, default values, and sensitive (PII) flags.
ButtonbuttonAction buttons with primary, secondary, and danger variants.
TasklisttasklistInteractive checkbox task items with labels.
TabletableData tables with typed columns and row data.
ChartchartTable fallback by default — renders chart data as a simple HTML table to avoid forcing a charting dependency (~400KB). Override with your own renderer (e.g. recharts) via customizations.components.chart (see Custom Chart Renderer below).
CalloutcalloutAlert banners with info, warning, error, and success variants. Supports optional title and dismiss button.
Approval Gateapproval-gateApprove/deny workflow gates with pending, approved, and denied states.
WebhookwebhookWebhook triggers with idle, executing, success, and error status indicators.
ThinkingthinkingCollapsible thinking/reasoning blocks that show the AI's chain of thought.

Additionally, standard Markdown content (headings, paragraphs, lists, code blocks, images, links, tables, etc.) is rendered inline between components.

Custom Chart Renderer

The built-in chart renderer intentionally renders data as a plain table so the library stays lightweight. To get actual charts, register a custom renderer:

import { MdmaDocument } from '@mobile-reality/mdma-renderer-react';
import { MyRechartsRenderer } from './MyRechartsRenderer';

function App({ ast, store }) {
  return (
    <MdmaDocument
      ast={ast}
      store={store}
      customizations={{
        components: {
          chart: MyRechartsRenderer,
        },
      }}
    />
  );
}

This pattern works for overriding any built-in component — pass a custom React component under customizations.components.<type>.

Installation

# Core — parse and run MDMA documents
npm install @mobile-reality/mdma-parser @mobile-reality/mdma-runtime

# React rendering
npm install @mobile-reality/mdma-renderer-react

# AI authoring — system prompts for LLM-based generation
npm install @mobile-reality/mdma-prompt-pack

# Validation — static analysis for MDMA documents
npm install @mobile-reality/mdma-validator

# CLI — interactive prompt builder + document validation
npx @mobile-reality/mdma-cli

All packages are published under the @mobile-reality npm org.

Usage

import { unified } from 'unified';
import remarkParse from 'remark-parse';
import { remarkMdma } from '@mobile-reality/mdma-parser';
import { createDocumentStore } from '@mobile-reality/mdma-runtime';
import type { MdmaRoot } from '@mobile-reality/mdma-spec';

// 1. Parse markdown into AST
const processor = unified().use(remarkParse).use(remarkMdma);
const tree = processor.parse(markdown);
const ast = (await processor.run(tree)) as MdmaRoot;

// 2. Create a reactive document store
const store = createDocumentStore(ast, {
  documentId: 'my-doc',
  sessionId: crypto.randomUUID(),
});

// 3. Subscribe to state changes
store.subscribe((state) => {
  console.log('Bindings:', state.bindings);
});

// 4. Dispatch user actions
store.dispatch({
  type: 'FIELD_CHANGED',
  componentId: 'intake-form',
  field: 'patient-name',
  value: 'Jane Doe',
});

In a Chat

import { buildSystemPrompt, getAuthorPromptVariant } from '@mobile-reality/mdma-prompt-pack';

// Pick the prompt variant tuned for your model (falls back to default if unknown)
const { prompt: authorPrompt } = getAuthorPromptVariant('google/gemini-2.5-pro');

// Optionally layer a custom prompt on top for domain-specific generation
const systemPrompt = buildSystemPrompt({
  authorPrompt,
  customPrompt: `You are a bug tracking assistant. When a user reports a bug,
always generate a s

…
View source on GitHub