Basic Syntax
MDX templates are strings containing Markdown and JSX:
@Tool({
name: 'get_article',
ui: {
template: `
# {output.title}
**Author:** {output.author}
**Published:** {helpers.formatDate(output.date)}
{output.content}
---
*Tags: {output.tags.join(', ')}*
`,
},
})
Variable Interpolation
Access template context variables directly:
# Welcome, {output.name}!
Your account was created on {helpers.formatDate(output.createdAt)}.
| Field | Value |
|-------|-------|
| Email | {output.email} |
| Plan | {output.plan} |
| Status | {output.status} |
Available variables:
input - Tool input arguments
output - Tool output/result
helpers - Utility functions
Using Components
Built-in Components
MDX templates can use components you provide:
@Tool({
name: 'show_status',
ui: {
template: `
# System Status
<Alert type="info">
All systems operational
</Alert>
<Card title="Server Status">
- CPU: {output.cpu}%
- Memory: {output.memory}%
- Disk: {output.disk}%
</Card>
`,
mdxComponents: {
Alert: ({ type, children }) => `
<div class="alert alert-${type}">${children}</div>
`,
Card: ({ title, children }) => `
<div class="card">
<h3>${title}</h3>
${children}
</div>
`,
},
},
})
FrontMCP UI Components
Register FrontMCP components for use in MDX:
import { card, badge, button, alert } from '@frontmcp/ui';
@Tool({
name: 'my_tool',
ui: {
template: `
# User Profile
<Card title={output.name}>
<Badge variant="success">{output.status}</Badge>
**Email:** {output.email}
<Button variant="outline" size="sm">Edit Profile</Button>
</Card>
`,
mdxComponents: {
Card: ({ title, children }) => card(children, { title }),
Badge: ({ variant, children }) => badge(children, { variant }),
Button: ({ variant, size, children }) => button(children, { variant, size }),
},
},
})
Markdown Features
Headings
# Heading 1
## Heading 2
### Heading 3
Text Formatting
**Bold text**
*Italic text*
~~Strikethrough~~
`inline code`
Lists
Unordered:
- Item 1
- Item 2
- Nested item
Ordered:
1. First
2. Second
3. Third
Tables
| Name | Value | Status |
|------|-------|--------|
| API | v2.0 | {output.apiStatus} |
| DB | v1.5 | {output.dbStatus} |
Code Blocks
```javascript
const greeting = 'Hello, World!';
console.log(greeting);
```
Blockquotes
> This is a blockquote.
> It can span multiple lines.
Links and Images
[Link text](https://example.com)

JSX in MDX
Inline JSX
The current status is <Badge variant="success">Active</Badge>.
Block JSX
<Card title="Statistics">
<div className="grid grid-cols-3 gap-4">
<div>
<h4>Users</h4>
<p className="text-2xl font-bold">{output.users}</p>
</div>
<div>
<h4>Revenue</h4>
<p className="text-2xl font-bold">{helpers.formatCurrency(output.revenue)}</p>
</div>
</div>
</Card>
Conditional Rendering
# Status Report
{output.hasErrors ? (
<Alert type="danger">
Found {output.errorCount} errors
</Alert>
) : (
<Alert type="success">
All checks passed!
</Alert>
)}
Mapping Arrays
# Team Members
{output.members.map(member => (
<Card key={member.id} title={member.name}>
**Role:** {member.role}
**Email:** {member.email}
</Card>
))}
Template Functions
For complex logic, use a template function:
@Tool({
name: 'get_report',
ui: {
template: (ctx) => {
const { output, helpers } = ctx;
const statusEmoji = output.status === 'success' ? '✅' : '❌';
return `
# ${statusEmoji} ${helpers.escapeHtml(output.title)}
**Generated:** ${helpers.formatDate(output.generatedAt)}
## Summary
${output.summary}
## Details
${output.details.map(d => `- **${d.label}:** ${d.value}`).join('\n')}
---
<Button href="${output.downloadUrl}">Download Report</Button>
`;
},
mdxComponents: {
Button: ({ href, children }) => button(children, { href }),
},
},
})
Styling
Tailwind in JSX
<div className="bg-gradient-to-r from-blue-500 to-purple-500 p-6 rounded-xl text-white">
<h2 className="text-2xl font-bold mb-2">{output.title}</h2>
<p className="opacity-90">{output.subtitle}</p>
</div>
CSS Classes in Markdown
MDX supports adding classes to elements:
# Heading {.text-center .text-blue-500}
Paragraph with custom class. {.text-gray-600}
Class support depends on the MDX processor configuration.
FrontMCP uses @mdx-js/mdx which supports this syntax.
Complete Example
import { Tool, ToolContext } from '@frontmcp/sdk';
import { card, badge, button, alert, table } from '@frontmcp/ui';
import { z } from 'zod';
@Tool({
name: 'get_dashboard',
description: 'Get dashboard summary',
outputSchema: z.object({
title: z.string(),
stats: z.object({
users: z.number(),
revenue: z.number(),
orders: z.number(),
}),
recentOrders: z.array(z.object({
id: z.string(),
customer: z.string(),
amount: z.number(),
status: z.string(),
})),
alerts: z.array(z.object({
type: z.enum(['info', 'warning', 'danger']),
message: z.string(),
})),
}),
ui: {
template: `
# {output.title}
{output.alerts.length > 0 && (
<div className="space-y-2 mb-6">
{output.alerts.map((alert, i) => (
<Alert key={i} type={alert.type}>{alert.message}</Alert>
))}
</div>
)}
## Quick Stats
<div className="grid grid-cols-3 gap-4 mb-6">
<StatCard label="Users" value={output.stats.users} />
<StatCard label="Revenue" value={helpers.formatCurrency(output.stats.revenue)} />
<StatCard label="Orders" value={output.stats.orders} />
</div>
## Recent Orders
<Table
headers={['Order', 'Customer', 'Amount', 'Status']}
rows={output.recentOrders.map(o => [
o.id,
o.customer,
helpers.formatCurrency(o.amount),
o.status
])}
/>
---
<Button variant="outline" href="/orders">View All Orders</Button>
`,
mdxComponents: {
Alert: ({ type, children }) => alert(children, { variant: type }),
StatCard: ({ label, value }) => card(`
<div class="text-center">
<div class="text-3xl font-bold">${value}</div>
<div class="text-sm text-gray-500">${label}</div>
</div>
`),
Table: ({ headers, rows }) => table({ headers, rows }),
Button: ({ variant, href, children }) => button(children, { variant, href }),
},
},
})
export class GetDashboardTool extends ToolContext {
async execute() {
// Return dashboard data...
}
}
Best Practices
- Use for content-heavy widgets - MDX shines when you have lots of text
- Register reusable components - Create an mdxComponents library
- Keep JSX simple - Complex logic should be in components
- Escape user content - Use
helpers.escapeHtml() when needed
- Test rendering - MDX can fail silently on syntax errors