@genlobe/mcp-server 3.6.2 → 3.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +787 -0
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -3497,6 +3497,741 @@ has a different shape because of where the API key can safely live.`,
3497
3497
  const body = sections[appType] ?? sections["unsure"];
3498
3498
  return `# Stack recommendation\n\n${keyLine}\n\n${body}\n\n---\n\nRun \`get_security_guide\` for the full security cheat-sheet that matches your key type.`;
3499
3499
  }
3500
+ const ENTITY_SCHEMA_RECIPES = {
3501
+ product: {
3502
+ slug: "product",
3503
+ name: "Product",
3504
+ description: "Catalog product with price, stock, category, description.",
3505
+ fields_definition: {
3506
+ name: { type: "string", required: true, max_length: 200 },
3507
+ price: { type: "float", required: true, min: 0 },
3508
+ stock: { type: "integer", required: true, min: 0, default: 0 },
3509
+ currency: { type: "string", required: false, default: "USD", max_length: 3 },
3510
+ category: { type: "string", required: false, max_length: 100 },
3511
+ description: { type: "text", required: false },
3512
+ is_active: { type: "boolean", required: false, default: true },
3513
+ },
3514
+ bulk_seed_example: [
3515
+ { name: "Sugar 1kg", price: 2.5, stock: 100, category: "groceries" },
3516
+ { name: "Rice 1kg", price: 3.0, stock: 50, category: "groceries" },
3517
+ { name: "Coffee 250g", price: 6.75, stock: 30, category: "groceries" },
3518
+ ],
3519
+ notes: "If you need product variants (size/color), keep `name` for the base product and add a separate `variant` entity with a `product_id` reference (type=reference, target_schema_slug='product').",
3520
+ },
3521
+ customer: {
3522
+ slug: "customer",
3523
+ name: "Customer",
3524
+ description: "Store-side customer record (NOT a Genlobe end-user — those go through /v1/auth/register). Use this when you want to track purchases without forcing email signup.",
3525
+ fields_definition: {
3526
+ name: { type: "string", required: true, max_length: 200 },
3527
+ phone: { type: "string", required: false, max_length: 50 },
3528
+ email: { type: "email", required: false, max_length: 200 },
3529
+ notes: { type: "text", required: false },
3530
+ tags: { type: "text", required: false },
3531
+ },
3532
+ bulk_seed_example: [
3533
+ { name: "Ana Pérez", phone: "+1-809-555-0100" },
3534
+ { name: "Walk-in", notes: "no-data anonymous buyer" },
3535
+ ],
3536
+ notes: "Common mistake: registering each store customer as a Genlobe end-user. Don't — that requires real email + SES delivery. Keep store customers as this custom entity unless they need to log into the storefront. `tags` is a comma-separated string (the schema validator does NOT support `array` as a field type — see the global notes at the top of every recipe).",
3537
+ },
3538
+ order: {
3539
+ slug: "order",
3540
+ name: "Order / Sale",
3541
+ description: "A completed sale with line items (price + name snapshotted at the time of sale, so editing a product later does NOT mutate past orders).",
3542
+ fields_definition: {
3543
+ customer_id: {
3544
+ type: "reference",
3545
+ target_schema_slug: "customer",
3546
+ required: false,
3547
+ on_delete: "set_null",
3548
+ },
3549
+ items_json: {
3550
+ type: "text",
3551
+ required: true,
3552
+ },
3553
+ total: { type: "float", required: true, min: 0 },
3554
+ currency: { type: "string", required: false, default: "USD" },
3555
+ paid: { type: "boolean", required: true, default: false },
3556
+ paid_at: { type: "datetime", required: false },
3557
+ notes: { type: "text", required: false },
3558
+ },
3559
+ notes: "**Line items modeling — important.** Genlobe's schema validator does NOT support `array` as a field type (validated types: string, integer, float, boolean, datetime, text, enum, email, url, phone, reference). Two ways to model line items:\n\n**Option A — simple, recommended for MVP**: store the items as a JSON string in `items_json` (type=text). Parse client-side. Each item should already include `product_id`, `name_snapshot`, `price_snapshot`, `qty`. Trade-off: you cannot search/filter by items via `POST /v1/entity/records/search` — items are opaque to the engine.\n\n**Option B — queryable, for production**: create a separate `order_item` schema with `order_id: reference->order`, `product_id: reference->product`, `name_snapshot`, `price_snapshot`, `qty`. Cross-schema joins (ADR-0009) make `\"top 5 products sold this month\"` a single search call.\n\nEither way: **always snapshot `name` and `price` at write time** so editing the product later does NOT mutate past orders.",
3560
+ },
3561
+ post: {
3562
+ slug: "post",
3563
+ name: "Blog post / Article",
3564
+ description: "Authored content with title, body, status, tags.",
3565
+ fields_definition: {
3566
+ title: { type: "string", required: true, max_length: 200 },
3567
+ slug: { type: "string", required: true, max_length: 200 },
3568
+ body: { type: "string", required: true },
3569
+ status: {
3570
+ type: "string",
3571
+ required: true,
3572
+ enum: ["draft", "published", "archived"],
3573
+ default: "draft",
3574
+ },
3575
+ tags: { type: "text", required: false },
3576
+ published_at: { type: "datetime", required: false },
3577
+ cover_image_url: { type: "string", required: false },
3578
+ },
3579
+ notes: "`created_by_id` is the author (auto-stamped by the API from the JWT, no need to include in records). For multi-author orgs the elevated-role bypass on /v1/entity/records/search lets editors see everyone's posts.",
3580
+ },
3581
+ comment: {
3582
+ slug: "comment",
3583
+ name: "Comment",
3584
+ description: "User-authored comment that points at another record (post, product, order, anything).",
3585
+ fields_definition: {
3586
+ target_id: {
3587
+ type: "string",
3588
+ required: true,
3589
+ notes: "UUID of the commented record. Not a reference field because the target schema varies — store the target schema slug in `target_schema` if you need polymorphism.",
3590
+ },
3591
+ target_schema: { type: "string", required: true, max_length: 100 },
3592
+ body: { type: "string", required: true },
3593
+ is_deleted: { type: "boolean", required: false, default: false },
3594
+ },
3595
+ notes: "Polymorphic targets defeat the ADR-0009 `reference` validator. If your comments only ever point at ONE schema (e.g. `post`), drop `target_schema` and convert `target_id` to a proper reference for FK integrity.",
3596
+ },
3597
+ message: {
3598
+ slug: "message",
3599
+ name: "Chat / direct message",
3600
+ description: "A single message in a conversation. Pair with the native `conversations` table (Genlobe AI agents) or roll your own thread entity.",
3601
+ fields_definition: {
3602
+ conversation_id: { type: "string", required: true },
3603
+ sender_user_id: { type: "string", required: false },
3604
+ sender_role: {
3605
+ type: "string",
3606
+ required: true,
3607
+ enum: ["user", "assistant", "system"],
3608
+ },
3609
+ body: { type: "string", required: true },
3610
+ read_at: { type: "datetime", required: false },
3611
+ },
3612
+ notes: "If you're building a chatbot powered by a Genlobe agent, you do NOT need this entity — agent conversations are persisted automatically (see `/v1/user/agents/{id}/conversations`). Use this only for human-to-human messaging.",
3613
+ },
3614
+ task: {
3615
+ slug: "task",
3616
+ name: "Task / To-do",
3617
+ description: "Single task with title, status, due date, assignee.",
3618
+ fields_definition: {
3619
+ title: { type: "string", required: true, max_length: 200 },
3620
+ description: { type: "string", required: false },
3621
+ status: {
3622
+ type: "string",
3623
+ required: true,
3624
+ enum: ["pending", "in_progress", "completed", "cancelled"],
3625
+ default: "pending",
3626
+ },
3627
+ priority: {
3628
+ type: "string",
3629
+ required: false,
3630
+ enum: ["low", "medium", "high"],
3631
+ default: "medium",
3632
+ },
3633
+ assigned_to_id: {
3634
+ type: "reference",
3635
+ target_schema_slug: "users",
3636
+ required: false,
3637
+ on_delete: "set_null",
3638
+ },
3639
+ due_date: { type: "datetime", required: false },
3640
+ },
3641
+ notes: "`assigned_to_id` references the native `users` table (Genlobe end-users), not a custom entity. The reference validator enforces `OrganizationMember` active membership server-side.",
3642
+ },
3643
+ note: {
3644
+ slug: "note",
3645
+ name: "Personal note",
3646
+ description: "A free-text note owned by the creator. Row-level scope means each end-user only sees their own notes via /v1/entity/records/mine.",
3647
+ fields_definition: {
3648
+ title: { type: "string", required: false, max_length: 200 },
3649
+ body: { type: "string", required: true },
3650
+ tags: { type: "text", required: false },
3651
+ is_pinned: { type: "boolean", required: false, default: false },
3652
+ },
3653
+ notes: "Perfect for ADR-0007 row-level scope: regular end-users see only their own notes via `GET /v1/entity/records/mine` (no extra filter needed in the frontend).",
3654
+ },
3655
+ };
3656
+ function ENTITY_SCHEMA_RECIPE(entityType) {
3657
+ const recipe = ENTITY_SCHEMA_RECIPES[entityType];
3658
+ if (!recipe) {
3659
+ return `# Unknown entity_type "${entityType}"\n\nAvailable: ${Object.keys(ENTITY_SCHEMA_RECIPES).join(", ")}.`;
3660
+ }
3661
+ const postBody = {
3662
+ name: recipe.name,
3663
+ slug: recipe.slug,
3664
+ description: recipe.description,
3665
+ fields_definition: recipe.fields_definition,
3666
+ };
3667
+ const bulkBody = recipe.bulk_seed_example
3668
+ ? {
3669
+ schema_id: "<paste the id returned by POST /v1/entity/schemas above>",
3670
+ records: recipe.bulk_seed_example,
3671
+ }
3672
+ : null;
3673
+ return `# Entity schema recipe — ${recipe.name}
3674
+
3675
+ ${recipe.description}
3676
+
3677
+ ## Schema field types — what the backend actually accepts
3678
+
3679
+ The Custom Entities validator only accepts these types in \`fields_definition\`:
3680
+
3681
+ \`string\`, \`text\`, \`integer\`, \`float\`, \`boolean\`, \`datetime\`, \`enum\`, \`email\`, \`url\`, \`phone\`, \`reference\` (ADR-0009).
3682
+
3683
+ **There is no \`array\` type and no \`number\` type.** Use \`integer\`/\`float\` instead of \`number\`. For list-shaped data either (a) store as a comma-separated \`text\` (simple, not queryable), or (b) model as a separate entity with a \`reference\` field pointing back (queryable, ADR-0009 cross-schema search).
3684
+
3685
+ ## Step 1 — Create the schema
3686
+
3687
+ \`\`\`http
3688
+ POST /v1/entity/schemas
3689
+ Content-Type: application/json
3690
+ X-API-Key: <your sk_live_*>
3691
+ X-Organization-Id: <your organization_id>
3692
+ \`\`\`
3693
+
3694
+ Body:
3695
+ \`\`\`json
3696
+ ${JSON.stringify(postBody, null, 2)}
3697
+ \`\`\`
3698
+
3699
+ ${bulkBody
3700
+ ? `## Step 2 — Seed sample rows (bulk)
3701
+
3702
+ \`\`\`http
3703
+ POST /v1/entity/records/bulk
3704
+ Content-Type: application/json
3705
+ X-API-Key: <your sk_live_*>
3706
+ X-Organization-Id: <your organization_id>
3707
+ \`\`\`
3708
+
3709
+ Body:
3710
+ \`\`\`json
3711
+ ${JSON.stringify(bulkBody, null, 2)}
3712
+ \`\`\`
3713
+
3714
+ Bulk insert is capped at 500 records per call; chunk larger migrations.
3715
+ All-or-nothing: any validation failure rolls back the whole batch.
3716
+ `
3717
+ : ""}
3718
+ ${recipe.notes ? `## Notes\n\n${recipe.notes}\n` : ""}
3719
+
3720
+ ---
3721
+
3722
+ **Before posting**, call \`get_reserved_schema_slugs()\` to confirm \`${recipe.slug}\` is not on the reserved list. It is not, today, but slug-collision checking is part of the contract.`;
3723
+ }
3724
+ // =============================================================================
3725
+ // Chatbot setup recipe (v3.7.0) — closes G2.
3726
+ // =============================================================================
3727
+ function CHATBOT_SETUP_RECIPE() {
3728
+ return `# Chatbot setup recipe — agent + KB bound by \`rag_role\`
3729
+
3730
+ End-to-end recipe to wire a chatbot agent that answers from a knowledge base
3731
+ populated by a snapshot of your custom-entity catalog (typical "store
3732
+ assistant" / "product Q&A" / "support deflection" use case).
3733
+
3734
+ ## Auth context — clarification (frequent confusion)
3735
+
3736
+ The end-user chat endpoint \`POST /v1/user/agents/{agent_id}/chat\` requires
3737
+ an end-user JWT. **There are two legitimate ways to obtain that JWT**, and a
3738
+ public-facing bot needs option (b):
3739
+
3740
+ - **(a) Real human end-user**: customer signs up via \`POST /v1/auth/register\`,
3741
+ logs in, and chats with the agent. The JWT is theirs.
3742
+ - **(b) Bot service account**: register **one real bot user** for the chat
3743
+ surface (use a real email you control, e.g. \`bot@yourshop.com\`), keep its
3744
+ credentials in your server env, log in server-side, attach the resulting
3745
+ JWT to incoming chat requests. This is what "register a bot user" means.
3746
+
3747
+ **What is NOT allowed** (and what some agents mis-read as "no LLM bot is
3748
+ possible"): inventing/synthesizing fake email addresses to bulk-create
3749
+ end-users on the fly. That bypass would route through SES and burn the
3750
+ sender reputation — exactly the incident pattern of #168. Registering one
3751
+ or a few real bot accounts that you actually own is fine.
3752
+
3753
+ If you cannot or do not want to register a bot user, an acceptable fallback
3754
+ for MVP is a deterministic keyword search over the catalog (no LLM, no JWT
3755
+ required) — useful when the storefront has to be live before you set up the
3756
+ LLM layer. The bot user path is still the canonical way to get a real LLM bot.
3757
+
3758
+ ## Architecture in one diagram
3759
+
3760
+ \`\`\`
3761
+ Custom-entity catalog
3762
+ (e.g. products)
3763
+
3764
+ │ 1. snapshot to plain text
3765
+
3766
+ KnowledgeBase (per-Org)
3767
+
3768
+ │ 2. uploaded as a Document
3769
+
3770
+ Agent (rag_role bound to KB) ←─ end-user chats
3771
+ /v1/user/agents/{id}/chat
3772
+ \`\`\`
3773
+
3774
+ ## Step-by-step
3775
+
3776
+ ### 1. Create the KnowledgeBase
3777
+
3778
+ \`\`\`http
3779
+ POST /v1/organization-admin/{org_id}/knowledge-bases
3780
+ \`\`\`
3781
+
3782
+ Body:
3783
+ \`\`\`json
3784
+ {
3785
+ "name": "Vendy catalog",
3786
+ "description": "Snapshot of products, updated on demand",
3787
+ "rag_role": "product_catalog"
3788
+ }
3789
+ \`\`\`
3790
+
3791
+ Save the returned \`kb_id\`. The \`rag_role\` value is the binding key — the
3792
+ agent will reference the same role string to find this KB at chat time.
3793
+
3794
+ ### 2. Snapshot your catalog and upload it as a Document
3795
+
3796
+ Pull every product record:
3797
+
3798
+ \`\`\`http
3799
+ POST /v1/entity/records/search
3800
+ \`\`\`
3801
+
3802
+ Body: \`{ "schema_id": "<your product schema id>", "query": { "is_active": true } }\`
3803
+
3804
+ Format each row as one paragraph the LLM can read:
3805
+
3806
+ \`\`\`text
3807
+ Product: Sugar 1kg
3808
+ Price: $2.50
3809
+ Stock: 100
3810
+ Category: groceries
3811
+ Description: white sugar in 1kg bags
3812
+ ---
3813
+ Product: Rice 1kg
3814
+ ...
3815
+ \`\`\`
3816
+
3817
+ Upload as a document:
3818
+
3819
+ \`\`\`http
3820
+ POST /v1/organization-admin/{org_id}/knowledge-bases/{kb_id}/documents
3821
+ \`\`\`
3822
+
3823
+ Body:
3824
+ \`\`\`json
3825
+ {
3826
+ "title": "Catalog snapshot 2026-05-26",
3827
+ "content": "<the multi-paragraph text from above>",
3828
+ "metadata": { "source": "catalog_snapshot", "snapshotted_at": "<ISO timestamp>" }
3829
+ }
3830
+ \`\`\`
3831
+
3832
+ ### 3. Create the Agent
3833
+
3834
+ \`\`\`http
3835
+ POST /v1/agents
3836
+ \`\`\`
3837
+
3838
+ Body:
3839
+ \`\`\`json
3840
+ {
3841
+ "name": "Vendy Assistant",
3842
+ "description": "Answers customer questions about the store catalog.",
3843
+ "system_prompt": "You are a helpful store assistant for Vendy. Answer ONLY about products in the catalog you have access to. If the customer asks about anything else (delivery, billing, hours), reply that you can only help with product questions. Never invent products or prices.",
3844
+ "is_public": true,
3845
+ "rag_role": "product_catalog"
3846
+ }
3847
+ \`\`\`
3848
+
3849
+ \`is_public: true\` lets every end-user of the Org talk to the same agent (no
3850
+ per-user fork). \`rag_role: "product_catalog"\` is the binding to the KB
3851
+ created in step 1.
3852
+
3853
+ ### 4. End-user chat (frontend)
3854
+
3855
+ \`\`\`http
3856
+ POST /v1/user/agents/{agent_id}/chat
3857
+ \`\`\`
3858
+
3859
+ Body:
3860
+ \`\`\`json
3861
+ {
3862
+ "message": "do you have rice?",
3863
+ "conversation_id": "<optional, persist in sessionStorage to keep context>"
3864
+ }
3865
+ \`\`\`
3866
+
3867
+ Returns the assistant's response plus a \`conversation_id\` you should pass on
3868
+ subsequent messages to keep the same thread.
3869
+
3870
+ ## Refresh policy
3871
+
3872
+ When products change (price update, new SKU), regenerate the snapshot text
3873
+ and either:
3874
+
3875
+ - **Replace the document** (\`DELETE /v1/organization-admin/{org_id}/knowledge-bases/{kb_id}/documents/{doc_id}\` then re-upload) — simplest.
3876
+ - **Append a delta document** (\`POST\` a new document with only the changes) — faster but you need to manage stale entries.
3877
+
3878
+ For an MVP the first option is what we recommend.
3879
+
3880
+ ## Server-side TypeScript snippet
3881
+
3882
+ \`\`\`typescript
3883
+ // lib/genlobe.server.ts
3884
+ import 'server-only';
3885
+
3886
+ const API = process.env.SAAS_API_URL!;
3887
+ const KEY = process.env.SAAS_API_KEY!; // sk_live_*
3888
+ const ORG = process.env.SAAS_ORGANIZATION_ID!;
3889
+
3890
+ async function api(path: string, body: any, jwt?: string) {
3891
+ const res = await fetch(\`\${API}\${path}\`, {
3892
+ method: 'POST',
3893
+ headers: {
3894
+ 'Content-Type': 'application/json',
3895
+ 'X-API-Key': KEY,
3896
+ 'X-Organization-Id': ORG,
3897
+ ...(jwt ? { 'Authorization': \`Bearer \${jwt}\` } : {}),
3898
+ },
3899
+ body: JSON.stringify(body),
3900
+ });
3901
+ if (!res.ok) throw new Error(\`\${res.status} \${await res.text()}\`);
3902
+ return res.json();
3903
+ }
3904
+
3905
+ export async function refreshCatalogKB(productSchemaId: string, kbId: string, existingDocId?: string) {
3906
+ const { records } = await api('/v1/entity/records/search', {
3907
+ schema_id: productSchemaId,
3908
+ query: { is_active: true },
3909
+ });
3910
+ const content = records
3911
+ .map((r: any) =>
3912
+ \`Product: \${r.data.name}\\nPrice: $\${r.data.price}\\nStock: \${r.data.stock}\\nCategory: \${r.data.category ?? 'n/a'}\\nDescription: \${r.data.description ?? ''}\`
3913
+ )
3914
+ .join('\\n---\\n');
3915
+
3916
+ if (existingDocId) {
3917
+ await fetch(\`\${API}/v1/organization-admin/\${ORG}/knowledge-bases/\${kbId}/documents/\${existingDocId}\`, {
3918
+ method: 'DELETE',
3919
+ headers: { 'X-API-Key': KEY, 'X-Organization-Id': ORG },
3920
+ });
3921
+ }
3922
+ return api(\`/v1/organization-admin/\${ORG}/knowledge-bases/\${kbId}/documents\`, {
3923
+ title: \`Catalog snapshot \${new Date().toISOString()}\`,
3924
+ content,
3925
+ metadata: { source: 'catalog_snapshot' },
3926
+ });
3927
+ }
3928
+
3929
+ export async function chatWithAgent(agentId: string, message: string, conversationId?: string) {
3930
+ return api(\`/v1/user/agents/\${agentId}/chat\`, {
3931
+ message,
3932
+ ...(conversationId ? { conversation_id: conversationId } : {}),
3933
+ });
3934
+ }
3935
+ \`\`\`
3936
+
3937
+ ## Notes
3938
+
3939
+ - \`POST /v1/agents\` is the Tenant Dashboard endpoint — when configuring from
3940
+ your scaffolding script, use \`sk_live_*\`. End-user chat (\`/v1/user/agents/{id}/chat\`)
3941
+ is reached with \`pk_live_*\` + the end-user's JWT.
3942
+ - Tool calling vs RAG: for MVP / low-frequency catalog updates, RAG via KB is
3943
+ simpler. Switch to tool calling when the catalog updates faster than the
3944
+ refresh cadence you can sustain.
3945
+ - The bot does not write to the catalog and does not take orders unless you
3946
+ add a separate tool / API path. Keep the system prompt strict.`;
3947
+ }
3948
+ const APP_SCAFFOLDS = {
3949
+ pos: {
3950
+ template: "pos",
3951
+ title: "POS / small-store register",
3952
+ description: "A storefront with a chatbot for product questions and an owner dashboard for products, customers, sales.",
3953
+ entities: [
3954
+ { slug: "product", recipe_key: "product", purpose: "Catalog the owner manages." },
3955
+ {
3956
+ slug: "customer",
3957
+ recipe_key: "customer",
3958
+ purpose: "Store-side customer record (NOT a Genlobe end-user).",
3959
+ },
3960
+ {
3961
+ slug: "order",
3962
+ recipe_key: "order",
3963
+ purpose: "Completed sales with snapshotted line items (FK to product + customer).",
3964
+ },
3965
+ ],
3966
+ agents: [
3967
+ {
3968
+ name: "Store Assistant",
3969
+ rag_role: "product_catalog",
3970
+ purpose: "Answers customer questions about the product catalog. Read-only.",
3971
+ },
3972
+ ],
3973
+ kbs: [{ rag_role: "product_catalog", source: "Snapshot of `product` records" }],
3974
+ kpis: [
3975
+ {
3976
+ name: "Today's revenue",
3977
+ how: "POST /v1/entity/records/search with `query: { paid: true, paid_at: { operator: 'gte', value: <today_iso> } }`, sum `data.total` client-side.",
3978
+ },
3979
+ {
3980
+ name: "Top product this month",
3981
+ how: "Search orders, flatten `items[]`, group by `product_id`, sum `qty`. Cross-schema `join` with `product` to get the name in the same call.",
3982
+ },
3983
+ {
3984
+ name: "New customers this week",
3985
+ how: "Search customers with `created_at` filter; show count + names.",
3986
+ },
3987
+ ],
3988
+ build_order: [
3989
+ "1. Next.js 16 app router + Tailwind + shadcn. Add `lib/genlobe.server.ts` with `import 'server-only'`.",
3990
+ "2. Bootstrap script: create `product`, `customer`, `order` schemas via `get_entity_schema_recipe`. Bulk-seed 5-10 sample products.",
3991
+ "3. Owner login: `/admin/login` → POST /v1/auth/login server-side → cookie httpOnly with JWT.",
3992
+ "4. Owner CRUD products (single end-to-end vertical slice first).",
3993
+ "5. Sales form (`/admin/sales/new`): customer autocomplete, multi-product line items, total auto-calc, POST one `order` record.",
3994
+ "6. Sales list + detail.",
3995
+ "7. Customers list + per-customer purchase history (cross-schema join from order → customer).",
3996
+ "8. Dashboard KPIs (search + sum client-side; switch to server-side aggregates later if data grows).",
3997
+ "9. Public storefront `/` + `/chat`. Bot via `get_chatbot_setup_recipe`.",
3998
+ "10. Catalog refresh button in `/admin` (regenerates the KB document).",
3999
+ ],
4000
+ notes: "Stock decrement on sale is intentionally NOT automatic — for MVP let the owner adjust stock manually after a sale, to avoid double-decrement bugs.",
4001
+ },
4002
+ crm: {
4003
+ template: "crm",
4004
+ title: "Lightweight CRM",
4005
+ description: "Track contacts, deals, notes, and a sales pipeline. No marketing automation, no email integration — that's later.",
4006
+ entities: [
4007
+ { slug: "customer", recipe_key: "customer", purpose: "Contact records." },
4008
+ {
4009
+ slug: "deal",
4010
+ recipe_key: "order",
4011
+ purpose: "Reuse the `order` recipe but rename to `deal`; line items become deliverables and `paid` becomes `closed_won`.",
4012
+ },
4013
+ {
4014
+ slug: "note",
4015
+ recipe_key: "note",
4016
+ purpose: "Free-text notes per contact (rename `note` → `interaction` if you want to scope it to the contact).",
4017
+ },
4018
+ ],
4019
+ agents: [
4020
+ {
4021
+ name: "CRM Helper",
4022
+ purpose: "Internal-only agent that drafts follow-up emails based on the most recent notes on a contact. No KB needed; works with conversation context.",
4023
+ },
4024
+ ],
4025
+ kbs: [],
4026
+ kpis: [
4027
+ { name: "Open pipeline value", how: "Search deals where `closed_won: false`, sum `total`." },
4028
+ {
4029
+ name: "Conversion rate",
4030
+ how: "Count deals `closed_won: true` / total deals over a period.",
4031
+ },
4032
+ {
4033
+ name: "Stale contacts",
4034
+ how: "Customers with no `note` records in the last 30 days (cross-schema NOT-EXISTS).",
4035
+ },
4036
+ ],
4037
+ build_order: [
4038
+ "1. Same bootstrap as POS.",
4039
+ "2. Customer CRUD (list + detail + edit).",
4040
+ "3. Notes per customer (reference field to customer).",
4041
+ "4. Deals pipeline view (kanban by `status`).",
4042
+ "5. CRM Helper agent (no KB) for drafting follow-ups.",
4043
+ ],
4044
+ notes: "If you want incoming email parsing, that's a Stage-3 deploy capability — not in MVP.",
4045
+ },
4046
+ blog: {
4047
+ template: "blog",
4048
+ title: "Multi-author blog",
4049
+ description: "Posts, comments, tags. Public read, authenticated write.",
4050
+ entities: [
4051
+ { slug: "post", recipe_key: "post", purpose: "Articles." },
4052
+ { slug: "comment", recipe_key: "comment", purpose: "User comments on posts." },
4053
+ ],
4054
+ agents: [
4055
+ {
4056
+ name: "Blog Recommender",
4057
+ rag_role: "blog_archive",
4058
+ purpose: "Suggests related posts based on what the reader is browsing.",
4059
+ },
4060
+ ],
4061
+ kbs: [{ rag_role: "blog_archive", source: "Snapshot of published `post` records" }],
4062
+ kpis: [
4063
+ { name: "Posts published this month", how: "Search posts where `status: published`, count." },
4064
+ {
4065
+ name: "Most commented post",
4066
+ how: "Search comments, group by `target_id`, count, cross-schema join with `post` for the title.",
4067
+ },
4068
+ ],
4069
+ build_order: [
4070
+ "1. Bootstrap.",
4071
+ "2. Post CRUD (admin only — gate with role check).",
4072
+ "3. Public reader view (`/blog/[slug]`) with comments form.",
4073
+ "4. Comments moderation page.",
4074
+ "5. Recommender bot via KB.",
4075
+ ],
4076
+ },
4077
+ task_manager: {
4078
+ template: "task_manager",
4079
+ title: "Personal / team task manager",
4080
+ description: "To-do list with assignees, due dates, and status. Per-user view by default (ADR-0007 row-level scope).",
4081
+ entities: [{ slug: "task", recipe_key: "task", purpose: "Tasks." }],
4082
+ agents: [
4083
+ {
4084
+ name: "Task Coach",
4085
+ purpose: "Suggests next-best task based on due dates and priorities (no KB; uses context).",
4086
+ },
4087
+ ],
4088
+ kbs: [],
4089
+ kpis: [
4090
+ {
4091
+ name: "My open tasks",
4092
+ how: "GET /v1/entity/records/mine?schema_id=<task> — returns only the caller's tasks via row-level scope, no extra filter.",
4093
+ },
4094
+ {
4095
+ name: "Overdue tasks (team)",
4096
+ how: "Search tasks where `due_date < now` AND `status != completed`. Elevated role only.",
4097
+ },
4098
+ ],
4099
+ build_order: [
4100
+ "1. Bootstrap.",
4101
+ "2. End-user signup + login (`pk_live_*` + JWT flow).",
4102
+ "3. `/tasks` list using `GET /v1/entity/records/mine`.",
4103
+ "4. Create / edit task.",
4104
+ "5. Coach bot.",
4105
+ ],
4106
+ notes: "Use `GET /v1/entity/records/mine` instead of POST /search with `created_by_id` filter — row-level scope is enforced server-side, the frontend MUST NOT be the filter.",
4107
+ },
4108
+ notes: {
4109
+ template: "notes",
4110
+ title: "Personal notes app",
4111
+ description: "Free-text notes per user, scoped to the author. Inspired by Apple Notes / Bear.",
4112
+ entities: [{ slug: "note", recipe_key: "note", purpose: "Notes." }],
4113
+ agents: [
4114
+ {
4115
+ name: "Notes Search",
4116
+ rag_role: "user_notes",
4117
+ purpose: "Semantic search across the user's notes. Per-user KB scoped via row-level scope on the search side.",
4118
+ },
4119
+ ],
4120
+ kbs: [
4121
+ {
4122
+ rag_role: "user_notes",
4123
+ source: "Each user's notes uploaded as one document per note. Could also be one consolidated doc per user, refreshed on each save.",
4124
+ },
4125
+ ],
4126
+ kpis: [
4127
+ { name: "Total notes", how: "GET /v1/entity/records/mine — count." },
4128
+ { name: "Pinned", how: "GET /v1/entity/records/mine with `is_pinned: true` filter." },
4129
+ ],
4130
+ build_order: [
4131
+ "1. Bootstrap + end-user auth.",
4132
+ "2. Note CRUD (`/v1/entity/records/mine` for the list).",
4133
+ "3. Search bot per user.",
4134
+ "4. Pin / tag filters.",
4135
+ ],
4136
+ },
4137
+ support_inbox: {
4138
+ template: "support_inbox",
4139
+ title: "Customer support inbox",
4140
+ description: "Capture support tickets, route to agents, let an AI first-responder handle FAQs before escalation.",
4141
+ entities: [
4142
+ {
4143
+ slug: "ticket",
4144
+ recipe_key: "task",
4145
+ purpose: "Reuse `task` recipe. Rename `task` → `ticket`. `assigned_to_id` becomes the support agent (human end-user).",
4146
+ },
4147
+ {
4148
+ slug: "message",
4149
+ recipe_key: "message",
4150
+ purpose: "Conversation thread per ticket.",
4151
+ },
4152
+ ],
4153
+ agents: [
4154
+ {
4155
+ name: "First Responder",
4156
+ rag_role: "support_kb",
4157
+ purpose: "Answers from the support knowledge base before escalating to a human.",
4158
+ },
4159
+ ],
4160
+ kbs: [
4161
+ {
4162
+ rag_role: "support_kb",
4163
+ source: "Help docs / FAQs. Upload manually or sync from your existing help center.",
4164
+ },
4165
+ ],
4166
+ kpis: [
4167
+ { name: "Open tickets", how: "Search tickets where `status != completed`." },
4168
+ {
4169
+ name: "Avg time to first response",
4170
+ how: "For each ticket, find the first `message` where `sender_role != 'user'`, compute delta. Aggregate client-side.",
4171
+ },
4172
+ ],
4173
+ build_order: [
4174
+ "1. Bootstrap.",
4175
+ "2. Public form (`/contact`) that creates a `ticket` + an initial `message`.",
4176
+ "3. Internal queue view (`/support/inbox`).",
4177
+ "4. First Responder bot, with escalation flag.",
4178
+ "5. SLA dashboard.",
4179
+ ],
4180
+ },
4181
+ };
4182
+ function APP_SCAFFOLD(template) {
4183
+ const s = APP_SCAFFOLDS[template];
4184
+ if (!s) {
4185
+ return `# Unknown template "${template}"\n\nAvailable: ${Object.keys(APP_SCAFFOLDS).join(", ")}.`;
4186
+ }
4187
+ const entitySection = s.entities
4188
+ .map((e) => `- **\`${e.slug}\`** — ${e.purpose} Get the schema recipe with \`get_entity_schema_recipe({ entity_type: "${e.recipe_key}" })\`.`)
4189
+ .join("\n");
4190
+ const agentSection = s.agents
4191
+ .map((a) => `- **${a.name}**${a.rag_role ? ` (\`rag_role: "${a.rag_role}"\`)` : ""} — ${a.purpose}`)
4192
+ .join("\n");
4193
+ const kbSection = s.kbs.length
4194
+ ? s.kbs
4195
+ .map((kb) => `- \`rag_role: "${kb.rag_role}"\` ← ${kb.source}`)
4196
+ .join("\n")
4197
+ : "_(none — agents work from conversation context)_";
4198
+ const kpiSection = s.kpis
4199
+ .map((k) => `- **${k.name}** — ${k.how}`)
4200
+ .join("\n");
4201
+ const orderSection = s.build_order.map((step) => step).join("\n");
4202
+ return `# App scaffold — ${s.title}
4203
+
4204
+ ${s.description}
4205
+
4206
+ ## Custom entities to create
4207
+
4208
+ ${entitySection}
4209
+
4210
+ ## Agents to create
4211
+
4212
+ ${agentSection}
4213
+
4214
+ ## Knowledge bases
4215
+
4216
+ ${kbSection}
4217
+
4218
+ ## Typical KPIs and how to compute them
4219
+
4220
+ ${kpiSection}
4221
+
4222
+ ## Suggested build order
4223
+
4224
+ ${orderSection}
4225
+
4226
+ ${s.notes ? `## Notes\n\n${s.notes}\n` : ""}
4227
+
4228
+ ---
4229
+
4230
+ **Next calls**:
4231
+ - For each entity above, call \`get_entity_schema_recipe({ entity_type: "..." })\` and POST the result.
4232
+ - For the chatbot wiring, call \`get_chatbot_setup_recipe()\`.
4233
+ - For the auth shape (sk_live_* / pk_live_* + cookies), call \`get_authentication_flow()\` and \`get_security_guide()\`.`;
4234
+ }
3500
4235
  // =============================================================================
