@genlobe/mcp-server 3.1.3 → 3.3.0

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 (3) hide show
  1. package/README.md +0 -29
  2. package/dist/index.js +223 -18
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -159,35 +159,6 @@ To point your IDE at a local build instead of npm:
159
159
 
160
160
  ---
161
161
 
162
- ## Releasing a new version
163
-
164
- The MCP version follows semver and is bumped independently of the backend API:
165
-
166
- - **patch** — bug fix in a tool's behavior, doc correction
167
- - **minor** — new tool, new optional field, new env var with a default
168
- - **major** — tool removed/renamed, env var removed, breaking change in returned shape
169
-
170
- Publish flow (works around the subtree quirk where `npm version` doesn't always commit cleanly):
171
-
172
- ```bash
173
- cd mcp-server
174
- # 1. Move CHANGELOG [Unreleased] section under the new version header.
175
- # 2. Bump versions without letting npm touch git:
176
- npm version <patch|minor|major> --no-git-tag-version
177
- # 3. One commit + one tag, both intentional:
178
- git add package.json package-lock.json CHANGELOG.md
179
- git commit -m "chore(mcp): release vX.Y.Z"
180
- git tag -a vX.Y.Z -m "@genlobe/mcp-server vX.Y.Z"
181
- # 4. Publish (prepublishOnly runs `tsc` automatically):
182
- npm publish --access public
183
- # 5. Push commit + tag:
184
- git push origin main && git push origin vX.Y.Z
185
- ```
186
-
187
- The CI workflow `.github/workflows/publish-mcp.yml` automates steps 4–5 when you push a `mcp-v*` tag (see [Automated publishing](#automated-publishing) below).
188
-
189
- ---
190
-
191
162
  ## License
192
163
 
193
164
  MIT — see [package.json](./package.json).
package/dist/index.js CHANGED
@@ -64,8 +64,13 @@ There are TWO types of authentication contexts:
64
64
  - These endpoints ARE for building end-user frontends
65
65
 
66
66
  ## API Key Types
67
- - \`pk_live_*\`: Public key - safe to expose in frontend code (LIMITED to auth endpoints only)
68
- - \`sk_live_*\`: Secret key - backend only, never expose (FULL API access)
67
+
68
+ - \`pk_live_*\`: Public key safe to expose in frontend code.
69
+ - **By itself** (no JWT) the key only unlocks \`/v1/auth/*\` (register, login, refresh, logout, me, forgot-password, reset-password). These are the seven scopes auto-assigned to public keys.
70
+ - **Combined with an end-user JWT** returned by \`/v1/auth/login\`, the same public key unlocks the full end-user API: \`/v1/user/agents/*\`, \`/v1/entity/records/*\`, \`/v1/plans\`, \`/v1/billing/*\`, \`/v1/users/me\`, etc. Once the JWT is in play, the JWT carries end-user identity and the public key just keeps verifying the tenant context.
71
+ - Restricted by an Origin allowlist (configured per key) and per-IP rate limits.
72
+
73
+ - \`sk_live_*\`: Secret key — backend-only, never expose. Required for server-to-server work that no end-user JWT can cover: defining entity schemas (\`/v1/entity/schemas/*\`), bulk record operations (\`/v1/entity/records/bulk\`, \`/v1/entity/records/search\`), webhooks, batch jobs, and other admin tooling on the tenant's own server.
69
74
 
70
75
  ## API Key Organization Scope (B2B2C)
71
76
 
@@ -102,6 +107,38 @@ If you're building a frontend application for end-users, you should:
102
107
  2. Use the public API key (pk_live_*) for client-side requests
103
108
  3. Include X-API-Key header in ALL requests
104
109
  4. After login, also include Authorization: Bearer <jwt> for authenticated requests
110
+
111
+ ## End-User App Flow (the canonical two-phase pattern)
112
+
113
+ A common confusion: the public key looks restricted to \`/v1/auth/*\`, so developers assume they need to ship the secret key in their frontend to call agents or records. They don't. The pattern is two phases:
114
+
115
+ \`\`\`
116
+ [Tenant's app frontend in the user's browser]
117
+
118
+ │ Phase 1 — auth gateway (public key alone is enough)
119
+ │ X-API-Key: pk_live_xxx
120
+
121
+ POST /v1/auth/register ─┐
122
+ POST /v1/auth/login ├─ returns { access_token (JWT), refresh_token, user }
123
+ POST /v1/auth/refresh ─┘
124
+
125
+ │ Phase 2 — end-user features (public key + JWT)
126
+ │ X-API-Key: pk_live_xxx
127
+ │ Authorization: Bearer <end-user JWT>
128
+
129
+ GET /v1/user/agents/available
130
+ POST /v1/user/agents/{id}/chat
131
+ GET/POST/PUT/DELETE /v1/entity/records/... ← custom-entity records as your app's DB
132
+ GET /v1/plans
133
+ POST /v1/billing/checkout
134
+ GET /v1/users/me
135
+ \`\`\`
136
+
137
+ The \`sk_live_*\` key never reaches the browser. It lives only on the tenant's own server, used for:
138
+ - defining entity schemas (\`/v1/entity/schemas/*\`),
139
+ - bulk record operations (\`/v1/entity/records/bulk\`, \`/search\`),
140
+ - receiving webhooks from the platform,
141
+ - cron jobs and admin tooling.
105
142
  `,
106
143
  base_url: API_URL,
107
144
  documentation_urls: {
@@ -787,7 +824,7 @@ const END_USER_ENDPOINTS = {
787
824
  ]
788
825
  },
789
826
  billing: {
790
- description: "Billing operations - checkout, plan changes, cancellation. All endpoints require SECRET key (sk_live_*) only.",
827
+ description: "Billing operations. Two flavors:\n\n- **Tier 1 (legacy / platform-level)**: endpoints under /v1/billing/* use the platform's Stripe and a SECRET key (sk_live_*) only. They were originally for Genlobe → Tenant billing and a since-superseded Tier 2 prototype; keep using them for tenant-internal flows.\n\n- **Tier 3 (Organization → end-user, Epic #209)**: each Organization runs its OWN Stripe (BYO key). End-users subscribe via /v1/organizations/{org_id}/billing/* (api key + JWT + active OrganizationMember). The Org's plan catalog is read via the public /v1/organizations/{org_id}/plans (api key only — no JWT). See the master spec at specs/features/billing/three-tier-billing.spec.md.",
791
828
  endpoints: [
792
829
  {
793
830
  method: "GET",
@@ -880,6 +917,91 @@ const END_USER_ENDPOINTS = {
880
917
  total_invoices: "number"
881
918
  },
882
919
  note: "Returns 404 if organization not found or doesn't belong to tenant."
920
+ },
921
+ // ---------------------------------------------------------------
922
+ // Tier 3 endpoints (Epic #209). Each Organization runs its OWN
923
+ // Stripe (BYO key). These run against the org's Stripe, not the
924
+ // platform's, and require an active OrganizationMember of the path's
925
+ // org. Plans are sold by the Organization to its end-users.
926
+ // ---------------------------------------------------------------
927
+ {
928
+ method: "GET",
929
+ path: "/v1/organizations/{organization_id}/plans",
930
+ summary: "List the Organization's active plans (Tier 3) — public to anonymous visitors of the org's app",
931
+ auth: { api_key: "pk_live_* or sk_live_*", jwt: false },
932
+ response: {
933
+ items: "array of OrganizationPlan: id (uuid), organization_id (uuid), name (string), slug (string), description (string | null), price_amount (decimal), currency (ISO-4217), billing_interval ('month' | 'year'), stripe_product_id (string | null), stripe_price_id (string | null), stripe_price_id_yearly (string | null), features (array of strings), limits (object<string, int | null>), is_active (boolean), created_at (ISO datetime), updated_at (ISO datetime)",
934
+ total: "number"
935
+ },
936
+ note: "Tenant-scoped: returns 404 if the Organization doesn't belong to the API key's tenant (no existence leak across tenants). Only plans with is_active=true and stripe_price_id != null are returned — use one of those plan ids when calling /billing/checkout-session.",
937
+ example_request: `GET /v1/organizations/660e8400-e29b-41d4-a716-446655440001/plans`
938
+ },
939
+ {
940
+ method: "POST",
941
+ path: "/v1/organizations/{organization_id}/billing/checkout-session",
942
+ summary: "Start a Stripe Checkout subscription flow for the calling end-user against an Org plan (Tier 3)",
943
+ auth: { api_key: "pk_live_* or sk_live_*", jwt: true },
944
+ request_body: {
945
+ plan_id: "uuid (required) — OrganizationPlan id (NOT a Stripe price id). Get it from /v1/organizations/{organization_id}/plans.",
946
+ success_url: "string url (required) — Stripe redirects here on success.",
947
+ cancel_url: "string url (required) — Stripe redirects here if the user cancels."
948
+ },
949
+ response: {
950
+ checkout_url: "string — Stripe-hosted Checkout URL (redirect the user here).",
951
+ session_id: "string — Stripe Checkout session id (cs_*)."
952
+ },
953
+ errors: {
954
+ "403": "Caller is not an active OrganizationMember of the path's org.",
955
+ "404": "Plan not found / belongs to a different org / inactive (collapsed to the same 404 to avoid cross-org existence leaks).",
956
+ "409": "Plan has no stripe_price_id yet OR the Organization has no Stripe configured (the Org Owner must finish setup first)."
957
+ },
958
+ note: "The caller becomes the end-user customer in the Organization's Stripe account, not in the platform's. The actual Subscription row is created by the webhook handler after Stripe fires checkout.session.completed.",
959
+ example_request: `{
960
+ "plan_id": "770e8400-e29b-41d4-a716-446655440002",
961
+ "success_url": "https://claude.example.com/billing/success?cs={CHECKOUT_SESSION_ID}",
962
+ "cancel_url": "https://claude.example.com/pricing"
963
+ }`
964
+ },
965
+ {
966
+ method: "POST",
967
+ path: "/v1/organizations/{organization_id}/billing/customer-portal-session",
968
+ summary: "Open a Stripe Customer Portal session for the calling end-user (Tier 3, manage / cancel)",
969
+ auth: { api_key: "pk_live_* or sk_live_*", jwt: true },
970
+ request_body: {
971
+ return_url: "string url (required) — Stripe redirects here when the user closes the Portal."
972
+ },
973
+ response: {
974
+ portal_url: "string — Stripe-hosted Customer Portal URL."
975
+ },
976
+ errors: {
977
+ "403": "Caller is not an active OrganizationMember of the path's org.",
978
+ "409": "Caller has no Subscription / no Stripe customer for this Organization yet, OR the Organization has no Stripe configured."
979
+ },
980
+ note: "Portal allowed actions (cancel / update payment method / etc.) are configured by the Organization Owner in their own Stripe Dashboard."
981
+ },
982
+ {
983
+ method: "GET",
984
+ path: "/v1/organizations/{organization_id}/billing/subscription",
985
+ summary: "Read the calling end-user's current subscription in this Organization (Tier 3)",
986
+ auth: { api_key: "pk_live_* or sk_live_*", jwt: true },
987
+ response: {
988
+ id: "uuid",
989
+ organization_id: "uuid",
990
+ user_id: "uuid",
991
+ plan_id: "uuid | null",
992
+ status: "string | null — Stripe Subscription.status (active, past_due, canceled, …)",
993
+ billing_cycle: "string — 'monthly' or 'yearly'",
994
+ current_period_start: "ISO datetime | null",
995
+ current_period_end: "ISO datetime | null",
996
+ cancel_at_period_end: "boolean",
997
+ canceled_at: "ISO datetime | null",
998
+ trial_start: "ISO datetime | null",
999
+ trial_end: "ISO datetime | null"
1000
+ },
1001
+ errors: {
1002
+ "403": "Caller is not an active OrganizationMember of the path's org.",
1003
+ "404": "Caller has no Subscription for this Organization yet."
1004
+ }
883
1005
  }
884
1006
  ]
885
1007
  },
@@ -1026,6 +1148,29 @@ const END_USER_ENDPOINTS = {
1026
1148
  error: "string | null"
1027
1149
  },
1028
1150
  note: "Returns 402 Payment Required if usage limits exceeded."
1151
+ },
1152
+ {
1153
+ method: "GET",
1154
+ path: "/v1/organizations/{organization_id}/usage",
1155
+ summary: "Get aggregate usage for one Organization in the queried period",
1156
+ auth: { api_key: "pk_live_* or sk_live_*", jwt: true },
1157
+ query_params: {
1158
+ period_start: "ISO datetime (optional) - Inclusive start of the aggregation window (UTC). Defaults to the current calendar month.",
1159
+ period_end: "ISO datetime (optional) - Exclusive end of the aggregation window (UTC). Must be supplied together with period_start.",
1160
+ },
1161
+ response: {
1162
+ organization_id: "uuid",
1163
+ organization_name: "string | null - Human-readable name from organizations row, null if the org row was deleted",
1164
+ organization_slug: "string | null",
1165
+ period_start: "ISO datetime - resolved window start",
1166
+ period_end: "ISO datetime - resolved window end",
1167
+ messages_count: "number - Sum of messages_count across plan_usages rows for the org in the period",
1168
+ tokens_count: "number - Sum of tokens_count (input + output)",
1169
+ input_tokens_count: "number",
1170
+ output_tokens_count: "number",
1171
+ cost_usd: "string - Decimal as string (e.g. '12.345600')",
1172
+ },
1173
+ note: "Auth requires the caller to be an active OrganizationMember of the path's org. Returns a zero row when the org had no activity in the period, so callers can always render a header without a 404 fallback. Read-only — does not consume usage.",
1029
1174
  }
1030
1175
  ]
1031
1176
  },
