@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.
Files changed (3) hide show
  1. package/README.md +23 -6
  2. package/dist/index.js +332 -120
  3. 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` | yes | `https://api-core.genlobe.ai` |
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,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. 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
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) (this single tracker covers the whole Genlobe platform, including this MCP server)
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
- const API_URL = process.env.SAAS_API_URL || process.env.API_URL || "http://localhost:8001";
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. 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.",
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 that the tenant has made available"
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: 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" },
1655
1778
  request_body: {
1656
1779
  schema_id: "uuid (required)",
1657
1780
  data: "object (required) - validated against schema fields_definition"
1658
1781
  },
1659
- 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."
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
- 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.`,
3092
3242
  inputSchema: {
3093
3243
  type: "object",
3094
3244
  properties: {
3095
3245
  category: {
3096
3246
  type: "string",
3097
- 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.",
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
- let endpoints = END_USER_ENDPOINTS;
3528
+ const allCategories = Object.keys(END_USER_ENDPOINTS);
3323
3529
  if (category && category in END_USER_ENDPOINTS) {
3324
- 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
+ };
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${category ? ` - ${category}` : ''}
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(endpoints, null, 2)}
3335
-
3336
- ## Quick Reference
3337
-
3338
- ### Authentication (No JWT required)
3339
- - POST /v1/auth/register - Register new user
3340
- - POST /v1/auth/login - Login user
3341
- - POST /v1/auth/refresh - Refresh access token
3342
- - POST /v1/auth/forgot-password - Request password reset
3343
- - POST /v1/auth/reset-password - Reset password
3344
- - POST /v1/auth/verify-email - Verify email (with API Key)
3345
- - POST /v1/auth/verify-email-with-token - Verify email with token only (no API Key)
3346
- - POST /v1/auth/resend-verification - Resend verification email
3347
- - GET /v1/auth/providers - List enabled OAuth providers (e.g. Google)
3348
- - GET /v1/auth/google/url?redirect_uri=... - Get Google OAuth URL
3349
- - GET /v1/auth/google/callback - OAuth callback (redirects with tokens in URL fragment #)
3350
-
3351
- ### Authenticated Endpoints (JWT required)
3352
- - GET /v1/auth/me - Get current user
3353
- - POST /v1/auth/logout - Logout
3354
- - POST /v1/auth/change-password - Change password (authenticated)
3355
- - GET /v1/organizations/my-organizations - Get user's organizations
3356
- - POST /v1/organizations - Create organization
3357
- - GET /v1/organizations/{id}/departments - List departments
3358
- - GET /v1/organizations/{id}/members - List members
3359
- - PUT /v1/organizations/{id}/members/{user_id} - Update member role
3360
- - DELETE /v1/organizations/{id}/members/{user_id} - Remove member
3361
- - POST /v1/organizations/{id}/invite - Invite member
3362
- - POST /v1/organizations/accept-invitation - Accept invitation
3363
- - POST /v1/organizations/reject-invitation - Reject invitation
3364
- - GET /v1/organizations/invitations/pending?email= - Check pending invitations
3365
- - GET /v1/organizations/invitations/{token} - Get invitation details
3366
- - POST /v1/organizations/{id}/set-default - Set default org
3367
- - GET /v1/organizations/{id}/subscription - Get org subscription
3368
- - POST /v1/organizations/{id}/subscription/{plan_id} - Subscribe to plan
3369
- - DELETE /v1/organizations/{id}/subscription - Cancel subscription
3370
- - GET /v1/organizations/{id}/usage - Get org usage
3371
-
3372
- ### Plans (JWT required)
3373
- - GET /v1/plans - List all plans (paginated)
3374
- - GET /v1/plans/active - List active plans
3375
- - GET /v1/plans/free - List free plans
3376
- - GET /v1/plans/{plan_id} - Get plan details
3377
-
3378
- ### Subscriptions (JWT required)
3379
- - GET /v1/subscriptions/plans - List plans
3380
- - GET /v1/subscriptions/plans/{plan_id} - Get plan by ID
3381
- - GET /v1/subscriptions/current - Get current subscription
3382
- - POST /v1/subscriptions/subscriptions - Create subscription
3383
- - GET /v1/subscriptions/subscriptions/{id} - Get subscription by ID
3384
- - PUT /v1/subscriptions/subscriptions/{id} - Update subscription
3385
- - DELETE /v1/subscriptions/subscriptions/{id} - Cancel subscription
3386
- - POST /v1/subscriptions/checkout - Start checkout
3387
- - GET /v1/subscriptions/users/{user_id}/usage - Get user plan usage
3388
- - GET /v1/subscriptions/users/{user_id}/usage/limits - Check user limits
3389
- - PUT /v1/subscriptions/users/{user_id}/plan/{plan_id} - Assign plan
3390
- - POST /v1/subscriptions/organizations/{org_id}/resume - Resume org subscription (sk_live_*)
3391
- - GET /v1/subscriptions/organizations/{org_id}/invoices - Get org invoices (sk_live_*)
3392
- - DELETE /v1/subscriptions/organizations/{org_id}/subscription - Cancel org subscription (sk_live_*)
3393
-
3394
- ### Billing (sk_live_* ONLY)
3395
- - GET /v1/billing/available-plans - List available plans
3396
- - POST /v1/billing/create-checkout-session - Create checkout session
3397
- - POST /v1/billing/change-plan - Change plan (upgrade/downgrade)
3398
- - POST /v1/billing/cancel-subscription - Cancel subscription
3399
- - GET /v1/billing/billing-history-org/{id} - Billing history
3400
-
3401
- ### Users
3402
- - POST /v1/users - Create user (sk_live_* + JWT)
3403
- - GET /v1/users - List users (sk_live_* only, paginated)
3404
- - GET /v1/users/stats - User stats (sk_live_* only)
3405
- - GET /v1/users/{id} - Get user (self or sk_live_*)
3406
- - PUT /v1/users/{id} - Update user
3407
- - PUT /v1/users/{id}/settings - Update settings
3408
- - POST /v1/users/{id}/deactivate - Deactivate user (soft delete)
3409
- - POST /v1/users/{id}/activate - Activate user (sk_live_* only)
3410
- - DELETE /v1/users/{id} - Delete user (sk_live_* only, soft delete)
3411
-
3412
- ### Usage (JWT required)
3413
- - GET /v1/api/usage/current - Current usage stats
3414
- - GET /v1/api/usage/history - Usage history (limit/offset pagination)
3415
- - GET /v1/api/usage/entity-usage - Entity usage stats
3416
- - GET /v1/usage/stats - Comprehensive usage statistics
3417
- - POST /v1/usage/check - Check if usage allowed (without consuming)
3418
- - POST /v1/usage/consume - Consume usage tokens/messages
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(` - 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)`);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@genlobe/mcp-server",
3
- "version": "3.3.1",
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": {