@pancake-apps/server 0.0.3 → 0.1.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/dist/index.cjs CHANGED
@@ -44,8 +44,30 @@ var __export = (target, all) => {
44
44
  __defProp(target, name, { get: all[name], enumerable: true });
45
45
  };
46
46
 
47
- // src/server/mcp.ts
48
- function visibilityToAudience(visibility) {
47
+ // src/utils/metadata.ts
48
+ function mapVisibilityToMcp(visibility) {
49
+ switch (visibility) {
50
+ case "model":
51
+ return ["model"];
52
+ case "app":
53
+ return ["app"];
54
+ case "both":
55
+ default:
56
+ return ["model", "app"];
57
+ }
58
+ }
59
+ function mapVisibilityToOpenAI(visibility) {
60
+ switch (visibility) {
61
+ case "model":
62
+ return { "openai/visibility": "public", "openai/widgetAccessible": false };
63
+ case "app":
64
+ return { "openai/visibility": "private", "openai/widgetAccessible": true };
65
+ case "both":
66
+ default:
67
+ return { "openai/visibility": "public", "openai/widgetAccessible": true };
68
+ }
69
+ }
70
+ function mapVisibilityToAudience(visibility) {
49
71
  switch (visibility) {
50
72
  case "model":
51
73
  return ["assistant"];
@@ -56,6 +78,201 @@ function visibilityToAudience(visibility) {
56
78
  return ["assistant", "user"];
57
79
  }
58
80
  }
81
+ var init_metadata = __esm({
82
+ "src/utils/metadata.ts"() {
83
+ }
84
+ });
85
+
86
+ // src/utils/csp.ts
87
+ function generateMcpCSPMetadata(csp) {
88
+ const result = {};
89
+ if (csp.connectDomains && csp.connectDomains.length > 0) {
90
+ result.connectDomains = csp.connectDomains;
91
+ }
92
+ if (csp.resourceDomains && csp.resourceDomains.length > 0) {
93
+ result.resourceDomains = csp.resourceDomains;
94
+ }
95
+ if (csp.scriptDomains && csp.scriptDomains.length > 0) {
96
+ result.scriptDomains = csp.scriptDomains;
97
+ }
98
+ return result;
99
+ }
100
+ function generateOpenAICSPMetadata(csp) {
101
+ const result = {};
102
+ if (csp.connectDomains && csp.connectDomains.length > 0) {
103
+ result.connect_domains = csp.connectDomains;
104
+ }
105
+ if (csp.resourceDomains && csp.resourceDomains.length > 0) {
106
+ result.resource_domains = csp.resourceDomains;
107
+ }
108
+ if (csp.scriptDomains && csp.scriptDomains.length > 0) {
109
+ result.script_domains = csp.scriptDomains;
110
+ }
111
+ return result;
112
+ }
113
+ var init_csp = __esm({
114
+ "src/utils/csp.ts"() {
115
+ }
116
+ });
117
+
118
+ // src/adapters/mcp.ts
119
+ var McpAdapter;
120
+ var init_mcp = __esm({
121
+ "src/adapters/mcp.ts"() {
122
+ init_metadata();
123
+ init_csp();
124
+ McpAdapter = class {
125
+ /**
126
+ * Build tool metadata for MCP protocol
127
+ */
128
+ buildToolMeta(tool, uiUri) {
129
+ const visibility = mapVisibilityToMcp(tool.visibility);
130
+ const uiMeta = {
131
+ visibility,
132
+ ...uiUri ? { resourceUri: uiUri } : {}
133
+ };
134
+ const annotations = {
135
+ audience: mapVisibilityToAudience(tool.visibility)
136
+ };
137
+ const meta = {
138
+ ui: uiMeta,
139
+ "ui/visibility": visibility,
140
+ ...uiUri ? { "ui/resourceUri": uiUri } : {}
141
+ };
142
+ return {
143
+ annotations,
144
+ _meta: meta
145
+ };
146
+ }
147
+ /**
148
+ * Build UI resource metadata for MCP protocol
149
+ */
150
+ buildUIResourceMeta(uiDef) {
151
+ const uiMeta = {};
152
+ if (uiDef.csp) {
153
+ const cspMetadata = generateMcpCSPMetadata(uiDef.csp);
154
+ if (Object.keys(cspMetadata).length > 0) {
155
+ uiMeta.csp = cspMetadata;
156
+ }
157
+ }
158
+ if (uiDef.prefersBorder !== void 0) {
159
+ uiMeta.prefersBorder = uiDef.prefersBorder;
160
+ }
161
+ if (uiDef.autoResize !== void 0) {
162
+ uiMeta.autoResize = uiDef.autoResize;
163
+ }
164
+ if (uiDef.domain) {
165
+ uiMeta.domain = uiDef.domain;
166
+ }
167
+ const result = {
168
+ mimeType: "text/html;profile=mcp-app"
169
+ };
170
+ if (Object.keys(uiMeta).length > 0) {
171
+ result._meta = { ui: uiMeta };
172
+ }
173
+ return result;
174
+ }
175
+ };
176
+ }
177
+ });
178
+
179
+ // src/adapters/openai.ts
180
+ var OpenAIAdapter;
181
+ var init_openai = __esm({
182
+ "src/adapters/openai.ts"() {
183
+ init_metadata();
184
+ init_csp();
185
+ OpenAIAdapter = class {
186
+ /**
187
+ * Build tool metadata for OpenAI protocol
188
+ */
189
+ buildToolMeta(tool, uiUri) {
190
+ const meta = {};
191
+ const visibilitySettings = mapVisibilityToOpenAI(tool.visibility);
192
+ Object.assign(meta, visibilitySettings);
193
+ if (uiUri) {
194
+ meta["openai/outputTemplate"] = uiUri;
195
+ }
196
+ const annotations = {
197
+ audience: mapVisibilityToAudience(tool.visibility)
198
+ };
199
+ return {
200
+ annotations,
201
+ _meta: Object.keys(meta).length > 0 ? meta : void 0
202
+ };
203
+ }
204
+ /**
205
+ * Build UI resource metadata for OpenAI protocol
206
+ */
207
+ buildUIResourceMeta(uiDef) {
208
+ const meta = {};
209
+ if (uiDef.csp) {
210
+ const cspMetadata = generateOpenAICSPMetadata(uiDef.csp);
211
+ if (Object.keys(cspMetadata).length > 0) {
212
+ meta["openai/widgetCSP"] = cspMetadata;
213
+ }
214
+ }
215
+ if (uiDef.prefersBorder !== void 0) {
216
+ meta["openai/widgetPrefersBorder"] = uiDef.prefersBorder;
217
+ }
218
+ if (uiDef.domain) {
219
+ meta["openai/widgetDomain"] = uiDef.domain;
220
+ }
221
+ const result = {
222
+ mimeType: "text/html+skybridge"
223
+ };
224
+ if (Object.keys(meta).length > 0) {
225
+ result._meta = meta;
226
+ }
227
+ return result;
228
+ }
229
+ };
230
+ }
231
+ });
232
+
233
+ // src/adapters/index.ts
234
+ function createAdapter(protocol) {
235
+ return protocol === "openai" ? new OpenAIAdapter() : new McpAdapter();
236
+ }
237
+ var init_adapters = __esm({
238
+ "src/adapters/index.ts"() {
239
+ init_mcp();
240
+ init_openai();
241
+ init_mcp();
242
+ init_openai();
243
+ }
244
+ });
245
+
246
+ // src/utils/detect-protocol.ts
247
+ function detectProtocol(req, defaultProtocol = "mcp") {
248
+ const headers = req.headers;
249
+ const accept = getHeader(headers, "accept");
250
+ if (accept && accept.includes("text/html+skybridge")) {
251
+ return "openai";
252
+ }
253
+ const openaiHeader = getHeader(headers, "x-openai-client");
254
+ if (openaiHeader) {
255
+ return "openai";
256
+ }
257
+ const userAgent = getHeader(headers, "user-agent");
258
+ if (userAgent && (userAgent.includes("ChatGPT") || userAgent.includes("OpenAI"))) {
259
+ return "openai";
260
+ }
261
+ return defaultProtocol;
262
+ }
263
+ function getHeader(headers, name) {
264
+ const value = headers[name] || headers[name.toLowerCase()];
265
+ if (Array.isArray(value)) {
266
+ return value[0];
267
+ }
268
+ return value;
269
+ }
270
+ var init_detect_protocol = __esm({
271
+ "src/utils/detect-protocol.ts"() {
272
+ }
273
+ });
274
+
275
+ // src/server/mcp.ts
59
276
  function createMcpHandler(config) {
60
277
  return async (req, res) => {
61
278
  const request = req.body;
@@ -68,7 +285,9 @@ function createMcpHandler(config) {
68
285
  return;
69
286
  }
70
287
  try {
71
- const result = await handleMethod(config, request.method, request.params ?? {});
288
+ const protocol = detectProtocol(req, config.defaultProtocol);
289
+ const adapter = createAdapter(protocol);
290
+ const result = await handleMethod(config, request.method, request.params ?? {}, adapter);
72
291
  const response = {
73
292
  jsonrpc: "2.0",
74
293
  id: request.id,
@@ -89,7 +308,7 @@ function createMcpHandler(config) {
89
308
  }
90
309
  };
91
310
  }
92
- async function handleMethod(config, method, params) {
311
+ async function handleMethod(config, method, params, adapter) {
93
312
  switch (method) {
94
313
  case "initialize":
95
314
  return {
@@ -105,20 +324,17 @@ async function handleMethod(config, method, params) {
105
324
  };
106
325
  case "tools/list":
107
326
  return {
108
- tools: Array.from(config.tools.values()).filter((tool) => tool.visibility !== "app").map((tool) => ({
109
- name: tool.name,
110
- description: tool.description,
111
- inputSchema: tool.inputSchema,
112
- annotations: {
113
- audience: visibilityToAudience(tool.visibility)
114
- },
115
- _meta: tool.ui ? {
116
- ui: {
117
- visibility: visibilityToAudience(tool.visibility),
118
- resourceUri: getUIResourceUri(tool.name, tool.category)
119
- }
120
- } : void 0
121
- }))
327
+ tools: Array.from(config.tools.values()).filter((tool) => tool.visibility !== "app").map((tool) => {
328
+ const uiUri = tool.ui ? getUIResourceUri(tool.name, tool.category) : void 0;
329
+ const meta = adapter.buildToolMeta(tool, uiUri);
330
+ return {
331
+ name: tool.name,
332
+ description: tool.description,
333
+ inputSchema: tool.inputSchema,
334
+ annotations: meta.annotations,
335
+ _meta: tool.ui ? meta._meta : void 0
336
+ };
337
+ })
122
338
  };
123
339
  case "tools/call": {
124
340
  const toolName = params["name"];
@@ -136,18 +352,15 @@ async function handleMethod(config, method, params) {
136
352
  }
137
353
  case "resources/list":
138
354
  return {
139
- resources: Array.from(config.uiResources.values()).map((resource) => ({
140
- uri: resource.uri,
141
- name: resource.name,
142
- mimeType: "text/html;profile=mcp-app",
143
- _meta: {
144
- ui: {
145
- prefersBorder: resource.definition.prefersBorder,
146
- autoResize: resource.definition.autoResize,
147
- csp: resource.definition.csp
148
- }
149
- }
150
- }))
355
+ resources: Array.from(config.uiResources.values()).map((resource) => {
356
+ const meta = adapter.buildUIResourceMeta(resource.definition);
357
+ return {
358
+ uri: resource.uri,
359
+ name: resource.name,
360
+ mimeType: meta.mimeType,
361
+ _meta: meta._meta
362
+ };
363
+ })
151
364
  };
152
365
  case "resources/read": {
153
366
  const uri = params["uri"];
@@ -186,8 +399,10 @@ function getUIResourceUri(toolName, category) {
186
399
  const name = toolName.replace(/^(view|action|data|tool):/, "");
187
400
  return `pancake://ui/${category}/${name}`;
188
401
  }
189
- var init_mcp = __esm({
402
+ var init_mcp2 = __esm({
190
403
  "src/server/mcp.ts"() {
404
+ init_adapters();
405
+ init_detect_protocol();
191
406
  }
192
407
  });
193
408
 
@@ -364,18 +579,8 @@ var stdio_exports = {};
364
579
  __export(stdio_exports, {
365
580
  startStdioServer: () => startStdioServer
366
581
  });
367
- function visibilityToAudience2(visibility) {
368
- switch (visibility) {
369
- case "model":
370
- return ["assistant"];
371
- case "app":
372
- return ["user"];
373
- case "both":
374
- default:
375
- return ["assistant", "user"];
376
- }
377
- }
378
582
  async function startStdioServer(config) {
583
+ const adapter = createAdapter(config.protocol ?? "mcp");
379
584
  const server = new index_js.Server(
380
585
  {
381
586
  name: config.name,
@@ -392,20 +597,17 @@ async function startStdioServer(config) {
392
597
  types_js.ListToolsRequestSchema,
393
598
  async () => {
394
599
  return {
395
- tools: Array.from(config.tools.values()).map((tool) => ({
396
- name: tool.name,
397
- description: tool.description,
398
- inputSchema: tool.inputSchema,
399
- annotations: {
400
- audience: visibilityToAudience2(tool.visibility)
401
- },
402
- _meta: tool.ui ? {
403
- ui: {
404
- visibility: visibilityToAudience2(tool.visibility),
405
- resourceUri: getUIResourceUri2(tool.name, tool.category)
406
- }
407
- } : void 0
408
- }))
600
+ tools: Array.from(config.tools.values()).map((tool) => {
601
+ const uiUri = tool.ui ? getUIResourceUri2(tool.name, tool.category) : void 0;
602
+ const meta = adapter.buildToolMeta(tool, uiUri);
603
+ return {
604
+ name: tool.name,
605
+ description: tool.description,
606
+ inputSchema: tool.inputSchema,
607
+ annotations: meta.annotations,
608
+ _meta: tool.ui ? meta._meta : void 0
609
+ };
610
+ })
409
611
  };
410
612
  }
411
613
  );
@@ -429,18 +631,15 @@ async function startStdioServer(config) {
429
631
  types_js.ListResourcesRequestSchema,
430
632
  async () => {
431
633
  return {
432
- resources: Array.from(config.uiResources.values()).map((resource) => ({
433
- uri: resource.uri,
434
- name: resource.name,
435
- mimeType: "text/html;profile=mcp-app",
436
- _meta: {
437
- ui: {
438
- prefersBorder: resource.definition.prefersBorder,
439
- autoResize: resource.definition.autoResize,
440
- csp: resource.definition.csp
441
- }
442
- }
443
- }))
634
+ resources: Array.from(config.uiResources.values()).map((resource) => {
635
+ const meta = adapter.buildUIResourceMeta(resource.definition);
636
+ return {
637
+ uri: resource.uri,
638
+ name: resource.name,
639
+ mimeType: meta.mimeType,
640
+ _meta: meta._meta
641
+ };
642
+ })
444
643
  };
445
644
  }
446
645
  );
@@ -477,6 +676,7 @@ function getUIResourceUri2(toolName, category) {
477
676
  }
478
677
  var init_stdio = __esm({
479
678
  "src/server/stdio.ts"() {
679
+ init_adapters();
480
680
  }
481
681
  });
482
682
 
@@ -564,7 +764,8 @@ async function createServer(config) {
564
764
  version: config.version,
565
765
  tools: config.tools,
566
766
  uiResources: config.uiResources,
567
- executeTool
767
+ executeTool,
768
+ defaultProtocol: config.config?.protocol
568
769
  });
569
770
  app.post(mcpRoute, mcpHandler);
570
771
  app.get("/health", (_req, res) => {
@@ -577,16 +778,36 @@ async function createServer(config) {
577
778
  version: config.version,
578
779
  tools: config.tools,
579
780
  uiResources: config.uiResources,
580
- executeTool
781
+ executeTool,
782
+ protocol: config.config?.protocol
581
783
  });
582
784
  }
583
785
  return new Promise((resolve3) => {
584
786
  const httpServer = app.listen(config.port, config.host, () => {
585
787
  if (config.config?.debug) {
586
- console.log(`Pancake server "${config.name}" running on http://${config.host}:${config.port}`);
587
- console.log(` MCP endpoint: ${mcpRoute}`);
588
- console.log(` UI endpoint: /ui/:viewName`);
589
- console.log(` Tools registered: ${config.tools.size}`);
788
+ const baseUrl = `http://localhost:${config.port}`;
789
+ const viewNames = config.viewNames ?? [];
790
+ const actionNames = config.actionNames ?? [];
791
+ const dataNames = config.dataNames ?? [];
792
+ console.log("");
793
+ console.log(` \x1B[1m\x1B[32m\u2713\x1B[0m \x1B[1m${config.name}\x1B[0m is running`);
794
+ console.log("");
795
+ console.log(` \x1B[2m\u279C\x1B[0m \x1B[1mServer:\x1B[0m ${baseUrl}`);
796
+ console.log(` \x1B[2m\u279C\x1B[0m \x1B[1mMCP:\x1B[0m ${baseUrl}${mcpRoute}`);
797
+ if (viewNames.length > 0) {
798
+ console.log(` \x1B[2m\u279C\x1B[0m \x1B[1mViews:\x1B[0m ${viewNames.map((v) => `${baseUrl}/ui/${v}`).join("\n ")}`);
799
+ }
800
+ console.log("");
801
+ console.log(` \x1B[2mRegistered:\x1B[0m`);
802
+ console.log(` ${viewNames.length} view${viewNames.length !== 1 ? "s" : ""}${viewNames.length > 0 ? ` (${viewNames.join(", ")})` : ""}`);
803
+ console.log(` ${actionNames.length} action${actionNames.length !== 1 ? "s" : ""}${actionNames.length > 0 ? ` (${actionNames.join(", ")})` : ""}`);
804
+ console.log(` ${dataNames.length} data fetcher${dataNames.length !== 1 ? "s" : ""}${dataNames.length > 0 ? ` (${dataNames.join(", ")})` : ""}`);
805
+ console.log("");
806
+ console.log(` \x1B[2mNext steps:\x1B[0m`);
807
+ console.log(` \x1B[36m1.\x1B[0m Open a view in your browser: ${baseUrl}/ui/${viewNames[0] || "yourView"}`);
808
+ console.log(` \x1B[36m2.\x1B[0m Connect an MCP client to: ${baseUrl}${mcpRoute}`);
809
+ console.log(` \x1B[36m3.\x1B[0m Or use Claude Desktop with this server URL`);
810
+ console.log("");
590
811
  }
591
812
  resolve3({
592
813
  close: () => new Promise((resolveClose) => {
@@ -598,7 +819,7 @@ async function createServer(config) {
598
819
  }
599
820
  var init_server = __esm({
600
821
  "src/server/index.ts"() {
601
- init_mcp();
822
+ init_mcp2();
602
823
  init_routes();
603
824
  init_dev_proxy();
604
825
  }
@@ -838,7 +1059,10 @@ function createApp(config) {
838
1059
  events,
839
1060
  port,
840
1061
  host,
841
- transport
1062
+ transport,
1063
+ viewNames: Object.keys(config.views ?? {}),
1064
+ actionNames: Object.keys(config.actions ?? {}),
1065
+ dataNames: Object.keys(config.data ?? {})
842
1066
  };
843
1067
  if (config.config) {
844
1068
  const cfg = {};
@@ -846,6 +1070,7 @@ function createApp(config) {
846
1070
  if (config.config.debug !== void 0) cfg.debug = config.config.debug;
847
1071
  if (config.config.serverRoute !== void 0) cfg.serverRoute = config.config.serverRoute;
848
1072
  if (config.config.devServer !== void 0) cfg.devServer = config.config.devServer;
1073
+ if (config.config.protocol !== void 0) cfg.protocol = config.config.protocol;
849
1074
  serverConfig.config = cfg;
850
1075
  }
851
1076
  server = await createServer2(serverConfig);