pulsemcp-cms-admin-mcp-server 0.9.13 → 0.9.15

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 (168) hide show
  1. package/README.md +15 -8
  2. package/build/shared/src/pulsemcp-admin-client/lib/add-official-mirror-to-regular-queue.js +2 -1
  3. package/build/shared/src/pulsemcp-admin-client/lib/admin-fetch.js +33 -0
  4. package/build/shared/src/pulsemcp-admin-client/lib/approve-official-mirror-queue-item-without-modifying.js +2 -1
  5. package/build/shared/src/pulsemcp-admin-client/lib/approve-official-mirror-queue-item.js +2 -1
  6. package/build/shared/src/pulsemcp-admin-client/lib/cleanup-good-jobs.js +2 -1
  7. package/build/shared/src/pulsemcp-admin-client/lib/create-api-key.js +51 -0
  8. package/build/shared/src/pulsemcp-admin-client/lib/create-mcp-implementation.js +2 -1
  9. package/build/shared/src/pulsemcp-admin-client/lib/create-mcp-json.js +2 -1
  10. package/build/shared/src/pulsemcp-admin-client/lib/create-post.js +2 -1
  11. package/build/shared/src/pulsemcp-admin-client/lib/create-redirect.js +2 -1
  12. package/build/shared/src/pulsemcp-admin-client/lib/create-tenant.js +35 -0
  13. package/build/shared/src/pulsemcp-admin-client/lib/create-unofficial-mirror.js +2 -1
  14. package/build/shared/src/pulsemcp-admin-client/lib/delete-mcp-json.js +2 -1
  15. package/build/shared/src/pulsemcp-admin-client/lib/delete-redirect.js +2 -1
  16. package/build/shared/src/pulsemcp-admin-client/lib/delete-unofficial-mirror.js +2 -1
  17. package/build/shared/src/pulsemcp-admin-client/lib/discard-good-job.js +2 -1
  18. package/build/shared/src/pulsemcp-admin-client/lib/force-trigger-good-job-cron.js +2 -1
  19. package/build/shared/src/pulsemcp-admin-client/lib/get-author-by-id.js +2 -1
  20. package/build/shared/src/pulsemcp-admin-client/lib/get-author-by-slug.js +2 -1
  21. package/build/shared/src/pulsemcp-admin-client/lib/get-authors.js +2 -1
  22. package/build/shared/src/pulsemcp-admin-client/lib/get-discovered-url-stats.js +2 -1
  23. package/build/shared/src/pulsemcp-admin-client/lib/get-discovered-urls.js +2 -1
  24. package/build/shared/src/pulsemcp-admin-client/lib/get-draft-mcp-implementations.js +2 -1
  25. package/build/shared/src/pulsemcp-admin-client/lib/get-good-job-cron-schedules.js +2 -1
  26. package/build/shared/src/pulsemcp-admin-client/lib/get-good-job-processes.js +2 -1
  27. package/build/shared/src/pulsemcp-admin-client/lib/get-good-job-statistics.js +2 -1
  28. package/build/shared/src/pulsemcp-admin-client/lib/get-good-job.js +2 -1
  29. package/build/shared/src/pulsemcp-admin-client/lib/get-good-jobs.js +2 -1
  30. package/build/shared/src/pulsemcp-admin-client/lib/get-mcp-client-by-id.js +2 -1
  31. package/build/shared/src/pulsemcp-admin-client/lib/get-mcp-client-by-slug.js +2 -1
  32. package/build/shared/src/pulsemcp-admin-client/lib/get-mcp-implementation-by-id.js +2 -1
  33. package/build/shared/src/pulsemcp-admin-client/lib/get-mcp-json.js +2 -1
  34. package/build/shared/src/pulsemcp-admin-client/lib/get-mcp-jsons.js +2 -1
  35. package/build/shared/src/pulsemcp-admin-client/lib/get-mcp-server-by-id.js +2 -1
  36. package/build/shared/src/pulsemcp-admin-client/lib/get-mcp-server-by-slug.js +2 -1
  37. package/build/shared/src/pulsemcp-admin-client/lib/get-moz-backlinks.js +2 -1
  38. package/build/shared/src/pulsemcp-admin-client/lib/get-moz-metrics.js +2 -1
  39. package/build/shared/src/pulsemcp-admin-client/lib/get-moz-stored-metrics.js +2 -1
  40. package/build/shared/src/pulsemcp-admin-client/lib/get-official-mirror-queue-item.js +2 -1
  41. package/build/shared/src/pulsemcp-admin-client/lib/get-official-mirror-queue-items.js +2 -1
  42. package/build/shared/src/pulsemcp-admin-client/lib/get-official-mirror.js +2 -1
  43. package/build/shared/src/pulsemcp-admin-client/lib/get-official-mirrors.js +2 -1
  44. package/build/shared/src/pulsemcp-admin-client/lib/get-post.js +2 -1
  45. package/build/shared/src/pulsemcp-admin-client/lib/get-posts.js +2 -1
  46. package/build/shared/src/pulsemcp-admin-client/lib/get-proctor-metadata.js +2 -1
  47. package/build/shared/src/pulsemcp-admin-client/lib/get-proctor-runs.js +2 -1
  48. package/build/shared/src/pulsemcp-admin-client/lib/get-provider-by-id.js +2 -1
  49. package/build/shared/src/pulsemcp-admin-client/lib/get-redirect.js +2 -1
  50. package/build/shared/src/pulsemcp-admin-client/lib/get-redirects.js +2 -1
  51. package/build/shared/src/pulsemcp-admin-client/lib/get-tenant.js +2 -1
  52. package/build/shared/src/pulsemcp-admin-client/lib/get-tenants.js +2 -1
  53. package/build/shared/src/pulsemcp-admin-client/lib/get-unified-mcp-server.js +3 -2
  54. package/build/shared/src/pulsemcp-admin-client/lib/get-unified-mcp-servers.js +2 -1
  55. package/build/shared/src/pulsemcp-admin-client/lib/get-unofficial-mirror.js +2 -1
  56. package/build/shared/src/pulsemcp-admin-client/lib/get-unofficial-mirrors.js +2 -1
  57. package/build/shared/src/pulsemcp-admin-client/lib/mark-discovered-url-processed.js +2 -1
  58. package/build/shared/src/pulsemcp-admin-client/lib/recache-mcp-server.js +24 -0
  59. package/build/shared/src/pulsemcp-admin-client/lib/reject-official-mirror-queue-item.js +2 -1
  60. package/build/shared/src/pulsemcp-admin-client/lib/reschedule-good-job.js +2 -1
  61. package/build/shared/src/pulsemcp-admin-client/lib/retry-good-job.js +2 -1
  62. package/build/shared/src/pulsemcp-admin-client/lib/run-exam-for-mirror.js +2 -1
  63. package/build/shared/src/pulsemcp-admin-client/lib/save-mcp-implementation.js +2 -1
  64. package/build/shared/src/pulsemcp-admin-client/lib/save-results-for-mirror.js +2 -1
  65. package/build/shared/src/pulsemcp-admin-client/lib/search-mcp-implementations.js +2 -1
  66. package/build/shared/src/pulsemcp-admin-client/lib/search-providers.js +2 -1
  67. package/build/shared/src/pulsemcp-admin-client/lib/send-email.js +2 -1
  68. package/build/shared/src/pulsemcp-admin-client/lib/unlink-official-mirror-queue-item.js +2 -1
  69. package/build/shared/src/pulsemcp-admin-client/lib/update-mcp-json.js +2 -1
  70. package/build/shared/src/pulsemcp-admin-client/lib/update-post.js +2 -1
  71. package/build/shared/src/pulsemcp-admin-client/lib/update-redirect.js +2 -1
  72. package/build/shared/src/pulsemcp-admin-client/lib/update-unofficial-mirror.js +2 -1
  73. package/build/shared/src/pulsemcp-admin-client/lib/upload-image.js +2 -1
  74. package/build/shared/src/pulsemcp-admin-client/pulsemcp-admin-client.integration-mock.js +27 -0
  75. package/build/shared/src/server.js +13 -1
  76. package/build/shared/src/tools/create-api-key.js +78 -0
  77. package/build/shared/src/tools/create-tenant.js +58 -0
  78. package/build/shared/src/tools/recache-mcp-server.js +49 -0
  79. package/build/shared/src/tools.js +13 -2
  80. package/package.json +2 -2
  81. package/shared/pulsemcp-admin-client/lib/add-official-mirror-to-regular-queue.js +2 -1
  82. package/shared/pulsemcp-admin-client/lib/admin-fetch.d.ts +6 -0
  83. package/shared/pulsemcp-admin-client/lib/admin-fetch.js +33 -0
  84. package/shared/pulsemcp-admin-client/lib/approve-official-mirror-queue-item-without-modifying.js +2 -1
  85. package/shared/pulsemcp-admin-client/lib/approve-official-mirror-queue-item.js +2 -1
  86. package/shared/pulsemcp-admin-client/lib/cleanup-good-jobs.js +2 -1
  87. package/shared/pulsemcp-admin-client/lib/create-api-key.d.ts +3 -0
  88. package/shared/pulsemcp-admin-client/lib/create-api-key.js +51 -0
  89. package/shared/pulsemcp-admin-client/lib/create-mcp-implementation.js +2 -1
  90. package/shared/pulsemcp-admin-client/lib/create-mcp-json.js +2 -1
  91. package/shared/pulsemcp-admin-client/lib/create-post.js +2 -1
  92. package/shared/pulsemcp-admin-client/lib/create-redirect.js +2 -1
  93. package/shared/pulsemcp-admin-client/lib/create-tenant.d.ts +3 -0
  94. package/shared/pulsemcp-admin-client/lib/create-tenant.js +35 -0
  95. package/shared/pulsemcp-admin-client/lib/create-unofficial-mirror.js +2 -1
  96. package/shared/pulsemcp-admin-client/lib/delete-mcp-json.js +2 -1
  97. package/shared/pulsemcp-admin-client/lib/delete-redirect.js +2 -1
  98. package/shared/pulsemcp-admin-client/lib/delete-unofficial-mirror.js +2 -1
  99. package/shared/pulsemcp-admin-client/lib/discard-good-job.js +2 -1
  100. package/shared/pulsemcp-admin-client/lib/force-trigger-good-job-cron.js +2 -1
  101. package/shared/pulsemcp-admin-client/lib/get-author-by-id.js +2 -1
  102. package/shared/pulsemcp-admin-client/lib/get-author-by-slug.js +2 -1
  103. package/shared/pulsemcp-admin-client/lib/get-authors.js +2 -1
  104. package/shared/pulsemcp-admin-client/lib/get-discovered-url-stats.js +2 -1
  105. package/shared/pulsemcp-admin-client/lib/get-discovered-urls.js +2 -1
  106. package/shared/pulsemcp-admin-client/lib/get-draft-mcp-implementations.js +2 -1
  107. package/shared/pulsemcp-admin-client/lib/get-good-job-cron-schedules.js +2 -1
  108. package/shared/pulsemcp-admin-client/lib/get-good-job-processes.js +2 -1
  109. package/shared/pulsemcp-admin-client/lib/get-good-job-statistics.js +2 -1
  110. package/shared/pulsemcp-admin-client/lib/get-good-job.js +2 -1
  111. package/shared/pulsemcp-admin-client/lib/get-good-jobs.js +2 -1
  112. package/shared/pulsemcp-admin-client/lib/get-mcp-client-by-id.js +2 -1
  113. package/shared/pulsemcp-admin-client/lib/get-mcp-client-by-slug.js +2 -1
  114. package/shared/pulsemcp-admin-client/lib/get-mcp-implementation-by-id.js +2 -1
  115. package/shared/pulsemcp-admin-client/lib/get-mcp-json.js +2 -1
  116. package/shared/pulsemcp-admin-client/lib/get-mcp-jsons.js +2 -1
  117. package/shared/pulsemcp-admin-client/lib/get-mcp-server-by-id.js +2 -1
  118. package/shared/pulsemcp-admin-client/lib/get-mcp-server-by-slug.js +2 -1
  119. package/shared/pulsemcp-admin-client/lib/get-moz-backlinks.js +2 -1
  120. package/shared/pulsemcp-admin-client/lib/get-moz-metrics.js +2 -1
  121. package/shared/pulsemcp-admin-client/lib/get-moz-stored-metrics.js +2 -1
  122. package/shared/pulsemcp-admin-client/lib/get-official-mirror-queue-item.js +2 -1
  123. package/shared/pulsemcp-admin-client/lib/get-official-mirror-queue-items.js +2 -1
  124. package/shared/pulsemcp-admin-client/lib/get-official-mirror.js +2 -1
  125. package/shared/pulsemcp-admin-client/lib/get-official-mirrors.js +2 -1
  126. package/shared/pulsemcp-admin-client/lib/get-post.js +2 -1
  127. package/shared/pulsemcp-admin-client/lib/get-posts.js +2 -1
  128. package/shared/pulsemcp-admin-client/lib/get-proctor-metadata.js +2 -1
  129. package/shared/pulsemcp-admin-client/lib/get-proctor-runs.js +2 -1
  130. package/shared/pulsemcp-admin-client/lib/get-provider-by-id.js +2 -1
  131. package/shared/pulsemcp-admin-client/lib/get-redirect.js +2 -1
  132. package/shared/pulsemcp-admin-client/lib/get-redirects.js +2 -1
  133. package/shared/pulsemcp-admin-client/lib/get-tenant.js +2 -1
  134. package/shared/pulsemcp-admin-client/lib/get-tenants.js +2 -1
  135. package/shared/pulsemcp-admin-client/lib/get-unified-mcp-server.js +3 -2
  136. package/shared/pulsemcp-admin-client/lib/get-unified-mcp-servers.js +2 -1
  137. package/shared/pulsemcp-admin-client/lib/get-unofficial-mirror.js +2 -1
  138. package/shared/pulsemcp-admin-client/lib/get-unofficial-mirrors.js +2 -1
  139. package/shared/pulsemcp-admin-client/lib/mark-discovered-url-processed.js +2 -1
  140. package/shared/pulsemcp-admin-client/lib/recache-mcp-server.d.ts +3 -0
  141. package/shared/pulsemcp-admin-client/lib/recache-mcp-server.js +24 -0
  142. package/shared/pulsemcp-admin-client/lib/reject-official-mirror-queue-item.js +2 -1
  143. package/shared/pulsemcp-admin-client/lib/reschedule-good-job.js +2 -1
  144. package/shared/pulsemcp-admin-client/lib/retry-good-job.js +2 -1
  145. package/shared/pulsemcp-admin-client/lib/run-exam-for-mirror.js +2 -1
  146. package/shared/pulsemcp-admin-client/lib/save-mcp-implementation.js +2 -1
  147. package/shared/pulsemcp-admin-client/lib/save-results-for-mirror.js +2 -1
  148. package/shared/pulsemcp-admin-client/lib/search-mcp-implementations.js +2 -1
  149. package/shared/pulsemcp-admin-client/lib/search-providers.js +2 -1
  150. package/shared/pulsemcp-admin-client/lib/send-email.js +2 -1
  151. package/shared/pulsemcp-admin-client/lib/unlink-official-mirror-queue-item.js +2 -1
  152. package/shared/pulsemcp-admin-client/lib/update-mcp-json.js +2 -1
  153. package/shared/pulsemcp-admin-client/lib/update-post.js +2 -1
  154. package/shared/pulsemcp-admin-client/lib/update-redirect.js +2 -1
  155. package/shared/pulsemcp-admin-client/lib/update-unofficial-mirror.js +2 -1
  156. package/shared/pulsemcp-admin-client/lib/upload-image.js +2 -1
  157. package/shared/pulsemcp-admin-client/pulsemcp-admin-client.integration-mock.js +27 -0
  158. package/shared/server.d.ts +18 -1
  159. package/shared/server.js +13 -1
  160. package/shared/tools/create-api-key.d.ts +39 -0
  161. package/shared/tools/create-api-key.js +78 -0
  162. package/shared/tools/create-tenant.d.ts +30 -0
  163. package/shared/tools/create-tenant.js +58 -0
  164. package/shared/tools/recache-mcp-server.d.ts +30 -0
  165. package/shared/tools/recache-mcp-server.js +49 -0
  166. package/shared/tools.d.ts +2 -1
  167. package/shared/tools.js +13 -2
  168. package/shared/types.d.ts +22 -0
