@deployfoundation/foundation-deploy 0.2.19 → 0.2.20
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/bin/foundation-deploy.js +123 -43
- package/package.json +1 -1
|
@@ -119,18 +119,132 @@ var GOOGLE_CALENDAR_EVENTS_SCOPE = [
|
|
|
119
119
|
var GOOGLE_CALENDAR_JSON_MAX_BYTES = 2 * 1024 * 1024;
|
|
120
120
|
// ../connectors/src/otter.ts
|
|
121
121
|
var OTTER_MAX_RESPONSE_BYTES = 2 * 1024 * 1024;
|
|
122
|
+
// ../connectors/src/remote-mcp.ts
|
|
123
|
+
var REMOTE_MCP_PROTOCOL_VERSIONS = ["2025-11-25", "2025-06-18", "2025-03-26"];
|
|
124
|
+
var REMOTE_MCP_MAX_RESPONSE_BYTES = 1024 * 1024;
|
|
125
|
+
var REMOTE_MCP_MAX_REQUEST_BYTES = 256 * 1024;
|
|
126
|
+
var MAX_URL_CHARS = 2048;
|
|
127
|
+
var MAX_TOKEN_TTL_SECONDS = 31 * 24 * 60 * 60;
|
|
128
|
+
class RemoteMcpResponseTooLargeError extends Error {
|
|
129
|
+
constructor() {
|
|
130
|
+
super(`remote MCP response exceeded ${REMOTE_MCP_MAX_RESPONSE_BYTES} bytes`);
|
|
131
|
+
this.name = "RemoteMcpResponseTooLargeError";
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
class RemoteMcpTimeoutError extends Error {
|
|
135
|
+
constructor() {
|
|
136
|
+
super("remote MCP did not answer in time");
|
|
137
|
+
this.name = "RemoteMcpTimeoutError";
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
function defineRemoteMcpProvider(input) {
|
|
141
|
+
const protocolVersions = [...input.protocolVersions ?? REMOTE_MCP_PROTOCOL_VERSIONS];
|
|
142
|
+
if (protocolVersions.length === 0 || new Set(protocolVersions).size !== protocolVersions.length || !protocolVersions.every((version) => /^\d{4}-\d{2}-\d{2}$/.test(version)))
|
|
143
|
+
throw new Error("remote MCP provider: invalid protocolVersions");
|
|
144
|
+
if (!/^[A-Za-z0-9_.-]{1,128}$/.test(input.clientName))
|
|
145
|
+
throw new Error("remote MCP provider: invalid clientName");
|
|
146
|
+
return Object.freeze({
|
|
147
|
+
endpoint: httpsUrl(input.endpoint, "remote MCP provider: endpoint"),
|
|
148
|
+
issuer: httpsUrl(input.issuer, "remote MCP provider: issuer"),
|
|
149
|
+
tokenUrl: httpsUrl(input.tokenUrl, "remote MCP provider: tokenUrl"),
|
|
150
|
+
clientName: input.clientName,
|
|
151
|
+
protocolVersions: Object.freeze(protocolVersions)
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
async function readBoundedRemoteMcpResponse(response, maxBytes = REMOTE_MCP_MAX_RESPONSE_BYTES, signal) {
|
|
155
|
+
const chunks = [];
|
|
156
|
+
let total = 0;
|
|
157
|
+
await consumeBody(response, maxBytes, signal, (chunk) => {
|
|
158
|
+
chunks.push(chunk);
|
|
159
|
+
total += chunk.byteLength;
|
|
160
|
+
return false;
|
|
161
|
+
});
|
|
162
|
+
const bytes = new Uint8Array(total);
|
|
163
|
+
let offset = 0;
|
|
164
|
+
for (const chunk of chunks) {
|
|
165
|
+
bytes.set(chunk, offset);
|
|
166
|
+
offset += chunk.byteLength;
|
|
167
|
+
}
|
|
168
|
+
return new TextDecoder().decode(bytes);
|
|
169
|
+
}
|
|
170
|
+
async function consumeBody(response, maxBytes, signal, onChunk) {
|
|
171
|
+
const declaredBytes = Number(response.headers.get("content-length") ?? "");
|
|
172
|
+
if (Number.isFinite(declaredBytes) && declaredBytes > maxBytes) {
|
|
173
|
+
await response.body?.cancel().catch(() => {
|
|
174
|
+
return;
|
|
175
|
+
});
|
|
176
|
+
throw new RemoteMcpResponseTooLargeError;
|
|
177
|
+
}
|
|
178
|
+
const reader = response.body?.getReader();
|
|
179
|
+
if (reader === undefined)
|
|
180
|
+
return 0;
|
|
181
|
+
const cancel = () => {
|
|
182
|
+
reader.cancel().catch(() => {
|
|
183
|
+
return;
|
|
184
|
+
});
|
|
185
|
+
};
|
|
186
|
+
const aborted = () => signal?.aborted === true;
|
|
187
|
+
if (aborted()) {
|
|
188
|
+
cancel();
|
|
189
|
+
throw new RemoteMcpTimeoutError;
|
|
190
|
+
}
|
|
191
|
+
signal?.addEventListener("abort", cancel, { once: true });
|
|
192
|
+
let total = 0;
|
|
193
|
+
try {
|
|
194
|
+
while (true) {
|
|
195
|
+
let next;
|
|
196
|
+
try {
|
|
197
|
+
next = await reader.read();
|
|
198
|
+
} catch {
|
|
199
|
+
if (aborted())
|
|
200
|
+
throw new RemoteMcpTimeoutError;
|
|
201
|
+
throw new Error("remote MCP response could not be read");
|
|
202
|
+
}
|
|
203
|
+
if (aborted())
|
|
204
|
+
throw new RemoteMcpTimeoutError;
|
|
205
|
+
if (next.done)
|
|
206
|
+
return total;
|
|
207
|
+
total += next.value.byteLength;
|
|
208
|
+
if (total > maxBytes) {
|
|
209
|
+
cancel();
|
|
210
|
+
throw new RemoteMcpResponseTooLargeError;
|
|
211
|
+
}
|
|
212
|
+
if (onChunk(next.value)) {
|
|
213
|
+
cancel();
|
|
214
|
+
return total;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
} finally {
|
|
218
|
+
signal?.removeEventListener("abort", cancel);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
function httpsUrl(value, label) {
|
|
222
|
+
let url;
|
|
223
|
+
try {
|
|
224
|
+
url = new URL(value);
|
|
225
|
+
} catch {
|
|
226
|
+
throw new Error(`${label} must be an HTTPS URL`);
|
|
227
|
+
}
|
|
228
|
+
if (value.length > MAX_URL_CHARS || url.protocol !== "https:" || url.username !== "" || url.password !== "" || url.hash !== "" || url.search !== "")
|
|
229
|
+
throw new Error(`${label} must be an HTTPS URL`);
|
|
230
|
+
return value;
|
|
231
|
+
}
|
|
232
|
+
|
|
122
233
|
// ../connectors/src/knock.ts
|
|
234
|
+
var KNOCK_MCP_ENDPOINT = "https://mcp.knock.app/mcp";
|
|
235
|
+
var KNOCK_OAUTH_ISSUER = "https://mcp.knock.app";
|
|
236
|
+
var KNOCK_TOKEN_URL = "https://mcp.knock.app/token";
|
|
123
237
|
var KNOCK_REGISTER_URL = "https://mcp.knock.app/register";
|
|
124
|
-
var
|
|
125
|
-
var
|
|
238
|
+
var KNOCK_MCP_PROTOCOL_VERSION = "2025-06-18";
|
|
239
|
+
var KNOCK_MCP_PROVIDER = defineRemoteMcpProvider({
|
|
240
|
+
endpoint: KNOCK_MCP_ENDPOINT,
|
|
241
|
+
issuer: KNOCK_OAUTH_ISSUER,
|
|
242
|
+
tokenUrl: KNOCK_TOKEN_URL,
|
|
243
|
+
clientName: "foundation-knock-proxy",
|
|
244
|
+
protocolVersions: [KNOCK_MCP_PROTOCOL_VERSION]
|
|
245
|
+
});
|
|
126
246
|
var MAX_CLIENT_ID_CHARS = 2048;
|
|
127
247
|
var MAX_REDIRECT_URI_CHARS = 2048;
|
|
128
|
-
class KnockResponseTooLargeError extends Error {
|
|
129
|
-
constructor() {
|
|
130
|
-
super(`Knock response exceeded ${KNOCK_MAX_RESPONSE_BYTES} bytes`);
|
|
131
|
-
this.name = "KnockResponseTooLargeError";
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
248
|
function parseKnockOAuthClient(raw) {
|
|
135
249
|
const value = parseJsonObject(raw, "Knock OAuth client");
|
|
136
250
|
const keys = Object.keys(value);
|
|
@@ -162,46 +276,12 @@ async function registerKnockPublicClient(args) {
|
|
|
162
276
|
}
|
|
163
277
|
if (!response.ok)
|
|
164
278
|
throw new Error("Knock OAuth registration was rejected");
|
|
165
|
-
const body = parseJsonObject(await
|
|
279
|
+
const body = parseJsonObject(await readBoundedRemoteMcpResponse(response), "Knock OAuth registration");
|
|
166
280
|
return {
|
|
167
281
|
client_id: requiredOpaqueString(body.client_id, "Knock OAuth registration: missing client_id", MAX_CLIENT_ID_CHARS),
|
|
168
282
|
redirect_uri: redirectUri
|
|
169
283
|
};
|
|
170
284
|
}
|
|
171
|
-
async function readBoundedKnockResponse(response, maxBytes = KNOCK_MAX_RESPONSE_BYTES) {
|
|
172
|
-
const declaredBytes = Number(response.headers.get("content-length") ?? "");
|
|
173
|
-
if (Number.isFinite(declaredBytes) && declaredBytes > maxBytes) {
|
|
174
|
-
await response.body?.cancel().catch(() => {
|
|
175
|
-
return;
|
|
176
|
-
});
|
|
177
|
-
throw new KnockResponseTooLargeError;
|
|
178
|
-
}
|
|
179
|
-
const reader = response.body?.getReader();
|
|
180
|
-
if (reader === undefined)
|
|
181
|
-
return "";
|
|
182
|
-
const chunks = [];
|
|
183
|
-
let total = 0;
|
|
184
|
-
while (true) {
|
|
185
|
-
const { done, value } = await reader.read();
|
|
186
|
-
if (done)
|
|
187
|
-
break;
|
|
188
|
-
total += value.byteLength;
|
|
189
|
-
if (total > maxBytes) {
|
|
190
|
-
await reader.cancel().catch(() => {
|
|
191
|
-
return;
|
|
192
|
-
});
|
|
193
|
-
throw new KnockResponseTooLargeError;
|
|
194
|
-
}
|
|
195
|
-
chunks.push(value);
|
|
196
|
-
}
|
|
197
|
-
const bytes = new Uint8Array(total);
|
|
198
|
-
let offset = 0;
|
|
199
|
-
for (const chunk of chunks) {
|
|
200
|
-
bytes.set(chunk, offset);
|
|
201
|
-
offset += chunk.byteLength;
|
|
202
|
-
}
|
|
203
|
-
return new TextDecoder().decode(bytes);
|
|
204
|
-
}
|
|
205
285
|
function parseJsonObject(raw, label) {
|
|
206
286
|
let value;
|
|
207
287
|
try {
|
package/package.json
CHANGED