@genlobe/mcp-server 3.7.1 → 3.7.2

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 +202 -15
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -3773,6 +3773,43 @@ LLM layer. The bot user path is still the canonical way to get a real LLM bot.
3773
3773
 
3774
3774
  ## Step-by-step
3775
3775
 
3776
+ ### 0. Register the bot service-account user (only once per project)
3777
+
3778
+ The chat endpoint requires an end-user JWT. Register **one** real user that
3779
+ the server uses to obtain that JWT — this is not the same as inventing fake
3780
+ emails for bulk signups (which is what the security guide prohibits). Use a
3781
+ mailbox you actually own.
3782
+
3783
+ \`\`\`http
3784
+ POST /v1/auth/register
3785
+ Content-Type: application/json
3786
+ X-API-Key: <your pk_live_* ← register works with the public key>
3787
+ \`\`\`
3788
+
3789
+ Body:
3790
+ \`\`\`json
3791
+ {
3792
+ "email": "bot@yourshop.com",
3793
+ "password": "<32-char random string, store it once and never again in plain text>",
3794
+ "display_name": "Store Bot"
3795
+ }
3796
+ \`\`\`
3797
+
3798
+ Save the email and the password in your server-side env (\`.env.local\`):
3799
+
3800
+ \`\`\`
3801
+ BOT_USER_EMAIL=bot@yourshop.com
3802
+ BOT_USER_PASSWORD=<that random string>
3803
+ \`\`\`
3804
+
3805
+ From then on the server logs in this user on demand and caches the JWT for
3806
+ its lifetime. **Never** ship \`BOT_USER_PASSWORD\` to the browser. See the
3807
+ "Server-side TypeScript snippet" further down for the cached login helper.
3808
+
3809
+ If your project does not allow public chat (the bot is internal-only and
3810
+ every conversation already belongs to a logged-in human), skip this step
3811
+ and pass each user's own JWT to the chat endpoint.
3812
+
3776
3813
  ### 1. Create the KnowledgeBase
3777
3814
 
3778
3815
  \`\`\`http
@@ -3926,11 +3963,28 @@ export async function refreshCatalogKB(productSchemaId: string, kbId: string, ex
3926
3963
  });
3927
3964
  }
3928
3965
 
3966
+ // Cached login for the bot service-account user. The JWT is reused until it
3967
+ // nears expiry; on next call we refresh server-side.
3968
+ let cachedBotJwt: { token: string; expiresAt: number } | null = null;
3969
+
3970
+ export async function getBotJwt(): Promise<string> {
3971
+ if (cachedBotJwt && cachedBotJwt.expiresAt > Date.now() + 60_000) {
3972
+ return cachedBotJwt.token;
3973
+ }
3974
+ const email = process.env.BOT_USER_EMAIL!;
3975
+ const password = process.env.BOT_USER_PASSWORD!;
3976
+ const r = await api('/v1/auth/login', { email, password });
3977
+ const ttl = (r.expires_in ?? 3600) * 1000;
3978
+ cachedBotJwt = { token: r.access_token, expiresAt: Date.now() + ttl };
3979
+ return r.access_token;
3980
+ }
3981
+
3929
3982
  export async function chatWithAgent(agentId: string, message: string, conversationId?: string) {
3983
+ const jwt = await getBotJwt();
3930
3984
  return api(\`/v1/user/agents/\${agentId}/chat\`, {
3931
3985
  message,
3932
3986
  ...(conversationId ? { conversation_id: conversationId } : {}),
3933
- });
3987
+ }, jwt);
3934
3988
  }
3935
3989
  \`\`\`
3936
3990
 
@@ -3938,12 +3992,19 @@ export async function chatWithAgent(agentId: string, message: string, conversati
3938
3992
 
3939
3993
  - \`POST /v1/agents\` is the Tenant Dashboard endpoint — when configuring from
3940
3994
  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.
3995
+ is reached with \`pk_live_*\` (or \`sk_live_*\` on the server) + the end-user's
3996
+ JWT (or the bot's, if you registered one in Step 0).
3942
3997
  - Tool calling vs RAG: for MVP / low-frequency catalog updates, RAG via KB is
3943
3998
  simpler. Switch to tool calling when the catalog updates faster than the
3944
3999
  refresh cadence you can sustain.
3945
4000
  - 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.`;
