@pancake-apps/server 0.0.4 → 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.
@@ -0,0 +1,172 @@
1
+ // src/utils/metadata.ts
2
+ function mapVisibilityToMcp(visibility) {
3
+ switch (visibility) {
4
+ case "model":
5
+ return ["model"];
6
+ case "app":
7
+ return ["app"];
8
+ case "both":
9
+ default:
10
+ return ["model", "app"];
11
+ }
12
+ }
13
+ function mapVisibilityToOpenAI(visibility) {
14
+ switch (visibility) {
15
+ case "model":
16
+ return { "openai/visibility": "public", "openai/widgetAccessible": false };
17
+ case "app":
18
+ return { "openai/visibility": "private", "openai/widgetAccessible": true };
19
+ case "both":
20
+ default:
21
+ return { "openai/visibility": "public", "openai/widgetAccessible": true };
22
+ }
23
+ }
24
+ function mapVisibilityToAudience(visibility) {
25
+ switch (visibility) {
26
+ case "model":
27
+ return ["assistant"];
28
+ case "app":
29
+ return ["user"];
30
+ case "both":
31
+ default:
32
+ return ["assistant", "user"];
33
+ }
34
+ }
35
+
36
+ // src/utils/csp.ts
37
+ function generateMcpCSPMetadata(csp) {
38
+ const result = {};
39
+ if (csp.connectDomains && csp.connectDomains.length > 0) {
40
+ result.connectDomains = csp.connectDomains;
41
+ }
42
+ if (csp.resourceDomains && csp.resourceDomains.length > 0) {
43
+ result.resourceDomains = csp.resourceDomains;
44
+ }
45
+ if (csp.scriptDomains && csp.scriptDomains.length > 0) {
46
+ result.scriptDomains = csp.scriptDomains;
47
+ }
48
+ return result;
49
+ }
50
+ function generateOpenAICSPMetadata(csp) {
51
+ const result = {};
52
+ if (csp.connectDomains && csp.connectDomains.length > 0) {
53
+ result.connect_domains = csp.connectDomains;
54
+ }
55
+ if (csp.resourceDomains && csp.resourceDomains.length > 0) {
56
+ result.resource_domains = csp.resourceDomains;
57
+ }
58
+ if (csp.scriptDomains && csp.scriptDomains.length > 0) {
59
+ result.script_domains = csp.scriptDomains;
60
+ }
61
+ return result;
62
+ }
63
+
64
+ // src/adapters/mcp.ts
65
+ var McpAdapter = class {
66
+ /**
67
+ * Build tool metadata for MCP protocol
68
+ */
69
+ buildToolMeta(tool, uiUri) {
70
+ const visibility = mapVisibilityToMcp(tool.visibility);
71
+ const uiMeta = {
72
+ visibility,
73
+ ...uiUri ? { resourceUri: uiUri } : {}
74
+ };
75
+ const annotations = {
76
+ audience: mapVisibilityToAudience(tool.visibility)
77
+ };
78
+ const meta = {
79
+ ui: uiMeta,
80
+ "ui/visibility": visibility,
81
+ ...uiUri ? { "ui/resourceUri": uiUri } : {}
82
+ };
83
+ return {
84
+ annotations,
85
+ _meta: meta
86
+ };
87
+ }
88
+ /**
89
+ * Build UI resource metadata for MCP protocol
90
+ */
91
+ buildUIResourceMeta(uiDef) {
92
+ const uiMeta = {};
93
+ if (uiDef.csp) {
94
+ const cspMetadata = generateMcpCSPMetadata(uiDef.csp);
95
+ if (Object.keys(cspMetadata).length > 0) {
96
+ uiMeta.csp = cspMetadata;
97
+ }
98
+ }
99
+ if (uiDef.prefersBorder !== void 0) {
100
+ uiMeta.prefersBorder = uiDef.prefersBorder;
101
+ }
102
+ if (uiDef.autoResize !== void 0) {
103
+ uiMeta.autoResize = uiDef.autoResize;
104
+ }
105
+ if (uiDef.domain) {
106
+ uiMeta.domain = uiDef.domain;
107
+ }
108
+ const result = {
109
+ mimeType: "text/html;profile=mcp-app"
110
+ };
111
+ if (Object.keys(uiMeta).length > 0) {
112
+ result._meta = { ui: uiMeta };
113
+ }
114
+ return result;
115
+ }
116
+ };
117
+
118
+ // src/adapters/openai.ts
119
+ var OpenAIAdapter = class {
120
+ /**
121
+ * Build tool metadata for OpenAI protocol
122
+ */
123
+ buildToolMeta(tool, uiUri) {
124
+ const meta = {};
125
+ const visibilitySettings = mapVisibilityToOpenAI(tool.visibility);
126
+ Object.assign(meta, visibilitySettings);
127
+ if (uiUri) {
128
+ meta["openai/outputTemplate"] = uiUri;
129
+ }
130
+ const annotations = {
131
+ audience: mapVisibilityToAudience(tool.visibility)
132
+ };
133
+ return {
134
+ annotations,
135
+ _meta: Object.keys(meta).length > 0 ? meta : void 0
136
+ };
137
+ }
138
+ /**
139
+ * Build UI resource metadata for OpenAI protocol
140
+ */
141
+ buildUIResourceMeta(uiDef) {
142
+ const meta = {};
143
+ if (uiDef.csp) {
144
+ const cspMetadata = generateOpenAICSPMetadata(uiDef.csp);
145
+ if (Object.keys(cspMetadata).length > 0) {
146
+ meta["openai/widgetCSP"] = cspMetadata;
147
+ }
148
+ }
149
+ if (uiDef.prefersBorder !== void 0) {
150
+ meta["openai/widgetPrefersBorder"] = uiDef.prefersBorder;
151
+ }
152
+ if (uiDef.domain) {
153
+ meta["openai/widgetDomain"] = uiDef.domain;
154
+ }
155
+ const result = {
156
+ mimeType: "text/html+skybridge"
157
+ };
158
+ if (Object.keys(meta).length > 0) {
159
+ result._meta = meta;
160
+ }
161
+ return result;
162
+ }
163
+ };
164
+
165
+ // src/adapters/index.ts
166
+ function createAdapter(protocol) {
167
+ return protocol === "openai" ? new OpenAIAdapter() : new McpAdapter();
168
+ }
169
+
170
+ export { createAdapter };
171
+ //# sourceMappingURL=chunk-CVG3DAN6.js.map
172
+ //# sourceMappingURL=chunk-CVG3DAN6.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/utils/metadata.ts","../src/utils/csp.ts","../src/adapters/mcp.ts","../src/adapters/openai.ts","../src/adapters/index.ts"],"names":[],"mappings":"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","file":"chunk-CVG3DAN6.js","sourcesContent":["/**\n * Protocol metadata utilities\n *\n * Provides utilities for mapping tool definitions to protocol-specific metadata\n * formats for MCP Apps and OpenAI/ChatGPT Apps.\n */\n\nimport type { Visibility } from '../types/protocol.js';\n\n// =============================================================================\n// MCP VISIBILITY MAPPING\n// =============================================================================\n\n/**\n * MCP visibility value\n * Array of who can invoke: \"model\", \"app\", or both\n */\nexport type McpVisibilityValue = ('model' | 'app')[];\n\n/**\n * Map visibility to MCP protocol format\n *\n * Visibility mappings:\n * - \"both\" (default): [\"model\", \"app\"] - Tool accessible to both model and UI\n * - \"model\": [\"model\"] - Tool only accessible to the model\n * - \"app\": [\"app\"] - Tool only accessible to the UI\n */\nexport function mapVisibilityToMcp(visibility?: Visibility): McpVisibilityValue {\n switch (visibility) {\n case 'model':\n return ['model'];\n case 'app':\n return ['app'];\n case 'both':\n default:\n return ['model', 'app'];\n }\n}\n\n// =============================================================================\n// OPENAI VISIBILITY MAPPING\n// =============================================================================\n\n/**\n * OpenAI visibility settings with proper openai/ prefixed keys\n *\n * Settings:\n * - openai/visibility: \"public\" | \"private\" - Controls model access\n * - openai/widgetAccessible: boolean - Controls UI access\n */\nexport interface OpenAIVisibilitySettings {\n 'openai/visibility': 'public' | 'private';\n 'openai/widgetAccessible': boolean;\n}\n\n/**\n * Map visibility to OpenAI/ChatGPT protocol settings\n *\n * Visibility mappings:\n * - \"both\" (default): visibility=\"public\", widgetAccessible=true\n * - \"model\": visibility=\"public\", widgetAccessible=false\n * - \"app\": visibility=\"private\", widgetAccessible=true\n */\nexport function mapVisibilityToOpenAI(visibility?: Visibility): OpenAIVisibilitySettings {\n switch (visibility) {\n case 'model':\n return { 'openai/visibility': 'public', 'openai/widgetAccessible': false };\n case 'app':\n return { 'openai/visibility': 'private', 'openai/widgetAccessible': true };\n case 'both':\n default:\n return { 'openai/visibility': 'public', 'openai/widgetAccessible': true };\n }\n}\n\n// =============================================================================\n// MCP AUDIENCE MAPPING (for annotations)\n// =============================================================================\n\n/**\n * Map visibility to MCP audience format for annotations\n *\n * Visibility mappings:\n * - \"both\" (default): [\"assistant\", \"user\"]\n * - \"model\": [\"assistant\"]\n * - \"app\": [\"user\"]\n */\nexport function mapVisibilityToAudience(visibility?: Visibility): string[] {\n switch (visibility) {\n case 'model':\n return ['assistant'];\n case 'app':\n return ['user'];\n case 'both':\n default:\n return ['assistant', 'user'];\n }\n}\n","/**\n * CSP (Content Security Policy) utilities\n *\n * Provides utilities for mapping CSP configuration to protocol-specific formats\n * for MCP Apps and OpenAI/ChatGPT Apps.\n */\n\nimport type { ContentSecurityPolicy } from '../types/protocol.js';\n\n// =============================================================================\n// MCP CSP METADATA\n// =============================================================================\n\n/**\n * MCP CSP metadata format (camelCase)\n */\nexport interface McpCSPMetadata {\n connectDomains?: string[];\n resourceDomains?: string[];\n scriptDomains?: string[];\n}\n\n/**\n * Generate CSP metadata for MCP Apps protocol\n *\n * MCP Apps use camelCase field names.\n */\nexport function generateMcpCSPMetadata(csp: ContentSecurityPolicy): McpCSPMetadata {\n const result: McpCSPMetadata = {};\n\n if (csp.connectDomains && csp.connectDomains.length > 0) {\n result.connectDomains = csp.connectDomains;\n }\n\n if (csp.resourceDomains && csp.resourceDomains.length > 0) {\n result.resourceDomains = csp.resourceDomains;\n }\n\n if (csp.scriptDomains && csp.scriptDomains.length > 0) {\n result.scriptDomains = csp.scriptDomains;\n }\n\n return result;\n}\n\n// =============================================================================\n// OPENAI CSP METADATA\n// =============================================================================\n\n/**\n * OpenAI CSP metadata format (snake_case)\n */\nexport interface OpenAICSPMetadata {\n connect_domains?: string[];\n resource_domains?: string[];\n script_domains?: string[];\n}\n\n/**\n * Generate CSP metadata for OpenAI/ChatGPT Apps protocol\n *\n * ChatGPT Apps use snake_case field names.\n */\nexport function generateOpenAICSPMetadata(csp: ContentSecurityPolicy): OpenAICSPMetadata {\n const result: OpenAICSPMetadata = {};\n\n if (csp.connectDomains && csp.connectDomains.length > 0) {\n result.connect_domains = csp.connectDomains;\n }\n\n if (csp.resourceDomains && csp.resourceDomains.length > 0) {\n result.resource_domains = csp.resourceDomains;\n }\n\n if (csp.scriptDomains && csp.scriptDomains.length > 0) {\n result.script_domains = csp.scriptDomains;\n }\n\n return result;\n}\n","/**\n * MCP Protocol Adapter\n *\n * Handles metadata generation for MCP Apps protocol.\n * Uses camelCase naming and _meta.ui.* namespace.\n */\n\nimport type { InternalTool } from '../types/tool.js';\nimport type { UIDefinition } from '../types/protocol.js';\nimport type { ProtocolAdapter, ToolMetaResult, UIResourceMetaResult } from './types.js';\nimport { mapVisibilityToMcp, mapVisibilityToAudience } from '../utils/metadata.js';\nimport { generateMcpCSPMetadata } from '../utils/csp.js';\n\n// =============================================================================\n// MCP ADAPTER\n// =============================================================================\n\n/**\n * MCP protocol adapter implementation\n *\n * Generates metadata in MCP Apps format:\n * - Visibility as array: [\"model\"], [\"app\"], or [\"model\", \"app\"]\n * - Metadata under _meta.ui.* namespace\n * - MIME type: text/html;profile=mcp-app\n * - CSP with camelCase keys (connectDomains, resourceDomains)\n */\nexport class McpAdapter implements ProtocolAdapter {\n /**\n * Build tool metadata for MCP protocol\n */\n buildToolMeta(tool: InternalTool, uiUri?: string): ToolMetaResult {\n const visibility = mapVisibilityToMcp(tool.visibility);\n const uiMeta: Record<string, unknown> = {\n visibility,\n ...(uiUri ? { resourceUri: uiUri } : {}),\n };\n\n // Build annotations with audience\n const annotations: Record<string, unknown> = {\n audience: mapVisibilityToAudience(tool.visibility),\n };\n\n // Provide both nested and flat shapes for compatibility:\n // - ext-apps / MCP Apps uses nested `_meta.ui.resourceUri`\n // - Some inspectors use flat `_meta[\"ui/resourceUri\"]` key\n const meta: Record<string, unknown> = {\n ui: uiMeta,\n 'ui/visibility': visibility,\n ...(uiUri ? { 'ui/resourceUri': uiUri } : {}),\n };\n\n return {\n annotations,\n _meta: meta,\n };\n }\n\n /**\n * Build UI resource metadata for MCP protocol\n */\n buildUIResourceMeta(uiDef: UIDefinition): UIResourceMetaResult {\n const uiMeta: Record<string, unknown> = {};\n\n // Add CSP metadata if specified\n if (uiDef.csp) {\n const cspMetadata = generateMcpCSPMetadata(uiDef.csp);\n if (Object.keys(cspMetadata).length > 0) {\n uiMeta.csp = cspMetadata;\n }\n }\n\n // Add prefersBorder if specified\n if (uiDef.prefersBorder !== undefined) {\n uiMeta.prefersBorder = uiDef.prefersBorder;\n }\n\n // Add autoResize if specified\n if (uiDef.autoResize !== undefined) {\n uiMeta.autoResize = uiDef.autoResize;\n }\n\n // Add domain if specified\n if (uiDef.domain) {\n uiMeta.domain = uiDef.domain;\n }\n\n const result: UIResourceMetaResult = {\n mimeType: 'text/html;profile=mcp-app',\n };\n\n if (Object.keys(uiMeta).length > 0) {\n result._meta = { ui: uiMeta };\n }\n\n return result;\n }\n}\n","/**\n * OpenAI Protocol Adapter\n *\n * Handles metadata generation for OpenAI/ChatGPT Apps protocol.\n * Uses snake_case naming and openai/* prefixed keys.\n */\n\nimport type { InternalTool } from '../types/tool.js';\nimport type { UIDefinition } from '../types/protocol.js';\nimport type { ProtocolAdapter, ToolMetaResult, UIResourceMetaResult } from './types.js';\nimport { mapVisibilityToOpenAI, mapVisibilityToAudience } from '../utils/metadata.js';\nimport { generateOpenAICSPMetadata } from '../utils/csp.js';\n\n// =============================================================================\n// OPENAI ADAPTER\n// =============================================================================\n\n/**\n * OpenAI protocol adapter implementation\n *\n * Generates metadata in OpenAI/ChatGPT Apps format:\n * - Visibility as openai/visibility (\"public\"/\"private\") + openai/widgetAccessible\n * - Metadata with openai/* prefixed keys\n * - MIME type: text/html+skybridge\n * - CSP with snake_case keys (connect_domains, resource_domains, etc.)\n */\nexport class OpenAIAdapter implements ProtocolAdapter {\n /**\n * Build tool metadata for OpenAI protocol\n */\n buildToolMeta(tool: InternalTool, uiUri?: string): ToolMetaResult {\n const meta: Record<string, unknown> = {};\n\n // Add visibility settings with openai/ prefixes\n const visibilitySettings = mapVisibilityToOpenAI(tool.visibility);\n Object.assign(meta, visibilitySettings);\n\n // Add UI binding with OpenAI prefix\n if (uiUri) {\n meta['openai/outputTemplate'] = uiUri;\n }\n\n // Build annotations with audience (same as MCP)\n const annotations: Record<string, unknown> = {\n audience: mapVisibilityToAudience(tool.visibility),\n };\n\n return {\n annotations,\n _meta: Object.keys(meta).length > 0 ? meta : undefined,\n };\n }\n\n /**\n * Build UI resource metadata for OpenAI protocol\n */\n buildUIResourceMeta(uiDef: UIDefinition): UIResourceMetaResult {\n const meta: Record<string, unknown> = {};\n\n // Add CSP metadata with openai/ prefix\n if (uiDef.csp) {\n const cspMetadata = generateOpenAICSPMetadata(uiDef.csp);\n if (Object.keys(cspMetadata).length > 0) {\n meta['openai/widgetCSP'] = cspMetadata;\n }\n }\n\n // Add prefersBorder with openai/ prefix\n if (uiDef.prefersBorder !== undefined) {\n meta['openai/widgetPrefersBorder'] = uiDef.prefersBorder;\n }\n\n // Add domain with openai/ prefix\n if (uiDef.domain) {\n meta['openai/widgetDomain'] = uiDef.domain;\n }\n\n const result: UIResourceMetaResult = {\n mimeType: 'text/html+skybridge',\n };\n\n if (Object.keys(meta).length > 0) {\n result._meta = meta;\n }\n\n return result;\n }\n}\n","/**\n * Protocol adapters module\n *\n * Provides adapter factory and exports for protocol-specific metadata generation.\n */\n\nimport type { Protocol } from '../types/protocol.js';\nimport type { ProtocolAdapter } from './types.js';\nimport { McpAdapter } from './mcp.js';\nimport { OpenAIAdapter } from './openai.js';\n\n// =============================================================================\n// ADAPTER FACTORY\n// =============================================================================\n\n/**\n * Create a protocol adapter for the specified protocol\n *\n * @param protocol - Target protocol (\"mcp\" | \"openai\")\n * @returns Protocol-specific adapter instance\n *\n * @example\n * ```typescript\n * const adapter = createAdapter(\"openai\");\n * const toolMeta = adapter.buildToolMeta(tool, uiUri);\n * ```\n */\nexport function createAdapter(protocol: Protocol): ProtocolAdapter {\n return protocol === 'openai' ? new OpenAIAdapter() : new McpAdapter();\n}\n\n// =============================================================================\n// EXPORTS\n// =============================================================================\n\nexport type { ProtocolAdapter, ToolMetaResult, UIResourceMetaResult } from './types.js';\nexport { McpAdapter } from './mcp.js';\nexport { OpenAIAdapter } from './openai.js';\n"]}
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,7 +778,8 @@ 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) => {
@@ -617,7 +819,7 @@ async function createServer(config) {
617
819
  }
618
820
  var init_server = __esm({
619
821
  "src/server/index.ts"() {
620
- init_mcp();
822
+ init_mcp2();
621
823
  init_routes();
622
824
  init_dev_proxy();
623
825
  }
@@ -868,6 +1070,7 @@ function createApp(config) {
868
1070
  if (config.config.debug !== void 0) cfg.debug = config.config.debug;
869
1071
  if (config.config.serverRoute !== void 0) cfg.serverRoute = config.config.serverRoute;
870
1072
  if (config.config.devServer !== void 0) cfg.devServer = config.config.devServer;
1073
+ if (config.config.protocol !== void 0) cfg.protocol = config.config.protocol;
871
1074
  serverConfig.config = cfg;
872
1075
  }
873
1076
  server = await createServer2(serverConfig);