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
@@ -0,0 +1,78 @@
1
+ import { z } from 'zod';
2
+ const PARAM_DESCRIPTIONS = {
3
+ tenant_slug: 'The slug of the tenant to create the API key for',
4
+ name: 'Optional display name for the API key',
5
+ permission_level: 'Permission level for the key: read_only, read_and_upsert, or full_access. Defaults to full_access.',
6
+ };
7
+ const CreateApiKeySchema = z.object({
8
+ tenant_slug: z.string().describe(PARAM_DESCRIPTIONS.tenant_slug),
9
+ name: z.string().optional().describe(PARAM_DESCRIPTIONS.name),
10
+ permission_level: z
11
+ .enum(['read_only', 'read_and_upsert', 'full_access'])
12
+ .optional()
13
+ .describe(PARAM_DESCRIPTIONS.permission_level),
14
+ });
15
+ export function createApiKey(_server, clientFactory) {
16
+ return {
17
+ name: 'create_api_key',
18
+ description: `Create an API key for a specified tenant. Returns the raw key value, which is only available at creation time.
19
+
20
+ Example request:
21
+ {
22
+ "tenant_slug": "acme-corp",
23
+ "name": "Production Key",
24
+ "permission_level": "read_and_upsert"
25
+ }
26
+
27
+ Use cases:
28
+ - Provision API access for a new tenant
29
+ - Create keys with specific permission levels for different use cases
30
+ - Generate read-only keys for monitoring integrations`,
31
+ inputSchema: {
32
+ type: 'object',
33
+ properties: {
34
+ tenant_slug: { type: 'string', description: PARAM_DESCRIPTIONS.tenant_slug },
35
+ name: { type: 'string', description: PARAM_DESCRIPTIONS.name },
36
+ permission_level: {
37
+ type: 'string',
38
+ enum: ['read_only', 'read_and_upsert', 'full_access'],
39
+ description: PARAM_DESCRIPTIONS.permission_level,
40
+ },
41
+ },
42
+ required: ['tenant_slug'],
43
+ },
44
+ handler: async (args) => {
45
+ const validatedArgs = CreateApiKeySchema.parse(args);
46
+ const client = clientFactory();
47
+ try {
48
+ const apiKey = await client.createApiKey({
49
+ tenant_slug: validatedArgs.tenant_slug,
50
+ name: validatedArgs.name,
51
+ permission_level: validatedArgs.permission_level,
52
+ });
53
+ let content = `Successfully created API key!\n\n`;
54
+ content += `**ID:** ${apiKey.id}\n`;
55
+ if (apiKey.name) {
56
+ content += `**Name:** ${apiKey.name}\n`;
57
+ }
58
+ content += `**Tenant:** ${apiKey.tenant_slug} (ID: ${apiKey.tenant_id})\n`;
59
+ content += `**Permission Level:** ${apiKey.permission_level}\n`;
60
+ content += `**Key:** \`${apiKey.key}\`\n`;
61
+ content += `**Created:** ${apiKey.created_at}\n`;
62
+ content += `\n> **Important:** Save this key now — it cannot be retrieved again.`;
63
+ return { content: [{ type: 'text', text: content }] };
64
+ }
65
+ catch (error) {
66
+ return {
67
+ content: [
68
+ {
69
+ type: 'text',
70
+ text: `Error creating API key: ${error instanceof Error ? error.message : String(error)}`,
71
+ },
72
+ ],
73
+ isError: true,
74
+ };
75
+ }
76
+ },
77
+ };
78
+ }
@@ -0,0 +1,58 @@
1
+ import { z } from 'zod';
2
+ const PARAM_DESCRIPTIONS = {
3
+ slug: 'Unique slug identifier for the new tenant (e.g., "acme-corp")',
4
+ };
5
+ const CreateTenantSchema = z.object({
6
+ slug: z.string().describe(PARAM_DESCRIPTIONS.slug),
7
+ });
8
+ export function createTenant(_server, clientFactory) {
9
+ return {
10
+ name: 'create_tenant',
11
+ description: `Create a new tenant for sub-registry provisioning.
12
+
13
+ Example request:
14
+ {
15
+ "slug": "acme-corp"
16
+ }
17
+
18
+ Use cases:
19
+ - Provision a new sub-registry tenant
20
+ - Set up a new organization in the PulseMCP platform`,
21
+ inputSchema: {
22
+ type: 'object',
23
+ properties: {
24
+ slug: { type: 'string', description: PARAM_DESCRIPTIONS.slug },
25
+ },
26
+ required: ['slug'],
27
+ },
28
+ handler: async (args) => {
29
+ const validatedArgs = CreateTenantSchema.parse(args);
30
+ const client = clientFactory();
31
+ try {
32
+ const tenant = await client.createTenant({ slug: validatedArgs.slug });
33
+ let content = `Successfully created tenant!\n\n`;
34
+ content += `**ID:** ${tenant.id}\n`;
35
+ content += `**Slug:** ${tenant.slug}\n`;
36
+ content += `**Admin:** ${tenant.is_admin ? 'Yes' : 'No'}\n`;
37
+ if (tenant.enrichments && Object.keys(tenant.enrichments).length > 0) {
38
+ content += `**Enrichments:** ${Object.keys(tenant.enrichments).join(', ')}\n`;
39
+ }
40
+ if (tenant.created_at) {
41
+ content += `**Created:** ${tenant.created_at}\n`;
42
+ }
43
+ return { content: [{ type: 'text', text: content }] };
44
+ }
45
+ catch (error) {
46
+ return {
47
+ content: [
48
+ {
49
+ type: 'text',
50
+ text: `Error creating tenant: ${error instanceof Error ? error.message : String(error)}`,
51
+ },
52
+ ],
53
+ isError: true,
54
+ };
55
+ }
56
+ },
57
+ };
58
+ }
@@ -0,0 +1,49 @@
1
+ import { z } from 'zod';
2
+ const PARAM_DESCRIPTIONS = {
3
+ slug: 'The slug of the MCP server to recache (e.g., "filesystem")',
4
+ };
5
+ const RecacheMCPServerSchema = z.object({
6
+ slug: z.string().describe(PARAM_DESCRIPTIONS.slug),
7
+ });
8
+ export function recacheMCPServer(_server, clientFactory) {
9
+ return {
10
+ name: 'recache_mcp_server',
11
+ description: `Refresh the cache for a specific MCP server. Clears and warms the show page, card fragments across all sort variations, canonical URLs, and parent pages.
12
+
13
+ Example request:
14
+ {
15
+ "slug": "filesystem"
16
+ }
17
+
18
+ Use cases:
19
+ - Force a cache refresh after updating server data
20
+ - Fix stale cache entries for a specific server
21
+ - Warm caches after content changes`,
22
+ inputSchema: {
23
+ type: 'object',
24
+ properties: {
25
+ slug: { type: 'string', description: PARAM_DESCRIPTIONS.slug },
26
+ },
27
+ required: ['slug'],
28
+ },
29
+ handler: async (args) => {
30
+ const validatedArgs = RecacheMCPServerSchema.parse(args);
31
+ const client = clientFactory();
32
+ try {
33
+ const result = await client.recacheMCPServer(validatedArgs.slug);
34
+ return { content: [{ type: 'text', text: result.message }] };
35
+ }
36
+ catch (error) {
37
+ return {
38
+ content: [
39
+ {
40
+ type: 'text',
41
+ text: `Error recaching MCP server: ${error instanceof Error ? error.message : String(error)}`,
42
+ },
43
+ ],
44
+ isError: true,
45
+ };
46
+ }
47
+ },
48
+ };
49
+ }
@@ -26,9 +26,11 @@ import { deleteUnofficialMirror } from './tools/delete-unofficial-mirror.js';
26
26
  // Official mirrors REST tools (read-only)
