@genlobe/mcp-server 3.5.1 → 3.6.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 (3) hide show
  1. package/README.md +3 -2
  2. package/dist/index.js +361 -26
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -82,7 +82,7 @@ Edit `claude_desktop_config.json`:
82
82
 
83
83
  ## Tools
84
84
 
85
- 18 tools, in four groups.
85
+ 19 tools, in four groups.
86
86
 
87
87
  ### Documentation (offline, no API key needed)
88
88
 
@@ -90,7 +90,8 @@ Edit `claude_desktop_config.json`:
90
90
  |---|---|
91
91
  | `get_api_overview` | High-level architecture, auth model, key concepts, recommended call order. **Start here.** |
92
92
  | `list_endpoint_categories` | Bare list of end-user endpoint categories plus a one-line summary of each. ~30 tokens. Call this once when you don't already know what's available; then use `get_end_user_endpoints({ category })` for the actual docs. |
93
- | `get_end_user_endpoints` | Reference for end-user endpoints in ONE category. Pass `category` (one of `authentication`, `organizations`, `subscriptions`, `billing`, `usage`, `agents`, `users`, `plans`, `ai`, `entities`, `departments`, `files`, `external_agents`). The `billing` category now also covers the Organization-admin read-only surface (`/v1/organization-admin/{org_id}/plans`, `/v1/organization-admin/{org_id}/config/stripe`, `/v1/organization-admin/{org_id}/audit`). Calling without a category returns the full catalog — use sparingly. |
93
+ | `get_end_user_endpoints` | Reference for end-user endpoints in ONE category. Pass `category` (one of `authentication`, `organizations`, `subscriptions`, `billing`, `organization_admin`, `usage`, `agents`, `users`, `plans`, `ai`, `entities`, `departments`, `files`, `external_agents`). The `billing` category covers Tier-3 admin read-only billing endpoints (`/v1/organization-admin/{org_id}/{plans, config/stripe, audit, subscriptions}`); the new `organization_admin` category covers the non-billing Org Owner Dashboard surfaces (members, customers, agents, knowledge bases, agent-change notifications, Stripe webhook audit). Calling without a category returns the full catalog — use sparingly. |
94
+ | `get_reserved_schema_slugs` | Returns the 53 slugs the Custom Entities subsystem refuses at `POST /v1/entity/schemas` (ADR-0011 — they collide with native DB tables: `users`, `conversations`, `agents`, …). Pure data, no network call. Use it to pre-validate before issuing the POST and avoid a `400` round-trip. |
94
95
  | `get_request_headers` | The exact HTTP headers required for end-user requests, with examples. |
95
96
  | `get_authentication_flow` | Step-by-step register → login → refresh → logout, including token storage and refresh-on-401 patterns. |
96
97
  | `get_common_patterns` | Pagination, error handling, optimistic updates, file upload, RAG queries, agent execution. |
