@genlobe/mcp-server 3.3.1 → 3.5.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 +23 -6
- package/dist/index.js +332 -120
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -72,8 +72,9 @@ Edit `claude_desktop_config.json`:
|
|
|
72
72
|
|
|
73
73
|
| Variable | Required | Value |
|
|
74
74
|
|---|---|---|
|
|
75
|
-
| `SAAS_API_URL` |
|
|
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,15 @@ Edit `claude_desktop_config.json`:
|
|
|
81
82
|
|
|
82
83
|
## Tools
|
|
83
84
|
|
|
84
|
-
|
|
85
|
+
18 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`, `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. |
|
|
92
94
|
| `get_request_headers` | The exact HTTP headers required for end-user requests, with examples. |
|
|
93
95
|
| `get_authentication_flow` | Step-by-step register → login → refresh → logout, including token storage and refresh-on-401 patterns. |
|
|
94
96
|
| `get_common_patterns` | Pagination, error handling, optimistic updates, file upload, RAG queries, agent execution. |
|
|
@@ -96,6 +98,18 @@ Edit `claude_desktop_config.json`:
|
|
|
96
98
|
| `search_endpoints` | Keyword search across path, summary, and description. |
|
|
97
99
|
| `get_endpoint_details` | Full request/response schema for a specific `(method, path)`. |
|
|
98
100
|
|
|
101
|
+
### Redirect-URL tools (no secrets ever returned by the MCP)
|
|
102
|
+
|
|
103
|
+
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.
|
|
104
|
+
|
|
105
|
+
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`.
|
|
106
|
+
|
|
107
|
+
| Tool | Returns |
|
|
108
|
+
|---|---|
|
|
109
|
+
| `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. |
|
|
110
|
+
| `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. |
|
|
111
|
+
| `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`. |
|
|
112
|
+
|
|
99
113
|
### Live (require `SAAS_API_KEY`)
|
|
100
114
|
|
|
101
115
|
| Tool | Does |
|
|
@@ -122,18 +136,21 @@ Edit `claude_desktop_config.json`:
|
|
|
122
136
|
3. recommend_stack → pick a framework that won't leak the key
|
|
123
137
|
4. get_api_overview → understand the API at a high level
|
|
124
138
|
5. get_authentication_flow → wire up register / login / refresh
|
|
125
|
-
6.
|
|
126
|
-
7.
|
|
139
|
+
6. list_endpoint_categories → see which endpoint categories exist
|
|
140
|
+
7. get_end_user_endpoints → drill into one category at a time
|
|
141
|
+
8. get_sdk_template → start writing real code
|
|
127
142
|
```
|
|
128
143
|
|
|
129
144
|
For targeted questions ("how do I list organizations?"), `search_endpoints` + `get_endpoint_details` is faster than scrolling through `get_end_user_endpoints`.
|
|
130
145
|
|
|
146
|
+
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.
|
|
147
|
+
|
|
131
148
|
---
|
|
132
149
|
|
|
133
150
|
## Support
|
|
134
151
|
|
|
135
152
|
- Sign up and manage API keys: [genlobe.ai](https://genlobe.ai)
|
|
136
|
-
- Issues and feature requests: [github.com/KleioTechnology/saas-multi-agent-tenant-ui/issues](https://github.com/KleioTechnology/saas-multi-agent-tenant-ui/issues)
|
|
153
|
+
- Issues and feature requests: [github.com/KleioTechnology/saas-multi-agent-tenant-ui/issues](https://github.com/KleioTechnology/saas-multi-agent-tenant-ui/issues)
|
|
137
154
|
|
|
138
155
|
---
|
|
139
156
|
|
package/dist/index.js
CHANGED
|
@@ -29,8 +29,52 @@ import { createRequire } from "module";
|
|
|
29
29
|
const require = createRequire(import.meta.url);
|
|
30
30
|
const pkg = require("../package.json");
|
|
31
31
|
const SERVER_VERSION = pkg.version;
|
|
32
|
-
|
|
32
|
+
// Default to the public Genlobe API for npm-distributed installs (#268). A
|
|
33
|
+
// developer who installs this package and forgets to set SAAS_API_URL gets
|
|
34
|
+
// a working install pointed at production instead of `connection refused`.
|
|
35
|
+
// Override with `SAAS_API_URL=http://localhost:8001` when running against
|
|
36
|
+
// a local backend in development.
|
|
37
|
+
const API_URL = process.env.SAAS_API_URL || process.env.API_URL || "https://api-core.genlobe.ai";
|
|
33
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
|
+
}
|
|
34
78
|
// Cache for API responses
|
|
35
79
|
const cache = new Map();
|
|
36
80
|
const CACHE_TTL = 5 * 60 * 1000; // 5 minutes
|
|
@@ -158,7 +202,7 @@ const END_USER_ENDPOINTS = {
|
|
|
158
202
|
method: "POST",
|
|
159
203
|
path: "/v1/auth/register",
|
|
160
204
|
summary: "Register a new user",
|
|
161
|
-
auth: { api_key: "pk_live_* or sk_live_*", jwt: false },
|
|
205
|
+
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." },
|
|
162
206
|
rate_limit: "3 per hour per IP (blocked 30 min after exceeding)",
|
|
163
207
|
request_body: {
|
|
164
208
|
email: "string (required) - valid email",
|
|
@@ -824,7 +868,7 @@ const END_USER_ENDPOINTS = {
|
|
|
824
868
|
]
|
|
825
869
|
},
|
|
826
870
|
billing: {
|
|
827
|
-
description: "Billing operations.
|
|
871
|
+
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.",
|
|
828
872
|
endpoints: [
|
|
829
873
|
{
|
|
830
874
|
method: "GET",
|
|
@@ -1002,6 +1046,81 @@ const END_USER_ENDPOINTS = {
|
|
|
1002
1046
|
"403": "Caller is not an active OrganizationMember of the path's org.",
|
|
1003
1047
|
"404": "Caller has no Subscription for this Organization yet."
|
|
1004
1048
|
}
|
|
1049
|
+
},
|
|
1050
|
+
// ---------------------------------------------------------------
|
|
1051
|
+
// Organization-admin read-only endpoints (BILL-4 / BILL-9, Epic
|
|
1052
|
+
// #209). Auth: end-user JWT + `require_org_admin` (the caller must
|
|
1053
|
+
// be an OrganizationMember of the path's org with role IN ('owner',
|
|
1054
|
+
// 'admin')). These are the surfaces an Org Owner uses to inspect
|
|
1055
|
+
// billing state from the MCP without touching any secret. The
|
|
1056
|
+
// secret-setting paths (POST/PATCH/DELETE on config/stripe + plan
|
|
1057
|
+
// CRUD that needs a Stripe key) are intentionally NOT exposed here
|
|
1058
|
+
// — they go through the redirect-URL tools (`get_stripe_config_url`,
|
|
1059
|
+
// `get_plan_management_url`) so the human pastes the secret in the
|
|
1060
|
+
// Dashboard. See "MCP server invariants" in the root CLAUDE.md.
|
|
1061
|
+
// ---------------------------------------------------------------
|
|
1062
|
+
{
|
|
1063
|
+
method: "GET",
|
|
1064
|
+
path: "/v1/organization-admin/{organization_id}/plans",
|
|
1065
|
+
summary: "List the Organization's plans (admin view — includes inactive / un-synced plans)",
|
|
1066
|
+
auth: { api_key: "pk_live_* or sk_live_*", jwt: true, require_org_admin: true },
|
|
1067
|
+
response: {
|
|
1068
|
+
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.",
|
|
1069
|
+
total: "number"
|
|
1070
|
+
},
|
|
1071
|
+
errors: {
|
|
1072
|
+
"403": "Caller is not an OrganizationMember with role owner/admin of the path's org (rejects TenantMember JWT)."
|
|
1073
|
+
},
|
|
1074
|
+
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."
|
|
1075
|
+
},
|
|
1076
|
+
{
|
|
1077
|
+
method: "GET",
|
|
1078
|
+
path: "/v1/organization-admin/{organization_id}/plans/{plan_id}",
|
|
1079
|
+
summary: "Read one plan (admin view)",
|
|
1080
|
+
auth: { api_key: "pk_live_* or sk_live_*", jwt: true, require_org_admin: true },
|
|
1081
|
+
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).",
|
|
1082
|
+
errors: {
|
|
1083
|
+
"403": "Caller is not an OrganizationMember with role owner/admin of the path's org.",
|
|
1084
|
+
"404": "Plan not found OR plan.organization_id != path organization_id (collapsed to 404 to avoid cross-org existence leaks)."
|
|
1085
|
+
}
|
|
1086
|
+
},
|
|
1087
|
+
{
|
|
1088
|
+
method: "GET",
|
|
1089
|
+
path: "/v1/organization-admin/{organization_id}/config/stripe",
|
|
1090
|
+
summary: "Read the Organization's MASKED Stripe config status (no secrets returned)",
|
|
1091
|
+
auth: { api_key: "pk_live_* or sk_live_*", jwt: true, require_org_admin: true },
|
|
1092
|
+
response: {
|
|
1093
|
+
organization_id: "uuid",
|
|
1094
|
+
publishable_key: "string | null — Stripe publishable key (pk_live_* / pk_test_*), safe to expose.",
|
|
1095
|
+
secret_key_display: "string | null — masked tail of the secret key, e.g. 'sk_live_****Pnxs'. Display-only — the actual key is never returned.",
|
|
1096
|
+
has_webhook_secret: "boolean — true if a webhook_secret is persisted.",
|
|
1097
|
+
is_active: "boolean — true once secret_key + webhook_secret are both present."
|
|
1098
|
+
},
|
|
1099
|
+
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.",
|
|
1100
|
+
errors: {
|
|
1101
|
+
"403": "Caller is not an OrganizationMember with role owner/admin of the path's org."
|
|
1102
|
+
}
|
|
1103
|
+
},
|
|
1104
|
+
{
|
|
1105
|
+
method: "GET",
|
|
1106
|
+
path: "/v1/organization-admin/{organization_id}/audit",
|
|
1107
|
+
summary: "Paginated billing audit log for the Organization",
|
|
1108
|
+
auth: { api_key: "pk_live_* or sk_live_*", jwt: true, require_org_admin: true },
|
|
1109
|
+
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'."
|
|
1113
|
+
},
|
|
1114
|
+
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)"
|
|
1119
|
+
},
|
|
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.",
|
|
1121
|
+
errors: {
|
|
1122
|
+
"403": "Caller is not an OrganizationMember with role owner/admin of the path's org."
|
|
1123
|
+
}
|
|
1005
1124
|
}
|
|
1006
1125
|
]
|
|
1007
1126
|
},
|
|
@@ -1190,20 +1309,24 @@ const END_USER_ENDPOINTS = {
|
|
|
1190
1309
|
{
|
|
1191
1310
|
id: "uuid",
|
|
1192
1311
|
tenant_id: "uuid",
|
|
1312
|
+
organization_id: "uuid - Org this agent belongs to. Tenant-catalog agents live in the Tenant's root Org; private agents live in customer Orgs (ADR-0006 v2 Hybrid Catalog)",
|
|
1313
|
+
is_public: "boolean - true when this agent is in the Tenant's catalog (visible to all customer Orgs of the Tenant)",
|
|
1314
|
+
forked_from_agent_id: "uuid | null - if this agent is a fork of a catalog agent, points to the source. Forks are independent (no template sync)",
|
|
1193
1315
|
name: "string",
|
|
1194
1316
|
description: "string | null",
|
|
1195
1317
|
system_prompt: "string",
|
|
1196
1318
|
response_type: "text | audio | video",
|
|
1197
1319
|
rag_provider: "NONE | GOOGLE_FILE_SEARCH",
|
|
1320
|
+
rag_role: "string | null - declarative tag matched against KnowledgeBase.role_tag in the caller's Org at runtime. Replaces the legacy single-store rag_config.store_id path (closes cross-org RAG leak G5)",
|
|
1198
1321
|
is_active: "boolean",
|
|
1199
|
-
rag_config: "object | null",
|
|
1322
|
+
rag_config: "object | null - DEPRECATED. New agents should use rag_role + a per-Org KnowledgeBase",
|
|
1200
1323
|
mcp_servers: "array of MCP server configs",
|
|
1201
1324
|
created_by: "uuid",
|
|
1202
1325
|
created_at: "ISO datetime",
|
|
1203
1326
|
updated_at: "ISO datetime"
|
|
1204
1327
|
}
|
|
1205
1328
|
],
|
|
1206
|
-
note: "Returns only active agents
|
|
1329
|
+
note: "Returns only active agents visible to the calling Organization: the Org's own private agents UNION public agents in the Tenant's catalog (root Org). End-user JWT context determines the Org via current_user.default_organization_id."
|
|
1207
1330
|
},
|
|
1208
1331
|
{
|
|
1209
1332
|
method: "GET",
|
|
@@ -1651,12 +1774,24 @@ const END_USER_ENDPOINTS = {
|
|
|
1651
1774
|
method: "POST",
|
|
1652
1775
|
path: "/v1/entity/records",
|
|
1653
1776
|
summary: "Create a new entity record",
|
|
1654
|
-
auth: { api_key: "sk_live_* or pk_live_*", jwt:
|
|
1777
|
+
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" },
|
|
1655
1778
|
request_body: {
|
|
1656
1779
|
schema_id: "uuid (required)",
|
|
1657
1780
|
data: "object (required) - validated against schema fields_definition"
|
|
1658
1781
|
},
|
|
1659
|
-
notes: "
|
|
1782
|
+
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."
|
|
1783
|
+
},
|
|
1784
|
+
{
|
|
1785
|
+
method: "GET",
|
|
1786
|
+
path: "/v1/entity/records/mine",
|
|
1787
|
+
summary: "List records authored by the current end-user (\"my data\")",
|
|
1788
|
+
auth: { api_key: "sk_live_* or pk_live_*", jwt: true, headers: "X-Organization-Id required when key is not organization-scoped" },
|
|
1789
|
+
query_params: {
|
|
1790
|
+
schema_id: "uuid (required) - schema whose records to list",
|
|
1791
|
+
page: "number (default 1)",
|
|
1792
|
+
size: "number (default 20, max 100)"
|
|
1793
|
+
},
|
|
1794
|
+
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."
|
|
1660
1795
|
},
|
|
1661
1796
|
{
|
|
1662
1797
|
method: "GET",
|
|
@@ -3082,25 +3217,91 @@ Call this first to understand the API architecture and authentication model.`,
|
|
|
3082
3217
|
},
|
|
3083
3218
|
{
|
|
3084
3219
|
name: "get_end_user_endpoints",
|
|
3085
|
-
description: `Get detailed documentation for END-USER API endpoints.
|
|
3220
|
+
description: `Get detailed documentation for END-USER API endpoints, filtered to ONE category.
|
|
3086
3221
|
|
|
3087
3222
|
Use this when building a frontend for end-users. These endpoints use:
|
|
3088
3223
|
- API Key (X-API-Key header) - required for ALL requests
|
|
3089
3224
|
- JWT token (Authorization: Bearer) - required AFTER login
|
|
3090
3225
|
|
|
3091
|
-
|
|
3226
|
+
PREFER passing a single category. The output is focused on that category + a
|
|
3227
|
+
one-line pointer to the others. Calling without a category dumps the entire
|
|
3228
|
+
catalog (every endpoint of every category) and is rarely what you want — it
|
|
3229
|
+
burns context quickly.
|
|
3230
|
+
|
|
3231
|
+
If you don't know which categories exist yet, call list_endpoint_categories()
|
|
3232
|
+
first.
|
|
3233
|
+
|
|
3234
|
+
Categories: authentication, organizations, subscriptions, billing (includes Tier-3 admin read-only endpoints), usage, agents, users, plans, ai, entities, departments, files, external_agents.
|
|
3235
|
+
|
|
3236
|
+
The 'billing' category now covers both the end-user surface
|
|
3237
|
+
(/v1/organizations/{org_id}/billing/*) and the Organization-admin read-only
|
|
3238
|
+
surface (/v1/organization-admin/{org_id}/{plans, config/stripe, audit}).
|
|
3239
|
+
Secret-setting paths (config/stripe POST/PATCH/DELETE, plan CRUD that needs
|
|
3240
|
+
the Stripe key) go through get_stripe_config_url / get_plan_management_url —
|
|
3241
|
+
the MCP never accepts raw secrets inline.`,
|
|
3092
3242
|
inputSchema: {
|
|
3093
3243
|
type: "object",
|
|
3094
3244
|
properties: {
|
|
3095
3245
|
category: {
|
|
3096
3246
|
type: "string",
|
|
3097
|
-
description: "Filter by category. Leave empty
|
|
3247
|
+
description: "Filter by category (strongly recommended). Leave empty only when you explicitly want the full catalog.",
|
|
3098
3248
|
enum: ["authentication", "organizations", "subscriptions", "billing", "usage", "agents", "users", "plans", "ai", "entities", "departments", "files", "external_agents"],
|
|
3099
3249
|
},
|
|
3100
3250
|
},
|
|
3101
3251
|
required: [],
|
|
3102
3252
|
},
|
|
3103
3253
|
},
|
|
3254
|
+
{
|
|
3255
|
+
name: "list_endpoint_categories",
|
|
3256
|
+
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.`,
|
|
3257
|
+
inputSchema: {
|
|
3258
|
+
type: "object",
|
|
3259
|
+
properties: {},
|
|
3260
|
+
required: [],
|
|
3261
|
+
},
|
|
3262
|
+
},
|
|
3263
|
+
{
|
|
3264
|
+
name: "get_stripe_config_url",
|
|
3265
|
+
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.`,
|
|
3266
|
+
inputSchema: {
|
|
3267
|
+
type: "object",
|
|
3268
|
+
properties: {
|
|
3269
|
+
org_id: {
|
|
3270
|
+
type: "string",
|
|
3271
|
+
description: "The Organization id (UUID) whose Stripe config the owner wants to manage.",
|
|
3272
|
+
},
|
|
3273
|
+
},
|
|
3274
|
+
required: ["org_id"],
|
|
3275
|
+
},
|
|
3276
|
+
},
|
|
3277
|
+
{
|
|
3278
|
+
name: "get_agent_secrets_url",
|
|
3279
|
+
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.`,
|
|
3280
|
+
inputSchema: {
|
|
3281
|
+
type: "object",
|
|
3282
|
+
properties: {
|
|
3283
|
+
agent_id: {
|
|
3284
|
+
type: "string",
|
|
3285
|
+
description: "The Agent id (UUID) whose secrets the developer wants to manage.",
|
|
3286
|
+
},
|
|
3287
|
+
},
|
|
3288
|
+
required: ["agent_id"],
|
|
3289
|
+
},
|
|
3290
|
+
},
|
|
3291
|
+
{
|
|
3292
|
+
name: "get_plan_management_url",
|
|
3293
|
+
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.`,
|
|
3294
|
+
inputSchema: {
|
|
3295
|
+
type: "object",
|
|
3296
|
+
properties: {
|
|
3297
|
+
org_id: {
|
|
3298
|
+
type: "string",
|
|
3299
|
+
description: "The Organization id (UUID) whose plan catalog the owner wants to manage.",
|
|
3300
|
+
},
|
|
3301
|
+
},
|
|
3302
|
+
required: ["org_id"],
|
|
3303
|
+
},
|
|
3304
|
+
},
|
|
3104
3305
|
{
|
|
3105
3306
|
name: "get_request_headers",
|
|
3106
3307
|
description: "Get detailed information about required HTTP headers for API requests.",
|
|
@@ -3318,125 +3519,130 @@ Skipping these is how secret keys end up shipped to a browser bundle. Don't skip
|
|
|
3318
3519
|
};
|
|
3319
3520
|
}
|
|
3320
3521
|
case "get_end_user_endpoints": {
|
|
3522
|
+
// Phase-1 P0 (#347): focused output. Return ONLY the requested category
|
|
3523
|
+
// plus a one-line pointer to the other categories. The previous
|
|
3524
|
+
// implementation dumped a global Quick Reference of every category
|
|
3525
|
+
// at the bottom of every per-category call — that burned thousands
|
|
3526
|
+
// of tokens of agent context for no benefit (see CHANGELOG 3.5.0).
|
|
3321
3527
|
const category = args.category;
|
|
3322
|
-
|
|
3528
|
+
const allCategories = Object.keys(END_USER_ENDPOINTS);
|
|
3323
3529
|
if (category && category in END_USER_ENDPOINTS) {
|
|
3324
|
-
|
|
3530
|
+
const focused = { [category]: END_USER_ENDPOINTS[category] };
|
|
3531
|
+
const otherCategories = allCategories.filter((c) => c !== category);
|
|
3532
|
+
return {
|
|
3533
|
+
content: [
|
|
3534
|
+
{
|
|
3535
|
+
type: "text",
|
|
3536
|
+
text: `# End-User API Endpoints — ${category}
|
|
3537
|
+
|
|
3538
|
+
These endpoints are for building frontend applications for end-users.
|
|
3539
|
+
|
|
3540
|
+
${JSON.stringify(focused, null, 2)}
|
|
3541
|
+
|
|
3542
|
+
---
|
|
3543
|
+
For other categories, call get_end_user_endpoints again with category=${otherCategories.join(" | ")}.
|
|
3544
|
+
Use list_endpoint_categories() if you just want the bare list of available categories.`,
|
|
3545
|
+
},
|
|
3546
|
+
],
|
|
3547
|
+
};
|
|
3325
3548
|
}
|
|
3549
|
+
// No category filter → return everything. This is the explicit
|
|
3550
|
+
// "give me the whole catalog" path; the focused path above is
|
|
3551
|
+
// what the agent should use on every other call.
|
|
3326
3552
|
return {
|
|
3327
3553
|
content: [
|
|
3328
3554
|
{
|
|
3329
3555
|
type: "text",
|
|
3330
|
-
text: `# End-User API Endpoints
|
|
3556
|
+
text: `# End-User API Endpoints — ALL categories
|
|
3331
3557
|
|
|
3332
3558
|
These endpoints are for building frontend applications for end-users.
|
|
3333
3559
|
|
|
3334
|
-
${JSON.stringify(
|
|
3335
|
-
|
|
3336
|
-
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
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
|
-
### AI Agents (JWT required)
|
|
3421
|
-
- GET /v1/user/agents/available - List available agents
|
|
3422
|
-
- GET /v1/user/agents/{agent_id} - Get agent details
|
|
3423
|
-
- POST /v1/user/agents/{agent_id}/chat - Chat with an agent
|
|
3424
|
-
- GET /v1/user/agents/{agent_id}/history - Get conversation history
|
|
3425
|
-
|
|
3426
|
-
### Custom Entities (API Key + JWT + X-Organization-Id)
|
|
3427
|
-
- GET /v1/entity/schemas - List schemas
|
|
3428
|
-
- POST /v1/entity/schemas - Create schema
|
|
3429
|
-
- GET /v1/entity/schemas/{id} - Get schema
|
|
3430
|
-
- PUT /v1/entity/schemas/{id} - Update schema
|
|
3431
|
-
- DELETE /v1/entity/schemas/{id} - Delete schema
|
|
3432
|
-
- POST /v1/entity/records - Create record
|
|
3433
|
-
- GET /v1/entity/records/{id} - Get record
|
|
3434
|
-
- PUT /v1/entity/records/{id} - Update record
|
|
3435
|
-
- DELETE /v1/entity/records/{id} - Delete record
|
|
3436
|
-
- POST /v1/entity/records/search - Search records with filters
|
|
3437
|
-
|
|
3438
|
-
### Departments
|
|
3439
|
-
- GET /v1/departments/{department_id} - Get department details`,
|
|
3560
|
+
${JSON.stringify(END_USER_ENDPOINTS, null, 2)}
|
|
3561
|
+
|
|
3562
|
+
---
|
|
3563
|
+
Tip: subsequent calls should pass a single category (one of: ${allCategories.join(", ")}) so the output stays focused on what the agent is working on.`,
|
|
3564
|
+
},
|
|
3565
|
+
],
|
|
3566
|
+
};
|
|
3567
|
+
}
|
|
3568
|
+
case "list_endpoint_categories": {
|
|
3569
|
+
// Lightweight helper (#347): give the agent just the category names
|
|
3570
|
+
// so it can target subsequent get_end_user_endpoints calls without
|
|
3571
|
+
// having to first download the whole catalog. ~30 tokens.
|
|
3572
|
+
const categories = Object.keys(END_USER_ENDPOINTS);
|
|
3573
|
+
const summaries = {};
|
|
3574
|
+
for (const c of categories) {
|
|
3575
|
+
const data = END_USER_ENDPOINTS[c];
|
|
3576
|
+
summaries[c] = (data && typeof data.description === "string")
|
|
3577
|
+
? data.description.split("\n")[0]
|
|
3578
|
+
: "";
|
|
3579
|
+
}
|
|
3580
|
+
return {
|
|
3581
|
+
content: [
|
|
3582
|
+
{
|
|
3583
|
+
type: "text",
|
|
3584
|
+
text: `# End-User Endpoint Categories
|
|
3585
|
+
|
|
3586
|
+
${categories.length} categories available. Call get_end_user_endpoints({ category }) with one of them.
|
|
3587
|
+
|
|
3588
|
+
${JSON.stringify({ categories, summaries }, null, 2)}`,
|
|
3589
|
+
},
|
|
3590
|
+
],
|
|
3591
|
+
};
|
|
3592
|
+
}
|
|
3593
|
+
case "get_stripe_config_url": {
|
|
3594
|
+
const orgId = args.org_id;
|
|
3595
|
+
if (!orgId) {
|
|
3596
|
+
throw new Error("get_stripe_config_url requires org_id");
|
|
3597
|
+
}
|
|
3598
|
+
const url = `${FRONTEND_URL}/org-admin/${encodeURIComponent(orgId)}/integrations/stripe`;
|
|
3599
|
+
return {
|
|
3600
|
+
content: [
|
|
3601
|
+
{
|
|
3602
|
+
type: "text",
|
|
3603
|
+
text: JSON.stringify({
|
|
3604
|
+
url,
|
|
3605
|
+
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.",
|
|
3606
|
+
next_action: { type: "open_in_browser", url },
|
|
3607
|
+
}, null, 2),
|
|
3608
|
+
},
|
|
3609
|
+
],
|
|
3610
|
+
};
|
|
3611
|
+
}
|
|
3612
|
+
case "get_agent_secrets_url": {
|
|
3613
|
+
const agentId = args.agent_id;
|
|
3614
|
+
if (!agentId) {
|
|
3615
|
+
throw new Error("get_agent_secrets_url requires agent_id");
|
|
3616
|
+
}
|
|
3617
|
+
const url = `${FRONTEND_URL}/dashboard/agents/${encodeURIComponent(agentId)}/secrets`;
|
|
3618
|
+
return {
|
|
3619
|
+
content: [
|
|
3620
|
+
{
|
|
3621
|
+
type: "text",
|
|
3622
|
+
text: JSON.stringify({
|
|
3623
|
+
url,
|
|
3624
|
+
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.",
|
|
3625
|
+
next_action: { type: "open_in_browser", url },
|
|
3626
|
+
}, null, 2),
|
|
3627
|
+
},
|
|
3628
|
+
],
|
|
3629
|
+
};
|
|
3630
|
+
}
|
|
3631
|
+
case "get_plan_management_url": {
|
|
3632
|
+
const orgId = args.org_id;
|
|
3633
|
+
if (!orgId) {
|
|
3634
|
+
throw new Error("get_plan_management_url requires org_id");
|
|
3635
|
+
}
|
|
3636
|
+
const url = `${FRONTEND_URL}/org-admin/${encodeURIComponent(orgId)}/plans`;
|
|
3637
|
+
return {
|
|
3638
|
+
content: [
|
|
3639
|
+
{
|
|
3640
|
+
type: "text",
|
|
3641
|
+
text: JSON.stringify({
|
|
3642
|
+
url,
|
|
3643
|
+
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.",
|
|
3644
|
+
next_action: { type: "open_in_browser", url },
|
|
3645
|
+
}, null, 2),
|
|
3440
3646
|
},
|
|
3441
3647
|
],
|
|
3442
3648
|
};
|
|
@@ -4310,6 +4516,7 @@ type to pick.`,
|
|
|
4310
4516
|
async function main() {
|
|
4311
4517
|
console.error(`🚀 Multi-tenant SaaS API MCP Server v${SERVER_VERSION}`);
|
|
4312
4518
|
console.error(`📡 API URL: ${API_URL}`);
|
|
4519
|
+
console.error(`🖥 Frontend URL (for redirect-URL tools): ${FRONTEND_URL}`);
|
|
4313
4520
|
console.error(`🔑 API Key: ${API_KEY ? "Configured" : "Not configured (set SAAS_API_KEY to enable authenticated calls)"}`);
|
|
4314
4521
|
console.error(`\n👉 Recommended first calls (vibecoding flow):`);
|
|
4315
4522
|
console.error(` 1. validate_credentials — verify key + detect type (pk_live_* vs sk_live_*)`);
|
|
@@ -4317,13 +4524,18 @@ async function main() {
|
|
|
4317
4524
|
console.error(` 3. recommend_stack — pick a framework that won't leak your key`);
|
|
4318
4525
|
console.error(`\nReference tools:`);
|
|
4319
4526
|
console.error(` - get_api_overview: Understand the API architecture`);
|
|
4320
|
-
console.error(` -
|
|
4527
|
+
console.error(` - list_endpoint_categories: List endpoint categories (cheap)`);
|
|
4528
|
+
console.error(` - get_end_user_endpoints: Get endpoint documentation (one category at a time)`);
|
|
4321
4529
|
console.error(` - get_sdk_template: Get TypeScript SDK template`);
|
|
4322
4530
|
console.error(` - get_authentication_flow: Auth implementation guide`);
|
|
4323
4531
|
console.error(` - get_common_patterns: Best practices and patterns`);
|
|
4324
4532
|
console.error(` - search_endpoints: Search for endpoints`);
|
|
4325
4533
|
console.error(` - get_endpoint_details: Get specific endpoint info`);
|
|
4326
4534
|
console.error(` - get_openapi_spec: Get full OpenAPI spec`);
|
|
4535
|
+
console.error(`\nRedirect-URL tools (no secrets ever returned by the MCP):`);
|
|
4536
|
+
console.error(` - get_stripe_config_url — Dashboard URL to set/rotate the Org's Stripe keys`);
|
|
4537
|
+
console.error(` - get_agent_secrets_url — Dashboard URL to rotate an Agent's secrets`);
|
|
4538
|
+
console.error(` - get_plan_management_url — Dashboard URL to create/edit Plans`);
|
|
4327
4539
|
console.error(`\nAuthenticated helpers:`);
|
|
4328
4540
|
console.error(` - list_end_users (sk_live_* required)`);
|
|
4329
4541
|
console.error(` - list_oauth_providers (any key)`);
|
package/package.json
CHANGED