Agent integration rules

Cursor can install Importer. Open Importer Keys at https://app.bootstrapware.co/importer/keys and click Add to Cursor (OAuth). The MCP endpoint is https://importer.bootstrapware.co/mcp.

Paste this into AGENTS.md, Cursor rules, or your agent system prompt when adding Bootstrapware Importer. The same text ships inside the npm package as AGENTS.md.

What the package does

Client-side CSV / TSV / XLSX / XLSB / ODS / JSON import UI. Parsing, mapping, validation, preview, duplicate detection, and error export run in the browser. XML is not supported. Optional useWorker parses on a Web Worker and falls back to the main thread. Prefer thousands of rows.

Decode is UTF-8, including a UTF-8 BOM. UTF-16 LE/BE still requires a BOM. Bytes that are not valid UTF-8 are decoded as Windows-1252. There is no encoding picker. On the map step, Separator can set Auto, comma, semicolon, tab, or pipe for this file only. That choice is not saved, and the text stays in the browser. .tsv / .tab Auto stays tab.

On the upload step, once local or hosted fields are known, the widget offers Download template.csv. Headers use field labels (empty labels fall back to keys). One example row is derived from field type. Generated in the browser; never fetched from Bootstrapware. The same step accepts Paste from clipboard and Ctrl/Cmd+V (text/plain, including tab-separated Excel/Sheets copies). Paste stays in the browser; a synthetic pasted.csv name is used only locally.

After the user confirms the map step (Preview rows), field→header mappings are remembered in this browser's localStorage, keyed by a schema fingerprint (field keys, types, required, aliases) plus optional importerId. The next file with the same fingerprint prefills that mapping and shows a dismissible Applied saved mapping notice with Clear saved mapping. No saved mapping (or a fingerprint mismatch) keeps the existing auto-map / aliases behavior. File contents, rows, and filenames are never stored or sent to Bootstrapware. At most ~20 fingerprints are kept (LRU).

On the map step, headers the current mapping will not import appear as Columns that will not be imported (capped, then "+ N more"), updating as the user remaps, without blocking Preview or storing those header names.

On the preview step, Show invalid only lists rows that failed validation, still capped at 100 rows. When more than 100 rows are invalid, the summary says how many more invalid rows are not shown. Each valid row has an Include checkbox, on by default. Unchecking it leaves that row out of Import and out of import-valid.csv. Invalid rows stay excluded and cannot be included. onComplete receives the included rows. meta.validRows still counts every row that passed validation, and meta.skippedRows counts the valid rows left unchecked. Unknown enum values show a remap list (pick a canonical option or leave invalid). Download errors writes import-errors.csv. When any included row remains, Download valid rows writes import-valid.csv with field labels and the same normalized values Import sends.

Never send file contents, parsed rows, or filenames to Bootstrapware. The customer's onComplete callback receives normalized objects. Send those to the customer's own backend. onComplete may be async. If it throws or rejects, the widget shows that message and re-enables Import. A successful callback still disables Import until Back, so a second click cannot duplicate the send.

Hosted mode only fetches published field configuration and reports non-content session metadata.

Install

pnpm add @bootstrapware/importer

Local schema

import { Importer } from "@bootstrapware/importer";
import "@bootstrapware/importer/styles.css";

<Importer
  fields={[
    { key: "email", label: "Email", type: "email", required: true, aliases: ["Email Address", "E-mail"] },
    { key: "name", label: "Name", type: "string", required: true },
  ]}
  duplicateKey="email"
  dateOrder="MDY"
  numberLocale="auto"
  onComplete={async (rows) => {
    await fetch("/api/customers/import", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ rows }),
    });
  }}
/>

Hosted configuration

Publishable keys may appear in the browser. Secret keys must stay server-side.

<Importer
  importerId="imp_..."
  publishableKey="bsw_live_pub_..."
  onComplete={(rows) => {
    void fetch("/api/customers/import", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ rows }),
    });
  }}
/>

Optional apiBaseUrl defaults to https://importer.bootstrapware.co.

Hosted management (secret key)

Secret keys (bsw_test_sec_ / bsw_live_sec_) must stay server-side. Use them as Authorization: Bearer against https://importer.bootstrapware.co to create, draft, and publish hosted importers without the dashboard.

Local fields schema remains free forever. Live publishable config fetch still requires a paid Importer plan ($19.99). API keys, webhooks, importer delete, and branding stay dashboard-only.

# Create
curl -s -X POST https://importer.bootstrapware.co/api/v1/importers \
  -H "Authorization: Bearer $BSW_SECRET" \
  -H "Content-Type: application/json" \
  -d '{"name":"Customers"}'

# Save draft (replace IMPORTER_ID)
curl -s -X PATCH https://importer.bootstrapware.co/api/v1/importers/IMPORTER_ID \
  -H "Authorization: Bearer $BSW_SECRET" \
  -H "Content-Type: application/json" \
  -d '{"name":"Customers","config":{"fields":[{"key":"email","label":"Email","type":"email","required":true},{"key":"name","label":"Name","type":"string","required":true}],"duplicateKey":"email"}}'

# Publish
curl -s -X POST https://importer.bootstrapware.co/api/v1/importers/IMPORTER_ID \
  -H "Authorization: Bearer $BSW_SECRET" \
  -H "Content-Type: application/json" \
  -d '{"action":"publish"}'

Also available with the same secret key: GET /api/v1/importers, GET /api/v1/importers/:id, GET /api/v1/usage.

Hosted MCP (Cursor)

HTTP MCP endpoint: https://importer.bootstrapware.co/mcp. Prefer OAuth Connect (no secret in mcp.json). Secret Bearer keys remain a fallback. Never send spreadsheet file contents or row payloads through MCP tools.