27
27
  import { getOfficialMirrors } from './tools/get-official-mirrors.js';
28
28
  import { getOfficialMirror } from './tools/get-official-mirror.js';
29
- // Tenant tools (read-only)
29
+ // Tenant tools
30
30
  import { getTenants } from './tools/get-tenants.js';
31
31
  import { getTenant } from './tools/get-tenant.js';
32
+ import { createTenant } from './tools/create-tenant.js';
33
+ import { createApiKey } from './tools/create-api-key.js';
32
34
  // MCP JSON tools
33
35
  import { getMcpJsons } from './tools/get-mcp-jsons.js';
34
36
  import { getMcpJson } from './tools/get-mcp-json.js';
@@ -39,6 +41,7 @@ import { deleteMcpJson } from './tools/delete-mcp-json.js';
39
41
  import { listMCPServers } from './tools/list-mcp-servers.js';
40
42
  import { getMCPServer } from './tools/get-mcp-server.js';
41
43
  import { updateMCPServer } from './tools/update-mcp-server.js';
44
+ import { recacheMCPServer } from './tools/recache-mcp-server.js';
42
45
  // Redirect tools
43
46
  import { getRedirects } from './tools/get-redirects.js';
44
47
  import { getRedirect } from './tools/get-redirect.js';
@@ -170,9 +173,11 @@ const ALL_TOOLS = [
170
173
  groups: ['official_mirrors', 'server_directory'],
171
174
  isWriteOperation: false,
172
175
  },