@@ -1434,7 +1579,7 @@ const END_USER_ENDPOINTS = {
1434
1579
  method: "GET",
1435
1580
  path: "/v1/entity/schemas",
1436
1581
  summary: "List all entity schemas in the organization",
1437
- auth: { api_key: "sk_live_*", jwt: true, headers: "X-Organization-Id required" },
1582
+ auth: { api_key: "sk_live_*", jwt: false, headers: "X-Organization-Id required when key is not organization-scoped" },
1438
1583
  query_params: {
1439
1584
  page: "number (default 1)",
1440
1585
  size: "number (default 20, max 100)"
@@ -1444,26 +1589,49 @@ const END_USER_ENDPOINTS = {
1444
1589
  method: "POST",
1445
1590
  path: "/v1/entity/schemas",
1446
1591
  summary: "Create a new entity schema",
1447
- auth: { api_key: "sk_live_*", jwt: true, headers: "X-Organization-Id required" },
1592
+ auth: { api_key: "sk_live_*", jwt: false, headers: "X-Organization-Id required when key is not organization-scoped" },
1448
1593
  request_body: {
1449
1594
  name: "string (required) - unique per organization",
1450
1595
  slug: "string (required) - unique per organization, lowercase/hyphens only",
1451
1596
  description: "string (optional)",
1452
- fields_definition: "object (required) - field definitions with type, required, constraints"
1597
+ fields_definition: `object (required) - keys are field names (regex ^[a-zA-Z_][a-zA-Z0-9_]*$, max 64 chars; reserved: id, created_at, updated_at, tenant_id), values are objects with at least { "type": <one-of-the-supported-types>, "required": boolean }. Per-type shapes below.`
1598
+ },
1599
+ fields_definition_per_type: {
1600
+ string: '{"type":"string","required":true,"max_length":255,"pattern":"^[a-z]+$"} // pattern is optional regex',
1601
+ text: '{"type":"text","required":false,"max_length":5000}',
1602
+ integer: '{"type":"integer","required":true,"min":0,"max":100}',
1603
+ float: '{"type":"float","required":false,"min":0.0,"max":1.0}',
1604
+ boolean: '{"type":"boolean","required":true}',
1605
+ datetime: '{"type":"datetime","required":false}',
1606
+ enum: '{"type":"enum","required":true,"values":["draft","published","archived"]} // KEY IS "values", NOT "choices" — Genlobe rejects "choices" with 400.',
1607
+ email: '{"type":"email","required":true,"max_length":255}',
1608
+ url: '{"type":"url","required":false,"max_length":2048}',
1609
+ phone: '{"type":"phone","required":false,"max_length":20}'
1453
1610
  },
1454
- notes: "Supported field types: string, integer, float, boolean, datetime, text, enum, email, url, phone. Returns 409 if name or slug already exists."
1611
+ example_request: `{
1612
+ "name": "Blog Post",
1613
+ "slug": "blog-post",
1614
+ "description": "Journal / blog content",
1615
+ "fields_definition": {
1616
+ "title": {"type": "string", "required": true, "max_length": 255},
1617
+ "body": {"type": "text", "required": true},
1618
+ "status": {"type": "enum", "required": true, "values": ["draft", "published", "archived"]},
1619
+ "views": {"type": "integer", "required": false, "min": 0}
1620
+ }
1621
+ }`,
1622
+ notes: "Returns 409 if name or slug already exists. Common pitfall: the enum field uses 'values' (an array) — 'choices' is rejected with a 400 that names this directly. The validator runs at field level, so all errors come back together in `detail.errors[]`."
1455
1623
  },
1456
1624
  {
1457
1625
  method: "GET",
1458
1626
  path: "/v1/entity/schemas/{schema_id}",
1459
1627
  summary: "Get a specific schema by ID",
1460
- auth: { api_key: "sk_live_*", jwt: true, headers: "X-Organization-Id required" }
1628
+ auth: { api_key: "sk_live_*", jwt: false, headers: "X-Organization-Id required when key is not organization-scoped" }
1461
1629
  },
1462
1630
  {
1463
1631
  method: "PUT",
1464
1632
  path: "/v1/entity/schemas/{schema_id}",
1465
1633
  summary: "Update an existing schema",
1466
- auth: { api_key: "sk_live_*", jwt: true, headers: "X-Organization-Id required" },
1634
+ auth: { api_key: "sk_live_*", jwt: false, headers: "X-Organization-Id required when key is not organization-scoped" },
1467
1635
  request_body: {
1468
1636
  name: "string (optional)",
1469
1637
  slug: "string (optional)",
@@ -1476,47 +1644,47 @@ const END_USER_ENDPOINTS = {
1476
1644
  method: "DELETE",
1477
1645
  path: "/v1/entity/schemas/{schema_id}",
1478
1646
  summary: "Delete a schema (may fail if records exist)",
1479
- auth: { api_key: "sk_live_*", jwt: true, headers: "X-Organization-Id required" }
1647
+ auth: { api_key: "sk_live_*", jwt: false, headers: "X-Organization-Id required when key is not organization-scoped" }
1480
1648
  },
1481
- // ── End-User Record Endpoints ──
1649
+ // ── Entity Record Endpoints (tenant-scoped or user-scoped) ──
1482
1650
  {
1483
1651
  method: "POST",
1484
1652
  path: "/v1/entity/records",
1485
1653
  summary: "Create a new entity record",
1486
- auth: { api_key: "sk_live_* or pk_live_*", jwt: true, headers: "X-Organization-Id required" },
1654
+ auth: { api_key: "sk_live_* or pk_live_*", jwt: false, headers: "X-Organization-Id required when key is not organization-scoped" },
1487
1655
  request_body: {
1488
1656
  schema_id: "uuid (required)",
1489
1657
  data: "object (required) - validated against schema fields_definition"
1490
1658
  },
1491
- notes: "Sets created_by_id to the authenticated end-user."
1659
+ notes: "Two modes: (1) sk_live_* without JWT -> tenant-scoped record, created_by_id is NULL (e.g. catalog/blog/global config). (2) API key + end-user JWT -> user-scoped record, created_by_id = user.id. The endpoint accepts both shapes; ownership is determined by whether a JWT is present."
1492
1660
  },
1493
1661
  {
1494
1662
  method: "GET",
1495
1663
  path: "/v1/entity/records/{record_id}",
1496
1664
  summary: "Get a specific record by ID",
1497
- auth: { api_key: "sk_live_* or pk_live_*", jwt: true, headers: "X-Organization-Id required" }
1665
+ auth: { api_key: "sk_live_* or pk_live_*", jwt: false, headers: "X-Organization-Id required when key is not organization-scoped" }
1498
1666
  },
1499
1667
  {
1500
1668
  method: "PUT",
1501
1669
  path: "/v1/entity/records/{record_id}",
1502
1670
  summary: "Update a record",
1503
- auth: { api_key: "sk_live_* or pk_live_*", jwt: true, headers: "X-Organization-Id required" },
1671
+ auth: { api_key: "sk_live_* or pk_live_*", jwt: false, headers: "X-Organization-Id required when key is not organization-scoped" },
1504
1672
  request_body: {
1505
1673
  data: "object (required) - validated against schema fields_definition"
1506
1674
  },
1507
- notes: "Sets updated_by_id to the authenticated end-user."
1675
+ notes: "updated_by_id follows the same rule as create: NULL for tenant-scoped (sk_live_* alone) or user.id for user-scoped (API key + JWT)."
1508
1676
  },
1509
1677
  {
1510
1678
  method: "DELETE",
1511
1679
  path: "/v1/entity/records/{record_id}",
1512
1680
  summary: "Delete a record",
1513
- auth: { api_key: "sk_live_* or pk_live_*", jwt: true, headers: "X-Organization-Id required" }
1681
+ auth: { api_key: "sk_live_* or pk_live_*", jwt: false, headers: "X-Organization-Id required when key is not organization-scoped" }
1514
1682
  },
1515
1683
  {
1516
1684
  method: "POST",
1517
1685
  path: "/v1/entity/records/search",
1518
1686
  summary: "Advanced search for entity records with filters and ordering",
1519
- auth: { api_key: "sk_live_*", jwt: true, headers: "X-Organization-Id required" },
1687
+ auth: { api_key: "sk_live_* or pk_live_*", jwt: false, headers: "X-Organization-Id required when key is not organization-scoped" },
1520
1688
  request_body: {
1521
1689
  schema_id: "uuid (optional) - filter by schema",
1522
1690
  query: "object (optional) - filters: {field: value} for equality, {field: {operator: 'gt', value: 100}} for operators"
@@ -2570,6 +2738,16 @@ for those.
2570
2738
  session token: keep it in memory or HTTP-only cookies, never in
2571
2739
  localStorage if XSS is a real threat for your app.
2572
2740
 
2741
+ ## ⚠️ For agents: don't invent end-user credentials
2742
+
2743
+ If your task requires acting as an end-user (calling a \`jwt: true\`
2744
+ endpoint) and the human has not given you an email + password, **stop
2745
+ and ask**. Do not register a new end-user with an email you generated
2746
+ (\`admin@<their-domain>\`, \`seed@<their-domain>\`, etc.) — invented
2747
+ addresses bounce, AWS SES suppresses the domain at the account level,
2748
+ and the tenant loses email delivery to that destination. See
2749
+ \`get_authentication_flow\` for the full guardrail.
2750
+
2573
2751
  ## Recommended next step
2574
2752
 
2575
2753
  Run \`recommend_stack\` with the kind of app you want to build to get a
@@ -2642,6 +2820,22 @@ browser app holds for that session.
2642
2820
  Refuse — the key belongs in env.
2643
2821
  - Logging \`console.log(headers)\` in middleware on a public-facing app.
2644
2822
 
2823
+ ## ⚠️ For agents: don't invent end-user credentials
2824
+
2825
+ A secret key gives you tenant-admin reach, but it does **not** give you
2826
+ the right to fabricate end-user accounts. If a task requires acting as
2827
+ an end-user (calling a \`jwt: true\` endpoint) and the human has not
2828
+ given you an email + password, **stop and ask** them to either share
2829
+ existing credentials or create an end-user in the dashboard. Inventing
2830
+ an email (\`admin@<their-domain>\`, \`seed@<their-domain>\`, etc.) leads
2831
+ to bounces, account-level SES suppression, and email delivery being
2832
+ locked out for that destination — see incident #168 for the canonical
2833
+ example. \`get_authentication_flow\` has the full guardrail.
2834
+
2835
+ Note also: \`/v1/entity/schemas\` is **not** a \`jwt: true\` endpoint —
2836
+ it accepts \`sk_live_*\` alone. Don't register an end-user just to call
2837
+ it. Read \`get_endpoint_details\` first.
2838
+
2645
2839
  ## Recommended next step
2646
2840
 
2647
2841
  Run \`recommend_stack\` with your app_type to get a server-aware framework
@@ -3445,6 +3639,17 @@ Use search_endpoints tool to find available endpoints.`,
3445
3639
  type: "text",
3446
3640
  text: `# Authentication Flow Guide
3447
3641
 
3642
+ > ## ⚠️ When you (the agent) need an end-user JWT
3643
+ >
3644
+ > Some endpoints document themselves as \`jwt: true\` (e.g. some \`/v1/entity/records\` modes, profile updates, anything user-scoped). If your task requires calling one of these and you do **not** have an end-user email and password supplied by the human you are working with:
3645
+ >
3646
+ > 1. **Stop.** Ask the human for credentials of an existing end-user, or ask them to create one in the dashboard and give you the credentials.
3647
+ > 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.
3648
+ > 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.
3649
+ > 4. If the human says \"register a user with this email: ...\" — fine, that's an explicit instruction. Inventing one is not.
3650
+
3651
+ ---
3652
+
3448
3653
  ## 1. Registration Flow
3449
3654
 
3450
3655
  \`\`\`typescript
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@genlobe/mcp-server",
3
- "version": "3.1.3",
3
+ "version": "3.3.0",
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": {