4001
+ add a separate tool / API path. Keep the system prompt strict.
4002
+ - **Fallback for MVP without an LLM** — if the project explicitly cannot
4003
+ register a bot user (no email domain available yet), a deterministic
4004
+ keyword-search bot over the catalog is acceptable. Implement \`/api/chat\` as
4005
+ a server-side \`POST /v1/entity/records/search\` with the user's query as an
4006
+ \`ilike\` filter. Not equivalent to the LLM bot but ships a working surface;
4007
+ swap it for the LLM later.`;
3947
4008
  }
3948
4009
  const APP_SCAFFOLDS = {
3949
4010
  pos: {
@@ -3986,18 +4047,23 @@ const APP_SCAFFOLDS = {
3986
4047
  },
3987
4048
  ],
3988
4049
  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).",
4050
+ "**0. Pre-flight.** Call `validate_credentials` to confirm sk_live_* and the org context. Restart any long-running uvicorn on the Genlobe backend if it predates today — the bulk endpoint is only available on backends started after the v3.6.2 merge.",
4051
+ "**1. Scaffold a Next.js 16 app router project under `vendy-app/` (or similar).** Tailwind + shadcn. `pnpm dev -p 3100` so it doesn't collide with the Genlobe Tenant UI on 3000. Verify `.gitignore` lists `.env*.local`, `.env`, `node_modules`, `.next`.",
4052
+ "**2. Create `lib/genlobe.server.ts` with `import 'server-only';` at the top.** Wrap fetch with X-API-Key + X-Organization-Id + optional JWT — see Section 10 of `get_authentication_flow`.",
4053
+ "**3. `.env.local`**: SAAS_API_URL, SAAS_API_KEY (sk_live_*), SAAS_ORGANIZATION_ID, BOT_USER_EMAIL, BOT_USER_PASSWORD, STORE_NAME, STORE_CURRENCY. Once schemas are created in step 5, append PRODUCT_SCHEMA_ID, CUSTOMER_SCHEMA_ID, ORDER_SCHEMA_ID.",
4054
+ "**4. Bot service-account user (Step 0 of `get_chatbot_setup_recipe`).** Register `bot@<your-shop>` via `POST /v1/auth/register`. Save credentials in `.env.local`. The server logs in this user on demand to obtain the chat JWT — never the human's.",
4055
+ "**5. Bootstrap script (`scripts/bootstrap.ts`)**: idempotent. Reads each schema slug from `.env.local`; if missing, calls `get_entity_schema_recipe({entity_type:'product|customer|order'})` shape and `POST /v1/entity/schemas`; appends the returned id to `.env.local`. Then `POST /v1/entity/records/bulk` with 5-10 sample products in ONE call (this is the saboreo demo of bulk insert).",
4056
+ "**6. Owner login**: `/admin/login` → server-side `POST /v1/auth/login` set `genlobe_jwt` cookie httpOnly. Use the email the human gave you (NOT the bot user).",
4057
+ "**7. Admin layout gate**: `app/(admin)/layout.tsx` reads `genlobe_jwt` cookie, redirects to `/admin/login` if missing.",
4058
+ "**8. Products vertical slice end-to-end first**: list, create, edit, delete. Confirms the full stack works before fanning out to other entities.",
4059
+ "**9. Sales (`/admin/sales/new`)**: customer autocomplete (search customers with `ilike`), product picker (paginated), line items with `name_snapshot` + `price_snapshot` per item, total auto-calc. POST one `order` record. Stock decrement is best-effort (continue if it fails — the sale is recorded).",
4060
+ "**10. Sales list (`/admin/sales`) + per-customer history (`/admin/customers/{id}`)**: cross-schema join (`POST /v1/entity/records/search` with `join` clause on `customer_id`/`product_id`) to pull customer / product names in the same call.",
4061
+ "**11. Dashboard KPIs (`/admin`)**: today's revenue, top 3 products, new customers this week. Client-side sums for MVP; flag any KPI taking >300ms as a candidate for server-side aggregation later.",
4062
+ "**12. KB + bot setup (`scripts/sync-catalog-kb.ts`)**: snapshot the catalog to plain text, create KB with `rag_role: 'product_catalog'`, upload document, create the Store Assistant agent with the same `rag_role`. Add a button in `/admin` that re-runs this script when the catalog changes.",
4063
+ "**13. Public storefront (`/`) + `/chat`**: storefront uses the catalog directly via a server route (no auth on the customer); `/chat` UI posts to `/api/chat` which calls `chatWithAgent(agentId, message, conversationId)` — JWT handled server-side by the cached bot login.",
4064
+ "**14. Smoke**: open `http://localhost:3100`, place one test order, confirm it shows up in `/admin/sales`, ask the bot \"¿tienen X?\", verify it answers from the KB.",
3999
4065
  ],
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.",
4066
+ notes: "Stock decrement on sale is intentionally NOT automatic to the level of a DB transaction — for MVP it is best-effort. If the stock UPDATE fails, the order is still saved. The owner reconciles manually. This avoids the double-decrement and partial-failure bugs that a naive transactional implementation hits without a row-lock primitive.\n\nReal-life pitfall (Vendy build, 2026-05-26): an agent built this 95% successfully but skipped the LLM bot setup because it mis-read the security guidance as 'no bot users allowed'. The Step 0 in `get_chatbot_setup_recipe` and the bot service-account clarification in `get_authentication_flow` exist to prevent that exact regression. If you find yourself building a keyword-search fallback because LLM 'isn't possible', re-read both — the LLM bot IS possible with one registered bot user, and the recipes spell out the ceremony.",
4001
4067
  },