package/README.md CHANGED
@@ -69,6 +69,8 @@ This server is built and tested on macOS with Claude Desktop. It should work wit
69
69
  | `get_official_mirror` | official_mirrors | read | Get detailed official mirror info by ID or name. |
70
70
  | `get_tenants` | tenants | read | List tenants with search and admin status filtering. |
71
71
  | `get_tenant` | tenants | read | Get detailed tenant info by ID or slug. |
72
+ | `create_tenant` | tenants | write | Create a new tenant for sub-registry provisioning. |
73
+ | `create_api_key` | tenants | write | Create an API key for a tenant. Returns the raw key (only available at creation time). |
72
74
  | `get_mcp_jsons` | mcp_jsons | read | List MCP JSON configs with mirror and server filtering. |
73
75
  | `get_mcp_json` | mcp_jsons | read | Get a single MCP JSON configuration by ID. |
74
76
  | `create_mcp_json` | mcp_jsons | write | Create a new MCP JSON configuration for an unofficial mirror. |
@@ -77,6 +79,7 @@ This server is built and tested on macOS with Claude Desktop. It should work wit
77
79
  | `list_mcp_servers` | mcp_servers | read | List/search MCP servers with filtering by status, classification, pagination. |
78
80
  | `get_mcp_server` | mcp_servers | read | Get detailed MCP server info by slug (unified view of all admin UI fields). |