package/dist/index.js CHANGED
@@ -192,6 +192,82 @@ The \`sk_live_*\` key never reaches the browser. It lives only on the tenant's o
192
192
  }
193
193
  };
194
194
  // =============================================================================
195
+ // Reserved schema slugs (ADR-0011)
196
+ // =============================================================================
197
+ //
198
+ // Mirrors `RESERVED_SCHEMA_SLUGS` in
199
+ // `src/tenants/services/custom_data_schema_service.py`. The backend is the
200
+ // authoritative source — this list is documentation-only and is exposed
201
+ // through the `get_reserved_schema_slugs` tool so agents can pre-validate
202
+ // before POSTing to /v1/entity/schemas.
203
+ //
204
+ // When the backend list changes (a new native __tablename__ is added),
205
+ // this constant MUST be updated in the same MCP PR.
206
+ const RESERVED_SCHEMA_SLUGS = Object.freeze([
207
+ // --- Alembic / Postgres internals -------------------------------------
208
+ "alembic_version",
209
+ // --- Tenants domain ---------------------------------------------------
210
+ "tenants",
211
+ "tenant_members",
212
+ "tenant_configs",
213
+ "tenant_password_reset_tokens",
214
+ "api_keys",
215
+ "email_logs",
216
+ "email_domains",
217
+ "email_verification_codes",
218
+ "custom_data_schemas",
219
+ "custom_data_records",
220
+ // --- Organizations domain --------------------------------------------
221
+ "organizations",
222
+ "organization_members",
223
+ "organization_configs",
224
+ "organization_invitations",
225
+ "departments",
226
+ "permissions",
227
+ "role_permissions",
228
+ // --- Users / Auth -----------------------------------------------------
229
+ "users",
230
+ "user_settings",
231
+ "password_reset_tokens",
232
+ // --- Subscriptions / Billing -----------------------------------------
233
+ "plans",
234
+ "tenant_plans",
235
+ "organization_plans",
236
+ "subscriptions",
237
+ "tenant_subscriptions",
238
+ "tenant_usages",
239
+ "tenant_usage_details",
240
+ "organization_usage_details",
241
+ "plan_usages",
242
+ "usage_logs",
243
+ "billing_audit_log",
244
+ "billing_meters",
245
+ "processed_stripe_events",
246
+ // --- AI domain --------------------------------------------------------
247
+ "agents",
248
+ "agent_executions",
249
+ "agent_tools",
250
+ "tool_executions",
251
+ "tool_secrets",
252
+ "external_agents",
253
+ "external_agent_calls",
254
+ "conversations",
255
+ "conversation_messages",
256
+ "knowledge_bases",
257
+ "documents",
258
+ "document_chunks",
259
+ // --- Files / Drive ---------------------------------------------------
260
+ "tenant_files",
261
+ "user_files",
262
+ "folders",
263
+ // --- Workflows (no Python model; defined only in migration) ---------
264
+ "workflows",
265
+ "workflow_executions",
266
+ "node_executions",
267
+ // --- Admin domain ----------------------------------------------------
268
+ "admin_audit_logs",
269
+ ]);
270
+ // =============================================================================
195
271
  // End-User Endpoints (for building frontends)
196
272
  // =============================================================================
197
273
  const END_USER_ENDPOINTS = {
@@ -223,7 +299,8 @@ const END_USER_ENDPOINTS = {
223
299
  display_name: "string | null",
224
300
  avatar_url: "string | null",
225
301
  is_active: "boolean",
226
- created_at: "ISO datetime"
302
+ created_at: "ISO datetime",
303
+ profile_data: "object — Tenant-defined free-form metadata for this user. Defaults to {} when empty (never null). Mutate via PATCH /v1/users/me/profile-data. ADR-0010."
227
304
  }
228
305
  },
229
306
  response_when_verification_required: {
@@ -275,7 +352,8 @@ const END_USER_ENDPOINTS = {
275
352
  display_name: "string | null",
276
353
  avatar_url: "string | null",
277
354
  is_active: "boolean",
278
- created_at: "ISO datetime"
355
+ created_at: "ISO datetime",
356
+ profile_data: "object — Tenant-defined free-form metadata. Defaults to {} when empty (never null). ADR-0010."
279
357
  }
280
358
  },
281
359
  {
@@ -1101,29 +1179,217 @@ const END_USER_ENDPOINTS = {
1101
1179
  "403": "Caller is not an OrganizationMember with role owner/admin of the path's org."
1102
1180
  }
1103
1181
  },
1182
+ // ---------------------------------------------------------------
1183
+ // Org-admin Subscription list (Tier-3, end-user subscriptions view).
1184
+ // Distinct from the END-USER read at GET /v1/organizations/{id}/billing/subscription
1185
+ // (which returns ONLY the caller's own subscription). This one is
1186
+ // the dashboard's "list of paying customers" surface.
1187
+ // ---------------------------------------------------------------
1104
1188
  {
1105
1189
  method: "GET",
1106
- path: "/v1/organization-admin/{organization_id}/audit",
1107
- summary: "Paginated billing audit log for the Organization",
1190
+ path: "/v1/organization-admin/{organization_id}/subscriptions",
1191
+ summary: "List end-user subscriptions for the Organization (admin)",
1108
1192
  auth: { api_key: "pk_live_* or sk_live_*", jwt: true, require_org_admin: true },
1109
1193
  query_params: {
1110
- limit: "integer (default 100, 1 limit 500)",
1111
- offset: "integer (default 0, ≥ 0)",
1112
- action_prefix: "string (optional) — filter by action prefix, e.g. 'config.stripe' or 'webhook.security'."
1194
+ status: "string (optional) comma-separated subscription statuses to filter (e.g. 'active,trialing,past_due'). Omit for all."
1113
1195
  },
1114
1196
  response: {
1115
- items: "array of BillingAuditLogEntry: { id (uuid), organization_id (uuid), actor_user_id (uuid | null), actor_role (string), action (string, e.g. 'config.stripe.set'), target_type (string | null), target_id (string | null), before_json (any | null), after_json (any | null), note (string | null), created_at (ISO datetime) }. Reverse-chronological.",
1116
- total: "number",
1117
- limit: "integer (echo)",
1118
- offset: "integer (echo)"
1197
+ items: "array of SubscriptionResponse every Subscription row scoped to this Organization, sorted newest-first.",
1198
+ total: "number"
1119
1199
  },
1120
- note: "Audit entries are pre-masked at write time `before_json` / `after_json` for config.stripe.* contain `secret_key_display` ('sk_live_****Pnxs'), never the raw key.",
1200
+ note: "Active statuses for the 'paying customers' count are: active, trialing, past_due. canceled / incomplete_expired / stripe_disconnected do NOT count as active.",
1121
1201
  errors: {
1122
1202
  "403": "Caller is not an OrganizationMember with role owner/admin of the path's org."
1123
1203
  }
1124
1204
  }
1125
1205
  ]
1126
1206
  },
1207
+ organization_admin: {
1208
+ description: "Organization Owner / Admin surface that an agent uses while BUILDING the Tenant's product — team management (`members`), AI resource management (`agents` visibility, `knowledge-bases` + `documents` CRUD). Excludes observability endpoints (audit log, webhook delivery history, admin notifications, customer CRM list) — those live in the dashboards for humans, not in the MCP. Auth pattern across all of these: pk_live_*/sk_live_* + end-user JWT + `require_org_admin` (OrganizationMember.role IN owner/admin). For billing-specific admin endpoints (Tier-3 plans, Stripe config status) see the `billing` category. Secret-setting paths go through redirect-URL tools (`get_stripe_config_url`, `get_plan_management_url`, `get_agent_secrets_url`).",
1209
+ endpoints: [
1210
+ {
1211
+ method: "GET",
1212
+ path: "/v1/organization-admin/{organization_id}/members",
1213
+ summary: "List active members of the Organization (admin view)",
1214
+ auth: { api_key: "pk_live_* or sk_live_*", jwt: true, require_org_admin: true },
1215
+ response: {
1216
+ items: "array of OrganizationMemberResponse: { user_id (uuid), email (string), display_name (string | null), avatar_url (string | null), role ('owner'|'admin'|'member'|'developer'), is_active (boolean), joined_at (ISO datetime) }",
1217
+ total: "number"
1218
+ },
1219
+ note: "Subset of /v1/organizations/{org_id}/members but enforces require_org_admin (rejects TenantMember JWT). Use this when building the Org Owner dashboard's Members page.",
1220
+ errors: {
1221
+ "403": "Caller is not an OrganizationMember with role owner/admin of the path's org."
1222
+ }
1223
+ },
1224
+ {
1225
+ method: "PATCH",
1226
+ path: "/v1/organization-admin/{organization_id}/members/{member_user_id}/role",
1227
+ summary: "Update a member's role within the Organization",
1228
+ auth: { api_key: "pk_live_* or sk_live_*", jwt: true, require_org_admin: true },
1229
+ request_body: {
1230
+ role: "string (required) — one of 'owner', 'admin', 'member', 'developer'."
1231
+ },
1232
+ response: "OrganizationMemberResponse — the updated member row.",
1233
+ notes: "Service-level guards: only owners can promote to owner; admins cannot touch owners; the last active owner cannot be demoted (returns 400).",
1234
+ errors: {
1235
+ "400": "Last-owner demotion guard / target not a member.",
1236
+ "403": "Insufficient role for the requested transition (e.g. admin trying to touch an owner).",
1237
+ "404": "Member not found in this organization."
1238
+ }
1239
+ },
1240
+ {
1241
+ method: "DELETE",
1242
+ path: "/v1/organization-admin/{organization_id}/members/{member_user_id}",
1243
+ summary: "Remove a member from the Organization",
1244
+ auth: { api_key: "pk_live_* or sk_live_*", jwt: true, require_org_admin: true },
1245
+ response: "204 No Content",
1246
+ notes: "Service-level guards: only owners can remove other owners; the last active owner cannot be removed.",
1247
+ errors: {
1248
+ "400": "Last-owner removal guard.",
1249
+ "403": "Insufficient role for removal (e.g. admin trying to remove an owner).",
1250
+ "404": "Member not found in this organization."
1251
+ }
1252
+ },
1253
+ {
1254
+ method: "GET",
1255
+ path: "/v1/organization-admin/{organization_id}/agents",
1256
+ summary: "List agents visible to the Organization (Hybrid Catalog — ADR-0006 v2)",
1257
+ auth: { api_key: "pk_live_* or sk_live_*", jwt: true, require_org_admin: true },
1258
+ query_params: {
1259
+ include_catalog: "boolean (default true) — when true, includes public agents from the Tenant's root Organization (the catalog). When false, returns only agents owned by this Organization.",
1260
+ skip: "integer (default 0, ≥ 0)",
1261
+ limit: "integer (default 100, 1 ≤ limit ≤ 200)"
1262
+ },
1263
+ response: {
1264
+ items: "array of AgentResponse: { id, tenant_id, organization_id, is_public, forked_from_agent_id, name, description, system_prompt, response_type, rag_provider, rag_role, is_active, rag_config, mcp_servers, created_by, created_at, updated_at }",
1265
+ total: "number"
1266
+ },
1267
+ note: "Visibility rules (ADR-0006 v2 Hybrid Catalog): agents where organization_id == this Org UNION (if include_catalog=true) agents where is_public=true AND organization_id is the Tenant's root Organization. To customize a catalog agent, the Org forks it via POST /v1/agents/{id}/fork (Tenant Dashboard endpoint — not exposed to end-users in the MCP).",
1268
+ errors: {
1269
+ "403": "Caller is not an OrganizationMember with role owner/admin of the path's org."
1270
+ }
1271
+ },
1272
+ // ---------------------------------------------------------------
1273
+ // Knowledge Base management (per-Org, ADR-0006 v2 §rag_role).
1274
+ // Org-scoped CRUD on KnowledgeBase + Document. Replaces the legacy
1275
+ // single-store rag_config.store_id path (closes cross-org RAG leak G5).
1276
+ // ---------------------------------------------------------------
1277
+ {
1278
+ method: "POST",
1279
+ path: "/v1/organization-admin/{organization_id}/knowledge-bases",
1280
+ summary: "Create a KnowledgeBase in the caller's Organization",
1281
+ auth: { api_key: "pk_live_* or sk_live_*", jwt: true, require_org_admin: true },
1282
+ request_body: {
1283
+ name: "string (optional, max 200)",
1284
+ description: "string (optional)",
1285
+ role_tag: "string (optional, max 100) — Declarative tag matched against Agent.rag_role at runtime. When an Agent is invoked, the runtime looks up a KB in the caller's Org with role_tag == agent.rag_role and uses its vector store.",
1286
+ vector_store_config: "object (optional) — provider-specific vector-store settings (e.g. OpenAI File Search store id).",
1287
+ embedding_config: "object (optional) — provider-specific embedding settings."
1288
+ },
1289
+ response: {
1290
+ id: "uuid",
1291
+ organization_id: "uuid",
1292
+ name: "string | null",
1293
+ description: "string | null",
1294
+ role_tag: "string | null",
1295
+ is_active: "boolean",
1296
+ is_processing: "boolean"
1297
+ },
1298
+ notes: "KBs are per-Organization (KnowledgeBase.organization_id REQUIRED). Agents bind to per-Org KBs via the `rag_role` tag — the Agent declares `rag_role='support'` and the runtime finds the calling Org's KB with `role_tag='support'`. Multiple Orgs can have a KB with the same `role_tag`; each Org sees only its own."
1299
+ },
1300
+ {
1301
+ method: "GET",
1302
+ path: "/v1/organization-admin/{organization_id}/knowledge-bases",
1303
+ summary: "List KnowledgeBases in the caller's Organization",
1304
+ auth: { api_key: "pk_live_* or sk_live_*", jwt: true, require_org_admin: true },
1305
+ query_params: {
1306
+ include_inactive: "boolean (default false)",
1307
+ skip: "integer (default 0, ≥ 0)",
1308
+ limit: "integer (default 100, 1 ≤ limit ≤ 200)"
1309
+ },
1310
+ response: "Array of KnowledgeBaseResponse (same shape as POST response)."
1311
+ },
1312
+ {
1313
+ method: "GET",
1314
+ path: "/v1/organization-admin/{organization_id}/knowledge-bases/{kb_id}",
1315
+ summary: "Get a KnowledgeBase by ID",
1316
+ auth: { api_key: "pk_live_* or sk_live_*", jwt: true, require_org_admin: true },
1317
+ response: "KnowledgeBaseResponse",
1318
+ errors: {
1319
+ "404": "KB not found OR kb.organization_id != path organization_id."
1320
+ }
1321
+ },
1322
+ {
1323
+ method: "PATCH",
1324
+ path: "/v1/organization-admin/{organization_id}/knowledge-bases/{kb_id}",
1325
+ summary: "Update a KnowledgeBase (partial)",
1326
+ auth: { api_key: "pk_live_* or sk_live_*", jwt: true, require_org_admin: true },
1327
+ request_body: {
1328
+ name: "string (optional)",
1329
+ description: "string (optional)",
1330
+ role_tag: "string (optional)",
1331
+ vector_store_config: "object (optional)",
1332
+ embedding_config: "object (optional)",
1333
+ is_active: "boolean (optional)"
1334
+ },
1335
+ response: "KnowledgeBaseResponse"
1336
+ },
1337
+ {
1338
+ method: "DELETE",
1339
+ path: "/v1/organization-admin/{organization_id}/knowledge-bases/{kb_id}",
1340
+ summary: "Soft delete a KnowledgeBase (is_active=false). Documents survive for audit.",
1341
+ auth: { api_key: "pk_live_* or sk_live_*", jwt: true, require_org_admin: true },
1342
+ response: "204 No Content"
1343
+ },
1344
+ {
1345
+ method: "POST",
1346
+ path: "/v1/organization-admin/{organization_id}/knowledge-bases/{kb_id}/documents",
1347
+ summary: "Upload a document to a KnowledgeBase",
1348
+ auth: { api_key: "pk_live_* or sk_live_*", jwt: true, require_org_admin: true },
1349
+ request_body: "multipart/form-data with `file` (single file). File size hard cap: 100MB (413 if exceeded).",
1350
+ response: {
1351
+ id: "integer",
1352
+ knowledge_base_id: "uuid",
1353
+ organization_id: "uuid",
1354
+ filename: "string",
1355
+ file_size: "integer (bytes)",
1356
+ content_type: "string",
1357
+ status: "string — UPLOADED initially; worker layer transitions to PROCESSING/READY/FAILED.",
1358
+ error_message: "string | null"
1359
+ },
1360
+ notes: "The controller only records metadata; the worker layer consumes Document rows with status=UPLOADED and pushes to the storage provider. Cross-Org uploads (kb.organization_id != caller's Org) return 404, never 403.",
1361
+ errors: {
1362
+ "400": "Empty file or missing filename.",
1363
+ "413": "File size exceeds 100MB."
1364
+ }
1365
+ },
1366
+ {
1367
+ method: "GET",
1368
+ path: "/v1/organization-admin/{organization_id}/knowledge-bases/{kb_id}/documents",
1369
+ summary: "List documents in a KnowledgeBase",
1370
+ auth: { api_key: "pk_live_* or sk_live_*", jwt: true, require_org_admin: true },
1371
+ query_params: {
1372
+ skip: "integer (default 0, ≥ 0)",
1373
+ limit: "integer (default 100, 1 ≤ limit ≤ 200)"
1374
+ },
1375
+ response: "Array of DocumentResponse."
1376
+ },
1377
+ {
1378
+ method: "GET",
1379
+ path: "/v1/organization-admin/{organization_id}/knowledge-bases/{kb_id}/documents/{document_id}",
1380
+ summary: "Get a document by ID",
1381
+ auth: { api_key: "pk_live_* or sk_live_*", jwt: true, require_org_admin: true },
1382
+ response: "DocumentResponse"
1383
+ },
1384
+ {
1385
+ method: "DELETE",
1386
+ path: "/v1/organization-admin/{organization_id}/knowledge-bases/{kb_id}/documents/{document_id}",
1387
+ summary: "Delete a document from a KnowledgeBase",
1388
+ auth: { api_key: "pk_live_* or sk_live_*", jwt: true, require_org_admin: true },
1389
+ response: "204 No Content"
1390
+ }
1391
+ ]
1392
+ },
1127
1393
  usage: {
1128
1394
  description: "Usage tracking and limits. Endpoints map to /v1/api/usage/* (current/history) and /v1/usage/* (stats/check/consume).",
1129
1395
  endpoints: [
@@ -1467,8 +1733,26 @@ const END_USER_ENDPOINTS = {
1467
1733
  ]
1468
1734
  },
1469
1735
  users: {
1470
- description: "User management - CRUD, settings, activation/deactivation. Dual auth: sk_live_* for admin ops, pk_live_* + JWT for self-service.",
1736
+ description: "User management - CRUD, settings, activation/deactivation, and Tenant-defined `profile_data` extensions (ADR-0010). Dual auth: sk_live_* for admin ops, pk_live_* + JWT for self-service.",
1471
1737
  endpoints: [
1738
+ {
1739
+ method: "PATCH",
1740
+ path: "/v1/users/me/profile-data",
1741
+ summary: "Merge keys into the authenticated user's profile_data (ADR-0010)",
1742
+ auth: { api_key: "pk_live_* or sk_live_*", jwt: true },
1743
+ request_body: {
1744
+ profile_data: "object (required) — Top-level keys to merge into the existing profile_data JSONB. Keys present here overwrite same-named keys; keys absent are preserved. To clear a key, send it with value `null`. To clear the whole document, a future DELETE endpoint will be added (not implemented yet)."
1745
+ },
1746
+ response: "UserResponse — the updated row, including the merged profile_data. Same shape as GET /v1/users/{user_id}.",
1747
+ notes: "Merge semantics, NOT replace. The target user_id is taken from the decoded JWT — there is no body/path parameter that can redirect the write, so a user can ONLY edit their own profile_data. There is no admin-override path on this endpoint by design (use PUT /v1/users/{user_id} with sk_live_* if a Tenant admin needs to mutate someone else's row; that endpoint does NOT touch profile_data today). The platform does NOT validate against any schema — the Tenant owns the shape end-to-end. PII warning: do NOT store unencrypted PII (passwords, SSNs, card numbers); the column is plaintext at rest and ships on every /me call. Tenants needing per-Org-scoped or schema-validated user metadata can layer a custom entity (e.g. `user_profiles`) on top.",
1748
+ example_request: `{
1749
+ "profile_data": {
1750
+ "phone": "+1-555-0123",
1751
+ "preferences": { "theme": "dark", "lang": "en" },
1752
+ "premium_tier": "gold"
1753
+ }
1754
+ }`
1755
+ },
1472
1756
  {
1473
1757
  method: "POST",
1474
1758
  path: "/v1/users",
@@ -1498,6 +1782,7 @@ const END_USER_ENDPOINTS = {
1498
1782
  status: "string",
1499
1783
  current_plan_id: "uuid | null",
1500
1784
  default_organization_id: "uuid | null",
1785
+ profile_data: "object — Tenant-defined free-form metadata (ADR-0010). Defaults to {} when empty (POST does NOT seed this — mutate via PATCH /v1/users/me/profile-data afterward).",
1501
1786
  created_at: "ISO datetime",
1502
1787
  updated_at: "ISO datetime",
1503
1788
  settings: "UserSettingsResponse | null"
@@ -1555,6 +1840,7 @@ const END_USER_ENDPOINTS = {
1555
1840
  status: "string",
1556
1841
  current_plan_id: "uuid | null",
1557
1842
  default_organization_id: "uuid | null",
1843
+ profile_data: "object — Tenant-defined free-form metadata (ADR-0010). Defaults to {} when empty.",
1558
1844
  created_at: "ISO datetime",
1559
1845
  updated_at: "ISO datetime",
1560
1846
  settings: "UserSettingsResponse | null (only if include_settings=true)"
@@ -1695,7 +1981,7 @@ const END_USER_ENDPOINTS = {
1695
1981
  ]
1696
1982
  },
1697
1983
  entities: {
1698
- description: "Entity management (Custom Data) - define schemas and manage records. Two contexts: End-User API (API Key + JWT + X-Organization-Id) and Dashboard (Tenant JWT).",
1984
+ description: "Entity management (Custom Data) - define schemas and manage records. Two contexts: End-User API (API Key + JWT + X-Organization-Id) and Dashboard (Tenant JWT). Phase 3 additions: `reference` field type + cross-schema `join` clauses (ADR-0009), and reserved-slug enforcement (ADR-0011). Use `get_reserved_schema_slugs()` to pre-validate slugs.",
1699
1985
  endpoints: [
1700
1986
  // ── End-User Schema Endpoints ──
1701
1987
  {
@@ -1729,7 +2015,8 @@ const END_USER_ENDPOINTS = {
1729
2015
  enum: '{"type":"enum","required":true,"values":["draft","published","archived"]} // KEY IS "values", NOT "choices" — Genlobe rejects "choices" with 400.',
1730
2016
  email: '{"type":"email","required":true,"max_length":255}',
1731
2017
  url: '{"type":"url","required":false,"max_length":2048}',
1732
- phone: '{"type":"phone","required":false,"max_length":20}'
2018
+ phone: '{"type":"phone","required":false,"max_length":20}',
2019
+ reference: '{"type":"reference","target_schema_slug":"users","required":true,"on_delete":"set_null"} // ADR-0009. target_schema_slug must be lowercase letters/numbers/hyphens and reference an existing schema slug in the same Org — use the literal "users" for the native users table. on_delete is one of "set_null" (default), "restrict", or "cascade". Self-references (target_schema_slug == this schema\'s slug) are rejected with 400.'
1733
2020
  },
1734
2021
  example_request: `{
1735
2022
  "name": "Blog Post",
@@ -1739,10 +2026,11 @@ const END_USER_ENDPOINTS = {
1739
2026
  "title": {"type": "string", "required": true, "max_length": 255},
1740
2027
  "body": {"type": "text", "required": true},
1741
2028
  "status": {"type": "enum", "required": true, "values": ["draft", "published", "archived"]},
1742
- "views": {"type": "integer", "required": false, "min": 0}
2029
+ "views": {"type": "integer", "required": false, "min": 0},
2030
+ "author": {"type": "reference", "target_schema_slug": "users", "required": true, "on_delete": "restrict"}
1743
2031
  }
1744
2032
  }`,
1745
- 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[]`."
2033
+ notes: "Returns 409 if name or slug already exists. Returns 400 if the slug collides with a native DB table (e.g. `users`, `conversations`, `agents`, `subscriptions`, ...) — ADR-0011 reserves 53 such slugs (case-insensitive). Call `get_reserved_schema_slugs()` to get the full list and pre-validate before POSTing. Common pitfall: the enum field uses 'values' (an array) — 'choices' is rejected with a 400 that names this directly. Reference fields (ADR-0009) are validated app-side (no SQL FK from JSONB) — `target_schema_slug` MUST point at an existing schema in the same Organization, or the literal `users` for the native users table. The validator runs at field level, so all errors come back together in `detail.errors[]`."
1746
2034
  },
