@uni-design-system/uni-mcp 4.4.0 → 4.6.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 +28 -0
- package/dist/{chunk-252ZWTTU.js → chunk-GT3T5DVX.js} +2022 -352
- package/dist/chunk-GT3T5DVX.js.map +1 -0
- package/dist/http.js +28 -4
- package/dist/http.js.map +1 -1
- package/dist/server.js +1 -1
- package/dist/stdio.js +1 -1
- package/package.json +2 -2
- package/dist/chunk-252ZWTTU.js.map +0 -1
package/dist/http.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
+
buildStoredTheme,
|
|
3
4
|
createUniServer,
|
|
5
|
+
listRuntimeThemes,
|
|
4
6
|
meta
|
|
5
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-GT3T5DVX.js";
|
|
6
8
|
|
|
7
9
|
// src/server/http.ts
|
|
8
10
|
import { createServer } from "http";
|
|
@@ -11,11 +13,20 @@ var PORT = Number(process.env.PORT ?? 8080);
|
|
|
11
13
|
var HOST = process.env.HOST ?? "0.0.0.0";
|
|
12
14
|
var TOKEN = process.env.UNI_MCP_TOKEN;
|
|
13
15
|
var ALLOWED_HOSTS = process.env.UNI_ALLOWED_HOSTS?.split(",").map((h) => h.trim()).filter(Boolean);
|
|
14
|
-
function send(res, status, body) {
|
|
16
|
+
function send(res, status, body, headers = {}) {
|
|
15
17
|
const payload = typeof body === "string" ? body : JSON.stringify(body);
|
|
16
|
-
res.writeHead(status, {
|
|
18
|
+
res.writeHead(status, {
|
|
19
|
+
"Content-Type": typeof body === "string" ? "text/plain" : "application/json",
|
|
20
|
+
...headers
|
|
21
|
+
});
|
|
17
22
|
res.end(payload);
|
|
18
23
|
}
|
|
24
|
+
var CORS = {
|
|
25
|
+
"Access-Control-Allow-Origin": "*",
|
|
26
|
+
"Access-Control-Allow-Methods": "GET, OPTIONS",
|
|
27
|
+
"Access-Control-Max-Age": "86400",
|
|
28
|
+
"Cache-Control": "public, max-age=300"
|
|
29
|
+
};
|
|
19
30
|
function readBody(req) {
|
|
20
31
|
return new Promise((resolve, reject) => {
|
|
21
32
|
const chunks = [];
|
|
@@ -61,13 +72,26 @@ async function handleMcp(req, res) {
|
|
|
61
72
|
if (!res.headersSent) rpcError(res, 500, "Internal server error");
|
|
62
73
|
}
|
|
63
74
|
}
|
|
75
|
+
function handleThemes(res, pathname) {
|
|
76
|
+
if (pathname === "/themes") {
|
|
77
|
+
return send(res, 200, { themes: listRuntimeThemes() }, CORS);
|
|
78
|
+
}
|
|
79
|
+
const id = pathname.slice("/themes/".length).replace(/\.json$/, "");
|
|
80
|
+
const result = buildStoredTheme(id);
|
|
81
|
+
return result.ok ? send(res, 200, result.envelope, CORS) : send(res, 404, { error: result.error }, CORS);
|
|
82
|
+
}
|
|
64
83
|
var httpServer = createServer((req, res) => {
|
|
65
84
|
const url = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
|
|
85
|
+
const isThemeRoute = url.pathname === "/themes" || url.pathname.startsWith("/themes/");
|
|
86
|
+
if (req.method === "OPTIONS" && isThemeRoute) return send(res, 204, "", CORS);
|
|
66
87
|
if (req.method === "GET" && (url.pathname === "/health" || url.pathname === "/")) {
|
|
67
88
|
return send(res, 200, { status: "ok", service: "uni-mcp", uniRelease: meta.version, ...meta.counts });
|
|
68
89
|
}
|
|
90
|
+
if (req.method === "GET" && isThemeRoute) return handleThemes(res, url.pathname);
|
|
69
91
|
if (url.pathname === "/mcp") return void handleMcp(req, res);
|
|
70
|
-
return send(res, 404, {
|
|
92
|
+
return send(res, 404, {
|
|
93
|
+
error: "Not found. POST JSON-RPC to /mcp; GET /health for status; GET /themes for theme JSON."
|
|
94
|
+
});
|
|
71
95
|
});
|
|
72
96
|
httpServer.listen(PORT, HOST, () => {
|
|
73
97
|
console.error(`[uni-mcp] HTTP server on http://${HOST}:${PORT}/mcp (Uni v${meta.version})`);
|
package/dist/http.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/server/http.ts"],"sourcesContent":["/**\n * HTTP entry — Streamable HTTP transport for a shared, always-current team\n * endpoint. Runs stateless (a fresh server+transport per request) so it scales\n * horizontally and is trivial to host on Render.\n *\n * Env:\n * PORT port to bind (Render sets this) default 8080\n * HOST bind address default 0.0.0.0\n * UNI_MCP_TOKEN if set, require `Authorization: Bearer <it>` default none\n * UNI_ALLOWED_HOSTS comma list for DNS-rebinding protection default none\n */\nimport { createServer, type IncomingMessage, type ServerResponse } from 'node:http';\nimport { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';\nimport { createUniServer } from './core.js';\nimport { meta } from './store.js';\n\nconst PORT = Number(process.env.PORT ?? 8080);\nconst HOST = process.env.HOST ?? '0.0.0.0';\nconst TOKEN = process.env.UNI_MCP_TOKEN;\nconst ALLOWED_HOSTS = process.env.UNI_ALLOWED_HOSTS?.split(',').map((h) => h.trim()).filter(Boolean);\n\nfunction send(res: ServerResponse, status: number, body: unknown) {\n const payload = typeof body === 'string' ? body : JSON.stringify(body);\n res.writeHead(status, {
|
|
1
|
+
{"version":3,"sources":["../src/server/http.ts"],"sourcesContent":["/**\n * HTTP entry — Streamable HTTP transport for a shared, always-current team\n * endpoint. Runs stateless (a fresh server+transport per request) so it scales\n * horizontally and is trivial to host on Render.\n *\n * Also serves the theme registry (`GET /themes`, `GET /themes/:id.json`) for\n * apps that fetch a theme without speaking MCP. Those routes are public,\n * read-only and CORS-enabled — they exist to be fetched from a browser —\n * whereas `/mcp` stays token-guarded and same-origin. Like the index, the\n * themes served are the snapshot of uni-core bundled at build time.\n *\n * Env:\n * PORT port to bind (Render sets this) default 8080\n * HOST bind address default 0.0.0.0\n * UNI_MCP_TOKEN if set, require `Authorization: Bearer <it>` default none\n * (applies to /mcp only; /health and /themes are public)\n * UNI_ALLOWED_HOSTS comma list for DNS-rebinding protection default none\n */\nimport { createServer, type IncomingMessage, type ServerResponse } from 'node:http';\nimport { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';\nimport { createUniServer } from './core.js';\nimport { meta } from './store.js';\nimport { buildStoredTheme, listRuntimeThemes } from './theme-json.js';\n\nconst PORT = Number(process.env.PORT ?? 8080);\nconst HOST = process.env.HOST ?? '0.0.0.0';\nconst TOKEN = process.env.UNI_MCP_TOKEN;\nconst ALLOWED_HOSTS = process.env.UNI_ALLOWED_HOSTS?.split(',').map((h) => h.trim()).filter(Boolean);\n\nfunction send(res: ServerResponse, status: number, body: unknown, headers: Record<string, string> = {}) {\n const payload = typeof body === 'string' ? body : JSON.stringify(body);\n res.writeHead(status, {\n 'Content-Type': typeof body === 'string' ? 'text/plain' : 'application/json',\n ...headers,\n });\n res.end(payload);\n}\n\n/**\n * Theme JSON is public, read-only data meant for browser apps, so it opts into\n * cross-origin reads. Deliberately scoped to the theme routes: `/mcp` carries\n * the token-guarded RPC surface and must not become reachable from any page.\n */\nconst CORS = {\n 'Access-Control-Allow-Origin': '*',\n 'Access-Control-Allow-Methods': 'GET, OPTIONS',\n 'Access-Control-Max-Age': '86400',\n 'Cache-Control': 'public, max-age=300',\n};\n\nfunction readBody(req: IncomingMessage): Promise<unknown> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = [];\n req.on('data', (c) => chunks.push(c as Buffer));\n req.on('end', () => {\n const raw = Buffer.concat(chunks).toString('utf8');\n if (!raw) return resolve(undefined);\n try {\n resolve(JSON.parse(raw));\n } catch (e) {\n reject(e);\n }\n });\n req.on('error', reject);\n });\n}\n\nfunction authorized(req: IncomingMessage): boolean {\n if (!TOKEN) return true;\n const header = req.headers.authorization ?? '';\n return header === `Bearer ${TOKEN}`;\n}\n\n/** JSON-RPC error envelope for pre-transport failures. */\nfunction rpcError(res: ServerResponse, status: number, message: string) {\n send(res, status, { jsonrpc: '2.0', error: { code: -32000, message }, id: null });\n}\n\nasync function handleMcp(req: IncomingMessage, res: ServerResponse) {\n if (!authorized(req)) return rpcError(res, 401, 'Unauthorized');\n\n // Stateless: isolate each request in its own server+transport instance.\n const server = createUniServer();\n const transport = new StreamableHTTPServerTransport({\n sessionIdGenerator: undefined,\n enableJsonResponse: true,\n ...(ALLOWED_HOSTS?.length ? { enableDnsRebindingProtection: true, allowedHosts: ALLOWED_HOSTS } : {}),\n });\n\n res.on('close', () => {\n transport.close();\n server.close();\n });\n\n try {\n await server.connect(transport);\n const body = await readBody(req);\n await transport.handleRequest(req, res, body);\n } catch (err) {\n console.error('[uni-mcp] request error:', err);\n if (!res.headersSent) rpcError(res, 500, 'Internal server error');\n }\n}\n\n/** `GET /themes` (index) and `GET /themes/:id.json` (one registerable theme). */\nfunction handleThemes(res: ServerResponse, pathname: string) {\n if (pathname === '/themes') {\n return send(res, 200, { themes: listRuntimeThemes() }, CORS);\n }\n\n const id = pathname.slice('/themes/'.length).replace(/\\.json$/, '');\n const result = buildStoredTheme(id);\n return result.ok\n ? send(res, 200, result.envelope, CORS)\n : send(res, 404, { error: result.error }, CORS);\n}\n\nconst httpServer = createServer((req, res) => {\n const url = new URL(req.url ?? '/', `http://${req.headers.host ?? 'localhost'}`);\n const isThemeRoute = url.pathname === '/themes' || url.pathname.startsWith('/themes/');\n\n if (req.method === 'OPTIONS' && isThemeRoute) return send(res, 204, '', CORS);\n\n if (req.method === 'GET' && (url.pathname === '/health' || url.pathname === '/')) {\n return send(res, 200, { status: 'ok', service: 'uni-mcp', uniRelease: meta.version, ...meta.counts });\n }\n if (req.method === 'GET' && isThemeRoute) return handleThemes(res, url.pathname);\n if (url.pathname === '/mcp') return void handleMcp(req, res);\n return send(res, 404, {\n error: 'Not found. POST JSON-RPC to /mcp; GET /health for status; GET /themes for theme JSON.',\n });\n});\n\nhttpServer.listen(PORT, HOST, () => {\n console.error(`[uni-mcp] HTTP server on http://${HOST}:${PORT}/mcp (Uni v${meta.version})`);\n if (!TOKEN) console.error('[uni-mcp] WARNING: no UNI_MCP_TOKEN set - endpoint is unauthenticated.');\n});\n"],"mappings":"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","names":[]}
|
package/dist/server.js
CHANGED
package/dist/stdio.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uni-design-system/uni-mcp",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.6.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Model Context Protocol server for the Uni Design System — gives AI coding assistants an authoritative interface to Uni components, tokens, themes, and guidelines.",
|
|
6
6
|
"repository": {
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"typescript": "~5.9.0",
|
|
34
34
|
"vitest": "^4.1.6",
|
|
35
35
|
"@uni-design-system/tsconfig": "0.1.0",
|
|
36
|
-
"@uni-design-system/uni-core": "
|
|
36
|
+
"@uni-design-system/uni-core": "8.0.0"
|
|
37
37
|
},
|
|
38
38
|
"scripts": {
|
|
39
39
|
"build-index": "tsx src/build/index.ts",
|