3501
4236
  // MCP Server Implementation
3502
4237
  // =============================================================================
@@ -3800,6 +4535,41 @@ developer says "I want to build X" before writing code.`,
3800
4535
  required: ["app_type"],
3801
4536
  },
3802
4537
  },
4538
+ {
4539
+ name: "get_entity_schema_recipe",
4540
+ description: `Return a ready-to-POST custom entity schema for a common entity type. Removes the guesswork of building \`fields_definition\` JSON by hand and ensures the agent picks the right field types (\`string\`, \`number\`, \`boolean\`, \`reference\` per ADR-0009, \`datetime\`, ...). Pure data, no network call. Pair with \`get_reserved_schema_slugs\` to avoid native-table collisions, and with \`POST /v1/entity/records/bulk\` for a fast seed of sample rows. Available entity types: product, customer, order, post, comment, message, task, note. Each recipe ships with the matching POST body, a sample \`fields_definition\`, and \`bulk_seed_example\` if applicable.`,
4541
+ inputSchema: {
4542
+ type: "object",
4543
+ properties: {
4544
+ entity_type: {
4545
+ type: "string",
4546
+ enum: ["product", "customer", "order", "post", "comment", "message", "task", "note"],
4547
+ description: "Which common entity pattern do you want a recipe for?",
4548
+ },
4549
+ },
4550
+ required: ["entity_type"],
4551
+ },
4552
+ },
4553
+ {
4554
+ name: "get_chatbot_setup_recipe",
4555
+ description: `End-to-end recipe for wiring a chatbot agent to a knowledge base populated from your custom-entity catalog. Returns the ordered list of API calls (create agent → create KB → upload doc → bind via rag_role) plus a TypeScript snippet (server-side, sk_live_* required). Aimed at the canonical "store assistant" / "product Q&A bot" / "support deflection" use case. Pure data; no network call.`,
4556
+ inputSchema: { type: "object", properties: {}, required: [] },
4557
+ },
4558
+ {
4559
+ name: "get_app_scaffold",
4560
+ description: `Return a complete app scaffold for a common product template (\`pos\`, \`crm\`, \`blog\`, \`task_manager\`, \`notes\`, \`support_inbox\`). Each scaffold emits: the custom-entity schemas to create (with field types pre-decided), the agent(s) + KBs to set up, the typical KPIs and how to compute them (cross-schema search with \`join\` per ADR-0009), and the suggested build order. Designed to short-circuit the agent's "what should I build first" deliberation when the prompt is vibecoder-style ("armame un POS para una tiendita"). Pure data; no network call.`,
4561
+ inputSchema: {
4562
+ type: "object",
4563
+ properties: {
4564
+ template: {
4565
+ type: "string",
4566
+ enum: ["pos", "crm", "blog", "task_manager", "notes", "support_inbox"],
4567
+ description: "Which app template do you want to scaffold?",
4568
+ },
4569
+ },
4570
+ required: ["template"],
4571
+ },
4572
+ },
3803
4573
  ],