173
- // Tenant tools (read-only)
176
+ // Tenant tools
174
177
  { factory: getTenants, groups: ['tenants'], isWriteOperation: false },
175
178
  { factory: getTenant, groups: ['tenants'], isWriteOperation: false },
179
+ { factory: createTenant, groups: ['tenants'], isWriteOperation: true },
180
+ { factory: createApiKey, groups: ['tenants'], isWriteOperation: true },
176
181
  // MCP JSON tools (CRUD) (also in server_directory)
177
182
  {
178
183
  factory: getMcpJsons,
@@ -215,6 +220,11 @@ const ALL_TOOLS = [
215
220
  groups: ['mcp_servers', 'server_directory'],
216
221
  isWriteOperation: true,
217
222
  },
223
+ {
224
+ factory: recacheMCPServer,
225
+ groups: ['mcp_servers', 'server_directory'],
226
+ isWriteOperation: true,
227
+ },
218
228
  // Redirect tools (CRUD)
219
229
  { factory: getRedirects, groups: ['redirects'], isWriteOperation: false },
220
230
  { factory: getRedirect, groups: ['redirects'], isWriteOperation: false },
@@ -372,6 +382,7 @@ function shouldIncludeTool(toolDef, enabledGroups) {
372
382
  * - unofficial_mirrors: Unofficial mirror CRUD tools (read + write)
373
383
  * - unofficial_mirrors_readonly: Unofficial mirror tools (read only)
374
384
  * - official_mirrors_readonly: Official mirrors REST API tools (read only)
385
+ * - tenants: Tenant management tools including API key provisioning (read + write)
375
386
  * - tenants_readonly: Tenant tools (read only)
376
387
  * - mcp_jsons: MCP JSON configuration tools (read + write)
377
388
  * - mcp_jsons_readonly: MCP JSON tools (read only)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pulsemcp-cms-admin-mcp-server",
3
- "version": "0.9.13",
3
+ "version": "0.9.15",
4
4
  "description": "Local implementation of PulseMCP CMS Admin MCP server",
5
5
  "mcpName": "com.pulsemcp.servers/pulsemcp-cms-admin",
6
6
  "main": "build/index.js",
@@ -30,7 +30,7 @@
30
30
  "stage-publish": "npm version"
31
31
  },
32
32
  "dependencies": {
33
- "@modelcontextprotocol/sdk": "^1.19.1",
33
+ "@modelcontextprotocol/sdk": "^1.29.0",
34
34
  "zod": "^3.24.1"
35
35
  },
36
36
  "devDependencies": {
@@ -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,6 @@
1
+ export interface AdminFetchDeps {
2
+ fetchFn?: typeof fetch;
3
+ getInternalSecret?: () => string | undefined;
4
+ }
5
+ export declare function adminFetch(input: string | URL, init?: RequestInit, deps?: AdminFetchDeps): Promise<Response>;
6
+ //# sourceMappingURL=admin-fetch.d.ts.map
@@ -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,3 @@
1
+ import type { ApiKey, CreateApiKeyParams } from '../../types.js';
2
+ export declare function createApiKey(apiKey: string, baseUrl: string, params: CreateApiKeyParams): Promise<ApiKey>;
3
+ //# sourceMappingURL=create-api-key.d.ts.map
@@ -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,3 @@
1
+ import type { Tenant, CreateTenantParams } from '../../types.js';
2
+ export declare function createTenant(apiKey: string, baseUrl: string, params: CreateTenantParams): Promise<Tenant>;
3
+ //# sourceMappingURL=create-tenant.d.ts.map
@@ -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,