79
81
  | `update_mcp_server` | mcp_servers | write | Update an MCP server's fields (all admin UI fields supported). |
82
+ | `recache_mcp_server` | mcp_servers | write | Refresh the cache for a specific MCP server (show page, cards, canonicals, parent pages). |
80
83
  | `get_redirects` | redirects | read | List URL redirects with search, status filtering, and pagination. |
81
84
  | `get_redirect` | redirects | read | Get detailed redirect info by ID. |
82
85
  | `create_redirect` | redirects | write | Create a new URL redirect entry. |
@@ -125,11 +128,11 @@ This server organizes tools into groups that can be selectively enabled or disab
125
128
  | `unofficial_mirrors_readonly` | 2 | Unofficial mirrors read-only |
126
129
  | `official_mirrors` | 2 | Official mirrors REST API (read-only) |
127
130
  | `official_mirrors_readonly` | 2 | Official mirrors read-only (alias) |
128
- | `tenants` | 2 | Tenants REST API (read-only) |
129
- | `tenants_readonly` | 2 | Tenants read-only (alias) |
131
+ | `tenants` | 4 | Tenant management including API key provisioning (read + write) |
132
+ | `tenants_readonly` | 2 | Tenants read-only (list, get) |
130
133
  | `mcp_jsons` | 5 | Full MCP JSON configurations (read + write) |
