toolbox-co-mcp-bridge 1.0.8 → 1.0.9

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 (3) hide show
  1. package/Dockerfile +7 -0
  2. package/index.js +551 -69
  3. package/package.json +10 -6
package/Dockerfile ADDED
@@ -0,0 +1,7 @@
1
+ FROM node:20-alpine
2
+ WORKDIR /app
3
+ COPY package*.json ./
4
+ RUN npm install --only=production
5
+ COPY . .
6
+ EXPOSE 8080
7
+ CMD ["npm", "start"]
package/index.js CHANGED
@@ -1,86 +1,568 @@
1
- #!/usr/bin/env node
2
- import fetch from "node-fetch";
3
- import readline from "readline";
1
+ import express from "express";
2
+ import cors from "cors";
3
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
4
+ import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
4
5
  import crypto from "crypto";
6
+ import bcrypt from "bcryptjs";
7
+ import yaml from "yaml";
5
8
 
6
- const baseUrl = "https://toolbox-mcp-1058072229791.us-central1.run.app";
7
- const cid = crypto.randomBytes(8).toString('hex');
8
- const sseInitUrl = `${baseUrl}/sseInit?sessionId=${cid}&t=${Date.now()}`;
9
- const messageUrl = `${baseUrl}/message?sessionId=${cid}`;
9
+ import {
10
+ CallToolRequestSchema,
11
+ ListToolsRequestSchema,
12
+ } from "@modelcontextprotocol/sdk/types.js";
10
13
 
