@fload-ai/mcp 0.2.1 → 0.3.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.
@@ -1,3 +1,26 @@
1
+ // src/public-errors.ts
2
+ var INTERNAL_ERROR_PATTERNS = [
3
+ /\b(stack|trace|at\s+[\w.$<>]+\s*\()/i,
4
+ /\b(sql|postgres|drizzle|database|constraint|duplicate key|violates|relation)\b/i,
5
+ /\b(fetch|undici|socket|ECONNRESET|ETIMEDOUT|ENOTFOUND|EAI_AGAIN|HTTP\s+\d{3})\b/i,
6
+ /\b(token|secret|api[_-]?key|authorization|bearer|cookie|password)\b/i,
7
+ /\b[a-z0-9]+_(secret|token|key)\b/i,
8
+ /\b(secret|token|key)[_-][a-z0-9]+\b/i,
9
+ /\b(scrap|browser|patchright|playwright|chromium|page\.|locator|selector)\b/i,
10
+ /\b(redis|bullmq|queue|job id|worker)\b/i
11
+ ];
12
+ function looksInternal(message) {
13
+ return INTERNAL_ERROR_PATTERNS.some((pattern) => pattern.test(message));
14
+ }
15
+ function publicMcpErrorMessage(message, fallbackMessage = "Fload could not complete the request. Please try again.") {
16
+ const raw = message instanceof Error ? message.message : typeof message === "string" ? message : "";
17
+ const normalized = raw.replace(/\s+/g, " ").trim();
18
+ if (!normalized || normalized.length > 180 || looksInternal(normalized)) {
19
+ return fallbackMessage;
20
+ }
21
+ return normalized;
22
+ }
23
+
1
24
  // src/api-client.ts
2
25
  var FloadApiClient = class {
3
26
  constructor(baseUrl, apiKey) {
@@ -6,17 +29,22 @@ var FloadApiClient = class {
6
29
  }
7
30
  async request(path, options = {}) {
8
31
  const url = `${this.baseUrl}${path}`;
32
+ const headers = {
33
+ Authorization: `Bearer ${this.apiKey}`,
34
+ ...options.body ? { "Content-Type": "application/json" } : {},
35
+ ...options.headers
36
+ };
9
37
  const response = await fetch(url, {
10
38
  ...options,
11
- headers: {
12
- "Authorization": `Bearer ${this.apiKey}`,
13
- "Content-Type": "application/json",
14
- ...options.headers
15
- }
39
+ headers
16
40
  });
17
41
  if (!response.ok) {
18
42
  const error = await response.json().catch(() => ({ message: response.statusText }));
19
- throw new Error(error.message || `API error: ${response.status}`);
43
+ const safeError = publicMcpErrorMessage(
44
+ error.publicMessage || error.message || response.statusText,
45
+ response.status >= 500 ? "Fload could not complete the request. Please try again." : "Fload rejected the request. Please check access and try again."
46
+ );
47
+ throw new Error(safeError);
20
48
  }
21
49
  return response.json();
22
50
  }
@@ -46,6 +74,31 @@ var FloadApiClient = class {
46
74
  async delete(path) {
47
75
  return this.request(path, { method: "DELETE" });
48
76
  }
77
+ /**
78
+ * POST to a streaming endpoint (SSE / AI SDK UI-message stream) and
79
+ * return the raw response body as text. Callers parse the event lines.
80
+ */
81
+ async postStream(path, body) {
82
+ const url = `${this.baseUrl}${path}`;
83
+ const response = await fetch(url, {
84
+ method: "POST",
85
+ headers: {
86
+ Authorization: `Bearer ${this.apiKey}`,
87
+ "Content-Type": "application/json",
88
+ Accept: "text/event-stream"
89
+ },
90
+ body: body ? JSON.stringify(body) : void 0
91
+ });
92
+ if (!response.ok) {
93
+ const error = await response.json().catch(() => ({ message: response.statusText }));
94
+ const safeError = publicMcpErrorMessage(
95
+ error.publicMessage || error.message || response.statusText,
96
+ response.status >= 500 ? "Fload could not complete the request. Please try again." : "Fload rejected the request. Please check access and try again."
97
+ );
98
+ throw new Error(safeError);
99
+ }
100
+ return response.text();
101
+ }
49
102
  };
50
103
  export {
51
104
  FloadApiClient
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../src/api-client.ts"],
4
- "sourcesContent": ["/**\n * HTTP client for calling the Fload API\n * Replaces direct database access\n */\n\nexport class FloadApiClient {\n constructor(\n private baseUrl: string,\n private apiKey: string\n ) {}\n\n private async request(path: string, options: RequestInit = {}): Promise<any> {\n const url = `${this.baseUrl}${path}`;\n const response = await fetch(url, {\n ...options,\n headers: {\n 'Authorization': `Bearer ${this.apiKey}`,\n 'Content-Type': 'application/json',\n ...options.headers,\n },\n });\n\n if (!response.ok) {\n const error = await response.json().catch(() => ({ message: response.statusText })) as { message?: string };\n throw new Error(error.message || `API error: ${response.status}`);\n }\n\n return response.json();\n }\n\n async get(path: string, params?: Record<string, string | number | boolean | undefined>): Promise<any> {\n const searchParams = new URLSearchParams();\n if (params) {\n for (const [key, value] of Object.entries(params)) {\n if (value !== undefined) searchParams.set(key, String(value));\n }\n }\n const query = searchParams.toString();\n return this.request(`${path}${query ? `?${query}` : ''}`);\n }\n\n async post(path: string, body?: any): Promise<any> {\n return this.request(path, {\n method: 'POST',\n body: body ? JSON.stringify(body) : undefined,\n });\n }\n\n async patch(path: string, body?: any): Promise<any> {\n return this.request(path, {\n method: 'PATCH',\n body: body ? JSON.stringify(body) : undefined,\n });\n }\n\n async delete(path: string): Promise<any> {\n return this.request(path, { method: 'DELETE' });\n }\n}\n"],
5
- "mappings": ";AAKO,IAAM,iBAAN,MAAqB;AAAA,EAC1B,YACU,SACA,QACR;AAFQ;AACA;AAAA,EACP;AAAA,EAEH,MAAc,QAAQ,MAAc,UAAuB,CAAC,GAAiB;AAC3E,UAAM,MAAM,GAAG,KAAK,OAAO,GAAG,IAAI;AAClC,UAAM,WAAW,MAAM,MAAM,KAAK;AAAA,MAChC,GAAG;AAAA,MACH,SAAS;AAAA,QACP,iBAAiB,UAAU,KAAK,MAAM;AAAA,QACtC,gBAAgB;AAAA,QAChB,GAAG,QAAQ;AAAA,MACb;AAAA,IACF,CAAC;AAED,QAAI,CAAC,SAAS,IAAI;AAChB,YAAM,QAAQ,MAAM,SAAS,KAAK,EAAE,MAAM,OAAO,EAAE,SAAS,SAAS,WAAW,EAAE;AAClF,YAAM,IAAI,MAAM,MAAM,WAAW,cAAc,SAAS,MAAM,EAAE;AAAA,IAClE;AAEA,WAAO,SAAS,KAAK;AAAA,EACvB;AAAA,EAEA,MAAM,IAAI,MAAc,QAA8E;AACpG,UAAM,eAAe,IAAI,gBAAgB;AACzC,QAAI,QAAQ;AACV,iBAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,MAAM,GAAG;AACjD,YAAI,UAAU;AAAW,uBAAa,IAAI,KAAK,OAAO,KAAK,CAAC;AAAA,MAC9D;AAAA,IACF;AACA,UAAM,QAAQ,aAAa,SAAS;AACpC,WAAO,KAAK,QAAQ,GAAG,IAAI,GAAG,QAAQ,IAAI,KAAK,KAAK,EAAE,EAAE;AAAA,EAC1D;AAAA,EAEA,MAAM,KAAK,MAAc,MAA0B;AACjD,WAAO,KAAK,QAAQ,MAAM;AAAA,MACxB,QAAQ;AAAA,MACR,MAAM,OAAO,KAAK,UAAU,IAAI,IAAI;AAAA,IACtC,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,MAAM,MAAc,MAA0B;AAClD,WAAO,KAAK,QAAQ,MAAM;AAAA,MACxB,QAAQ;AAAA,MACR,MAAM,OAAO,KAAK,UAAU,IAAI,IAAI;AAAA,IACtC,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,OAAO,MAA4B;AACvC,WAAO,KAAK,QAAQ,MAAM,EAAE,QAAQ,SAAS,CAAC;AAAA,EAChD;AACF;",
3
+ "sources": ["../src/public-errors.ts", "../src/api-client.ts"],
4
+ "sourcesContent": ["const INTERNAL_ERROR_PATTERNS: RegExp[] = [\n /\\b(stack|trace|at\\s+[\\w.$<>]+\\s*\\()/i,\n /\\b(sql|postgres|drizzle|database|constraint|duplicate key|violates|relation)\\b/i,\n /\\b(fetch|undici|socket|ECONNRESET|ETIMEDOUT|ENOTFOUND|EAI_AGAIN|HTTP\\s+\\d{3})\\b/i,\n /\\b(token|secret|api[_-]?key|authorization|bearer|cookie|password)\\b/i,\n /\\b[a-z0-9]+_(secret|token|key)\\b/i,\n /\\b(secret|token|key)[_-][a-z0-9]+\\b/i,\n /\\b(scrap|browser|patchright|playwright|chromium|page\\.|locator|selector)\\b/i,\n /\\b(redis|bullmq|queue|job id|worker)\\b/i,\n];\n\nfunction looksInternal(message: string): boolean {\n return INTERNAL_ERROR_PATTERNS.some(pattern => pattern.test(message));\n}\n\nexport function publicMcpErrorMessage(\n message: unknown,\n fallbackMessage = 'Fload could not complete the request. Please try again.'\n): string {\n const raw =\n message instanceof Error\n ? message.message\n : typeof message === 'string'\n ? message\n : '';\n const normalized = raw.replace(/\\s+/g, ' ').trim();\n\n if (!normalized || normalized.length > 180 || looksInternal(normalized)) {\n return fallbackMessage;\n }\n\n return normalized;\n}\n", "/**\n * HTTP client for calling the Fload API\n * Replaces direct database access\n */\n\nimport { publicMcpErrorMessage } from './public-errors';\n\nexport class FloadApiClient {\n constructor(\n private baseUrl: string,\n private apiKey: string\n ) {}\n\n private async request<TResponse = Record<string, unknown>>(\n path: string,\n options: RequestInit = {}\n ): Promise<TResponse> {\n const url = `${this.baseUrl}${path}`;\n // Only claim a JSON body when there is one \u2014 Fastify rejects\n // Content-Type: application/json requests with an empty body.\n const headers: Record<string, string> = {\n Authorization: `Bearer ${this.apiKey}`,\n ...(options.body ? { 'Content-Type': 'application/json' } : {}),\n ...(options.headers as Record<string, string> | undefined),\n };\n const response = await fetch(url, {\n ...options,\n headers,\n });\n\n if (!response.ok) {\n const error = (await response\n .json()\n .catch(() => ({ message: response.statusText }))) as {\n error?: string;\n message?: string;\n publicMessage?: string;\n };\n const safeError = publicMcpErrorMessage(\n error.publicMessage || error.message || response.statusText,\n response.status >= 500\n ? 'Fload could not complete the request. Please try again.'\n : 'Fload rejected the request. Please check access and try again.'\n );\n throw new Error(safeError);\n }\n\n return response.json() as Promise<TResponse>;\n }\n\n async get<TResponse = Record<string, unknown>>(\n path: string,\n params?: Record<string, string | number | boolean | undefined>\n ): Promise<TResponse> {\n const searchParams = new URLSearchParams();\n if (params) {\n for (const [key, value] of Object.entries(params)) {\n if (value !== undefined) searchParams.set(key, String(value));\n }\n }\n const query = searchParams.toString();\n return this.request<TResponse>(`${path}${query ? `?${query}` : ''}`);\n }\n\n async post<TResponse = Record<string, unknown>>(\n path: string,\n body?: unknown\n ): Promise<TResponse> {\n return this.request<TResponse>(path, {\n method: 'POST',\n body: body ? JSON.stringify(body) : undefined,\n });\n }\n\n async patch<TResponse = Record<string, unknown>>(\n path: string,\n body?: unknown\n ): Promise<TResponse> {\n return this.request<TResponse>(path, {\n method: 'PATCH',\n body: body ? JSON.stringify(body) : undefined,\n });\n }\n\n async delete<TResponse = Record<string, unknown>>(\n path: string\n ): Promise<TResponse> {\n return this.request<TResponse>(path, { method: 'DELETE' });\n }\n\n /**\n * POST to a streaming endpoint (SSE / AI SDK UI-message stream) and\n * return the raw response body as text. Callers parse the event lines.\n */\n async postStream(path: string, body?: unknown): Promise<string> {\n const url = `${this.baseUrl}${path}`;\n const response = await fetch(url, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${this.apiKey}`,\n 'Content-Type': 'application/json',\n Accept: 'text/event-stream',\n },\n body: body ? JSON.stringify(body) : undefined,\n });\n\n if (!response.ok) {\n const error = (await response\n .json()\n .catch(() => ({ message: response.statusText }))) as {\n error?: string;\n message?: string;\n publicMessage?: string;\n };\n const safeError = publicMcpErrorMessage(\n error.publicMessage || error.message || response.statusText,\n response.status >= 500\n ? 'Fload could not complete the request. Please try again.'\n : 'Fload rejected the request. Please check access and try again.'\n );\n throw new Error(safeError);\n }\n\n return response.text();\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }