mcp-scaleway 0.2.1 → 0.2.2

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 (2) hide show
  1. package/dist/index.js +244 -75
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -2110,16 +2110,87 @@ function registerMongodbTools(server) {
2110
2110
  server.tool("scaleway_mongodb_list_versions", "List available MongoDB versions", ListVersionsParams.shape, async (params) => handleListVersions(params));
2111
2111
  }
2112
2112
 
2113
+ // src/shared/s3-signer.ts
2114
+ import { createHash, createHmac } from "node:crypto";
2115
+ function hmacSha256(key, data) {
2116
+ return createHmac("sha256", key).update(data, "utf8").digest();
2117
+ }
2118
+ function sha256Hex(data) {
2119
+ return createHash("sha256").update(data).digest("hex");
2120
+ }
2121
+ function getSigningKey(secretKey, dateStamp, region) {
2122
+ const kDate = hmacSha256(`AWS4${secretKey}`, dateStamp);
2123
+ const kRegion = hmacSha256(kDate, region);
2124
+ const kService = hmacSha256(kRegion, "s3");
2125
+ return hmacSha256(kService, "aws4_request");
2126
+ }
2127
+ function signS3Request(params) {
2128
+ const { method, url, accessKey, secretKey, region } = params;
2129
+ const parsedUrl = new URL(url);
2130
+ const now = new Date;
2131
+ const dateStamp = now.toISOString().replace(/[-:]/g, "").slice(0, 8);
2132
+ const amzDate = `${dateStamp}T${now.toISOString().replace(/[-:]/g, "").slice(9, 15)}Z`;
2133
+ const payloadHash = params.body ? sha256Hex(params.body) : sha256Hex("");
2134
+ const headers = {
2135
+ ...params.headers,
2136
+ host: parsedUrl.host,
2137
+ "x-amz-date": amzDate,
2138
+ "x-amz-content-sha256": payloadHash
2139
+ };
2140
+ const lowerHeaders = {};
2141
+ for (const [k, v] of Object.entries(headers)) {
2142
+ lowerHeaders[k.toLowerCase()] = v;
2143
+ }
2144
+ const sortedHeaderKeys = Object.keys(lowerHeaders).sort();
2145
+ const canonicalHeaders = sortedHeaderKeys.map((k) => `${k}:${lowerHeaders[k]}
2146
+ `).join("");
2147
+ const signedHeaders = sortedHeaderKeys.join(";");
2148
+ const queryParams = [...parsedUrl.searchParams.entries()].sort(([a], [b]) => a.localeCompare(b));
2149
+ const canonicalQueryString = queryParams.map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`).join("&");
2150
+ const canonicalUri = parsedUrl.pathname;
2151
+ const canonicalRequest = [
2152
+ method,
2153
+ canonicalUri,
2154
+ canonicalQueryString,
2155
+ canonicalHeaders,
2156
+ signedHeaders,
2157
+ payloadHash
2158
+ ].join(`
2159
+ `);
2160
+ const credentialScope = `${dateStamp}/${region}/s3/aws4_request`;
2161
+ const stringToSign = [
2162
+ "AWS4-HMAC-SHA256",
2163
+ amzDate,
2164
+ credentialScope,
2165
+ sha256Hex(canonicalRequest)
2166
+ ].join(`
2167
+ `);
2168
+ const signingKey = getSigningKey(secretKey, dateStamp, region);
2169
+ const signature = hmacSha256(signingKey, stringToSign).toString("hex");
2170
+ const authorization = `AWS4-HMAC-SHA256 Credential=${accessKey}/${credentialScope}, SignedHeaders=${signedHeaders}, Signature=${signature}`;
2171
+ return {
2172
+ ...params.headers,
2173
+ host: parsedUrl.host,
2174
+ "x-amz-date": amzDate,
2175
+ "x-amz-content-sha256": payloadHash,
2176
+ Authorization: authorization
2177
+ };
2178
+ }
2179
+
2113
2180
  // src/tools/object-storage/handlers.ts
2114
2181
  function buildEndpoint(region) {
2115
2182
  return `https://s3.${region}.scw.cloud`;
2116
2183
  }
