@genlobe/mcp-server 3.4.0 → 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 +21 -4
- package/dist/index.js +320 -117
- 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,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,12 +136,15 @@ 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
|
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
|
|
@@ -163,7 +202,7 @@ const END_USER_ENDPOINTS = {
|
|
|
163
202
|
method: "POST",
|
|
164
203
|
path: "/v1/auth/register",
|
|
165
204
|
summary: "Register a new user",
|
|
166
|
-
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." },
|
|
167
206
|
rate_limit: "3 per hour per IP (blocked 30 min after exceeding)",
|
|
168
207
|
request_body: {
|
|
169
208
|
email: "string (required) - valid email",
|
|
@@ -829,7 +868,7 @@ const END_USER_ENDPOINTS = {
|
|
|
829
868
|
]
|
|
830
869
|
},
|
|
831
870
|
billing: {
|
|
832
|
-
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.",
|
|
833
872
|
endpoints: [
|
|
834
873
|
{
|
|
835
874
|
method: "GET",
|
|
@@ -1007,6 +1046,81 @@ const END_USER_ENDPOINTS = {
|
|
|
1007
1046
|
"403": "Caller is not an active OrganizationMember of the path's org.",
|
|
1008
1047
|
"404": "Caller has no Subscription for this Organization yet."
|
|
1009
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
|
+
}
|
|
1010
1124
|
}
|
|
1011
1125
|
]
|
|
1012
1126
|
},
|
|
@@ -1660,12 +1774,24 @@ const END_USER_ENDPOINTS = {
|
|
|
1660
1774
|
method: "POST",
|
|
1661
1775
|
path: "/v1/entity/records",
|
|
1662
1776
|
summary: "Create a new entity record",
|
|
1663
|
-
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" },
|
|
1664
1778
|
request_body: {
|
|
1665
1779
|
schema_id: "uuid (required)",
|
|
1666
1780
|
data: "object (required) - validated against schema fields_definition"
|
|
1667
1781
|
},
|
|
1668
|
-
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."
|
|
1669
1795
|
},
|
|
1670
1796
|
{
|
|
1671
1797
|
method: "GET",
|
|
@@ -3091,25 +3217,91 @@ Call this first to understand the API architecture and authentication model.`,
|
|
|
3091
3217
|
},
|
|
3092
3218
|
{
|
|
3093
3219
|
name: "get_end_user_endpoints",
|
|
3094
|
-
description: `Get detailed documentation for END-USER API endpoints.
|
|
3220
|
+
description: `Get detailed documentation for END-USER API endpoints, filtered to ONE category.
|
|
3095
3221
|
|
|
3096
3222
|
Use this when building a frontend for end-users. These endpoints use:
|
|
3097
3223
|
- API Key (X-API-Key header) - required for ALL requests
|
|
3098
3224
|
- JWT token (Authorization: Bearer) - required AFTER login
|
|
3099
3225
|
|
|
3100
|
-
|
|
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.`,
|
|
3101
3242
|
inputSchema: {
|
|
3102
3243
|
type: "object",
|
|
3103
3244
|
properties: {
|
|
3104
3245
|
category: {
|
|
3105
3246
|
type: "string",
|
|
3106
|
-
description: "Filter by category. Leave empty
|
|
3247
|
+
description: "Filter by category (strongly recommended). Leave empty only when you explicitly want the full catalog.",
|
|
3107
3248
|
enum: ["authentication", "organizations", "subscriptions", "billing", "usage", "agents", "users", "plans", "ai", "entities", "departments", "files", "external_agents"],
|
|
3108
3249
|
},
|
|
3109
3250
|
},
|
|
3110
3251
|
required: [],
|
|
3111
3252
|
},
|
|
3112
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
|
+
},
|
|
3113
3305
|
{
|
|
3114
3306
|
name: "get_request_headers",
|
|
3115
3307
|
description: "Get detailed information about required HTTP headers for API requests.",
|
|
@@ -3327,125 +3519,130 @@ Skipping these is how secret keys end up shipped to a browser bundle. Don't skip
|
|
|
3327
3519
|
};
|
|
3328
3520
|
}
|
|
3329
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).
|
|
3330
3527
|
const category = args.category;
|
|
3331
|
-
|
|
3528
|
+
const allCategories = Object.keys(END_USER_ENDPOINTS);
|
|
3332
3529
|
if (category && category in END_USER_ENDPOINTS) {
|
|
3333
|
-
|
|
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
|
+
};
|
|
3334
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.
|
|
3335
3552
|
return {
|
|
3336
3553
|
content: [
|
|
3337
3554
|
{
|
|
3338
3555
|
type: "text",
|
|
3339
|
-
text: `# End-User API Endpoints
|
|
3556
|
+
text: `# End-User API Endpoints — ALL categories
|
|
3340
3557
|
|
|
3341
3558
|
These endpoints are for building frontend applications for end-users.
|
|
3342
3559
|
|
|
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
|
-
### AI Agents (JWT required)
|
|
3430
|
-
- GET /v1/user/agents/available - List available agents
|
|
3431
|
-
- GET /v1/user/agents/{agent_id} - Get agent details
|
|
3432
|
-
- POST /v1/user/agents/{agent_id}/chat - Chat with an agent
|
|
3433
|
-
- GET /v1/user/agents/{agent_id}/history - Get conversation history
|
|
3434
|
-
|
|
3435
|
-
### Custom Entities (API Key + JWT + X-Organization-Id)
|
|
3436
|
-
- GET /v1/entity/schemas - List schemas
|
|
3437
|
-
- POST /v1/entity/schemas - Create schema
|
|
3438
|
-
- GET /v1/entity/schemas/{id} - Get schema
|
|
3439
|
-
- PUT /v1/entity/schemas/{id} - Update schema
|
|
3440
|
-
- DELETE /v1/entity/schemas/{id} - Delete schema
|
|
3441
|
-
- POST /v1/entity/records - Create record
|
|
3442
|
-
- GET /v1/entity/records/{id} - Get record
|
|
3443
|
-
- PUT /v1/entity/records/{id} - Update record
|
|
3444
|
-
- DELETE /v1/entity/records/{id} - Delete record
|
|
3445
|
-
- POST /v1/entity/records/search - Search records with filters
|
|
3446
|
-
|
|
3447
|
-
### Departments
|
|
3448
|
-
- 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),
|
|
3449
3646
|
},
|
|
3450
3647
|
],
|
|
3451
3648
|
};
|
|
@@ -4319,6 +4516,7 @@ type to pick.`,
|
|
|
4319
4516
|
async function main() {
|
|
4320
4517
|
console.error(`🚀 Multi-tenant SaaS API MCP Server v${SERVER_VERSION}`);
|
|
4321
4518
|
console.error(`📡 API URL: ${API_URL}`);
|
|
4519
|
+
console.error(`🖥 Frontend URL (for redirect-URL tools): ${FRONTEND_URL}`);
|
|
4322
4520
|
console.error(`🔑 API Key: ${API_KEY ? "Configured" : "Not configured (set SAAS_API_KEY to enable authenticated calls)"}`);
|
|
4323
4521
|
console.error(`\n👉 Recommended first calls (vibecoding flow):`);
|
|
4324
4522
|
console.error(` 1. validate_credentials — verify key + detect type (pk_live_* vs sk_live_*)`);
|
|
@@ -4326,13 +4524,18 @@ async function main() {
|
|
|
4326
4524
|
console.error(` 3. recommend_stack — pick a framework that won't leak your key`);
|
|
4327
4525
|
console.error(`\nReference tools:`);
|
|
4328
4526
|
console.error(` - get_api_overview: Understand the API architecture`);
|
|
4329
|
-
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)`);
|
|
4330
4529
|
console.error(` - get_sdk_template: Get TypeScript SDK template`);
|
|
4331
4530
|
console.error(` - get_authentication_flow: Auth implementation guide`);
|
|
4332
4531
|
console.error(` - get_common_patterns: Best practices and patterns`);
|
|
4333
4532
|
console.error(` - search_endpoints: Search for endpoints`);
|
|
4334
4533
|
console.error(` - get_endpoint_details: Get specific endpoint info`);
|
|
4335
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`);
|
|
4336
4539
|
console.error(`\nAuthenticated helpers:`);
|
|
4337
4540
|
console.error(` - list_end_users (sk_live_* required)`);
|
|
4338
4541
|
console.error(` - list_oauth_providers (any key)`);
|
package/package.json
CHANGED