{
  "markdown": "# csvbox-mcp-server\n\nA universal [Model Context Protocol](https://modelcontextprotocol.io) (MCP) server for [CSVBox](https://csvbox.io). It exposes CSVBox importer-sheet management as MCP tools so you can create, replace, patch, generate, validate, and scaffold importers from any MCP-compatible client — Claude Desktop, Cursor, Windsurf, Roo Code, Cline, VS Code, ChatGPT MCP, and more.\n\nRuns over **stdio**, so it works the same way in every client.\n\n## Tools\n\n| Tool | Purpose | API call |\n| --- | --- | --- |\n| `create_sheet` | Create a CSVBox sheet | `POST /1.1/sheet` |\n| `update_sheet` | Replace an existing sheet | `PUT /1.1/sheet/{key}` |\n| `patch_sheet` | Partially update a sheet | `PATCH /1.1/sheet/{key}` |\n| `generate_sheet_json` | NL prompt → complete sheet JSON (via LLM) | none (calls LLM) |\n| `create_importer_from_prompt` | NL prompt → validate → create | `POST /1.1/sheet` (+ LLM) |\n| `generate_import_code` | Integration code (vanilla-js/react/vue/angular) | none |\n| `generate_sheet_functions` | NL prompt → virtual columns / validation functions / data transforms (via LLM) | none (calls LLM) |\n| `validate_schema` | Local schema validation | none |\n\n> CSVBox currently has **no GET or LIST endpoints**, so there are intentionally no `get_sheet` / `list_sheet` tools.\n\nIt also exposes two **MCP prompts**:\n\n| Prompt | Purpose |\n| --- | --- |\n| `create_csvbox_sheet` | Make the host client's own LLM build a complete CSVBox sheet (no server-side LLM key needed). |\n| `csvbox_sheet_functions` | Make the host client's own LLM author virtual columns, validation functions, and data transforms (no server-side LLM key needed). |\n\n### Prompt → sheet generation\n\n`generate_sheet_json` and `create_importer_from_prompt` use an LLM to convert a free-form request into a **complete** CSVBox sheet — `title`, `sheet_columns`, `destinations`, `webhooks`, `security_settings`, and `steps`. Only actual data fields become columns; destinations, webhooks, domains, regions, file-upload and step settings are placed in their proper configuration sections, never turned into columns. There are three tiers:\n\n1. **Server LLM** — when `ANTHROPIC_API_KEY` or `OPENAI_API_KEY` is set, the server calls the LLM directly. Works in MCP Inspector and headless.\n2. **MCP prompt** (`create_csvbox_sheet`) — when you have no server key, host clients (Cursor, Claude Desktop, Cline) run the generation with their own model, then call `validate_schema` and `create_sheet`. Free.\n3. **None configured** — `generate_sheet_json` returns a structured \"no LLM provider configured\" error pointing to the MCP prompt, and `create_importer_from_prompt` does not call the CSVBox API. There is **no** regex fallback.\n\n#### Category / module expansion\n\nThe generator runs in one of two modes, chosen automatically from the prompt:\n\n- **Extraction** (default) — the prompt names concrete fields (e.g. *\"columns name, email, phone\"*). Only those become columns; nothing is invented.\n- **Expansion** — the prompt names business **modules / categories** as a list (e.g. *\"modules for: Company Information, Suppliers, Payroll, Invoice\"*), asks for a *comprehensive*/*detailed* schema, or asks for a column count (*\"at least 100 columns\"*). Each named module is expanded into several realistic, prefixed, correctly-typed columns (e.g. Suppliers → `supplier_id`, `supplier_name`, `supplier_gstin`, `supplier_email`, …). An explicit minimum count is honored and every `column_name` is globally unique.\n\nData types and validations are inferred from the field names and any requested types:\n\n| Requested / implied | Column `type` | Validators |\n| --- | --- | --- |\n| Dropdown / status / category with fixed options | `list` | `values: [...]` candidate options |\n| Percentage / percent | `number` | `min_value: 0`, `max_value: 100` |\n| Positive numeric (quantity, count, stock, cost, age) | `number` | `min_value: 0` |\n| ID / code / reference number | `text` | — |\n| Email | `email` | — |\n| Phone / mobile | `phone_number` | — |\n| URL / website | `url` | — |\n| Price / cost / amount / salary | `currency` | — |\n| Date fields | `date` | `format: \"YYYY-MM-DD\"` |\n| Boolean / is_* / active | `boolean` | — |\n| GST / GSTIN / tax id | `regex` | GSTIN pattern |\n| PIN code / postal code (India) | `regex` | `^[1-9][0-9]{5}$` |\n\n> **Large schemas:** the default models (`claude-haiku-4-5`, `gpt-4o-mini`) are cheap but produce noticeably better 100+ column schemas when you override with a stronger model via `LLM_MODEL` (e.g. `claude-sonnet-4-6`). The output cap is raised to fit big sheets; if a request is still too large the response is flagged **`TRUNCATED`** (a distinct result, not a parse error) and the CSVBox API is **not** called — reduce the column count / modules or use a model with a larger output budget and retry.\n\n## Function collections (virtual columns, validation functions, data transforms)\n\nBeyond the six sheet properties, the CSVBox Sheet API accepts three collections whose items carry a `js_code` string that **CSVBox executes during an import**:\n\n| Collection | Identified by | Max | `js_code` must… |\n| --- | --- | --- | --- |\n| `virtual_columns` | `column_name` | 20 | return the computed cell value |\n| `validation_functions` | `function_name` | 10 | return an array of error strings (`[]` = valid) |\n| `data_transforms` | `transform_name` | 10 | mutate the `csvbox` object and **return it** |\n\nInside `js_code` the `csvbox` object exposes `row`, `column`, `virtual`, `user`, `import`, and `environment`. The two accessors are **not** interchangeable — a virtual column is per-row and uses `csvbox.row.<name>` (a scalar), while a `\"column\"`-scoped function sees the whole column via `csvbox.column.<name>` (an array).\n\nShared optional fields: `scope` (`column` | `row`; not on virtual columns), `run_at` (`before_validation` | `after_validation`; data transforms only), `columns` / `dynamic_columns`, `active`, `dependencies`, and `_delete` (PATCH only).\n\n### Authoring them\n\n```json\n// generate_sheet_functions  (requires ANTHROPIC_API_KEY or OPENAI_API_KEY)\n{\n  \"prompt\": \"add a virtual column joining first and last name, and check every email contains an @\",\n  \"sheet\": { \"title\": \"Customers\", \"sheet_columns\": [ ... ] }\n}\n```\n\nReturns `{ \"virtual_columns\": [...], \"validation_functions\": [...], \"source\": ..., \"validation\": {...} }`. Collections the request does not imply are **omitted**, never returned as empty arrays.\n\nThis tool **does not call the CSVBox API**. Read the generated `js_code`, then apply it yourself with `patch_sheet`. Pass `sheet` so the model references real column names and the validator can check those references — CSVBox has no read endpoint, so it must be supplied inline. Without an LLM key, use the `csvbox_sheet_functions` MCP prompt instead.\n\n### PUT vs PATCH — read this before applying\n\n| | `update_sheet` (PUT) | `patch_sheet` (PATCH) |\n| --- | --- | --- |\n| Collection you send | **authoritative** — any existing item not named is **deleted** | **merged** — unnamed items are left alone |\n| `\"virtual_columns\": []` | **deletes all 20** | no-op |\n| Key omitted | untouched | untouched |\n| `_delete: true` | not valid | removes that item (all its other fields ignored) |\n\nUse `patch_sheet` to apply generated functions. Validate first with the matching verb:\n\n```json\n// validate_schema\n{ \"sheet\": { \"data_transforms\": [ ... ] }, \"mode\": \"patch\" }\n```\n\n`mode` is `create` (default), `put`, or `patch`. It only affects the function collections — under `put` an empty array is a hard error rather than a warning, and `_delete` is rejected outside `patch`.\n\n### Dependencies\n\nAn item may load up to 5 third-party scripts:\n\n```json\n{ \"url\": \"https://cdn.jsdelivr.net/npm/dayjs@1.11.10/dayjs.min.js\",\n  \"globals\": [\"dayjs\"],\n  \"integrity\": \"sha384-...\" }\n```\n\nOnly `cdn.jsdelivr.net`, `unpkg.com`, and `cdnjs.cloudflare.com` are allowed; https only, `.js`/`.mjs` path, no query string, fragment, userinfo, or port.\n\n> **Security.** This server never executes `js_code` — it is an opaque string here. Generated JavaScript is unreviewed model output, so read it before you PATCH it into a live importer. A dependency without an `integrity` digest can change under your customers at any time; `validate_schema` warns when one is missing.\n\nSee `docs/sheet-functions-example.json` for a full payload.\n\n## Installation\n\n```bash\nnpm install @csvbox/mcp-server\n```\n\nOr build from source:\n\n```bash\ngit clone <this-repo> csvbox-mcp-server\ncd csvbox-mcp-server\nnpm install\nnpm run build\n```\n\nThis produces `dist/index.js` — the entrypoint MCP clients launch.\n\n## Environment variables\n\nCopy `.env.example` to `.env` and fill in your CSVBox credentials:\n\n```bash\nCSVBOX_API_KEY=your_api_key\nCSVBOX_API_SECRET=your_api_secret\n```\n\nCSVBox credentials are **only** required for the API-backed tools (`create_sheet`, `update_sheet`, `patch_sheet`, `create_importer_from_prompt`). `validate_schema` and `generate_import_code` work without any credentials.\n\n> **Auth header note:** the client sends `x-csvbox-api-key` and `x-csvbox-secret-api-key` (matching the CSVBox reference payloads). These are defined as constants in `src/services/csvbox-api.ts` if your account uses different header names.\n\n### Client identification\n\nEvery request this server sends to the CSVBox API carries two extra headers:\n\n```\nx-csvbox-client: mcp\nx-csvbox-client-version: <the version in package.json>\n```\n\nCSVBox records how each sheet was created; without these, sheets and imports made through MCP are indistinguishable from any other REST caller. The version is read from `package.json` and is the same one the server advertises to your MCP client, so the two can never disagree.\n\nThese are **always sent** — there is no environment variable or tool input that disables or changes them. They carry no credentials and nothing derived from your tool inputs. Tools that make no API call (`generate_sheet_json`, `generate_sheet_functions`, `generate_import_code`, `validate_schema`) send no request and therefore no headers.\n\n### LLM provider (for prompt → sheet generation)\n\n`generate_sheet_json` and `create_importer_from_prompt` need an LLM. Set **one** of:\n\n```bash\nANTHROPIC_API_KEY=sk-ant-...\nOPENAI_API_KEY=sk-...\n```\n\nThe provider is auto-detected:\n\n| Condition | Provider | Default model |\n| --- | --- | --- |\n| `LLM_PROVIDER=anthropic` (and its key set) | Anthropic | `claude-haiku-4-5` |\n| `LLM_PROVIDER=openai` (and its key set) | OpenAI | `gpt-4o-mini` |\n| `ANTHROPIC_API_KEY` set (no `LLM_PROVIDER`) | Anthropic | `claude-haiku-4-5` |\n| `OPENAI_API_KEY` set (no `LLM_PROVIDER`) | OpenAI | `gpt-4o-mini` |\n| neither key set | none — tools return an error pointing to the `create_csvbox_sheet` MCP prompt | — |\n\n`LLM_PROVIDER` disambiguates when both keys are present; `LLM_MODEL` overrides the model for whichever provider is chosen. For large category/module schemas (100+ columns) set `LLM_MODEL` to a stronger model (e.g. `claude-sonnet-4-6`) — see [Category / module expansion](#category--module-expansion).\n\n> **MCP Inspector:** set the LLM key in the Inspector's environment-variables panel to use the server-LLM path. Inspector has no host LLM of its own, so it can *render* the `create_csvbox_sheet` prompt but cannot *execute* it — for the keyless path use a client with a model (Cursor, Claude Desktop, Cline).\n\n## Running locally\n\n```bash\n# After building:\nnpm start\n\n# Or run the built file directly:\nnode dist/index.js\n```\n\nThe server speaks MCP over stdio and logs `csvbox-mcp-server running on stdio` to **stderr** (stdout is reserved for the protocol).\n\n## Client configuration\n\nFor a published installation, use the npm package with `npx`. Set `CSVBOX_API_KEY` / `CSVBOX_API_SECRET` in the `env` block.\n\n> **Note:** The npm package is `@csvbox/mcp-server` and the executable is `csvbox-mcp-server`.\n\n### Claude Desktop\n\nAdd the following to your Claude Desktop MCP configuration:\n\n```json\n{\n  \"mcpServers\": {\n    \"csvbox\": {\n      \"command\": \"npx\",\n      \"args\": [\n        \"-y\",\n        \"--package=@csvbox/mcp-server\",\n        \"csvbox-mcp-server\"\n      ],\n      \"env\": {\n        \"CSVBOX_API_KEY\": \"your_api_key\",\n        \"CSVBOX_API_SECRET\": \"your_api_secret\"\n      }\n    }\n  }\n}\n```\n\n### Cursor\n\nEdit `~/.cursor/mcp.json` (global) or `.cursor/mcp.json` (per-project):\n\n```json\n{\n  \"mcpServers\": {\n    \"csvbox\": {\n      \"command\": \"npx\",\n      \"args\": [\n        \"-y\",\n        \"--package=@csvbox/mcp-server\",\n        \"csvbox-mcp-server\"\n      ],\n      \"env\": {\n        \"CSVBOX_API_KEY\": \"your_api_key\",\n        \"CSVBOX_API_SECRET\": \"your_api_secret\"\n      }\n    }\n  }\n}\n```\n\n### Windsurf\n\nEdit `~/.codeium/windsurf/mcp_config.json`:\n\n```json\n{\n  \"mcpServers\": {\n    \"csvbox\": {\n      \"command\": \"npx\",\n      \"args\": [\n        \"-y\",\n        \"--package=@csvbox/mcp-server\",\n        \"csvbox-mcp-server\"\n      ],\n      \"env\": {\n        \"CSVBOX_API_KEY\": \"your_api_key\",\n        \"CSVBOX_API_SECRET\": \"your_api_secret\"\n      }\n    }\n  }\n}\n```\n\n### Roo Code\n\nIn the Roo Code MCP settings (`mcp_settings.json`):\n\n```json\n{\n  \"mcpServers\": {\n    \"csvbox\": {\n      \"command\": \"npx\",\n      \"args\": [\n        \"-y\",\n        \"--package=@csvbox/mcp-server\",\n        \"csvbox-mcp-server\"\n      ],\n      \"env\": {\n        \"CSVBOX_API_KEY\": \"your_api_key\",\n        \"CSVBOX_API_SECRET\": \"your_api_secret\"\n      }\n    }\n  }\n}\n```\n\n### Cline\n\nIn the Cline MCP settings (`cline_mcp_settings.json`):\n\n```json\n{\n  \"mcpServers\": {\n    \"csvbox\": {\n      \"command\": \"npx\",\n      \"args\": [\n        \"-y\",\n        \"--package=@csvbox/mcp-server\",\n        \"csvbox-mcp-server\"\n      ],\n      \"env\": {\n        \"CSVBOX_API_KEY\": \"your_api_key\",\n        \"CSVBOX_API_SECRET\": \"your_api_secret\"\n      }\n    }\n  }\n}\n```\n\n### VS Code MCP\n\nAdd to `.vscode/mcp.json` (or the global `mcp.json`):\n\n```json\n{\n  \"servers\": {\n    \"csvbox\": {\n      \"command\": \"npx\",\n      \"args\": [\n        \"-y\",\n        \"--package=@csvbox/mcp-server\",\n        \"csvbox-mcp-server\"\n      ],\n      \"env\": {\n        \"CSVBOX_API_KEY\": \"your_api_key\",\n        \"CSVBOX_API_SECRET\": \"your_api_secret\"\n      }\n    }\n  }\n}\n```\n\n## Example tool calls\n\n**Generate a complete sheet from a prompt (LLM, no CSVBox API call):**\n\n```json\n// generate_sheet_json  (requires ANTHROPIC_API_KEY or OPENAI_API_KEY)\n{ \"prompt\": \"Create employee importer with name, email, salary, joining date; destination as testapi; allow only xlsx files\" }\n```\n\nReturns `{ \"sheet\": { \"title\": ..., \"sheet_columns\": [...], \"destinations\": [...], \"steps\": {...} }, \"source\": \"llm:anthropic:claude-haiku-4-5\", \"validation\": { \"valid\": true, ... } }`. Data fields become columns (`salary → currency`, `joining date → date`); the destination and xlsx setting go to `destinations` / `steps`, not columns. With no LLM key, returns an error pointing to the `create_csvbox_sheet` prompt.\n\n**Validate a schema before sending it:**\n\n```json\n// validate_schema\n{ \"sheet\": { \"title\": \"Customers\", \"sheet_columns\": [\n  { \"column_name\": \"email\", \"display_label\": \"Email\", \"type\": \"email\" }\n] } }\n```\n\nReturns `{ \"valid\": true, \"errors\": [], \"warnings\": [ ... ] }`.\n\n**Create a sheet:**\n\n```json\n// create_sheet\n{ \"sheet\": { \"title\": \"Customer Import\", \"sheet_columns\": [\n  { \"column_name\": \"name\", \"display_label\": \"Name\", \"type\": \"text\" },\n  { \"column_name\": \"email\", \"display_label\": \"Email\", \"type\": \"email\" }\n] } }\n```\n\n**Generate + create in one step:**\n\n```json\n// create_importer_from_prompt  (requires an LLM key + CSVBox credentials)\n{ \"prompt\": \"Create customer importer with name, email, phone; allow for example.com\" }\n```\n\nReturns `{ \"generated_schema\": { ... }, \"source\": ..., \"validation\": { ... }, \"api_response\": { ... } }`. Aborts without calling the API if no LLM provider is configured or the generated schema fails validation.\n\n**Replace a sheet:**\n\n```json\n// update_sheet\n{ \"sheet_license_key\": \"abc123\", \"sheet\": { \"title\": \"Updated\", \"sheet_columns\": [ ... ] } }\n```\n\n> Destructive for any collection you send — see [PUT vs PATCH](#put-vs-patch--read-this-before-applying).\n\n**Patch a sheet:**\n\n```json\n// patch_sheet\n{ \"sheet_license_key\": \"abc123\", \"changes\": { \"title\": \"New Title\" } }\n```\n\n**Remove one function without touching the rest:**\n\n```json\n// patch_sheet\n{ \"sheet_license_key\": \"abc123\",\n  \"changes\": { \"virtual_columns\": [ { \"column_name\": \"full_name\", \"_delete\": true } ] } }\n```\n\n**Generate integration code:**\n\n```json\n// generate_import_code\n{ \"framework\": \"react\" }\n```\n\n## Supported column types\n\n`text`, `number`, `email`, `date`, `time`, `boolean`, `regex`, `ip`, `url`, `credit_card`, `phone_number`, `currency`, `list`, `dependent_list`, `dynamic_list`, `dependent_dynamic_list`, `multiselect_list`, `multiselect_dynamic_list`.\n\n## Development\n\n```bash\nnpm run build   # compile TypeScript → dist/\nnpm start       # run the built server\nnpm run lint    # type-check without emitting\nnpm test        # compile and run the unit suite (alias: npm run test:unit)\n```\n\n### Tests\n\n`npm test` compiles `src/tests/` and runs it with Node's built-in test runner — no\ntest framework, no mocking library.\n\nThe suite is **hermetic**. It never contacts an external host, never reads your\nambient `CSVBOX_API_*` / `ANTHROPIC_API_KEY` / `OPENAI_API_KEY`, and never touches\na real CSVBox account, so it passes identically whether or not you have\ncredentials configured. HTTP is intercepted at the axios adapter; the LLM is a\nscripted fake; the one test that needs real request encoding starts an ephemeral\nlistener on `127.0.0.1` and closes it afterwards. Tests that read environment\nvariables set what they need explicitly and restore the previous values.\n\n#### E2E tests\n\n```bash\nnpm run test:e2e         # run the Playwright suite\nnpm run test:e2e:report  # open the HTML report from the last run\n```\n\nSpecs live in `e2e/`, configured by `playwright.config.ts`. Like the unit suite,\nthis suite is **hermetic**: it starts mock CSVBox and LLM servers on loopback\n(`e2e/support/mock-csvbox-server.ts`, `e2e/support/mock-llm-server.ts`) and\ndrives the real built server (`dist/index.js`) through MCP Inspector with\nfake credentials pointed at those mocks — it never contacts a real CSVBox\naccount or LLM provider, and never reads your `.env`. A separate,\nzero-credential Inspector instance covers the \"missing credentials\" error\npaths. Requires `npm run build` first (the `test:e2e` webServer entries build\nautomatically).\n\n### Embedding the server\n\n`createServer()` is exported from the entry module. It registers every tool and\nprompt and returns the `McpServer` **without** attaching a transport, so you can\nconnect it to one of your own:\n\n```ts\nimport { createServer } from \"@csvbox/mcp-server\";\n\nconst server = createServer();\nawait server.connect(myTransport);\n```\n\nImporting the module does not start anything; the stdio server runs only when\n`dist/index.js` is executed directly.\n\n## License\n\nMIT\n",
  "bytes": 18843,
  "sha": "7c05427509e9a138b053398a30b62d41d3a045c0e661d16968067e7a5919cfd1",
  "repo_slug": "csvbox-io/csvbox-mcp-server",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_csvbox_io_csvbox_mcp_server_e37f57d9/readme"
}