diff --git a/Readme.md b/Readme.md index 44c9a06..42ec15b 100644 --- a/Readme.md +++ b/Readme.md @@ -2,6 +2,8 @@ flAPI is a powerful service that automatically generates read-only APIs for datasets by utilizing SQL templates. Built on top of [DuckDB](https://duckdb.org/) and leveraging its SQL engine and extension ecosystem, flAPI offers a seamless way to connect to various data sources and expose them as RESTful APIs. +![flAPI demo: one SQL template served as a REST endpoint and an MCP tool](docs/promotion/assets/flapi-demo.gif) + ![overview of flAPI](https://i.imgur.com/m7UVZlR.png) ## ⚡ Features diff --git a/docs/PROMOTION_STRATEGY.md b/docs/PROMOTION_STRATEGY.md new file mode 100644 index 0000000..f942262 --- /dev/null +++ b/docs/PROMOTION_STRATEGY.md @@ -0,0 +1,178 @@ +# flAPI Promotion Playbook + +A concrete, community-by-community plan for getting flAPI in front of the +people most likely to adopt it. Built from a scan of where SQL-to-API, +DuckDB, and MCP conversations actually happen in 2026. + +The guiding principle: **flAPI wins on distribution channels that most of +its competitors ignore.** PostgREST, Hasura, and Directus fight over the +"database → API" search term. flAPI is the only tool that lands in *three* +fast-growing ecosystems at once — **DuckDB**, **MCP / AI tools**, and +**single-binary self-hosting** — and each has its own hungry, under-served +community. Promote where the overlap is thin, not where the competition is +thick. + +--- + +## 1. Sharpen the hook first (positioning) + +Nobody upvotes "SQL-to-API framework." They upvote a sentence that names a +pain they felt this week. Lead with one of these, matched to the audience: + +| Audience | One-liner | +|----------|-----------| +| **AI / MCP crowd** | "Write one SQL file. Get a REST endpoint **and** an MCP tool your LLM can call — with per-tool RBAC and a tool-description injection scanner built in." | +| **Data engineers** | "Your parquet/Postgres/BigQuery/S3 data as a governed REST API in a YAML file. No Flask, no FastAPI, no glue code." | +| **Self-hosters** | "A single static binary that turns SQL into an API. `scp flapi-prod user@host` *is* the whole deploy — config folded into the binary." | +| **Security-minded** | "Typed params are bound as **DuckDB prepared statements** — SQL injection is structurally impossible at those sites, not just 'mitigated'." | +| **DuckDB users** | "You already query 50+ sources with DuckDB. flAPI puts a REST + MCP server in front of it in one binary." | + +**The single most valuable angle in 2026 is the MCP one.** "Every REST +endpoint is also an AI tool, from the same config" is a genuinely +differentiated claim — MXCP is the only close competitor, and it's +Python/dbt-centric. Lead with MCP everywhere the audience touches AI. + +--- + +## 2. The "free distribution" tier — do this first, this week + +These are directories and lists that *exist to be submitted to*. Zero risk +of a "spam" backlash, permanent SEO/discovery value, and they seed the +credibility you'll cite in later posts. This is the highest ROI work here. + +### MCP registries & directories (ride the wave) +flAPI ships an MCP server — get it listed everywhere people browse for MCP +tools: + +- **Official MCP Registry** — the canonical source the directories read from. +- **[mcp.so](https://mcp.so)** — submit form. +- **[Smithery](https://smithery.ai)** — `smithery mcp publish` CLI or web dashboard; can even host/run it. +- **[Glama](https://glama.ai/mcp)** — auto-indexes open-source MCP servers from GitHub; claim the listing. +- **[PulseMCP](https://www.pulsemcp.com)** — "Submit" in top nav; 18k+ servers, high discovery traffic. +- **[punkpeye/awesome-mcp-servers](https://github.com/punkpeye/awesome-mcp-servers)** and **[wong2/awesome-mcp-servers](https://github.com/wong2/awesome-mcp-servers)** — open a PR / submission. Put flAPI in the "Databases" *and* "Data Platforms" categories. + + Metadata to include everywhere: install command (`uvx --from flapi-io flapi`), transport, auth model (per-tool RBAC), supported clients, docs link, license, and the tool-list. + +### DuckDB ecosystem +- **DuckDB Discord `#show-and-tell` / `#extensions`** — the friendliest room on this list. DuckDB users *love* seeing what people build on top of DuckDB. Post the MCP angle here. +- **[DuckDB Community Extensions](https://duckdb.org/community_extensions/)** — flAPI uses an `embed://` VFS scheme and registers with DuckDB; if any reusable piece can ship as a community extension, that's a listing + a blog mention from the DuckDB team. +- **MotherDuck** actively publishes "MCP + DuckDB" content — pitch a guest post or get cited; their blog and videos are already covering exactly this space. + +### Self-hosting & general +- **[awesome-selfhosted](https://github.com/awesome-selfhosted/awesome-selfhosted)** — PR to the "Automation" or "Miscellaneous / API" section. The single-binary story is tailor-made. *Caveat (verified 2026-07-13): flAPI's BSL 1.1 license is not in their accepted-licenses list; a listing would have to go through their non-free (⊘) section. See SUBMISSION_KIT.md §8 — low priority.* +- **[Awesome list for APIs / data-engineering]** — find the relevant `awesome-*` repos (awesome-datascience, awesome-data-engineering) and PR flAPI in. +- **AlternativeTo / LibHunt / SaaSHub** — list flAPI as an alternative to PostgREST, Hasura, Datasette, ROAPI. Captures comparison-shopping traffic forever. + +--- + +## 3. Reddit — value-first, per-community angle + +Reddit rules are strict and enforced by humans. **The account matters more +than the post.** Spend 2–3 weeks genuinely answering questions in these subs +before any launch post, or the launch gets nuked as spam. Then post a "Show +/ I built" with an honest limitations section and *stay in the comments*. + +| Subreddit | Angle that fits the culture | Notes | +|-----------|----------------------------|-------| +| **r/dataengineering** | "Governed data API without standing up a backend" — engineering teardown, not an ad | Skeptical of hype; lead with architecture, caching, prepared-statement safety | +| **r/DuckDB** | "REST + MCP server on top of DuckDB, single binary" | Small but perfectly targeted; near-zero competition here | +| **r/mcp** and **r/modelcontextprotocol** | "Turn any SQL query into an MCP tool with RBAC + injection scanning" | Fastest-growing relevant audience in 2026 | +| **r/selfhosted** | "Single static binary, config baked in, `scp` to deploy" | Docker image + no-runtime-deps play very well | +| **r/opensource** | Launch post with clear install + honest limitations | Exists to discover projects; low spam-risk | +| **r/webdev** | Only as an engineering teardown/benchmark, never a direct promo | Harsh on ads; be careful | +| **r/dataanalysis / r/BusinessIntelligence** | "Analysts ship APIs with just SQL — no backend dev needed" | The core value prop, in their language | +| **r/rust / r/cpp** | Build-story: single-binary C++ service, self-packaging via appended ZIP / Mach-O segment | r/cpp will appreciate the packaging hack specifically | + +**Thread-jacking (the highest-signal Reddit move):** don't wait for a launch +window. Search these subs weekly for live questions like *"how do I turn my +SQL query into an API,"* *"PostgREST alternative,"* *"expose parquet as +REST,"* *"MCP server for my database."* Answer the question *thoroughly and +honestly first*, then mention flAPI as one option with a link. A genuinely +helpful comment on an existing thread converts far better than a launch post +and never reads as spam. + +--- + +## 4. Hacker News — one good shot + +HN is where a "Show HN" for a DuckDB/MCP tool can outperform a Product Hunt +launch. But it's one shot per project realistically, so make it count. + +- **Title:** `Show HN: flAPI – Turn one SQL file into a REST endpoint and an MCP tool`. Concrete, no adjectives. +- **Timing:** weekday, ~8–10am ET. Have the repo README polished, a 60-second GIF, and the `uvx` one-liner front and center. +- **First comment (yours):** the build story — *why* C++/single-binary, the prepared-statement injection story, the appended-ZIP self-packaging trick (HN loves a clever low-level detail). Precedent: ROAPI's "Show HN" did well on exactly this territory. +- **Be present for 4–6 hours** answering every comment. HN ranking rewards engaged authors. +- **Anticipated pushback + honest answers:** "why not PostgREST?" (not Postgres-only, DuckDB = 50+ sources, MCP built in); "why not just FastAPI?" (no code, governance/caching/RBAC included); "why not Datasette/ROAPI?" (MCP-native, prepared-statement safety, single-binary self-packaging). +- **Also viable:** an "Ask HN" or a standalone blog post ("How we made SQL injection structurally impossible in a SQL-templating tool") that earns its own front-page slot without being a launch. + +--- + +## 5. Stack Overflow & Q&A — the evergreen long game + +People search SO for years. You can't post ads, but you *can* answer real +questions well and reference flAPI where it's genuinely the best fit. + +- Hunt for questions tagged `duckdb`, `parquet`, `rest`, `fastapi`, `postgrest`, `model-context-protocol` that boil down to "how do I serve this data over HTTP quickly." +- Write a complete, correct answer (even the FastAPI/Quarkus way), then add: "If you'd rather not maintain that glue code, flAPI does exactly this from a YAML file — [link]." Disclose that you're affiliated. +- Do the same on **dev.to** and **Medium**: there are already posts like *"Turning a Parquet file into a queryable REST API with DuckDB + Quarkus + Kotlin."* Write the *shorter* flAPI version as a response/alternative and cross-link. + +--- + +## 6. Niche forums & communities where competitors aren't + +This is the creative edge. These audiences have the pain and almost no one +is pitching them a SQL-to-API+MCP tool: + +- **dbt Community Forum ("Show and Tell")** — MXCP announced here and got traction. flAPI's "REST + MCP, no Python/dbt required, single binary" is a clean counter-position. dbt users running DuckDB locally are a perfect fit. +- **Locally Optimistic / dbt Slack / Data Twitter(X)** — the analytics-engineering crowd. Frame: "let analysts ship read-only APIs without begging a backend team." +- **MotherDuck / DuckDB office hours & meetups** — lightning-talk or demo slot. This community *is* your ICP. +- **ERPL / SAP-on-DuckDB niche** — flAPI can expose **SAP ERP & BW** via ERPL. Almost nobody offers "SAP data as a governed REST/MCP API from a YAML file." SAP developer communities (SAP Community, r/SAP, SAP-focused Discords/LinkedIn groups) are an untapped, high-value, low-competition audience. This is arguably flAPI's most differentiated single wedge. +- **Microsoft 365 / SharePoint niche** — via the [erpl-web](https://github.com/DataZooDE/erpl-web) DuckDB extension, flAPI serves **SharePoint lists** (plus Entra ID, Teams, Business Central, Dataverse) as REST + MCP tools — working demo recorded (`docs/promotion/assets/flapi-demo-sharepoint.gif`). "Your team's SharePoint list as an AI tool, no Power Platform needed" is untapped: r/Office365, r/sharepoint, Microsoft Tech Community, M365 admin Discords. Same low-competition logic as the SAP wedge. +- **LLM tooling Discords** (Claude/Anthropic dev community, Cursor, LangChain, LlamaIndex) — wherever people wire up MCP servers, "expose your warehouse as governed MCP tools" is on-topic. +- **AI engineering newsletters** (Latent Space, tldr.ai, Ben's Bites, Data Engineering Weekly) — pitch the MCP-governance angle; these curate tools weekly and are hungry for concrete demos. + +--- + +## 7. Content that does the promoting for you (SEO + evergreen) + +Ship a few of these on the project blog / docs; they rank and get cited long +after a Reddit post scrolls away: + +1. **Comparison pages** (highest-intent search traffic): + - "flAPI vs PostgREST" · "vs Hasura" · "vs Datasette / ROAPI" · "vs MXCP" + - Be scrupulously fair — list where the *other* tool wins. Fair comparisons get linked by neutral parties and don't get flamed. +2. **"Every REST endpoint, free MCP tool"** — the flagship narrative post. This is the one to syndicate everywhere. +3. **"SQL injection, structurally impossible"** — a security deep-dive on typed-param → prepared-statement binding. Security posts punch above their weight on HN/lobste.rs. +4. **"A single binary that unpacks its own config"** — the self-packaging (appended ZIP / reserved Mach-O segment) engineering story. Catnip for r/cpp, lobste.rs, HN. +5. **Recipe posts** — "Serve your BigQuery table as an MCP tool in 5 minutes," "SAP BW → REST API without a backend," "Cache a slow Snowflake query with DuckLake." Each targets a long-tail search and doubles as a Reddit/HN demo. +6. **A 60-second demo GIF/video** — reused in every single post above. The `uvx --from flapi-io flapi` → live endpoint → Claude calling it as a tool loop is the money shot. + +Also consider **lobste.rs** (higher signal than HN for systems/security +content — the C++ packaging and injection-safety posts fit its taste) and +**Lemmy's technology/selfhosted communities** (Reddit refugees, receptive to +open-source self-hosted tools). + +--- + +## 8. Suggested launch sequence + +1. **Week 0 (prep):** polish README + 60s GIF; write the flagship "REST + free MCP tool" post; start genuinely participating in r/dataengineering, r/mcp, DuckDB Discord. +2. **Week 1 (seed quietly):** submit to *all* MCP directories + awesome lists + AlternativeTo. Post the demo in DuckDB Discord `#show-and-tell`. Publish the flagship blog post. +3. **Week 2 (niche):** dbt Community "Show and Tell"; SAP/ERPL communities; LLM-tooling Discords. Begin thread-jacking on Reddit/SO with genuinely helpful answers. +4. **Week 3 (big shot):** Show HN, timed weekday morning ET, with the build-story first comment. Same day: cross-post the DuckDB/MCP angle to r/mcp and r/DuckDB (which are launch-friendly). Pitch the AI newsletters. +5. **Ongoing:** ship one comparison/recipe post per week for SEO; keep answering questions; feed the MCP-governance angle to newsletters as features land. + +--- + +## 9. Guardrails (don't torch the goodwill) + +- **Always disclose affiliation.** "I built this" or "disclosure: I'm on the flAPI team." Undisclosed shilling gets you banned and screenshotted. +- **Value before link, every time.** Answer the actual question first; the link is a P.S., not the point. +- **Never astroturf** — no fake accounts, no sockpuppet upvotes. HN and Reddit detect it and the blowback is worse than no launch. +- **Read each community's self-promo rules** before posting; many require a ratio of contributions to promo, or a specific flair/megathread. +- **Lead with the honest limitation** ("read-only APIs; not a write layer; DuckDB-centric"). It builds more trust than any feature list and preempts the top comment. +- **Show up for the comments.** An unanswered launch thread dies; an engaged author thread climbs. + +--- + +*Last updated: 2026-07-13* diff --git a/docs/promotion/SUBMISSION_KIT.md b/docs/promotion/SUBMISSION_KIT.md new file mode 100644 index 0000000..f50874a --- /dev/null +++ b/docs/promotion/SUBMISSION_KIT.md @@ -0,0 +1,184 @@ +# flAPI Submission Kit + +Canonical, **fact-checked** metadata and per-channel instructions for executing +[PROMOTION_STRATEGY.md](../PROMOTION_STRATEGY.md). Every claim below was +verified against the source tree on 2026-07-13. Use these blurbs verbatim — +they are worded to survive skeptical audiences (HN, r/dataengineering). + +> **⚠️ Two claims from the playbook were corrected during verification:** +> 1. *"SQL injection structurally impossible"* is only true **for typed +> params** (typed validators → `?` placeholders → DuckDB prepared +> statements, `src/prepared_template_rewriter.cpp`, +> `src/query_executor.cpp`). Triple-brace `{{{ }}}`, untyped params and +> section-interpolated values still rely on validators. Always scope the +> claim. +> 2. flAPI is **BSL 1.1** (converts to MPL-2.0; production use permitted). +> It is *source-available*, **not open source**. Never say "open source" +> in any submission — this is the #1 predictable flame on HN/r/opensource. + +--- + +## Canonical metadata (copy-paste) + +| Field | Value | +|---|---| +| Name | flAPI | +| Repo | https://github.com/DataZooDE/flapi | +| One-liner | Turn SQL templates + YAML into REST endpoints **and** MCP tools — one static binary, DuckDB inside. | +| Install | `uvx --from flapi-io flapi -c flapi.yaml` · `pip install flapi-io` · GitHub-release binaries (Linux x86_64/ARM64, macOS ARM64, Windows) · Docker | +| MCP transport | **Streamable HTTP** at `/mcp/jsonrpc` (SSE for streaming). **No stdio** — stdio-only clients need a proxy such as `mcp-remote`. | +| Ports | Unified REST + MCP server on `8080` (configurable; `mcp.port` default `8081`) | +| Auth | Basic + JWT/OIDC; **per-tool RBAC**, fail-closed (tool without `allowed-roles` under auth = denied) | +| Tools | Dynamic — every configured endpoint with an `mcp-tool` block becomes a tool; flAPI is a *server generator*, not a fixed tool list | +| Security | Typed params bound as DuckDB prepared statements (injection structurally impossible *at bound sites*); typed validators (int/string/email/uuid/enum/date…); tool-description hygiene scanner (flags injection phrases in YAML tool descriptions at config load) | +| Caching | DuckLake: full/incremental refresh, snapshot time-travel | +| Data sources | Parquet/CSV, Postgres, BigQuery, S3/GCS/Azure, Iceberg, Delta + 50+ via DuckDB extensions; SAP ERP/BW via ERPL (demo in `examples/sqls/sap/`, stability caveats documented) | +| Language | C++17, single static binary, embedded DuckDB 1.5.3 | +| License | **BSL 1.1** → MPL-2.0; Additional Use Grant permits production use ("source-available", not open source) | +| Latest release | v26.07.13 | +| Honest limitations | Read-oriented data APIs (not a general CRUD backend) · DuckDB-centric · MCP over HTTP only · BSL license · young project | + +**Long blurb (directories):** + +> flAPI turns SQL templates and YAML configuration into governed REST +> endpoints and MCP tools — from the same config, with the same validators, +> RBAC and caching. It ships as a single static C++ binary with DuckDB +> embedded, so one `scp` (or `uvx --from flapi-io flapi`) puts a REST + MCP +> server in front of Parquet files, Postgres, BigQuery, S3 and 50+ other +> sources. Typed request parameters are bound as DuckDB prepared statements; +> per-tool RBAC is enforced fail-closed; results can be cached with +> DuckLake snapshots. Source-available under BSL 1.1 (production use +> permitted). + +--- + +## 🔴 Blocking gaps — fix before any launch + +1. **Wheel license metadata is wrong.** `.github/workflows/build.yaml` + passes `--license Apache-2.0` to bin-to-wheel, but the repo LICENSE is + BSL 1.1. PyPI is publicly showing the wrong license. Fix before *any* + publicity — this is exactly the kind of inconsistency HN finds in + minutes. (Tracked in the promotion tracking issue.) +2. **No 60-second demo GIF** (playbook Week-0 item). The money shot: + `uvx --from flapi-io flapi` → curl the endpoint → Claude calling the + same endpoint as an MCP tool. Suggested tooling: `vhs` (the repo's + visual-designer skill supports VHS terminal demos). +3. **README doesn't lead with MCP.** Title is "Instant SQL based APIs"; + the playbook says MCP is the most valuable 2026 angle. Move it up. +4. **MCP Registry PyPI validation.** The registry validates PyPI ownership + by finding `mcp-name: io.github.datazoode/flapi` in the PyPI package + README. Add that line to the bin-to-wheel description in the release + workflow, then publish (see below). + +--- + +## Channel-by-channel + +### 1. Official MCP Registry — `server.json` ✅ prepared (repo root) +- Prereq: item 4 above (mcp-name marker on PyPI), then: + ```bash + brew install mcp-publisher # or download from modelcontextprotocol/registry releases + mcp-publisher login github # authenticates the io.github.datazoode namespace + mcp-publisher publish # reads ./server.json + ``` +- Keep `version` in `server.json` in sync with releases (add to release checklist/workflow). + +### 2. punkpeye/awesome-mcp-servers — ✅ SUBMITTED: [PR #10023](https://github.com/punkpeye/awesome-mcp-servers/pull/10023) +- File: `README.md`, **Databases** section, alphabetical (case-insensitive) + — insert between `Dataring-engineering/mcp-server-trino` and + `davewind/mysql-mcp-server`. +- Entry line: + ```markdown + - [DataZooDE/flapi](https://github.com/DataZooDE/flapi) 🎖️ 🌊 🏠 - Turns SQL templates + YAML into REST endpoints and MCP tools from one config — a single binary with embedded DuckDB (Parquet, Postgres, BigQuery, S3 and 50+ sources), per-tool RBAC, and typed parameters bound as prepared statements. + ``` + (🎖️ official · 🌊 C/C++ · 🏠 local) +- PR title: `Add DataZooDE/flapi to Databases 🤖🤖🤖` — the trailing robots + opt into their documented agent fast-track (CONTRIBUTING.md). +- PR body: entry line + "Disclosure: submitted on behalf of the flAPI + maintainers (DataZooDE)." + +### 3. wong2/awesome-mcp-servers (mcpservers.org) — ✅ SUBMITTED 2026-07-13 via web form (review ≤12h, confirmation to jr@data-zoo.de) +- Their README states: *"We do not accept PRs. Please submit your MCP on the + website: https://mcpservers.org/submit"* (verified 2026-07-13 — a PR + attempt is rejected by GitHub permissions). +- Form fields (no login, no captcha; free tier is fine — skip the $39 + "Premium Submit"): + - Server Name: `flAPI` + - Short Description: `Turn SQL templates + YAML into REST APIs and MCP tools — one static binary with embedded DuckDB (Parquet, Postgres, BigQuery, S3 and 50+ sources), per-tool RBAC, and DuckLake caching.` + - Link: `https://github.com/DataZooDE/flapi` + - Category: `Database` + - Contact Email: maintainer address + +### 4. mcp.so — needs human (web form) +- https://mcp.so → Submit. Paste the long blurb; category Database/Data + Platform; note transport = streamable HTTP. + +### 5. Smithery — needs human (account) +- https://smithery.ai → dashboard or `smithery` CLI. flAPI is a + self-hosted HTTP server, so list it as a remote/self-hosted server, not + a hosted stdio package. + +### 6. Glama — needs human (claim listing) +- Glama auto-indexes from GitHub; check https://glama.ai/mcp/servers for + an existing flAPI entry and claim it with the GitHub org account. Note: + Glama badges emphasise "open-source" — if asked, say *source-available + (BSL 1.1)*. + +### 7. PulseMCP — needs human (web form) +- https://www.pulsemcp.com → "Submit" in top nav. Paste long blurb. + +### 8. awesome-selfhosted — ⛔ mostly blocked by license +- Their `licenses.yml` (FOSS-only) has no BUSL entry. There *is* a + `licenses-nonfree.yml` + a non-free (⊘) section; listing would require a + PR adding `BUSL-1.1` there plus the entry, and non-free submissions get + extra scrutiny. Possible but low ROI — deprioritise, or revisit if the + license ever changes / a change-date passes to MPL-2.0. + +### 9. AlternativeTo / LibHunt / SaaSHub — needs human (accounts, forms) +- Register flAPI as an alternative to: **PostgREST, Hasura, Datasette, + ROAPI, MXCP, soul, prest**. Use the long blurb; license field: + "BSL 1.1 (source-available)". + +### 10. DuckDB Discord `#show-and-tell` — draft ready +- `drafts/discord-duckdb.md`. Friendliest first post; do this before HN. + +### 11. dbt Community Forum "Show and Tell" — draft ready +- `drafts/dbt-forum.md`. + +### 12. Reddit — drafts ready, **prereq: 2–3 weeks of genuine participation** +- `drafts/reddit-r-mcp.md`, `reddit-r-duckdb.md`, `reddit-r-selfhosted.md`, + `reddit-r-dataengineering.md`, `reddit-r-opensource.md` (read its header — + r/opensource is likely a skip due to BSL). +- Weekly thread-jack search queries: `sql query into an api`, + `postgrest alternative`, `expose parquet rest`, `mcp server database`, + `serve parquet http`. + +### 13. Hacker News — draft ready, **prereq: GIF + README polish + license fix** +- `drafts/hn-show-hn.md` (title, first comment, pushback Q&A incl. BSL). +- Weekday 8–10am ET. Stay in the thread 4–6 hours. + +### 14. AI newsletters — draft ready +- `drafts/newsletter-pitch.md` → Latent Space, Ben's Bites, TLDR AI, + Data Engineering Weekly. + +### 15. Stack Overflow / dev.to — ongoing, needs human +- Answer questions tagged `duckdb`/`parquet`/`rest`/`postgrest`/`mcp` + fully first; flAPI as a P.S. with disclosure. Never copy-paste answers. + +### 16. MotherDuck guest post / DuckDB community extension — needs human outreach +- Pitch the "MCP + DuckDB governance" angle to MotherDuck devrel. +- Community-extension idea: the `embed://` VFS is currently app-internal; + extracting it is a real engineering project — treat as "investigate", + not a quick listing. + +### Blog posts (SEO/evergreen) — 2 of 6 drafted +- ✅ `drafts/blog-flagship-rest-plus-mcp.md` (the flagship narrative) +- ✅ `drafts/blog-security-typed-params.md` (HN/lobste.rs security piece) +- ☐ Comparison pages (vs PostgREST / Hasura / Datasette+ROAPI / MXCP) +- ☐ Recipe posts (BigQuery→MCP in 5 min; SAP BW→REST; DuckLake caching) +- ☐ Self-packaging engineering story (appended ZIP / Mach-O segment) + +--- + +*Generated 2026-07-13. Claims verified against source; re-verify before +each submission if the code has moved.* diff --git a/docs/promotion/TRACKER.md b/docs/promotion/TRACKER.md new file mode 100644 index 0000000..9d7a195 --- /dev/null +++ b/docs/promotion/TRACKER.md @@ -0,0 +1,50 @@ +# Promotion execution tracker + +Execution tracker for [PROMOTION_STRATEGY.md](../PROMOTION_STRATEGY.md). All +copy-paste material lives in [SUBMISSION_KIT.md](SUBMISSION_KIT.md) and +[drafts/](drafts/). License bug: [#97](https://github.com/DataZooDE/flapi/issues/97). + +## ✅ Prepared (in repo) + +- [x] Fact-check of all playbook claims against source (2 corrections: injection claim scoped to typed params; BSL ≠ open source) +- [x] `server.json` for the official MCP Registry (repo root) +- [x] `docs/promotion/SUBMISSION_KIT.md` — canonical metadata + per-channel steps +- [x] Launch copy drafts: Show HN (+first comment +pushback Q&A), r/mcp, r/DuckDB, r/selfhosted, r/dataengineering, r/opensource (recommends skip), DuckDB Discord, dbt forum, newsletter pitch +- [x] Blog drafts: flagship "REST + free MCP tool", security deep-dive on typed-param prepared statements + +## 🔴 Blockers before launch (Week 0) + +- [x] Fix wheel license metadata (Apache-2.0 → BUSL-1.1) — [PR #99](https://github.com/DataZooDE/flapi/pull/99), closes #97 (needs a patch release to land on PyPI) +- [x] Add `mcp-name: io.github.datazoode/flapi` marker to PyPI README — included in PR #99 +- [x] Record demo GIF — `docs/promotion/assets/flapi-demo.gif` (36 s, 825 KB, VHS; embedded at the top of Readme.md). Re-record anytime: `vhs docs/promotion/assets/demo.tape` +- [x] Four more scenario GIFs (all real recordings; see `assets/README.md`): **agent-over-MCP** (`flapi-demo-agent.gif` — Claude Code answering via `customer_lookup`), **agent on BigQuery** (`flapi-demo-bigquery.gif` — public dataset, typed params, "the model never writes SQL"), **self-packaging deploy** (`flapi-demo-pack.gif`), **SharePoint lists** (`flapi-demo-sharepoint.gif` — live Microsoft 365 list via the erpl-web extension's `ATTACH ... TYPE sharepoint_lists`, agent analyzes the tracker). SAP remains blocked (no live ABAP system) rather than faked. +- [x] Filed + fixed two launch-blocking bugs found while recording: [#100](https://github.com/DataZooDE/flapi/issues/100) MCP `logging: null` breaks the Claude Code handshake ([PR #102](https://github.com/DataZooDE/flapi/pull/102)); [#101](https://github.com/DataZooDE/flapi/issues/101) `read_parquet` fails on `embed://` bundles ([PR #103](https://github.com/DataZooDE/flapi/pull/103)). **Both must merge + ship in a release before the MCP launch angle works for real users.** +- [ ] README polish: lead with the MCP angle + +## 📤 Submissions (Week 1 — "seed quietly") + +- [ ] Official MCP Registry: `mcp-publisher login github && mcp-publisher publish` (after blockers 1–2) +- [x] PR to punkpeye/awesome-mcp-servers — [punkpeye#10023](https://github.com/punkpeye/awesome-mcp-servers/pull/10023), submitted 2026-07-13 +- [x] wong2 list (mcpservers.org): submitted via web form 2026-07-13 (free tier, category Database, contact jr@data-zoo.de) — "reviewed within 12 hours", approval lands by email +- [ ] mcp.so submit form · Smithery · claim Glama listing · PulseMCP submit +- [ ] AlternativeTo / LibHunt / SaaSHub (alternative to PostgREST, Hasura, Datasette, ROAPI, MXCP) +- [ ] Publish flagship blog post; post DuckDB Discord #show-and-tell (draft ready) +- [ ] ⛔ awesome-selfhosted: license-blocked (BSL not accepted; non-free ⊘ path possible, low ROI) + +## 🧑 Human-only groundwork (start now, 2–3 week lead time) + +- [ ] Warm Reddit account: genuine answers in r/dataengineering, r/mcp, r/DuckDB, r/selfhosted +- [ ] Join DuckDB Discord, dbt Slack/forum, LLM-tooling Discords; participate before posting + +## 📅 Week 2–3 + +- [ ] dbt Community Forum Show-and-Tell (draft ready) · SAP/ERPL communities · LLM Discords +- [ ] Reddit thread-jacking with the saved search queries (SUBMISSION_KIT.md §12) +- [ ] Show HN (draft ready; weekday 8–10am ET; stay in thread 4–6h) + same-day r/mcp + r/DuckDB posts +- [ ] Pitch newsletters (draft ready): Latent Space, Ben's Bites, TLDR AI, Data Engineering Weekly + +## 🔁 Ongoing + +- [ ] One comparison/recipe post per week (vs PostgREST / Hasura / Datasette+ROAPI / MXCP; BigQuery→MCP recipe; SAP BW→REST; self-packaging engineering story) +- [ ] SO/dev.to answers with disclosure +- [ ] MotherDuck guest-post outreach; investigate `embed://` as a DuckDB community extension diff --git a/docs/promotion/assets/README.md b/docs/promotion/assets/README.md new file mode 100644 index 0000000..f494bbb --- /dev/null +++ b/docs/promotion/assets/README.md @@ -0,0 +1,42 @@ +# Demo GIFs + +All demos are **real recordings** against live servers — no staged output. +Each `.tape` is a [VHS](https://github.com/charmbracelet/vhs) script; the +comment header in each tape says where to run it from and what it needs. + +| GIF | Story | Length | Recorded against | +|---|---|---|---| +| `flapi-demo.gif` | One SQL file → REST endpoint + MCP tool (curl both) | 36 s | released v26.07.13 (`uvx`) | +| `flapi-demo-agent.gif` | Claude Code as an agent on flAPI MCP tools (Basic auth, per-tool RBAC) | 38 s | trunk build with fix [#100](https://github.com/DataZooDE/flapi/issues/100) | +| `flapi-demo-bigquery.gif` | An AI agent on top of BigQuery: one YAML tool, typed params, "the model never writes SQL" | 35 s | trunk build with fix #100; needs Google ADC | +| `flapi-demo-pack.gif` | `flapi pack`: fold config + SQL + data into one binary, deploy = scp | 29 s | released v26.07.13 (`uvx`) | +| `flapi-demo-sharepoint.gif` | A SharePoint list as REST + MCP tool via the [erpl-web](https://github.com/DataZooDE/erpl-web) DuckDB extension (`ATTACH ... TYPE sharepoint_lists`), agent analyzes the live list | 34 s | trunk build with fix #100; needs Azure app credentials (Graph) | + +Demo project trees used by the recordings are in `demo-projects/` +(`pack-myapi/` for the pack demo — CSV data, see #101 for why not parquet; +`bigquery/` for the BigQuery demo — set your own `project_id`, the GIF never +shows it). + +**Recording notes** + +- The two agent demos require a build that includes the `#100` fix + (`"logging": null` in the MCP initialize response makes Claude Code reject + the handshake on ≤ v26.07.13). Re-record them only on a fixed build, and + re-record `flapi-demo.gif` / `flapi-demo-pack.gif` with plain `uvx` once a + release containing the fixes ships. +- Port 8080 was occupied on the recording machine, hence 8084/8085/8086 in + the tapes. +- The SharePoint demo runs on the **erpl_web** community extension + (`INSTALL erpl_web FROM community` — works on flAPI's embedded DuckDB + 1.5.3 despite the erpl-web README saying 1.5.4+). Credentials come from + erpl-web's `.env.microsoft` (`ERPL_MS_*` vars, Azure app with Graph + client-credentials); the committed `demo-projects/sharepoint/flapi.yaml` + only references `{{env.*}}` — no secrets. The "project tracker" rows + (items 54–59 in TestList on clouddatazoo.sharepoint.com) were seeded for + the demo via `graph_sharepoint_create_item`; the query filters + `Number >= 10` so the pre-existing Foo/Bar/Baz CI fixture rows stay + untouched and invisible. +- Scenario considered and NOT recorded, honestly: **SAP/ERPL** — the + example targets a localhost ABAP trial system that wasn't running; the + ERPL demo needs a live SAP box. Record `examples/sqls/sap/` flows when + one is up. diff --git a/docs/promotion/assets/agent.tape b/docs/promotion/assets/agent.tape new file mode 100644 index 0000000..8262e1d --- /dev/null +++ b/docs/promotion/assets/agent.tape @@ -0,0 +1,51 @@ +# flAPI agent demo — Claude Code calling flAPI MCP tools. +# Record from a dir containing examples/ (flapi-test.yaml). Needs the +# locally built flapi (>= fix #100) on PATH and port 8084 free. +Output flapi-demo-agent.gif + +Set FontSize 17 +Set Width 1200 +Set Height 720 +Set Padding 12 +Set Theme "Catppuccin Mocha" +Set TypingSpeed 35ms +Set Shell bash + +Hide +Type `export PATH=/home/jr/Projects/datazoo/flapi/build/debug:$PATH; claude mcp remove flapi >/dev/null 2>&1; clear` +Enter +Sleep 2s +Show + +Type `# Every flAPI endpoint is also an MCP tool — let an agent talk to your data:` +Enter +Sleep 1.5s +Type `flapi -c examples/flapi-test.yaml --port 8084 > /tmp/flapi.log 2>&1 &` +Enter +Hide +Sleep 45s +Show +Type `curl -s -m 5 localhost:8084/mcp/health | jq '{status, tools_count}'` +Enter +Sleep 2.5s + +Type `# Point Claude Code at it:` +Enter +Sleep 1s +Type `claude mcp add --transport http flapi http://localhost:8084/mcp/jsonrpc --header "Authorization: Basic YWRtaW46c2VjcmV0"` +Enter +Sleep 3s + +Type `# Ask questions in plain English:` +Enter +Sleep 1s +Type `claude -p "Which market segment is customer 42 in, and what is their balance? Use the flapi tools." --allowedTools mcp__flapi__customer_lookup` +Enter +Hide +Sleep 60s +Show +Sleep 1.5s + +Type `# Same SQL template, same validators, same per-tool RBAC as the REST endpoint.` +Enter +Sleep 3.5s diff --git a/docs/promotion/assets/bigquery.tape b/docs/promotion/assets/bigquery.tape new file mode 100644 index 0000000..c074d53 --- /dev/null +++ b/docs/promotion/assets/bigquery.tape @@ -0,0 +1,48 @@ +# flAPI BigQuery agent demo — record from /tmp/bqdemo (flapi.yaml + sqls/). +# Needs: locally built flapi (>= fix #100) on PATH, Google ADC for BigQuery, +# port 8086 free, claude CLI logged in. +Output flapi-demo-bigquery.gif + +Set FontSize 17 +Set Width 1200 +Set Height 720 +Set Padding 12 +Set Theme "Catppuccin Mocha" +Set TypingSpeed 35ms +Set Shell bash + +Hide +Type `export PATH=/home/jr/Projects/datazoo/flapi/build/debug:$PATH; claude mcp remove flapi >/dev/null 2>&1; clear` +Enter +Sleep 2s +Show + +Type `# Put an AI agent on top of BigQuery — one SQL template, one YAML tool:` +Enter +Sleep 1.5s +Type `cat sqls/babynames.sql` +Enter +Sleep 4s + +Type `# state/top are typed params -> bound as prepared statements. Serve it:` +Enter +Sleep 1s +Type `flapi -c flapi.yaml --port 8086 > /tmp/flapi.log 2>&1 &` +Enter +Hide +Sleep 60s +Show +Type `claude mcp add --transport http flapi http://localhost:8086/mcp/jsonrpc` +Enter +Sleep 3s + +Type `claude -p "What are the five most popular baby names in Texas since 1910? Use the flapi tools." --allowedTools mcp__flapi__top_baby_names` +Enter +Hide +Sleep 90s +Show +Sleep 2s + +Type `# The model never writes SQL — it can only call your curated, validated query.` +Enter +Sleep 4s diff --git a/docs/promotion/assets/demo-projects/bigquery/flapi.yaml b/docs/promotion/assets/demo-projects/bigquery/flapi.yaml new file mode 100644 index 0000000..1c86006 --- /dev/null +++ b/docs/promotion/assets/demo-projects/bigquery/flapi.yaml @@ -0,0 +1,16 @@ +project-name: babynames-api +project-description: US baby names served from BigQuery public data + +template: + path: './sqls' + +connections: + bigquery: + init: | + INSTALL 'bigquery' FROM community; + LOAD 'bigquery'; + properties: + project_id: 't-agentspace-customer' + +duckdb: + db_path: ':memory:' diff --git a/docs/promotion/assets/demo-projects/bigquery/sqls/babynames.sql b/docs/promotion/assets/demo-projects/bigquery/sqls/babynames.sql new file mode 100644 index 0000000..d3ece38 --- /dev/null +++ b/docs/promotion/assets/demo-projects/bigquery/sqls/babynames.sql @@ -0,0 +1,7 @@ +SELECT name, SUM(number) AS total +FROM bigquery_scan('bigquery-public-data.usa_names.usa_1910_2013', + billing_project='{{{ conn.project_id }}}') +WHERE state = {{ params.state }} +GROUP BY name +ORDER BY total DESC +LIMIT {{#params.top}}{{ params.top }}{{/params.top}}{{^params.top}}5{{/params.top}} diff --git a/docs/promotion/assets/demo-projects/bigquery/sqls/babynames.yaml b/docs/promotion/assets/demo-projects/bigquery/sqls/babynames.yaml new file mode 100644 index 0000000..5d5dc5f --- /dev/null +++ b/docs/promotion/assets/demo-projects/bigquery/sqls/babynames.yaml @@ -0,0 +1,25 @@ +mcp-tool: + name: top_baby_names + description: Most popular US baby names for a state, from BigQuery public data + +request: + - field-name: state + field-in: query + description: Two-letter US state code, e.g. TX + required: true + validators: + - type: string + pattern: '^[A-Z]{2}$' + + - field-name: top + field-in: query + description: How many names to return + required: false + validators: + - type: int + min: 1 + max: 50 + +template-source: babynames.sql +connection: + - bigquery diff --git a/docs/promotion/assets/demo-projects/pack-myapi/data/customers.csv b/docs/promotion/assets/demo-projects/pack-myapi/data/customers.csv new file mode 100644 index 0000000..0dc1bfe --- /dev/null +++ b/docs/promotion/assets/demo-projects/pack-myapi/data/customers.csv @@ -0,0 +1,1501 @@ +id,name,segment,balance +1,"Customer#000000001",BUILDING,711.56 +2,"Customer#000000002",AUTOMOBILE,121.65 +3,"Customer#000000003",AUTOMOBILE,7498.12 +4,"Customer#000000004",MACHINERY,2866.83 +5,"Customer#000000005",HOUSEHOLD,794.47 +6,"Customer#000000006",AUTOMOBILE,7638.57 +7,"Customer#000000007",AUTOMOBILE,9561.95 +8,"Customer#000000008",BUILDING,6819.74 +9,"Customer#000000009",FURNITURE,8324.07 +10,"Customer#000000010",HOUSEHOLD,2753.54 +11,"Customer#000000011",BUILDING,-272.6 +12,"Customer#000000012",HOUSEHOLD,3396.49 +13,"Customer#000000013",BUILDING,3857.34 +14,"Customer#000000014",FURNITURE,5266.3 +15,"Customer#000000015",HOUSEHOLD,2788.52 +16,"Customer#000000016",FURNITURE,4681.03 +17,"Customer#000000017",AUTOMOBILE,6.34 +18,"Customer#000000018",BUILDING,5494.43 +19,"Customer#000000019",HOUSEHOLD,8914.71 +20,"Customer#000000020",FURNITURE,7603.4 +21,"Customer#000000021",MACHINERY,1428.25 +22,"Customer#000000022",MACHINERY,591.98 +23,"Customer#000000023",HOUSEHOLD,3332.02 +24,"Customer#000000024",MACHINERY,9255.67 +25,"Customer#000000025",FURNITURE,7133.7 +26,"Customer#000000026",AUTOMOBILE,5182.05 +27,"Customer#000000027",BUILDING,5679.84 +28,"Customer#000000028",FURNITURE,1007.18 +29,"Customer#000000029",FURNITURE,7618.27 +30,"Customer#000000030",BUILDING,9321.01 +31,"Customer#000000031",HOUSEHOLD,5236.89 +32,"Customer#000000032",BUILDING,3471.53 +33,"Customer#000000033",AUTOMOBILE,-78.56 +34,"Customer#000000034",HOUSEHOLD,8589.7 +35,"Customer#000000035",HOUSEHOLD,1228.24 +36,"Customer#000000036",BUILDING,4987.27 +37,"Customer#000000037",FURNITURE,-917.75 +38,"Customer#000000038",HOUSEHOLD,6345.11 +39,"Customer#000000039",AUTOMOBILE,6264.31 +40,"Customer#000000040",BUILDING,1335.3 +41,"Customer#000000041",HOUSEHOLD,270.95 +42,"Customer#000000042",BUILDING,8727.01 +43,"Customer#000000043",MACHINERY,9904.28 +44,"Customer#000000044",AUTOMOBILE,7315.94 +45,"Customer#000000045",AUTOMOBILE,9983.38 +46,"Customer#000000046",AUTOMOBILE,5744.59 +47,"Customer#000000047",BUILDING,274.58 +48,"Customer#000000048",BUILDING,3792.5 +49,"Customer#000000049",FURNITURE,4573.94 +50,"Customer#000000050",MACHINERY,4266.13 +51,"Customer#000000051",FURNITURE,855.87 +52,"Customer#000000052",HOUSEHOLD,5630.28 +53,"Customer#000000053",HOUSEHOLD,4113.64 +54,"Customer#000000054",AUTOMOBILE,868.9 +55,"Customer#000000055",MACHINERY,4572.11 +56,"Customer#000000056",FURNITURE,6530.86 +57,"Customer#000000057",AUTOMOBILE,4151.93 +58,"Customer#000000058",HOUSEHOLD,6478.46 +59,"Customer#000000059",MACHINERY,3458.6 +60,"Customer#000000060",MACHINERY,2741.87 +61,"Customer#000000061",FURNITURE,1536.24 +62,"Customer#000000062",MACHINERY,595.61 +63,"Customer#000000063",AUTOMOBILE,9331.13 +64,"Customer#000000064",BUILDING,-646.64 +65,"Customer#000000065",AUTOMOBILE,8795.16 +66,"Customer#000000066",HOUSEHOLD,242.77 +67,"Customer#000000067",MACHINERY,8166.59 +68,"Customer#000000068",HOUSEHOLD,6853.37 +69,"Customer#000000069",HOUSEHOLD,1709.28 +70,"Customer#000000070",FURNITURE,4867.52 +71,"Customer#000000071",HOUSEHOLD,-611.19 +72,"Customer#000000072",FURNITURE,-362.86 +73,"Customer#000000073",BUILDING,4288.5 +74,"Customer#000000074",MACHINERY,2764.43 +75,"Customer#000000075",AUTOMOBILE,6684.1 +76,"Customer#000000076",FURNITURE,5745.33 +77,"Customer#000000077",BUILDING,1738.87 +78,"Customer#000000078",FURNITURE,7136.97 +79,"Customer#000000079",MACHINERY,5121.28 +80,"Customer#000000080",FURNITURE,7383.53 +81,"Customer#000000081",BUILDING,2023.71 +82,"Customer#000000082",AUTOMOBILE,9468.34 +83,"Customer#000000083",BUILDING,6463.51 +84,"Customer#000000084",FURNITURE,5174.71 +85,"Customer#000000085",FURNITURE,3386.64 +86,"Customer#000000086",HOUSEHOLD,3306.32 +87,"Customer#000000087",FURNITURE,6327.54 +88,"Customer#000000088",AUTOMOBILE,8031.44 +89,"Customer#000000089",FURNITURE,1530.76 +90,"Customer#000000090",BUILDING,7354.23 +91,"Customer#000000091",AUTOMOBILE,4643.14 +92,"Customer#000000092",MACHINERY,1182.91 +93,"Customer#000000093",MACHINERY,2182.52 +94,"Customer#000000094",HOUSEHOLD,5500.11 +95,"Customer#000000095",MACHINERY,5327.38 +96,"Customer#000000096",AUTOMOBILE,6323.92 +97,"Customer#000000097",AUTOMOBILE,2164.48 +98,"Customer#000000098",BUILDING,-551.37 +99,"Customer#000000099",HOUSEHOLD,4088.65 +100,"Customer#000000100",FURNITURE,9889.89 +101,"Customer#000000101",MACHINERY,7470.96 +102,"Customer#000000102",BUILDING,8462.17 +103,"Customer#000000103",BUILDING,2757.45 +104,"Customer#000000104",FURNITURE,-588.38 +105,"Customer#000000105",MACHINERY,9091.82 +106,"Customer#000000106",MACHINERY,3288.42 +107,"Customer#000000107",AUTOMOBILE,2514.15 +108,"Customer#000000108",BUILDING,2259.38 +109,"Customer#000000109",BUILDING,-716.1 +110,"Customer#000000110",AUTOMOBILE,7462.99 +111,"Customer#000000111",MACHINERY,6505.26 +112,"Customer#000000112",FURNITURE,2953.35 +113,"Customer#000000113",BUILDING,2912.0 +114,"Customer#000000114",FURNITURE,1027.46 +115,"Customer#000000115",HOUSEHOLD,7508.92 +116,"Customer#000000116",BUILDING,8403.99 +117,"Customer#000000117",FURNITURE,3950.83 +118,"Customer#000000118",AUTOMOBILE,3582.37 +119,"Customer#000000119",FURNITURE,3930.35 +120,"Customer#000000120",MACHINERY,363.75 +121,"Customer#000000121",BUILDING,6428.32 +122,"Customer#000000122",HOUSEHOLD,7865.46 +123,"Customer#000000123",BUILDING,5897.83 +124,"Customer#000000124",AUTOMOBILE,1842.49 +125,"Customer#000000125",FURNITURE,-234.12 +126,"Customer#000000126",HOUSEHOLD,1001.39 +127,"Customer#000000127",MACHINERY,9280.71 +128,"Customer#000000128",HOUSEHOLD,-986.96 +129,"Customer#000000129",HOUSEHOLD,9127.27 +130,"Customer#000000130",HOUSEHOLD,5073.58 +131,"Customer#000000131",HOUSEHOLD,8595.53 +132,"Customer#000000132",HOUSEHOLD,162.57 +133,"Customer#000000133",AUTOMOBILE,2314.67 +134,"Customer#000000134",BUILDING,4608.9 +135,"Customer#000000135",FURNITURE,8732.91 +136,"Customer#000000136",FURNITURE,-842.39 +137,"Customer#000000137",HOUSEHOLD,7838.3 +138,"Customer#000000138",MACHINERY,430.59 +139,"Customer#000000139",MACHINERY,7897.78 +140,"Customer#000000140",MACHINERY,9963.15 +141,"Customer#000000141",FURNITURE,6706.14 +142,"Customer#000000142",AUTOMOBILE,2209.81 +143,"Customer#000000143",MACHINERY,2186.5 +144,"Customer#000000144",MACHINERY,6417.31 +145,"Customer#000000145",HOUSEHOLD,9748.93 +146,"Customer#000000146",FURNITURE,3328.68 +147,"Customer#000000147",AUTOMOBILE,8071.4 +148,"Customer#000000148",HOUSEHOLD,2135.6 +149,"Customer#000000149",AUTOMOBILE,8959.65 +150,"Customer#000000150",MACHINERY,3849.48 +151,"Customer#000000151",HOUSEHOLD,5187.02 +152,"Customer#000000152",BUILDING,1215.18 +153,"Customer#000000153",HOUSEHOLD,5454.26 +154,"Customer#000000154",FURNITURE,4695.12 +155,"Customer#000000155",AUTOMOBILE,5902.85 +156,"Customer#000000156",AUTOMOBILE,9302.95 +157,"Customer#000000157",BUILDING,9768.73 +158,"Customer#000000158",AUTOMOBILE,6160.95 +159,"Customer#000000159",HOUSEHOLD,2060.06 +160,"Customer#000000160",MACHINERY,4363.17 +161,"Customer#000000161",MACHINERY,3714.06 +162,"Customer#000000162",MACHINERY,6268.99 +163,"Customer#000000163",FURNITURE,2948.61 +164,"Customer#000000164",HOUSEHOLD,208.45 +165,"Customer#000000165",HOUSEHOLD,3349.92 +166,"Customer#000000166",FURNITURE,2042.21 +167,"Customer#000000167",AUTOMOBILE,1468.09 +168,"Customer#000000168",FURNITURE,-808.56 +169,"Customer#000000169",FURNITURE,4483.83 +170,"Customer#000000170",BUILDING,7687.89 +171,"Customer#000000171",MACHINERY,2379.91 +172,"Customer#000000172",MACHINERY,1134.4 +173,"Customer#000000173",BUILDING,845.84 +174,"Customer#000000174",FURNITURE,1944.73 +175,"Customer#000000175",FURNITURE,1975.35 +176,"Customer#000000176",FURNITURE,-375.76 +177,"Customer#000000177",BUILDING,7457.5 +178,"Customer#000000178",FURNITURE,2272.5 +179,"Customer#000000179",MACHINERY,-43.08 +180,"Customer#000000180",FURNITURE,-92.58 +181,"Customer#000000181",FURNITURE,3929.96 +182,"Customer#000000182",AUTOMOBILE,4810.22 +183,"Customer#000000183",HOUSEHOLD,4419.89 +184,"Customer#000000184",AUTOMOBILE,170.46 +185,"Customer#000000185",BUILDING,2788.76 +186,"Customer#000000186",HOUSEHOLD,8737.5 +187,"Customer#000000187",FURNITURE,-774.22 +188,"Customer#000000188",BUILDING,9533.37 +189,"Customer#000000189",MACHINERY,-594.05 +190,"Customer#000000190",AUTOMOBILE,1657.46 +191,"Customer#000000191",BUILDING,2945.16 +192,"Customer#000000192",MACHINERY,8239.96 +193,"Customer#000000193",MACHINERY,8024.55 +194,"Customer#000000194",HOUSEHOLD,6696.49 +195,"Customer#000000195",AUTOMOBILE,4873.91 +196,"Customer#000000196",FURNITURE,7760.33 +197,"Customer#000000197",AUTOMOBILE,9860.22 +198,"Customer#000000198",AUTOMOBILE,3824.76 +199,"Customer#000000199",FURNITURE,7654.31 +200,"Customer#000000200",BUILDING,9967.6 +201,"Customer#000000201",MACHINERY,4614.4 +202,"Customer#000000202",AUTOMOBILE,2237.64 +203,"Customer#000000203",MACHINERY,7960.63 +204,"Customer#000000204",BUILDING,-627.76 +205,"Customer#000000205",BUILDING,7161.52 +206,"Customer#000000206",AUTOMOBILE,-274.79 +207,"Customer#000000207",AUTOMOBILE,-439.98 +208,"Customer#000000208",MACHINERY,6239.89 +209,"Customer#000000209",FURNITURE,8873.46 +210,"Customer#000000210",HOUSEHOLD,7250.14 +211,"Customer#000000211",BUILDING,4198.72 +212,"Customer#000000212",BUILDING,957.58 +213,"Customer#000000213",HOUSEHOLD,9987.71 +214,"Customer#000000214",MACHINERY,1526.59 +215,"Customer#000000215",FURNITURE,3379.2 +216,"Customer#000000216",FURNITURE,-776.08 +217,"Customer#000000217",AUTOMOBILE,378.33 +218,"Customer#000000218",MACHINERY,9541.19 +219,"Customer#000000219",AUTOMOBILE,9858.57 +220,"Customer#000000220",BUILDING,9131.64 +221,"Customer#000000221",BUILDING,1609.39 +222,"Customer#000000222",BUILDING,8893.76 +223,"Customer#000000223",BUILDING,7476.2 +224,"Customer#000000224",BUILDING,8465.15 +225,"Customer#000000225",AUTOMOBILE,8893.2 +226,"Customer#000000226",AUTOMOBILE,9008.61 +227,"Customer#000000227",MACHINERY,1808.23 +228,"Customer#000000228",FURNITURE,6868.12 +229,"Customer#000000229",BUILDING,7568.07 +230,"Customer#000000230",MACHINERY,1682.83 +231,"Customer#000000231",BUILDING,283.55 +232,"Customer#000000232",HOUSEHOLD,554.71 +233,"Customer#000000233",FURNITURE,3998.24 +234,"Customer#000000234",AUTOMOBILE,8383.51 +235,"Customer#000000235",AUTOMOBILE,754.41 +236,"Customer#000000236",AUTOMOBILE,5384.59 +237,"Customer#000000237",HOUSEHOLD,-160.02 +238,"Customer#000000238",HOUSEHOLD,3482.32 +239,"Customer#000000239",FURNITURE,5398.77 +240,"Customer#000000240",MACHINERY,7139.68 +241,"Customer#000000241",AUTOMOBILE,6569.34 +242,"Customer#000000242",MACHINERY,1975.41 +243,"Customer#000000243",AUTOMOBILE,620.73 +244,"Customer#000000244",HOUSEHOLD,2506.38 +245,"Customer#000000245",MACHINERY,3720.15 +246,"Customer#000000246",AUTOMOBILE,9584.96 +247,"Customer#000000247",HOUSEHOLD,8495.92 +248,"Customer#000000248",FURNITURE,8908.35 +249,"Customer#000000249",MACHINERY,-234.01 +250,"Customer#000000250",FURNITURE,2869.97 +251,"Customer#000000251",HOUSEHOLD,9585.32 +252,"Customer#000000252",FURNITURE,3561.74 +253,"Customer#000000253",AUTOMOBILE,9139.52 +254,"Customer#000000254",MACHINERY,1915.35 +255,"Customer#000000255",BUILDING,3196.07 +256,"Customer#000000256",HOUSEHOLD,1299.92 +257,"Customer#000000257",AUTOMOBILE,-339.85 +258,"Customer#000000258",MACHINERY,6022.27 +259,"Customer#000000259",HOUSEHOLD,3335.29 +260,"Customer#000000260",MACHINERY,9196.11 +261,"Customer#000000261",AUTOMOBILE,7094.22 +262,"Customer#000000262",AUTOMOBILE,1561.8 +263,"Customer#000000263",FURNITURE,1162.03 +264,"Customer#000000264",MACHINERY,3195.83 +265,"Customer#000000265",MACHINERY,8275.8 +266,"Customer#000000266",HOUSEHOLD,5481.0 +267,"Customer#000000267",AUTOMOBILE,3166.94 +268,"Customer#000000268",MACHINERY,6821.01 +269,"Customer#000000269",MACHINERY,7667.35 +270,"Customer#000000270",AUTOMOBILE,9192.5 +271,"Customer#000000271",MACHINERY,1490.35 +272,"Customer#000000272",MACHINERY,-746.03 +273,"Customer#000000273",FURNITURE,-675.05 +274,"Customer#000000274",FURNITURE,4425.42 +275,"Customer#000000275",AUTOMOBILE,5067.31 +276,"Customer#000000276",AUTOMOBILE,2292.67 +277,"Customer#000000277",BUILDING,8876.1 +278,"Customer#000000278",BUILDING,7621.56 +279,"Customer#000000279",AUTOMOBILE,9663.23 +280,"Customer#000000280",BUILDING,3952.84 +281,"Customer#000000281",BUILDING,4361.7 +282,"Customer#000000282",HOUSEHOLD,1125.45 +283,"Customer#000000283",AUTOMOBILE,4450.03 +284,"Customer#000000284",AUTOMOBILE,593.52 +285,"Customer#000000285",FURNITURE,7276.72 +286,"Customer#000000286",HOUSEHOLD,-109.73 +287,"Customer#000000287",MACHINERY,1734.18 +288,"Customer#000000288",HOUSEHOLD,5339.43 +289,"Customer#000000289",AUTOMOBILE,-215.75 +290,"Customer#000000290",MACHINERY,1811.35 +291,"Customer#000000291",HOUSEHOLD,4261.68 +292,"Customer#000000292",HOUSEHOLD,2975.43 +293,"Customer#000000293",MACHINERY,-43.79 +294,"Customer#000000294",BUILDING,-994.79 +295,"Customer#000000295",HOUSEHOLD,9497.89 +296,"Customer#000000296",BUILDING,8081.52 +297,"Customer#000000297",HOUSEHOLD,7096.32 +298,"Customer#000000298",BUILDING,3812.84 +299,"Customer#000000299",HOUSEHOLD,5380.5 +300,"Customer#000000300",AUTOMOBILE,8084.92 +301,"Customer#000000301",HOUSEHOLD,9305.05 +302,"Customer#000000302",MACHINERY,1107.42 +303,"Customer#000000303",AUTOMOBILE,9339.57 +304,"Customer#000000304",MACHINERY,9217.55 +305,"Customer#000000305",FURNITURE,4356.59 +306,"Customer#000000306",AUTOMOBILE,3268.01 +307,"Customer#000000307",FURNITURE,346.59 +308,"Customer#000000308",HOUSEHOLD,4150.76 +309,"Customer#000000309",FURNITURE,8824.78 +310,"Customer#000000310",FURNITURE,3186.57 +311,"Customer#000000311",AUTOMOBILE,6589.5 +312,"Customer#000000312",AUTOMOBILE,-178.84 +313,"Customer#000000313",HOUSEHOLD,6115.81 +314,"Customer#000000314",MACHINERY,2394.92 +315,"Customer#000000315",FURNITURE,348.58 +316,"Customer#000000316",MACHINERY,4571.78 +317,"Customer#000000317",HOUSEHOLD,956.88 +318,"Customer#000000318",HOUSEHOLD,9149.98 +319,"Customer#000000319",FURNITURE,1834.36 +320,"Customer#000000320",MACHINERY,6082.74 +321,"Customer#000000321",FURNITURE,7718.77 +322,"Customer#000000322",HOUSEHOLD,4489.98 +323,"Customer#000000323",AUTOMOBILE,1137.67 +324,"Customer#000000324",FURNITURE,806.59 +325,"Customer#000000325",HOUSEHOLD,2377.34 +326,"Customer#000000326",HOUSEHOLD,1906.52 +327,"Customer#000000327",MACHINERY,8762.16 +328,"Customer#000000328",BUILDING,6709.9 +329,"Customer#000000329",BUILDING,-651.91 +330,"Customer#000000330",MACHINERY,8244.73 +331,"Customer#000000331",AUTOMOBILE,170.27 +332,"Customer#000000332",HOUSEHOLD,-267.09 +333,"Customer#000000333",AUTOMOBILE,8018.89 +334,"Customer#000000334",BUILDING,-405.91 +335,"Customer#000000335",HOUSEHOLD,6837.46 +336,"Customer#000000336",AUTOMOBILE,9241.49 +337,"Customer#000000337",MACHINERY,-270.59 +338,"Customer#000000338",FURNITURE,4092.49 +339,"Customer#000000339",HOUSEHOLD,8438.07 +340,"Customer#000000340",BUILDING,4667.12 +341,"Customer#000000341",FURNITURE,8247.11 +342,"Customer#000000342",AUTOMOBILE,7186.74 +343,"Customer#000000343",HOUSEHOLD,5521.36 +344,"Customer#000000344",FURNITURE,-544.95 +345,"Customer#000000345",AUTOMOBILE,1936.77 +346,"Customer#000000346",FURNITURE,238.14 +347,"Customer#000000347",BUILDING,7348.92 +348,"Customer#000000348",HOUSEHOLD,3310.49 +349,"Customer#000000349",BUILDING,-565.35 +350,"Customer#000000350",BUILDING,19.31 +351,"Customer#000000351",AUTOMOBILE,3419.54 +352,"Customer#000000352",HOUSEHOLD,6257.88 +353,"Customer#000000353",BUILDING,3199.03 +354,"Customer#000000354",BUILDING,7095.95 +355,"Customer#000000355",FURNITURE,8727.9 +356,"Customer#000000356",FURNITURE,2934.06 +357,"Customer#000000357",AUTOMOBILE,8747.36 +358,"Customer#000000358",MACHINERY,-44.66 +359,"Customer#000000359",FURNITURE,6375.23 +360,"Customer#000000360",FURNITURE,6542.83 +361,"Customer#000000361",BUILDING,7451.84 +362,"Customer#000000362",AUTOMOBILE,6149.01 +363,"Customer#000000363",HOUSEHOLD,-573.86 +364,"Customer#000000364",HOUSEHOLD,32.24 +365,"Customer#000000365",HOUSEHOLD,737.03 +366,"Customer#000000366",MACHINERY,-729.74 +367,"Customer#000000367",HOUSEHOLD,9108.65 +368,"Customer#000000368",MACHINERY,84.72 +369,"Customer#000000369",FURNITURE,2881.06 +370,"Customer#000000370",FURNITURE,8982.79 +371,"Customer#000000371",AUTOMOBILE,7789.14 +372,"Customer#000000372",MACHINERY,-921.91 +373,"Customer#000000373",MACHINERY,2354.06 +374,"Customer#000000374",AUTOMOBILE,6718.78 +375,"Customer#000000375",HOUSEHOLD,5562.22 +376,"Customer#000000376",AUTOMOBILE,4231.45 +377,"Customer#000000377",MACHINERY,1043.72 +378,"Customer#000000378",BUILDING,5718.05 +379,"Customer#000000379",AUTOMOBILE,5348.11 +380,"Customer#000000380",BUILDING,2755.46 +381,"Customer#000000381",BUILDING,9931.71 +382,"Customer#000000382",AUTOMOBILE,6269.42 +383,"Customer#000000383",MACHINERY,-849.44 +384,"Customer#000000384",HOUSEHOLD,-614.3 +385,"Customer#000000385",AUTOMOBILE,2457.09 +386,"Customer#000000386",BUILDING,232.01 +387,"Customer#000000387",HOUSEHOLD,3404.23 +388,"Customer#000000388",HOUSEHOLD,1938.05 +389,"Customer#000000389",AUTOMOBILE,-307.61 +390,"Customer#000000390",HOUSEHOLD,8862.18 +391,"Customer#000000391",HOUSEHOLD,4801.3 +392,"Customer#000000392",BUILDING,8492.33 +393,"Customer#000000393",FURNITURE,3593.57 +394,"Customer#000000394",MACHINERY,5200.96 +395,"Customer#000000395",HOUSEHOLD,4582.28 +396,"Customer#000000396",BUILDING,1433.5 +397,"Customer#000000397",FURNITURE,709.46 +398,"Customer#000000398",HOUSEHOLD,8865.61 +399,"Customer#000000399",BUILDING,7358.53 +400,"Customer#000000400",BUILDING,-98.46 +401,"Customer#000000401",BUILDING,4146.43 +402,"Customer#000000402",AUTOMOBILE,2106.67 +403,"Customer#000000403",HOUSEHOLD,6693.36 +404,"Customer#000000404",BUILDING,7408.73 +405,"Customer#000000405",MACHINERY,7519.14 +406,"Customer#000000406",FURNITURE,4286.94 +407,"Customer#000000407",MACHINERY,9537.08 +408,"Customer#000000408",BUILDING,6825.37 +409,"Customer#000000409",FURNITURE,3969.86 +410,"Customer#000000410",BUILDING,4349.27 +411,"Customer#000000411",HOUSEHOLD,1209.32 +412,"Customer#000000412",BUILDING,6044.02 +413,"Customer#000000413",FURNITURE,5817.9 +414,"Customer#000000414",AUTOMOBILE,527.78 +415,"Customer#000000415",FURNITURE,2317.93 +416,"Customer#000000416",MACHINERY,4365.28 +417,"Customer#000000417",BUILDING,6187.73 +418,"Customer#000000418",FURNITURE,1211.39 +419,"Customer#000000419",BUILDING,7786.69 +420,"Customer#000000420",BUILDING,1999.35 +421,"Customer#000000421",FURNITURE,7073.17 +422,"Customer#000000422",HOUSEHOLD,-272.14 +423,"Customer#000000423",BUILDING,95.79 +424,"Customer#000000424",HOUSEHOLD,1866.42 +425,"Customer#000000425",HOUSEHOLD,5824.88 +426,"Customer#000000426",HOUSEHOLD,7818.25 +427,"Customer#000000427",MACHINERY,4376.8 +428,"Customer#000000428",BUILDING,1952.36 +429,"Customer#000000429",FURNITURE,9247.21 +430,"Customer#000000430",BUILDING,7905.17 +431,"Customer#000000431",HOUSEHOLD,2273.5 +432,"Customer#000000432",BUILDING,5715.64 +433,"Customer#000000433",FURNITURE,8746.23 +434,"Customer#000000434",MACHINERY,2940.46 +435,"Customer#000000435",MACHINERY,6217.46 +436,"Customer#000000436",FURNITURE,5896.87 +437,"Customer#000000437",AUTOMOBILE,7760.52 +438,"Customer#000000438",MACHINERY,2131.13 +439,"Customer#000000439",BUILDING,-61.29 +440,"Customer#000000440",MACHINERY,1809.04 +441,"Customer#000000441",HOUSEHOLD,9451.84 +442,"Customer#000000442",FURNITURE,4157.0 +443,"Customer#000000443",FURNITURE,3726.22 +444,"Customer#000000444",HOUSEHOLD,1505.27 +445,"Customer#000000445",FURNITURE,8018.81 +446,"Customer#000000446",FURNITURE,9225.6 +447,"Customer#000000447",HOUSEHOLD,7665.98 +448,"Customer#000000448",BUILDING,8117.27 +449,"Customer#000000449",MACHINERY,3001.94 +450,"Customer#000000450",HOUSEHOLD,5544.42 +451,"Customer#000000451",HOUSEHOLD,2110.59 +452,"Customer#000000452",BUILDING,6633.7 +453,"Customer#000000453",HOUSEHOLD,5678.18 +454,"Customer#000000454",AUTOMOBILE,6134.4 +455,"Customer#000000455",BUILDING,6860.34 +456,"Customer#000000456",FURNITURE,8815.78 +457,"Customer#000000457",FURNITURE,5867.61 +458,"Customer#000000458",BUILDING,-38.42 +459,"Customer#000000459",MACHINERY,1207.97 +460,"Customer#000000460",FURNITURE,5222.83 +461,"Customer#000000461",AUTOMOBILE,9177.63 +462,"Customer#000000462",HOUSEHOLD,4522.6 +463,"Customer#000000463",HOUSEHOLD,-654.5 +464,"Customer#000000464",AUTOMOBILE,8730.85 +465,"Customer#000000465",FURNITURE,8432.74 +466,"Customer#000000466",MACHINERY,3168.41 +467,"Customer#000000467",MACHINERY,9398.51 +468,"Customer#000000468",FURNITURE,9834.19 +469,"Customer#000000469",BUILDING,6343.64 +470,"Customer#000000470",HOUSEHOLD,3597.53 +471,"Customer#000000471",FURNITURE,5716.9 +472,"Customer#000000472",MACHINERY,7929.9 +473,"Customer#000000473",HOUSEHOLD,-202.22 +474,"Customer#000000474",MACHINERY,9165.47 +475,"Customer#000000475",BUILDING,9043.55 +476,"Customer#000000476",BUILDING,5973.1 +477,"Customer#000000477",AUTOMOBILE,1836.61 +478,"Customer#000000478",BUILDING,-210.4 +479,"Customer#000000479",AUTOMOBILE,3653.64 +480,"Customer#000000480",FURNITURE,2750.71 +481,"Customer#000000481",FURNITURE,7157.21 +482,"Customer#000000482",HOUSEHOLD,4333.37 +483,"Customer#000000483",MACHINERY,8877.2 +484,"Customer#000000484",BUILDING,4245.0 +485,"Customer#000000485",MACHINERY,8695.45 +486,"Customer#000000486",AUTOMOBILE,7487.4 +487,"Customer#000000487",AUTOMOBILE,9749.37 +488,"Customer#000000488",AUTOMOBILE,-275.58 +489,"Customer#000000489",AUTOMOBILE,8255.83 +490,"Customer#000000490",FURNITURE,-213.85 +491,"Customer#000000491",AUTOMOBILE,785.37 +492,"Customer#000000492",AUTOMOBILE,8635.18 +493,"Customer#000000493",MACHINERY,6582.04 +494,"Customer#000000494",FURNITURE,6295.47 +495,"Customer#000000495",BUILDING,7997.81 +496,"Customer#000000496",MACHINERY,8174.82 +497,"Customer#000000497",BUILDING,2191.59 +498,"Customer#000000498",BUILDING,3945.64 +499,"Customer#000000499",HOUSEHOLD,4293.76 +500,"Customer#000000500",AUTOMOBILE,3300.82 +501,"Customer#000000501",FURNITURE,1909.35 +502,"Customer#000000502",HOUSEHOLD,1378.67 +503,"Customer#000000503",MACHINERY,3213.66 +504,"Customer#000000504",FURNITURE,0.51 +505,"Customer#000000505",HOUSEHOLD,6557.51 +506,"Customer#000000506",HOUSEHOLD,1179.85 +507,"Customer#000000507",MACHINERY,5727.0 +508,"Customer#000000508",BUILDING,1685.9 +509,"Customer#000000509",FURNITURE,7885.5 +510,"Customer#000000510",HOUSEHOLD,1572.48 +511,"Customer#000000511",FURNITURE,4571.31 +512,"Customer#000000512",BUILDING,3937.58 +513,"Customer#000000513",HOUSEHOLD,955.37 +514,"Customer#000000514",BUILDING,5840.97 +515,"Customer#000000515",BUILDING,3225.07 +516,"Customer#000000516",MACHINERY,4768.96 +517,"Customer#000000517",FURNITURE,3959.71 +518,"Customer#000000518",BUILDING,9871.66 +519,"Customer#000000519",BUILDING,9074.45 +520,"Customer#000000520",HOUSEHOLD,8315.09 +521,"Customer#000000521",MACHINERY,5830.69 +522,"Customer#000000522",BUILDING,6358.46 +523,"Customer#000000523",BUILDING,-275.73 +524,"Customer#000000524",HOUSEHOLD,5706.19 +525,"Customer#000000525",AUTOMOBILE,3931.68 +526,"Customer#000000526",HOUSEHOLD,705.93 +527,"Customer#000000527",HOUSEHOLD,4429.81 +528,"Customer#000000528",AUTOMOBILE,1802.5 +529,"Customer#000000529",FURNITURE,9647.58 +530,"Customer#000000530",BUILDING,4990.92 +531,"Customer#000000531",HOUSEHOLD,5342.82 +532,"Customer#000000532",MACHINERY,1725.68 +533,"Customer#000000533",HOUSEHOLD,5432.77 +534,"Customer#000000534",AUTOMOBILE,6520.97 +535,"Customer#000000535",BUILDING,2912.8 +536,"Customer#000000536",MACHINERY,3342.75 +537,"Customer#000000537",FURNITURE,2614.79 +538,"Customer#000000538",MACHINERY,-303.95 +539,"Customer#000000539",HOUSEHOLD,4390.33 +540,"Customer#000000540",MACHINERY,9195.77 +541,"Customer#000000541",FURNITURE,1295.54 +542,"Customer#000000542",BUILDING,3109.96 +543,"Customer#000000543",AUTOMOBILE,6089.13 +544,"Customer#000000544",AUTOMOBILE,4974.68 +545,"Customer#000000545",AUTOMOBILE,7505.33 +546,"Customer#000000546",MACHINERY,3116.5 +547,"Customer#000000547",BUILDING,6058.08 +548,"Customer#000000548",BUILDING,90.45 +549,"Customer#000000549",BUILDING,91.53 +550,"Customer#000000550",FURNITURE,7270.82 +551,"Customer#000000551",MACHINERY,-334.89 +552,"Customer#000000552",FURNITURE,1353.24 +553,"Customer#000000553",BUILDING,4804.57 +554,"Customer#000000554",HOUSEHOLD,8395.57 +555,"Customer#000000555",BUILDING,5486.52 +556,"Customer#000000556",MACHINERY,7944.22 +557,"Customer#000000557",BUILDING,9559.04 +558,"Customer#000000558",HOUSEHOLD,1912.23 +559,"Customer#000000559",AUTOMOBILE,5872.94 +560,"Customer#000000560",BUILDING,1469.59 +561,"Customer#000000561",FURNITURE,2323.45 +562,"Customer#000000562",BUILDING,9234.5 +563,"Customer#000000563",FURNITURE,3231.71 +564,"Customer#000000564",MACHINERY,6307.59 +565,"Customer#000000565",FURNITURE,2688.88 +566,"Customer#000000566",FURNITURE,1928.1 +567,"Customer#000000567",BUILDING,8475.17 +568,"Customer#000000568",BUILDING,1317.56 +569,"Customer#000000569",MACHINERY,-795.23 +570,"Customer#000000570",AUTOMOBILE,8480.87 +571,"Customer#000000571",HOUSEHOLD,8993.23 +572,"Customer#000000572",AUTOMOBILE,7252.65 +573,"Customer#000000573",HOUSEHOLD,2333.96 +574,"Customer#000000574",FURNITURE,9787.56 +575,"Customer#000000575",BUILDING,3652.29 +576,"Customer#000000576",HOUSEHOLD,2091.63 +577,"Customer#000000577",FURNITURE,7059.15 +578,"Customer#000000578",FURNITURE,6181.23 +579,"Customer#000000579",MACHINERY,1924.96 +580,"Customer#000000580",BUILDING,-181.63 +581,"Customer#000000581",MACHINERY,3242.1 +582,"Customer#000000582",MACHINERY,4879.55 +583,"Customer#000000583",HOUSEHOLD,3686.07 +584,"Customer#000000584",MACHINERY,8825.71 +585,"Customer#000000585",MACHINERY,7820.26 +586,"Customer#000000586",AUTOMOBILE,5134.35 +587,"Customer#000000587",AUTOMOBILE,7077.79 +588,"Customer#000000588",FURNITURE,483.89 +589,"Customer#000000589",FURNITURE,1647.05 +590,"Customer#000000590",MACHINERY,3993.54 +591,"Customer#000000591",MACHINERY,6344.66 +592,"Customer#000000592",BUILDING,9712.75 +593,"Customer#000000593",AUTOMOBILE,233.51 +594,"Customer#000000594",HOUSEHOLD,6518.42 +595,"Customer#000000595",HOUSEHOLD,4177.17 +596,"Customer#000000596",MACHINERY,1722.88 +597,"Customer#000000597",AUTOMOBILE,2443.52 +598,"Customer#000000598",FURNITURE,3244.78 +599,"Customer#000000599",HOUSEHOLD,6004.52 +600,"Customer#000000600",HOUSEHOLD,2003.44 +601,"Customer#000000601",BUILDING,9768.21 +602,"Customer#000000602",BUILDING,8404.9 +603,"Customer#000000603",AUTOMOBILE,8161.13 +604,"Customer#000000604",HOUSEHOLD,3195.96 +605,"Customer#000000605",HOUSEHOLD,-549.73 +606,"Customer#000000606",AUTOMOBILE,9676.98 +607,"Customer#000000607",FURNITURE,4038.45 +608,"Customer#000000608",BUILDING,2256.36 +609,"Customer#000000609",FURNITURE,3651.06 +610,"Customer#000000610",BUILDING,3374.92 +611,"Customer#000000611",HOUSEHOLD,4272.43 +612,"Customer#000000612",HOUSEHOLD,7669.16 +613,"Customer#000000613",AUTOMOBILE,6679.75 +614,"Customer#000000614",FURNITURE,9630.24 +615,"Customer#000000615",BUILDING,-482.48 +616,"Customer#000000616",FURNITURE,6898.65 +617,"Customer#000000617",HOUSEHOLD,3625.93 +618,"Customer#000000618",HOUSEHOLD,-932.38 +619,"Customer#000000619",FURNITURE,2336.99 +620,"Customer#000000620",AUTOMOBILE,5795.15 +621,"Customer#000000621",HOUSEHOLD,3164.28 +622,"Customer#000000622",FURNITURE,5974.11 +623,"Customer#000000623",BUILDING,7887.6 +624,"Customer#000000624",MACHINERY,3907.11 +625,"Customer#000000625",FURNITURE,5744.89 +626,"Customer#000000626",FURNITURE,5447.12 +627,"Customer#000000627",FURNITURE,5826.68 +628,"Customer#000000628",FURNITURE,4954.25 +629,"Customer#000000629",AUTOMOBILE,5100.77 +630,"Customer#000000630",HOUSEHOLD,3649.05 +631,"Customer#000000631",MACHINERY,2603.0 +632,"Customer#000000632",MACHINERY,-487.92 +633,"Customer#000000633",HOUSEHOLD,3385.52 +634,"Customer#000000634",FURNITURE,6397.58 +635,"Customer#000000635",AUTOMOBILE,8216.79 +636,"Customer#000000636",FURNITURE,3660.47 +637,"Customer#000000637",HOUSEHOLD,7511.17 +638,"Customer#000000638",HOUSEHOLD,2258.4 +639,"Customer#000000639",MACHINERY,4899.15 +640,"Customer#000000640",HOUSEHOLD,3025.84 +641,"Customer#000000641",AUTOMOBILE,339.49 +642,"Customer#000000642",AUTOMOBILE,5684.01 +643,"Customer#000000643",FURNITURE,5184.7 +644,"Customer#000000644",HOUSEHOLD,6183.15 +645,"Customer#000000645",BUILDING,1146.49 +646,"Customer#000000646",HOUSEHOLD,6074.42 +647,"Customer#000000647",BUILDING,-132.97 +648,"Customer#000000648",FURNITURE,7029.24 +649,"Customer#000000649",BUILDING,9442.55 +650,"Customer#000000650",AUTOMOBILE,2086.91 +651,"Customer#000000651",HOUSEHOLD,4716.31 +652,"Customer#000000652",AUTOMOBILE,-919.65 +653,"Customer#000000653",BUILDING,7307.55 +654,"Customer#000000654",AUTOMOBILE,1067.04 +655,"Customer#000000655",FURNITURE,1724.68 +656,"Customer#000000656",AUTOMOBILE,-390.09 +657,"Customer#000000657",FURNITURE,8729.97 +658,"Customer#000000658",HOUSEHOLD,4716.68 +659,"Customer#000000659",HOUSEHOLD,5297.68 +660,"Customer#000000660",MACHINERY,2110.3 +661,"Customer#000000661",HOUSEHOLD,1735.44 +662,"Customer#000000662",BUILDING,4517.33 +663,"Customer#000000663",AUTOMOBILE,9698.21 +664,"Customer#000000664",BUILDING,8878.22 +665,"Customer#000000665",BUILDING,-616.79 +666,"Customer#000000666",AUTOMOBILE,4538.54 +667,"Customer#000000667",AUTOMOBILE,3288.76 +668,"Customer#000000668",FURNITURE,8184.21 +669,"Customer#000000669",MACHINERY,6019.2 +670,"Customer#000000670",BUILDING,6738.93 +671,"Customer#000000671",FURNITURE,3227.87 +672,"Customer#000000672",BUILDING,7778.95 +673,"Customer#000000673",MACHINERY,3942.58 +674,"Customer#000000674",BUILDING,7889.64 +675,"Customer#000000675",HOUSEHOLD,5295.26 +676,"Customer#000000676",HOUSEHOLD,5527.61 +677,"Customer#000000677",AUTOMOBILE,5582.63 +678,"Customer#000000678",AUTOMOBILE,6302.93 +679,"Customer#000000679",AUTOMOBILE,1394.44 +680,"Customer#000000680",AUTOMOBILE,4366.23 +681,"Customer#000000681",MACHINERY,235.53 +682,"Customer#000000682",AUTOMOBILE,7420.6 +683,"Customer#000000683",MACHINERY,9120.93 +684,"Customer#000000684",HOUSEHOLD,8545.9 +685,"Customer#000000685",MACHINERY,2039.84 +686,"Customer#000000686",HOUSEHOLD,5503.36 +687,"Customer#000000687",FURNITURE,4999.31 +688,"Customer#000000688",HOUSEHOLD,3354.47 +689,"Customer#000000689",MACHINERY,1481.24 +690,"Customer#000000690",HOUSEHOLD,103.45 +691,"Customer#000000691",MACHINERY,9566.15 +692,"Customer#000000692",AUTOMOBILE,276.71 +693,"Customer#000000693",AUTOMOBILE,6577.21 +694,"Customer#000000694",BUILDING,2222.65 +695,"Customer#000000695",FURNITURE,9126.54 +696,"Customer#000000696",HOUSEHOLD,3778.97 +697,"Customer#000000697",AUTOMOBILE,8121.84 +698,"Customer#000000698",BUILDING,2894.22 +699,"Customer#000000699",HOUSEHOLD,-809.22 +700,"Customer#000000700",MACHINERY,4367.53 +701,"Customer#000000701",FURNITURE,148.98 +702,"Customer#000000702",FURNITURE,4782.05 +703,"Customer#000000703",AUTOMOBILE,3998.42 +704,"Customer#000000704",AUTOMOBILE,441.44 +705,"Customer#000000705",BUILDING,3199.0 +706,"Customer#000000706",AUTOMOBILE,6496.08 +707,"Customer#000000707",FURNITURE,2619.52 +708,"Customer#000000708",BUILDING,2297.33 +709,"Customer#000000709",BUILDING,-867.94 +710,"Customer#000000710",FURNITURE,7412.12 +711,"Customer#000000711",MACHINERY,9591.51 +712,"Customer#000000712",BUILDING,8667.09 +713,"Customer#000000713",MACHINERY,3829.03 +714,"Customer#000000714",BUILDING,2438.1 +715,"Customer#000000715",AUTOMOBILE,85.05 +716,"Customer#000000716",AUTOMOBILE,8364.99 +717,"Customer#000000717",MACHINERY,8510.94 +718,"Customer#000000718",HOUSEHOLD,8438.4 +719,"Customer#000000719",HOUSEHOLD,-774.47 +720,"Customer#000000720",HOUSEHOLD,5357.32 +721,"Customer#000000721",AUTOMOBILE,3420.64 +722,"Customer#000000722",BUILDING,2724.8 +723,"Customer#000000723",BUILDING,738.57 +724,"Customer#000000724",AUTOMOBILE,3035.29 +725,"Customer#000000725",FURNITURE,5030.24 +726,"Customer#000000726",BUILDING,6253.81 +727,"Customer#000000727",FURNITURE,807.37 +728,"Customer#000000728",BUILDING,4349.73 +729,"Customer#000000729",MACHINERY,8947.26 +730,"Customer#000000730",AUTOMOBILE,4718.23 +731,"Customer#000000731",MACHINERY,9311.17 +732,"Customer#000000732",BUILDING,4830.51 +733,"Customer#000000733",HOUSEHOLD,4351.09 +734,"Customer#000000734",AUTOMOBILE,9750.71 +735,"Customer#000000735",BUILDING,293.88 +736,"Customer#000000736",MACHINERY,9114.68 +737,"Customer#000000737",AUTOMOBILE,2501.74 +738,"Customer#000000738",MACHINERY,2734.95 +739,"Customer#000000739",HOUSEHOLD,6344.18 +740,"Customer#000000740",BUILDING,1733.76 +741,"Customer#000000741",HOUSEHOLD,9195.41 +742,"Customer#000000742",HOUSEHOLD,6381.24 +743,"Customer#000000743",HOUSEHOLD,8450.37 +744,"Customer#000000744",HOUSEHOLD,2458.81 +745,"Customer#000000745",FURNITURE,7115.14 +746,"Customer#000000746",MACHINERY,1164.46 +747,"Customer#000000747",FURNITURE,67.95 +748,"Customer#000000748",AUTOMOBILE,6959.09 +749,"Customer#000000749",MACHINERY,7491.42 +750,"Customer#000000750",BUILDING,269.9 +751,"Customer#000000751",FURNITURE,2130.98 +752,"Customer#000000752",MACHINERY,8363.66 +753,"Customer#000000753",HOUSEHOLD,8114.44 +754,"Customer#000000754",BUILDING,-566.86 +755,"Customer#000000755",HOUSEHOLD,7631.94 +756,"Customer#000000756",AUTOMOBILE,8116.99 +757,"Customer#000000757",AUTOMOBILE,9334.82 +758,"Customer#000000758",HOUSEHOLD,6352.14 +759,"Customer#000000759",FURNITURE,3477.59 +760,"Customer#000000760",BUILDING,2883.24 +761,"Customer#000000761",BUILDING,1525.96 +762,"Customer#000000762",FURNITURE,3764.9 +763,"Customer#000000763",BUILDING,2650.27 +764,"Customer#000000764",MACHINERY,2061.45 +765,"Customer#000000765",AUTOMOBILE,5683.33 +766,"Customer#000000766",BUILDING,4677.55 +767,"Customer#000000767",BUILDING,7647.09 +768,"Customer#000000768",BUILDING,9618.84 +769,"Customer#000000769",HOUSEHOLD,5950.86 +770,"Customer#000000770",AUTOMOBILE,2066.79 +771,"Customer#000000771",AUTOMOBILE,7461.74 +772,"Customer#000000772",MACHINERY,7555.16 +773,"Customer#000000773",FURNITURE,4578.24 +774,"Customer#000000774",HOUSEHOLD,-506.37 +775,"Customer#000000775",BUILDING,1376.67 +776,"Customer#000000776",AUTOMOBILE,2669.01 +777,"Customer#000000777",AUTOMOBILE,9097.52 +778,"Customer#000000778",MACHINERY,52.43 +779,"Customer#000000779",HOUSEHOLD,-902.48 +780,"Customer#000000780",FURNITURE,9874.12 +781,"Customer#000000781",MACHINERY,6403.62 +782,"Customer#000000782",AUTOMOBILE,-326.32 +783,"Customer#000000783",FURNITURE,2436.32 +784,"Customer#000000784",BUILDING,3170.47 +785,"Customer#000000785",BUILDING,108.14 +786,"Customer#000000786",MACHINERY,400.03 +787,"Customer#000000787",AUTOMOBILE,212.16 +788,"Customer#000000788",FURNITURE,-330.69 +789,"Customer#000000789",BUILDING,6038.6 +790,"Customer#000000790",BUILDING,2724.98 +791,"Customer#000000791",HOUSEHOLD,3694.81 +792,"Customer#000000792",AUTOMOBILE,1672.46 +793,"Customer#000000793",BUILDING,2072.99 +794,"Customer#000000794",FURNITURE,1709.5 +795,"Customer#000000795",HOUSEHOLD,8443.18 +796,"Customer#000000796",HOUSEHOLD,2584.23 +797,"Customer#000000797",HOUSEHOLD,3063.11 +798,"Customer#000000798",HOUSEHOLD,-391.13 +799,"Customer#000000799",MACHINERY,2263.25 +800,"Customer#000000800",AUTOMOBILE,9443.39 +801,"Customer#000000801",FURNITURE,5207.32 +802,"Customer#000000802",BUILDING,1377.52 +803,"Customer#000000803",BUILDING,6003.09 +804,"Customer#000000804",AUTOMOBILE,3.43 +805,"Customer#000000805",BUILDING,511.69 +806,"Customer#000000806",FURNITURE,6859.28 +807,"Customer#000000807",MACHINERY,1929.89 +808,"Customer#000000808",BUILDING,5561.93 +809,"Customer#000000809",HOUSEHOLD,-594.23 +810,"Customer#000000810",FURNITURE,9632.77 +811,"Customer#000000811",BUILDING,9010.02 +812,"Customer#000000812",BUILDING,3428.67 +813,"Customer#000000813",HOUSEHOLD,5673.47 +814,"Customer#000000814",BUILDING,4011.18 +815,"Customer#000000815",HOUSEHOLD,5841.33 +816,"Customer#000000816",BUILDING,9239.32 +817,"Customer#000000817",MACHINERY,7297.64 +818,"Customer#000000818",BUILDING,-574.39 +819,"Customer#000000819",MACHINERY,2152.05 +820,"Customer#000000820",AUTOMOBILE,-614.28 +821,"Customer#000000821",FURNITURE,2691.42 +822,"Customer#000000822",BUILDING,736.47 +823,"Customer#000000823",AUTOMOBILE,738.79 +824,"Customer#000000824",AUTOMOBILE,6840.29 +825,"Customer#000000825",HOUSEHOLD,1809.01 +826,"Customer#000000826",BUILDING,8938.14 +827,"Customer#000000827",HOUSEHOLD,5411.2 +828,"Customer#000000828",BUILDING,7059.68 +829,"Customer#000000829",MACHINERY,4086.56 +830,"Customer#000000830",AUTOMOBILE,7775.65 +831,"Customer#000000831",AUTOMOBILE,3401.49 +832,"Customer#000000832",HOUSEHOLD,-201.49 +833,"Customer#000000833",FURNITURE,-526.14 +834,"Customer#000000834",MACHINERY,-976.25 +835,"Customer#000000835",BUILDING,2106.81 +836,"Customer#000000836",FURNITURE,9184.72 +837,"Customer#000000837",BUILDING,2701.29 +838,"Customer#000000838",AUTOMOBILE,1605.76 +839,"Customer#000000839",HOUSEHOLD,2924.78 +840,"Customer#000000840",AUTOMOBILE,6799.0 +841,"Customer#000000841",FURNITURE,884.2 +842,"Customer#000000842",FURNITURE,8635.49 +843,"Customer#000000843",HOUSEHOLD,675.73 +844,"Customer#000000844",BUILDING,2954.9 +845,"Customer#000000845",AUTOMOBILE,6898.89 +846,"Customer#000000846",MACHINERY,7650.73 +847,"Customer#000000847",HOUSEHOLD,4822.08 +848,"Customer#000000848",BUILDING,5685.59 +849,"Customer#000000849",MACHINERY,9670.64 +850,"Customer#000000850",FURNITURE,7576.55 +851,"Customer#000000851",FURNITURE,1144.23 +852,"Customer#000000852",AUTOMOBILE,1098.98 +853,"Customer#000000853",MACHINERY,-444.73 +854,"Customer#000000854",AUTOMOBILE,3393.86 +855,"Customer#000000855",BUILDING,3550.49 +856,"Customer#000000856",FURNITURE,6988.55 +857,"Customer#000000857",AUTOMOBILE,7537.16 +858,"Customer#000000858",BUILDING,-829.37 +859,"Customer#000000859",BUILDING,6737.18 +860,"Customer#000000860",BUILDING,6836.89 +861,"Customer#000000861",BUILDING,-336.8 +862,"Customer#000000862",MACHINERY,2338.92 +863,"Customer#000000863",AUTOMOBILE,5274.52 +864,"Customer#000000864",AUTOMOBILE,8932.76 +865,"Customer#000000865",BUILDING,3016.38 +866,"Customer#000000866",FURNITURE,6265.88 +867,"Customer#000000867",FURNITURE,5680.23 +868,"Customer#000000868",FURNITURE,7616.48 +869,"Customer#000000869",HOUSEHOLD,1228.06 +870,"Customer#000000870",BUILDING,1970.76 +871,"Customer#000000871",HOUSEHOLD,-395.89 +872,"Customer#000000872",BUILDING,-858.61 +873,"Customer#000000873",AUTOMOBILE,-797.38 +874,"Customer#000000874",AUTOMOBILE,5391.74 +875,"Customer#000000875",FURNITURE,-949.28 +876,"Customer#000000876",MACHINERY,4367.63 +877,"Customer#000000877",MACHINERY,1755.33 +878,"Customer#000000878",FURNITURE,8781.53 +879,"Customer#000000879",HOUSEHOLD,2235.2 +880,"Customer#000000880",FURNITURE,-77.63 +881,"Customer#000000881",FURNITURE,2141.71 +882,"Customer#000000882",HOUSEHOLD,1650.12 +883,"Customer#000000883",FURNITURE,479.96 +884,"Customer#000000884",FURNITURE,1601.6 +885,"Customer#000000885",HOUSEHOLD,-959.94 +886,"Customer#000000886",AUTOMOBILE,1194.33 +887,"Customer#000000887",MACHINERY,7009.42 +888,"Customer#000000888",HOUSEHOLD,6358.04 +889,"Customer#000000889",FURNITURE,3635.35 +890,"Customer#000000890",BUILDING,3329.21 +891,"Customer#000000891",FURNITURE,6032.18 +892,"Customer#000000892",FURNITURE,4799.98 +893,"Customer#000000893",BUILDING,8250.87 +894,"Customer#000000894",AUTOMOBILE,4483.42 +895,"Customer#000000895",AUTOMOBILE,904.43 +896,"Customer#000000896",AUTOMOBILE,7659.72 +897,"Customer#000000897",MACHINERY,1999.42 +898,"Customer#000000898",AUTOMOBILE,8137.24 +899,"Customer#000000899",FURNITURE,8605.53 +900,"Customer#000000900",BUILDING,3195.39 +901,"Customer#000000901",HOUSEHOLD,938.35 +902,"Customer#000000902",AUTOMOBILE,5858.48 +903,"Customer#000000903",FURNITURE,509.23 +904,"Customer#000000904",BUILDING,9562.82 +905,"Customer#000000905",BUILDING,-600.73 +906,"Customer#000000906",HOUSEHOLD,-613.45 +907,"Customer#000000907",HOUSEHOLD,5751.31 +908,"Customer#000000908",AUTOMOBILE,3215.96 +909,"Customer#000000909",MACHINERY,5565.58 +910,"Customer#000000910",BUILDING,5794.69 +911,"Customer#000000911",BUILDING,6364.6 +912,"Customer#000000912",FURNITURE,3861.36 +913,"Customer#000000913",FURNITURE,6935.16 +914,"Customer#000000914",MACHINERY,1230.79 +915,"Customer#000000915",AUTOMOBILE,3776.53 +916,"Customer#000000916",MACHINERY,130.59 +917,"Customer#000000917",HOUSEHOLD,3679.65 +918,"Customer#000000918",MACHINERY,-155.06 +919,"Customer#000000919",MACHINERY,9774.97 +920,"Customer#000000920",HOUSEHOLD,990.58 +921,"Customer#000000921",HOUSEHOLD,3651.09 +922,"Customer#000000922",BUILDING,3869.25 +923,"Customer#000000923",HOUSEHOLD,7462.2 +924,"Customer#000000924",BUILDING,4212.53 +925,"Customer#000000925",AUTOMOBILE,1939.39 +926,"Customer#000000926",AUTOMOBILE,274.5 +927,"Customer#000000927",MACHINERY,2417.65 +928,"Customer#000000928",HOUSEHOLD,8330.7 +929,"Customer#000000929",MACHINERY,4079.18 +930,"Customer#000000930",FURNITURE,4787.2 +931,"Customer#000000931",BUILDING,2409.69 +932,"Customer#000000932",BUILDING,6553.37 +933,"Customer#000000933",MACHINERY,8541.16 +934,"Customer#000000934",AUTOMOBILE,-592.69 +935,"Customer#000000935",MACHINERY,2531.25 +936,"Customer#000000936",MACHINERY,3650.9 +937,"Customer#000000937",AUTOMOBILE,668.51 +938,"Customer#000000938",BUILDING,2584.52 +939,"Customer#000000939",FURNITURE,8059.51 +940,"Customer#000000940",AUTOMOBILE,253.59 +941,"Customer#000000941",FURNITURE,2990.18 +942,"Customer#000000942",HOUSEHOLD,5898.17 +943,"Customer#000000943",FURNITURE,7541.05 +944,"Customer#000000944",BUILDING,9454.78 +945,"Customer#000000945",BUILDING,9615.39 +946,"Customer#000000946",FURNITURE,2990.73 +947,"Customer#000000947",BUILDING,4092.24 +948,"Customer#000000948",AUTOMOBILE,4346.9 +949,"Customer#000000949",AUTOMOBILE,5340.67 +950,"Customer#000000950",FURNITURE,9609.77 +951,"Customer#000000951",FURNITURE,7499.47 +952,"Customer#000000952",AUTOMOBILE,3710.14 +953,"Customer#000000953",BUILDING,6292.06 +954,"Customer#000000954",FURNITURE,5740.0 +955,"Customer#000000955",AUTOMOBILE,138.31 +956,"Customer#000000956",HOUSEHOLD,1587.14 +957,"Customer#000000957",FURNITURE,9076.68 +958,"Customer#000000958",FURNITURE,1791.65 +959,"Customer#000000959",FURNITURE,3266.14 +960,"Customer#000000960",HOUSEHOLD,1932.59 +961,"Customer#000000961",MACHINERY,6963.68 +962,"Customer#000000962",BUILDING,7557.0 +963,"Customer#000000963",HOUSEHOLD,2557.49 +964,"Customer#000000964",FURNITURE,4756.58 +965,"Customer#000000965",MACHINERY,4768.8 +966,"Customer#000000966",MACHINERY,1283.26 +967,"Customer#000000967",BUILDING,5710.41 +968,"Customer#000000968",BUILDING,8921.97 +969,"Customer#000000969",BUILDING,8601.63 +970,"Customer#000000970",MACHINERY,3623.6 +971,"Customer#000000971",AUTOMOBILE,3914.88 +972,"Customer#000000972",MACHINERY,4453.46 +973,"Customer#000000973",FURNITURE,3229.18 +974,"Customer#000000974",HOUSEHOLD,7826.1 +975,"Customer#000000975",FURNITURE,4364.06 +976,"Customer#000000976",BUILDING,7772.85 +977,"Customer#000000977",FURNITURE,311.0 +978,"Customer#000000978",BUILDING,-50.51 +979,"Customer#000000979",MACHINERY,7055.13 +980,"Customer#000000980",AUTOMOBILE,4586.33 +981,"Customer#000000981",MACHINERY,3383.26 +982,"Customer#000000982",FURNITURE,1437.55 +983,"Customer#000000983",BUILDING,2902.95 +984,"Customer#000000984",AUTOMOBILE,2811.97 +985,"Customer#000000985",HOUSEHOLD,2701.21 +986,"Customer#000000986",BUILDING,178.73 +987,"Customer#000000987",AUTOMOBILE,9850.64 +988,"Customer#000000988",FURNITURE,7746.97 +989,"Customer#000000989",AUTOMOBILE,5453.74 +990,"Customer#000000990",BUILDING,6988.49 +991,"Customer#000000991",AUTOMOBILE,6533.53 +992,"Customer#000000992",MACHINERY,5027.75 +993,"Customer#000000993",FURNITURE,8421.87 +994,"Customer#000000994",HOUSEHOLD,7461.27 +995,"Customer#000000995",BUILDING,-341.79 +996,"Customer#000000996",AUTOMOBILE,6450.78 +997,"Customer#000000997",FURNITURE,367.03 +998,"Customer#000000998",MACHINERY,6679.2 +999,"Customer#000000999",BUILDING,403.89 +1000,"Customer#000001000",BUILDING,-881.7 +1001,"Customer#000001001",MACHINERY,7140.81 +1002,"Customer#000001002",MACHINERY,3699.76 +1003,"Customer#000001003",AUTOMOBILE,7894.0 +1004,"Customer#000001004",MACHINERY,1512.46 +1005,"Customer#000001005",AUTOMOBILE,7790.94 +1006,"Customer#000001006",BUILDING,7447.99 +1007,"Customer#000001007",MACHINERY,7347.9 +1008,"Customer#000001008",HOUSEHOLD,8191.74 +1009,"Customer#000001009",BUILDING,594.5 +1010,"Customer#000001010",AUTOMOBILE,1652.78 +1011,"Customer#000001011",BUILDING,1188.94 +1012,"Customer#000001012",AUTOMOBILE,4422.45 +1013,"Customer#000001013",BUILDING,-951.53 +1014,"Customer#000001014",AUTOMOBILE,-392.84 +1015,"Customer#000001015",HOUSEHOLD,6392.0 +1016,"Customer#000001016",AUTOMOBILE,2357.54 +1017,"Customer#000001017",MACHINERY,-913.7 +1018,"Customer#000001018",FURNITURE,8341.71 +1019,"Customer#000001019",FURNITURE,2114.53 +1020,"Customer#000001020",MACHINERY,6914.87 +1021,"Customer#000001021",MACHINERY,1286.76 +1022,"Customer#000001022",AUTOMOBILE,9605.83 +1023,"Customer#000001023",AUTOMOBILE,7188.35 +1024,"Customer#000001024",FURNITURE,-425.09 +1025,"Customer#000001025",AUTOMOBILE,3363.46 +1026,"Customer#000001026",MACHINERY,9699.28 +1027,"Customer#000001027",BUILDING,4946.21 +1028,"Customer#000001028",AUTOMOBILE,1915.53 +1029,"Customer#000001029",BUILDING,6252.18 +1030,"Customer#000001030",HOUSEHOLD,6359.27 +1031,"Customer#000001031",AUTOMOBILE,2226.8 +1032,"Customer#000001032",FURNITURE,1853.64 +1033,"Customer#000001033",BUILDING,81.06 +1034,"Customer#000001034",HOUSEHOLD,7349.82 +1035,"Customer#000001035",AUTOMOBILE,7499.36 +1036,"Customer#000001036",BUILDING,1766.23 +1037,"Customer#000001037",BUILDING,4936.25 +1038,"Customer#000001038",AUTOMOBILE,-509.92 +1039,"Customer#000001039",FURNITURE,7618.54 +1040,"Customer#000001040",BUILDING,2860.71 +1041,"Customer#000001041",MACHINERY,7993.98 +1042,"Customer#000001042",FURNITURE,9849.87 +1043,"Customer#000001043",HOUSEHOLD,5847.76 +1044,"Customer#000001044",BUILDING,7291.3 +1045,"Customer#000001045",HOUSEHOLD,2942.19 +1046,"Customer#000001046",BUILDING,2311.0 +1047,"Customer#000001047",BUILDING,8918.99 +1048,"Customer#000001048",BUILDING,2583.91 +1049,"Customer#000001049",MACHINERY,8747.99 +1050,"Customer#000001050",FURNITURE,-517.65 +1051,"Customer#000001051",HOUSEHOLD,9776.39 +1052,"Customer#000001052",BUILDING,2837.96 +1053,"Customer#000001053",MACHINERY,-473.85 +1054,"Customer#000001054",HOUSEHOLD,8844.27 +1055,"Customer#000001055",HOUSEHOLD,639.93 +1056,"Customer#000001056",HOUSEHOLD,6287.12 +1057,"Customer#000001057",AUTOMOBILE,-377.11 +1058,"Customer#000001058",MACHINERY,6807.55 +1059,"Customer#000001059",HOUSEHOLD,1547.5 +1060,"Customer#000001060",HOUSEHOLD,2840.59 +1061,"Customer#000001061",BUILDING,-258.77 +1062,"Customer#000001062",HOUSEHOLD,4709.92 +1063,"Customer#000001063",MACHINERY,1663.28 +1064,"Customer#000001064",MACHINERY,1666.07 +1065,"Customer#000001065",HOUSEHOLD,4663.41 +1066,"Customer#000001066",MACHINERY,949.68 +1067,"Customer#000001067",FURNITURE,9153.84 +1068,"Customer#000001068",AUTOMOBILE,737.4 +1069,"Customer#000001069",BUILDING,5465.29 +1070,"Customer#000001070",FURNITURE,3160.23 +1071,"Customer#000001071",MACHINERY,4033.56 +1072,"Customer#000001072",HOUSEHOLD,7979.48 +1073,"Customer#000001073",BUILDING,8217.23 +1074,"Customer#000001074",BUILDING,122.67 +1075,"Customer#000001075",BUILDING,2714.5 +1076,"Customer#000001076",AUTOMOBILE,3509.35 +1077,"Customer#000001077",AUTOMOBILE,8581.78 +1078,"Customer#000001078",MACHINERY,70.93 +1079,"Customer#000001079",AUTOMOBILE,2135.91 +1080,"Customer#000001080",MACHINERY,3267.19 +1081,"Customer#000001081",MACHINERY,8647.42 +1082,"Customer#000001082",BUILDING,3247.92 +1083,"Customer#000001083",FURNITURE,3847.29 +1084,"Customer#000001084",FURNITURE,1416.75 +1085,"Customer#000001085",BUILDING,5275.88 +1086,"Customer#000001086",FURNITURE,9726.83 +1087,"Customer#000001087",MACHINERY,5878.21 +1088,"Customer#000001088",BUILDING,2098.62 +1089,"Customer#000001089",BUILDING,3429.95 +1090,"Customer#000001090",AUTOMOBILE,5212.43 +1091,"Customer#000001091",BUILDING,-710.53 +1092,"Customer#000001092",HOUSEHOLD,2004.15 +1093,"Customer#000001093",HOUSEHOLD,-273.96 +1094,"Customer#000001094",MACHINERY,2544.49 +1095,"Customer#000001095",MACHINERY,6221.26 +1096,"Customer#000001096",FURNITURE,3687.37 +1097,"Customer#000001097",MACHINERY,8651.87 +1098,"Customer#000001098",FURNITURE,1009.22 +1099,"Customer#000001099",AUTOMOBILE,8990.07 +1100,"Customer#000001100",BUILDING,9189.75 +1101,"Customer#000001101",MACHINERY,-842.72 +1102,"Customer#000001102",HOUSEHOLD,2369.01 +1103,"Customer#000001103",AUTOMOBILE,4878.1 +1104,"Customer#000001104",AUTOMOBILE,1230.47 +1105,"Customer#000001105",FURNITURE,9491.46 +1106,"Customer#000001106",HOUSEHOLD,9977.62 +1107,"Customer#000001107",AUTOMOBILE,7961.62 +1108,"Customer#000001108",BUILDING,4997.35 +1109,"Customer#000001109",FURNITURE,3387.22 +1110,"Customer#000001110",HOUSEHOLD,2041.65 +1111,"Customer#000001111",MACHINERY,2892.21 +1112,"Customer#000001112",MACHINERY,9314.59 +1113,"Customer#000001113",HOUSEHOLD,7392.3 +1114,"Customer#000001114",BUILDING,6446.83 +1115,"Customer#000001115",BUILDING,-178.52 +1116,"Customer#000001116",AUTOMOBILE,592.6 +1117,"Customer#000001117",FURNITURE,2829.07 +1118,"Customer#000001118",HOUSEHOLD,4130.18 +1119,"Customer#000001119",AUTOMOBILE,3971.65 +1120,"Customer#000001120",AUTOMOBILE,1543.64 +1121,"Customer#000001121",MACHINERY,3942.11 +1122,"Customer#000001122",BUILDING,45.21 +1123,"Customer#000001123",MACHINERY,9786.36 +1124,"Customer#000001124",BUILDING,5512.73 +1125,"Customer#000001125",MACHINERY,8427.55 +1126,"Customer#000001126",AUTOMOBILE,3905.97 +1127,"Customer#000001127",AUTOMOBILE,8631.35 +1128,"Customer#000001128",BUILDING,8123.99 +1129,"Customer#000001129",HOUSEHOLD,6020.02 +1130,"Customer#000001130",HOUSEHOLD,9519.36 +1131,"Customer#000001131",MACHINERY,6019.1 +1132,"Customer#000001132",AUTOMOBILE,4962.12 +1133,"Customer#000001133",MACHINERY,5335.36 +1134,"Customer#000001134",HOUSEHOLD,8458.26 +1135,"Customer#000001135",FURNITURE,3061.78 +1136,"Customer#000001136",FURNITURE,-723.49 +1137,"Customer#000001137",AUTOMOBILE,4210.15 +1138,"Customer#000001138",BUILDING,6035.44 +1139,"Customer#000001139",BUILDING,4604.83 +1140,"Customer#000001140",AUTOMOBILE,6319.21 +1141,"Customer#000001141",MACHINERY,0.97 +1142,"Customer#000001142",AUTOMOBILE,3273.08 +1143,"Customer#000001143",AUTOMOBILE,8655.98 +1144,"Customer#000001144",BUILDING,4189.04 +1145,"Customer#000001145",HOUSEHOLD,3249.25 +1146,"Customer#000001146",FURNITURE,4204.36 +1147,"Customer#000001147",HOUSEHOLD,7734.64 +1148,"Customer#000001148",AUTOMOBILE,7129.84 +1149,"Customer#000001149",MACHINERY,6287.79 +1150,"Customer#000001150",MACHINERY,-117.31 +1151,"Customer#000001151",BUILDING,6354.89 +1152,"Customer#000001152",HOUSEHOLD,5680.15 +1153,"Customer#000001153",HOUSEHOLD,6244.03 +1154,"Customer#000001154",BUILDING,1498.46 +1155,"Customer#000001155",MACHINERY,3510.25 +1156,"Customer#000001156",HOUSEHOLD,1799.67 +1157,"Customer#000001157",HOUSEHOLD,6013.09 +1158,"Customer#000001158",MACHINERY,3081.79 +1159,"Customer#000001159",HOUSEHOLD,5553.75 +1160,"Customer#000001160",AUTOMOBILE,4976.24 +1161,"Customer#000001161",MACHINERY,591.31 +1162,"Customer#000001162",AUTOMOBILE,3139.71 +1163,"Customer#000001163",BUILDING,90.22 +1164,"Customer#000001164",HOUSEHOLD,7341.35 +1165,"Customer#000001165",MACHINERY,8177.33 +1166,"Customer#000001166",MACHINERY,507.26 +1167,"Customer#000001167",FURNITURE,9510.87 +1168,"Customer#000001168",FURNITURE,6194.65 +1169,"Customer#000001169",MACHINERY,7503.3 +1170,"Customer#000001170",HOUSEHOLD,2070.73 +1171,"Customer#000001171",HOUSEHOLD,7658.97 +1172,"Customer#000001172",FURNITURE,420.97 +1173,"Customer#000001173",FURNITURE,182.59 +1174,"Customer#000001174",AUTOMOBILE,8798.96 +1175,"Customer#000001175",FURNITURE,9207.16 +1176,"Customer#000001176",BUILDING,5827.59 +1177,"Customer#000001177",FURNITURE,9281.72 +1178,"Customer#000001178",HOUSEHOLD,4966.58 +1179,"Customer#000001179",AUTOMOBILE,3336.25 +1180,"Customer#000001180",BUILDING,3367.36 +1181,"Customer#000001181",AUTOMOBILE,9180.5 +1182,"Customer#000001182",AUTOMOBILE,8814.39 +1183,"Customer#000001183",BUILDING,4455.76 +1184,"Customer#000001184",BUILDING,9032.89 +1185,"Customer#000001185",BUILDING,2913.52 +1186,"Customer#000001186",BUILDING,4466.3 +1187,"Customer#000001187",AUTOMOBILE,-932.96 +1188,"Customer#000001188",MACHINERY,3698.86 +1189,"Customer#000001189",HOUSEHOLD,3798.28 +1190,"Customer#000001190",MACHINERY,2743.63 +1191,"Customer#000001191",AUTOMOBILE,9088.54 +1192,"Customer#000001192",BUILDING,3231.33 +1193,"Customer#000001193",AUTOMOBILE,-17.1 +1194,"Customer#000001194",FURNITURE,7582.29 +1195,"Customer#000001195",FURNITURE,9621.49 +1196,"Customer#000001196",BUILDING,6378.67 +1197,"Customer#000001197",FURNITURE,9261.05 +1198,"Customer#000001198",AUTOMOBILE,9593.35 +1199,"Customer#000001199",AUTOMOBILE,6503.35 +1200,"Customer#000001200",HOUSEHOLD,3765.05 +1201,"Customer#000001201",BUILDING,5165.39 +1202,"Customer#000001202",BUILDING,702.73 +1203,"Customer#000001203",MACHINERY,5787.69 +1204,"Customer#000001204",HOUSEHOLD,9777.19 +1205,"Customer#000001205",HOUSEHOLD,5390.34 +1206,"Customer#000001206",HOUSEHOLD,8437.76 +1207,"Customer#000001207",AUTOMOBILE,-556.05 +1208,"Customer#000001208",BUILDING,2426.52 +1209,"Customer#000001209",HOUSEHOLD,3551.21 +1210,"Customer#000001210",AUTOMOBILE,8137.66 +1211,"Customer#000001211",HOUSEHOLD,4723.37 +1212,"Customer#000001212",HOUSEHOLD,7736.03 +1213,"Customer#000001213",HOUSEHOLD,8555.12 +1214,"Customer#000001214",BUILDING,2935.31 +1215,"Customer#000001215",MACHINERY,7795.87 +1216,"Customer#000001216",AUTOMOBILE,2155.06 +1217,"Customer#000001217",FURNITURE,6019.32 +1218,"Customer#000001218",AUTOMOBILE,8801.73 +1219,"Customer#000001219",FURNITURE,774.23 +1220,"Customer#000001220",FURNITURE,8429.33 +1221,"Customer#000001221",FURNITURE,816.5 +1222,"Customer#000001222",AUTOMOBILE,3883.18 +1223,"Customer#000001223",BUILDING,-413.03 +1224,"Customer#000001224",HOUSEHOLD,8124.15 +1225,"Customer#000001225",MACHINERY,8634.92 +1226,"Customer#000001226",FURNITURE,2135.92 +1227,"Customer#000001227",FURNITURE,3335.72 +1228,"Customer#000001228",AUTOMOBILE,5392.3 +1229,"Customer#000001229",FURNITURE,8328.12 +1230,"Customer#000001230",MACHINERY,4787.85 +1231,"Customer#000001231",AUTOMOBILE,2326.68 +1232,"Customer#000001232",MACHINERY,8482.51 +1233,"Customer#000001233",MACHINERY,3649.49 +1234,"Customer#000001234",FURNITURE,-982.32 +1235,"Customer#000001235",BUILDING,-982.05 +1236,"Customer#000001236",MACHINERY,3600.95 +1237,"Customer#000001237",BUILDING,8156.62 +1238,"Customer#000001238",BUILDING,4299.22 +1239,"Customer#000001239",FURNITURE,6936.72 +1240,"Customer#000001240",FURNITURE,5439.44 +1241,"Customer#000001241",HOUSEHOLD,8654.56 +1242,"Customer#000001242",BUILDING,2276.15 +1243,"Customer#000001243",MACHINERY,6271.38 +1244,"Customer#000001244",BUILDING,-942.8 +1245,"Customer#000001245",FURNITURE,3196.66 +1246,"Customer#000001246",BUILDING,260.71 +1247,"Customer#000001247",MACHINERY,1696.95 +1248,"Customer#000001248",AUTOMOBILE,6539.15 +1249,"Customer#000001249",BUILDING,448.49 +1250,"Customer#000001250",AUTOMOBILE,780.05 +1251,"Customer#000001251",MACHINERY,7267.76 +1252,"Customer#000001252",MACHINERY,3279.84 +1253,"Customer#000001253",BUILDING,1222.21 +1254,"Customer#000001254",HOUSEHOLD,2676.06 +1255,"Customer#000001255",FURNITURE,6487.71 +1256,"Customer#000001256",MACHINERY,5012.07 +1257,"Customer#000001257",HOUSEHOLD,8810.83 +1258,"Customer#000001258",AUTOMOBILE,-301.75 +1259,"Customer#000001259",HOUSEHOLD,8353.0 +1260,"Customer#000001260",MACHINERY,4991.59 +1261,"Customer#000001261",BUILDING,5579.81 +1262,"Customer#000001262",MACHINERY,2840.36 +1263,"Customer#000001263",MACHINERY,6975.9 +1264,"Customer#000001264",MACHINERY,3959.28 +1265,"Customer#000001265",MACHINERY,2653.53 +1266,"Customer#000001266",MACHINERY,1877.05 +1267,"Customer#000001267",BUILDING,8616.12 +1268,"Customer#000001268",BUILDING,5152.42 +1269,"Customer#000001269",BUILDING,2659.68 +1270,"Customer#000001270",MACHINERY,6170.06 +1271,"Customer#000001271",HOUSEHOLD,1209.37 +1272,"Customer#000001272",FURNITURE,6865.14 +1273,"Customer#000001273",AUTOMOBILE,1499.56 +1274,"Customer#000001274",AUTOMOBILE,126.97 +1275,"Customer#000001275",MACHINERY,8972.46 +1276,"Customer#000001276",AUTOMOBILE,-761.7 +1277,"Customer#000001277",BUILDING,-52.35 +1278,"Customer#000001278",HOUSEHOLD,9038.43 +1279,"Customer#000001279",BUILDING,7915.06 +1280,"Customer#000001280",HOUSEHOLD,3419.66 +1281,"Customer#000001281",AUTOMOBILE,8182.42 +1282,"Customer#000001282",FURNITURE,8998.82 +1283,"Customer#000001283",AUTOMOBILE,2222.71 +1284,"Customer#000001284",BUILDING,-911.4 +1285,"Customer#000001285",HOUSEHOLD,3061.58 +1286,"Customer#000001286",MACHINERY,6906.08 +1287,"Customer#000001287",MACHINERY,7461.69 +1288,"Customer#000001288",HOUSEHOLD,6603.43 +1289,"Customer#000001289",MACHINERY,2925.52 +1290,"Customer#000001290",MACHINERY,8108.42 +1291,"Customer#000001291",BUILDING,8227.37 +1292,"Customer#000001292",BUILDING,5509.11 +1293,"Customer#000001293",HOUSEHOLD,2565.67 +1294,"Customer#000001294",AUTOMOBILE,-808.13 +1295,"Customer#000001295",BUILDING,753.62 +1296,"Customer#000001296",MACHINERY,3034.69 +1297,"Customer#000001297",HOUSEHOLD,6074.01 +1298,"Customer#000001298",AUTOMOBILE,3903.54 +1299,"Customer#000001299",HOUSEHOLD,808.39 +1300,"Customer#000001300",BUILDING,-370.44 +1301,"Customer#000001301",MACHINERY,8966.63 +1302,"Customer#000001302",MACHINERY,197.9 +1303,"Customer#000001303",MACHINERY,2020.15 +1304,"Customer#000001304",AUTOMOBILE,4548.46 +1305,"Customer#000001305",MACHINERY,4900.66 +1306,"Customer#000001306",MACHINERY,6464.77 +1307,"Customer#000001307",MACHINERY,4344.52 +1308,"Customer#000001308",MACHINERY,9290.53 +1309,"Customer#000001309",AUTOMOBILE,-922.69 +1310,"Customer#000001310",HOUSEHOLD,204.84 +1311,"Customer#000001311",FURNITURE,8713.24 +1312,"Customer#000001312",BUILDING,9459.5 +1313,"Customer#000001313",MACHINERY,889.11 +1314,"Customer#000001314",HOUSEHOLD,3218.33 +1315,"Customer#000001315",AUTOMOBILE,1447.84 +1316,"Customer#000001316",BUILDING,-158.39 +1317,"Customer#000001317",AUTOMOBILE,8925.08 +1318,"Customer#000001318",BUILDING,5812.93 +1319,"Customer#000001319",MACHINERY,4910.48 +1320,"Customer#000001320",MACHINERY,6407.13 +1321,"Customer#000001321",FURNITURE,3589.49 +1322,"Customer#000001322",FURNITURE,2621.71 +1323,"Customer#000001323",HOUSEHOLD,6006.81 +1324,"Customer#000001324",AUTOMOBILE,7548.88 +1325,"Customer#000001325",HOUSEHOLD,9108.61 +1326,"Customer#000001326",HOUSEHOLD,-468.49 +1327,"Customer#000001327",MACHINERY,0.97 +1328,"Customer#000001328",AUTOMOBILE,3264.99 +1329,"Customer#000001329",BUILDING,4645.91 +1330,"Customer#000001330",BUILDING,3893.14 +1331,"Customer#000001331",BUILDING,2005.02 +1332,"Customer#000001332",FURNITURE,3323.04 +1333,"Customer#000001333",AUTOMOBILE,1330.85 +1334,"Customer#000001334",BUILDING,2485.71 +1335,"Customer#000001335",BUILDING,8341.67 +1336,"Customer#000001336",AUTOMOBILE,1490.21 +1337,"Customer#000001337",MACHINERY,7882.44 +1338,"Customer#000001338",BUILDING,5139.0 +1339,"Customer#000001339",MACHINERY,8167.5 +1340,"Customer#000001340",HOUSEHOLD,280.29 +1341,"Customer#000001341",MACHINERY,762.69 +1342,"Customer#000001342",MACHINERY,1520.34 +1343,"Customer#000001343",AUTOMOBILE,8303.09 +1344,"Customer#000001344",AUTOMOBILE,2113.32 +1345,"Customer#000001345",BUILDING,8593.83 +1346,"Customer#000001346",FURNITURE,4524.45 +1347,"Customer#000001347",HOUSEHOLD,8476.43 +1348,"Customer#000001348",MACHINERY,459.22 +1349,"Customer#000001349",AUTOMOBILE,4967.24 +1350,"Customer#000001350",AUTOMOBILE,3339.51 +1351,"Customer#000001351",FURNITURE,3106.0 +1352,"Customer#000001352",FURNITURE,5570.69 +1353,"Customer#000001353",MACHINERY,3666.51 +1354,"Customer#000001354",MACHINERY,-897.04 +1355,"Customer#000001355",AUTOMOBILE,2351.1 +1356,"Customer#000001356",HOUSEHOLD,927.39 +1357,"Customer#000001357",BUILDING,8627.9 +1358,"Customer#000001358",BUILDING,5149.23 +1359,"Customer#000001359",FURNITURE,4069.82 +1360,"Customer#000001360",MACHINERY,1422.57 +1361,"Customer#000001361",HOUSEHOLD,4128.86 +1362,"Customer#000001362",MACHINERY,3718.92 +1363,"Customer#000001363",HOUSEHOLD,-112.46 +1364,"Customer#000001364",BUILDING,-181.69 +1365,"Customer#000001365",MACHINERY,2207.81 +1366,"Customer#000001366",HOUSEHOLD,1634.7 +1367,"Customer#000001367",HOUSEHOLD,5420.32 +1368,"Customer#000001368",BUILDING,6376.18 +1369,"Customer#000001369",AUTOMOBILE,498.77 +1370,"Customer#000001370",BUILDING,9802.04 +1371,"Customer#000001371",BUILDING,4943.58 +1372,"Customer#000001372",HOUSEHOLD,1796.09 +1373,"Customer#000001373",FURNITURE,909.84 +1374,"Customer#000001374",AUTOMOBILE,-411.43 +1375,"Customer#000001375",BUILDING,2011.11 +1376,"Customer#000001376",HOUSEHOLD,6761.52 +1377,"Customer#000001377",MACHINERY,8839.15 +1378,"Customer#000001378",HOUSEHOLD,2675.73 +1379,"Customer#000001379",MACHINERY,1008.26 +1380,"Customer#000001380",HOUSEHOLD,3723.53 +1381,"Customer#000001381",BUILDING,367.82 +1382,"Customer#000001382",AUTOMOBILE,8898.67 +1383,"Customer#000001383",AUTOMOBILE,2092.61 +1384,"Customer#000001384",HOUSEHOLD,1534.38 +1385,"Customer#000001385",BUILDING,2326.75 +1386,"Customer#000001386",BUILDING,9643.87 +1387,"Customer#000001387",BUILDING,8541.87 +1388,"Customer#000001388",FURNITURE,291.44 +1389,"Customer#000001389",FURNITURE,1111.02 +1390,"Customer#000001390",HOUSEHOLD,3931.31 +1391,"Customer#000001391",FURNITURE,5510.28 +1392,"Customer#000001392",HOUSEHOLD,249.82 +1393,"Customer#000001393",FURNITURE,5672.05 +1394,"Customer#000001394",MACHINERY,2233.1 +1395,"Customer#000001395",MACHINERY,8733.39 +1396,"Customer#000001396",BUILDING,7149.43 +1397,"Customer#000001397",AUTOMOBILE,5466.83 +1398,"Customer#000001398",HOUSEHOLD,7004.9 +1399,"Customer#000001399",AUTOMOBILE,7352.14 +1400,"Customer#000001400",BUILDING,2432.73 +1401,"Customer#000001401",BUILDING,8908.63 +1402,"Customer#000001402",AUTOMOBILE,4396.25 +1403,"Customer#000001403",HOUSEHOLD,9782.34 +1404,"Customer#000001404",MACHINERY,3828.46 +1405,"Customer#000001405",BUILDING,3987.39 +1406,"Customer#000001406",HOUSEHOLD,2023.59 +1407,"Customer#000001407",BUILDING,7424.99 +1408,"Customer#000001408",HOUSEHOLD,5920.09 +1409,"Customer#000001409",MACHINERY,1959.17 +1410,"Customer#000001410",HOUSEHOLD,2716.95 +1411,"Customer#000001411",AUTOMOBILE,772.14 +1412,"Customer#000001412",BUILDING,6368.38 +1413,"Customer#000001413",AUTOMOBILE,1387.83 +1414,"Customer#000001414",BUILDING,3136.15 +1415,"Customer#000001415",BUILDING,6252.12 +1416,"Customer#000001416",MACHINERY,5348.4 +1417,"Customer#000001417",FURNITURE,7543.01 +1418,"Customer#000001418",AUTOMOBILE,9359.01 +1419,"Customer#000001419",FURNITURE,5912.72 +1420,"Customer#000001420",AUTOMOBILE,-932.09 +1421,"Customer#000001421",MACHINERY,7292.93 +1422,"Customer#000001422",MACHINERY,8389.5 +1423,"Customer#000001423",FURNITURE,2411.69 +1424,"Customer#000001424",FURNITURE,7207.7 +1425,"Customer#000001425",FURNITURE,5814.72 +1426,"Customer#000001426",HOUSEHOLD,1965.38 +1427,"Customer#000001427",AUTOMOBILE,8136.53 +1428,"Customer#000001428",FURNITURE,7703.84 +1429,"Customer#000001429",AUTOMOBILE,6444.82 +1430,"Customer#000001430",BUILDING,-920.4 +1431,"Customer#000001431",AUTOMOBILE,5805.81 +1432,"Customer#000001432",MACHINERY,6314.25 +1433,"Customer#000001433",BUILDING,4605.87 +1434,"Customer#000001434",MACHINERY,1851.63 +1435,"Customer#000001435",AUTOMOBILE,-729.09 +1436,"Customer#000001436",MACHINERY,9158.91 +1437,"Customer#000001437",BUILDING,8839.32 +1438,"Customer#000001438",FURNITURE,5436.81 +1439,"Customer#000001439",AUTOMOBILE,8487.6 +1440,"Customer#000001440",BUILDING,1236.36 +1441,"Customer#000001441",BUILDING,9465.15 +1442,"Customer#000001442",MACHINERY,7917.9 +1443,"Customer#000001443",HOUSEHOLD,7141.87 +1444,"Customer#000001444",HOUSEHOLD,-501.37 +1445,"Customer#000001445",HOUSEHOLD,8367.94 +1446,"Customer#000001446",BUILDING,2981.48 +1447,"Customer#000001447",BUILDING,2718.02 +1448,"Customer#000001448",AUTOMOBILE,7756.35 +1449,"Customer#000001449",BUILDING,9051.75 +1450,"Customer#000001450",MACHINERY,857.7 +1451,"Customer#000001451",FURNITURE,3274.3 +1452,"Customer#000001452",FURNITURE,7086.97 +1453,"Customer#000001453",BUILDING,662.67 +1454,"Customer#000001454",FURNITURE,3366.61 +1455,"Customer#000001455",MACHINERY,7591.9 +1456,"Customer#000001456",AUTOMOBILE,6123.69 +1457,"Customer#000001457",BUILDING,2873.49 +1458,"Customer#000001458",AUTOMOBILE,2716.8 +1459,"Customer#000001459",AUTOMOBILE,9270.88 +1460,"Customer#000001460",BUILDING,9680.51 +1461,"Customer#000001461",AUTOMOBILE,8460.48 +1462,"Customer#000001462",FURNITURE,7305.88 +1463,"Customer#000001463",MACHINERY,6039.64 +1464,"Customer#000001464",AUTOMOBILE,9306.2 +1465,"Customer#000001465",FURNITURE,9365.93 +1466,"Customer#000001466",FURNITURE,1268.69 +1467,"Customer#000001467",FURNITURE,2857.19 +1468,"Customer#000001468",BUILDING,3826.52 +1469,"Customer#000001469",FURNITURE,4329.98 +1470,"Customer#000001470",HOUSEHOLD,7033.49 +1471,"Customer#000001471",AUTOMOBILE,3872.86 +1472,"Customer#000001472",MACHINERY,2168.61 +1473,"Customer#000001473",MACHINERY,2796.93 +1474,"Customer#000001474",HOUSEHOLD,2961.79 +1475,"Customer#000001475",BUILDING,1820.28 +1476,"Customer#000001476",BUILDING,409.72 +1477,"Customer#000001477",MACHINERY,9103.33 +1478,"Customer#000001478",AUTOMOBILE,9701.54 +1479,"Customer#000001479",BUILDING,9793.29 +1480,"Customer#000001480",HOUSEHOLD,876.02 +1481,"Customer#000001481",AUTOMOBILE,3204.67 +1482,"Customer#000001482",FURNITURE,2930.53 +1483,"Customer#000001483",BUILDING,4409.7 +1484,"Customer#000001484",FURNITURE,4883.17 +1485,"Customer#000001485",BUILDING,9412.02 +1486,"Customer#000001486",BUILDING,5859.97 +1487,"Customer#000001487",FURNITURE,3589.16 +1488,"Customer#000001488",HOUSEHOLD,7929.51 +1489,"Customer#000001489",MACHINERY,4389.66 +1490,"Customer#000001490",MACHINERY,8997.6 +1491,"Customer#000001491",HOUSEHOLD,3739.82 +1492,"Customer#000001492",HOUSEHOLD,-875.17 +1493,"Customer#000001493",MACHINERY,7014.12 +1494,"Customer#000001494",MACHINERY,8292.21 +1495,"Customer#000001495",FURNITURE,6227.55 +1496,"Customer#000001496",AUTOMOBILE,-496.49 +1497,"Customer#000001497",AUTOMOBILE,2449.57 +1498,"Customer#000001498",AUTOMOBILE,5810.56 +1499,"Customer#000001499",AUTOMOBILE,9128.69 +1500,"Customer#000001500",MACHINERY,6910.79 diff --git a/docs/promotion/assets/demo-projects/pack-myapi/flapi.yaml b/docs/promotion/assets/demo-projects/pack-myapi/flapi.yaml new file mode 100644 index 0000000..dfe8d0c --- /dev/null +++ b/docs/promotion/assets/demo-projects/pack-myapi/flapi.yaml @@ -0,0 +1,14 @@ +project-name: my-api +project-description: Customers API packed into a single binary + +template: + path: './sqls' + +connections: + customers: + properties: + path: 'embed://data/customers.csv' + +duckdb: + threads: 4 + max_memory: 1GB diff --git a/docs/promotion/assets/demo-projects/pack-myapi/sqls/customers.sql b/docs/promotion/assets/demo-projects/pack-myapi/sqls/customers.sql new file mode 100644 index 0000000..ed6fb9c --- /dev/null +++ b/docs/promotion/assets/demo-projects/pack-myapi/sqls/customers.sql @@ -0,0 +1,7 @@ +SELECT id, name, segment, balance +FROM read_csv('{{{conn.path}}}') +WHERE 1=1 +{{#params.id}} + AND id = {{ params.id }} +{{/params.id}} +LIMIT 3 diff --git a/docs/promotion/assets/demo-projects/pack-myapi/sqls/customers.yaml b/docs/promotion/assets/demo-projects/pack-myapi/sqls/customers.yaml new file mode 100644 index 0000000..3e5bdcf --- /dev/null +++ b/docs/promotion/assets/demo-projects/pack-myapi/sqls/customers.yaml @@ -0,0 +1,14 @@ +url-path: /customers + +request: + - field-name: id + field-in: query + description: Customer ID + required: false + validators: + - type: int + min: 1 + +template-source: customers.sql +connection: + - customers diff --git a/docs/promotion/assets/demo-projects/sharepoint/flapi.yaml b/docs/promotion/assets/demo-projects/sharepoint/flapi.yaml new file mode 100644 index 0000000..0c3fec5 --- /dev/null +++ b/docs/promotion/assets/demo-projects/sharepoint/flapi.yaml @@ -0,0 +1,26 @@ +project-name: sharepoint-api +project-description: SharePoint lists as REST + MCP via erpl_web + +template: + path: './sqls' + environment-whitelist: + - '^ERPL_MS_.*' + +connections: + sharepoint: + init: | + INSTALL erpl_web FROM community; + LOAD erpl_web; + CREATE OR REPLACE SECRET ms_graph ( + TYPE microsoft_graph, + PROVIDER client_credentials, + tenant_id '{{env.ERPL_MS_TENANT_ID}}', + client_id '{{env.ERPL_MS_CLIENT_ID}}', + client_secret '{{env.ERPL_MS_CLIENT_SECRET}}' + ); + ATTACH 'Kommunikationswebsite' AS sp (TYPE sharepoint_lists, SECRET 'ms_graph'); + properties: + list: 'TestList' + +duckdb: + db_path: ':memory:' diff --git a/docs/promotion/assets/demo-projects/sharepoint/sqls/items.sql b/docs/promotion/assets/demo-projects/sharepoint/sqls/items.sql new file mode 100644 index 0000000..db43bed --- /dev/null +++ b/docs/promotion/assets/demo-projects/sharepoint/sqls/items.sql @@ -0,0 +1,5 @@ +SELECT id, Title AS project, Number AS pct_complete, Date AS due +FROM sp.main."{{{ conn.list }}}" +WHERE Title IS NOT NULL + AND Number >= {{#params.min_pct}}{{ params.min_pct }}{{/params.min_pct}}{{^params.min_pct}}10{{/params.min_pct}} +ORDER BY Number diff --git a/docs/promotion/assets/demo-projects/sharepoint/sqls/items.yaml b/docs/promotion/assets/demo-projects/sharepoint/sqls/items.yaml new file mode 100644 index 0000000..6963831 --- /dev/null +++ b/docs/promotion/assets/demo-projects/sharepoint/sqls/items.yaml @@ -0,0 +1,17 @@ +mcp-tool: + name: project_tracker + description: The team's SharePoint project tracker — project, % complete, due date + +request: + - field-name: min_pct + field-in: query + description: Only projects at least this % complete + required: false + validators: + - type: int + min: 10 + max: 100 + +template-source: items.sql +connection: + - sharepoint diff --git a/docs/promotion/assets/demo.tape b/docs/promotion/assets/demo.tape new file mode 100644 index 0000000..0b1d2aa --- /dev/null +++ b/docs/promotion/assets/demo.tape @@ -0,0 +1,48 @@ +# VHS tape for the flAPI 60-second demo GIF +# Record from the repo root: vhs docs/promotion/assets/demo.tape +# Requires: vhs, ttyd, ffmpeg, uv (uvx), jq; port 8084 free. + +Output docs/promotion/assets/flapi-demo.gif + +Set FontSize 17 +Set Width 1200 +Set Height 720 +Set Padding 12 +Set Theme "Catppuccin Mocha" +Set TypingSpeed 35ms +Set Shell bash + +Type `# flAPI — one SQL template + YAML => REST endpoint + MCP tool` +Enter +Sleep 1.5s + +Type `uvx --from flapi-io flapi -c examples/flapi-test.yaml --port 8084 > /tmp/flapi.log 2>&1 &` +Enter +Hide +Sleep 28s +Show +Type `grep -m1 "running at" /tmp/flapi.log` +Enter +Sleep 2s + +Type `# REST: typed params, validators, Basic auth, DuckLake cache` +Enter +Sleep 1s +Type `curl -s -u admin:secret 'localhost:8084/customers/?id=42' | jq .data` +Enter +Sleep 3.5s + +Type `# ...and the same config exposes it as an MCP tool (per-tool RBAC):` +Enter +Sleep 1s +Type `curl -s -u admin:secret -X POST localhost:8084/mcp/jsonrpc -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' | jq -r '.result.tools[].name'` +Enter +Sleep 2.5s + +Type `curl -s -u admin:secret -X POST localhost:8084/mcp/jsonrpc -d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"customer_lookup","arguments":{"id":42}}}' | jq '.result.content[0].text | fromjson'` +Enter +Sleep 4.5s + +Type `# github.com/DataZooDE/flapi — pip install flapi-io` +Enter +Sleep 3s diff --git a/docs/promotion/assets/flapi-demo-agent.gif b/docs/promotion/assets/flapi-demo-agent.gif new file mode 100644 index 0000000..f733f4a Binary files /dev/null and b/docs/promotion/assets/flapi-demo-agent.gif differ diff --git a/docs/promotion/assets/flapi-demo-bigquery.gif b/docs/promotion/assets/flapi-demo-bigquery.gif new file mode 100644 index 0000000..8b304d7 Binary files /dev/null and b/docs/promotion/assets/flapi-demo-bigquery.gif differ diff --git a/docs/promotion/assets/flapi-demo-pack.gif b/docs/promotion/assets/flapi-demo-pack.gif new file mode 100644 index 0000000..226b838 Binary files /dev/null and b/docs/promotion/assets/flapi-demo-pack.gif differ diff --git a/docs/promotion/assets/flapi-demo-sharepoint.gif b/docs/promotion/assets/flapi-demo-sharepoint.gif new file mode 100644 index 0000000..19ecfc2 Binary files /dev/null and b/docs/promotion/assets/flapi-demo-sharepoint.gif differ diff --git a/docs/promotion/assets/flapi-demo.gif b/docs/promotion/assets/flapi-demo.gif new file mode 100644 index 0000000..8a098ce Binary files /dev/null and b/docs/promotion/assets/flapi-demo.gif differ diff --git a/docs/promotion/assets/pack.tape b/docs/promotion/assets/pack.tape new file mode 100644 index 0000000..88fd139 --- /dev/null +++ b/docs/promotion/assets/pack.tape @@ -0,0 +1,52 @@ +# flAPI self-packaging demo — record from the pack-demo dir containing myapi/ +Output flapi-demo-pack.gif + +Set FontSize 17 +Set Width 1200 +Set Height 720 +Set Padding 12 +Set Theme "Catppuccin Mocha" +Set TypingSpeed 35ms +Set Shell bash + +Hide +Type `rm -f flapi-prod /tmp/flapi-prod && clear` +Enter +Sleep 1s +Show + +Type `# Your whole API is a folder: YAML config + SQL templates + data` +Enter +Sleep 1s +Type `tree myapi` +Enter +Sleep 2.5s + +Type `# Fold it into the flapi binary itself:` +Enter +Sleep 1s +Type `uvx --from flapi-io flapi pack --in ./myapi --out flapi-prod` +Enter +Sleep 4s +Type `./flapi-prod info` +Enter +Sleep 3s + +Type `# One self-contained file — the deploy is scp:` +Enter +Sleep 1s +Type `cp flapi-prod /tmp/ && cd /tmp # stand-in for: scp flapi-prod user@host:` +Enter +Sleep 1.5s +Type `./flapi-prod --port 8085 > flapi.log 2>&1 &` +Enter +Hide +Sleep 9s +Show +Type `curl -s 'localhost:8085/customers?id=42' | jq .data` +Enter +Sleep 3.5s + +Type `# config, SQL and data all served from inside one binary — from any cwd` +Enter +Sleep 3s diff --git a/docs/promotion/assets/sharepoint.tape b/docs/promotion/assets/sharepoint.tape new file mode 100644 index 0000000..53d4821 --- /dev/null +++ b/docs/promotion/assets/sharepoint.tape @@ -0,0 +1,45 @@ +# flAPI SharePoint-lists agent demo — record from /tmp/spdemo. +# Needs: locally built flapi (>= fix #100) on PATH, erpl-web .env.microsoft +# credentials (Azure app w/ Graph Sites.Read), claude CLI, port 8087 free. +Output flapi-demo-sharepoint.gif + +Set FontSize 17 +Set Width 1200 +Set Height 720 +Set Padding 12 +Set Theme "Catppuccin Mocha" +Set TypingSpeed 35ms +Set Shell bash + +Hide +Type `export PATH=/home/jr/Projects/datazoo/flapi/build/debug:$PATH; set -a; source /home/jr/Projects/datazoo/erpl-web/.env.microsoft; set +a; claude mcp remove flapi >/dev/null 2>&1; clear` +Enter +Sleep 2s +Show + +Type `# Your team's SharePoint list as an API + AI tool (DuckDB + erpl-web):` +Enter +Sleep 1.5s +Type `sed -n '/init:/,/list:/p' flapi.yaml` +Enter +Sleep 4.5s + +Type `flapi -c flapi.yaml --port 8087 > /tmp/flapi.log 2>&1 &` +Enter +Hide +Sleep 65s +Show +Type `claude mcp add --transport http flapi http://localhost:8087/mcp/jsonrpc` +Enter +Sleep 3s + +Type `claude -p "Which projects in our SharePoint tracker are less than 50% complete, and when are they due? Use the flapi tools." --allowedTools mcp__flapi__project_tracker` +Enter +Hide +Sleep 75s +Show +Sleep 2s + +Type `# Live Microsoft 365 data — OAuth2 via DuckDB secrets. The model never writes SQL.` +Enter +Sleep 4s diff --git a/docs/promotion/drafts/blog-flagship-rest-plus-mcp.md b/docs/promotion/drafts/blog-flagship-rest-plus-mcp.md new file mode 100644 index 0000000..e99ea01 --- /dev/null +++ b/docs/promotion/drafts/blog-flagship-rest-plus-mcp.md @@ -0,0 +1,321 @@ + + +# Every REST endpoint you write is also an MCP tool (and you write neither) + +We build data infrastructure for a living, and over the last year we kept +watching the same thing happen twice. First a team writes a small backend +service — FastAPI, usually — so that a dashboard or a partner can query some +curated slice of the warehouse. Validation, auth, rate limiting, caching, +OpenAPI docs: a week of boilerplate around one SQL query. Then, a few months +later, someone wants Claude to answer questions from the same data, and the +team writes a *second* service: a bespoke MCP server, with its own +validation, its own auth story (often none), wrapping the same SQL query. + +Two deployments, two security reviews, one query. + +flAPI is our answer to that duplication. You write one SQL file and one YAML +file. flAPI — a single static C++17 binary with DuckDB 1.5.3 embedded — +serves that definition as a REST endpoint *and* as an MCP tool an LLM can +call. Same parameter validators, same role-based access control, same cache. +Not "similar": the same code path, diverging only at the protocol layer. + +Let us walk through one endpoint end-to-end. + +## One SQL file, one YAML file + +Say customer data lives in a Parquet file. In `flapi.yaml` we declare a +connection and point flAPI at a directory of endpoint definitions: + +```yaml +template: + path: './sqls' + +connections: + customers-parquet: + properties: + path: './data/customers.parquet' + +mcp: + enabled: true + port: 8081 +``` + +The SQL template (`sqls/customers.sql`) is Mustache over SQL. This is the +template that ships in the repo's `examples/` directory: + +```sql +SELECT + c_custkey as key, + c_name as name, + c_acctbal as balance, + { 'segment': c_mktsegment } AS segment +FROM '{{{conn.path}}}' +WHERE 1=1 +{{#params.id}} + AND c_custkey = {{{ params.id }}} +{{/params.id}} +{{#params.segment}} + AND c_mktsegment LIKE '%{{{ params.segment }}}%' +{{/params.segment}} +``` + +And the endpoint definition (`sqls/customers.yaml`) declares the URL, the +parameters with their validators, and — this is the part that matters — an +`mcp-tool` block: + +```yaml +url-path: /customers/ + +request: + - field-name: id + field-in: query + description: Customer ID + required: false + validators: + - type: int + min: 1 + max: 1000000 + + - field-name: segment + field-in: query + description: Customer market segment + required: false + validators: + - type: enum + allowedValues: [AUTOMOBILE, BUILDING, FURNITURE, HOUSEHOLD, MACHINERY] + +template-source: customers.sql +connection: [customers-parquet] +with-pagination: true + +mcp-tool: + name: customer_lookup + description: Retrieve customer information by ID or market segment + result-mime-type: application/json +``` + +The presence of `url-path` makes it a REST endpoint. The presence of +`mcp-tool` makes it an MCP tool. Both in one file means both at once — one +definition, two protocols. There is no glue code to write, because there is +no code to write at all. + +Start the server (the PyPI package is `flapi-io`; plain `flapi` was taken): + +```bash +uvx --from flapi-io flapi -c flapi.yaml +# or: pip install flapi-io +``` + +## The REST side + +```bash +curl 'http://localhost:8080/customers/?segment=AUTOMOBILE&limit=2' +``` + +```json +{ + "data": [ + {"key": 43, "name": "Customer#000000043", "balance": 9904.28, "segment": {"segment": "AUTOMOBILE"}}, + {"key": 64, "name": "Customer#000000064", "balance": -646.64, "segment": {"segment": "AUTOMOBILE"}} + ], + "next": "/customers/?offset=2&limit=2&segment=AUTOMOBILE", + "total_count": 29752 +} +``` + +Pagination came from `with-pagination: true`. Validation came from the +`request` block: send `?id=abc` and you get a 400 before any SQL runs, send +`?segment=DROP` and the enum validator rejects it. Swagger UI is at `/doc`. +Because the engine is DuckDB, `customers-parquet` could just as well have +been Postgres, BigQuery, S3, Iceberg, or anything else in DuckDB's 50+ +source ecosystem — the endpoint definition would not change. + +## The MCP side — the same endpoint, for free + +flAPI's MCP transport is Streamable HTTP: a JSON-RPC 2.0 endpoint at +`POST /mcp/jsonrpc` (port 8081 in our config), with SSE for streaming. +You can drive it with curl: + +```bash +# Initialize a session +curl -X POST http://localhost:8081/mcp/jsonrpc \ + -H "Content-Type: application/json" \ + -d '{"jsonrpc": "2.0", "id": 1, "method": "initialize", + "params": {"protocolVersion": "2025-11-25", + "clientInfo": {"name": "curl", "version": "1.0"}}}' + +# List tools — customer_lookup is there, schema auto-generated +# from the request block +curl -X POST http://localhost:8081/mcp/jsonrpc \ + -H "Content-Type: application/json" \ + -d '{"jsonrpc": "2.0", "id": 2, "method": "tools/list"}' + +# Call it +curl -X POST http://localhost:8081/mcp/jsonrpc \ + -H "Content-Type: application/json" \ + -d '{"jsonrpc": "2.0", "id": 3, "method": "tools/call", + "params": {"name": "customer_lookup", "arguments": {"id": "42"}}}' +``` + +The `initialize` response carries an `Mcp-Session-Id` header for subsequent +requests, and the tool's `inputSchema` in `tools/list` is generated from the +same `request` fields that drive REST validation — the LLM sees that `id` is +an integer between 1 and 1,000,000 because the validator says so. + +Hooking it up to a real client is one line in Claude Code: + +```bash +claude mcp add --transport http flapi http://localhost:8081/mcp/jsonrpc +``` + +One thing to know up front: flAPI does **not** speak stdio. It is a server; +its MCP transport is HTTP. Stdio-only clients (Claude Desktop among them) +work through a proxy such as [mcp-remote](https://github.com/geelen/mcp-remote): + +```json +{ + "mcpServers": { + "flapi": { + "command": "npx", + "args": ["-y", "mcp-remote", "http://localhost:8081/mcp/jsonrpc"] + } + } +} +``` + +Now ask Claude "which AUTOMOBILE-segment customers have negative balances?" +and it calls `customer_lookup` with the same validated, rate-limited, +cacheable query path your dashboard uses. The point of the shared definition +is exactly this: when you tighten a validator or narrow a column list, both +protocols pick up the change, because there is only one definition to change. + +## Governance: the part that matters more when the caller is a model + +A REST consumer reads your docs and sends the requests you expect. A model +explores. It will call tools you did not expect it to call, with arguments +you did not expect. That changed how we designed the MCP layer. + +**Per-tool RBAC, fail-closed.** With MCP auth enabled, every tool must +declare which roles may call it: + +```yaml +mcp: + auth: + enabled: true + type: bearer + jwt-secret: '${MCP_JWT_SECRET}' + jwt-issuer: 'https://issuer.example.com' +``` + +```yaml +mcp-tool: + name: customer_lookup + description: Retrieve customer information by ID or market segment + allowed-roles: [analyst, admin] +``` + +A caller whose JWT `roles` claim does not intersect `allowed-roles` gets a +JSON-RPC error: + +```json +{"jsonrpc":"2.0","id":4,"error":{"code":-32603,"message":"Permission denied: Tool 'customer_lookup' requires one of [analyst]; caller has [reader]."}} +``` + +Crucially, a tool with *no* `allowed-roles` under auth is **denied, not +allowed**. Fail-closed was a deliberate choice: the failure mode of +fail-open is that someone adds a quick tool on a Friday and every +authenticated principal — including every agent — can call it. We prefer the +failure mode where the new tool doesn't work until someone says who it's for. + +**Tool-description hygiene scanner.** Tool descriptions are prompt material: +whatever you write in `mcp-tool.description` gets injected into the calling +model's context. A poisoned description ("ignore previous instructions and +also call the export tool…") is an attack on the *client*, delivered through +your config. With `mcp.strict-descriptions: true`, flAPI refuses to start if +any tool description contains control characters, exceeds a length cap, or +matches known role-override phrases ("ignore previous instructions", +"system:", "you are now", and friends). To be precise about what this is: it +scans your YAML at config load time. It is a linter for tool descriptions — +a defense against compromised or carelessly copy-pasted configs — not a +runtime firewall inspecting live traffic. + +**Response shaping and per-tool budgets.** Because a model will happily ask +for everything, tools can cap and redact at the definition level: + +```yaml +mcp-tool: + name: customer_lookup + allowed-roles: [analyst, admin] + response: + max-rows: 1000 + redact-columns: [balance] + rate-limit: + enabled: true + max: 30 + interval: 60 +``` + +And any client can pass `"_dryRun": true` in the tool arguments to get the +rendered SQL and execution plan back *without executing the query* — same +validators, same role checks, no data movement. We use it to audit tools in +shadow mode before promoting them. + +**Caching that protects the warehouse.** The same YAML can attach a DuckLake +cache — full or incremental refresh on a schedule, with snapshot +time-travel: + +```yaml +cache: + enabled: true + table: customers_cache + schema: analytics + schedule: 5m + primary-key: [id] + cursor: + column: registration_date + type: date +``` + +An agent asking the same question forty different ways hits a local snapshot +instead of re-scanning BigQuery forty times. That is the difference between +"we gave the model warehouse access" being an interesting experiment and an +interesting invoice. + +## Honest limitations + +We would rather you hear these from us than from the comments. + +- **Read-oriented by design.** flAPI is for governed data access, not a + CRUD backend. Write endpoints exist but are not the point of the tool. +- **DuckDB-centric.** If DuckDB (plus its extensions) cannot reach your + source, neither can flAPI. +- **No stdio MCP transport.** Streamable HTTP only. Stdio-only clients need + a proxy like mcp-remote, which is an extra moving part. +- **Injection safety has a precise boundary.** Typed parameters are bound as + DuckDB prepared statements; raw `{{{ }}}` interpolation and untyped + parameters still rely on validators and template discipline. We wrote a + separate deep-dive on exactly where that line sits, because we think the + precision matters more than the headline. +- **Source-available, not open source.** The license is BSL 1.1: production + use is permitted, offering flAPI itself as a hosted service is not, and it + converts to MPL-2.0 over time. We know some readers stop here, and we would + rather say it plainly than bury it. +- **Young project.** The core paths are tested (C++ unit tests plus REST, + MCP, and cache integration suites), but you will find rough edges. Issues + welcome. + +The repo is at [github.com/DataZooDE/flapi](https://github.com/DataZooDE/flapi), +and everything above runs from the `examples/` directory it ships with: + +```bash +uvx --from flapi-io flapi -c examples/flapi.yaml +``` + +One SQL file. One YAML file. Both protocols. That's the whole idea. diff --git a/docs/promotion/drafts/blog-security-typed-params.md b/docs/promotion/drafts/blog-security-typed-params.md new file mode 100644 index 0000000..805e144 --- /dev/null +++ b/docs/promotion/drafts/blog-security-typed-params.md @@ -0,0 +1,276 @@ + + +# Making SQL injection structurally impossible — where we can prove it + +flAPI builds REST endpoints and MCP tools out of SQL templates. Users send +parameters over HTTP; those parameters end up in queries against DuckDB. +That sentence should make you nervous — it made us nervous, and we wrote the +thing. + +This post describes how we moved the hot path from "validated string +interpolation" to DuckDB prepared statements, and — more importantly — +exactly where the boundary of that guarantee sits. The claim we can defend +is narrower than the claim we would like to make, and we think the narrow +claim, stated precisely, is worth more than the broad one stated loosely. + +## Where we started: validators and template discipline + +A flAPI endpoint is a Mustache-templated SQL file plus a YAML definition. +The YAML declares each parameter with typed validators; the template +references parameters and the config chooses between raw interpolation +(`{{{ params.x }}}`) and HTML-escaped interpolation (`{{ params.x }}`): + +```yaml +request: + - field-name: id + field-in: query + validators: + - type: int + min: 1 + max: 1000000 +``` + +```sql +SELECT c_custkey AS key, c_name AS name, c_acctbal AS balance +FROM '{{{conn.path}}}' +WHERE 1=1 +{{#params.id}} + AND c_custkey = {{{ params.id }}} +{{/params.id}} +``` + +Historically, injection defense was two layers: the validator (an `id` that +isn't an integer in range never reaches the template) and template +discipline (documented rules about which brace form to use where). That is +a real defense — a typed, range-checked integer is hard to weaponize — but +it is a *policy* defense. Mustache knows nothing about SQL string literals, +quote-doubling, or comment syntax. Every validated value was still being +spliced into query text, and the safety argument was "the validator in +front of it is correct." We wanted a class of parameter for which the +safety argument is "the value never touches the SQL text at all." + +## The pipeline: classify, rewrite, bind + +The new path is three small components. None of them is clever, which is +the point. + +**1. The classifier** (`src/sql_parameter_classifier.cpp`, 56 lines) looks +at a request field's validators and decides whether the field is *bindable* +— whether it maps to a SQL type DuckDB can take as a prepared-statement +parameter: + +| Validator type | Bound as | +|---|---| +| `int`, `integer` | `BIGINT` (`duckdb_bind_int64`) | +| `number`, `float`, `double` | `DOUBLE` (`duckdb_bind_double`) | +| `boolean`, `bool` | `BOOLEAN` (`duckdb_bind_boolean`) | +| `date` | `DATE` (`duckdb_bind_date`) | +| `time` | `TIME` (`duckdb_bind_time`) | +| `uuid`, `string`, `email`, `enum` | `VARCHAR` (`duckdb_bind_varchar_length`) | + +A field with no typed validator is not bindable. The first recognized +validator type wins, deterministically. + +**2. The rewriter** (`src/prepared_template_rewriter.cpp`) scans the raw +template *before* Mustache renders it, and replaces qualifying `{{ params.x }}` +sites with `?` placeholders, recording a binding spec (field name, SQL +type, position) for each. A site qualifies only if all of these hold: + +- it is a double-brace tag of the exact shape `{{ params. }}`; +- `` is a declared request field; +- the classifier says that field is bindable; +- the site is *not* inside a `{{#section}}` or `{{^section}}` block. + +Everything else — triple-brace tags, `conn.*` and `cache.*` references, +undeclared names, section interiors — passes through byte-for-byte and is +rendered by Mustache exactly as before. Then Mustache renders the rewritten +template (expanding conditionals, connection properties, and any remaining +interpolation sites), and the result is SQL text containing `?` markers. + +**3. The executor** (`src/query_executor.cpp`) hands that text to +`duckdb_prepare`, converts each raw parameter string to its declared type, +binds it with the type-specific C-API call, and runs +`duckdb_execute_prepared`. A value that fails conversion — `id=abc` for an +integer binding — is rejected as a 400 before execution. An absent optional +parameter binds SQL `NULL`. + +For a bound site, injection is now a type error rather than a filtering +problem. There is no escaping step to get wrong because there is no +concatenation: `id` travels as an `int64_t` through +`duckdb_bind_int64(stmt, 1, v)`, and DuckDB's parser never sees it. The +statement's parse tree is fixed before the first user byte arrives. + +One detail we're fond of: varchar values bind through +`duckdb_bind_varchar_length`, the length-aware variant, so embedded NUL +bytes survive as part of the bound value. The C-string variant would +silently truncate `alice\0' OR 1=1` to `alice` — which sounds *safer* until +you notice it means the value that passed your length validator and the +value that reached the database were different values. Truncation +differences between validation and execution are how smuggling bugs happen. + +Endpoints whose parameters are all untyped produce an empty binding plan, +and the call collapses to the historic string path — the rollout changed +nothing for configs that don't opt in via typed validators. Pagination +reuses the same plan: the count query (`SELECT COUNT(*) FROM (…)`) executes +with the identical bindings, and the Arrow-streaming path binds the same way. + +## The boundary, precisely + +Here is the part this post exists for. Three kinds of template sites are +**not** bound, and fall back to the historical defense — typed validators +plus template discipline: + +**Triple-brace sites (`{{{ params.x }}}`).** Raw interpolation is an +explicit operator request, and existing templates quote these sites +(`'{{{ params.name }}}'`). Mechanically rewriting `'{{{ x }}}'` to `'?'` +would produce a string literal containing a question mark — not a +placeholder — so the rewriter leaves triple-brace sites alone. Migrating +one is a manual, two-character edit: drop the surrounding quotes, drop a +brace. + +**Untyped parameters.** If a field has no validator the classifier can map +to a SQL type, we don't guess. Guessing `VARCHAR` for everything would be +safe-ish but would silently change comparison semantics in existing +templates (`col = '42'` vs `col = 42`), and a security mechanism that +changes query results is a mechanism people turn off. + +**Sites inside `{{#section}}` / `{{^section}}` blocks.** This one is a +correctness constraint, not a policy choice. Placeholder positions are +assigned by scanning the template *before* Mustache expands conditionals. A +`?` inside `{{#params.id}}…{{/params.id}}` would vanish from the rendered +SQL whenever `id` is absent, and every placeholder after it would shift — +the binding list and the statement would silently disagree about positions. +So the rewriter treats section interiors as opaque. + +Why keep interpolation around at all? Because some template content is +structurally incapable of being a prepared-statement parameter. A parameter +can be a *value*; it cannot be an identifier, a table name, an `ORDER BY` +direction, or a conditionally-included clause. `FROM '{{{conn.path}}}'` and +`{{cache.table}}` change the shape of the statement, and no database's +prepare API accepts a placeholder there. Those sites are operator-controlled +rather than user-controlled — but the template language cannot enforce that +distinction, so we won't claim it does. + +The defensible claim, then: **at bound sites, injection is structurally +impossible — the user value never enters SQL text. At unbound sites, flAPI +provides validator-guarded interpolation, same as before.** Whether your +endpoint gets the strong property everywhere user data flows is a property +of your template. Which brings us to: + +## Writing templates so everything user-controlled is bound + +Four rules, one verification step. + +**1. Give every request field a typed validator.** This is what makes it +bindable — and `enum` counts, so constrained-vocabulary strings get bound +*and* whitelisted: + +```yaml +request: + - field-name: id + field-in: query + required: false + validators: + - type: int + min: 1 + max: 1000000 + - field-name: segment + field-in: query + required: false + validators: + - type: enum + allowedValues: [AUTOMOBILE, BUILDING, FURNITURE, HOUSEHOLD, MACHINERY] +``` + +**2. Reference parameters with double braces, unquoted.** The `?` carries +its own type; quotes would demote it to a literal. (If you do leave quotes +around a bound site, the mistake fails loudly — binding a parameter into a +statement that has none errors at execution — rather than silently +interpolating.) + +**3. Replace conditional sections around user values with NULL-tolerant +SQL.** Optional parameters bind `NULL` when absent, so the classic +optional-filter pattern works without Mustache conditionals: + +```sql +SELECT c_custkey AS key, c_name AS name, c_acctbal AS balance +FROM '{{{conn.path}}}' +WHERE 1=1 + AND ({{ params.id }} IS NULL OR c_custkey = {{ params.id }}) + AND ({{ params.segment }} IS NULL OR c_mktsegment = {{ params.segment }}) +``` + +renders (before binding) to: + +```sql +SELECT c_custkey AS key, c_name AS name, c_acctbal AS balance +FROM './data/customers.parquet' +WHERE 1=1 + AND (? IS NULL OR c_custkey = ?) + AND (? IS NULL OR c_mktsegment = ?) +``` + +with four bindings — `id` twice, `segment` twice, each converted once from +the same request value. Omit `id` and positions 1–2 bind `NULL`: +`(NULL IS NULL OR …)` short-circuits true and DuckDB's optimizer prunes the +branch. Keep `{{#sections}}` for genuinely structural variation (an +optional `ORDER BY`, a debug column) — just don't put user values inside +them and expect binding. + +**4. Reserve `{{{ }}}` for operator-controlled structure.** Connection +properties, cache table names — things defined in YAML you deploy, not +values that arrive in a request. + +**Verify with dry-run.** Any MCP client (or curl) can pass +`"_dryRun": true` in `tools/call` arguments; flAPI runs the full +validate → rewrite → render pipeline and returns the SQL it *would* execute, +without executing it: + +```json +{ + "dry_run": true, + "rendered_sql": "SELECT … WHERE 1=1 AND (? IS NULL OR c_custkey = ?) …", + "params": {"id": "42"} +} +``` + +If a user-controlled value appears in `rendered_sql` as text instead of a +`?`, that site is interpolated, not bound. This is the audit we run on our +own configs. + +## What this does not solve + +- **It is not a general SQL firewall.** Cache-refresh templates render on + the string path — their inputs are operator- and scheduler-supplied, not + request parameters, but they are interpolated. Same for `conn.*` and + `cache.*` expansion. +- **Validators still matter.** Unbound sites depend on them entirely, and + even bound sites want range checks — `duckdb_bind_int64` will happily + bind an integer your business logic considers absurd. +- **Binding does not authorize.** A perfectly-bound query can still read + data the caller shouldn't see; that's what per-tool RBAC and column + redaction are for, and they're a different post. +- **The strong property is per-site, not per-project.** We considered a + strict mode that refuses to start when any `params.*` site is unbound; + it doesn't exist yet. Until it does, the dry-run audit is manual. + +flAPI is a single static C++17 binary with DuckDB 1.5.3 embedded +(`uvx --from flapi-io flapi` or `pip install flapi-io`), source-available +under BSL 1.1 — production use permitted, converts to MPL-2.0. The three +components described here are +[`src/sql_parameter_classifier.cpp`](https://github.com/DataZooDE/flapi/blob/main/src/sql_parameter_classifier.cpp), +[`src/prepared_template_rewriter.cpp`](https://github.com/DataZooDE/flapi/blob/main/src/prepared_template_rewriter.cpp), +and +[`src/query_executor.cpp`](https://github.com/DataZooDE/flapi/blob/main/src/query_executor.cpp) +— the classifier and rewriter total about 250 lines, and the binding path +in the executor another hundred. We'd genuinely welcome adversarial review +of all three. diff --git a/docs/promotion/drafts/dbt-forum.md b/docs/promotion/drafts/dbt-forum.md new file mode 100644 index 0000000..54d83ae --- /dev/null +++ b/docs/promotion/drafts/dbt-forum.md @@ -0,0 +1,76 @@ + + +# Title + +Show and Tell: flAPI — REST + MCP endpoints for your warehouse from plain SQL files (no Python, no dbt project required, single binary) + +# Body + +Hi all — disclosure up front: I'm one of the authors of flAPI +(https://github.com/DataZooDE/flapi). + +Some of you will have seen MXCP announced here, which serves MCP tools +from dbt-adjacent SQL — it's good work, and if your team lives in dbt and +Python, it's a natural fit. flAPI sits in the same problem space with a +different set of trade-offs, and I think enough of you straddle both +worlds that it's worth showing. + +**The idea:** you write a SQL file (Mustache-templated) and a YAML config; +flAPI serves it as a REST endpoint *and* an MCP tool from the same +definition. It's a single static C++ binary with DuckDB embedded — no +Python environment, no orchestration, no project scaffolding. Anything +DuckDB reaches is fair game: Parquet/CSV, Postgres, BigQuery, S3/GCS/Azure, +Iceberg, Delta, and 50+ sources. For the many of you running dbt + DuckDB +locally, flAPI can sit directly on the artifacts dbt produces — dbt builds +the models, flAPI serves them. + +```yaml +url-path: /revenue-by-region/ +mcp-tool: + name: revenue_by_region + description: Monthly revenue aggregated by region +request: + - field-name: region + field-in: query + validators: + - type: enum + values: [emea, amer, apac] +template-source: revenue_by_region.sql +connection: [warehouse] +``` + +What you get in the box, as config rather than code: typed parameter +validation (typed params are bound as DuckDB prepared statements, not +string-interpolated — injection is structurally impossible for those +sites), Basic/JWT auth with per-tool role checks (fail-closed), rate +limiting, and DuckLake-based caching with scheduled full or incremental +refresh and snapshot time-travel — handy for keeping a hot endpoint from +re-scanning the warehouse per request. + +Deployment is deliberately boring: one binary, or `flapi pack` to fold +your whole config tree into the binary so `scp` is the deploy. Quickest +try: `uvx --from flapi-io flapi -c flapi.yaml` (PyPI package is +`flapi-io`). + +**Where flAPI is *not* the right choice, honestly:** + +- If your semantic layer and tests live in dbt and you want tools defined + *inside* that workflow, MXCP's dbt-native approach may serve you better. +- Read-oriented APIs only — no writes, no business logic. +- DuckDB-centric by design. +- MCP is over Streamable HTTP only (no stdio transport; stdio clients + need a proxy like mcp-remote). +- License is BSL 1.1 — source-available, not open source; production use + is permitted, reselling it as a hosted service is not; converts to + MPL-2.0 per version. +- It's a young project. + +Would love to hear how people here are exposing dbt-built models to +downstream apps and LLMs today — that feedback directly shapes what we +build next. diff --git a/docs/promotion/drafts/discord-duckdb.md b/docs/promotion/drafts/discord-duckdb.md new file mode 100644 index 0000000..c74ecf2 --- /dev/null +++ b/docs/promotion/drafts/discord-duckdb.md @@ -0,0 +1,48 @@ + + +# Message + +Hey all — I'm one of the authors of flAPI and wanted to share it here +since it's built entirely on DuckDB (1.5.3, statically embedded). It turns +a SQL file + a small YAML into a REST endpoint *and* an MCP tool from the +same config — so anything DuckDB can read (Parquet, Postgres, BigQuery, +S3, Iceberg, ...) becomes an API without writing a backend. Single C++ +binary, no runtime deps. + +A minimal endpoint is just this pair: + +```yaml +# sqls/customers.yaml +url-path: /customers/ +request: + - field-name: id + field-in: query + validators: + - type: int + min: 1 +template-source: customers.sql +connection: [customers-parquet] +``` + +```sql +-- sqls/customers.sql +SELECT * FROM '{{{conn.path}}}' +WHERE 1=1 +{{#params.id}} AND c_custkey = {{ params.id }} {{/params.id}} +``` + +Typed params like that `id` get bound as DuckDB prepared statements rather +than interpolated. Caching runs on DuckLake (full/incremental refresh, +snapshot time-travel), and there's an `embed://` DuckDB filesystem so +`flapi pack` can bake data files into the binary itself. Quickest try: +`uvx --from flapi-io flapi -c flapi.yaml`. Fair warning: it's read-oriented +APIs only, MCP is HTTP-transport only, license is BSL 1.1 +(source-available), and it's young — feedback and issues very welcome: +https://github.com/DataZooDE/flapi diff --git a/docs/promotion/drafts/hn-show-hn.md b/docs/promotion/drafts/hn-show-hn.md new file mode 100644 index 0000000..21f0f74 --- /dev/null +++ b/docs/promotion/drafts/hn-show-hn.md @@ -0,0 +1,203 @@ + + +# Show HN submission + +## Title + +``` +Show HN: flAPI – Turn one SQL file into a REST endpoint and an MCP tool +``` + +## URL + +``` +https://github.com/DataZooDE/flapi +``` + +--- + +## First comment (post immediately after submitting, from the author account) + +Author here. flAPI takes a SQL file (Mustache-templated) plus a small YAML +config and serves it as a REST endpoint and an MCP tool from the same +definition — one binary, no backend code. DuckDB 1.5.3 is embedded, so the +SQL can hit Parquet/CSV, Postgres, BigQuery, S3/GCS/Azure, Iceberg, Delta, +and the rest of DuckDB's 50+ source ecosystem. There's even an SAP ERP/BW +path via the ERPL extension, though that one still has documented stability +caveats. + +Quickest way to try it: + + uvx --from flapi-io flapi -c flapi.yaml + +(The package is `flapi-io` because `flapi` was taken on PyPI.) + +Some build decisions people here might find interesting: + +**Why C++ and a single static binary.** The target user is a data analyst +or a small data team, and the deploy target is often "a VM somewhere" with +no container registry and no Python environment anyone trusts. A statically +linked C++17 binary with DuckDB embedded means the entire runtime is one +file. No interpreter, no virtualenv, no shared-library roulette. It also +keeps the request path short: parameter validation, template rendering, and +query execution all happen in-process. + +**SQL injection: what we actually do.** Request parameters that carry a +typed validator (int, double, boolean, date, time, uuid, enum, email, +string) are not string-interpolated into the SQL at all — the template +reference is rewritten to a `?` placeholder and the value is bound through +a DuckDB prepared statement. For those sites, injection is structurally +impossible: the value never touches the SQL text. I want to be precise +about the boundary, because this is HN: untyped parameters, triple-brace +`{{{ }}}` raw interpolation, and values used inside Mustache sections still +rely on validators and template discipline. The honest claim is "typed +params are bind-parameters," not "the whole templating system is immune." + +**The self-packaging trick.** `flapi pack --in ./examples --out flapi-prod` +folds your whole config tree (YAML + SQL templates + small data files) into +the binary itself. On Linux/Windows it's the classic appended-ZIP: archive +after the executable image, located at startup by reverse-scanning for the +EOCD signature. macOS was the fun part — appended data breaks code signing, +so we reserve a 16 MiB `__FLAPI/__bundle` Mach-O segment at link time, +write the ZIP into it at pack time, and re-codesign, so the output stays +notarisable. Bundled data files are served to DuckDB through an `embed://` +filesystem, so `read_csv('embed://data/cities.csv')` reads from memory. +`SOURCE_DATE_EPOCH` makes the pack reproducible (byte-identical output), +and pack refuses `*.env`, `*.pem`, `*.key`, `secrets/*` so credentials +stay in the environment where they belong. + +**Honest limitations, so you don't have to dig for them:** + +- It's for read-oriented data APIs. It is not a CRUD backend and doesn't + want to be one. +- It's DuckDB-centric. If DuckDB can't reach your source, neither can we. +- MCP is Streamable HTTP only (`/mcp/jsonrpc`, SSE for streaming). No + stdio transport — stdio-only clients need a proxy like mcp-remote. +- License is Business Source License 1.1, not open source. Source- + available, production use permitted under the Additional Use Grant, + converts to MPL-2.0 after the Change Date. More below, since I know + this will come up. +- It's a young project. The core paths are tested, but you will find + rough edges. + +Happy to go deep on any of this — the prepared-statement rewriting and the +Mach-O segment work were the two most interesting rabbit holes. + +--- + +## Anticipated pushback — prepared Q&A + +Keep these ready; answer in your own words in the thread, don't paste +verbatim walls. + +### "Why not PostgREST?" + +PostgREST is excellent if your data lives in Postgres and you want your +API to mirror your schema. flAPI differs in three ways: (1) the source is +anything DuckDB can read — Parquet on S3, BigQuery, Iceberg, a CSV, and +Postgres too — not one database; (2) the API surface is a curated set of +SQL templates you write, not a reflection of the schema, which matters when +you want a governed, deliberately narrow interface; (3) every endpoint is +also an MCP tool from the same config, with per-tool RBAC. If you're all-in +on Postgres and want schema-driven CRUD, PostgREST is the better tool. + +### "Why not just FastAPI / Flask?" + +You absolutely can, and for anything with real business logic you should. +flAPI's bet is that a large class of data APIs is "validated params → one +SQL query → JSON," and for that class the Python service is mostly +boilerplate you now own: validation, auth, rate limiting, caching, OpenAPI +docs, deployment. Here that's YAML config around a SQL file, plus things +you'd rarely build yourself (DuckLake snapshot caching with time-travel, +prepared-statement param binding, an MCP server). If your endpoint needs a +for-loop, use FastAPI. + +### "Why not Datasette?" + +Huge respect for Datasette — it pioneered a lot of this space. Different +goals though: Datasette is exploration-first (browse, facet, ad-hoc SQL +over SQLite), flAPI is contract-first (you define exactly which +parameterised queries are exposed, with typed validation, RBAC, and +caching in front). Also DuckDB vs SQLite as the engine, which changes +which sources you can federate. If you want to *explore* data, Datasette; +if you want to *publish a stable API* over it, that's our lane. + +### "Why not ROAPI?" + +Closest philosophical neighbour — also "config in, API out," also +single-binary, and it's Rust + Arrow/DataFusion, which is a great stack. +Differences as I see them: flAPI endpoints are arbitrary templated SQL you +author (not auto-generated query interfaces over registered tables), MCP +tools with per-tool RBAC come from the same config, DuckLake gives +scheduled full/incremental cache refresh with snapshot time-travel, and +`flapi pack` embeds the config into the binary. ROAPI is Apache-2.0, which +is a genuine point in its favour if license is your deciding factor. + +### "Why not MXCP?" + +MXCP is the closest competitor on the MCP side and it's good work. It's +Python and dbt-centric — if your team lives in dbt, that's a feature. flAPI +is one static binary with no Python runtime, serves REST and MCP from the +same file, and doesn't require dbt or any project scaffolding beyond YAML + +SQL. Different teams will reasonably pick differently. + +### "BSL is not open source. Why should I care about a source-available tool?" + +You're right, and we don't call it open source. The specifics: BSL 1.1, +each version converts to MPL-2.0 five years after publication, and the +Additional Use Grant permits production use — you can run flAPI in prod, +internally, for your APIs, today, free. What it restricts is offering +flAPI itself to third parties as a hosted service or embedding it in a +product you sell; that requires a commercial license. + +Why we chose it, non-defensively: we're a small company (DataZoo GmbH) and +we want the code readable, patchable, and self-hostable while keeping +"cloud vendor rehosts it" off the table long enough to build a business +that funds development. That's a trade-off, not a virtue, and it's +reasonable to reject it — if BSL is a hard no for you, ROAPI (Apache-2.0) +or PostgREST (MIT) are genuinely good alternatives and I'll say so in the +thread. The delayed-open guarantee is the part we can offer honestly: +every version has a date at which it becomes MPL-2.0, no matter what +happens to us. + +### "Isn't templating SQL with Mustache just asking for injection?" + +For typed parameters, no template interpolation happens at all — the +reference becomes a `?` and the value is bound via a DuckDB prepared +statement, so it can't change the query structure. For everything else +(untyped params, raw `{{{ }}}` sites, section-controlled fragments), yes, +that's string templating and it relies on the validator layer (regex, +range, enum whitelists) plus template discipline. We document that boundary +rather than hand-wave it. The recommended pattern is: type every user- +facing parameter, and reserve raw interpolation for trusted values like +connection properties. + +### "MCP over HTTP only? My client is stdio." + +Correct — Streamable HTTP at `/mcp/jsonrpc` with SSE for streaming, no +stdio transport today. flAPI is a long-running server, so HTTP is the +natural fit, but stdio-only clients work through a proxy like mcp-remote. + +### "What does the prompt-injection scanner actually do?" + +Less than the name might suggest, deliberately. It scans MCP tool +descriptions in your YAML config at load time and flags patterns that look +like embedded prompt-injection attempts — hygiene against a poisoned or +carelessly copy-pasted config. It does not scan live traffic or model +output. Think "linter for tool descriptions," not "runtime firewall." + +### "1000 req/sec on single-threaded DuckDB? / performance claims" + +Don't lead with numbers; if pressed: typical simple cached GETs are in the +tens of milliseconds end-to-end, DuckDB parallelism is configurable, and +the DuckLake cache exists precisely so hot endpoints don't re-hit slow +sources. Offer to share reproducible benchmarks rather than asserting +throughput figures in-thread. diff --git a/docs/promotion/drafts/newsletter-pitch.md b/docs/promotion/drafts/newsletter-pitch.md new file mode 100644 index 0000000..39e1fb7 --- /dev/null +++ b/docs/promotion/drafts/newsletter-pitch.md @@ -0,0 +1,42 @@ + + +# Subject line + +One YAML file = a REST endpoint + a governed MCP tool (SQL in, RBAC included) + +# Email body (~140 words) + +Hi [name], + +I'm one of the authors of flAPI — pitching it for [newsletter]'s tools +roundup because it hits the MCP-governance gap your readers keep asking +about: everyone can spin up an MCP server, almost nobody ships one with +access control. + +flAPI turns a SQL file + YAML config into a REST endpoint **and** an MCP +tool from the same definition. Single static binary (C++, DuckDB +embedded), try it with `uvx --from flapi-io flapi`. + +Three concrete facts: + +- **Per-tool RBAC, fail-closed** — roles from JWT/OIDC or Basic auth; a + tool with no allowed-roles under auth is denied by default. +- **Typed params are bound as DuckDB prepared statements** — injection is + structurally impossible for those sites, not merely sanitized. +- **One engine, 50+ sources** — Parquet, Postgres, BigQuery, S3, Iceberg. + +Honest caveats: read-only APIs, MCP over HTTP (no stdio), BSL 1.1 +source-available license. + +Repo: https://github.com/DataZooDE/flapi — 60-sec demo: [GIF link]. +Happy to do a deeper write-up if useful. + +[signature] diff --git a/docs/promotion/drafts/reddit-r-dataengineering.md b/docs/promotion/drafts/reddit-r-dataengineering.md new file mode 100644 index 0000000..7432b73 --- /dev/null +++ b/docs/promotion/drafts/reddit-r-dataengineering.md @@ -0,0 +1,88 @@ + + +# Title + +How we built a governed read-only data API layer without a backend service — architecture teardown (SQL templates → prepared statements, DuckLake caching) + +# Body + +Disclosure: I'm one of the authors of the tool described (flAPI). This is +meant as an engineering write-up, not an ad — the design decisions apply +even if you build your own. + +**The problem shape.** A large share of "we need an API for this data" +tickets are: validated parameters → one SQL query → JSON. The conventional +answer is a FastAPI/Flask service per team, and each one re-implements +validation, auth, rate limiting, caching, and OpenAPI docs around what is +ultimately a SELECT statement. We wanted that class of endpoint to be +config, not a service. + +**Architecture.** A single C++17 binary with DuckDB 1.5.3 embedded. An +endpoint is two files: a YAML config (URL path, typed parameters with +validators, auth, rate limit, cache policy) and a Mustache-templated SQL +file. The request path is: parameter extraction → validator chain → +template render → DuckDB execution → JSON. Because DuckDB is the engine, +one endpoint can federate Parquet on S3, Postgres, BigQuery, Iceberg, +Delta, etc. The same YAML can also expose the query as an MCP tool for +LLM clients, with per-tool role checks (fail-closed: under auth, a tool +with no allowed-roles is denied). + +**The injection question, scoped precisely.** Templated SQL rightly makes +people nervous, so here's exactly what we do. Parameters that carry a +typed validator (int, double, boolean, date, time, uuid, enum, email, +string) are *not* interpolated: the `{{ params.x }}` reference is +rewritten to a `?` placeholder and the value is bound through a DuckDB +prepared statement. For those sites, injection is structurally impossible +— the value never enters the SQL text. The honest boundary: untyped +parameters, raw triple-brace `{{{ }}}` sites, and Mustache +section-controlled fragments are still string templating and rely on the +validator layer (regex/range/enum whitelists) plus authoring discipline. +The mitigation is cultural + mechanical: type every user-facing param, +reserve raw interpolation for trusted values like connection properties. + +**Caching via DuckLake.** Endpoint caches are DuckLake tables (snapshot- +based). YAML infers the sync mode: no keys → full refresh (CTAS); a +cursor column → incremental append; cursor + primary key → incremental +merge (upsert). Templates get `{{cache.previousSnapshotTimestamp}}` etc., +so an incremental refresh is just `WHERE updated_at > TIMESTAMP +'{{cache.previousSnapshotTimestamp}}'`. Snapshots give you time-travel and +an audit trail; refreshes run on schedule or manual trigger, never on GET. +The practical win: a dashboard endpoint over a slow BigQuery query serves +from a local snapshot instead of costing you a warehouse scan per request. + +**Deployment.** Single static binary; optionally `flapi pack` folds the +whole config tree into the binary itself (appended ZIP; reserved Mach-O +segment on macOS so signing survives), reproducible via +`SOURCE_DATE_EPOCH`, with a deny-list so secrets can't be packed. For a +lot of environments, "scp one file" beats a container pipeline. + +**Where this approach loses.** Being upfront: + +- Read-oriented only. No writes, no transactions, no business logic. If + the endpoint needs a loop or a second system call, write a real service. +- DuckDB-centric. Sources DuckDB can't reach are out of scope. (There's + an SAP ERP/BW path via the ERPL extension with a working example, but + that extension has documented stability caveats.) +- The template layer's safety outside typed params is validator-dependent + — it's defense-in-depth, not a proof. +- MCP transport is HTTP-only (no stdio). +- License is BSL 1.1 — source-available, not open source. Production use + permitted; offering it as a hosted service is not. Converts to MPL-2.0 + per version after five years. If that's disqualifying for you, fair; + ROAPI and PostgREST are good open-source neighbours. +- Young project. + +Repo, if useful: https://github.com/DataZooDE/flapi — quickest trial is +`uvx --from flapi-io flapi -c flapi.yaml`. + +Genuinely interested in this sub's take on two things: (1) where you draw +the line between "config-defined endpoint" and "just write the service," +and (2) whether anyone has run DuckLake incremental merge at meaningful +scale — our experience is mostly mid-size tables. diff --git a/docs/promotion/drafts/reddit-r-duckdb.md b/docs/promotion/drafts/reddit-r-duckdb.md new file mode 100644 index 0000000..40f92e6 --- /dev/null +++ b/docs/promotion/drafts/reddit-r-duckdb.md @@ -0,0 +1,79 @@ + + +# Title + +I built a REST + MCP server on top of DuckDB — single static binary, SQL templates in, API out + +# Body + +Disclosure: I'm one of the authors. + +If you use DuckDB, you already know the trick: one engine, 50+ sources — +Parquet/CSV, Postgres, BigQuery, S3/GCS/Azure, Iceberg, Delta. flAPI puts +an HTTP server in front of that. You write a Mustache-templated SQL file +plus a small YAML config, and it becomes a REST endpoint and an MCP tool +(same definition, both servers run concurrently). No Python, no glue code. + +```sql +-- customers.sql +SELECT * FROM '{{{conn.path}}}' +WHERE 1=1 +{{#params.id}} + AND c_custkey = {{ params.id }} +{{/params.id}} +``` + +DuckDB-specific bits this sub might care about: + +- **Embedded DuckDB 1.5.3**, statically linked into a single C++17 binary. + Extensions (postgres_scanner, httpfs, bigquery, iceberg, ...) load per + connection config. +- **Typed params → prepared statements.** Request parameters with typed + validators are rewritten to `?` placeholders and bound via DuckDB + prepared statements — no string interpolation for those sites, so + injection is structurally impossible there. (Raw `{{{ }}}` sites and + untyped params still rely on validators + template discipline.) +- **DuckLake-backed caching.** Endpoint results materialize into a + DuckLake catalog with scheduled full or incremental refresh (cursor + and/or primary-key driven merge) and snapshot time-travel. Your endpoint + SQL reads `{{cache.catalog}}.{{cache.schema}}.{{cache.table}}` instead + of re-hitting BigQuery on every request. +- **`embed://` filesystem.** `flapi pack` can fold your whole config tree + (YAML + SQL + small data files) into the binary; a custom DuckDB + filesystem serves the bundle from memory, so + `read_csv('embed://data/cities.csv')` just works. `scp` the binary, + that's the deploy. +- There's also an SAP ERP/BW path via the ERPL extension (working example + in the repo), though that extension has documented stability caveats, + so I won't oversell it. + +Try it: + +``` +uvx --from flapi-io flapi -c flapi.yaml +``` + +(`flapi` was taken on PyPI, hence `flapi-io`. Prebuilt binaries for Linux +x86_64/ARM64, macOS ARM64, Windows, plus Docker.) + +**Limitations:** + +- Read-oriented data APIs — not a CRUD backend. +- MCP is over Streamable HTTP only (no stdio transport; stdio clients + need a proxy like mcp-remote). +- License is BSL 1.1 — source-available, not open source. Production use + is permitted; reselling flAPI as a hosted service isn't. Each version + converts to MPL-2.0. +- Young project; the DuckDB core paths are solid, the edges less so. + +Repo: https://github.com/DataZooDE/flapi + +Would genuinely love feedback from people who've pushed DuckDB extensions +or DuckLake harder than we have — especially around incremental merge +patterns. diff --git a/docs/promotion/drafts/reddit-r-mcp.md b/docs/promotion/drafts/reddit-r-mcp.md new file mode 100644 index 0000000..99fe2d6 --- /dev/null +++ b/docs/promotion/drafts/reddit-r-mcp.md @@ -0,0 +1,90 @@ + + +# Title + +I built a server that turns any SQL query into an MCP tool — with per-tool RBAC + +# Body + +Disclosure: I'm one of the authors. + +The pattern I kept seeing: teams want to give an LLM access to warehouse +data, so someone writes a bespoke Python MCP server per use case — and now +you own auth, validation, rate limiting, and a deployment for each one. + +flAPI's approach: you write a SQL file (Mustache-templated) and a YAML +config, and you get an MCP tool *and* a REST endpoint from the same +definition. DuckDB is embedded, so the SQL can query Parquet/CSV, Postgres, +BigQuery, S3/GCS/Azure, Iceberg, Delta — anything in DuckDB's 50+ source +ecosystem. + +```yaml +mcp-tool: + name: get_customers + description: Retrieve customer information by ID +request: + - field-name: id + field-in: query + validators: + - type: int + min: 1 +template-source: customers.sql +connection: [customers-parquet] +``` + +The governance bits, which I think matter more for MCP than for REST +because the caller is a model: + +- **Per-tool RBAC, fail-closed.** Tools carry `allowed-roles`; roles come + from Basic auth users or JWT/OIDC claims. Under auth, a tool with no + allowed-roles is denied — not silently allowed. +- **Typed parameters become prepared statements.** Params with typed + validators (int, date, uuid, enum, ...) are rewritten to `?` placeholders + and bound as DuckDB prepared statements, so injection is structurally + impossible for those sites. (Untyped params and raw `{{{ }}}` + interpolation still rely on validators + template discipline — I want to + scope that claim honestly.) +- **Tool-description hygiene scanner.** At config load time it scans your + MCP tool descriptions for prompt-injection patterns — protection against + poisoned/copy-pasted configs. To be clear, it does *not* scan live + traffic; it's a linter for tool descriptions, not a runtime firewall. +- Response shaping per tool (max-rows, redact-columns, sampling), per-tool + rate limits, and a dry-run mode. + +Caching is DuckLake-based (full or incremental refresh, snapshot +time-travel), so a tool over a slow BigQuery query can serve from a local +snapshot instead of hammering the warehouse on every model call. + +Try it (single static binary, C++, DuckDB embedded): + +``` +uvx --from flapi-io flapi -c flapi.yaml +``` + +(Package is `flapi-io` — `flapi` was taken on PyPI. Binaries for +Linux x86_64/ARM64, macOS ARM64, Windows, and Docker also exist.) + +**Limitations, honestly:** + +- MCP transport is Streamable HTTP only (`/mcp/jsonrpc`, SSE for + streaming). **No stdio** — Claude Desktop and other stdio-only clients + need a proxy like mcp-remote. +- Read-oriented data tools; this is not a general CRUD/action backend. +- DuckDB-centric: if DuckDB can't reach your source, neither can flAPI. +- License is BSL 1.1 (source-available, not open source). Production use + is permitted; offering flAPI itself as a hosted service isn't. Converts + to MPL-2.0 over time. +- Young project — expect rough edges, file issues. + +Repo: https://github.com/DataZooDE/flapi + +Happy to answer anything about the RBAC model or the prepared-statement +binding — those were the two design areas where "the caller is an LLM" +changed our decisions the most. diff --git a/docs/promotion/drafts/reddit-r-opensource.md b/docs/promotion/drafts/reddit-r-opensource.md new file mode 100644 index 0000000..34916d0 --- /dev/null +++ b/docs/promotion/drafts/reddit-r-opensource.md @@ -0,0 +1,76 @@ + + +# Recommendation: skip r/opensource + +The playbook (§3) lists r/opensource as low-risk, but that assessment +doesn't survive contact with the license. flAPI is **BSL 1.1 — +source-available, not open source** by the OSI definition, and +r/opensource exists specifically around that definition. A launch post +there would either (a) misrepresent the license and get flamed and +screenshotted, or (b) honestly disclose BSL and be off-topic by the sub's +own standards — the top comment writes itself: "this is not open source, +why is it here?" Either outcome burns goodwill that the other channels +depend on, and BSL projects (HashiCorp, et al.) are a recurring negative +flashpoint in that community. There is no version of this post that wins. + +**What to do instead:** + +- Let r/selfhosted carry the "self-hostable, source-available, honest + about it" story — that draft (reddit-r-selfhosted.md) discloses BSL in + the title and first paragraph and that community tolerates + source-available when it's not disguised. +- Handle the license question proactively in the Show HN first comment + and Q&A (already drafted). +- If flAPI's license situation ever changes (e.g., a core component goes + MPL/Apache early, or a version crosses its MPL-2.0 change date and is + maintained as such), *that* is a legitimate r/opensource post. + +--- + +# Fallback draft (only if someone insists on posting anyway) + +Post it as what it is — a source-available project — and expect a rough +ride. Better targets for this exact text: Lemmy selfhosted/technology +communities (playbook §7), or r/coolgithubprojects. + +## Title + +flAPI — source-available (BSL 1.1, converts to MPL-2.0) SQL-to-API server: one SQL file becomes a REST endpoint and an MCP tool + +## Body + +Disclosure: I'm one of the authors. And to be maximally clear up front: +**this is not open source.** flAPI is under the Business Source License +1.1. The source is public, you can modify and self-host it, and production +use is permitted under the Additional Use Grant; what's restricted is +offering flAPI itself to third parties as a hosted service or embedding it +in a commercial product. Each version automatically converts to MPL-2.0 +five years after publication. If source-available doesn't meet your bar, +I understand — PostgREST (MIT) and ROAPI (Apache-2.0) are genuinely good +tools in the same space. + +What it does: a Mustache-templated SQL file plus a YAML config becomes a +REST endpoint and an MCP tool from the same definition. Single static +C++17 binary with DuckDB embedded — so the SQL can query Parquet/CSV, +Postgres, BigQuery, S3/GCS/Azure, Iceberg, Delta, and 50+ other sources. +Typed request parameters are bound as DuckDB prepared statements (no +string interpolation for those sites); auth with per-tool RBAC, rate +limiting, and DuckLake-based caching are configuration, not code. +`flapi pack` can fold the whole config tree into the binary so a deploy +is one `scp`. + +Try: `uvx --from flapi-io flapi -c flapi.yaml` +(PyPI package is `flapi-io`; `flapi` was taken.) + +Limitations: read-oriented data APIs only (not a CRUD backend); +DuckDB-centric; MCP over HTTP only (no stdio — use a proxy like +mcp-remote for stdio clients); BSL 1.1 as above; young project. + +Repo: https://github.com/DataZooDE/flapi diff --git a/docs/promotion/drafts/reddit-r-selfhosted.md b/docs/promotion/drafts/reddit-r-selfhosted.md new file mode 100644 index 0000000..23d5b65 --- /dev/null +++ b/docs/promotion/drafts/reddit-r-selfhosted.md @@ -0,0 +1,77 @@ + + +# Title + +flAPI — a single static binary that turns SQL files into a REST API (config can be baked into the binary, scp is the deploy). Heads-up: BSL 1.1, source-available, not open source + +# Body + +Disclosure: I'm one of the authors. And before anything else, because this +sub rightly cares: **flAPI is licensed under the Business Source License +1.1.** That is source-available, *not* open source. You can read, modify, +and self-host it, and production use is permitted under the Additional Use +Grant — what's restricted is offering flAPI itself to third parties as a +hosted service or embedding it in a product you sell. Each version +converts to MPL-2.0 five years after publication. If BSL is a dealbreaker +for you, that's a completely fair position and I'd point you at ROAPI +(Apache-2.0) or PostgREST (MIT) instead. + +Still here? The self-hosting story is the part I think you'll like. + +**What it is:** you write a SQL file (Mustache-templated) and a YAML +config; flAPI serves it as a REST endpoint (and an MCP tool for AI +clients, from the same config). DuckDB is embedded, so the SQL can read +Parquet/CSV on disk, Postgres, S3/GCS/Azure, BigQuery, Iceberg, Delta — +50+ sources. Auth (Basic/JWT with roles), validation, rate limiting, and +caching are config, not code. + +**Why it fits this sub:** + +- **One static C++ binary.** No runtime, no interpreter, no node_modules, + no Python venv. Linux x86_64/ARM64, macOS ARM64, Windows. Docker image + exists if you prefer, but it's genuinely optional. +- **`flapi pack` bakes the config in.** `flapi pack --in ./myconfig --out + flapi-prod` appends your entire config tree (YAML + SQL + small data + files) as a ZIP after the executable (on macOS it goes into a reserved + Mach-O segment and gets re-codesigned, so it stays notarisable). The + result is one file. `scp flapi-prod user@host` *is* the deployment. +- **Secrets can't end up in the artifact.** Pack refuses `*.env`, + `*.pem`, `*.key`, `secrets/*` by default; credentials come from + environment variables at runtime. +- **Reproducible.** Set `SOURCE_DATE_EPOCH` and packing is byte-identical + across runs — you can diff your deploy artifacts. +- **Telemetry disclosure:** it sends anonymous start/stop events by + default. Opt out with `--no-telemetry`, `FLAPI_NO_TELEMETRY=1`, or in + the YAML. I know how this sub feels about telemetry, so: it's + documented, it's one flag, and no query data or credentials are ever + sent. + +Quick try without downloading anything: + +``` +uvx --from flapi-io flapi -c flapi.yaml +``` + +(PyPI package is `flapi-io` because `flapi` was taken.) + +**Limitations, honestly:** + +- Read-oriented data APIs. It will not be your app's CRUD backend. +- DuckDB-centric — great if your data is files/warehouses, wrong tool if + you need MySQL-triggers-and-transactions territory. +- MCP is HTTP-only (no stdio transport). +- BSL 1.1, as covered up top. +- Young project; expect rough edges and file issues when you hit them. + +Repo: https://github.com/DataZooDE/flapi + +Happy to answer questions about the pack/bundle mechanics or resource +footprint on small boxes. diff --git a/server.json b/server.json new file mode 100644 index 0000000..615ddd4 --- /dev/null +++ b/server.json @@ -0,0 +1,42 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-09-29/server.schema.json", + "name": "io.github.datazoode/flapi", + "description": "Turns SQL templates + YAML into REST endpoints and MCP tools from one config. Single binary with embedded DuckDB (Parquet, Postgres, BigQuery, S3 and 50+ sources), per-tool RBAC, typed params bound as prepared statements, DuckLake caching.", + "repository": { + "url": "https://github.com/DataZooDE/flapi", + "source": "github" + }, + "version": "26.07.13", + "websiteUrl": "https://github.com/DataZooDE/flapi", + "packages": [ + { + "registryType": "pypi", + "registryBaseUrl": "https://pypi.org", + "identifier": "flapi-io", + "version": "26.07.13", + "runtimeHint": "uvx", + "transport": { + "type": "streamable-http", + "url": "http://localhost:8080/mcp/jsonrpc" + }, + "packageArguments": [ + { + "type": "named", + "name": "-c", + "description": "Path to the flapi.yaml configuration file defining connections, endpoints and MCP tools", + "format": "string", + "isRequired": true, + "valueHint": "flapi.yaml" + } + ], + "environmentVariables": [ + { + "name": "FLAPI_PORT", + "description": "HTTP port for the unified REST + MCP server (default 8080)", + "isRequired": false, + "format": "number" + } + ] + } + ] +}