@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.
Files changed (3) hide show
  1. package/README.md +21 -4
  2. package/dist/index.js +320 -117
  3. 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
- 14 tools, in three groups.
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
- | `get_end_user_endpoints` | Reference for every end-user endpoint, filterable by category (`authentication`, `organizations`, `subscriptions`, `billing`, `usage`, `agents`, `users`, `plans`, `ai`, `entities`, `departments`, `files`, `external_agents`). |
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. get_end_user_endpoints → see what's available for the feature you're building
126
- 7. get_sdk_template start writing real code
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. Two flavors:\n\n- **Tier 1 (legacy / platform-level)**: endpoints under /v1/billing/* use the platform's Stripe and a SECRET key (sk_live_*) only. They were originally for Genlobe → Tenant billing and a since-superseded Tier 2 prototype; keep using them for tenant-internal flows.\n\n- **Tier 3 (Organization end-user, Epic #209)**: each Organization runs its OWN Stripe (BYO key). End-users subscribe via /v1/organizations/{org_id}/billing/* (api key + JWT + active OrganizationMember). The Org's plan catalog is read via the public /v1/organizations/{org_id}/plans (api key only — no JWT). See the master spec at specs/features/billing/three-tier-billing.spec.md.",
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: false, headers: "X-Organization-Id required when key is not organization-scoped" },
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: "Two modes: (1) sk_live_* without JWT -> tenant-scoped record, created_by_id is NULL (e.g. catalog/blog/global config). (2) API key + end-user JWT -> user-scoped record, created_by_id = user.id. The endpoint accepts both shapes; ownership is determined by whether a JWT is present."
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
- Categories: authentication, organizations, subscriptions, billing, usage, agents, users, plans, ai, entities, departments, external_agents`,
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 for all endpoints.",
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
- let endpoints = END_USER_ENDPOINTS;
3528
+ const allCategories = Object.keys(END_USER_ENDPOINTS);
3332
3529
  if (category && category in END_USER_ENDPOINTS) {
3333
- endpoints = { [category]: END_USER_ENDPOINTS[category] };
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${category ? ` - ${category}` : ''}
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(endpoints, null, 2)}
3344
-
3345
- ## Quick Reference
3346
-
3347
- ### Authentication (No JWT required)
3348
- - POST /v1/auth/register - Register new user
3349
- - POST /v1/auth/login - Login user
3350
- - POST /v1/auth/refresh - Refresh access token
3351
- - POST /v1/auth/forgot-password - Request password reset
3352
- - POST /v1/auth/reset-password - Reset password
3353
- - POST /v1/auth/verify-email - Verify email (with API Key)
3354
- - POST /v1/auth/verify-email-with-token - Verify email with token only (no API Key)
3355
- - POST /v1/auth/resend-verification - Resend verification email
3356
- - GET /v1/auth/providers - List enabled OAuth providers (e.g. Google)
3357
- - GET /v1/auth/google/url?redirect_uri=... - Get Google OAuth URL
3358
- - GET /v1/auth/google/callback - OAuth callback (redirects with tokens in URL fragment #)
3359
-
3360
- ### Authenticated Endpoints (JWT required)
3361
- - GET /v1/auth/me - Get current user
3362
- - POST /v1/auth/logout - Logout
3363
- - POST /v1/auth/change-password - Change password (authenticated)
3364
- - GET /v1/organizations/my-organizations - Get user's organizations
3365
- - POST /v1/organizations - Create organization
3366
- - GET /v1/organizations/{id}/departments - List departments
3367
- - GET /v1/organizations/{id}/members - List members
3368
- - PUT /v1/organizations/{id}/members/{user_id} - Update member role
3369
- - DELETE /v1/organizations/{id}/members/{user_id} - Remove member
3370
- - POST /v1/organizations/{id}/invite - Invite member
3371
- - POST /v1/organizations/accept-invitation - Accept invitation
3372
- - POST /v1/organizations/reject-invitation - Reject invitation
3373
- - GET /v1/organizations/invitations/pending?email= - Check pending invitations
3374
- - GET /v1/organizations/invitations/{token} - Get invitation details
3375
- - POST /v1/organizations/{id}/set-default - Set default org
3376
- - GET /v1/organizations/{id}/subscription - Get org subscription
3377
- - POST /v1/organizations/{id}/subscription/{plan_id} - Subscribe to plan
3378
- - DELETE /v1/organizations/{id}/subscription - Cancel subscription
3379
- - GET /v1/organizations/{id}/usage - Get org usage
3380
-
3381
- ### Plans (JWT required)
3382
- - GET /v1/plans - List all plans (paginated)
3383
- - GET /v1/plans/active - List active plans
3384
- - GET /v1/plans/free - List free plans
3385
- - GET /v1/plans/{plan_id} - Get plan details
3386
-
3387
- ### Subscriptions (JWT required)
3388
- - GET /v1/subscriptions/plans - List plans
3389
- - GET /v1/subscriptions/plans/{plan_id} - Get plan by ID
3390
- - GET /v1/subscriptions/current - Get current subscription
3391
- - POST /v1/subscriptions/subscriptions - Create subscription
3392
- - GET /v1/subscriptions/subscriptions/{id} - Get subscription by ID
3393
- - PUT /v1/subscriptions/subscriptions/{id} - Update subscription
3394
- - DELETE /v1/subscriptions/subscriptions/{id} - Cancel subscription
3395
- - POST /v1/subscriptions/checkout - Start checkout
3396
- - GET /v1/subscriptions/users/{user_id}/usage - Get user plan usage
3397
- - GET /v1/subscriptions/users/{user_id}/usage/limits - Check user limits
3398
- - PUT /v1/subscriptions/users/{user_id}/plan/{plan_id} - Assign plan
3399
- - POST /v1/subscriptions/organizations/{org_id}/resume - Resume org subscription (sk_live_*)
3400
- - GET /v1/subscriptions/organizations/{org_id}/invoices - Get org invoices (sk_live_*)
3401
- - DELETE /v1/subscriptions/organizations/{org_id}/subscription - Cancel org subscription (sk_live_*)
3402
-
3403
- ### Billing (sk_live_* ONLY)
3404
- - GET /v1/billing/available-plans - List available plans
3405
- - POST /v1/billing/create-checkout-session - Create checkout session
3406
- - POST /v1/billing/change-plan - Change plan (upgrade/downgrade)
3407
- - POST /v1/billing/cancel-subscription - Cancel subscription
3408
- - GET /v1/billing/billing-history-org/{id} - Billing history
3409
-
3410
- ### Users
3411
- - POST /v1/users - Create user (sk_live_* + JWT)
3412
- - GET /v1/users - List users (sk_live_* only, paginated)
3413
- - GET /v1/users/stats - User stats (sk_live_* only)
3414
- - GET /v1/users/{id} - Get user (self or sk_live_*)
3415
- - PUT /v1/users/{id} - Update user
3416
- - PUT /v1/users/{id}/settings - Update settings
3417
- - POST /v1/users/{id}/deactivate - Deactivate user (soft delete)
3418
- - POST /v1/users/{id}/activate - Activate user (sk_live_* only)
3419
- - DELETE /v1/users/{id} - Delete user (sk_live_* only, soft delete)
3420
-
3421
- ### Usage (JWT required)
3422
- - GET /v1/api/usage/current - Current usage stats
3423
- - GET /v1/api/usage/history - Usage history (limit/offset pagination)
3424
- - GET /v1/api/usage/entity-usage - Entity usage stats
3425
- - GET /v1/usage/stats - Comprehensive usage statistics
3426
- - POST /v1/usage/check - Check if usage allowed (without consuming)
3427
- - POST /v1/usage/consume - Consume usage tokens/messages
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(` - get_end_user_endpoints: Get endpoint documentation`);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@genlobe/mcp-server",
3
- "version": "3.4.0",
3
+ "version": "3.5.1",
4
4
  "description": "MCP Server for GenLobe SaaS API - Provides AI assistants with comprehensive API documentation for building frontend applications",
5
5
  "main": "dist/index.js",
6
6
  "bin": {