@genlobe/mcp-server 3.4.0 → 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.
- package/README.md +22 -4
- package/dist/index.js +667 -129
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -74,6 +74,7 @@ Edit `claude_desktop_config.json`:
|
|
|
74
74
|
|---|---|---|
|
|
75
75
|
| `SAAS_API_URL` | no | Defaults to `https://api-core.genlobe.ai`. Override only if you point at a local or on-prem backend. |
|
|
76
76
|
| `SAAS_API_KEY` | optional | Your Genlobe API key. Accepts both `pk_live_*` (public) and `sk_live_*` (secret). Without it, only the offline documentation tools work — the four live tools (`validate_credentials`, `list_end_users`, `list_oauth_providers`, `get_openapi_spec`) need it. |
|
|
77
|
+
| `SAAS_FRONTEND_URL` | no | Dashboard URL used by the redirect-URL tools (`get_stripe_config_url`, `get_agent_secrets_url`, `get_plan_management_url`). Resolution order: (1) this variable if set; (2) derived from `SAAS_API_URL` by swapping the leading `api.` / `api-core.` sub-domain for `app.`; (3) `https://app.genlobe.ai`. |
|
|
77
78
|
|
|
78
79
|
`API_URL` and `API_KEY` (without the `SAAS_` prefix) are accepted as aliases for backward compatibility.
|
|
79
80
|
|
|
@@ -81,14 +82,16 @@ Edit `claude_desktop_config.json`:
|
|
|
81
82
|
|
|
82
83
|
## Tools
|
|
83
84
|
|
|
84
|
-
|
|
85
|
+
19 tools, in four groups.
|
|
85
86
|
|
|
86
87
|
### Documentation (offline, no API key needed)
|
|
87
88
|
|
|
88
89
|
| Tool | Returns |
|
|
89
90
|
|---|---|
|
|
90
91
|
| `get_api_overview` | High-level architecture, auth model, key concepts, recommended call order. **Start here.** |
|
|
91
|
-
| `
|
|
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`, `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. |
|
|
92
95
|
| `get_request_headers` | The exact HTTP headers required for end-user requests, with examples. |
|
|
93
96
|
| `get_authentication_flow` | Step-by-step register → login → refresh → logout, including token storage and refresh-on-401 patterns. |
|
|
94
97
|
| `get_common_patterns` | Pagination, error handling, optimistic updates, file upload, RAG queries, agent execution. |
|
|
@@ -96,6 +99,18 @@ Edit `claude_desktop_config.json`:
|
|
|
96
99
|
| `search_endpoints` | Keyword search across path, summary, and description. |
|
|
97
100
|
| `get_endpoint_details` | Full request/response schema for a specific `(method, path)`. |
|
|
98
101
|
|
|
102
|
+
### Redirect-URL tools (no secrets ever returned by the MCP)
|
|
103
|
+
|
|
104
|
+
These tools return a Dashboard URL the human developer should visit to set or rotate a secret. The MCP NEVER accepts or returns raw secret values; instead it points the agent at the right page so the human pastes the key in the browser. See the "MCP server invariants" section in the root `CLAUDE.md` for the pattern.
|
|
105
|
+
|
|
106
|
+
All three return `{ url, reason, next_action: { type: "open_in_browser", url } }` and do no network calls — they are pure URL computation against the configured `SAAS_FRONTEND_URL`.
|
|
107
|
+
|
|
108
|
+
| Tool | Returns |
|
|
109
|
+
|---|---|
|
|
110
|
+
| `get_stripe_config_url({ org_id })` | `${frontend_url}/org-admin/{org_id}/integrations/stripe` — page where the Organization Owner sets / rotates the org's Stripe `secret_key`, `publishable_key`, `webhook_secret`. Pair it with `GET /v1/organization-admin/{org_id}/config/stripe` (returned masked) to detect whether Stripe is already configured. |
|
|
111
|
+
| `get_agent_secrets_url({ agent_id })` | `${frontend_url}/dashboard/agents/{agent_id}/secrets` — page where the Tenant rotates / sets an Agent's provider keys (OpenAI / Anthropic / etc.) and webhook secrets. |
|
|
112
|
+
| `get_plan_management_url({ org_id })` | `${frontend_url}/org-admin/{org_id}/plans` — page where the Organization Owner creates / edits / deletes Plans. Write operations are not exposed via MCP because the plan-sync flow needs the encrypted Stripe `secret_key`. |
|
|
113
|
+
|
|
99
114
|
### Live (require `SAAS_API_KEY`)
|
|
100
115
|
|
|
101
116
|
| Tool | Does |
|
|
@@ -122,12 +137,15 @@ Edit `claude_desktop_config.json`:
|
|
|
122
137
|
3. recommend_stack → pick a framework that won't leak the key
|
|
123
138
|
4. get_api_overview → understand the API at a high level
|
|
124
139
|
5. get_authentication_flow → wire up register / login / refresh
|
|
125
|
-
6.
|
|
126
|
-
7.
|
|
140
|
+
6. list_endpoint_categories → see which endpoint categories exist
|
|
141
|
+
7. get_end_user_endpoints → drill into one category at a time
|
|
142
|
+
8. get_sdk_template → start writing real code
|
|
127
143
|
```
|
|
128
144
|
|
|
129
145
|
For targeted questions ("how do I list organizations?"), `search_endpoints` + `get_endpoint_details` is faster than scrolling through `get_end_user_endpoints`.
|
|
130
146
|
|
|
147
|
+
When the developer needs to set or rotate a secret (Stripe key, Agent provider key, webhook secret), call the matching `get_*_url` tool — the MCP returns a Dashboard URL the human visits to paste the key. Inline secret input through the MCP is not supported by design.
|
|
148
|
+
|
|
131
149
|
---
|
|
132
150
|
|
|
133
151
|
## Support
|
package/dist/index.js
CHANGED
|
@@ -36,6 +36,45 @@ const SERVER_VERSION = pkg.version;
|
|
|
36
36
|
// a local backend in development.
|
|
37
37
|
const API_URL = process.env.SAAS_API_URL || process.env.API_URL || "https://api-core.genlobe.ai";
|
|
38
38
|
const API_KEY = process.env.SAAS_API_KEY || process.env.API_KEY || "";
|
|
39
|
+
// Frontend URL used by the redirect-URL tools (get_stripe_config_url,
|
|
40
|
+
// get_agent_secrets_url, get_plan_management_url). Resolution order:
|
|
41
|
+
// 1. SAAS_FRONTEND_URL env var (e.g. https://app.genlobe.ai)
|
|
42
|
+
// 2. Derive from SAAS_API_URL by swapping the leading "api." / "api-core."
|
|
43
|
+
// sub-domain for "app." — works for the public prod + staging
|
|
44
|
+
// environments out of the box.
|
|
45
|
+
// 3. Default https://app.genlobe.ai.
|
|
46
|
+
//
|
|
47
|
+
// The MCP NEVER returns secret values to the agent; instead it returns
|
|
48
|
+
// these URLs so the human developer can paste the secret in the Dashboard.
|
|
49
|
+
// See "MCP server invariants" in the root CLAUDE.md.
|
|
50
|
+
function resolveFrontendUrl() {
|
|
51
|
+
const explicit = process.env.SAAS_FRONTEND_URL;
|
|
52
|
+
if (explicit)
|
|
53
|
+
return explicit.replace(/\/+$/, "");
|
|
54
|
+
const derived = API_URL.replace(/^(https?:\/\/)(api-core\.|api\.)/, "$1app.");
|
|
55
|
+
if (derived !== API_URL)
|
|
56
|
+
return derived.replace(/\/+$/, "");
|
|
57
|
+
// Localhost API → keep the convention of port 3000/3001 for the dashboards
|
|
58
|
+
// by NOT pretending to know which one. Fall back to the documented default.
|
|
59
|
+
return "https://app.genlobe.ai";
|
|
60
|
+
}
|
|
61
|
+
const FRONTEND_URL = resolveFrontendUrl();
|
|
62
|
+
// Canonical structured rejection payload for any future tool that might
|
|
63
|
+
// receive secret-shaped input. The MCP NEVER accepts raw secrets inline —
|
|
64
|
+
// the agent must follow `next_action.url` and the human pastes the secret
|
|
65
|
+
// in the dashboard. Documented in the root CLAUDE.md "MCP server invariants".
|
|
66
|
+
//
|
|
67
|
+
// Not wired into any current tool (none of them accept secret-shaped input
|
|
68
|
+
// today), but kept here so future maintainers have a single canonical
|
|
69
|
+
// shape to reuse rather than re-invent.
|
|
70
|
+
function rejectSecretInline(secretType, reason, redirectUrl) {
|
|
71
|
+
return {
|
|
72
|
+
error: "blocked_by_security_policy",
|
|
73
|
+
reason,
|
|
74
|
+
secret_type: secretType,
|
|
75
|
+
next_action: { type: "redirect", url: redirectUrl },
|
|
76
|
+
};
|
|
77
|
+
}
|
|
39
78
|
// Cache for API responses
|
|
40
79
|
const cache = new Map();
|
|
41
80
|
const CACHE_TTL = 5 * 60 * 1000; // 5 minutes
|
|
@@ -153,6 +192,82 @@ The \`sk_live_*\` key never reaches the browser. It lives only on the tenant's o
|
|
|
153
192
|
}
|
|
154
193
|
};
|
|
155
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
|
+
// =============================================================================
|
|
156
271
|
// End-User Endpoints (for building frontends)
|
|
157
272
|
// =============================================================================
|
|
158
273
|
const END_USER_ENDPOINTS = {
|
|
@@ -163,7 +278,7 @@ const END_USER_ENDPOINTS = {
|
|
|
163
278
|
method: "POST",
|
|
164
279
|
path: "/v1/auth/register",
|
|
165
280
|
summary: "Register a new user",
|
|
166
|
-
auth: { api_key: "pk_live_* or sk_live_*", jwt: false },
|
|
281
|
+
auth: { api_key: "pk_live_* or sk_live_* (REQUIRED)", jwt: false, headers: "X-Organization-Id required when the API key is root-scoped (org_id NULL on the key). Ignored when the key is already scoped to a specific Organization — the key wins." },
|
|
167
282
|
rate_limit: "3 per hour per IP (blocked 30 min after exceeding)",
|
|
168
283
|
request_body: {
|
|
169
284
|
email: "string (required) - valid email",
|
|
@@ -184,7 +299,8 @@ const END_USER_ENDPOINTS = {
|
|
|
184
299
|
display_name: "string | null",
|
|
185
300
|
avatar_url: "string | null",
|
|
186
301
|
is_active: "boolean",
|
|
187
|
-
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."
|
|
188
304
|
}
|
|
189
305
|
},
|
|
190
306
|
response_when_verification_required: {
|
|
@@ -236,7 +352,8 @@ const END_USER_ENDPOINTS = {
|
|
|
236
352
|
display_name: "string | null",
|
|
237
353
|
avatar_url: "string | null",
|
|
238
354
|
is_active: "boolean",
|
|
239
|
-
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."
|
|
240
357
|
}
|
|
241
358
|
},
|
|
242
359
|
{
|
|
@@ -829,7 +946,7 @@ const END_USER_ENDPOINTS = {
|
|
|
829
946
|
]
|
|
830
947
|
},
|
|
831
948
|
billing: {
|
|
832
|
-
description: "Billing operations.
|
|
949
|
+
description: "Billing operations. Three 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 — end-user surface (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.\n\n- **Tier 3 — Organization-admin surface (Phase 1, this MCP)**: /v1/organization-admin/{org_id}/* — read-only inspection of plans, masked Stripe config, and billing audit log. Auth: end-user JWT + `require_org_admin` (role IN owner/admin). Secret-setting paths (POST/PATCH/DELETE on config/stripe, plan CRUD) are NOT exposed via MCP — use the `get_stripe_config_url({ org_id })` / `get_plan_management_url({ org_id })` tools so the human pastes secrets in the Dashboard.",
|
|
833
950
|
endpoints: [
|
|
834
951
|
{
|
|
835
952
|
method: "GET",
|
|
@@ -1007,6 +1124,269 @@ const END_USER_ENDPOINTS = {
|
|
|
1007
1124
|
"403": "Caller is not an active OrganizationMember of the path's org.",
|
|
1008
1125
|
"404": "Caller has no Subscription for this Organization yet."
|
|
1009
1126
|
}
|
|
1127
|
+
},
|
|
1128
|
+
// ---------------------------------------------------------------
|
|
1129
|
+
// Organization-admin read-only endpoints (BILL-4 / BILL-9, Epic
|
|
1130
|
+
// #209). Auth: end-user JWT + `require_org_admin` (the caller must
|
|
1131
|
+
// be an OrganizationMember of the path's org with role IN ('owner',
|
|
1132
|
+
// 'admin')). These are the surfaces an Org Owner uses to inspect
|
|
1133
|
+
// billing state from the MCP without touching any secret. The
|
|
1134
|
+
// secret-setting paths (POST/PATCH/DELETE on config/stripe + plan
|
|
1135
|
+
// CRUD that needs a Stripe key) are intentionally NOT exposed here
|
|
1136
|
+
// — they go through the redirect-URL tools (`get_stripe_config_url`,
|
|
1137
|
+
// `get_plan_management_url`) so the human pastes the secret in the
|
|
1138
|
+
// Dashboard. See "MCP server invariants" in the root CLAUDE.md.
|
|
1139
|
+
// ---------------------------------------------------------------
|
|
1140
|
+
{
|
|
1141
|
+
method: "GET",
|
|
1142
|
+
path: "/v1/organization-admin/{organization_id}/plans",
|
|
1143
|
+
summary: "List the Organization's plans (admin view — includes inactive / un-synced plans)",
|
|
1144
|
+
auth: { api_key: "pk_live_* or sk_live_*", jwt: true, require_org_admin: true },
|
|
1145
|
+
response: {
|
|
1146
|
+
items: "array of OrganizationPlan (see GET /v1/organizations/{organization_id}/plans for the field list — same shape). Unlike the public endpoint this one also returns plans with is_active=false and plans that don't have a stripe_price_id yet.",
|
|
1147
|
+
total: "number"
|
|
1148
|
+
},
|
|
1149
|
+
errors: {
|
|
1150
|
+
"403": "Caller is not an OrganizationMember with role owner/admin of the path's org (rejects TenantMember JWT)."
|
|
1151
|
+
},
|
|
1152
|
+
note: "The admin variant of GET /v1/organizations/{organization_id}/plans. Use this when an Org Owner needs to see every plan they have, including drafts. No secret material is returned."
|
|
1153
|
+
},
|
|
1154
|
+
{
|
|
1155
|
+
method: "GET",
|
|
1156
|
+
path: "/v1/organization-admin/{organization_id}/plans/{plan_id}",
|
|
1157
|
+
summary: "Read one plan (admin view)",
|
|
1158
|
+
auth: { api_key: "pk_live_* or sk_live_*", jwt: true, require_org_admin: true },
|
|
1159
|
+
response: "OrganizationPlan (full shape — id, organization_id, name, slug, description, price_amount, currency, billing_interval, features, limits, stripe_product_id, stripe_price_id, stripe_price_id_yearly, is_active, created_at, updated_at).",
|
|
1160
|
+
errors: {
|
|
1161
|
+
"403": "Caller is not an OrganizationMember with role owner/admin of the path's org.",
|
|
1162
|
+
"404": "Plan not found OR plan.organization_id != path organization_id (collapsed to 404 to avoid cross-org existence leaks)."
|
|
1163
|
+
}
|
|
1164
|
+
},
|
|
1165
|
+
{
|
|
1166
|
+
method: "GET",
|
|
1167
|
+
path: "/v1/organization-admin/{organization_id}/config/stripe",
|
|
1168
|
+
summary: "Read the Organization's MASKED Stripe config status (no secrets returned)",
|
|
1169
|
+
auth: { api_key: "pk_live_* or sk_live_*", jwt: true, require_org_admin: true },
|
|
1170
|
+
response: {
|
|
1171
|
+
organization_id: "uuid",
|
|
1172
|
+
publishable_key: "string | null — Stripe publishable key (pk_live_* / pk_test_*), safe to expose.",
|
|
1173
|
+
secret_key_display: "string | null — masked tail of the secret key, e.g. 'sk_live_****Pnxs'. Display-only — the actual key is never returned.",
|
|
1174
|
+
has_webhook_secret: "boolean — true if a webhook_secret is persisted.",
|
|
1175
|
+
is_active: "boolean — true once secret_key + webhook_secret are both present."
|
|
1176
|
+
},
|
|
1177
|
+
note: "Use this to detect whether the Org has Stripe configured before sending users into a checkout flow that would 409. To SET or ROTATE the Stripe credentials, call the MCP tool `get_stripe_config_url({ org_id })` and have the human paste the keys in the Dashboard — the MCP does NOT accept raw secrets inline.",
|
|
1178
|
+
errors: {
|
|
1179
|
+
"403": "Caller is not an OrganizationMember with role owner/admin of the path's org."
|
|
1180
|
+
}
|
|
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
|
+
// ---------------------------------------------------------------
|
|
1188
|
+
{
|
|
1189
|
+
method: "GET",
|
|
1190
|
+
path: "/v1/organization-admin/{organization_id}/subscriptions",
|
|
1191
|
+
summary: "List end-user subscriptions for the Organization (admin)",
|
|
1192
|
+
auth: { api_key: "pk_live_* or sk_live_*", jwt: true, require_org_admin: true },
|
|
1193
|
+
query_params: {
|
|
1194
|
+
status: "string (optional) — comma-separated subscription statuses to filter (e.g. 'active,trialing,past_due'). Omit for all."
|
|
1195
|
+
},
|
|
1196
|
+
response: {
|
|
1197
|
+
items: "array of SubscriptionResponse — every Subscription row scoped to this Organization, sorted newest-first.",
|
|
1198
|
+
total: "number"
|
|
1199
|
+
},
|
|
1200
|
+
note: "Active statuses for the 'paying customers' count are: active, trialing, past_due. canceled / incomplete_expired / stripe_disconnected do NOT count as active.",
|
|
1201
|
+
errors: {
|
|
1202
|
+
"403": "Caller is not an OrganizationMember with role owner/admin of the path's org."
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
]
|
|
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"
|
|
1010
1390
|
}
|
|
1011
1391
|
]
|
|
1012
1392
|
},
|
|
@@ -1353,8 +1733,26 @@ const END_USER_ENDPOINTS = {
|
|
|
1353
1733
|
]
|
|
1354
1734
|
},
|
|
1355
1735
|
users: {
|
|
1356
|
-
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.",
|
|
1357
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
|
+
},
|
|
1358
1756
|
{
|
|
1359
1757
|
method: "POST",
|
|
1360
1758
|
path: "/v1/users",
|
|
@@ -1384,6 +1782,7 @@ const END_USER_ENDPOINTS = {
|
|
|
1384
1782
|
status: "string",
|
|
1385
1783
|
current_plan_id: "uuid | null",
|
|
1386
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).",
|
|
1387
1786
|
created_at: "ISO datetime",
|
|
1388
1787
|
updated_at: "ISO datetime",
|
|
1389
1788
|
settings: "UserSettingsResponse | null"
|
|
@@ -1441,6 +1840,7 @@ const END_USER_ENDPOINTS = {
|
|
|
1441
1840
|
status: "string",
|
|
1442
1841
|
current_plan_id: "uuid | null",
|
|
1443
1842
|
default_organization_id: "uuid | null",
|
|
1843
|
+
profile_data: "object — Tenant-defined free-form metadata (ADR-0010). Defaults to {} when empty.",
|
|
1444
1844
|
created_at: "ISO datetime",
|
|
1445
1845
|
updated_at: "ISO datetime",
|
|
1446
1846
|
settings: "UserSettingsResponse | null (only if include_settings=true)"
|
|
@@ -1581,7 +1981,7 @@ const END_USER_ENDPOINTS = {
|
|
|
1581
1981
|
]
|
|
1582
1982
|
},
|
|
1583
1983
|
entities: {
|
|
1584
|
-
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.",
|
|
1585
1985
|
endpoints: [
|
|
1586
1986
|
// ── End-User Schema Endpoints ──
|
|
1587
1987
|
{
|
|
@@ -1615,7 +2015,8 @@ const END_USER_ENDPOINTS = {
|
|
|
1615
2015
|
enum: '{"type":"enum","required":true,"values":["draft","published","archived"]} // KEY IS "values", NOT "choices" — Genlobe rejects "choices" with 400.',
|
|
1616
2016
|
email: '{"type":"email","required":true,"max_length":255}',
|
|
1617
2017
|
url: '{"type":"url","required":false,"max_length":2048}',
|
|
1618
|
-
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.'
|
|
1619
2020
|
},
|
|
1620
2021
|
example_request: `{
|
|
1621
2022
|
"name": "Blog Post",
|
|
@@ -1625,10 +2026,11 @@ const END_USER_ENDPOINTS = {
|
|
|
1625
2026
|
"title": {"type": "string", "required": true, "max_length": 255},
|
|
1626
2027
|
"body": {"type": "text", "required": true},
|
|
1627
2028
|
"status": {"type": "enum", "required": true, "values": ["draft", "published", "archived"]},
|
|
1628
|
-
"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"}
|
|
1629
2031
|
}
|
|
1630
2032
|
}`,
|
|
1631
|
-
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[]`."
|
|
1632
2034
|
},
|
|
1633
2035
|
{
|
|
1634
2036
|
method: "GET",
|
|
@@ -1660,12 +2062,24 @@ const END_USER_ENDPOINTS = {
|
|
|
1660
2062
|
method: "POST",
|
|
1661
2063
|
path: "/v1/entity/records",
|
|
1662
2064
|
summary: "Create a new entity record",
|
|
1663
|
-
auth: { api_key: "sk_live_* or pk_live_*", jwt:
|
|
2065
|
+
auth: { api_key: "sk_live_* or pk_live_*", jwt: "optional (required for pk_live_*)", headers: "X-Organization-Id required when key is not organization-scoped" },
|
|
1664
2066
|
request_body: {
|
|
1665
2067
|
schema_id: "uuid (required)",
|
|
1666
2068
|
data: "object (required) - validated against schema fields_definition"
|
|
1667
2069
|
},
|
|
1668
|
-
notes: "
|
|
2070
|
+
notes: "Three modes (#178 + #350): (1) sk_live_* alone -> tenant-scoped record, created_by_id is NULL (catalog/blog/global config). (2) sk_live_* + end-user JWT -> user-scoped record, created_by_id = user.id (server-side caller acting on behalf of a user). (3) pk_live_* + end-user JWT -> user-scoped record, created_by_id = user.id (canonical 'two-phase' frontend pattern: pk in the browser, JWT after login). pk_live_* WITHOUT a JWT is rejected with 401 — a public key alone is anonymous and can't be the creator of a record."
|
|
2071
|
+
},
|
|
2072
|
+
{
|
|
2073
|
+
method: "GET",
|
|
2074
|
+
path: "/v1/entity/records/mine",
|
|
2075
|
+
summary: "List records authored by the current end-user (\"my data\")",
|
|
2076
|
+
auth: { api_key: "sk_live_* or pk_live_*", jwt: true, headers: "X-Organization-Id required when key is not organization-scoped" },
|
|
2077
|
+
query_params: {
|
|
2078
|
+
schema_id: "uuid (required) - schema whose records to list",
|
|
2079
|
+
page: "number (default 1)",
|
|
2080
|
+
size: "number (default 20, max 100)"
|
|
2081
|
+
},
|
|
2082
|
+
notes: "Returns ONLY records where created_by_id = current_user.id, regardless of the user's role inside the Org (members and elevated roles alike). Natural path for end-user 'my data' queries (my chats, my orders, my notes). Use this instead of POST /v1/entity/records/search with manual created_by_id filters — the row-level scope (ADR-0007) is enforced server-side, frontend MUST NOT be the filter. Requires an end-user JWT; tenant-scoped (sk_live_* alone) callers get 401 — they should use POST /v1/entity/records/search."
|
|
1669
2083
|
},
|
|
1670
2084
|
{
|
|
1671
2085
|
method: "GET",
|
|
@@ -1692,11 +2106,12 @@ const END_USER_ENDPOINTS = {
|
|
|
1692
2106
|
{
|
|
1693
2107
|
method: "POST",
|
|
1694
2108
|
path: "/v1/entity/records/search",
|
|
1695
|
-
summary: "Advanced search for entity records with filters and
|
|
2109
|
+
summary: "Advanced search for entity records with filters, ordering, and cross-schema joins (ADR-0009)",
|
|
1696
2110
|
auth: { api_key: "sk_live_* or pk_live_*", jwt: false, headers: "X-Organization-Id required when key is not organization-scoped" },
|
|
1697
2111
|
request_body: {
|
|
1698
2112
|
schema_id: "uuid (optional) - filter by schema",
|
|
1699
|
-
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)."
|
|
1700
2115
|
},
|
|
1701
2116
|
query_params: {
|
|
1702
2117
|
page: "number (default 1)",
|
|
@@ -1704,14 +2119,21 @@ const END_USER_ENDPOINTS = {
|
|
|
1704
2119
|
order_by: "string (default 'created_at')",
|
|
1705
2120
|
order_dir: "'asc' | 'desc' (default 'desc')"
|
|
1706
2121
|
},
|
|
1707
|
-
notes: "Supported operators
|
|
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.",
|
|
1708
2123
|
example_request: `{
|
|
1709
2124
|
"schema_id": "550e8400-e29b-41d4-a716-446655440000",
|
|
1710
2125
|
"query": {
|
|
1711
2126
|
"price": { "operator": "gt", "value": 100 },
|
|
1712
2127
|
"category": "electronics",
|
|
1713
2128
|
"status": { "operator": "in", "value": ["active", "pending"] }
|
|
1714
|
-
}
|
|
2129
|
+
},
|
|
2130
|
+
"join": [
|
|
2131
|
+
{
|
|
2132
|
+
"field": "author",
|
|
2133
|
+
"target_schema_slug": "users",
|
|
2134
|
+
"where": { "is_active": true }
|
|
2135
|
+
}
|
|
2136
|
+
]
|
|
1715
2137
|
}`
|
|
1716
2138
|
}
|
|
1717
2139
|
]
|
|
@@ -3091,25 +3513,105 @@ Call this first to understand the API architecture and authentication model.`,
|
|
|
3091
3513
|
},
|
|
3092
3514
|
{
|
|
3093
3515
|
name: "get_end_user_endpoints",
|
|
3094
|
-
description: `Get detailed documentation for END-USER API endpoints.
|
|
3516
|
+
description: `Get detailed documentation for END-USER API endpoints, filtered to ONE category.
|
|
3095
3517
|
|
|
3096
3518
|
Use this when building a frontend for end-users. These endpoints use:
|
|
3097
3519
|
- API Key (X-API-Key header) - required for ALL requests
|
|
3098
3520
|
- JWT token (Authorization: Bearer) - required AFTER login
|
|
3099
3521
|
|
|
3100
|
-
|
|
3522
|
+
PREFER passing a single category. The output is focused on that category + a
|
|
3523
|
+
one-line pointer to the others. Calling without a category dumps the entire
|
|
3524
|
+
catalog (every endpoint of every category) and is rarely what you want — it
|
|
3525
|
+
burns context quickly.
|
|
3526
|
+
|
|
3527
|
+
If you don't know which categories exist yet, call list_endpoint_categories()
|
|
3528
|
+
first.
|
|
3529
|
+
|
|
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.
|
|
3531
|
+
|
|
3532
|
+
The 'billing' category covers both the end-user surface
|
|
3533
|
+
(/v1/organizations/{org_id}/billing/*) and the Organization-admin read-only
|
|
3534
|
+
billing surface (/v1/organization-admin/{org_id}/{plans, config/stripe, audit, subscriptions}).
|
|
3535
|
+
Secret-setting paths (config/stripe POST/PATCH/DELETE, plan CRUD that needs
|
|
3536
|
+
the Stripe key) go through get_stripe_config_url / get_plan_management_url —
|
|
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.`,
|
|
3101
3543
|
inputSchema: {
|
|
3102
3544
|
type: "object",
|
|
3103
3545
|
properties: {
|
|
3104
3546
|
category: {
|
|
3105
3547
|
type: "string",
|
|
3106
|
-
description: "Filter by category. Leave empty
|
|
3107
|
-
enum: ["authentication", "organizations", "subscriptions", "billing", "usage", "agents", "users", "plans", "ai", "entities", "departments", "files", "external_agents"],
|
|
3548
|
+
description: "Filter by category (strongly recommended). Leave empty only when you explicitly want the full catalog.",
|
|
3549
|
+
enum: ["authentication", "organizations", "subscriptions", "billing", "organization_admin", "usage", "agents", "users", "plans", "ai", "entities", "departments", "files", "external_agents"],
|
|
3108
3550
|
},
|
|
3109
3551
|
},
|
|
3110
3552
|
required: [],
|
|
3111
3553
|
},
|
|
3112
3554
|
},
|
|
3555
|
+
{
|
|
3556
|
+
name: "list_endpoint_categories",
|
|
3557
|
+
description: `Return the bare list of available end-user endpoint categories plus a one-line summary of each. Cheap call (~30 tokens). Use this once if you don't already know what's available; then call get_end_user_endpoints({ category }) for the actual endpoint docs of the category you care about. This replaces the old per-call "Quick Reference" dump that get_end_user_endpoints used to print at the bottom of every response.`,
|
|
3558
|
+
inputSchema: {
|
|
3559
|
+
type: "object",
|
|
3560
|
+
properties: {},
|
|
3561
|
+
required: [],
|
|
3562
|
+
},
|
|
3563
|
+
},
|
|
3564
|
+
{
|
|
3565
|
+
name: "get_stripe_config_url",
|
|
3566
|
+
description: `Return the Dashboard URL where the Organization Owner sets or rotates the Organization's Stripe credentials (secret_key, publishable_key, webhook_secret). The MCP NEVER accepts raw secrets through tool input — this redirect URL is the canonical way to set them. Pure URL computation; no network call. Pair it with GET /v1/organization-admin/{org_id}/config/stripe to detect whether the Organization has Stripe configured already.`,
|
|
3567
|
+
inputSchema: {
|
|
3568
|
+
type: "object",
|
|
3569
|
+
properties: {
|
|
3570
|
+
org_id: {
|
|
3571
|
+
type: "string",
|
|
3572
|
+
description: "The Organization id (UUID) whose Stripe config the owner wants to manage.",
|
|
3573
|
+
},
|
|
3574
|
+
},
|
|
3575
|
+
required: ["org_id"],
|
|
3576
|
+
},
|
|
3577
|
+
},
|
|
3578
|
+
{
|
|
3579
|
+
name: "get_agent_secrets_url",
|
|
3580
|
+
description: `Return the Dashboard URL where the Tenant rotates / sets an Agent's provider keys (OpenAI / Anthropic / etc.) and webhook secrets. The MCP NEVER accepts raw secrets through tool input — this redirect URL is the canonical way to set them. Pure URL computation; no network call.`,
|
|
3581
|
+
inputSchema: {
|
|
3582
|
+
type: "object",
|
|
3583
|
+
properties: {
|
|
3584
|
+
agent_id: {
|
|
3585
|
+
type: "string",
|
|
3586
|
+
description: "The Agent id (UUID) whose secrets the developer wants to manage.",
|
|
3587
|
+
},
|
|
3588
|
+
},
|
|
3589
|
+
required: ["agent_id"],
|
|
3590
|
+
},
|
|
3591
|
+
},
|
|
3592
|
+
{
|
|
3593
|
+
name: "get_plan_management_url",
|
|
3594
|
+
description: `Return the Dashboard URL where the Organization Owner creates / edits / deletes Plans (Tier-3, BYO-Stripe). Write operations are NOT exposed via MCP because plan sync needs the Organization's Stripe secret_key; the MCP only exposes the read paths (GET /v1/organization-admin/{org_id}/plans, GET /v1/organization-admin/{org_id}/plans/{plan_id}) for inspection. Pure URL computation; no network call.`,
|
|
3595
|
+
inputSchema: {
|
|
3596
|
+
type: "object",
|
|
3597
|
+
properties: {
|
|
3598
|
+
org_id: {
|
|
3599
|
+
type: "string",
|
|
3600
|
+
description: "The Organization id (UUID) whose plan catalog the owner wants to manage.",
|
|
3601
|
+
},
|
|
3602
|
+
},
|
|
3603
|
+
required: ["org_id"],
|
|
3604
|
+
},
|
|
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
|
+
},
|
|
3113
3615
|
{
|
|
3114
3616
|
name: "get_request_headers",
|
|
3115
3617
|
description: "Get detailed information about required HTTP headers for API requests.",
|
|
@@ -3327,125 +3829,153 @@ Skipping these is how secret keys end up shipped to a browser bundle. Don't skip
|
|
|
3327
3829
|
};
|
|
3328
3830
|
}
|
|
3329
3831
|
case "get_end_user_endpoints": {
|
|
3832
|
+
// Phase-1 P0 (#347): focused output. Return ONLY the requested category
|
|
3833
|
+
// plus a one-line pointer to the other categories. The previous
|
|
3834
|
+
// implementation dumped a global Quick Reference of every category
|
|
3835
|
+
// at the bottom of every per-category call — that burned thousands
|
|
3836
|
+
// of tokens of agent context for no benefit (see CHANGELOG 3.5.0).
|
|
3330
3837
|
const category = args.category;
|
|
3331
|
-
|
|
3838
|
+
const allCategories = Object.keys(END_USER_ENDPOINTS);
|
|
3332
3839
|
if (category && category in END_USER_ENDPOINTS) {
|
|
3333
|
-
|
|
3840
|
+
const focused = { [category]: END_USER_ENDPOINTS[category] };
|
|
3841
|
+
const otherCategories = allCategories.filter((c) => c !== category);
|
|
3842
|
+
return {
|
|
3843
|
+
content: [
|
|
3844
|
+
{
|
|
3845
|
+
type: "text",
|
|
3846
|
+
text: `# End-User API Endpoints — ${category}
|
|
3847
|
+
|
|
3848
|
+
These endpoints are for building frontend applications for end-users.
|
|
3849
|
+
|
|
3850
|
+
${JSON.stringify(focused, null, 2)}
|
|
3851
|
+
|
|
3852
|
+
---
|
|
3853
|
+
For other categories, call get_end_user_endpoints again with category=${otherCategories.join(" | ")}.
|
|
3854
|
+
Use list_endpoint_categories() if you just want the bare list of available categories.`,
|
|
3855
|
+
},
|
|
3856
|
+
],
|
|
3857
|
+
};
|
|
3334
3858
|
}
|
|
3859
|
+
// No category filter → return everything. This is the explicit
|
|
3860
|
+
// "give me the whole catalog" path; the focused path above is
|
|
3861
|
+
// what the agent should use on every other call.
|
|
3335
3862
|
return {
|
|
3336
3863
|
content: [
|
|
3337
3864
|
{
|
|
3338
3865
|
type: "text",
|
|
3339
|
-
text: `# End-User API Endpoints
|
|
3866
|
+
text: `# End-User API Endpoints — ALL categories
|
|
3340
3867
|
|
|
3341
3868
|
These endpoints are for building frontend applications for end-users.
|
|
3342
3869
|
|
|
3343
|
-
${JSON.stringify(
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
|
|
3364
|
-
|
|
3365
|
-
|
|
3366
|
-
|
|
3367
|
-
|
|
3368
|
-
|
|
3369
|
-
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
|
|
3374
|
-
|
|
3375
|
-
|
|
3376
|
-
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
|
|
3436
|
-
|
|
3437
|
-
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3870
|
+
${JSON.stringify(END_USER_ENDPOINTS, null, 2)}
|
|
3871
|
+
|
|
3872
|
+
---
|
|
3873
|
+
Tip: subsequent calls should pass a single category (one of: ${allCategories.join(", ")}) so the output stays focused on what the agent is working on.`,
|
|
3874
|
+
},
|
|
3875
|
+
],
|
|
3876
|
+
};
|
|
3877
|
+
}
|
|
3878
|
+
case "list_endpoint_categories": {
|
|
3879
|
+
// Lightweight helper (#347): give the agent just the category names
|
|
3880
|
+
// so it can target subsequent get_end_user_endpoints calls without
|
|
3881
|
+
// having to first download the whole catalog. ~30 tokens.
|
|
3882
|
+
const categories = Object.keys(END_USER_ENDPOINTS);
|
|
3883
|
+
const summaries = {};
|
|
3884
|
+
for (const c of categories) {
|
|
3885
|
+
const data = END_USER_ENDPOINTS[c];
|
|
3886
|
+
summaries[c] = (data && typeof data.description === "string")
|
|
3887
|
+
? data.description.split("\n")[0]
|
|
3888
|
+
: "";
|
|
3889
|
+
}
|
|
3890
|
+
return {
|
|
3891
|
+
content: [
|
|
3892
|
+
{
|
|
3893
|
+
type: "text",
|
|
3894
|
+
text: `# End-User Endpoint Categories
|
|
3895
|
+
|
|
3896
|
+
${categories.length} categories available. Call get_end_user_endpoints({ category }) with one of them.
|
|
3897
|
+
|
|
3898
|
+
${JSON.stringify({ categories, summaries }, null, 2)}`,
|
|
3899
|
+
},
|
|
3900
|
+
],
|
|
3901
|
+
};
|
|
3902
|
+
}
|
|
3903
|
+
case "get_stripe_config_url": {
|
|
3904
|
+
const orgId = args.org_id;
|
|
3905
|
+
if (!orgId) {
|
|
3906
|
+
throw new Error("get_stripe_config_url requires org_id");
|
|
3907
|
+
}
|
|
3908
|
+
const url = `${FRONTEND_URL}/org-admin/${encodeURIComponent(orgId)}/integrations/stripe`;
|
|
3909
|
+
return {
|
|
3910
|
+
content: [
|
|
3911
|
+
{
|
|
3912
|
+
type: "text",
|
|
3913
|
+
text: JSON.stringify({
|
|
3914
|
+
url,
|
|
3915
|
+
reason: "Stripe secrets (secret_key, webhook_secret) must be set via the Organization Owner Dashboard, not inline through the MCP. The human developer pastes the keys; the MCP NEVER accepts or returns secret material.",
|
|
3916
|
+
next_action: { type: "open_in_browser", url },
|
|
3917
|
+
}, null, 2),
|
|
3918
|
+
},
|
|
3919
|
+
],
|
|
3920
|
+
};
|
|
3921
|
+
}
|
|
3922
|
+
case "get_agent_secrets_url": {
|
|
3923
|
+
const agentId = args.agent_id;
|
|
3924
|
+
if (!agentId) {
|
|
3925
|
+
throw new Error("get_agent_secrets_url requires agent_id");
|
|
3926
|
+
}
|
|
3927
|
+
const url = `${FRONTEND_URL}/dashboard/agents/${encodeURIComponent(agentId)}/secrets`;
|
|
3928
|
+
return {
|
|
3929
|
+
content: [
|
|
3930
|
+
{
|
|
3931
|
+
type: "text",
|
|
3932
|
+
text: JSON.stringify({
|
|
3933
|
+
url,
|
|
3934
|
+
reason: "Agent provider keys (OpenAI / Anthropic / etc.) and webhook secrets must be set or rotated via the Tenant Dashboard, not inline through the MCP. The human developer pastes the keys; the MCP NEVER accepts or returns secret material.",
|
|
3935
|
+
next_action: { type: "open_in_browser", url },
|
|
3936
|
+
}, null, 2),
|
|
3937
|
+
},
|
|
3938
|
+
],
|
|
3939
|
+
};
|
|
3940
|
+
}
|
|
3941
|
+
case "get_plan_management_url": {
|
|
3942
|
+
const orgId = args.org_id;
|
|
3943
|
+
if (!orgId) {
|
|
3944
|
+
throw new Error("get_plan_management_url requires org_id");
|
|
3945
|
+
}
|
|
3946
|
+
const url = `${FRONTEND_URL}/org-admin/${encodeURIComponent(orgId)}/plans`;
|
|
3947
|
+
return {
|
|
3948
|
+
content: [
|
|
3949
|
+
{
|
|
3950
|
+
type: "text",
|
|
3951
|
+
text: JSON.stringify({
|
|
3952
|
+
url,
|
|
3953
|
+
reason: "Plan create / update / delete (and the Stripe-product sync they trigger) needs the Organization's Stripe secret_key, which lives encrypted in OrganizationConfig and is never exposed to the agent. Use the Dashboard for write operations; the MCP exposes the read paths (GET /v1/organization-admin/{org_id}/plans, GET /v1/organization-admin/{org_id}/plans/{plan_id}) for inspection.",
|
|
3954
|
+
next_action: { type: "open_in_browser", url },
|
|
3955
|
+
}, null, 2),
|
|
3956
|
+
},
|
|
3957
|
+
],
|
|
3958
|
+
};
|
|
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),
|
|
3449
3979
|
},
|
|
3450
3980
|
],
|
|
3451
3981
|
};
|
|
@@ -4319,6 +4849,7 @@ type to pick.`,
|
|
|
4319
4849
|
async function main() {
|
|
4320
4850
|
console.error(`🚀 Multi-tenant SaaS API MCP Server v${SERVER_VERSION}`);
|
|
4321
4851
|
console.error(`📡 API URL: ${API_URL}`);
|
|
4852
|
+
console.error(`🖥 Frontend URL (for redirect-URL tools): ${FRONTEND_URL}`);
|
|
4322
4853
|
console.error(`🔑 API Key: ${API_KEY ? "Configured" : "Not configured (set SAAS_API_KEY to enable authenticated calls)"}`);
|
|
4323
4854
|
console.error(`\n👉 Recommended first calls (vibecoding flow):`);
|
|
4324
4855
|
console.error(` 1. validate_credentials — verify key + detect type (pk_live_* vs sk_live_*)`);
|
|
@@ -4326,13 +4857,20 @@ async function main() {
|
|
|
4326
4857
|
console.error(` 3. recommend_stack — pick a framework that won't leak your key`);
|
|
4327
4858
|
console.error(`\nReference tools:`);
|
|
4328
4859
|
console.error(` - get_api_overview: Understand the API architecture`);
|
|
4329
|
-
console.error(` -
|
|
4860
|
+
console.error(` - list_endpoint_categories: List endpoint categories (cheap)`);
|
|
4861
|
+
console.error(` - get_end_user_endpoints: Get endpoint documentation (one category at a time)`);
|
|
4330
4862
|
console.error(` - get_sdk_template: Get TypeScript SDK template`);
|
|
4331
4863
|
console.error(` - get_authentication_flow: Auth implementation guide`);
|
|
4332
4864
|
console.error(` - get_common_patterns: Best practices and patterns`);
|
|
4333
4865
|
console.error(` - search_endpoints: Search for endpoints`);
|
|
4334
4866
|
console.error(` - get_endpoint_details: Get specific endpoint info`);
|
|
4335
4867
|
console.error(` - get_openapi_spec: Get full OpenAPI spec`);
|
|
4868
|
+
console.error(`\nRedirect-URL tools (no secrets ever returned by the MCP):`);
|
|
4869
|
+
console.error(` - get_stripe_config_url — Dashboard URL to set/rotate the Org's Stripe keys`);
|
|
4870
|
+
console.error(` - get_agent_secrets_url — Dashboard URL to rotate an Agent's secrets`);
|
|
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`);
|
|
4336
4874
|
console.error(`\nAuthenticated helpers:`);
|
|
4337
4875
|
console.error(` - list_end_users (sk_live_* required)`);
|
|
4338
4876
|
console.error(` - list_oauth_providers (any key)`);
|
package/package.json
CHANGED