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.
Files changed (3) hide show
  1. package/README.md +110 -0
  2. package/package.json +31 -0
  3. 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);