3804
4574
  };
3805
4575
  });
@@ -4853,6 +5623,23 @@ type to pick.`,
4853
5623
  ],
4854
5624
  };
4855
5625
  }
5626
+ case "get_entity_schema_recipe": {
5627
+ const entityType = args.entity_type;
5628
+ return {
5629
+ content: [{ type: "text", text: ENTITY_SCHEMA_RECIPE(entityType) }],
5630
+ };
5631
+ }
5632
+ case "get_chatbot_setup_recipe": {
5633
+ return {
5634
+ content: [{ type: "text", text: CHATBOT_SETUP_RECIPE() }],
5635
+ };
5636
+ }
5637
+ case "get_app_scaffold": {
5638
+ const template = args.template;
5639
+ return {
5640
+ content: [{ type: "text", text: APP_SCAFFOLD(template) }],
5641
+ };
5642
+ }
4856
5643
  default:
4857
5644
  throw new Error(`Unknown tool: ${name}`);
4858
5645
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@genlobe/mcp-server",
3
- "version": "3.6.2",
3
+ "version": "3.7.1",
4
4
  "description": "MCP Server for GenLobe SaaS API - Provides AI assistants with comprehensive API documentation for building frontend applications",
5
5
  "main": "dist/index.js",
6
6
  "bin": {