2117
- function buildHeaders(accessKey, secretKey) {
2118
- return {
2119
- "X-Auth-Token": secretKey,
2120
- "X-Amz-Content-Sha256": "UNSIGNED-PAYLOAD",
2121
- Authorization: `SCW ${accessKey}:${secretKey}`
2122
- };
2184
+ function buildSignedHeaders(params) {
2185
+ return signS3Request({
2186
+ method: params.method,
2187
+ url: params.url,
2188
+ headers: params.extraHeaders ?? {},
2189
+ body: params.body,
2190
+ accessKey: params.accessKey,
2191
+ secretKey: params.secretKey,
2192
+ region: params.region
2193
+ });
2123
2194
  }
2124
2195
  function formatSuccess(data) {
2125
2196
  return {
@@ -2130,9 +2201,15 @@ async function handleListBuckets(input) {
2130
2201
  try {
2131
2202
  const config = loadAuthConfig();
2132
2203
  const region = input.region ?? config.defaultRegion;
2133
- const endpoint = buildEndpoint(region);
2134
- const headers = buildHeaders(config.accessKey, config.secretKey);
2135
- const response = await fetch(endpoint, { method: "GET", headers });
2204
+ const url = buildEndpoint(region);
2205
+ const headers = buildSignedHeaders({
2206
+ method: "GET",
2207
+ url,
2208
+ accessKey: config.accessKey,
2209
+ secretKey: config.secretKey,
2210
+ region
2211
+ });
2212
+ const response = await fetch(url, { method: "GET", headers });
2136
2213
  if (!response.ok) {
2137
2214
  const errText = await response.text();
2138
2215
  return formatErrorResponse(mapScalewayError(Object.assign(new Error(errText), { statusCode: response.status })));
@@ -2148,14 +2225,20 @@ async function handleCreateBucket(input) {
2148
2225
  try {
2149
2226
  const config = loadAuthConfig();
2150
2227
  const region = input.region ?? config.defaultRegion;
2151
- const endpoint = buildEndpoint(region);
2152
- const headers = {
2153
- ...buildHeaders(config.accessKey, config.secretKey)
2154
- };
2228
+ const url = `${buildEndpoint(region)}/${input.name}`;
2229
+ const extraHeaders = {};
2155
2230
  if (input.acl) {
2156
- headers["x-amz-acl"] = input.acl;
2231
+ extraHeaders["x-amz-acl"] = input.acl;
2157
2232
  }
2158
- const response = await fetch(`${endpoint}/${input.name}`, {
2233
+ const headers = buildSignedHeaders({
2234
+ method: "PUT",
2235
+ url,
2236
+ accessKey: config.accessKey,
2237
+ secretKey: config.secretKey,
2238
+ region,
2239
+ extraHeaders
2240
+ });
2241
+ const response = await fetch(url, {
2159
2242
  method: "PUT",
2160
2243
  headers
2161
2244
  });
@@ -2172,9 +2255,15 @@ async function handleDeleteBucket(input) {
2172
2255
  try {
2173
2256
  const config = loadAuthConfig();
2174
2257
  const region = input.region ?? config.defaultRegion;
2175
- const endpoint = buildEndpoint(region);
2176
- const headers = buildHeaders(config.accessKey, config.secretKey);
2177
- const response = await fetch(`${endpoint}/${input.name}`, {
2258
+ const url = `${buildEndpoint(region)}/${input.name}`;
2259
+ const headers = buildSignedHeaders({
2260
+ method: "DELETE",
2261
+ url,
2262
+ accessKey: config.accessKey,
2263
+ secretKey: config.secretKey,
2264
+ region
2265
+ });
2266
+ const response = await fetch(url, {
2178
2267
  method: "DELETE",
2179
2268
  headers
2180
2269
  });
@@ -2191,25 +2280,47 @@ async function handleGetBucketInfo(input) {
2191
2280
  try {
2192
2281
  const config = loadAuthConfig();
2193
2282
  const region = input.region ?? config.defaultRegion;
2194
- const endpoint = buildEndpoint(region);
2195
- const headers = buildHeaders(config.accessKey, config.secretKey);
2196
- const headResponse = await fetch(`${endpoint}/${input.name}`, {
2283
+ const baseUrl = `${buildEndpoint(region)}/${input.name}`;
2284
+ const headHeaders = buildSignedHeaders({
2197
2285
  method: "HEAD",
2198
- headers
2286
+ url: baseUrl,
2287
+ accessKey: config.accessKey,
2288
+ secretKey: config.secretKey,
2289
+ region
2290
+ });
2291
+ const headResponse = await fetch(baseUrl, {
2292
+ method: "HEAD",
2293
+ headers: headHeaders
2199
2294
  });
2200
2295
  if (!headResponse.ok) {
2201
2296
  const errText = "Bucket not found or access denied";
2202
2297
  return formatErrorResponse(mapScalewayError(Object.assign(new Error(errText), { statusCode: headResponse.status })));
2203
2298
  }
2204
- const versioningResponse = await fetch(`${endpoint}/${input.name}?versioning`, {
2299
+ const versioningUrl = `${baseUrl}?versioning`;
2300
+ const versioningHeaders = buildSignedHeaders({
2205
2301
  method: "GET",
2206
- headers
2302
+ url: versioningUrl,
2303
+ accessKey: config.accessKey,
2304
+ secretKey: config.secretKey,
2305
+ region
2306
+ });
2307
+ const versioningResponse = await fetch(versioningUrl, {
2308
+ method: "GET",
2309
+ headers: versioningHeaders
2207
2310
  });
2208
2311
  const versioningXml = versioningResponse.ok ? await versioningResponse.text() : "";
2209
2312
  const versioning = parseVersioningXml(versioningXml);
2210
- const listResponse = await fetch(`${endpoint}/${input.name}?list-type=2&max-keys=0`, {
2313
+ const listUrl = `${baseUrl}?list-type=2&max-keys=0`;
2314
+ const listHeaders = buildSignedHeaders({
2211
2315
  method: "GET",
2212
- headers
2316
+ url: listUrl,
2317
+ accessKey: config.accessKey,
2318
+ secretKey: config.secretKey,
2319
+ region
2320
+ });
2321
+ const listResponse = await fetch(listUrl, {
2322
+ method: "GET",
2323
+ headers: listHeaders
2213
2324
  });
2214
2325
  const listXml = listResponse.ok ? await listResponse.text() : "";
2215
2326
  const objectCount = parseKeyCount(listXml);
@@ -2230,8 +2341,6 @@ async function handleListObjects(input) {
2230
2341
  try {
2231
2342
  const config = loadAuthConfig();
2232
2343
  const region = input.region ?? config.defaultRegion;
2233
- const endpoint = buildEndpoint(region);
2234
- const headers = buildHeaders(config.accessKey, config.secretKey);
2235
2344
  const params = new URLSearchParams({ "list-type": "2" });
2236
2345
  if (input.prefix)
2237
2346
  params.set("prefix", input.prefix);
@@ -2241,7 +2350,15 @@ async function handleListObjects(input) {
2241
2350
  params.set("max-keys", String(input.maxKeys));
2242
2351
  if (input.continuationToken)
2243
2352
  params.set("continuation-token", input.continuationToken);
2244
- const response = await fetch(`${endpoint}/${input.bucket}?${params.toString()}`, {
2353
+ const url = `${buildEndpoint(region)}/${input.bucket}?${params.toString()}`;
2354
+ const headers = buildSignedHeaders({
2355
+ method: "GET",
2356
+ url,
2357
+ accessKey: config.accessKey,
2358
+ secretKey: config.secretKey,
2359
+ region
2360
+ });
2361
+ const response = await fetch(url, {
2245
2362
  method: "GET",
2246
2363
  headers
2247
2364
  });
@@ -2260,9 +2377,15 @@ async function handleGetObjectInfo(input) {
2260
2377
  try {
2261
2378
  const config = loadAuthConfig();
2262
2379
  const region = input.region ?? config.defaultRegion;
2263
- const endpoint = buildEndpoint(region);
2264
- const headers = buildHeaders(config.accessKey, config.secretKey);
2265
- const response = await fetch(`${endpoint}/${input.bucket}/${encodeURIComponent(input.key)}`, {
2380
+ const url = `${buildEndpoint(region)}/${input.bucket}/${encodeURIComponent(input.key)}`;
2381
+ const headers = buildSignedHeaders({
2382
+ method: "HEAD",
2383
+ url,
2384
+ accessKey: config.accessKey,
2385
+ secretKey: config.secretKey,
2386
+ region
2387
+ });
2388
+ const response = await fetch(url, {
2266
2389
  method: "HEAD",
2267
2390
  headers
2268
2391
  });
@@ -2288,21 +2411,28 @@ async function handlePutObject(input) {
2288
2411
  try {
2289
2412
  const config = loadAuthConfig();
2290
2413
  const region = input.region ?? config.defaultRegion;
2291
- const endpoint = buildEndpoint(region);
2292
- const headers = {
2293
- ...buildHeaders(config.accessKey, config.secretKey)
2294
- };
2414
+ const url = `${buildEndpoint(region)}/${input.bucket}/${encodeURIComponent(input.key)}`;
2415
+ const extraHeaders = {};
2295
2416
  if (input.contentType)
2296
- headers["Content-Type"] = input.contentType;
2417
+ extraHeaders["Content-Type"] = input.contentType;
2297
2418
  if (input.storageClass)
2298
- headers["x-amz-storage-class"] = input.storageClass;
2419
+ extraHeaders["x-amz-storage-class"] = input.storageClass;
2299
2420
  if (input.metadata) {
2300
2421
  for (const [k, v] of Object.entries(input.metadata)) {
2301
- headers[`x-amz-meta-${k}`] = v;
2422
+ extraHeaders[`x-amz-meta-${k}`] = v;
2302
2423
  }
2303
2424
  }
2304
2425
  const body = input.contentBase64 ? Buffer.from(input.contentBase64, "base64") : undefined;
2305
- const response = await fetch(`${endpoint}/${input.bucket}/${encodeURIComponent(input.key)}`, {
2426
+ const headers = buildSignedHeaders({
2427
+ method: "PUT",
2428
+ url,
2429
+ accessKey: config.accessKey,
2430
+ secretKey: config.secretKey,
2431
+ region,
2432
+ extraHeaders,
2433
+ body
2434
+ });
2435
+ const response = await fetch(url, {
2306
2436
  method: "PUT",
2307
2437
  headers,
2308
2438
  body
@@ -2324,9 +2454,15 @@ async function handleDeleteObject(input) {
2324
2454
  try {
2325
2455
  const config = loadAuthConfig();
2326
2456
  const region = input.region ?? config.defaultRegion;
2327
- const endpoint = buildEndpoint(region);
2328
- const headers = buildHeaders(config.accessKey, config.secretKey);
2329
- const response = await fetch(`${endpoint}/${input.bucket}/${encodeURIComponent(input.key)}`, {
2457
+ const url = `${buildEndpoint(region)}/${input.bucket}/${encodeURIComponent(input.key)}`;
2458
+ const headers = buildSignedHeaders({
2459
+ method: "DELETE",
2460
+ url,
2461
+ accessKey: config.accessKey,
2462
+ secretKey: config.secretKey,
2463
+ region
2464
+ });
2465
+ const response = await fetch(url, {
2330
2466
  method: "DELETE",
2331
2467
  headers
2332
2468
  });
@@ -2345,9 +2481,15 @@ async function handleGetBucketPolicy(input) {
2345
2481
  try {
2346
2482
  const config = loadAuthConfig();
2347
2483
  const region = input.region ?? config.defaultRegion;
2348
- const endpoint = buildEndpoint(region);
2349
- const headers = buildHeaders(config.accessKey, config.secretKey);
2350
- const response = await fetch(`${endpoint}/${input.bucket}?policy`, {
2484
+ const url = `${buildEndpoint(region)}/${input.bucket}?policy`;
2485
+ const headers = buildSignedHeaders({
2486
+ method: "GET",
2487
+ url,
2488
+ accessKey: config.accessKey,
2489
+ secretKey: config.secretKey,
2490
+ region
2491
+ });
2492
+ const response = await fetch(url, {
2351
2493
  method: "GET",
2352
2494
  headers
2353
2495
  });
@@ -2368,12 +2510,17 @@ async function handleSetBucketPolicy(input) {
2368
2510
  try {
2369
2511
  const config = loadAuthConfig();
2370
2512
  const region = input.region ?? config.defaultRegion;
2371
- const endpoint = buildEndpoint(region);
2372
- const headers = {
2373
- ...buildHeaders(config.accessKey, config.secretKey),
2374
- "Content-Type": "application/json"
2375
- };
2376
- const response = await fetch(`${endpoint}/${input.bucket}?policy`, {
2513
+ const url = `${buildEndpoint(region)}/${input.bucket}?policy`;
2514
+ const headers = buildSignedHeaders({
2515
+ method: "PUT",
2516
+ url,
2517
+ accessKey: config.accessKey,
2518
+ secretKey: config.secretKey,
2519
+ region,
2520
+ extraHeaders: { "Content-Type": "application/json" },
2521
+ body: input.policy
2522
+ });
2523
+ const response = await fetch(url, {
2377
2524
  method: "PUT",
2378
2525
  headers,
2379
2526
  body: input.policy
@@ -2391,9 +2538,15 @@ async function handleGetBucketLifecycle(input) {
2391
2538
  try {
2392
2539
  const config = loadAuthConfig();
2393
2540
  const region = input.region ?? config.defaultRegion;
2394
- const endpoint = buildEndpoint(region);
2395
- const headers = buildHeaders(config.accessKey, config.secretKey);
2396
- const response = await fetch(`${endpoint}/${input.bucket}?lifecycle`, {
2541
+ const url = `${buildEndpoint(region)}/${input.bucket}?lifecycle`;
2542
+ const headers = buildSignedHeaders({
2543
+ method: "GET",
2544
+ url,
2545
+ accessKey: config.accessKey,
2546
+ secretKey: config.secretKey,
2547
+ region
2548
+ });
2549
+ const response = await fetch(url, {
2397
2550
  method: "GET",
2398
2551
  headers
2399
2552
  });
@@ -2415,13 +2568,18 @@ async function handleSetBucketLifecycle(input) {
2415
2568
  try {
2416
2569
  const config = loadAuthConfig();
2417
2570
  const region = input.region ?? config.defaultRegion;
2418
- const endpoint = buildEndpoint(region);
2419
- const headers = {
2420
- ...buildHeaders(config.accessKey, config.secretKey),
2421
- "Content-Type": "application/xml"
2422
- };
2571
+ const url = `${buildEndpoint(region)}/${input.bucket}?lifecycle`;
2423
2572
  const xml = buildLifecycleXml(input.rules);
2424
- const response = await fetch(`${endpoint}/${input.bucket}?lifecycle`, {
2573
+ const headers = buildSignedHeaders({
2574
+ method: "PUT",
2575
+ url,
2576
+ accessKey: config.accessKey,
2577
+ secretKey: config.secretKey,
2578
+ region,
2579
+ extraHeaders: { "Content-Type": "application/xml" },
2580
+ body: xml
2581
+ });
2582
+ const response = await fetch(url, {
2425
2583
  method: "PUT",
2426
2584
  headers,
2427
2585
  body: xml
@@ -2441,9 +2599,15 @@ async function handleGetBucketVersioning(input) {
2441
2599
  try {
2442
2600
  const config = loadAuthConfig();
2443
2601
  const region = input.region ?? config.defaultRegion;
2444
- const endpoint = buildEndpoint(region);
2445
- const headers = buildHeaders(config.accessKey, config.secretKey);
2446
- const response = await fetch(`${endpoint}/${input.bucket}?versioning`, {
2602
+ const url = `${buildEndpoint(region)}/${input.bucket}?versioning`;
2603
+ const headers = buildSignedHeaders({
2604
+ method: "GET",
2605
+ url,
2606
+ accessKey: config.accessKey,
2607
+ secretKey: config.secretKey,
2608
+ region
2609
+ });
2610
+ const response = await fetch(url, {
2447
2611
  method: "GET",
2448
2612
  headers
2449
2613
  });
@@ -2462,13 +2626,18 @@ async function handleSetBucketVersioning(input) {
2462
2626
  try {
2463
2627
  const config = loadAuthConfig();
2464
2628
  const region = input.region ?? config.defaultRegion;
2465
- const endpoint = buildEndpoint(region);
2466
- const headers = {
2467
- ...buildHeaders(config.accessKey, config.secretKey),
2468
- "Content-Type": "application/xml"
2469
- };
2629
+ const url = `${buildEndpoint(region)}/${input.bucket}?versioning`;
2470
2630
  const xml = `<?xml version="1.0" encoding="UTF-8"?><VersioningConfiguration><Status>${input.status}</Status></VersioningConfiguration>`;
2471
- const response = await fetch(`${endpoint}/${input.bucket}?versioning`, {
2631
+ const headers = buildSignedHeaders({
2632
+ method: "PUT",
2633
+ url,
2634
+ accessKey: config.accessKey,
2635
+ secretKey: config.secretKey,
2636
+ region,
2637
+ extraHeaders: { "Content-Type": "application/xml" },
2638
+ body: xml
2639
+ });
2640
+ const response = await fetch(url, {
2472
2641
  method: "PUT",
2473
2642
  headers,
2474
2643
  body: xml
@@ -11940,7 +12109,7 @@ var CreateEmbeddingInputSchema = z27.object({
11940
12109
  });
11941
12110
 
11942
12111
  // src/tools/generative-apis/handlers.ts
11943
- function buildHeaders2() {
12112
+ function buildHeaders() {
11944
12113
  const config = loadAuthConfig();
11945
12114
  return {
11946
12115
  Authorization: `Bearer ${config.secretKey}`,
@@ -11955,7 +12124,7 @@ async function handleListModels(input) {
11955
12124
  const baseUrl2 = buildBaseUrl(input.region);
11956
12125
  const response = await fetch(`${baseUrl2}/v1/models`, {
11957
12126
  method: "GET",
11958
- headers: buildHeaders2()
12127
+ headers: buildHeaders()
11959
12128
  });
11960
12129
  if (!response.ok) {
11961
12130
  const errorBody = await response.text();
@@ -11976,7 +12145,7 @@ async function handleGetModel(input) {
11976
12145
  const baseUrl2 = buildBaseUrl(input.region);
11977
12146
  const response = await fetch(`${baseUrl2}/v1/models`, {
11978
12147
  method: "GET",
11979
- headers: buildHeaders2()
12148
+ headers: buildHeaders()
11980
12149
  });
11981
12150
  if (!response.ok) {
11982
12151
  const errorBody = await response.text();
@@ -12011,7 +12180,7 @@ async function handleChatCompletion(input) {
12011
12180
  };
12012
12181
  const response = await fetch(`${baseUrl2}/v1/chat/completions`, {
12013
12182
  method: "POST",
12014
- headers: buildHeaders2(),
12183
+ headers: buildHeaders(),
12015
12184
  body: JSON.stringify(body)
12016
12185
  });
12017
12186
  if (!response.ok) {
@@ -12037,7 +12206,7 @@ async function handleCreateEmbedding(input) {
12037
12206
  };
12038
12207
  const response = await fetch(`${baseUrl2}/v1/embeddings`, {
12039
12208
  method: "POST",
12040
- headers: buildHeaders2(),
12209
+ headers: buildHeaders(),
12041
12210
  body: JSON.stringify(body)
12042
12211
  });
12043
12212
  if (!response.ok) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-scaleway",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "MCP server for Scaleway cloud provider",
5
5
  "type": "module",
6
6
  "main": "src/server.ts",