sunpeak 0.2.3 → 0.2.5
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 +36 -9
- package/dist/index.cjs +264 -75
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +212 -12
- package/dist/index.d.ts +212 -12
- package/dist/index.js +246 -76
- package/dist/index.js.map +1 -1
- package/dist/mcp/index.cjs +250 -0
- package/dist/mcp/index.cjs.map +1 -0
- package/dist/mcp/index.d.cts +12 -0
- package/dist/mcp/index.d.ts +12 -0
- package/dist/mcp/index.js +243 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/styles/globals.css +0 -1
- package/package.json +10 -2
- package/template/README.md +38 -2
- package/template/dev/main.tsx +51 -48
- package/template/mcp/server.ts +66 -0
- package/template/package.json +3 -0
- package/template/postcss.config.js +5 -0
- package/template/src/App.tsx +9 -5
- package/template/src/components/sunpeak-card.tsx +48 -17
- package/template/src/components/sunpeak-carousel.tsx +42 -8
- package/template/src/index.chatgpt.tsx +8 -0
- package/template/src/styles/globals.css +0 -1
- package/template/tsup.config.ts +29 -2
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var http = require('http');
|
|
4
|
+
var fs = require('fs');
|
|
5
|
+
var path = require('path');
|
|
6
|
+
var url = require('url');
|
|
7
|
+
var index_js = require('@modelcontextprotocol/sdk/server/index.js');
|
|
8
|
+
var sse_js = require('@modelcontextprotocol/sdk/server/sse.js');
|
|
9
|
+
var types_js = require('@modelcontextprotocol/sdk/types.js');
|
|
10
|
+
var zod = require('zod');
|
|
11
|
+
|
|
12
|
+
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
13
|
+
|
|
14
|
+
var fs__default = /*#__PURE__*/_interopDefault(fs);
|
|
15
|
+
var path__default = /*#__PURE__*/_interopDefault(path);
|
|
16
|
+
|
|
17
|
+
// src/mcp/server.ts
|
|
18
|
+
function widgetDescriptorMeta() {
|
|
19
|
+
return {
|
|
20
|
+
"openai/outputTemplate": "ui://widget/app.html",
|
|
21
|
+
"openai/toolInvocation/invoking": "Loading your app",
|
|
22
|
+
"openai/toolInvocation/invoked": "App loaded",
|
|
23
|
+
"openai/widgetAccessible": true,
|
|
24
|
+
"openai/resultCanProduceWidget": true
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
function widgetInvocationMeta() {
|
|
28
|
+
return {
|
|
29
|
+
"openai/toolInvocation/invoking": "Loading your app",
|
|
30
|
+
"openai/toolInvocation/invoked": "App loaded"
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
function readWidgetHtml(distPath) {
|
|
34
|
+
const htmlPath = path__default.default.resolve(distPath);
|
|
35
|
+
if (!fs__default.default.existsSync(htmlPath)) {
|
|
36
|
+
throw new Error(
|
|
37
|
+
`Widget HTML not found at ${htmlPath}. Run "pnpm build" to generate the built app.`
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
const jsContents = fs__default.default.readFileSync(htmlPath, "utf8");
|
|
41
|
+
return `<!DOCTYPE html>
|
|
42
|
+
<html>
|
|
43
|
+
<head>
|
|
44
|
+
<meta charset="UTF-8">
|
|
45
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
46
|
+
</head>
|
|
47
|
+
<body>
|
|
48
|
+
<div id="root"></div>
|
|
49
|
+
<script>
|
|
50
|
+
${jsContents}
|
|
51
|
+
</script>
|
|
52
|
+
</body>
|
|
53
|
+
</html>`;
|
|
54
|
+
}
|
|
55
|
+
function createAppServer(config) {
|
|
56
|
+
const {
|
|
57
|
+
name = "sunpeak-app",
|
|
58
|
+
version = "0.1.0",
|
|
59
|
+
toolName = "show-app",
|
|
60
|
+
toolDescription = "Show the app",
|
|
61
|
+
dummyData = {}
|
|
62
|
+
} = config;
|
|
63
|
+
const widgetHtml = readWidgetHtml(config.distPath);
|
|
64
|
+
const toolInputSchema = {
|
|
65
|
+
type: "object",
|
|
66
|
+
properties: {},
|
|
67
|
+
additionalProperties: false
|
|
68
|
+
};
|
|
69
|
+
const toolInputParser = zod.z.object({});
|
|
70
|
+
const tool = {
|
|
71
|
+
name: toolName,
|
|
72
|
+
description: toolDescription,
|
|
73
|
+
inputSchema: toolInputSchema,
|
|
74
|
+
title: toolDescription,
|
|
75
|
+
_meta: widgetDescriptorMeta(),
|
|
76
|
+
annotations: {
|
|
77
|
+
destructiveHint: false,
|
|
78
|
+
openWorldHint: false,
|
|
79
|
+
readOnlyHint: true
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
const resource = {
|
|
83
|
+
uri: "ui://widget/app.html",
|
|
84
|
+
name: toolDescription,
|
|
85
|
+
description: `${toolDescription} widget markup`,
|
|
86
|
+
mimeType: "text/html+skybridge",
|
|
87
|
+
_meta: widgetDescriptorMeta()
|
|
88
|
+
};
|
|
89
|
+
const server = new index_js.Server(
|
|
90
|
+
{
|
|
91
|
+
name,
|
|
92
|
+
version
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
capabilities: {
|
|
96
|
+
resources: {},
|
|
97
|
+
tools: {}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
);
|
|
101
|
+
server.setRequestHandler(
|
|
102
|
+
types_js.ListResourcesRequestSchema,
|
|
103
|
+
async (_request) => {
|
|
104
|
+
console.log("[MCP] ListResources");
|
|
105
|
+
return { resources: [resource] };
|
|
106
|
+
}
|
|
107
|
+
);
|
|
108
|
+
server.setRequestHandler(
|
|
109
|
+
types_js.ReadResourceRequestSchema,
|
|
110
|
+
async (request) => {
|
|
111
|
+
console.log("[MCP] ReadResource:", request.params.uri);
|
|
112
|
+
if (request.params.uri !== resource.uri) {
|
|
113
|
+
throw new Error(`Unknown resource: ${request.params.uri}`);
|
|
114
|
+
}
|
|
115
|
+
return {
|
|
116
|
+
contents: [
|
|
117
|
+
{
|
|
118
|
+
uri: resource.uri,
|
|
119
|
+
mimeType: "text/html+skybridge",
|
|
120
|
+
text: widgetHtml,
|
|
121
|
+
_meta: widgetDescriptorMeta()
|
|
122
|
+
}
|
|
123
|
+
]
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
);
|
|
127
|
+
server.setRequestHandler(
|
|
128
|
+
types_js.ListToolsRequestSchema,
|
|
129
|
+
async (_request) => {
|
|
130
|
+
console.log("[MCP] ListTools");
|
|
131
|
+
return { tools: [tool] };
|
|
132
|
+
}
|
|
133
|
+
);
|
|
134
|
+
server.setRequestHandler(
|
|
135
|
+
types_js.CallToolRequestSchema,
|
|
136
|
+
async (request) => {
|
|
137
|
+
console.log("[MCP] CallTool:", request.params.name, request.params.arguments);
|
|
138
|
+
if (request.params.name !== toolName) {
|
|
139
|
+
throw new Error(`Unknown tool: ${request.params.name}`);
|
|
140
|
+
}
|
|
141
|
+
toolInputParser.parse(request.params.arguments ?? {});
|
|
142
|
+
return {
|
|
143
|
+
content: [
|
|
144
|
+
{
|
|
145
|
+
type: "text",
|
|
146
|
+
text: `Rendered ${toolDescription}!`
|
|
147
|
+
}
|
|
148
|
+
],
|
|
149
|
+
structuredContent: dummyData,
|
|
150
|
+
_meta: widgetInvocationMeta()
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
);
|
|
154
|
+
return server;
|
|
155
|
+
}
|
|
156
|
+
var sessions = /* @__PURE__ */ new Map();
|
|
157
|
+
var ssePath = "/mcp";
|
|
158
|
+
var postPath = "/mcp/messages";
|
|
159
|
+
async function handleSseRequest(res, config) {
|
|
160
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
161
|
+
const server = createAppServer(config);
|
|
162
|
+
const transport = new sse_js.SSEServerTransport(postPath, res);
|
|
163
|
+
const sessionId = transport.sessionId;
|
|
164
|
+
sessions.set(sessionId, { server, transport });
|
|
165
|
+
transport.onclose = async () => {
|
|
166
|
+
sessions.delete(sessionId);
|
|
167
|
+
await server.close();
|
|
168
|
+
};
|
|
169
|
+
transport.onerror = (error) => {
|
|
170
|
+
console.error("SSE transport error", error);
|
|
171
|
+
};
|
|
172
|
+
try {
|
|
173
|
+
await server.connect(transport);
|
|
174
|
+
} catch (error) {
|
|
175
|
+
sessions.delete(sessionId);
|
|
176
|
+
console.error("Failed to start SSE session", error);
|
|
177
|
+
if (!res.headersSent) {
|
|
178
|
+
res.writeHead(500).end("Failed to establish SSE connection");
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
async function handlePostMessage(req, res, url) {
|
|
183
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
184
|
+
res.setHeader("Access-Control-Allow-Headers", "content-type");
|
|
185
|
+
const sessionId = url.searchParams.get("sessionId");
|
|
186
|
+
if (!sessionId) {
|
|
187
|
+
res.writeHead(400).end("Missing sessionId query parameter");
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
const session = sessions.get(sessionId);
|
|
191
|
+
if (!session) {
|
|
192
|
+
res.writeHead(404).end("Unknown session");
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
try {
|
|
196
|
+
await session.transport.handlePostMessage(req, res);
|
|
197
|
+
} catch (error) {
|
|
198
|
+
console.error("Failed to process message", error);
|
|
199
|
+
if (!res.headersSent) {
|
|
200
|
+
res.writeHead(500).end("Failed to process message");
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
function runMCPServer(config) {
|
|
205
|
+
const portEnv = Number(process.env.PORT ?? 6766);
|
|
206
|
+
const port = config.port ?? (Number.isFinite(portEnv) ? portEnv : 6766);
|
|
207
|
+
const httpServer = http.createServer(
|
|
208
|
+
async (req, res) => {
|
|
209
|
+
if (!req.url) {
|
|
210
|
+
res.writeHead(400).end("Missing URL");
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
const url$1 = new url.URL(req.url, `http://${req.headers.host ?? "localhost"}`);
|
|
214
|
+
console.log(`[HTTP] ${req.method} ${url$1.pathname}`);
|
|
215
|
+
if (req.method === "OPTIONS" && (url$1.pathname === ssePath || url$1.pathname === postPath)) {
|
|
216
|
+
res.writeHead(204, {
|
|
217
|
+
"Access-Control-Allow-Origin": "*",
|
|
218
|
+
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
|
|
219
|
+
"Access-Control-Allow-Headers": "content-type"
|
|
220
|
+
});
|
|
221
|
+
res.end();
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
if (req.method === "GET" && url$1.pathname === ssePath) {
|
|
225
|
+
await handleSseRequest(res, config);
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
if (req.method === "POST" && url$1.pathname === postPath) {
|
|
229
|
+
await handlePostMessage(req, res, url$1);
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
res.writeHead(404).end("Not Found");
|
|
233
|
+
}
|
|
234
|
+
);
|
|
235
|
+
httpServer.on("clientError", (err, socket) => {
|
|
236
|
+
console.error("HTTP client error", err);
|
|
237
|
+
socket.end("HTTP/1.1 400 Bad Request\r\n\r\n");
|
|
238
|
+
});
|
|
239
|
+
httpServer.listen(port, () => {
|
|
240
|
+
console.log(`Sunpeak MCP server listening on http://localhost:${port}`);
|
|
241
|
+
console.log(` SSE stream: GET http://localhost:${port}${ssePath}`);
|
|
242
|
+
console.log(
|
|
243
|
+
` Message post endpoint: POST http://localhost:${port}${postPath}?sessionId=...`
|
|
244
|
+
);
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
exports.runMCPServer = runMCPServer;
|
|
249
|
+
//# sourceMappingURL=index.cjs.map
|
|
250
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/mcp/server.ts"],"names":["path","fs","z","Server","ListResourcesRequestSchema","ReadResourceRequestSchema","ListToolsRequestSchema","CallToolRequestSchema","SSEServerTransport","createServer","url","URL"],"mappings":"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","file":"index.cjs","sourcesContent":["import {\n createServer,\n type IncomingMessage,\n type ServerResponse,\n} from \"node:http\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport { URL } from \"node:url\";\n\nimport { Server } from \"@modelcontextprotocol/sdk/server/index.js\";\nimport { SSEServerTransport } from \"@modelcontextprotocol/sdk/server/sse.js\";\nimport {\n CallToolRequestSchema,\n ListResourcesRequestSchema,\n ListToolsRequestSchema,\n ReadResourceRequestSchema,\n type CallToolRequest,\n type ListResourcesRequest,\n type ListToolsRequest,\n type ReadResourceRequest,\n type Resource,\n type Tool,\n} from \"@modelcontextprotocol/sdk/types.js\";\nimport { z } from \"zod\";\n\nexport interface MCPServerConfig {\n name?: string;\n version?: string;\n port?: number;\n distPath: string;\n toolName?: string;\n toolDescription?: string;\n dummyData?: Record<string, unknown>;\n}\n\nfunction widgetDescriptorMeta() {\n return {\n \"openai/outputTemplate\": \"ui://widget/app.html\",\n \"openai/toolInvocation/invoking\": \"Loading your app\",\n \"openai/toolInvocation/invoked\": \"App loaded\",\n \"openai/widgetAccessible\": true,\n \"openai/resultCanProduceWidget\": true,\n } as const;\n}\n\nfunction widgetInvocationMeta() {\n return {\n \"openai/toolInvocation/invoking\": \"Loading your app\",\n \"openai/toolInvocation/invoked\": \"App loaded\",\n } as const;\n}\n\nfunction readWidgetHtml(distPath: string): string {\n const htmlPath = path.resolve(distPath);\n\n if (!fs.existsSync(htmlPath)) {\n throw new Error(\n `Widget HTML not found at ${htmlPath}. Run \"pnpm build\" to generate the built app.`\n );\n }\n\n const jsContents = fs.readFileSync(htmlPath, \"utf8\");\n\n // Wrap the JS in a minimal HTML shell suitable for ChatGPT\n // Styles should already be bundled in the JS by the template's build process\n return `<!DOCTYPE html>\n<html>\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n</head>\n<body>\n <div id=\"root\"></div>\n <script>\n${jsContents}\n </script>\n</body>\n</html>`;\n}\n\nfunction createAppServer(config: MCPServerConfig): Server {\n const {\n name = \"sunpeak-app\",\n version = \"0.1.0\",\n toolName = \"show-app\",\n toolDescription = \"Show the app\",\n dummyData = {},\n } = config;\n\n const widgetHtml = readWidgetHtml(config.distPath);\n\n const toolInputSchema = {\n type: \"object\",\n properties: {},\n additionalProperties: false,\n } as const;\n\n const toolInputParser = z.object({});\n\n const tool: Tool = {\n name: toolName,\n description: toolDescription,\n inputSchema: toolInputSchema,\n title: toolDescription,\n _meta: widgetDescriptorMeta(),\n annotations: {\n destructiveHint: false,\n openWorldHint: false,\n readOnlyHint: true,\n },\n };\n\n const resource: Resource = {\n uri: \"ui://widget/app.html\",\n name: toolDescription,\n description: `${toolDescription} widget markup`,\n mimeType: \"text/html+skybridge\",\n _meta: widgetDescriptorMeta(),\n };\n\n const server = new Server(\n {\n name,\n version,\n },\n {\n capabilities: {\n resources: {},\n tools: {},\n },\n }\n );\n\n server.setRequestHandler(\n ListResourcesRequestSchema,\n async (_request: ListResourcesRequest) => {\n console.log(\"[MCP] ListResources\");\n return { resources: [resource] };\n }\n );\n\n server.setRequestHandler(\n ReadResourceRequestSchema,\n async (request: ReadResourceRequest) => {\n console.log(\"[MCP] ReadResource:\", request.params.uri);\n if (request.params.uri !== resource.uri) {\n throw new Error(`Unknown resource: ${request.params.uri}`);\n }\n\n return {\n contents: [\n {\n uri: resource.uri,\n mimeType: \"text/html+skybridge\",\n text: widgetHtml,\n _meta: widgetDescriptorMeta(),\n },\n ],\n };\n }\n );\n\n server.setRequestHandler(\n ListToolsRequestSchema,\n async (_request: ListToolsRequest) => {\n console.log(\"[MCP] ListTools\");\n return { tools: [tool] };\n }\n );\n\n server.setRequestHandler(\n CallToolRequestSchema,\n async (request: CallToolRequest) => {\n console.log(\"[MCP] CallTool:\", request.params.name, request.params.arguments);\n if (request.params.name !== toolName) {\n throw new Error(`Unknown tool: ${request.params.name}`);\n }\n\n toolInputParser.parse(request.params.arguments ?? {});\n\n return {\n content: [\n {\n type: \"text\",\n text: `Rendered ${toolDescription}!`,\n },\n ],\n structuredContent: dummyData,\n _meta: widgetInvocationMeta(),\n };\n }\n );\n\n return server;\n}\n\ntype SessionRecord = {\n server: Server;\n transport: SSEServerTransport;\n};\n\nconst sessions = new Map<string, SessionRecord>();\n\nconst ssePath = \"/mcp\";\nconst postPath = \"/mcp/messages\";\n\nasync function handleSseRequest(\n res: ServerResponse,\n config: MCPServerConfig\n) {\n res.setHeader(\"Access-Control-Allow-Origin\", \"*\");\n const server = createAppServer(config);\n const transport = new SSEServerTransport(postPath, res);\n const sessionId = transport.sessionId;\n\n sessions.set(sessionId, { server, transport });\n\n transport.onclose = async () => {\n sessions.delete(sessionId);\n await server.close();\n };\n\n transport.onerror = (error) => {\n console.error(\"SSE transport error\", error);\n };\n\n try {\n await server.connect(transport);\n } catch (error) {\n sessions.delete(sessionId);\n console.error(\"Failed to start SSE session\", error);\n if (!res.headersSent) {\n res.writeHead(500).end(\"Failed to establish SSE connection\");\n }\n }\n}\n\nasync function handlePostMessage(\n req: IncomingMessage,\n res: ServerResponse,\n url: URL\n) {\n res.setHeader(\"Access-Control-Allow-Origin\", \"*\");\n res.setHeader(\"Access-Control-Allow-Headers\", \"content-type\");\n const sessionId = url.searchParams.get(\"sessionId\");\n\n if (!sessionId) {\n res.writeHead(400).end(\"Missing sessionId query parameter\");\n return;\n }\n\n const session = sessions.get(sessionId);\n\n if (!session) {\n res.writeHead(404).end(\"Unknown session\");\n return;\n }\n\n try {\n await session.transport.handlePostMessage(req, res);\n } catch (error) {\n console.error(\"Failed to process message\", error);\n if (!res.headersSent) {\n res.writeHead(500).end(\"Failed to process message\");\n }\n }\n}\n\nexport function runMCPServer(config: MCPServerConfig): void {\n const portEnv = Number(process.env.PORT ?? 6766);\n const port = config.port ?? (Number.isFinite(portEnv) ? portEnv : 6766);\n\n const httpServer = createServer(\n async (req: IncomingMessage, res: ServerResponse) => {\n if (!req.url) {\n res.writeHead(400).end(\"Missing URL\");\n return;\n }\n\n const url = new URL(req.url, `http://${req.headers.host ?? \"localhost\"}`);\n console.log(`[HTTP] ${req.method} ${url.pathname}`);\n\n if (\n req.method === \"OPTIONS\" &&\n (url.pathname === ssePath || url.pathname === postPath)\n ) {\n res.writeHead(204, {\n \"Access-Control-Allow-Origin\": \"*\",\n \"Access-Control-Allow-Methods\": \"GET, POST, OPTIONS\",\n \"Access-Control-Allow-Headers\": \"content-type\",\n });\n res.end();\n return;\n }\n\n if (req.method === \"GET\" && url.pathname === ssePath) {\n await handleSseRequest(res, config);\n return;\n }\n\n if (req.method === \"POST\" && url.pathname === postPath) {\n await handlePostMessage(req, res, url);\n return;\n }\n\n res.writeHead(404).end(\"Not Found\");\n }\n );\n\n httpServer.on(\"clientError\", (err: Error, socket) => {\n console.error(\"HTTP client error\", err);\n socket.end(\"HTTP/1.1 400 Bad Request\\r\\n\\r\\n\");\n });\n\n httpServer.listen(port, () => {\n console.log(`Sunpeak MCP server listening on http://localhost:${port}`);\n console.log(` SSE stream: GET http://localhost:${port}${ssePath}`);\n console.log(\n ` Message post endpoint: POST http://localhost:${port}${postPath}?sessionId=...`\n );\n });\n}\n"]}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
interface MCPServerConfig {
|
|
2
|
+
name?: string;
|
|
3
|
+
version?: string;
|
|
4
|
+
port?: number;
|
|
5
|
+
distPath: string;
|
|
6
|
+
toolName?: string;
|
|
7
|
+
toolDescription?: string;
|
|
8
|
+
dummyData?: Record<string, unknown>;
|
|
9
|
+
}
|
|
10
|
+
declare function runMCPServer(config: MCPServerConfig): void;
|
|
11
|
+
|
|
12
|
+
export { type MCPServerConfig, runMCPServer };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
interface MCPServerConfig {
|
|
2
|
+
name?: string;
|
|
3
|
+
version?: string;
|
|
4
|
+
port?: number;
|
|
5
|
+
distPath: string;
|
|
6
|
+
toolName?: string;
|
|
7
|
+
toolDescription?: string;
|
|
8
|
+
dummyData?: Record<string, unknown>;
|
|
9
|
+
}
|
|
10
|
+
declare function runMCPServer(config: MCPServerConfig): void;
|
|
11
|
+
|
|
12
|
+
export { type MCPServerConfig, runMCPServer };
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import { createServer } from 'http';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { URL } from 'url';
|
|
5
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
6
|
+
import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
|
|
7
|
+
import { ListResourcesRequestSchema, ReadResourceRequestSchema, ListToolsRequestSchema, CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
8
|
+
import { z } from 'zod';
|
|
9
|
+
|
|
10
|
+
// src/mcp/server.ts
|
|
11
|
+
function widgetDescriptorMeta() {
|
|
12
|
+
return {
|
|
13
|
+
"openai/outputTemplate": "ui://widget/app.html",
|
|
14
|
+
"openai/toolInvocation/invoking": "Loading your app",
|
|
15
|
+
"openai/toolInvocation/invoked": "App loaded",
|
|
16
|
+
"openai/widgetAccessible": true,
|
|
17
|
+
"openai/resultCanProduceWidget": true
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
function widgetInvocationMeta() {
|
|
21
|
+
return {
|
|
22
|
+
"openai/toolInvocation/invoking": "Loading your app",
|
|
23
|
+
"openai/toolInvocation/invoked": "App loaded"
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
function readWidgetHtml(distPath) {
|
|
27
|
+
const htmlPath = path.resolve(distPath);
|
|
28
|
+
if (!fs.existsSync(htmlPath)) {
|
|
29
|
+
throw new Error(
|
|
30
|
+
`Widget HTML not found at ${htmlPath}. Run "pnpm build" to generate the built app.`
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
const jsContents = fs.readFileSync(htmlPath, "utf8");
|
|
34
|
+
return `<!DOCTYPE html>
|
|
35
|
+
<html>
|
|
36
|
+
<head>
|
|
37
|
+
<meta charset="UTF-8">
|
|
38
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
39
|
+
</head>
|
|
40
|
+
<body>
|
|
41
|
+
<div id="root"></div>
|
|
42
|
+
<script>
|
|
43
|
+
${jsContents}
|
|
44
|
+
</script>
|
|
45
|
+
</body>
|
|
46
|
+
</html>`;
|
|
47
|
+
}
|
|
48
|
+
function createAppServer(config) {
|
|
49
|
+
const {
|
|
50
|
+
name = "sunpeak-app",
|
|
51
|
+
version = "0.1.0",
|
|
52
|
+
toolName = "show-app",
|
|
53
|
+
toolDescription = "Show the app",
|
|
54
|
+
dummyData = {}
|
|
55
|
+
} = config;
|
|
56
|
+
const widgetHtml = readWidgetHtml(config.distPath);
|
|
57
|
+
const toolInputSchema = {
|
|
58
|
+
type: "object",
|
|
59
|
+
properties: {},
|
|
60
|
+
additionalProperties: false
|
|
61
|
+
};
|
|
62
|
+
const toolInputParser = z.object({});
|
|
63
|
+
const tool = {
|
|
64
|
+
name: toolName,
|
|
65
|
+
description: toolDescription,
|
|
66
|
+
inputSchema: toolInputSchema,
|
|
67
|
+
title: toolDescription,
|
|
68
|
+
_meta: widgetDescriptorMeta(),
|
|
69
|
+
annotations: {
|
|
70
|
+
destructiveHint: false,
|
|
71
|
+
openWorldHint: false,
|
|
72
|
+
readOnlyHint: true
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
const resource = {
|
|
76
|
+
uri: "ui://widget/app.html",
|
|
77
|
+
name: toolDescription,
|
|
78
|
+
description: `${toolDescription} widget markup`,
|
|
79
|
+
mimeType: "text/html+skybridge",
|
|
80
|
+
_meta: widgetDescriptorMeta()
|
|
81
|
+
};
|
|
82
|
+
const server = new Server(
|
|
83
|
+
{
|
|
84
|
+
name,
|
|
85
|
+
version
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
capabilities: {
|
|
89
|
+
resources: {},
|
|
90
|
+
tools: {}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
);
|
|
94
|
+
server.setRequestHandler(
|
|
95
|
+
ListResourcesRequestSchema,
|
|
96
|
+
async (_request) => {
|
|
97
|
+
console.log("[MCP] ListResources");
|
|
98
|
+
return { resources: [resource] };
|
|
99
|
+
}
|
|
100
|
+
);
|
|
101
|
+
server.setRequestHandler(
|
|
102
|
+
ReadResourceRequestSchema,
|
|
103
|
+
async (request) => {
|
|
104
|
+
console.log("[MCP] ReadResource:", request.params.uri);
|
|
105
|
+
if (request.params.uri !== resource.uri) {
|
|
106
|
+
throw new Error(`Unknown resource: ${request.params.uri}`);
|
|
107
|
+
}
|
|
108
|
+
return {
|
|
109
|
+
contents: [
|
|
110
|
+
{
|
|
111
|
+
uri: resource.uri,
|
|
112
|
+
mimeType: "text/html+skybridge",
|
|
113
|
+
text: widgetHtml,
|
|
114
|
+
_meta: widgetDescriptorMeta()
|
|
115
|
+
}
|
|
116
|
+
]
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
);
|
|
120
|
+
server.setRequestHandler(
|
|
121
|
+
ListToolsRequestSchema,
|
|
122
|
+
async (_request) => {
|
|
123
|
+
console.log("[MCP] ListTools");
|
|
124
|
+
return { tools: [tool] };
|
|
125
|
+
}
|
|
126
|
+
);
|
|
127
|
+
server.setRequestHandler(
|
|
128
|
+
CallToolRequestSchema,
|
|
129
|
+
async (request) => {
|
|
130
|
+
console.log("[MCP] CallTool:", request.params.name, request.params.arguments);
|
|
131
|
+
if (request.params.name !== toolName) {
|
|
132
|
+
throw new Error(`Unknown tool: ${request.params.name}`);
|
|
133
|
+
}
|
|
134
|
+
toolInputParser.parse(request.params.arguments ?? {});
|
|
135
|
+
return {
|
|
136
|
+
content: [
|
|
137
|
+
{
|
|
138
|
+
type: "text",
|
|
139
|
+
text: `Rendered ${toolDescription}!`
|
|
140
|
+
}
|
|
141
|
+
],
|
|
142
|
+
structuredContent: dummyData,
|
|
143
|
+
_meta: widgetInvocationMeta()
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
);
|
|
147
|
+
return server;
|
|
148
|
+
}
|
|
149
|
+
var sessions = /* @__PURE__ */ new Map();
|
|
150
|
+
var ssePath = "/mcp";
|
|
151
|
+
var postPath = "/mcp/messages";
|
|
152
|
+
async function handleSseRequest(res, config) {
|
|
153
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
154
|
+
const server = createAppServer(config);
|
|
155
|
+
const transport = new SSEServerTransport(postPath, res);
|
|
156
|
+
const sessionId = transport.sessionId;
|
|
157
|
+
sessions.set(sessionId, { server, transport });
|
|
158
|
+
transport.onclose = async () => {
|
|
159
|
+
sessions.delete(sessionId);
|
|
160
|
+
await server.close();
|
|
161
|
+
};
|
|
162
|
+
transport.onerror = (error) => {
|
|
163
|
+
console.error("SSE transport error", error);
|
|
164
|
+
};
|
|
165
|
+
try {
|
|
166
|
+
await server.connect(transport);
|
|
167
|
+
} catch (error) {
|
|
168
|
+
sessions.delete(sessionId);
|
|
169
|
+
console.error("Failed to start SSE session", error);
|
|
170
|
+
if (!res.headersSent) {
|
|
171
|
+
res.writeHead(500).end("Failed to establish SSE connection");
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
async function handlePostMessage(req, res, url) {
|
|
176
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
177
|
+
res.setHeader("Access-Control-Allow-Headers", "content-type");
|
|
178
|
+
const sessionId = url.searchParams.get("sessionId");
|
|
179
|
+
if (!sessionId) {
|
|
180
|
+
res.writeHead(400).end("Missing sessionId query parameter");
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
const session = sessions.get(sessionId);
|
|
184
|
+
if (!session) {
|
|
185
|
+
res.writeHead(404).end("Unknown session");
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
try {
|
|
189
|
+
await session.transport.handlePostMessage(req, res);
|
|
190
|
+
} catch (error) {
|
|
191
|
+
console.error("Failed to process message", error);
|
|
192
|
+
if (!res.headersSent) {
|
|
193
|
+
res.writeHead(500).end("Failed to process message");
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
function runMCPServer(config) {
|
|
198
|
+
const portEnv = Number(process.env.PORT ?? 6766);
|
|
199
|
+
const port = config.port ?? (Number.isFinite(portEnv) ? portEnv : 6766);
|
|
200
|
+
const httpServer = createServer(
|
|
201
|
+
async (req, res) => {
|
|
202
|
+
if (!req.url) {
|
|
203
|
+
res.writeHead(400).end("Missing URL");
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
const url = new URL(req.url, `http://${req.headers.host ?? "localhost"}`);
|
|
207
|
+
console.log(`[HTTP] ${req.method} ${url.pathname}`);
|
|
208
|
+
if (req.method === "OPTIONS" && (url.pathname === ssePath || url.pathname === postPath)) {
|
|
209
|
+
res.writeHead(204, {
|
|
210
|
+
"Access-Control-Allow-Origin": "*",
|
|
211
|
+
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
|
|
212
|
+
"Access-Control-Allow-Headers": "content-type"
|
|
213
|
+
});
|
|
214
|
+
res.end();
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
if (req.method === "GET" && url.pathname === ssePath) {
|
|
218
|
+
await handleSseRequest(res, config);
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
if (req.method === "POST" && url.pathname === postPath) {
|
|
222
|
+
await handlePostMessage(req, res, url);
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
res.writeHead(404).end("Not Found");
|
|
226
|
+
}
|
|
227
|
+
);
|
|
228
|
+
httpServer.on("clientError", (err, socket) => {
|
|
229
|
+
console.error("HTTP client error", err);
|
|
230
|
+
socket.end("HTTP/1.1 400 Bad Request\r\n\r\n");
|
|
231
|
+
});
|
|
232
|
+
httpServer.listen(port, () => {
|
|
233
|
+
console.log(`Sunpeak MCP server listening on http://localhost:${port}`);
|
|
234
|
+
console.log(` SSE stream: GET http://localhost:${port}${ssePath}`);
|
|
235
|
+
console.log(
|
|
236
|
+
` Message post endpoint: POST http://localhost:${port}${postPath}?sessionId=...`
|
|
237
|
+
);
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
export { runMCPServer };
|
|
242
|
+
//# sourceMappingURL=index.js.map
|
|
243
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/mcp/server.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import {\n createServer,\n type IncomingMessage,\n type ServerResponse,\n} from \"node:http\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport { URL } from \"node:url\";\n\nimport { Server } from \"@modelcontextprotocol/sdk/server/index.js\";\nimport { SSEServerTransport } from \"@modelcontextprotocol/sdk/server/sse.js\";\nimport {\n CallToolRequestSchema,\n ListResourcesRequestSchema,\n ListToolsRequestSchema,\n ReadResourceRequestSchema,\n type CallToolRequest,\n type ListResourcesRequest,\n type ListToolsRequest,\n type ReadResourceRequest,\n type Resource,\n type Tool,\n} from \"@modelcontextprotocol/sdk/types.js\";\nimport { z } from \"zod\";\n\nexport interface MCPServerConfig {\n name?: string;\n version?: string;\n port?: number;\n distPath: string;\n toolName?: string;\n toolDescription?: string;\n dummyData?: Record<string, unknown>;\n}\n\nfunction widgetDescriptorMeta() {\n return {\n \"openai/outputTemplate\": \"ui://widget/app.html\",\n \"openai/toolInvocation/invoking\": \"Loading your app\",\n \"openai/toolInvocation/invoked\": \"App loaded\",\n \"openai/widgetAccessible\": true,\n \"openai/resultCanProduceWidget\": true,\n } as const;\n}\n\nfunction widgetInvocationMeta() {\n return {\n \"openai/toolInvocation/invoking\": \"Loading your app\",\n \"openai/toolInvocation/invoked\": \"App loaded\",\n } as const;\n}\n\nfunction readWidgetHtml(distPath: string): string {\n const htmlPath = path.resolve(distPath);\n\n if (!fs.existsSync(htmlPath)) {\n throw new Error(\n `Widget HTML not found at ${htmlPath}. Run \"pnpm build\" to generate the built app.`\n );\n }\n\n const jsContents = fs.readFileSync(htmlPath, \"utf8\");\n\n // Wrap the JS in a minimal HTML shell suitable for ChatGPT\n // Styles should already be bundled in the JS by the template's build process\n return `<!DOCTYPE html>\n<html>\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n</head>\n<body>\n <div id=\"root\"></div>\n <script>\n${jsContents}\n </script>\n</body>\n</html>`;\n}\n\nfunction createAppServer(config: MCPServerConfig): Server {\n const {\n name = \"sunpeak-app\",\n version = \"0.1.0\",\n toolName = \"show-app\",\n toolDescription = \"Show the app\",\n dummyData = {},\n } = config;\n\n const widgetHtml = readWidgetHtml(config.distPath);\n\n const toolInputSchema = {\n type: \"object\",\n properties: {},\n additionalProperties: false,\n } as const;\n\n const toolInputParser = z.object({});\n\n const tool: Tool = {\n name: toolName,\n description: toolDescription,\n inputSchema: toolInputSchema,\n title: toolDescription,\n _meta: widgetDescriptorMeta(),\n annotations: {\n destructiveHint: false,\n openWorldHint: false,\n readOnlyHint: true,\n },\n };\n\n const resource: Resource = {\n uri: \"ui://widget/app.html\",\n name: toolDescription,\n description: `${toolDescription} widget markup`,\n mimeType: \"text/html+skybridge\",\n _meta: widgetDescriptorMeta(),\n };\n\n const server = new Server(\n {\n name,\n version,\n },\n {\n capabilities: {\n resources: {},\n tools: {},\n },\n }\n );\n\n server.setRequestHandler(\n ListResourcesRequestSchema,\n async (_request: ListResourcesRequest) => {\n console.log(\"[MCP] ListResources\");\n return { resources: [resource] };\n }\n );\n\n server.setRequestHandler(\n ReadResourceRequestSchema,\n async (request: ReadResourceRequest) => {\n console.log(\"[MCP] ReadResource:\", request.params.uri);\n if (request.params.uri !== resource.uri) {\n throw new Error(`Unknown resource: ${request.params.uri}`);\n }\n\n return {\n contents: [\n {\n uri: resource.uri,\n mimeType: \"text/html+skybridge\",\n text: widgetHtml,\n _meta: widgetDescriptorMeta(),\n },\n ],\n };\n }\n );\n\n server.setRequestHandler(\n ListToolsRequestSchema,\n async (_request: ListToolsRequest) => {\n console.log(\"[MCP] ListTools\");\n return { tools: [tool] };\n }\n );\n\n server.setRequestHandler(\n CallToolRequestSchema,\n async (request: CallToolRequest) => {\n console.log(\"[MCP] CallTool:\", request.params.name, request.params.arguments);\n if (request.params.name !== toolName) {\n throw new Error(`Unknown tool: ${request.params.name}`);\n }\n\n toolInputParser.parse(request.params.arguments ?? {});\n\n return {\n content: [\n {\n type: \"text\",\n text: `Rendered ${toolDescription}!`,\n },\n ],\n structuredContent: dummyData,\n _meta: widgetInvocationMeta(),\n };\n }\n );\n\n return server;\n}\n\ntype SessionRecord = {\n server: Server;\n transport: SSEServerTransport;\n};\n\nconst sessions = new Map<string, SessionRecord>();\n\nconst ssePath = \"/mcp\";\nconst postPath = \"/mcp/messages\";\n\nasync function handleSseRequest(\n res: ServerResponse,\n config: MCPServerConfig\n) {\n res.setHeader(\"Access-Control-Allow-Origin\", \"*\");\n const server = createAppServer(config);\n const transport = new SSEServerTransport(postPath, res);\n const sessionId = transport.sessionId;\n\n sessions.set(sessionId, { server, transport });\n\n transport.onclose = async () => {\n sessions.delete(sessionId);\n await server.close();\n };\n\n transport.onerror = (error) => {\n console.error(\"SSE transport error\", error);\n };\n\n try {\n await server.connect(transport);\n } catch (error) {\n sessions.delete(sessionId);\n console.error(\"Failed to start SSE session\", error);\n if (!res.headersSent) {\n res.writeHead(500).end(\"Failed to establish SSE connection\");\n }\n }\n}\n\nasync function handlePostMessage(\n req: IncomingMessage,\n res: ServerResponse,\n url: URL\n) {\n res.setHeader(\"Access-Control-Allow-Origin\", \"*\");\n res.setHeader(\"Access-Control-Allow-Headers\", \"content-type\");\n const sessionId = url.searchParams.get(\"sessionId\");\n\n if (!sessionId) {\n res.writeHead(400).end(\"Missing sessionId query parameter\");\n return;\n }\n\n const session = sessions.get(sessionId);\n\n if (!session) {\n res.writeHead(404).end(\"Unknown session\");\n return;\n }\n\n try {\n await session.transport.handlePostMessage(req, res);\n } catch (error) {\n console.error(\"Failed to process message\", error);\n if (!res.headersSent) {\n res.writeHead(500).end(\"Failed to process message\");\n }\n }\n}\n\nexport function runMCPServer(config: MCPServerConfig): void {\n const portEnv = Number(process.env.PORT ?? 6766);\n const port = config.port ?? (Number.isFinite(portEnv) ? portEnv : 6766);\n\n const httpServer = createServer(\n async (req: IncomingMessage, res: ServerResponse) => {\n if (!req.url) {\n res.writeHead(400).end(\"Missing URL\");\n return;\n }\n\n const url = new URL(req.url, `http://${req.headers.host ?? \"localhost\"}`);\n console.log(`[HTTP] ${req.method} ${url.pathname}`);\n\n if (\n req.method === \"OPTIONS\" &&\n (url.pathname === ssePath || url.pathname === postPath)\n ) {\n res.writeHead(204, {\n \"Access-Control-Allow-Origin\": \"*\",\n \"Access-Control-Allow-Methods\": \"GET, POST, OPTIONS\",\n \"Access-Control-Allow-Headers\": \"content-type\",\n });\n res.end();\n return;\n }\n\n if (req.method === \"GET\" && url.pathname === ssePath) {\n await handleSseRequest(res, config);\n return;\n }\n\n if (req.method === \"POST\" && url.pathname === postPath) {\n await handlePostMessage(req, res, url);\n return;\n }\n\n res.writeHead(404).end(\"Not Found\");\n }\n );\n\n httpServer.on(\"clientError\", (err: Error, socket) => {\n console.error(\"HTTP client error\", err);\n socket.end(\"HTTP/1.1 400 Bad Request\\r\\n\\r\\n\");\n });\n\n httpServer.listen(port, () => {\n console.log(`Sunpeak MCP server listening on http://localhost:${port}`);\n console.log(` SSE stream: GET http://localhost:${port}${ssePath}`);\n console.log(\n ` Message post endpoint: POST http://localhost:${port}${postPath}?sessionId=...`\n );\n });\n}\n"]}
|
package/dist/styles/globals.css
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sunpeak",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.5",
|
|
4
4
|
"description": "The ChatGPT Apps UI SDK. Build and test your ChatGPT App UI locally with approved shadcn React components.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -17,6 +17,12 @@
|
|
|
17
17
|
"default": "./dist/index.cjs"
|
|
18
18
|
}
|
|
19
19
|
},
|
|
20
|
+
"./mcp": {
|
|
21
|
+
"import": {
|
|
22
|
+
"types": "./dist/mcp/index.d.ts",
|
|
23
|
+
"default": "./dist/mcp/index.js"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
20
26
|
"./styles/globals.css": "./dist/styles/globals.css",
|
|
21
27
|
"./styles/chatgpt": "./dist/styles/chatgpt/index.css",
|
|
22
28
|
"./package.json": "./package.json"
|
|
@@ -50,6 +56,7 @@
|
|
|
50
56
|
"react-dom": "^18.0.0 || ^19.0.0"
|
|
51
57
|
},
|
|
52
58
|
"dependencies": {
|
|
59
|
+
"@modelcontextprotocol/sdk": "^0.5.0",
|
|
53
60
|
"@radix-ui/react-dialog": "^1.1.15",
|
|
54
61
|
"@radix-ui/react-label": "^2.1.8",
|
|
55
62
|
"@radix-ui/react-select": "^2.2.6",
|
|
@@ -60,7 +67,8 @@
|
|
|
60
67
|
"clsx": "^2.1.1",
|
|
61
68
|
"lucide-react": "^0.554.0",
|
|
62
69
|
"tailwind-merge": "^3.4.0",
|
|
63
|
-
"tw-animate-css": "^1.4.0"
|
|
70
|
+
"tw-animate-css": "^1.4.0",
|
|
71
|
+
"zod": "^3.23.8"
|
|
64
72
|
},
|
|
65
73
|
"devDependencies": {
|
|
66
74
|
"@tailwindcss/vite": "^4.1.17",
|