Cursor plugin: github.com/BootstrapWare/bootstrapware-cursor. Use Keys → Add to Cursor (OAuth), then Connect and sign in.

Tools:

  • list_importers
  • get_importer
  • create_importer
  • update_draft (does not publish; name optional, fields required)
  • publish_importer
  • get_published_config
  • get_install_snippet
  • ensure_test_publishable
  • list_capabilities

Dashboard-only: live/secret API key mint/revoke, webhooks, importer delete, branding, billing. Test publishable keys: ensure_test_publishable. Prefer list_capabilities before inventing endpoints.

Publishing stores hosted config. Live bsw_live_pub_ fetch still needs a paid Importer plan ($19.99). Test publishable keys work without that.

Revoke Cursor OAuth grants under Keys → Active Cursor connections. Marketplace listing may still be pending.

First-run setup

  1. Open Importer → Keys and click Add to Cursor (OAuth) (or install the plugin).
  2. In Cursor, click Connect, sign in on Bootstrapware, and Allow.
  3. Secret fallback: mint a test secret and paste the Keys-page mcp.json snippet if OAuth is unavailable.

Open Keys (Connect Cursor)

{
  "mcpServers": {
    "bootstrapware-importer": {
      "type": "http",
      "url": "https://importer.bootstrapware.co/mcp"
    }
  }
}

Field types

string | number | date | email | enum | boolean | url

enum requires enumValues.

Optional valueMap is a string-to-string map applied before type coercion (case-insensitive keys). Use it for enum aliases (Premium → pro). On preview, remaining unknown enum values can be remapped in the UI; those session remaps win over valueMap.

Optional transform?: (raw: string) => string runs after trim and before coercion. Optional validate?: (value, row) => string | null runs after coercion on the full row object (sync only). Optional dependsOn?: string[] documents sibling keys that validate reads; the widget always revalidates the whole row. transform and validate are local schema only — they cannot be published in hosted JSON.

Optional aliases is a string array of extra spreadsheet header names used during auto-map (in addition to key and label). A 1–2 character key or label matches a whole header token (ID, User ID, user_id, userId) and does not match inside a longer word (Paid, Middle Name). A glued lowercase userid header needs an alias or a manual map.

Optional unique: true on a field rejects duplicate values for that field within the file.

Numbers accept US and European formats, currency symbols, percentages as fractions, and accounting negatives ($1,234.50, 1.234,56, 50% → 0.5, (500)). With numberLocale="eu" (or semicolon-delimited files under auto), bare 1.234 is thousands. Common empty placeholders (N/A, -, null) are treated as blank. Emails like Name <addr@domain> or mailto:addr@domain are normalized to the address. Booleans accept true/false, yes/no, y/n, 1/0, on/off. URLs accept http/https and bare www.example.com. Mapped cells are trimmed and internal whitespace is collapsed.

CSV delimiter is auto-detected between comma, semicolon, tab, and pipe from the leading non-empty rows (a title line without separators does not force comma; separators inside quoted cells are ignored, including when a quoted cell spans lines). .tsv / .tab use tabs. On the map step, Separator can set Auto, comma, semicolon, tab, or pipe for this file only. That choice is not saved, and the text stays in the browser. Decode is UTF-8, including a UTF-8 BOM. UTF-16 LE/BE still requires a BOM. Bytes that are not valid UTF-8 are decoded as Windows-1252. There is no encoding picker.

Optional component props: dateOrder (MDY | DMY), numberLocale (us | eu | auto), dir (ltr | rtl), strings (partial override of chrome copy). Type-error messages still use English templates with field labels. This is not an ARIA grid and we do not claim WCAG AA. Numeric dates accept /, -, or . (15.03.2024). When day or month is above 12 the order is unambiguous and does not require dateOrder. Multi-sheet Excel files show a sheet picker. The map step also lets the user pick which spreadsheet row is the header (title/report rows above it are skipped). Auto-detect prefers a row that matches field keys, labels, or aliases. Column dropdowns show sample cell values from the file. Confirmed mappings are remembered locally per schema fingerprint (see above).

Optional presentation: section (default, inline), modal, window, or launcher. Modal, window, and launcher start closed unless defaultOpen. A successful import closes those overlays. Escape closes them. The backdrop closes modal and launcher. Nudge the corner with --bsw-importer-frame-launcher-right and --bsw-importer-frame-launcher-bottom.

Optional theme ("light" | "dark"). Omit it and the shell stays the light cream default: no data-theme, and no prefers-color-scheme or system theme. Hosts may set --bsw-bg, --bsw-panel, --bsw-fg, --bsw-muted, --bsw-line, --bsw-accent, --bsw-accent-fg, and --bsw-radius on the embed root (.bsw-importer; with theme="dark", .bsw-importer[data-theme="dark"]). Those overrides are the supported API. Do not fight .bsw-* classes. Hosted primaryColor still sets --bsw-accent inline when it is set.

Optional useWorker parses on a Web Worker with main-thread fallback. Thin Web Component: import "@bootstrapware/importer/wc" then document.querySelector("bsw-importer").assign({ fields, onComplete }). React must still be on the page. Optional primaryKey plus existingKeys adds meta.upsert counts. No multi-file merge UI. Not a spreadsheet editor.

Security

  • Do not put bsw_live_sec_ or bsw_test_sec_ in client code.
  • Do not upload spreadsheet contents to Bootstrapware.
  • Remembered column mappings stay in this browser's localStorage (field keys and header names only). Never send file contents, rows, or filenames.
  • onComplete is the data path. Throw or reject on a failed backend write so the widget can show the error and let the user retry.

Related: API overview · React component · Fields · Cursor · Claude Code · Codex · AI coding agents · Agent prompt template · Give this to your coding agent