4002
4068
  crm: {
4003
4069
  template: "crm",
@@ -4980,6 +5046,12 @@ Use search_endpoints tool to find available endpoints.`,
4980
5046
  > 2. **Do NOT register a new end-user with an email you generated.** Inventing an email (e.g. \`admin@<their-domain>\`, \`seed@<their-domain>\`) leads to bounces. AWS SES then suppresses the address at the account level and locks the tenant out of email delivery for that destination — recovery requires manual operator action. This is exactly how production incident #168 happened.
4981
5047
  > 3. **Schemas don't need a JWT.** \`/v1/entity/schemas\` is a tenant-admin operation: \`sk_live_*\` alone is enough (see issue #177). Read \`get_endpoint_details\` for the schemas endpoint before assuming you need to register a user.
4982
5048
  > 4. If the human says \"register a user with this email: ...\" — fine, that's an explicit instruction. Inventing one is not.
5049
+ >
5050
+ > ### Bot service-account user (LLM chat surface)
5051
+ >
5052
+ > **Public chatbot surfaces are an exception that is fully permitted**: you can — and should — register **one real bot service-account user** to obtain the JWT the agent chat endpoint requires. The human is implicitly authorizing this when they ask for "an LLM chatbot the customers can use". Use a real email you control (e.g. \`bot@<their-shop-domain>\` or a personal mailbox). Save the credentials in the server-side env (\`.env.local\` with \`BOT_USER_EMAIL\` + \`BOT_USER_PASSWORD\`), log in server-side, attach the JWT to incoming chat requests. See \`get_chatbot_setup_recipe()\` Step 0 for the full ceremony.
5053
+ >
5054
+ > Registering **one** bot account is not the same thing as "inventing emails in bulk" — that distinction matters.
4983
5055
 
4984
5056
  ---
4985
5057
 
@@ -5207,7 +5279,122 @@ await fetch('/v1/auth/logout', {
5207
5279
 
5208
5280
  localStorage.removeItem('access_token');
5209
5281
  localStorage.removeItem('refresh_token');
5210
- \`\`\``,
5282
+ \`\`\`
5283
+
5284
+ ---
5285
+
5286
+ ## 10. Server-side auth pattern (sk_live_* + cookies httpOnly) — Next.js App Router
5287
+
5288
+ When the app uses a **secret key** (\`sk_live_*\`), the browser must never see the key OR the API base URL. All Genlobe calls go through your own \`/api/*\` routes on the Next.js server. End-user sessions are kept in **httpOnly cookies**, not \`localStorage\`.
5289
+
5290
+ ### Project layout (recommended)
5291
+
5292
+ \`\`\`
5293
+ app/
5294
+ ├─ (admin)/ Owner dashboard, gated
5295
+ │ ├─ layout.tsx Reads cookie, redirects to /admin/login if missing
5296
+ │ ├─ admin/login/page.tsx
5297
+ │ └─ admin/page.tsx
5298
+ ├─ (public)/ No auth, anonymous customer surface
5299
+ │ ├─ page.tsx Storefront / POS / catalog
5300
+ │ └─ chat/page.tsx Public chatbot UI
5301
+ ├─ api/
5302
+ │ ├─ admin/login/route.ts POST → server-side call to /v1/auth/login, sets cookie
5303
+ │ ├─ admin/logout/route.ts Clears cookie
5304
+ │ └─ chat/route.ts POST → server-side call to /v1/user/agents/{id}/chat with bot JWT
5305
+ └─ middleware.ts Gates (admin)/*; refreshes bot JWT in background
5306
+ lib/
5307
+ └─ genlobe.server.ts 'server-only', wraps the API
5308
+ .env.local SAAS_API_URL, SAAS_API_KEY, SAAS_ORGANIZATION_ID, BOT_USER_EMAIL, BOT_USER_PASSWORD
5309
+ \`\`\`
5310
+
5311
+ ### lib/genlobe.server.ts
5312
+
5313
+ \`\`\`typescript
5314
+ import 'server-only';
5315
+
5316
+ const API = process.env.SAAS_API_URL!; // e.g. http://localhost:8001
5317
+ const KEY = process.env.SAAS_API_KEY!; // sk_live_* — server only
5318
+ const ORG = process.env.SAAS_ORGANIZATION_ID!; // organization scope
5319
+
5320
+ export async function genlobeApi<T = any>(
5321
+ method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE',
5322
+ path: string,
5323
+ body?: any,
5324
+ jwt?: string
5325
+ ): Promise<T> {
5326
+ const res = await fetch(\`\${API}\${path}\`, {
5327
+ method,
5328
+ headers: {
5329
+ 'Content-Type': 'application/json',
5330
+ 'X-API-Key': KEY,
5331
+ 'X-Organization-Id': ORG,
5332
+ ...(jwt ? { Authorization: \`Bearer \${jwt}\` } : {}),
5333
+ },
5334
+ body: body ? JSON.stringify(body) : undefined,
5335
+ cache: 'no-store',
5336
+ });
5337
+ if (!res.ok) throw new Error(\`Genlobe \${res.status}: \${await res.text()}\`);
5338
+ return res.json();
5339
+ }
5340
+ \`\`\`
5341
+
5342
+ ### app/api/admin/login/route.ts
5343
+
5344
+ \`\`\`typescript
5345
+ import { NextResponse } from 'next/server';
5346
+ import { cookies } from 'next/headers';
5347
+ import { genlobeApi } from '@/lib/genlobe.server';
5348
+
5349
+ export async function POST(req: Request) {
5350
+ const { email, password } = await req.json();
5351
+ const { access_token, refresh_token, user } = await genlobeApi(
5352
+ 'POST',
5353
+ '/v1/auth/login',
5354
+ { email, password }
5355
+ );
5356
+
5357
+ const jar = cookies();
5358
+ jar.set('genlobe_jwt', access_token, {
5359
+ httpOnly: true, secure: process.env.NODE_ENV === 'production',
5360
+ sameSite: 'lax', path: '/', maxAge: 60 * 60 * 24,
5361
+ });
5362
+ jar.set('genlobe_refresh', refresh_token, {
5363
+ httpOnly: true, secure: process.env.NODE_ENV === 'production',
5364
+ sameSite: 'lax', path: '/', maxAge: 60 * 60 * 24 * 30,
5365
+ });
5366
+
5367
+ return NextResponse.json({ user });
5368
+ }
5369
+ \`\`\`
5370
+
5371
+ ### app/(admin)/layout.tsx (gate)
5372
+
5373
+ \`\`\`typescript
5374
+ import { cookies } from 'next/headers';
5375
+ import { redirect } from 'next/navigation';
5376
+
5377
+ export default async function AdminLayout({ children }: { children: React.ReactNode }) {
5378
+ const jwt = cookies().get('genlobe_jwt')?.value;
5379
+ if (!jwt) redirect('/admin/login');
5380
+ return <>{children}</>;
5381
+ }
5382
+ \`\`\`
5383
+
5384
+ ### .gitignore (critical)
5385
+
5386
+ \`\`\`
5387
+ .env*.local
5388
+ .env
5389
+ \`\`\`
5390
+
5391
+ Next.js boilerplate puts this in by default if you used \`create-next-app\`. **Always verify it before the first commit** — the \`sk_live_*\` and \`BOT_USER_PASSWORD\` live in \`.env.local\` and must never leave your machine.
5392
+
5393
+ ### Three rules that keep this safe
5394
+
5395
+ 1. \`SAAS_API_KEY\` env var is **never** prefixed with \`NEXT_PUBLIC_\`. Same for \`BOT_USER_PASSWORD\` and \`SAAS_ORGANIZATION_ID\`.
5396
+ 2. The wrapper file (\`lib/genlobe.server.ts\`) starts with \`import 'server-only'\`. If a client component ever imports it, the Next.js build fails with a clear message — that's the contract.
5397
+ 3. The browser only talks to *your* \`/api/*\` routes, never to the Genlobe host directly.`,
5211
5398
  },
5212
5399
  ],
5213
5400
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@genlobe/mcp-server",
3
- "version": "3.7.1",
3
+ "version": "3.7.2",
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": {