131
134
  | `mcp_jsons_readonly` | 2 | MCP JSON configurations read-only |
132
- | `mcp_servers` | 3 | Full MCP servers management (read + write) |
135
+ | `mcp_servers` | 4 | Full MCP servers management (read + write) |
133
136
  | `mcp_servers_readonly` | 2 | MCP servers read-only (list, get) |
134
137
  | `redirects` | 5 | Full URL redirect management (read + write) |
135
138
  | `redirects_readonly` | 2 | URL redirects read-only (list, get) |
@@ -149,7 +152,7 @@ This server organizes tools into groups that can be selectively enabled or disab
149
152
  - Write: `draft_newsletter_post`, `update_newsletter_post`, `upload_image`
150
153
  - **server_directory** / **server_directory_readonly** (superset — includes tools from mcp_servers, unofficial_mirrors, official_mirrors, official_queue, and mcp_jsons):
151
154
  - Read-only: `search_mcp_implementations`, `get_draft_mcp_implementations`, `find_providers`, `list_mcp_servers`, `get_mcp_server`, `get_unofficial_mirrors`, `get_unofficial_mirror`, `get_official_mirrors`, `get_official_mirror`, `get_official_mirror_queue_items`, `get_official_mirror_queue_item`, `get_mcp_jsons`, `get_mcp_json`
152
- - Write: `save_mcp_implementation`, `send_impl_posted_notif`, `update_mcp_server`, `create_unofficial_mirror`, `update_unofficial_mirror`, `delete_unofficial_mirror`, `approve_official_mirror_queue_item`, `approve_mirror_no_modify`, `reject_official_mirror_queue_item`, `add_official_mirror_to_regular_queue`, `unlink_official_mirror_queue_item`, `create_mcp_json`, `update_mcp_json`, `delete_mcp_json`
155
+ - Write: `save_mcp_implementation`, `send_impl_posted_notif`, `update_mcp_server`, `recache_mcp_server`, `create_unofficial_mirror`, `update_unofficial_mirror`, `delete_unofficial_mirror`, `approve_official_mirror_queue_item`, `approve_mirror_no_modify`, `reject_official_mirror_queue_item`, `add_official_mirror_to_regular_queue`, `unlink_official_mirror_queue_item`, `create_mcp_json`, `update_mcp_json`, `delete_mcp_json`
153
156
  - **official_queue** / **official_queue_readonly**:
154
157
  - Read-only: `get_official_mirror_queue_items`, `get_official_mirror_queue_item`
155
158
  - Write: `approve_official_mirror_queue_item`, `approve_mirror_no_modify`, `reject_official_mirror_queue_item`, `add_official_mirror_to_regular_queue`, `unlink_official_mirror_queue_item`
@@ -160,12 +163,13 @@ This server organizes tools into groups that can be selectively enabled or disab
160
163
  - Read-only: `get_official_mirrors`, `get_official_mirror`
161
164
  - **tenants** / **tenants_readonly**:
162
165
  - Read-only: `get_tenants`, `get_tenant`
166
+ - Write: `create_tenant`, `create_api_key`
163
167
  - **mcp_jsons** / **mcp_jsons_readonly**:
164
168
  - Read-only: `get_mcp_jsons`, `get_mcp_json`
165
169
  - Write: `create_mcp_json`, `update_mcp_json`, `delete_mcp_json`
166
170
  - **mcp_servers** / **mcp_servers_readonly**:
167
171
  - Read-only: `list_mcp_servers`, `get_mcp_server`
168
- - Write: `update_mcp_server`
172
+ - Write: `update_mcp_server`, `recache_mcp_server`
169
173
  - **redirects** / **redirects_readonly**:
170
174
  - Read-only: `get_redirects`, `get_redirect`
171
175
  - Write: `create_redirect`, `update_redirect`, `delete_redirect`
@@ -183,9 +187,12 @@ This server organizes tools into groups that can be selectively enabled or disab
183
187
 
184
188
  ## Environment Variables
185
189
 
186
- | Variable | Description | Default |
187
- | ------------- | ------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
188
- | `TOOL_GROUPS` | Comma-separated list of enabled tool groups | `newsletter,server_directory,official_queue,unofficial_mirrors,official_mirrors,tenants,mcp_jsons,mcp_servers,redirects,good_jobs,proctor,discovered_urls,moz` (all base groups) |
190
+ | Variable | Description | Default |
191
+ | -------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
192
+ | `PULSEMCP_ADMIN_API_KEY` | API key sent as `X-API-Key` for admin-API authentication. Required. | — |
193
+ | `PULSEMCP_ADMIN_API_URL` | Base URL for the admin API. | `https://admin.pulsemcp.com` |
194
+ | `PULSEMCP_ADMIN_INTERNAL_SECRET` | Optional shared secret sent as the `X-PulseMCP-Internal` header. A Cloudflare custom rule uses it to route trusted internal traffic past the WAF and rate limiter in front of `admin.pulsemcp.com`. Needed in production to avoid spurious 403s; leave unset locally and against staging (staging is grey-clouded, no WAF). | unset |
195
+ | `TOOL_GROUPS` | Comma-separated list of enabled tool groups | `newsletter,server_directory,official_queue,unofficial_mirrors,official_mirrors,tenants,mcp_jsons,mcp_servers,redirects,good_jobs,proctor,discovered_urls,moz` (all base groups) |
189
196
 
190
197
  ## Examples
191
198
 