1747
2035
  {
1748
2036
  method: "GET",
@@ -1818,11 +2106,12 @@ const END_USER_ENDPOINTS = {
1818
2106
  {
1819
2107
  method: "POST",
1820
2108
  path: "/v1/entity/records/search",
1821
- summary: "Advanced search for entity records with filters and ordering",
2109
+ summary: "Advanced search for entity records with filters, ordering, and cross-schema joins (ADR-0009)",
1822
2110
  auth: { api_key: "sk_live_* or pk_live_*", jwt: false, headers: "X-Organization-Id required when key is not organization-scoped" },
1823
2111
  request_body: {
1824
2112
  schema_id: "uuid (optional) - filter by schema",
1825
- query: "object (optional) - filters: {field: value} for equality, {field: {operator: 'gt', value: 100}} for operators"
2113
+ query: "object (optional) - filters: {field: value} for equality, {field: {operator: 'gt', value: 100}} for operators",
2114
+ join: "array (optional, ADR-0009) — Cross-schema inner-join clauses against reference fields. Each item: { 'field': string (reference field on source schema), 'target_schema_slug': string (must match the field's declared target; use 'users' for the native users table), 'where': object (optional, JSONB equality predicates applied to the target's data; empty means 'just confirm the target exists') }. Multiple clauses are AND-ed. Inner-join only — no left/right. The target's fields are NOT returned to the caller; joins are filter-only. `where` does NOT support nested joins. Implemented as one EXISTS sub-select per clause (no N+1)."
1826
2115
  },
1827
2116
  query_params: {
1828
2117
  page: "number (default 1)",
@@ -1830,14 +2119,21 @@ const END_USER_ENDPOINTS = {
1830
2119
  order_by: "string (default 'created_at')",
1831
2120
  order_dir: "'asc' | 'desc' (default 'desc')"
1832
2121
  },
1833
- notes: "Supported operators: eq, ne, gt, gte, lt, lte, like, ilike, in, not_in",
2122
+ notes: "Supported operators in `query`: eq, ne, gt, gte, lt, lte, like, ilike, in, not_in. Row-level scope (ADR-0007): regular end-users (role=member) only see records where created_by_id = current_user.id; OrgAdmin and TenantMember bypass via include_all=true (audit-logged). Use the simpler GET /v1/entity/records/mine for the common 'my data' query.",
1834
2123
  example_request: `{
1835
2124
  "schema_id": "550e8400-e29b-41d4-a716-446655440000",
1836
2125
  "query": {
1837
2126
  "price": { "operator": "gt", "value": 100 },
1838
2127
  "category": "electronics",
1839
2128
  "status": { "operator": "in", "value": ["active", "pending"] }
1840
- }
2129
+ },
2130
+ "join": [
2131
+ {
2132
+ "field": "author",
2133
+ "target_schema_slug": "users",
2134
+ "where": { "is_active": true }
2135
+ }
2136
+ ]
1841
2137
  }`
1842
2138
  }
1843
2139
  ]
@@ -3231,21 +3527,26 @@ burns context quickly.
3231
3527
  If you don't know which categories exist yet, call list_endpoint_categories()
3232
3528
  first.
3233
3529
 
3234
- Categories: authentication, organizations, subscriptions, billing (includes Tier-3 admin read-only endpoints), usage, agents, users, plans, ai, entities, departments, files, external_agents.
3530
+ Categories: authentication, organizations, subscriptions, billing (incl. Tier-3 admin read-only endpoints + subscriptions list), organization_admin (non-billing Org Owner Dashboard surfaces — members, customers, agents, KB, notifications, webhook audit), usage, agents, users (incl. PATCH /users/me/profile-data — ADR-0010), plans, ai, entities (Phase 3: reference fields + cross-schema join — ADR-0009; reserved slugs — ADR-0011), departments, files, external_agents.
3235
3531
 
3236
- The 'billing' category now covers both the end-user surface
3532
+ The 'billing' category covers both the end-user surface
3237
3533
  (/v1/organizations/{org_id}/billing/*) and the Organization-admin read-only
3238
- surface (/v1/organization-admin/{org_id}/{plans, config/stripe, audit}).
3534
+ billing surface (/v1/organization-admin/{org_id}/{plans, config/stripe, audit, subscriptions}).
3239
3535
  Secret-setting paths (config/stripe POST/PATCH/DELETE, plan CRUD that needs
3240
3536
  the Stripe key) go through get_stripe_config_url / get_plan_management_url —
3241
- the MCP never accepts raw secrets inline.`,
3537
+ the MCP never accepts raw secrets inline.
3538
+
3539
+ The 'organization_admin' category covers the non-billing admin surfaces:
3540
+ members CRUD, customers list, agents visibility (Hybrid Catalog — ADR-0006 v2),
3541
+ KnowledgeBase + Documents CRUD (per-Org via rag_role), agent-change
3542
+ notifications, and the Stripe webhook event audit log.`,
3242
3543
  inputSchema: {
3243
3544
  type: "object",
3244
3545
  properties: {
3245
3546
  category: {
3246
3547
  type: "string",
3247
3548
  description: "Filter by category (strongly recommended). Leave empty only when you explicitly want the full catalog.",
3248
- enum: ["authentication", "organizations", "subscriptions", "billing", "usage", "agents", "users", "plans", "ai", "entities", "departments", "files", "external_agents"],
3549
+ enum: ["authentication", "organizations", "subscriptions", "billing", "organization_admin", "usage", "agents", "users", "plans", "ai", "entities", "departments", "files", "external_agents"],
3249
3550
  },
3250
3551
  },
3251
3552
  required: [],
@@ -3302,6 +3603,15 @@ the MCP never accepts raw secrets inline.`,
3302
3603
  required: ["org_id"],
3303
3604
  },
3304
3605
  },
3606
+ {
3607
+ name: "get_reserved_schema_slugs",
3608
+ description: `Return the full list of schema slugs the Custom Entities subsystem reserves (ADR-0011). These slugs collide with native DB tables (e.g. \`users\`, \`conversations\`, \`agents\`, \`subscriptions\`, \`alembic_version\`, ...) and CANNOT be used as a custom data schema slug — the server rejects them at \`POST /v1/entity/schemas\` with a 400. Comparison is case-insensitive and ignores surrounding whitespace. Call this tool BEFORE issuing a \`POST /v1/entity/schemas\` so the agent can refuse client-side instead of round-tripping a 400. Pure data; no network call.`,
3609
+ inputSchema: {
3610
+ type: "object",
3611
+ properties: {},
3612
+ required: [],
3613
+ },
3614
+ },
3305
3615
  {
3306
3616
  name: "get_request_headers",
3307
3617
  description: "Get detailed information about required HTTP headers for API requests.",
@@ -3647,6 +3957,29 @@ ${JSON.stringify({ categories, summaries }, null, 2)}`,
3647
3957
  ],
3648
3958
  };
3649
3959
  }
3960
+ case "get_reserved_schema_slugs": {
3961
+ // ADR-0011: returns the full list of slugs that cannot be used
3962
+ // for a Custom Entity schema because they collide with a native
3963
+ // DB table. Agents should call this BEFORE issuing
3964
+ // POST /v1/entity/schemas so they can refuse client-side.
3965
+ const slugs = [...RESERVED_SCHEMA_SLUGS];
3966
+ return {
3967
+ content: [
3968
+ {
3969
+ type: "text",
3970
+ text: JSON.stringify({
3971
+ reserved_slugs: slugs,
3972
+ total: slugs.length,
3973
+ comparison: "case-insensitive (server normalizes via .strip().lower() before comparison)",
3974
+ rejection_status: 400,
3975
+ rejection_message_template: "Schema slug '<slug>' is reserved (collides with a native table). Choose a different name.",
3976
+ source_of_truth: "src/tenants/services/custom_data_schema_service.py — RESERVED_SCHEMA_SLUGS. This MCP tool MUST be kept in sync; when a new native __tablename__ ships, both lists are updated in the same PR.",
3977
+ adr: "docs/adr/0011-namespace-collision-policy.md",
3978
+ }, null, 2),
3979
+ },
3980
+ ],
3981
+ };
3982
+ }
3650
3983
  case "get_request_headers": {
3651
3984
  return {
3652
3985
  content: [
@@ -4536,6 +4869,8 @@ async function main() {
4536
4869
  console.error(` - get_stripe_config_url — Dashboard URL to set/rotate the Org's Stripe keys`);
4537
4870
  console.error(` - get_agent_secrets_url — Dashboard URL to rotate an Agent's secrets`);
4538
4871
  console.error(` - get_plan_management_url — Dashboard URL to create/edit Plans`);
4872
+ console.error(`\nCustom Entities helpers (ADR-0011):`);
4873
+ console.error(` - get_reserved_schema_slugs — Pre-validate schema slug before POST /v1/entity/schemas`);
4539
4874
  console.error(`\nAuthenticated helpers:`);
4540
4875
  console.error(` - list_end_users (sk_live_* required)`);
4541
4876
  console.error(` - list_oauth_providers (any key)`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@genlobe/mcp-server",
3
- "version": "3.5.1",
3
+ "version": "3.6.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": {