@amalgm/chat 0.1.2 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (286) hide show
  1. package/AGENTS.md +98 -0
  2. package/PURPOSE.md +143 -112
  3. package/README.md +87 -54
  4. package/dist/acp/index.d.ts +7 -0
  5. package/dist/acp/index.d.ts.map +1 -0
  6. package/dist/acp/index.js +7 -0
  7. package/dist/acp/index.js.map +1 -0
  8. package/dist/acp/projection.d.ts +30 -0
  9. package/dist/acp/projection.d.ts.map +1 -0
  10. package/dist/acp/projection.js +147 -0
  11. package/dist/acp/projection.js.map +1 -0
  12. package/dist/api/conversations.d.ts +66 -0
  13. package/dist/api/conversations.d.ts.map +1 -0
  14. package/dist/api/conversations.js +202 -0
  15. package/dist/api/conversations.js.map +1 -0
  16. package/dist/api/index.d.ts +3 -3
  17. package/dist/api/index.d.ts.map +1 -1
  18. package/dist/api/index.js +1 -2
  19. package/dist/api/index.js.map +1 -1
  20. package/dist/conversations/index.d.ts +3 -0
  21. package/dist/conversations/index.d.ts.map +1 -0
  22. package/dist/conversations/index.js +3 -0
  23. package/dist/conversations/index.js.map +1 -0
  24. package/dist/conversations/transcript.d.ts +13 -0
  25. package/dist/conversations/transcript.d.ts.map +1 -0
  26. package/dist/conversations/transcript.js +171 -0
  27. package/dist/conversations/transcript.js.map +1 -0
  28. package/dist/conversations/types.d.ts +147 -0
  29. package/dist/conversations/types.d.ts.map +1 -0
  30. package/dist/conversations/types.js +9 -0
  31. package/dist/conversations/types.js.map +1 -0
  32. package/dist/events/constructors.d.ts +9 -15
  33. package/dist/events/constructors.d.ts.map +1 -1
  34. package/dist/events/constructors.js +6 -12
  35. package/dist/events/constructors.js.map +1 -1
  36. package/dist/events/index.d.ts +2 -3
  37. package/dist/events/index.d.ts.map +1 -1
  38. package/dist/events/index.js +2 -3
  39. package/dist/events/index.js.map +1 -1
  40. package/dist/events/schema.d.ts +2 -5
  41. package/dist/events/schema.d.ts.map +1 -1
  42. package/dist/events/schema.js +2 -5
  43. package/dist/events/schema.js.map +1 -1
  44. package/dist/execution/contract.d.ts +51 -0
  45. package/dist/execution/contract.d.ts.map +1 -0
  46. package/dist/execution/contract.js +127 -0
  47. package/dist/execution/contract.js.map +1 -0
  48. package/dist/execution/index.d.ts +6 -0
  49. package/dist/execution/index.d.ts.map +1 -0
  50. package/dist/execution/index.js +4 -0
  51. package/dist/execution/index.js.map +1 -0
  52. package/dist/execution/revision.d.ts +5 -0
  53. package/dist/execution/revision.d.ts.map +1 -0
  54. package/dist/execution/revision.js +17 -0
  55. package/dist/execution/revision.js.map +1 -0
  56. package/dist/execution/store.d.ts +26 -0
  57. package/dist/execution/store.d.ts.map +1 -0
  58. package/dist/execution/store.js +38 -0
  59. package/dist/execution/store.js.map +1 -0
  60. package/dist/index.d.ts +8 -25
  61. package/dist/index.d.ts.map +1 -1
  62. package/dist/index.js +7 -25
  63. package/dist/index.js.map +1 -1
  64. package/dist/input/index.d.ts +3 -0
  65. package/dist/input/index.d.ts.map +1 -0
  66. package/dist/input/index.js +2 -0
  67. package/dist/input/index.js.map +1 -0
  68. package/dist/input/prompt.d.ts +17 -0
  69. package/dist/input/prompt.d.ts.map +1 -0
  70. package/dist/input/prompt.js +104 -0
  71. package/dist/input/prompt.js.map +1 -0
  72. package/dist/normalizers/claude.d.ts +6 -9
  73. package/dist/normalizers/claude.d.ts.map +1 -1
  74. package/dist/normalizers/claude.js +5 -8
  75. package/dist/normalizers/claude.js.map +1 -1
  76. package/dist/normalizers/codex.d.ts +5 -8
  77. package/dist/normalizers/codex.d.ts.map +1 -1
  78. package/dist/normalizers/codex.js +4 -7
  79. package/dist/normalizers/codex.js.map +1 -1
  80. package/dist/normalizers/cursor.d.ts +1 -2
  81. package/dist/normalizers/cursor.d.ts.map +1 -1
  82. package/dist/normalizers/cursor.js +2 -3
  83. package/dist/normalizers/cursor.js.map +1 -1
  84. package/dist/normalizers/index.d.ts +3 -5
  85. package/dist/normalizers/index.d.ts.map +1 -1
  86. package/dist/normalizers/index.js +3 -5
  87. package/dist/normalizers/index.js.map +1 -1
  88. package/dist/normalizers/opencode.d.ts +2 -4
  89. package/dist/normalizers/opencode.d.ts.map +1 -1
  90. package/dist/normalizers/opencode.js +2 -4
  91. package/dist/normalizers/opencode.js.map +1 -1
  92. package/dist/normalizers/pi.d.ts +3 -4
  93. package/dist/normalizers/pi.d.ts.map +1 -1
  94. package/dist/normalizers/pi.js +3 -4
  95. package/dist/normalizers/pi.js.map +1 -1
  96. package/dist/sessions/chat.d.ts +176 -0
  97. package/dist/sessions/chat.d.ts.map +1 -0
  98. package/dist/sessions/chat.js +701 -0
  99. package/dist/sessions/chat.js.map +1 -0
  100. package/dist/sessions/index.d.ts +3 -0
  101. package/dist/sessions/index.d.ts.map +1 -0
  102. package/dist/sessions/index.js +2 -0
  103. package/dist/sessions/index.js.map +1 -0
  104. package/dist/transcript/index.d.ts +1 -1
  105. package/dist/transcript/index.js +1 -1
  106. package/dist/transcript/message-reducer.d.ts +1 -1
  107. package/dist/transcript/message-reducer.js +1 -1
  108. package/dist/transcript/part-accumulator.d.ts +1 -1
  109. package/dist/transcript/part-accumulator.js +1 -1
  110. package/dist/transcript/tool-display.d.ts +1 -1
  111. package/dist/transcript/tool-display.js +1 -1
  112. package/dist/transcript/tool-shape.d.ts +1 -1
  113. package/dist/transcript/tool-shape.js +1 -1
  114. package/dist/usage/finalize.d.ts +7 -9
  115. package/dist/usage/finalize.d.ts.map +1 -1
  116. package/dist/usage/finalize.js +11 -13
  117. package/dist/usage/finalize.js.map +1 -1
  118. package/dist/usage/index.d.ts +2 -3
  119. package/dist/usage/index.d.ts.map +1 -1
  120. package/dist/usage/index.js +2 -3
  121. package/dist/usage/index.js.map +1 -1
  122. package/dist/usage/model-catalog.d.ts +2 -3
  123. package/dist/usage/model-catalog.d.ts.map +1 -1
  124. package/dist/usage/model-catalog.js +10 -3
  125. package/dist/usage/model-catalog.js.map +1 -1
  126. package/dist/usage/normalize.d.ts +5 -7
  127. package/dist/usage/normalize.d.ts.map +1 -1
  128. package/dist/usage/normalize.js +3 -5
  129. package/dist/usage/normalize.js.map +1 -1
  130. package/dist/usage/ports.d.ts +14 -24
  131. package/dist/usage/ports.d.ts.map +1 -1
  132. package/dist/usage/ports.js +9 -17
  133. package/dist/usage/ports.js.map +1 -1
  134. package/dist/usage/steps.d.ts +70 -0
  135. package/dist/usage/steps.d.ts.map +1 -0
  136. package/dist/usage/steps.js +152 -0
  137. package/dist/usage/steps.js.map +1 -0
  138. package/dist/usage/tokens.d.ts +4 -7
  139. package/dist/usage/tokens.d.ts.map +1 -1
  140. package/dist/usage/tokens.js +4 -7
  141. package/dist/usage/tokens.js.map +1 -1
  142. package/docs/contracts/acp-and-step-usage.md +51 -0
  143. package/docs/contracts/conversation-persistence.md +47 -0
  144. package/docs/contracts/input-and-execution.md +177 -0
  145. package/docs/contracts/platform-authorization.md +22 -0
  146. package/host/adapters/acp-capabilities.js +30 -0
  147. package/host/adapters/acp-client.js +156 -0
  148. package/host/adapters/acp.js +284 -0
  149. package/host/adapters/claude.js +235 -0
  150. package/host/adapters/codex.js +733 -0
  151. package/host/adapters/cursor.js +347 -0
  152. package/host/adapters/input-capabilities.js +89 -0
  153. package/host/adapters/opencode.js +486 -0
  154. package/host/adapters/pi.js +519 -0
  155. package/host/adapters/prompt.js +212 -0
  156. package/host/auth.js +257 -0
  157. package/host/credentials/store.js +209 -0
  158. package/host/http.d.ts +32 -0
  159. package/host/http.js +238 -0
  160. package/host/index.d.ts +84 -0
  161. package/host/index.js +59 -0
  162. package/host/lib/runtime-paths.js +44 -0
  163. package/host/model-resolution.js +313 -0
  164. package/host/native-contract.js +185 -0
  165. package/host/native-runtime.js +194 -0
  166. package/host/permissions.js +27 -0
  167. package/host/platform-egress.js +146 -0
  168. package/host/recorder.js +61 -0
  169. package/host/sqlite/index.d.ts +69 -0
  170. package/host/sqlite/index.js +704 -0
  171. package/host/tooling/mcp-bundle.js +214 -0
  172. package/host/tooling/mcp-relay.js +83 -0
  173. package/host/tooling/native-binaries.js +667 -0
  174. package/host/tooling/native-config.js +460 -0
  175. package/host/tooling/package-import.js +111 -0
  176. package/host/tooling/runtime-home.js +26 -0
  177. package/host/tooling/system-instructions.js +69 -0
  178. package/host/tooling/system-prompt.js +35 -0
  179. package/package.json +45 -26
  180. package/skills/chat/SKILL.md +97 -0
  181. package/skills/chat/agents/openai.yaml +4 -0
  182. package/skills/chat/references/contracts.md +131 -0
  183. package/dist/api/chat-payloads.d.ts +0 -10
  184. package/dist/api/chat-payloads.d.ts.map +0 -1
  185. package/dist/api/chat-payloads.js +0 -57
  186. package/dist/api/chat-payloads.js.map +0 -1
  187. package/dist/contract/chat-input.d.ts +0 -144
  188. package/dist/contract/chat-input.d.ts.map +0 -1
  189. package/dist/contract/chat-input.js +0 -261
  190. package/dist/contract/chat-input.js.map +0 -1
  191. package/dist/contract/create.d.ts +0 -191
  192. package/dist/contract/create.d.ts.map +0 -1
  193. package/dist/contract/create.js +0 -249
  194. package/dist/contract/create.js.map +0 -1
  195. package/dist/contract/freeze.d.ts +0 -85
  196. package/dist/contract/freeze.d.ts.map +0 -1
  197. package/dist/contract/freeze.js +0 -151
  198. package/dist/contract/freeze.js.map +0 -1
  199. package/dist/contract/harnesses.d.ts +0 -21
  200. package/dist/contract/harnesses.d.ts.map +0 -1
  201. package/dist/contract/harnesses.js +0 -39
  202. package/dist/contract/harnesses.js.map +0 -1
  203. package/dist/contract/index.d.ts +0 -17
  204. package/dist/contract/index.d.ts.map +0 -1
  205. package/dist/contract/index.js +0 -17
  206. package/dist/contract/index.js.map +0 -1
  207. package/dist/contract/input.d.ts +0 -45
  208. package/dist/contract/input.d.ts.map +0 -1
  209. package/dist/contract/input.js +0 -78
  210. package/dist/contract/input.js.map +0 -1
  211. package/dist/contract/models.d.ts +0 -57
  212. package/dist/contract/models.d.ts.map +0 -1
  213. package/dist/contract/models.js +0 -380
  214. package/dist/contract/models.js.map +0 -1
  215. package/dist/contract/payload-merge.d.ts +0 -76
  216. package/dist/contract/payload-merge.d.ts.map +0 -1
  217. package/dist/contract/payload-merge.js +0 -215
  218. package/dist/contract/payload-merge.js.map +0 -1
  219. package/dist/events/tool-display.d.ts +0 -22
  220. package/dist/events/tool-display.d.ts.map +0 -1
  221. package/dist/events/tool-display.js +0 -60
  222. package/dist/events/tool-display.js.map +0 -1
  223. package/dist/events/tool-shape.d.ts +0 -48
  224. package/dist/events/tool-shape.d.ts.map +0 -1
  225. package/dist/events/tool-shape.js +0 -225
  226. package/dist/events/tool-shape.js.map +0 -1
  227. package/dist/relay/index.d.ts +0 -65
  228. package/dist/relay/index.d.ts.map +0 -1
  229. package/dist/relay/index.js +0 -197
  230. package/dist/relay/index.js.map +0 -1
  231. package/dist/turns/index.d.ts +0 -11
  232. package/dist/turns/index.d.ts.map +0 -1
  233. package/dist/turns/index.js +0 -11
  234. package/dist/turns/index.js.map +0 -1
  235. package/dist/turns/queries.d.ts +0 -36
  236. package/dist/turns/queries.d.ts.map +0 -1
  237. package/dist/turns/queries.js +0 -37
  238. package/dist/turns/queries.js.map +0 -1
  239. package/dist/turns/replay.d.ts +0 -105
  240. package/dist/turns/replay.d.ts.map +0 -1
  241. package/dist/turns/replay.js +0 -163
  242. package/dist/turns/replay.js.map +0 -1
  243. package/dist/turns/status.d.ts +0 -49
  244. package/dist/turns/status.d.ts.map +0 -1
  245. package/dist/turns/status.js +0 -56
  246. package/dist/turns/status.js.map +0 -1
  247. package/dist/turns/turn-store.d.ts +0 -109
  248. package/dist/turns/turn-store.d.ts.map +0 -1
  249. package/dist/turns/turn-store.js +0 -169
  250. package/dist/turns/turn-store.js.map +0 -1
  251. package/dist/warm/client-cache.d.ts +0 -47
  252. package/dist/warm/client-cache.d.ts.map +0 -1
  253. package/dist/warm/client-cache.js +0 -48
  254. package/dist/warm/client-cache.js.map +0 -1
  255. package/dist/warm/envelope-cache.d.ts +0 -79
  256. package/dist/warm/envelope-cache.d.ts.map +0 -1
  257. package/dist/warm/envelope-cache.js +0 -85
  258. package/dist/warm/envelope-cache.js.map +0 -1
  259. package/dist/warm/index.d.ts +0 -18
  260. package/dist/warm/index.d.ts.map +0 -1
  261. package/dist/warm/index.js +0 -12
  262. package/dist/warm/index.js.map +0 -1
  263. package/dist/warm/payload.d.ts +0 -137
  264. package/dist/warm/payload.d.ts.map +0 -1
  265. package/dist/warm/payload.js +0 -112
  266. package/dist/warm/payload.js.map +0 -1
  267. package/dist/warm/revision.d.ts +0 -70
  268. package/dist/warm/revision.d.ts.map +0 -1
  269. package/dist/warm/revision.js +0 -63
  270. package/dist/warm/revision.js.map +0 -1
  271. package/dist/warm/store-policy.d.ts +0 -57
  272. package/dist/warm/store-policy.d.ts.map +0 -1
  273. package/dist/warm/store-policy.js +0 -89
  274. package/dist/warm/store-policy.js.map +0 -1
  275. package/dist/warm/store.d.ts +0 -42
  276. package/dist/warm/store.d.ts.map +0 -1
  277. package/dist/warm/store.js +0 -69
  278. package/dist/warm/store.js.map +0 -1
  279. package/dist/wire/frames.d.ts +0 -208
  280. package/dist/wire/frames.d.ts.map +0 -1
  281. package/dist/wire/frames.js +0 -245
  282. package/dist/wire/frames.js.map +0 -1
  283. package/dist/wire/index.d.ts +0 -7
  284. package/dist/wire/index.d.ts.map +0 -1
  285. package/dist/wire/index.js +0 -7
  286. package/dist/wire/index.js.map +0 -1