11
- let isConnected = false;
12
- const messageQueue = [];
13
- let gcrCookie = '';
14
+ const app = express();
15
+ app.use(cors());
16
+
17
+ // List of all 47 ToolBox Tools
18
+ const ALL_TOOLS = [
19
+ { name: "format_json", desc: "Pretty prints and formats raw JSON string.", schema: { text: "string" } },
20
+ { name: "generate_uuid", desc: "Generates v4 UUIDs.", schema: {} },
21
+ { name: "encode_base64", desc: "Encodes plain text to Base64.", schema: { text: "string" } },
22
+ { name: "decode_base64", desc: "Decodes Base64 to plain text.", schema: { text: "string" } },
23
+ { name: "generate_password", desc: "Creates a secure password. Args: length (number), special (boolean).", schema: { length: "number", special: "boolean" } },
24
+ { name: "url_encode", desc: "URL encodes text.", schema: { text: "string" } },
25
+ { name: "url_decode", desc: "URL decodes text.", schema: { text: "string" } },
26
+ { name: "generate_hash", desc: "SHA256/MD5/SHA1 hash. Args: text, algo.", schema: { text: "string", algo: "string" } },
27
+ { name: "decode_jwt", desc: "Decodes JWT payload without verifying signature.", schema: { token: "string" } },
28
+ { name: "text_analyzer", desc: "Returns word/char count.", schema: { text: "string" } },
29
+ { name: "bcrypt_hash", desc: "Generates bcrypt hash from password.", schema: { text: "string", rounds: "number" } },
30
+ { name: "bcrypt_compare", desc: "Checks password against bcrypt hash. Args: text, hash.", schema: { text: "string", hash: "string" } },
31
+ { name: "color_hex_to_rgb", desc: "Converts HEX color to RGB formatting.", schema: { hex: "string" } },
32
+ { name: "html_encode", desc: "Converts characters to HTML entities.", schema: { text: "string" } },
33
+ { name: "html_decode", desc: "Converts HTML entities to characters.", schema: { text: "string" } },
34
+ { name: "yaml_to_json", desc: "Converts YAML to JSON string.", schema: { yamlText: "string" } },
35
+ { name: "json_to_yaml", desc: "Converts JSON to YAML string.", schema: { jsonText: "string" } },
36
+ { name: "utm_builder", desc: "Builds a complete UTM tracked URL. Args: url, source, medium, campaign.", schema: { url: "string", source: "string", medium: "string", campaign: "string" } },
37
+ { name: "social_media_limits", desc: "Checks platform constraints for social text.", schema: { text: "string" } },
38
+ { name: "json_to_csv", desc: "Converts flat JSON array string to CSV.", schema: { jsonArray: "string" } },
39
+ { name: "case_converter", desc: "Changes string case. Args: text, targetCase (upper, lower, camel, snake, kebab, title).", schema: { text: "string", targetCase: "string" } },
40
+ { name: "lorem_ipsum", desc: "Generates dummy text. Args: paragraphs (number).", schema: { paragraphs: "number" } },
41
+ { name: "duplicate_remover", desc: "Removes duplicate lines from text block.", schema: { text: "string" } },
42
+ { name: "freelance_rate_calc", desc: "Converts hourly rate to annual, or vice-versa. Args: rate, type (hourly|annual), hoursPerWeek.", schema: { rate: "number", type: "string", hoursPerWeek: "number" } },
43
+ { name: "tip_splitter", desc: "Calculates total and per-person cost. Args: bill, tipPercent, people.", schema: { bill: "number", tipPercent: "number", people: "number" } },
44
+ { name: "date_difference", desc: "Calculates days between two ISO dates. Args: start, end.", schema: { start: "string", end: "string" } },
45
+ { name: "percentage_calculator", desc: "Calculates what X percent of Y is, or what percent X is of Y. Args: x, y, mode (percentOf|isWhatPercentOf).", schema: { x: "number", y: "number", mode: "string" } },
46
+ { name: "ig_spacer", desc: "Replaces standard linebreaks with instagram-friendly zero-width joiners.", schema: { text: "string" } },
47
+ { name: "hashtag_mixer", desc: "Randomly shuffles a list of hashtags.", schema: { tags: "string" } },
48
+ { name: "regex_tester", desc: "Tests text against regex. Args: pattern, flags, testString.", schema: { pattern: "string", flags: "string", testString: "string" } },
49
+ { name: "loan_amortization_simulator", desc: "Simulates monthly payments and interest schedules for a loan. Args: principal (number), rate (number), termYears (number).", schema: { principal: "number", rate: "number", termYears: "number" } },
50
+ { name: "options_pricing_black_scholes", desc: "Calculates European option prices using Black-Scholes. Args: underlyingPrice (number), strikePrice (number), yearsToExpiry (number), volatility (number), riskFreeRate (number).", schema: { underlyingPrice: "number", strikePrice: "number", yearsToExpiry: "number", volatility: "number", riskFreeRate: "number" } },
51
+ { name: "cap_table_dilution", desc: "Calculates post-round ownership and shares. Args: preMoneyValuation (number), investmentAmount (number), founderShares (number).", schema: { preMoneyValuation: "number", investmentAmount: "number", founderShares: "number" } },
52
+ { name: "nda_clause_auditor", desc: "Audits NDAs for restrictive or missing clauses. Args: text (string).", schema: { text: "string" } },
53
+ { name: "gdpr_privacy_auditor", desc: "Checks privacy policy text for basic GDPR compliance. Args: text (string).", schema: { text: "string" } },
54
+ { name: "trademark_snippet_compiler", desc: "Compiles a standardized license header block. Args: licenseType (string), holderName (string), year (string).", schema: { licenseType: "string", holderName: "string", year: "string" } },
55
+ { name: "csv_to_sql_ddl", desc: "Generates SQL CREATE TABLE and INSERT statements from raw CSV. Args: csvText (string), tableName (string).", schema: { csvText: "string", tableName: "string" } },
56
+ { name: "json_schema_to_openapi", desc: "Generates an OpenAPI spec block from JSON. Args: jsonText (string).", schema: { jsonText: "string" } },
57
+ { name: "statistical_hypothesis_solver", desc: "Calculates t-statistic and p-value for two sample means. Args: mean1 (number), mean2 (number), sd1 (number), sd2 (number), n1 (number), n2 (number).", schema: { mean1: "number", mean2: "number", sd1: "number", sd2: "number", n1: "number", n2: "number" } },
58
+ { name: "volumetric_shipping_estimator", desc: "Calculates volumetric weight and flags shipping over-billing. Args: length (number), width (number), height (number), weight (number), divisor (number).", schema: { length: "number", width: "number", height: "number", weight: "number", divisor: "number" } },
59
+ { name: "product_profit_margin", desc: "Calculates product margins and return on ad spend. Args: costOfGoods (number), retailPrice (number), marketingSpend (number), unitsSold (number).", schema: { costOfGoods: "number", retailPrice: "number", marketingSpend: "number", unitsSold: "number" } },
60
+ { name: "ecom_listing_seo_auditor", desc: "Audits e-commerce listings for titles, description lengths, and SEO. Args: title (string), description (string).", schema: { title: "string", description: "string" } },
61
+ { name: "rental_yield_calculator", desc: "Calculates rental yield and cap rates for a real estate asset. Args: purchasePrice (number), monthlyRent (number), operatingExpenses (number).", schema: { purchasePrice: "number", monthlyRent: "number", operatingExpenses: "number" } },
62
+ { name: "lease_cost_escalation", desc: "Generates a compound rent escalation schedule over time. Args: baseRent (number), escalationRate (number), years (number).", schema: { baseRent: "number", escalationRate: "number", years: "number" } },
63
+ { name: "tabular_synthetic_generator", desc: "Generates synthetic CSV rows for healthcare, finance, or clickstream. Args: domain (string), sampleSize (number).", schema: { domain: "string", sampleSize: "number" } },
64
+ { name: "academic_anonymizer", desc: "Anonymizes CSV text client-side via SHA-256 tokens and binning. Args: csvText (string), salt (string).", schema: { csvText: "string", salt: "string" } },
65
+ { name: "hybrid_dataset_fuser", desc: "Fuses and scales up CSV baseline datasets while preserving statistics. Args: csvText (string), multiplier (number).", schema: { csvText: "string", multiplier: "number" } },
66
+ ];
67
+
68
+ function createServer() {
69
+ const mcpServer = new Server({ name: "toolbox-co-remote", version: "1.0.0" }, { capabilities: { tools: {} } });
70
+
71
+ mcpServer.setRequestHandler(ListToolsRequestSchema, async () => {
72
+ return {
73
+ tools: ALL_TOOLS.map(t => {
74
+ const props = {};
75
+ const required = Object.keys(t.schema);
76
+ for (const [key, type] of Object.entries(t.schema)) {
77
+ props[key] = { type };
78
+ }
79
+ return {
80
+ name: t.name,
81
+ description: t.desc,
82
+ inputSchema: { type: "object", properties: props, required }
83
+ };
84
+ })
85
+ };
86
+ });
87
+
88
+ mcpServer.setRequestHandler(CallToolRequestSchema, async (request) => {
89
+ const { name, arguments: args } = request.params;
90
+ let res = "";
14
91
 
15
- async function sendPost(msg) {
16
92
  try {
17
- const headers = { 'Content-Type': 'application/json' };
18
- if (gcrCookie) { headers['Cookie'] = gcrCookie; }
19
-
20
- const response = await fetch(messageUrl, {
21
- method: 'POST',
22
- headers,
23
- body: msg
24
- });
25
- if (!response.ok) {
26
- console.error(`HTTP Error from ToolBox server: ${response.status} ${response.statusText}`);
27
- console.error(`Server response body: ${await response.text()}`);
93
+ switch (name) {
94
+ case "format_json": res = JSON.stringify(JSON.parse(args.text), null, 2); break;
95
+ case "generate_uuid": res = crypto.randomUUID(); break;
96
+ case "encode_base64": res = Buffer.from(args.text).toString("base64"); break;
97
+ case "decode_base64": res = Buffer.from(args.text, "base64").toString("utf-8"); break;
98
+ case "generate_password":
99
+ const len = args.length || 16;
100
+ const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + (args.special !== false ? "!@#$%^&*()_+-=" : "");
101
+ for (let i = 0; i < len; i++) res += chars[Math.floor(Math.random() * chars.length)];
102
+ break;
103
+ case "url_encode": res = encodeURIComponent(args.text); break;
104
+ case "url_decode": res = decodeURIComponent(args.text); break;
105
+ case "generate_hash": res = crypto.createHash(args.algo || "sha256").update(args.text).digest("hex"); break;
106
+ case "decode_jwt":
107
+ const parts = args.token.split(".");
108
+ if (parts.length !== 3) throw new Error("Invalid JWT");
109
+ res = JSON.stringify({
110
+ header: JSON.parse(Buffer.from(parts[0], "base64url").toString()),
111
+ payload: JSON.parse(Buffer.from(parts[1], "base64url").toString())
112
+ }, null, 2); break;
113
+ case "text_analyzer":
114
+ res = JSON.stringify({
115
+ chars: args.text.length,
116
+ words: args.text.split(/\s+/).filter(w => w.length).length,
117
+ lines: args.text.split('\n').length
118
+ }, null, 2); break;
119
+ case "bcrypt_hash": res = bcrypt.hashSync(args.text, args.rounds || 10); break;
120
+ case "bcrypt_compare": res = bcrypt.compareSync(args.text, args.hash) ? "Match!" : "Does not match."; break;
121
+ case "color_hex_to_rgb":
122
+ let hex = args.hex.replace(/^#/, '');
123
+ if (hex.length === 3) hex = hex.split('').map(x => x+x).join('');
124
+ const r = parseInt(hex.substring(0, 2), 16), g = parseInt(hex.substring(2, 4), 16), b = parseInt(hex.substring(4, 6), 16);
125
+ if (isNaN(r) || isNaN(g) || isNaN(b)) throw new Error("Invalid hex");
126
+ res = `rgb(${r}, ${g}, ${b})`; break;
127
+ case "html_encode": res = args.text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;"); break;
128
+ case "html_decode": res = args.text.replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&#39;/g, "'").replace(/&amp;/g, "&"); break;
129
+ case "yaml_to_json": res = JSON.stringify(yaml.parse(args.yamlText), null, 2); break;
130
+ case "json_to_yaml": res = yaml.stringify(JSON.parse(args.jsonText)); break;
131
+ case "utm_builder":
132
+ const u = new URL(args.url.startsWith('http') ? args.url : `https://${args.url}`);
133
+ if (args.source) u.searchParams.set('utm_source', args.source);
134
+ if (args.medium) u.searchParams.set('utm_medium', args.medium);
135
+ if (args.campaign) u.searchParams.set('utm_campaign', args.campaign);
136
+ res = u.toString(); break;
137
+ case "social_media_limits":
138
+ const t = args.text;
139
+ res = JSON.stringify({ length: t.length, twitterOk: t.length <= 280, igOk: t.length <= 2200, linkedinOk: t.length <= 3000 }, null, 2); break;
140
+ case "json_to_csv":
141
+ const arr = JSON.parse(args.jsonArray);
142
+ if (!Array.isArray(arr) || !arr.length) throw new Error("Need JSON array of objects");
143
+ const keys = Object.keys(arr[0]);
144
+ res = [keys.join(","), ...arr.map(row => keys.map(k => `"${row[k]}"`).join(","))].join("\n"); break;
145
+ case "case_converter":
146
+ const txt = args.text;
147
+ switch (args.targetCase) {
148
+ case "upper": res = txt.toUpperCase(); break;
149
+ case "lower": res = txt.toLowerCase(); break;
150
+ case "camel": res = txt.replace(/(?:^\w|[A-Z]|\b\w|\s+)/g, (match, index) => { if (+match === 0) return ""; return index === 0 ? match.toLowerCase() : match.toUpperCase(); }); break;
151
+ case "snake": res = txt.replace(/\W+/g, " ").split(/ |\B(?=[A-Z])/).map(w => w.toLowerCase()).join('_'); break;
152
+ case "kebab": res = txt.replace(/\W+/g, " ").split(/ |\B(?=[A-Z])/).map(w => w.toLowerCase()).join('-'); break;
153
+ case "title": res = txt.replace(/\w\S*/g, (t) => t.charAt(0).toUpperCase() + t.substr(1).toLowerCase()); break;
154
+ default: throw new Error("Invalid case type");
155
+ } break;
156
+ case "lorem_ipsum":
157
+ const dummy = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.";
158
+ res = Array(args.paragraphs || 1).fill(dummy).join("\n\n"); break;
159
+ case "duplicate_remover":
160
+ const lines = args.text.split("\n");
161
+ res = [...new Set(lines)].join("\n"); break;
162
+ case "freelance_rate_calc":
163
+ const hpw = args.hoursPerWeek || 40;
164
+ if (args.type === "hourly") res = `Annual Rate: $${Math.round(args.rate * hpw * 52)}`;
165
+ else res = `Hourly Rate: $${(args.rate / (hpw * 52)).toFixed(2)}`; break;
166
+ case "tip_splitter":
167
+ const tipAmt = args.bill * (args.tipPercent / 100);
168
+ const tot = args.bill + tipAmt;
169
+ res = `Total: $${tot.toFixed(2)}, Per Person: $${(tot / args.people).toFixed(2)}`; break;
170
+ case "date_difference":
171
+ const d1 = new Date(args.start), d2 = new Date(args.end);
172
+ res = `${Math.abs(d2 - d1) / (1000 * 60 * 60 * 24)} days`; break;
173
+ case "percentage_calculator":
174
+ if (args.mode === "percentOf") res = `${(args.x / 100) * args.y}`;
175
+ else res = `${(args.x / args.y) * 100}%`; break;
176
+ case "ig_spacer": res = args.text.replace(/\n/g, "\n\u2063\n"); break;
177
+ case "hashtag_mixer":
178
+ let tags = args.tags.split(/[\s,]+/).filter(t => t.length);
179
+ res = tags.sort(() => 0.5 - Math.random()).join(" "); break;
180
+ case "regex_tester":
181
+ const re = new RegExp(args.pattern, args.flags || "");
182
+ const match = args.testString.match(re);
183
+ res = JSON.stringify({ success: !!match, matches: match }, null, 2); break;
184
+ case "loan_amortization_simulator": {
185
+ const P = args.principal;
186
+ const r = (args.rate || 5) / 1200;
187
+ const n = (args.termYears || 30) * 12;
188
+ const monthly = r === 0 ? P / n : (P * r * Math.pow(1 + r, n)) / (Math.pow(1 + r, n) - 1);
189
+ const totalPayment = monthly * n;
190
+ const totalInterest = totalPayment - P;
191
+ res = JSON.stringify({
192
+ monthlyPayment: monthly.toFixed(2),
193
+ totalPayment: totalPayment.toFixed(2),
194
+ totalInterest: totalInterest.toFixed(2)
195
+ }, null, 2);
196
+ break;
197
+ }
198
+ case "options_pricing_black_scholes": {
199
+ const S = args.underlyingPrice;
200
+ const K = args.strikePrice;
201
+ const T = args.yearsToExpiry;
202
+ const v = args.volatility;
203
+ const rf = args.riskFreeRate;
204
+ const stdNormCDF = (x) => {
205
+ const b1 = 0.319381530;
206
+ const b2 = -0.356563782;
207
+ const b3 = 1.781477937;
208
+ const b4 = -1.821255978;
209
+ const b5 = 1.330274429;
210
+ const p = 0.2316419;
211
+ const c = 0.39894228;
212
+ if (x >= 0.0) {
213
+ const t = 1.0 / (1.0 + p * x);
214
+ return (1.0 - c * Math.exp(-x * x / 2.0) * t * (t * (t * (t * (t * b5 + b4) + b3) + b2) + b1));
215
+ } else {
216
+ const t = 1.0 / (1.0 - p * x);
217
+ return (c * Math.exp(-x * x / 2.0) * t * (t * (t * (t * (t * b5 + b4) + b3) + b2) + b1));
218
+ }
219
+ };
220
+ const d1 = (Math.log(S / K) + (rf + (v * v) / 2) * T) / (v * Math.sqrt(T));
221
+ const d2 = d1 - v * Math.sqrt(T);
222
+ const call = S * stdNormCDF(d1) - K * Math.exp(-rf * T) * stdNormCDF(d2);
223
+ const put = K * Math.exp(-rf * T) * stdNormCDF(-d2) - S * stdNormCDF(-d1);
224
+ res = JSON.stringify({
225
+ callPrice: call.toFixed(4),
226
+ putPrice: put.toFixed(4)
227
+ }, null, 2);
228
+ break;
229
+ }
230
+ case "cap_table_dilution": {
231
+ const preVal = args.preMoneyValuation;
232
+ const inv = args.investmentAmount;
233
+ const postVal = preVal + inv;
234
+ const invPct = inv / postVal;
235
+ const founderShares = args.founderShares || 1000000;
236
+ const investorShares = Math.round(founderShares * (invPct / (1 - invPct)));
237
+ const totalShares = founderShares + investorShares;
238
+ res = JSON.stringify({
239
+ postMoneyValuation: postVal,
240
+ investorOwnershipPercent: (invPct * 100).toFixed(2) + "%",
241
+ founderShares: founderShares,
242
+ founderOwnershipPercent: ((founderShares / totalShares) * 100).toFixed(2) + "%",
243
+ investorShares: investorShares,
244
+ totalShares: totalShares
245
+ }, null, 2);
246
+ break;
247
+ }
248
+ case "nda_clause_auditor": {
249
+ const text = args.text || "";
250
+ const lowercase = text.toLowerCase();
251
+ const hasConfidentiality = lowercase.includes("confidential") || lowercase.includes("disclosure");
252
+ const hasTerm = lowercase.includes("term") || lowercase.includes("termination") || lowercase.includes("duration");
253
+ const hasNonCompete = lowercase.includes("compete") || lowercase.includes("non-compete");
254
+ const hasIntellectualProperty = lowercase.includes("intellectual property") || lowercase.includes("proprietary rights") || lowercase.includes("ipr");
255
+ const hasIndemnification = lowercase.includes("indemnity") || lowercase.includes("indemnify") || lowercase.includes("hold harmless");
256
+ res = JSON.stringify({
257
+ auditedCharacters: text.length,
258
+ confidentialityClause: hasConfidentiality ? "Present" : "Missing / Flagged",
259
+ termClause: hasTerm ? "Present" : "Missing / Flagged",
260
+ nonCompeteClause: hasNonCompete ? "Present (Review high-risk non-compete limits)" : "Not Detected",
261
+ intellectualProperty: hasIntellectualProperty ? "Present" : "Missing / Flagged",
262
+ indemnification: hasIndemnification ? "Present (Review risk distribution)" : "Not Detected"
263
+ }, null, 2);
264
+ break;
265
+ }
266
+ case "gdpr_privacy_auditor": {
267
+ const text = args.text || "";
268
+ const lowercase = text.toLowerCase();
269
+ const hasDataController = lowercase.includes("controller") || lowercase.includes("dpo") || lowercase.includes("privacy officer");
270
+ const hasUserRights = lowercase.includes("access") || lowercase.includes("rectify") || lowercase.includes("erase") || lowercase.includes("deletion") || lowercase.includes("gdpr rights");
271
+ const hasCookies = lowercase.includes("cookie") || lowercase.includes("tracking") || lowercase.includes("pixels");
272
+ const hasLegalBasis = lowercase.includes("legal basis") || lowercase.includes("consent") || lowercase.includes("legitimate interest");
273
+ res = JSON.stringify({
274
+ dataControllerOrDpo: hasDataController ? "Compliant" : "Needs Review (Missing controller contact)",
275
+ userRightsEnforcement: hasUserRights ? "Compliant" : "Needs Review (Missing deletion/access rights details)",
276
+ cookiesDeclaration: hasCookies ? "Compliant" : "Needs Review (Missing cookie statement)",
277
+ lawfulProcessingBasis: hasLegalBasis ? "Compliant" : "Needs Review (Missing processing justification)"
278
+ }, null, 2);
279
+ break;
280
+ }
281
+ case "trademark_snippet_compiler": {
282
+ const license = (args.licenseType || "MIT").toUpperCase();
283
+ const holder = args.holderName || "ToolBox Co.";
284
+ const yr = args.year || new Date().getFullYear();
285
+ if (license === "MIT") {
286
+ res = `/*\n * MIT License\n * Copyright (c) ${yr} ${holder}\n * Permission is hereby granted, free of charge, to any person obtaining a copy...\n */`;
287
+ } else {
288
+ res = `/*\n * Copyright (c) ${yr} ${holder}\n * Licensed under ${license}\n */`;
289
+ }
290
+ break;
291
+ }
292
+ case "csv_to_sql_ddl": {
293
+ const csvText = args.csvText || "";
294
+ const tbl = args.tableName || "temp_table";
295
+ const lines = csvText.split("\n").map(l => l.trim()).filter(l => l.length);
296
+ if (!lines.length) throw new Error("CSV text is empty");
297
+ const headers = lines[0].split(",").map(h => h.replace(/["']/g, "").trim());
298
+ const createTable = `CREATE TABLE ${tbl} (\n${headers.map(h => ` ${h} VARCHAR(255)`).join(",\n")}\n);`;
299
+ const rows = lines.slice(1);
300
+ const inserts = rows.map(r => {
301
+ const cols = r.split(",").map(c => `'${c.replace(/["']/g, "").replace(/'/g, "''").trim()}'`);
302
+ return `INSERT INTO ${tbl} (${headers.join(", ")}) VALUES (${cols.join(", ")});`;
303
+ });
304
+ res = [createTable, "", ...inserts].join("\n");
305
+ break;
306
+ }
307
+ case "json_schema_to_openapi": {
308
+ const jsonText = args.jsonText || "{}";
309
+ const parsed = JSON.parse(jsonText);
310
+ const generateSchema = (obj) => {
311
+ if (typeof obj === "string") return { type: "string" };
312
+ if (typeof obj === "number") return { type: "number" };
313
+ if (typeof obj === "boolean") return { type: "boolean" };
314
+ if (Array.isArray(obj)) {
315
+ return { type: "array", items: obj.length ? generateSchema(obj[0]) : { type: "string" } };
316
+ }
317
+ if (typeof obj === "object" && obj !== null) {
318
+ const properties = {};
319
+ for (const [k, v] of Object.entries(obj)) {
320
+ properties[k] = generateSchema(v);
321
+ }
322
+ return { type: "object", properties };
323
+ }
324
+ return { type: "string" };
325
+ };
326
+ const schema = generateSchema(parsed);
327
+ const openapi = {
328
+ openapi: "3.0.0",
329
+ info: { title: "API Sandbox Generated Specs", version: "1.0.0" },
330
+ paths: {},
331
+ components: {
332
+ schemas: {
333
+ GeneratedModel: schema
334
+ }
335
+ }
336
+ };
337
+ res = JSON.stringify(openapi, null, 2);
338
+ break;
339
+ }
340
+ case "statistical_hypothesis_solver": {
341
+ const { mean1, mean2, sd1, sd2, n1, n2 } = args;
342
+ const poolSD = Math.sqrt(((n1 - 1) * sd1 * sd1 + (n2 - 1) * sd2 * sd2) / (n1 + n2 - 2));
343
+ const tStat = (mean1 - mean2) / (poolSD * Math.sqrt(1 / n1 + 1 / n2));
344
+ const df = n1 + n2 - 2;
345
+ const pVal = 2 * (1 - (1 / (1 + Math.exp(-0.07 * Math.pow(Math.abs(tStat), 3) - 1.6 * Math.abs(tStat)))));
346
+ res = JSON.stringify({
347
+ tStatistic: tStat.toFixed(4),
348
+ degreesOfFreedom: df,
349
+ pValue: pVal.toFixed(6),
350
+ significance05: pVal < 0.05 ? "Statistically Significant" : "Not Significant"
351
+ }, null, 2);
352
+ break;
353
+ }
354
+ case "volumetric_shipping_estimator": {
355
+ const l = args.length;
356
+ const w = args.width;
357
+ const h = args.height;
358
+ const actualWt = args.weight;
359
+ const div = args.divisor || 139;
360
+ const volWt = (l * w * h) / div;
361
+ const billableWt = Math.max(actualWt, volWt);
362
+ res = JSON.stringify({
363
+ volumetricWeightLbs: volWt.toFixed(2),
364
+ actualWeightLbs: actualWt,
365
+ billableWeightLbs: billableWt.toFixed(2),
366
+ chargeableMethod: volWt > actualWt ? "Volumetric Dimensional Weight (Over-billed risk)" : "Actual Gross Weight"
367
+ }, null, 2);
368
+ break;
369
+ }
370
+ case "product_profit_margin": {
371
+ const cog = args.costOfGoods;
372
+ const price = args.retailPrice;
373
+ const marketing = args.marketingSpend || 0;
374
+ const sold = args.unitsSold || 1;
375
+ const totalRev = price * sold;
376
+ const totalCOG = cog * sold;
377
+ const grossProfit = totalRev - totalCOG;
378
+ const netProfit = grossProfit - marketing;
379
+ const netMargin = (netProfit / totalRev) * 100;
380
+ res = JSON.stringify({
381
+ totalRevenue: totalRev.toFixed(2),
382
+ grossProfit: grossProfit.toFixed(2),
383
+ netProfit: netProfit.toFixed(2),
384
+ netMarginPercent: netMargin.toFixed(2) + "%",
385
+ roas: marketing > 0 ? (totalRev / marketing).toFixed(2) : "N/A"
386
+ }, null, 2);
387
+ break;
388
+ }
389
+ case "ecom_listing_seo_auditor": {
390
+ const title = args.title || "";
391
+ const desc = args.description || "";
392
+ const titleLen = title.length;
393
+ const descLen = desc.length;
394
+ res = JSON.stringify({
395
+ titleLength: titleLen,
396
+ titleStatus: titleLen >= 50 && titleLen <= 80 ? "Optimized" : "Needs Review (Prefer 50-80 chars)",
397
+ descriptionLength: descLen,
398
+ descriptionStatus: descLen > 200 ? "Optimized" : "Needs Review (Prefer >200 chars for depth)"
399
+ }, null, 2);
400
+ break;
401
+ }
402
+ case "rental_yield_calculator": {
403
+ const price = args.purchasePrice;
404
+ const rent = args.monthlyRent;
405
+ const opex = args.operatingExpenses || 0;
406
+ const grossRev = rent * 12;
407
+ const grossYield = (grossRev / price) * 100;
408
+ const netRev = grossRev - opex;
409
+ const capRate = (netRev / price) * 100;
410
+ res = JSON.stringify({
411
+ grossAnnualRevenue: grossRev.toFixed(2),
412
+ grossYieldPercent: grossYield.toFixed(2) + "%",
413
+ netAnnualRevenue: netRev.toFixed(2),
414
+ capRatePercent: capRate.toFixed(2) + "%"
415
+ }, null, 2);
416
+ break;
417
+ }
418
+ case "lease_cost_escalation": {
419
+ const rent = args.baseRent;
420
+ const esc = args.escalationRate || 3;
421
+ const years = args.years || 5;
422
+ const schedule = [];
423
+ let currentRent = rent;
424
+ for (let i = 1; i <= years; i++) {
425
+ schedule.push({ year: i, rent: currentRent.toFixed(2) });
426
+ currentRent *= (1 + esc / 100);
427
+ }
428
+ res = JSON.stringify({
429
+ finalRent: schedule[schedule.length - 1].rent,
430
+ schedule
431
+ }, null, 2);
432
+ break;
28
433
  }
434
+ case "tabular_synthetic_generator": {
435
+ const domain = args.domain || "healthcare";
436
+ const size = args.sampleSize || 10;
437
+ const rows = [];
438
+ if (domain === "healthcare") {
439
+ rows.push("PatientID,Age,SystolicBP,Condition,Treatment");
440
+ for (let i = 0; i < size; i++) {
441
+ const bp = Math.round(110 + Math.random() * 35);
442
+ rows.push(`PT_${1000 + i},${Math.round(20 + Math.random() * 60)},${bp},${bp > 130 ? "Hypertension" : "Healthy"},${bp > 130 ? "Beta-Blockers" : "None"}`);
443
+ }
444
+ } else if (domain === "finance") {
445
+ rows.push("TransactionID,Amount,Country,IsFraud");
446
+ for (let i = 0; i < size; i++) {
447
+ const isFraud = Math.random() > 0.92;
448
+ rows.push(`TX_${2000 + i},${(10 + Math.random() * 1500).toFixed(2)},${["CA", "US", "GB", "NL"][Math.floor(Math.random() * 4)]},${isFraud}`);
449
+ }
450
+ } else {
451
+ rows.push("SessionID,ScrollDepth,ClickCount,Converted");
452
+ for (let i = 0; i < size; i++) {
453
+ const converted = Math.random() > 0.85;
454
+ rows.push(`SESS_${3000 + i},${Math.round(20 + Math.random() * 80)},${Math.round(1 + Math.random() * 12)},${converted}`);
455
+ }
456
+ }
457
+ res = rows.join("\n");
458
+ break;
459
+ }
460
+ case "academic_anonymizer": {
461
+ const csv = args.csvText || "";
462
+ const salt = args.salt || "default_salt";
463
+ const lines = csv.split("\n").map(l => l.trim()).filter(l => l.length);
464
+ if (!lines.length) throw new Error("CSV text is empty");
465
+ const headers = lines[0].split(",");
466
+ const rows = lines.slice(1);
467
+ const anonymized = [headers.join(",")];
468
+ for (const r of rows) {
469
+ const cols = r.split(",");
470
+ const anonCols = cols.map((col, idx) => {
471
+ const header = headers[idx]?.toLowerCase() || "";
472
+ if (header.includes("name") || header.includes("id") || header.includes("email") || header.includes("phone") || header.includes("sin")) {
473
+ return crypto.createHash("sha256").update(col + salt).digest("hex").slice(0, 16);
474
+ }
475
+ return col;
476
+ });
477
+ anonymized.push(anonCols.join(","));
478
+ }
479
+ res = anonymized.join("\n");
480
+ break;
481
+ }
482
+ case "hybrid_dataset_fuser": {
483
+ const csv = args.csvText || "";
484
+ const mult = args.multiplier || 2;
485
+ const lines = csv.split("\n").map(l => l.trim()).filter(l => l.length);
486
+ if (!lines.length) throw new Error("CSV text is empty");
487
+ const headers = lines[0].split(",");
488
+ const rows = lines.slice(1);
489
+ const results = [headers.join(",")];
490
+ for (let m = 0; m < mult; m++) {
491
+ for (const r of rows) {
492
+ const cols = r.split(",");
493
+ const fusedCols = cols.map((col) => {
494
+ const num = Number(col);
495
+ if (!isNaN(num)) {
496
+ const perturbation = (Math.random() - 0.5) * 0.1 * num;
497
+ return (num + perturbation).toFixed(2);
498
+ }
499
+ return col;
500
+ });
501
+ results.push(fusedCols.join(","));
502
+ }
503
+ }
504
+ res = results.join("\n");
505
+ break;
506
+ }
507
+ default: throw new Error(`Tool not found: ${name}`);
508
+ }
509
+ return { content: [{ type: "text", text: String(res) }] };
29
510
  } catch (e) {
30
- console.error("Post error:", e);
511
+ return { content: [{ type: "text", text: `Error: ${e.message}` }], isError: true };
31
512
  }
32
- }
513
+ });
33
514
 
34
- async function connectSSE() {
35
- console.error(`[ToolBox Bridge] Opening session ${cid} (1.0.8)...`);
36
- const res = await fetch(sseInitUrl, { method: 'POST' });
37
-
38
- const cookies = res.headers.raw()['set-cookie'] || [];
39
- for (const c of cookies) {
40
- if (c.startsWith('GCRAffinity=')) {
41
- gcrCookie = c.split(';')[0];
42
- }
43
- }
515
+ return mcpServer;
516
+ }
44
517
 
45
- if (!res.ok) {
46
- console.error(`Failed to connect: ${res.status}`);
47
- return;
48
- }
518
+ const transports = new Map();
49
519
 
50
- isConnected = true;
51
- console.error(`[ToolBox Bridge] SSE Stream established.`);
520
+ async function setupSseConnection(req, res) {
521
+ try {
522
+ let sessionId = req.query.sessionId || req.body?.sessionId || crypto.randomUUID();
523
+ console.log(`[SSE] Initializing session: ${sessionId}`);
52
524
 
53
- // Process queue
54
- for (const msg of messageQueue) await sendPost(msg);
55
- messageQueue.length = 0;
56
-
57
- let buffer = '';
58
- for await (const chunk of res.body) {
59
- buffer += chunk.toString();
60
- const events = buffer.split('\n\n');
61
- buffer = events.pop() || '';
62
-
63
- for (const block of events) {
64
- if (!block.trim()) continue;
65
- let type = 'message', data = '';
66
- for (const line of block.split('\n')) {
67
- if (line.startsWith('event:')) type = line.substring(6).trim();
68
- else if (line.startsWith('data:')) data += line.substring(5).trim();
69
- }
70
- if (type === 'message') {
71
- process.stdout.write(data + '\n');
72
- }
73
- }
74
- }
525
+ const transport = new SSEServerTransport(`/message?sessionId=${sessionId}`, res);
526
+ transports.set(sessionId, transport);
527
+ console.log(`[SSE] Added ${sessionId} to transports. Total active: ${transports.size}`);
528
+
529
+ const mcpServer = createServer();
530
+ await mcpServer.connect(transport);
531
+ console.log(`[SSE] Server connected for ${sessionId}`);
532
+
533
+ req.on('close', () => {
534
+ console.log(`[SSE] Client CLOSED connection for ${sessionId}. Removing from map.`);
535
+ transports.delete(sessionId);
536
+ });
537
+ } catch (err) {
538
+ console.error("SSE connection error:", err);
539
+ if (!res.headersSent) res.status(500).json({ error: "Internal Server Error" });
540
+ }
75
541
  }
76
542
 
77
- connectSSE().catch(e => console.error("SSE Error:", e));
543
+ app.get("/sse", setupSseConnection);
544
+ app.post("/sseInit", setupSseConnection);
78
545
 
79
- const rl = readline.createInterface({ input: process.stdin, terminal: false });
80
- rl.on('line', async (line) => {
81
- if (!line.trim()) return;
82
- if (!isConnected) messageQueue.push(line);
83
- else await sendPost(line);
546
+ app.post("/message", express.json(), async (req, res) => {
547
+ const sessionId = req.query.sessionId;
548
+ let transport = transports.get(sessionId);
549
+
550
+ if (!transport) {
551
+ console.warn(`[POST] Session ${sessionId} not in map. Retrying in 500ms...`);
552
+ await new Promise(r => setTimeout(r, 500));
553
+ transport = transports.get(sessionId);
554
+ }
555
+
556
+ if (!transport) {
557
+ const activeKeys = Array.from(transports.keys());
558
+ console.error(`[POST] REJECTED 404: Session ${sessionId} still NOT found!`);
559
+ return res.status(404).send(`Session not found. Map contains: [${activeKeys.join(', ')}]`);
560
+ }
561
+
562
+ await transport.handlePostMessage(req, res, req.body);
84
563
  });
85
564
 
86
- setInterval(() => {}, 60000);
565
+ const PORT = process.env.PORT || 8080;
566
+ app.listen(PORT, () => {
567
+ console.log(`ToolBox Mega MCP Server listening on port ${PORT}`);
568
+ });
package/package.json CHANGED
@@ -1,13 +1,17 @@
1
1
  {
2
2
  "name": "toolbox-co-mcp-bridge",
3
- "version": "1.0.8",
4
- "description": "Bridge for ToolBox Co.",
3
+ "version": "1.0.9",
4
+ "description": "ToolBox Co MCP Server",
5
+ "main": "index.js",
5
6
  "type": "module",
6
- "bin": {
7
- "toolbox-co-mcp-bridge": "index.js"
7
+ "scripts": {
8
+ "start": "node index.js"
8
9
  },
9
10
  "dependencies": {
10
- "node-fetch": "^3.3.0",
11
- "readline": "^1.3.0"
11
+ "@modelcontextprotocol/sdk": "^1.6.0",
12
+ "bcryptjs": "^3.0.3",
13
+ "cors": "^2.8.5",
14
+ "express": "^4.21.2",
15
+ "yaml": "^2.8.3"
12
16
  }
13
17
  }