@@ -1,3 +1,4 @@
1
+ import { adminFetch } from './admin-fetch.js';
1
2
  function mapQueueItem(item) {
2
3
  return {
3
4
  id: item.id,
@@ -23,7 +24,7 @@ function mapQueueItem(item) {
23
24
  }
24
25
  export async function addOfficialMirrorToRegularQueue(apiKey, baseUrl, id) {
25
26
  const url = new URL(`/api/official_mirror_queues/${id}/add_to_regular_queue`, baseUrl);
26
- const response = await fetch(url.toString(), {
27
+ const response = await adminFetch(url.toString(), {
27
28
  method: 'POST',
28
29
  headers: {
29
30
  'X-API-Key': apiKey,
@@ -0,0 +1,33 @@
1
+ // The admin API origin (admin.pulsemcp.com) sits behind Cloudflare, which
2
+ // applies rate limits (100 GET/min/IP → Managed Challenge) and Super Bot
3
+ // Fight Mode to all traffic. Internal clients (this MCP server, the discovery
4
+ // pipeline, etc.) can trip those rules and see spurious 403s even when their
5
+ // X-API-Key is valid. A Cloudflare custom rule skips the WAF stack when the
6
+ // X-PulseMCP-Internal header carries a shared secret.
7
+ //
8
+ // This helper wraps fetch() and injects that header from the env var
9
+ // PULSEMCP_ADMIN_INTERNAL_SECRET when set. When unset, requests still work
10
+ // but are subject to the full WAF treatment — fine for local dev, tests, and
11
+ // staging (admin.staging.pulsemcp.com is grey-clouded, so Cloudflare is not
12
+ // in the path). See pulsemcp/pulsemcp#2882 for context.
13
+ const INTERNAL_HEADER = 'X-PulseMCP-Internal';
14
+ const INTERNAL_SECRET_ENV_VAR = 'PULSEMCP_ADMIN_INTERNAL_SECRET';
15
+ function defaultGetInternalSecret() {
16
+ const value = process.env[INTERNAL_SECRET_ENV_VAR];
17
+ return value && value.length > 0 ? value : undefined;
18
+ }
19
+ // `input` is intentionally narrowed to `string | URL` rather than the broader
20
+ // `RequestInfo`. A `Request`'s body is a single-use `ReadableStream`, and
21
+ // narrowing keeps semantics predictable if a retry wrapper is ever layered
22
+ // on top. All call sites in this client lib pass URL strings.
23
+ export async function adminFetch(input, init, deps = {}) {
24
+ const fetchFn = deps.fetchFn ?? fetch;
25
+ const getSecret = deps.getInternalSecret ?? defaultGetInternalSecret;
26
+ const secret = getSecret();
27
+ if (!secret) {
28
+ return fetchFn(input, init);
29
+ }
30
+ const headers = new Headers(init?.headers);
31
+ headers.set(INTERNAL_HEADER, secret);
32
+ return fetchFn(input, { ...init, headers });
33
+ }
@@ -1,3 +1,4 @@
1
+ import { adminFetch } from './admin-fetch.js';
1
2
  function mapQueueItem(item) {
2
3
  return {
3
4
  id: item.id,
@@ -23,7 +24,7 @@ function mapQueueItem(item) {
23
24
  }
24
25
  export async function approveOfficialMirrorQueueItemWithoutModifying(apiKey, baseUrl, id) {
25
26
  const url = new URL(`/api/official_mirror_queues/${id}/approve_without_modifying`, baseUrl);
26
- const response = await fetch(url.toString(), {
27
+ const response = await adminFetch(url.toString(), {
27
28
  method: 'POST',
28
29
  headers: {
29
30
  'X-API-Key': apiKey,
@@ -1,3 +1,4 @@
1
+ import { adminFetch } from './admin-fetch.js';
1
2
  function mapQueueItem(item) {
2
3
  return {
3
4
  id: item.id,
@@ -25,7 +26,7 @@ export async function approveOfficialMirrorQueueItem(apiKey, baseUrl, id, mcpSer
25
26
  const url = new URL(`/api/official_mirror_queues/${id}/approve`, baseUrl);
26
27
  const formData = new URLSearchParams();
27
28
  formData.append('mcp_server_slug', mcpServerSlug);
28
- const response = await fetch(url.toString(), {
29
+ const response = await adminFetch(url.toString(), {
29
30
  method: 'POST',
30
31
  headers: {
31
32
  'X-API-Key': apiKey,
@@ -1,3 +1,4 @@
1
+ import { adminFetch } from './admin-fetch.js';
1
2
  export async function cleanupGoodJobs(apiKey, baseUrl, params) {
2
3
  const url = new URL('/api/good_jobs/cleanup', baseUrl);
3
4
  const body = {};
@@ -7,7 +8,7 @@ export async function cleanupGoodJobs(apiKey, baseUrl, params) {
7
8
  if (params?.status !== undefined) {
8
9
  body.status = params.status;
9
10
  }
10
- const response = await fetch(url.toString(), {
11
+ const response = await adminFetch(url.toString(), {
11
12
  method: 'DELETE',
12
13
  headers: {
13
14
  'X-API-Key': apiKey,
@@ -0,0 +1,51 @@
1
+ import { adminFetch } from './admin-fetch.js';
2
+ export async function createApiKey(apiKey, baseUrl, params) {
3
+ const url = new URL('/api/api_keys', baseUrl);
4
+ const body = {
5
+ tenant_slug: params.tenant_slug,
6
+ };
7
+ if (params.name !== undefined) {
8
+ body.name = params.name;
9
+ }
10
+ if (params.permission_level !== undefined) {
11
+ body.permission_level = params.permission_level;
12
+ }
13
+ const response = await adminFetch(url.toString(), {
14
+ method: 'POST',
15
+ headers: {
16
+ 'X-API-Key': apiKey,
17
+ 'Content-Type': 'application/json',
18
+ Accept: 'application/json',
19
+ },
20
+ body: JSON.stringify(body),
21
+ });
22
+ if (!response.ok) {
23
+ if (response.status === 401) {
24
+ throw new Error('Invalid API key');
25
+ }
26
+ if (response.status === 403) {
27
+ throw new Error('User lacks write privileges');
28
+ }
29
+ if (response.status === 404) {
30
+ const errorData = (await response.json());
31
+ throw new Error(errorData.error || 'Tenant not found');
32
+ }
33
+ if (response.status === 422) {
34
+ const errorData = (await response.json());
35
+ const message = errorData.errors?.join(', ') || errorData.error || 'Unknown error';
36
+ throw new Error(`Validation failed: ${message}`);
37
+ }
38
+ throw new Error(`Failed to create API key: ${response.status} ${response.statusText}`);
39
+ }
40
+ const result = (await response.json());
41
+ return {
42
+ id: result.id,
43
+ name: result.name,
44
+ tenant_id: result.tenant_id,
45
+ tenant_slug: result.tenant_slug,
46
+ tenant_is_admin: result.tenant_is_admin,
47
+ permission_level: result.permission_level,
48
+ key: result.key,
49
+ created_at: result.created_at,
50
+ };
51
+ }
@@ -1,3 +1,4 @@
1
+ import { adminFetch } from './admin-fetch.js';
1
2
  export async function createMCPImplementation(apiKey, baseUrl, params) {
2
3
  const url = new URL(`/api/implementations`, baseUrl);
3
4
  // Build form data for the POST request
@@ -141,7 +142,7 @@ export async function createMCPImplementation(apiKey, baseUrl, params) {
141
142
  });
142
143
  }
143
144
  }
144
- const response = await fetch(url.toString(), {
145
+ const response = await adminFetch(url.toString(), {
145
146
  method: 'POST',
146
147
  headers: {
147
148
  'X-API-Key': apiKey,
@@ -1,3 +1,4 @@
1
+ import { adminFetch } from './admin-fetch.js';
1
2
  export async function createMcpJson(apiKey, baseUrl, params) {
2
3
  const url = new URL('/api/mcp_jsons', baseUrl);
3
4
  const body = {
@@ -8,7 +9,7 @@ export async function createMcpJson(apiKey, baseUrl, params) {
8
9
  if (params.description !== undefined) {
9
10
  body.description = params.description;
10
11
  }
11
- const response = await fetch(url.toString(), {
12
+ const response = await adminFetch(url.toString(), {
12
13
  method: 'POST',
13
14
  headers: {
14
15
  'X-API-Key': apiKey,
@@ -1,3 +1,4 @@
1
+ import { adminFetch } from './admin-fetch.js';
1
2
  export async function createPost(apiKey, baseUrl, params) {
2
3
  const url = new URL('/posts', baseUrl);
3
4
  // Build form data for the POST request
@@ -47,7 +48,7 @@ export async function createPost(apiKey, baseUrl, params) {
47
48
  formData.append('post[featured_mcp_client_ids][]', id.toString());
48
49
  });
49
50
  }
50
- const response = await fetch(url.toString(), {
51
+ const response = await adminFetch(url.toString(), {
51
52
  method: 'POST',
52
53
  headers: {
53
54
  'X-API-Key': apiKey,
@@ -1,3 +1,4 @@
1
+ import { adminFetch } from './admin-fetch.js';
1
2
  export async function createRedirect(apiKey, baseUrl, params) {
2
3
  const url = new URL('/api/redirects', baseUrl);
3
4
  const body = {
@@ -7,7 +8,7 @@ export async function createRedirect(apiKey, baseUrl, params) {
7
8
  if (params.status !== undefined) {
8
9
  body.status = params.status;
9
10
  }
10
- const response = await fetch(url.toString(), {
11
+ const response = await adminFetch(url.toString(), {
11
12
  method: 'POST',
12
13
  headers: {
13
14
  'X-API-Key': apiKey,
@@ -0,0 +1,35 @@
1
+ import { adminFetch } from './admin-fetch.js';
2
+ export async function createTenant(apiKey, baseUrl, params) {
3
+ const url = new URL('/api/tenants', baseUrl);
4
+ const response = await adminFetch(url.toString(), {
5
+ method: 'POST',
6
+ headers: {
7
+ 'X-API-Key': apiKey,
8
+ 'Content-Type': 'application/json',
9
+ Accept: 'application/json',
10
+ },
11
+ body: JSON.stringify({ slug: params.slug }),
12
+ });
13
+ if (!response.ok) {
14
+ if (response.status === 401) {
15
+ throw new Error('Invalid API key');
16
+ }
17
+ if (response.status === 403) {
18
+ throw new Error('User lacks write privileges');
19
+ }
20
+ if (response.status === 422) {
21
+ const errorData = (await response.json());
22
+ throw new Error(`Validation failed: ${errorData.errors?.join(', ') || 'Unknown error'}`);
23
+ }
24
+ throw new Error(`Failed to create tenant: ${response.status} ${response.statusText}`);
25
+ }
26
+ const tenant = (await response.json());
27
+ return {
28
+ id: tenant.id,
29
+ slug: tenant.slug,
30
+ is_admin: tenant.is_admin,
31
+ enrichments: tenant.enrichments,
32
+ created_at: tenant.created_at,
33
+ updated_at: tenant.updated_at,
34
+ };
35
+ }
@@ -1,3 +1,4 @@
1
+ import { adminFetch } from './admin-fetch.js';
1
2
  export async function createUnofficialMirror(apiKey, baseUrl, params) {
2
3
  const url = new URL('/api/unofficial_mirrors', baseUrl);
3
4
  const body = {
@@ -14,7 +15,7 @@ export async function createUnofficialMirror(apiKey, baseUrl, params) {
14
15
  if (params.next_name !== undefined) {
15
16
  body.next_name = params.next_name;
16
17
  }
17
- const response = await fetch(url.toString(), {
18
+ const response = await adminFetch(url.toString(), {
18
19
  method: 'POST',
19
20
  headers: {
20
21
  'X-API-Key': apiKey,
@@ -1,6 +1,7 @@
1
+ import { adminFetch } from './admin-fetch.js';
1
2
  export async function deleteMcpJson(apiKey, baseUrl, id) {
2
3
  const url = new URL(`/api/mcp_jsons/${id}`, baseUrl);
3
- const response = await fetch(url.toString(), {
4
+ const response = await adminFetch(url.toString(), {
4
5
  method: 'DELETE',
5
6
  headers: {
6
7
  'X-API-Key': apiKey,
@@ -1,6 +1,7 @@
1
+ import { adminFetch } from './admin-fetch.js';
1
2
  export async function deleteRedirect(apiKey, baseUrl, id) {
2
3
  const url = new URL(`/api/redirects/${id}`, baseUrl);
3
- const response = await fetch(url.toString(), {
4
+ const response = await adminFetch(url.toString(), {
4
5
  method: 'DELETE',
5
6
  headers: {
6
7
  'X-API-Key': apiKey,
@@ -1,6 +1,7 @@
1
+ import { adminFetch } from './admin-fetch.js';
1
2
  export async function deleteUnofficialMirror(apiKey, baseUrl, id) {
2
3
  const url = new URL(`/api/unofficial_mirrors/${id}`, baseUrl);
3
- const response = await fetch(url.toString(), {
4
+ const response = await adminFetch(url.toString(), {
4
5
  method: 'DELETE',
5
6
  headers: {
6
7
  'X-API-Key': apiKey,
@@ -1,6 +1,7 @@
1
+ import { adminFetch } from './admin-fetch.js';
1
2
  export async function discardGoodJob(apiKey, baseUrl, id) {
2
3
  const url = new URL(`/api/good_jobs/${id}/discard`, baseUrl);
3
- const response = await fetch(url.toString(), {
4
+ const response = await adminFetch(url.toString(), {
4
5
  method: 'POST',
5
6
  headers: {
6
7
  'X-API-Key': apiKey,
@@ -1,6 +1,7 @@
1
+ import { adminFetch } from './admin-fetch.js';
1
2
  export async function forceTriggerGoodJobCron(apiKey, baseUrl, cronKey) {
2
3
  const url = new URL(`/api/good_jobs/cron_schedules/${cronKey}/trigger`, baseUrl);
3
- const response = await fetch(url.toString(), {
4
+ const response = await adminFetch(url.toString(), {
4
5
  method: 'POST',
5
6
  headers: {
6
7
  'X-API-Key': apiKey,
@@ -1,10 +1,11 @@
1
+ import { adminFetch } from './admin-fetch.js';
1
2
  // Simple in-memory cache for authors with TTL
2
3
  let authorsCache = null;
3
4
  let authorsCacheTimestamp = 0;
4
5
  const CACHE_TTL_MS = 60000; // 1 minute cache
5
6
  async function fetchAndCacheAuthors(apiKey, baseUrl) {
6
7
  const url = new URL('/supervisor/authors', baseUrl);
7
- const response = await fetch(url.toString(), {
8
+ const response = await adminFetch(url.toString(), {
8
9
  method: 'GET',
9
10
  headers: {
10
11
  'X-API-Key': apiKey,
@@ -1,7 +1,8 @@
1
+ import { adminFetch } from './admin-fetch.js';
1
2
  export async function getAuthorBySlug(apiKey, baseUrl, slug) {
2
3
  // Use the supervisor endpoint which supports JSON
3
4
  const url = new URL(`/supervisor/authors/${slug}`, baseUrl);
4
- const response = await fetch(url.toString(), {
5
+ const response = await adminFetch(url.toString(), {
5
6
  method: 'GET',
6
7
  headers: {
7
8
  'X-API-Key': apiKey,
@@ -1,3 +1,4 @@
1
+ import { adminFetch } from './admin-fetch.js';
1
2
  export async function getAuthors(apiKey, baseUrl, params) {
2
3
  // Use the supervisor endpoint which supports JSON
3
4
  const url = new URL('/supervisor/authors', baseUrl);
@@ -8,7 +9,7 @@ export async function getAuthors(apiKey, baseUrl, params) {
8
9
  if (params?.page) {
9
10
  url.searchParams.append('page', params.page.toString());
10
11
  }
11
- const response = await fetch(url.toString(), {
12
+ const response = await adminFetch(url.toString(), {
12
13
  method: 'GET',
13
14
  headers: {
14
15
  'X-API-Key': apiKey,
@@ -1,6 +1,7 @@
1
+ import { adminFetch } from './admin-fetch.js';
1
2
  export async function getDiscoveredUrlStats(apiKey, baseUrl) {
2
3
  const url = new URL('/api/discovered_urls/stats', baseUrl);
3
- const response = await fetch(url.toString(), {
4
+ const response = await adminFetch(url.toString(), {
4
5
  method: 'GET',
5
6
  headers: {
6
7
  'X-API-Key': apiKey,
@@ -1,3 +1,4 @@
1
+ import { adminFetch } from './admin-fetch.js';
1
2
  function mapDiscoveredUrl(raw) {
2
3
  const { id, url, source, created_at, ...rest } = raw;
3
4
  return {
@@ -19,7 +20,7 @@ export async function getDiscoveredUrls(apiKey, baseUrl, params) {
19
20
  if (params?.per_page) {
20
21
  url.searchParams.append('per_page', params.per_page.toString());
21
22
  }
22
- const response = await fetch(url.toString(), {
23
+ const response = await adminFetch(url.toString(), {
23
24
  method: 'GET',
24
25
  headers: {
25
26
  'X-API-Key': apiKey,
@@ -1,3 +1,4 @@
1
+ import { adminFetch } from './admin-fetch.js';
1
2
  function mapServerTags(tags) {
2
3
  if (!tags)
3
4
  return undefined;
@@ -58,7 +59,7 @@ export async function getDraftMCPImplementations(apiKey, baseUrl, params) {
58
59
  if (params?.search) {
59
60
  url.searchParams.append('search', params.search);
60
61
  }
61
- const response = await fetch(url.toString(), {
62
+ const response = await adminFetch(url.toString(), {
62
63
  method: 'GET',
63
64
  headers: {
64
65
  'X-API-Key': apiKey,
@@ -1,6 +1,7 @@
1
+ import { adminFetch } from './admin-fetch.js';
1
2
  export async function getGoodJobCronSchedules(apiKey, baseUrl) {
2
3
  const url = new URL('/api/good_jobs/cron_schedules', baseUrl);
3
- const response = await fetch(url.toString(), {
4
+ const response = await adminFetch(url.toString(), {
4
5
  method: 'GET',
5
6
  headers: {
6
7
  'X-API-Key': apiKey,
@@ -1,6 +1,7 @@
1
+ import { adminFetch } from './admin-fetch.js';
1
2
  export async function getGoodJobProcesses(apiKey, baseUrl) {
2
3
  const url = new URL('/api/good_jobs/processes', baseUrl);
3
- const response = await fetch(url.toString(), {
4
+ const response = await adminFetch(url.toString(), {
4
5
  method: 'GET',
5
6
  headers: {
6
7
  'X-API-Key': apiKey,
@@ -1,6 +1,7 @@
1
+ import { adminFetch } from './admin-fetch.js';
1
2
  export async function getGoodJobStatistics(apiKey, baseUrl) {
2
3
  const url = new URL('/api/good_jobs/statistics', baseUrl);
3
- const response = await fetch(url.toString(), {
4
+ const response = await adminFetch(url.toString(), {
4
5
  method: 'GET',
5
6
  headers: {
6
7
  'X-API-Key': apiKey,
@@ -1,6 +1,7 @@
1
+ import { adminFetch } from './admin-fetch.js';
1
2
  export async function getGoodJob(apiKey, baseUrl, id) {
2
3
  const url = new URL(`/api/good_jobs/${id}`, baseUrl);
3
- const response = await fetch(url.toString(), {
4
+ const response = await adminFetch(url.toString(), {
4
5
  method: 'GET',
5
6
  headers: {
6
7
  'X-API-Key': apiKey,
@@ -1,3 +1,4 @@
1
+ import { adminFetch } from './admin-fetch.js';
1
2
  function mapGoodJob(job) {
2
3
  return {
3
4
  id: job.id,
@@ -36,7 +37,7 @@ export async function getGoodJobs(apiKey, baseUrl, params) {
36
37
  if (params?.offset) {
37
38
  url.searchParams.append('offset', params.offset.toString());
38
39
  }
39
- const response = await fetch(url.toString(), {
40
+ const response = await adminFetch(url.toString(), {
40
41
  method: 'GET',
41
42
  headers: {
42
43
  'X-API-Key': apiKey,
@@ -1,8 +1,9 @@
1
+ import { adminFetch } from './admin-fetch.js';
1
2
  export async function getMCPClientById(apiKey, baseUrl, id) {
2
3
  // The API doesn't have a direct endpoint to get MCP client by ID,
3
4
  // so we need to fetch all clients and find the one with matching ID
4
5
  const url = new URL('/supervisor/mcp_clients', baseUrl);
5
- const response = await fetch(url.toString(), {
6
+ const response = await adminFetch(url.toString(), {
6
7
  method: 'GET',
7
8
  headers: {
8
9
  'X-API-Key': apiKey,
@@ -1,7 +1,8 @@
1
+ import { adminFetch } from './admin-fetch.js';
1
2
  export async function getMCPClientBySlug(apiKey, baseUrl, slug) {
2
3
  // Use the supervisor endpoint which supports JSON
3
4
  const url = new URL(`/supervisor/mcp_clients/${slug}`, baseUrl);
4
- const response = await fetch(url.toString(), {
5
+ const response = await adminFetch(url.toString(), {
5
6
  method: 'GET',
6
7
  headers: {
7
8
  'X-API-Key': apiKey,
@@ -1,6 +1,7 @@
1
+ import { adminFetch } from './admin-fetch.js';
1
2
  export async function getMCPImplementationById(apiKey, baseUrl, id) {
2
3
  const url = new URL(`/api/implementations/${id}`, baseUrl);
3
- const response = await fetch(url.toString(), {
4
+ const response = await adminFetch(url.toString(), {
4
5
  method: 'GET',
5
6
  headers: {
6
7
  'X-API-Key': apiKey,
@@ -1,6 +1,7 @@
1
+ import { adminFetch } from './admin-fetch.js';
1
2
  export async function getMcpJson(apiKey, baseUrl, id) {
2
3
  const url = new URL(`/api/mcp_jsons/${id}`, baseUrl);
3
- const response = await fetch(url.toString(), {
4
+ const response = await adminFetch(url.toString(), {
4
5
  method: 'GET',
5
6
  headers: {
6
7
  'X-API-Key': apiKey,
@@ -1,3 +1,4 @@
1
+ import { adminFetch } from './admin-fetch.js';
1
2
  function mapMcpJson(mcpJson) {
2
3
  return {
3
4
  id: mcpJson.id,
@@ -26,7 +27,7 @@ export async function getMcpJsons(apiKey, baseUrl, params) {
26
27
  if (params?.offset) {
27
28
  url.searchParams.append('offset', params.offset.toString());
28
29
  }
29
- const response = await fetch(url.toString(), {
30
+ const response = await adminFetch(url.toString(), {
30
31
  method: 'GET',
31
32
  headers: {
32
33
  'X-API-Key': apiKey,
@@ -1,8 +1,9 @@
1
+ import { adminFetch } from './admin-fetch.js';
1
2
  export async function getMCPServerById(apiKey, baseUrl, id) {
2
3
  // The API doesn't have a direct endpoint to get MCP server by ID,
3
4
  // so we need to fetch all servers and find the one with matching ID
4
5
  const url = new URL('/supervisor/mcp_servers', baseUrl);
5
- const response = await fetch(url.toString(), {
6
+ const response = await adminFetch(url.toString(), {
6
7
  method: 'GET',
7
8
  headers: {
8
9
  'X-API-Key': apiKey,
@@ -1,7 +1,8 @@
1
+ import { adminFetch } from './admin-fetch.js';
1
2
  export async function getMCPServerBySlug(apiKey, baseUrl, slug) {
2
3
  // Use the supervisor endpoint which supports JSON
3
4
  const url = new URL(`/supervisor/mcp_servers/${slug}`, baseUrl);
4
- const response = await fetch(url.toString(), {
5
+ const response = await adminFetch(url.toString(), {
5
6
  method: 'GET',
6
7
  headers: {
7
8
  'X-API-Key': apiKey,
@@ -1,3 +1,4 @@
1
+ import { adminFetch } from './admin-fetch.js';
1
2
  export async function getMozBacklinks(apiKey, baseUrl, params) {
2
3
  const apiUrl = new URL('/api/moz/backlinks', baseUrl);
3
4
  apiUrl.searchParams.append('url', params.url);
@@ -7,7 +8,7 @@ export async function getMozBacklinks(apiKey, baseUrl, params) {
7
8
  if (params.limit !== undefined) {
8
9
  apiUrl.searchParams.append('limit', params.limit.toString());
9
10
  }
10
- const response = await fetch(apiUrl.toString(), {
11
+ const response = await adminFetch(apiUrl.toString(), {
11
12
  method: 'GET',
12
13
  headers: {
13
14
  'X-API-Key': apiKey,