@@ -0,0 +1,214 @@
1
+ /** Exact prepared MCP-server projection for every native adapter. */
2
+
3
+ /** Engine `runtime-auth.js getRuntimeToken`, byte-faithful. */
4
+ function getRuntimeToken() {
5
+ return String(process.env.AMALGM_RUNTIME_TOKEN || '').trim();
6
+ }
7
+
8
+ export function headerArrayToRecord(headers) {
9
+ const out = {};
10
+ for (const header of Array.isArray(headers) ? headers : []) {
11
+ const name = String(header?.name || '').trim();
12
+ const value = String(header?.value || '');
13
+ if (name) out[name] = value;
14
+ }
15
+ return out;
16
+ }
17
+
18
+ function recordToHeaderArray(headers) {
19
+ if (!headers || typeof headers !== 'object') return [];
20
+ return Object.entries(headers)
21
+ .filter(([name, value]) => name && value != null)
22
+ .map(([name, value]) => ({ name, value: String(value) }));
23
+ }
24
+
25
+ export function safeName(name) {
26
+ return String(name || '')
27
+ .trim()
28
+ .replace(/[^A-Za-z0-9_.-]+/g, '_')
29
+ .replace(/^_+|_+$/g, '')
30
+ || 'mcp';
31
+ }
32
+
33
+ function normalizeType(type) {
34
+ const clean = String(type || '').toLowerCase();
35
+ if (clean === 'sse') return 'sse';
36
+ if (clean === 'stdio') return 'stdio';
37
+ return 'http';
38
+ }
39
+
40
+ export class InvalidMcpServerError extends Error {
41
+ constructor(message) {
42
+ super(message);
43
+ this.name = 'InvalidMcpServerError';
44
+ this.code = 'INVALID_MCP_SERVER';
45
+ }
46
+ }
47
+
48
+ function normalizeServer(server) {
49
+ if (!server || typeof server !== 'object') {
50
+ throw new InvalidMcpServerError('MCP server must be an object');
51
+ }
52
+ const name = safeName(server.name);
53
+ const type = normalizeType(server.type);
54
+ if (type === 'stdio') {
55
+ const command = String(server.command || '').trim();
56
+ if (!command) throw new InvalidMcpServerError(`${name} stdio MCP server requires command`);
57
+ const args = Array.isArray(server.args) ? server.args.map((arg) => String(arg)) : [];
58
+ const env = Array.isArray(server.env)
59
+ ? server.env.map((entry) => ({ name: String(entry?.name || ''), value: String(entry?.value || '') }))
60
+ : recordToHeaderArray(server.env);
61
+ if (env.some((entry) => !entry.name)) {
62
+ throw new InvalidMcpServerError(`${name} stdio MCP server has an empty environment variable name`);
63
+ }
64
+ return { name, type, command, args, env };
65
+ }
66
+ const url = String(server.url || '').trim();
67
+ if (!url) throw new InvalidMcpServerError(`${name} ${type} MCP server requires url`);
68
+ return {
69
+ name,
70
+ type,
71
+ url,
72
+ headers: Array.isArray(server.headers)
73
+ ? server.headers
74
+ : recordToHeaderArray(server.headers),
75
+ };
76
+ }
77
+
78
+ function localBaseUrl(contract) {
79
+ const value = String(contract.localBaseUrl || '').trim();
80
+ if (!value) throw new InvalidMcpServerError('Remote MCP servers require localBaseUrl');
81
+ return value.replace(/\/$/, '');
82
+ }
83
+
84
+ export function mcpServers(contract) {
85
+ const byName = new Map();
86
+ const add = (server) => {
87
+ const normalized = normalizeServer(server);
88
+ byName.set(normalized.name, normalized);
89
+ };
90
+ for (const server of Array.isArray(contract.mcpServers) ? contract.mcpServers : []) add(server);
91
+ return [...byName.values()];
92
+ }
93
+
94
+ function relayUrl(contract, serverName) {
95
+ return `${localBaseUrl(contract)}/chat/mcp/${encodeURIComponent(contract.sessionId)}/${encodeURIComponent(serverName)}`;
96
+ }
97
+
98
+ export function relayedMcpServers(contract) {
99
+ const runtimeToken = getRuntimeToken();
100
+ return mcpServers(contract).map((server) => server.type === 'stdio'
101
+ ? server
102
+ : ({
103
+ ...server,
104
+ url: relayUrl(contract, server.name),
105
+ headers: runtimeToken
106
+ ? [{ name: 'x-amalgm-runtime-token', value: runtimeToken }]
107
+ : [],
108
+ }));
109
+ }
110
+
111
+ export function findMcpRelayTarget(contract, serverName) {
112
+ const clean = safeName(serverName);
113
+ return mcpServers(contract).find((server) => server.name === clean && server.type !== 'stdio') || null;
114
+ }
115
+
116
+ export function toClaudeMcpServers(contract) {
117
+ const out = {};
118
+ for (const server of relayedMcpServers(contract)) {
119
+ if (server.type === 'stdio') {
120
+ out[server.name] = {
121
+ type: 'stdio',
122
+ command: server.command,
123
+ args: server.args,
124
+ env: headerArrayToRecord(server.env),
125
+ };
126
+ continue;
127
+ }
128
+ out[server.name] = {
129
+ type: server.type === 'sse' ? 'sse' : 'http',
130
+ url: server.url,
131
+ headers: headerArrayToRecord(server.headers),
132
+ alwaysLoad: server.name === 'amalgm' ? true : undefined,
133
+ };
134
+ }
135
+ return out;
136
+ }
137
+
138
+ export function toOpenCodeMcpConfig(contract) {
139
+ const out = {};
140
+ for (const server of relayedMcpServers(contract)) {
141
+ if (server.type === 'stdio') {
142
+ out[server.name] = {
143
+ type: 'local',
144
+ command: [server.command, ...server.args],
145
+ enabled: true,
146
+ environment: headerArrayToRecord(server.env),
147
+ timeout: 15000,
148
+ };
149
+ continue;
150
+ }
151
+ out[server.name] = {
152
+ type: 'remote',
153
+ url: server.url,
154
+ enabled: true,
155
+ headers: headerArrayToRecord(server.headers),
156
+ oauth: false,
157
+ timeout: 15000,
158
+ };
159
+ }
160
+ return out;
161
+ }
162
+
163
+ function tomlString(value) {
164
+ return JSON.stringify(String(value || ''));
165
+ }
166
+
167
+ export function toCodexMcpToml(contract) {
168
+ const lines = [];
169
+ const runtimeToken = getRuntimeToken();
170
+ for (const server of relayedMcpServers(contract)) {
171
+ lines.push(`[mcp_servers.${server.name}]`);
172
+ if (server.type === 'stdio') {
173
+ lines.push(`command = ${tomlString(server.command)}`);
174
+ if (server.args.length) lines.push(`args = ${JSON.stringify(server.args)}`);
175
+ if (server.env.length) {
176
+ lines.push('');
177
+ lines.push(`[mcp_servers.${server.name}.env]`);
178
+ for (const entry of server.env) lines.push(`${tomlString(entry.name)} = ${tomlString(entry.value)}`);
179
+ }
180
+ } else {
181
+ lines.push(`url = ${tomlString(server.url)}`);
182
+ if (runtimeToken) lines.push('bearer_token_env_var = "AMALGM_RUNTIME_TOKEN"');
183
+ }
184
+ lines.push('');
185
+ }
186
+ return lines.join('\n');
187
+ }
188
+
189
+ export function mcpSummary(contract) {
190
+ return mcpServers(contract).map((server) => ({
191
+ name: server.name,
192
+ type: server.type,
193
+ ...(server.type === 'stdio'
194
+ ? { command: server.command, hasEnvironment: server.env.length > 0 }
195
+ : { url: server.url, hasHeaders: server.headers.length > 0 }),
196
+ }));
197
+ }
198
+
199
+ /** Official ACP MCP-server projection used by Cursor and generic ACP agents. */
200
+ export function toAcpMcpServers(contract) {
201
+ return relayedMcpServers(contract).map((server) => server.type === 'stdio'
202
+ ? {
203
+ name: server.name,
204
+ command: server.command,
205
+ args: [...server.args],
206
+ env: server.env.map((entry) => ({ ...entry })),
207
+ }
208
+ : {
209
+ type: server.type,
210
+ name: server.name,
211
+ url: server.url,
212
+ headers: server.headers.map((header) => ({ ...header })),
213
+ });
214
+ }
@@ -0,0 +1,83 @@
1
+ /** Session-bound relay that keeps remote MCP credentials out of provider config. */
2
+
3
+ import { findMcpRelayTarget, headerArrayToRecord } from './mcp-bundle.js';
4
+
5
+ const HOP_HEADERS = new Set([
6
+ 'connection',
7
+ 'content-length',
8
+ 'host',
9
+ 'keep-alive',
10
+ 'proxy-authenticate',
11
+ 'proxy-authorization',
12
+ 'te',
13
+ 'trailer',
14
+ 'transfer-encoding',
15
+ 'upgrade',
16
+ ]);
17
+
18
+ function copyRequestHeaders(req, extraHeaders) {
19
+ const headers = {};
20
+ for (const [name, value] of Object.entries(req.headers || {})) {
21
+ const lower = name.toLowerCase();
22
+ if (HOP_HEADERS.has(lower)) continue;
23
+ if (lower === 'authorization') continue;
24
+ headers[name] = Array.isArray(value) ? value.join(', ') : String(value);
25
+ }
26
+ Object.assign(headers, extraHeaders);
27
+ return headers;
28
+ }
29
+
30
+ async function readBody(req) {
31
+ const chunks = [];
32
+ for await (const chunk of req) chunks.push(Buffer.from(chunk));
33
+ return chunks.length ? Buffer.concat(chunks) : null;
34
+ }
35
+
36
+ export async function forwardMcpRelay({ req, res, contract, serverName, search = '' }) {
37
+ const target = findMcpRelayTarget(contract, serverName);
38
+ if (!target) {
39
+ res.writeHead(404, { 'Content-Type': 'application/json' });
40
+ res.end(JSON.stringify({ error: 'Unknown MCP server', serverName }));
41
+ return;
42
+ }
43
+
44
+ const upstream = `${target.url}${search || ''}`;
45
+ const extraHeaders = {
46
+ ...headerArrayToRecord(target.headers),
47
+ 'x-amalgm-session-id': contract.sessionId,
48
+ 'x-amalgm-user-id': contract.userId || '',
49
+ 'x-amalgm-computer-id': contract.computerId || '',
50
+ };
51
+ for (const key of Object.keys(extraHeaders)) {
52
+ if (!extraHeaders[key]) delete extraHeaders[key];
53
+ }
54
+
55
+ const body = ['GET', 'HEAD'].includes(req.method) ? undefined : await readBody(req);
56
+ const response = await fetch(upstream, {
57
+ method: req.method,
58
+ headers: copyRequestHeaders(req, extraHeaders),
59
+ body,
60
+ redirect: 'manual',
61
+ });
62
+
63
+ const headers = {};
64
+ response.headers.forEach((value, name) => {
65
+ if (!HOP_HEADERS.has(name.toLowerCase())) headers[name] = value;
66
+ });
67
+ res.writeHead(response.status, headers);
68
+ if (!response.body) {
69
+ res.end();
70
+ return;
71
+ }
72
+ const reader = response.body.getReader();
73
+ try {
74
+ while (true) {
75
+ const { done, value } = await reader.read();
76
+ if (done) break;
77
+ if (value) res.write(Buffer.from(value));
78
+ }
79
+ res.end();
80
+ } catch (err) {
81
+ try { res.destroy(err); } catch {}
82
+ }
83
+ }