newprint-mcp 1.0.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 +110 -0
- package/package.json +31 -0
- package/src/index.js +123 -0
package/README.md
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# newprint-mcp
|
|
2
|
+
|
|
3
|
+
An MCP server that lets AI agents render rich UI — tables, charts, markdown, and pages — and return a shareable URL instead of plain text.
|
|
4
|
+
|
|
5
|
+
## How it works
|
|
6
|
+
|
|
7
|
+
1. Agent calls the `render_ui` tool with structured data
|
|
8
|
+
2. MCP sends it to your [newprint-server](https://github.com/yourusername/newprint)
|
|
9
|
+
3. Server stores it and returns a URL
|
|
10
|
+
4. User opens the URL and sees a rich rendered view
|
|
11
|
+
|
|
12
|
+
## Setup
|
|
13
|
+
|
|
14
|
+
### 1. Deploy newprint-server
|
|
15
|
+
|
|
16
|
+
You need a running instance of [newprint-server](https://github.com/yourusername/newprint). The easiest way is to deploy it to Railway — see the main repo for instructions.
|
|
17
|
+
|
|
18
|
+
### 2. Add to Claude Code
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
claude mcp add newprint-mcp \
|
|
22
|
+
-e NEWPRINT_SERVER_URL=https://your-server.up.railway.app \
|
|
23
|
+
-- npx newprint-mcp
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### 3. Add to Claude Desktop
|
|
27
|
+
|
|
28
|
+
Add this to your `claude_desktop_config.json`:
|
|
29
|
+
|
|
30
|
+
```json
|
|
31
|
+
{
|
|
32
|
+
"mcpServers": {
|
|
33
|
+
"newprint": {
|
|
34
|
+
"command": "npx",
|
|
35
|
+
"args": ["newprint-mcp"],
|
|
36
|
+
"env": {
|
|
37
|
+
"NEWPRINT_SERVER_URL": "https://your-server.up.railway.app"
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Usage
|
|
45
|
+
|
|
46
|
+
Once connected, ask Claude to render something:
|
|
47
|
+
|
|
48
|
+
> "Show me last month's sales as a bar chart"
|
|
49
|
+
|
|
50
|
+
> "Create a markdown report summarising these results"
|
|
51
|
+
|
|
52
|
+
> "Build a page with a summary, chart, and data table"
|
|
53
|
+
|
|
54
|
+
Claude will call `render_ui` and return a URL you can open in your browser.
|
|
55
|
+
|
|
56
|
+
## Tool: `render_ui`
|
|
57
|
+
|
|
58
|
+
| Parameter | Type | Required | Description |
|
|
59
|
+
|---|---|---|---|
|
|
60
|
+
| `type` | `table` \| `markdown` \| `chart` \| `page` | Yes | Render type |
|
|
61
|
+
| `title` | string | No | Optional display title |
|
|
62
|
+
| `payload` | object | Yes | Data matching the type schema |
|
|
63
|
+
|
|
64
|
+
### Payload schemas
|
|
65
|
+
|
|
66
|
+
**table**
|
|
67
|
+
```json
|
|
68
|
+
{
|
|
69
|
+
"columns": ["Name", "Score"],
|
|
70
|
+
"rows": [["Alice", 95], ["Bob", 88]]
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
**markdown**
|
|
75
|
+
```json
|
|
76
|
+
{
|
|
77
|
+
"content": "# Hello\n\nThis is **markdown**."
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
**chart**
|
|
82
|
+
```json
|
|
83
|
+
{
|
|
84
|
+
"chartType": "bar",
|
|
85
|
+
"xKey": "month",
|
|
86
|
+
"series": [{ "dataKey": "revenue", "label": "Revenue" }],
|
|
87
|
+
"data": [{ "month": "Jan", "revenue": 42000 }]
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
**page** (multiple blocks)
|
|
92
|
+
```json
|
|
93
|
+
{
|
|
94
|
+
"blocks": [
|
|
95
|
+
{ "type": "markdown", "payload": { "content": "## Summary" } },
|
|
96
|
+
{ "type": "table", "payload": { "columns": ["A", "B"], "rows": [[1, 2]] } }
|
|
97
|
+
]
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Environment variables
|
|
102
|
+
|
|
103
|
+
| Variable | Default | Description |
|
|
104
|
+
|---|---|---|
|
|
105
|
+
| `NEWPRINT_SERVER_URL` | `http://localhost:3000` | URL of your newprint-server instance |
|
|
106
|
+
|
|
107
|
+
## Requirements
|
|
108
|
+
|
|
109
|
+
- Node.js 18+
|
|
110
|
+
- A running [newprint-server](https://github.com/yourusername/newprint) instance
|
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "newprint-mcp",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "MCP server that lets AI agents render rich UI — tables, charts, markdown, and pages — and return a shareable URL.",
|
|
5
|
+
"bin": {
|
|
6
|
+
"newprint-mcp": "./src/index.js"
|
|
7
|
+
},
|
|
8
|
+
"files": [
|
|
9
|
+
"src"
|
|
10
|
+
],
|
|
11
|
+
"scripts": {
|
|
12
|
+
"start": "node src/index.js",
|
|
13
|
+
"dev": "node --watch src/index.js"
|
|
14
|
+
},
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
17
|
+
"zod": "^3.23.0"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"mcp",
|
|
21
|
+
"claude",
|
|
22
|
+
"ai",
|
|
23
|
+
"render",
|
|
24
|
+
"ui",
|
|
25
|
+
"agent"
|
|
26
|
+
],
|
|
27
|
+
"license": "MIT",
|
|
28
|
+
"engines": {
|
|
29
|
+
"node": ">=18"
|
|
30
|
+
}
|
|
31
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const { McpServer } = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
3
|
+
const { StdioServerTransport } = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
4
|
+
const { z } = require("zod");
|
|
5
|
+
|
|
6
|
+
// Schemas inlined so this package is self-contained on npm
|
|
7
|
+
const tablePayload = z.object({
|
|
8
|
+
columns: z.array(z.string()).min(1),
|
|
9
|
+
rows: z.array(
|
|
10
|
+
z.array(z.union([z.string(), z.number(), z.boolean(), z.null()]))
|
|
11
|
+
),
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
const markdownPayload = z.object({
|
|
15
|
+
content: z.string().min(1),
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
const chartPayload = z.object({
|
|
19
|
+
chartType: z.enum(["bar", "line"]),
|
|
20
|
+
xKey: z.string(),
|
|
21
|
+
series: z
|
|
22
|
+
.array(z.object({ dataKey: z.string(), label: z.string().optional() }))
|
|
23
|
+
.min(1),
|
|
24
|
+
data: z.array(z.record(z.union([z.string(), z.number()]))).min(1),
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const blockSchema = z.discriminatedUnion("type", [
|
|
28
|
+
z.object({ type: z.literal("table"), title: z.string().optional(), payload: tablePayload }),
|
|
29
|
+
z.object({ type: z.literal("markdown"), title: z.string().optional(), payload: markdownPayload }),
|
|
30
|
+
z.object({ type: z.literal("chart"), title: z.string().optional(), payload: chartPayload }),
|
|
31
|
+
]);
|
|
32
|
+
|
|
33
|
+
const renderInputSchema = z.discriminatedUnion("type", [
|
|
34
|
+
z.object({ type: z.literal("table"), title: z.string().optional(), payload: tablePayload }),
|
|
35
|
+
z.object({ type: z.literal("markdown"), title: z.string().optional(), payload: markdownPayload }),
|
|
36
|
+
z.object({ type: z.literal("chart"), title: z.string().optional(), payload: chartPayload }),
|
|
37
|
+
z.object({ type: z.literal("page"), title: z.string().optional(), payload: z.object({ blocks: z.array(blockSchema).min(1).max(50) }) }),
|
|
38
|
+
]);
|
|
39
|
+
|
|
40
|
+
const SERVER_URL = process.env.NEWPRINT_SERVER_URL || "http://localhost:3000";
|
|
41
|
+
|
|
42
|
+
const server = new McpServer({
|
|
43
|
+
name: "newprint-mcp",
|
|
44
|
+
version: "1.0.0",
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
server.tool(
|
|
48
|
+
"render_ui",
|
|
49
|
+
"Render structured data as a rich UI. Returns a URL the user can open to view tables, markdown, charts, or multi-block pages. Supported types: table, markdown, chart, page.",
|
|
50
|
+
{
|
|
51
|
+
type: z.enum(["table", "markdown", "chart", "page"]).describe("The render type"),
|
|
52
|
+
title: z.string().optional().describe("Optional display title"),
|
|
53
|
+
payload: z
|
|
54
|
+
.object({})
|
|
55
|
+
.passthrough()
|
|
56
|
+
.describe(
|
|
57
|
+
"Payload matching the render type. " +
|
|
58
|
+
"table: { columns: string[], rows: any[][] }. " +
|
|
59
|
+
"markdown: { content: string }. " +
|
|
60
|
+
"chart: { chartType: 'bar'|'line', xKey: string, series: [{ dataKey: string, label?: string }], data: object[] }. " +
|
|
61
|
+
"page: { blocks: Array<{ type: 'table'|'markdown'|'chart', title?: string, payload: object }> }."
|
|
62
|
+
),
|
|
63
|
+
},
|
|
64
|
+
async ({ type, title, payload }) => {
|
|
65
|
+
const parsed = renderInputSchema.safeParse({ type, title, payload });
|
|
66
|
+
if (!parsed.success) {
|
|
67
|
+
return {
|
|
68
|
+
isError: true,
|
|
69
|
+
content: [
|
|
70
|
+
{
|
|
71
|
+
type: "text",
|
|
72
|
+
text: `Invalid input: ${parsed.error.issues.map((i) => i.message).join(", ")}`,
|
|
73
|
+
},
|
|
74
|
+
],
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
let res;
|
|
79
|
+
try {
|
|
80
|
+
res = await fetch(`${SERVER_URL}/render`, {
|
|
81
|
+
method: "POST",
|
|
82
|
+
headers: { "Content-Type": "application/json" },
|
|
83
|
+
body: JSON.stringify(parsed.data),
|
|
84
|
+
});
|
|
85
|
+
} catch (err) {
|
|
86
|
+
return {
|
|
87
|
+
isError: true,
|
|
88
|
+
content: [
|
|
89
|
+
{
|
|
90
|
+
type: "text",
|
|
91
|
+
text: `Failed to reach newprint-server at ${SERVER_URL}: ${err.message}`,
|
|
92
|
+
},
|
|
93
|
+
],
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (!res.ok) {
|
|
98
|
+
const err = await res.json().catch(() => ({}));
|
|
99
|
+
return {
|
|
100
|
+
isError: true,
|
|
101
|
+
content: [
|
|
102
|
+
{
|
|
103
|
+
type: "text",
|
|
104
|
+
text: `Server error (${res.status}): ${err.details ? JSON.stringify(err.details) : res.statusText}`,
|
|
105
|
+
},
|
|
106
|
+
],
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const { url } = await res.json();
|
|
111
|
+
return {
|
|
112
|
+
content: [{ type: "text", text: url }],
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
async function main() {
|
|
118
|
+
const transport = new StdioServerTransport();
|
|
119
|
+
await server.connect(transport);
|
|
120
|
+
console.error("newprint-mcp running — connected to " + SERVER_URL);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
main().catch(console.error);
|