pulsemcp-cms-admin-mcp-server 0.10.3 → 0.10.6

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 (31) hide show
  1. package/README.md +83 -78
  2. package/build/shared/src/pulsemcp-admin-client/lib/create-secret.js +41 -0
  3. package/build/shared/src/pulsemcp-admin-client/lib/get-secret.js +28 -0
  4. package/build/shared/src/pulsemcp-admin-client/lib/link-secret-to-server.js +50 -0
  5. package/build/shared/src/pulsemcp-admin-client/lib/set-github-repository-classification.js +38 -0
  6. package/build/shared/src/pulsemcp-admin-client/pulsemcp-admin-client.integration-mock.js +59 -0
  7. package/build/shared/src/server.js +17 -0
  8. package/build/shared/src/tools/link-secret-to-mcp-server.js +140 -0
  9. package/build/shared/src/tools/set-github-repository-classification.js +73 -0
  10. package/build/shared/src/tools.js +17 -0
  11. package/node_modules/@pulsemcp/mcp-elicitation/package.json +1 -1
  12. package/package.json +1 -1
  13. package/shared/pulsemcp-admin-client/lib/create-secret.d.ts +7 -0
  14. package/shared/pulsemcp-admin-client/lib/create-secret.js +41 -0
  15. package/shared/pulsemcp-admin-client/lib/get-secret.d.ts +7 -0
  16. package/shared/pulsemcp-admin-client/lib/get-secret.js +28 -0
  17. package/shared/pulsemcp-admin-client/lib/link-secret-to-server.d.ts +12 -0
  18. package/shared/pulsemcp-admin-client/lib/link-secret-to-server.js +50 -0
  19. package/shared/pulsemcp-admin-client/lib/set-github-repository-classification.d.ts +3 -0
  20. package/shared/pulsemcp-admin-client/lib/set-github-repository-classification.js +38 -0
  21. package/shared/pulsemcp-admin-client/pulsemcp-admin-client.integration-mock.d.ts +7 -1
  22. package/shared/pulsemcp-admin-client/pulsemcp-admin-client.integration-mock.js +59 -0
  23. package/shared/server.d.ts +9 -1
  24. package/shared/server.js +17 -0
  25. package/shared/tools/link-secret-to-mcp-server.d.ts +50 -0
  26. package/shared/tools/link-secret-to-mcp-server.js +140 -0
  27. package/shared/tools/set-github-repository-classification.d.ts +41 -0
  28. package/shared/tools/set-github-repository-classification.js +73 -0
  29. package/shared/tools.d.ts +2 -1
  30. package/shared/tools.js +17 -0
  31. package/shared/types.d.ts +36 -0
package/README.md CHANGED
@@ -40,75 +40,77 @@ This is an MCP ([Model Context Protocol](https://modelcontextprotocol.io/)) Serv
40
40
 
41
41
  This server is built and tested on macOS with Claude Desktop. It should work with other MCP clients as well.
42
42
 
43
- | Tool Name | Tool Group | Read/Write | Description |
44
- | -------------------------------------- | ------------------- | ---------- | ---------------------------------------------------------------------------------------------------------- |
45
- | `get_newsletter_posts` | newsletter | read | List newsletter posts with search, sorting, and pagination options. |
46
- | `get_newsletter_post` | newsletter | read | Retrieve a specific newsletter post by its unique slug. |
47
- | `draft_newsletter_post` | newsletter | write | Create a new draft newsletter post with title, body, and metadata. |
48
- | `update_newsletter_post` | newsletter | write | Update an existing newsletter post's content and metadata (except status). |
49
- | `upload_image` | newsletter | write | Upload an image and attach it to a specific newsletter post. |
50
- | `get_authors` | newsletter | read | Get a list of authors with optional search and pagination. |
51
- | `search_mcp_implementations` | server_directory | read | Search for MCP servers and clients in the PulseMCP registry. |
52
- | `get_draft_mcp_implementations` | server_directory | read | Retrieve paginated list of draft MCP implementations needing review. |
53
- | `find_providers` | server_directory | read | Search for providers by ID, name, URL, or slug. |
54
- | `save_mcp_implementation` | server_directory | write | Update an MCP implementation (replicates Admin panel "Save Changes" button). |
55
- | `send_impl_posted_notif` | server_directory | write | Send email notification when MCP implementation goes live. |
56
- | `get_official_mirror_queue_items` | official_queue | read | List and filter official mirror queue entries with pagination and search. |
57
- | `get_official_mirror_queue_item` | official_queue | read | Get detailed information about a single official mirror queue entry. |
58
- | `approve_official_mirror_queue_item` | official_queue | write | Approve a queue entry and link it to an existing MCP server (async). |
59
- | `approve_mirror_no_modify` | official_queue | write | Approve without updating the linked server. |
60
- | `reject_official_mirror_queue_item` | official_queue | write | Reject a queue entry (async operation). |
61
- | `add_official_mirror_to_regular_queue` | official_queue | write | Convert a queue entry to a draft MCP implementation (async). |
62
- | `unlink_official_mirror_queue_item` | official_queue | write | Unlink a queue entry from its linked MCP server. |
63
- | `get_unofficial_mirrors` | unofficial_mirrors | read | List unofficial mirrors with search, pagination, and MCP server filtering. |
64
- | `get_unofficial_mirror` | unofficial_mirrors | read | Get detailed unofficial mirror info by ID or name. |
65
- | `create_unofficial_mirror` | unofficial_mirrors | write | Create a new unofficial mirror entry with JSON data. |
66
- | `update_unofficial_mirror` | unofficial_mirrors | write | Update an existing unofficial mirror by ID. |
67
- | `delete_unofficial_mirror` | unofficial_mirrors | write | Delete an unofficial mirror by ID (irreversible). |
68
- | `get_official_mirrors` | official_mirrors | read | List official mirrors with search, status, and processing filters. |
69
- | `get_official_mirror` | official_mirrors | read | Get detailed official mirror info by ID or name. |
70
- | `get_tenants` | tenants | read | List tenants with search and admin status filtering. |
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). |
74
- | `revoke_api_key` | tenants | write | Revoke an API key by ID, immediately invalidating it. Idempotent. Requires elicitation approval. |
75
- | `delete_tenant` | tenants_destructive | write | Permanently delete a tenant. Requires elicitation approval. With `force: true`, cascades to dependents. |
76
- | `delete_api_key` | tenants_destructive | write | Permanently revoke (delete) an API key. Idempotent. Requires elicitation approval. |
77
- | `get_mcp_jsons` | mcp_jsons | read | List MCP JSON configs with mirror and server filtering. |
78
- | `get_mcp_json` | mcp_jsons | read | Get a single MCP JSON configuration by ID. |
79
- | `create_mcp_json` | mcp_jsons | write | Create a new MCP JSON configuration for an unofficial mirror. |
80
- | `update_mcp_json` | mcp_jsons | write | Update an existing MCP JSON configuration by ID. |
81
- | `delete_mcp_json` | mcp_jsons | write | Delete an MCP JSON configuration by ID (irreversible). |
82
- | `list_mcp_servers` | mcp_servers | read | List/search MCP servers with filtering by status, classification, pagination. |
83
- | `get_mcp_server` | mcp_servers | read | Get detailed MCP server info by slug (unified view of all admin UI fields). |
84
- | `update_mcp_server` | mcp_servers | write | Update an MCP server's fields (all admin UI fields supported). |
85
- | `recache_mcp_server` | mcp_servers | write | Refresh the cache for a specific MCP server (show page, cards, canonicals, parent pages). |
86
- | `get_redirects` | redirects | read | List URL redirects with search, status filtering, and pagination. |
87
- | `get_redirect` | redirects | read | Get detailed redirect info by ID. |
88
- | `create_redirect` | redirects | write | Create a new URL redirect entry. |
89
- | `update_redirect` | redirects | write | Update an existing URL redirect by ID. |
90
- | `delete_redirect` | redirects | write | Delete a URL redirect by ID (irreversible). |
91
- | `list_good_jobs` | good_jobs | read | List and filter background jobs by queue, status, job class, and date range. |
92
- | `get_good_job` | good_jobs | read | Get detailed information about a specific background job. |
93
- | `list_good_job_cron_schedules` | good_jobs | read | List all configured cron schedules. |
94
- | `list_good_job_processes` | good_jobs | read | List active worker processes. |
95
- | `get_good_job_queue_statistics` | good_jobs | read | Get aggregate job statistics by status. |
96
- | `retry_good_job` | good_jobs | write | Retry a failed or discarded background job. |
97
- | `discard_good_job` | good_jobs | write | Discard a background job to prevent retries. |
98
- | `reschedule_good_job` | good_jobs | write | Reschedule a background job to a new time. |
99
- | `force_trigger_good_job_cron` | good_jobs | write | Force trigger a cron schedule immediately. |
100
- | `cleanup_good_jobs` | good_jobs | write | Clean up old background jobs by status and age. |
101
- | `run_exam_for_mirror` | proctor | write | Run proctor exams against unofficial mirrors via Fly Machines. Returns truncated summary with `result_id`. |
102
- | `get_exam_result` | proctor | read | Retrieve full untruncated exam results by `result_id`, with optional section/mirror filtering. |
103
- | `save_results_for_mirror` | proctor | write | Save proctor exam results via `result_id` from `run_exam_for_mirror`. |
104
- | `list_proctor_runs` | proctor | read | List proctor runs with filtering by name, recommended status, and tenant IDs. |
105
- | `get_proctor_metadata` | proctor | read | Get available proctor runtimes and exam types. |
106
- | `list_discovered_urls` | discovered_urls | read | List discovered URLs with status filtering and pagination. |
107
- | `mark_discovered_url_processed` | discovered_urls | write | Mark a discovered URL as processed with a result status. |
108
- | `get_discovered_url_stats` | discovered_urls | read | Get summary statistics for discovered URLs pipeline. |
109
- | `get_moz_metrics` | moz | read | Fetch live URL metrics from the MOZ API (page authority, domain authority, spam score, link counts). |
110
- | `get_moz_backlinks` | moz | read | Fetch live backlink data from the MOZ API (source pages, anchor text, domain authority). |
111
- | `get_moz_stored_metrics` | moz | read | List stored/historical MOZ data for a server's canonicals with pagination. |
43
+ | Tool Name | Tool Group | Read/Write | Description |
44
+ | -------------------------------------- | ------------------- | ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
45
+ | `get_newsletter_posts` | newsletter | read | List newsletter posts with search, sorting, and pagination options. |
46
+ | `get_newsletter_post` | newsletter | read | Retrieve a specific newsletter post by its unique slug. |
47
+ | `draft_newsletter_post` | newsletter | write | Create a new draft newsletter post with title, body, and metadata. |
48
+ | `update_newsletter_post` | newsletter | write | Update an existing newsletter post's content and metadata (except status). |
49
+ | `upload_image` | newsletter | write | Upload an image and attach it to a specific newsletter post. |
50
+ | `get_authors` | newsletter | read | Get a list of authors with optional search and pagination. |
51
+ | `search_mcp_implementations` | server_directory | read | Search for MCP servers and clients in the PulseMCP registry. |
52
+ | `get_draft_mcp_implementations` | server_directory | read | Retrieve paginated list of draft MCP implementations needing review. |
53
+ | `find_providers` | server_directory | read | Search for providers by ID, name, URL, or slug. |
54
+ | `save_mcp_implementation` | server_directory | write | Update an MCP implementation (replicates Admin panel "Save Changes" button). |
55
+ | `send_impl_posted_notif` | server_directory | write | Send email notification when MCP implementation goes live. |
56
+ | `get_official_mirror_queue_items` | official_queue | read | List and filter official mirror queue entries with pagination and search. |
57
+ | `get_official_mirror_queue_item` | official_queue | read | Get detailed information about a single official mirror queue entry. |
58
+ | `approve_official_mirror_queue_item` | official_queue | write | Approve a queue entry and link it to an existing MCP server (async). |
59
+ | `approve_mirror_no_modify` | official_queue | write | Approve without updating the linked server. |
60
+ | `reject_official_mirror_queue_item` | official_queue | write | Reject a queue entry (async operation). |
61
+ | `add_official_mirror_to_regular_queue` | official_queue | write | Convert a queue entry to a draft MCP implementation (async). |
62
+ | `unlink_official_mirror_queue_item` | official_queue | write | Unlink a queue entry from its linked MCP server. |
63
+ | `get_unofficial_mirrors` | unofficial_mirrors | read | List unofficial mirrors with search, pagination, and MCP server filtering. |
64
+ | `get_unofficial_mirror` | unofficial_mirrors | read | Get detailed unofficial mirror info by ID or name. |
65
+ | `create_unofficial_mirror` | unofficial_mirrors | write | Create a new unofficial mirror entry with JSON data. |
66
+ | `update_unofficial_mirror` | unofficial_mirrors | write | Update an existing unofficial mirror by ID. |
67
+ | `delete_unofficial_mirror` | unofficial_mirrors | write | Delete an unofficial mirror by ID (irreversible). |
68
+ | `get_official_mirrors` | official_mirrors | read | List official mirrors with search, status, and processing filters. |
69
+ | `get_official_mirror` | official_mirrors | read | Get detailed official mirror info by ID or name. |
70
+ | `get_tenants` | tenants | read | List tenants with search and admin status filtering. |
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). |
74
+ | `revoke_api_key` | tenants | write | Revoke an API key by ID, immediately invalidating it. Idempotent. Requires elicitation approval. |
75
+ | `delete_tenant` | tenants_destructive | write | Permanently delete a tenant. Requires elicitation approval. With `force: true`, cascades to dependents. |
76
+ | `delete_api_key` | tenants_destructive | write | Permanently revoke (delete) an API key. Idempotent. Requires elicitation approval. |
77
+ | `get_mcp_jsons` | mcp_jsons | read | List MCP JSON configs with mirror and server filtering. |
78
+ | `get_mcp_json` | mcp_jsons | read | Get a single MCP JSON configuration by ID. |
79
+ | `create_mcp_json` | mcp_jsons | write | Create a new MCP JSON configuration for an unofficial mirror. |
80
+ | `update_mcp_json` | mcp_jsons | write | Update an existing MCP JSON configuration by ID. |
81
+ | `delete_mcp_json` | mcp_jsons | write | Delete an MCP JSON configuration by ID (irreversible). |
82
+ | `list_mcp_servers` | mcp_servers | read | List/search MCP servers with filtering by status, classification, pagination. |
83
+ | `get_mcp_server` | mcp_servers | read | Get detailed MCP server info by slug (unified view of all admin UI fields). |
84
+ | `update_mcp_server` | mcp_servers | write | Update an MCP server's fields (all admin UI fields supported). |
85
+ | `recache_mcp_server` | mcp_servers | write | Refresh the cache for a specific MCP server (show page, cards, canonicals, parent pages). |
86
+ | `set_github_repository_classification` | mcp_servers | write | Set a GitHub repository's `classification` (e.g. `other` to drop a non-MCP-driven platform repo from the gh_stars popularity path). |
87
+ | `get_redirects` | redirects | read | List URL redirects with search, status filtering, and pagination. |
88
+ | `get_redirect` | redirects | read | Get detailed redirect info by ID. |
89
+ | `create_redirect` | redirects | write | Create a new URL redirect entry. |
90
+ | `update_redirect` | redirects | write | Update an existing URL redirect by ID. |
91
+ | `delete_redirect` | redirects | write | Delete a URL redirect by ID (irreversible). |
92
+ | `list_good_jobs` | good_jobs | read | List and filter background jobs by queue, status, job class, and date range. |
93
+ | `get_good_job` | good_jobs | read | Get detailed information about a specific background job. |
94
+ | `list_good_job_cron_schedules` | good_jobs | read | List all configured cron schedules. |
95
+ | `list_good_job_processes` | good_jobs | read | List active worker processes. |
96
+ | `get_good_job_queue_statistics` | good_jobs | read | Get aggregate job statistics by status. |
97
+ | `retry_good_job` | good_jobs | write | Retry a failed or discarded background job. |
98
+ | `discard_good_job` | good_jobs | write | Discard a background job to prevent retries. |
99
+ | `reschedule_good_job` | good_jobs | write | Reschedule a background job to a new time. |
100
+ | `force_trigger_good_job_cron` | good_jobs | write | Force trigger a cron schedule immediately. |
101
+ | `cleanup_good_jobs` | good_jobs | write | Clean up old background jobs by status and age. |
102
+ | `run_exam_for_mirror` | proctor | write | Run proctor exams against unofficial mirrors via Fly Machines. Returns truncated summary with `result_id`. |
103
+ | `get_exam_result` | proctor | read | Retrieve full untruncated exam results by `result_id`, with optional section/mirror filtering. |
104
+ | `save_results_for_mirror` | proctor | write | Save proctor exam results via `result_id` from `run_exam_for_mirror`. |
105
+ | `list_proctor_runs` | proctor | read | List proctor runs with filtering by name, recommended status, and tenant IDs. |
106
+ | `get_proctor_metadata` | proctor | read | Get available proctor runtimes and exam types. |
107
+ | `list_discovered_urls` | discovered_urls | read | List discovered URLs with status filtering and pagination. |
108
+ | `mark_discovered_url_processed` | discovered_urls | write | Mark a discovered URL as processed with a result status. |
109
+ | `get_discovered_url_stats` | discovered_urls | read | Get summary statistics for discovered URLs pipeline. |
110
+ | `get_moz_metrics` | moz | read | Fetch live URL metrics from the MOZ API (page authority, domain authority, spam score, link counts). |
111
+ | `get_moz_backlinks` | moz | read | Fetch live backlink data from the MOZ API (source pages, anchor text, domain authority). |
112
+ | `get_moz_stored_metrics` | moz | read | List stored/historical MOZ data for a server's canonicals with pagination. |
113
+ | `link_secret_to_mcp_server` | secrets | write | Make an auth secret available to an MCP server: upsert a Secret (referencing a 1Password item) and write the server↔secret link Proctor reads. The raw secret value never passes through. |
112
114
 
113
115
  # Tool Groups
114
116
 
@@ -148,6 +150,7 @@ This server organizes tools into groups that can be selectively enabled or disab
148
150
  | `discovered_urls_readonly` | 2 | Discovered URL tools read-only (list, stats) |
149
151
  | `moz` | 3 | MOZ SEO metrics — live URL metrics, backlinks, and stored historical data (all read-only) |
150
152
  | `moz_readonly` | 3 | MOZ tools read-only (alias — all MOZ tools are read-only) |
153
+ | `secrets` | 1 | Auth-secret ↔ MCP server linking. Write-only (no `_readonly` variant): upserts a 1Password-referenced Secret and links it to a server so Proctor can inject it at runtime. |
151
154
 
152
155
  ### Tools by Group
153
156
 
@@ -190,18 +193,20 @@ This server organizes tools into groups that can be selectively enabled or disab
190
193
  - Write: `mark_discovered_url_processed`
191
194
  - **moz** / **moz_readonly**:
192
195
  - Read-only: `get_moz_metrics`, `get_moz_backlinks`, `get_moz_stored_metrics`
196
+ - **secrets** (write-only — no `_readonly` variant):
197
+ - Write: `link_secret_to_mcp_server`
193
198
 
194
199
  ## Environment Variables
195
200
 
196
- | Variable | Description | Default |
197
- | -------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
198
- | `PULSEMCP_ADMIN_API_KEY` | API key sent as `X-API-Key` for admin-API authentication. Required. | — |
199
- | `PULSEMCP_ADMIN_API_URL` | Base URL for the admin API. | `https://admin.pulsemcp.com` |
200
- | `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 |
201
- | `TOOL_GROUPS` | Comma-separated list of enabled tool groups. `tenants_destructive` is **NOT** included in the default; opt in explicitly. | `newsletter,server_directory,official_queue,unofficial_mirrors,official_mirrors,tenants,mcp_jsons,mcp_servers,redirects,good_jobs,proctor,discovered_urls,moz` (all base groups) |
202
- | `DANGEROUSLY_SKIP_ELICITATIONS` | When set to `true`, skips elicitation prompts for destructive tools (`delete_tenant`, `delete_api_key`, `revoke_api_key`). Use only when running in trusted automation that already has its own approval gate. | unset (false) |
203
- | `PULSEMCP_CMS_ADMIN_ELICITATION_DESTRUCTIVE` | Per-action override. Set to `false` to skip elicitation for destructive tools while leaving other elicitation behavior unchanged. Implied false when `DANGEROUSLY_SKIP_ELICITATIONS=true`. | `true` |
204
- | `ELICITATION_REQUEST_URL` / `ELICITATION_POLL_URL` | HTTP fallback elicitation endpoints (used when the MCP client doesn't support native elicitation). Both must be set together. Required at startup if `tenants_destructive` is enabled and `DANGEROUSLY_SKIP_ELICITATIONS` is not set. | unset |
201
+ | Variable | Description | Default |
202
+ | -------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
203
+ | `PULSEMCP_ADMIN_API_KEY` | API key sent as `X-API-Key` for admin-API authentication. Required. | — |
204
+ | `PULSEMCP_ADMIN_API_URL` | Base URL for the admin API. | `https://admin.pulsemcp.com` |
205
+ | `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 |
206
+ | `TOOL_GROUPS` | Comma-separated list of enabled tool groups. `tenants_destructive` is **NOT** included in the default; opt in explicitly. | `newsletter,server_directory,official_queue,unofficial_mirrors,official_mirrors,tenants,mcp_jsons,mcp_servers,redirects,good_jobs,proctor,discovered_urls,moz,secrets` (all base groups) |
207
+ | `DANGEROUSLY_SKIP_ELICITATIONS` | When set to `true`, skips elicitation prompts for destructive tools (`delete_tenant`, `delete_api_key`, `revoke_api_key`). Use only when running in trusted automation that already has its own approval gate. | unset (false) |
208
+ | `PULSEMCP_CMS_ADMIN_ELICITATION_DESTRUCTIVE` | Per-action override. Set to `false` to skip elicitation for destructive tools while leaving other elicitation behavior unchanged. Implied false when `DANGEROUSLY_SKIP_ELICITATIONS=true`. | `true` |
209
+ | `ELICITATION_REQUEST_URL` / `ELICITATION_POLL_URL` | HTTP fallback elicitation endpoints (used when the MCP client doesn't support native elicitation). Both must be set together. Required at startup if `tenants_destructive` is enabled and `DANGEROUSLY_SKIP_ELICITATIONS` is not set. | unset |
205
210
 
206
211
  ### Destructive tools and elicitation
207
212
 
@@ -0,0 +1,41 @@
1
+ import { adminFetch } from './admin-fetch.js';
2
+ /**
3
+ * Create a secret. The secret's value stays in 1Password — this only records
4
+ * the `onepassword_item_id` reference plus metadata.
5
+ */
6
+ export async function createSecret(apiKey, baseUrl, params) {
7
+ const url = new URL('/api/secrets', baseUrl);
8
+ const body = {
9
+ slug: params.slug,
10
+ onepassword_item_id: params.onepassword_item_id,
11
+ };
12
+ if (params.title !== undefined) {
13
+ body.title = params.title;
14
+ }
15
+ if (params.description !== undefined) {
16
+ body.description = params.description;
17
+ }
18
+ const response = await adminFetch(url.toString(), {
19
+ method: 'POST',
20
+ headers: {
21
+ 'X-API-Key': apiKey,
22
+ 'Content-Type': 'application/json',
23
+ Accept: 'application/json',
24
+ },
25
+ body: JSON.stringify(body),
26
+ });
27
+ if (!response.ok) {
28
+ if (response.status === 401) {
29
+ throw new Error('Invalid API key');
30
+ }
31
+ if (response.status === 403) {
32
+ throw new Error('User lacks write privileges');
33
+ }
34
+ if (response.status === 422) {
35
+ const errorData = (await response.json());
36
+ throw new Error(`Validation failed: ${errorData.errors?.join(', ') || 'Unknown error'}`);
37
+ }
38
+ throw new Error(`Failed to create secret: ${response.status} ${response.statusText}`);
39
+ }
40
+ return (await response.json());
41
+ }
@@ -0,0 +1,28 @@
1
+ import { adminFetch } from './admin-fetch.js';
2
+ /**
3
+ * Fetch a secret by id or slug. Returns null when the secret does not exist
4
+ * (HTTP 404) so callers can branch on create-vs-reuse without catching errors.
5
+ */
6
+ export async function getSecret(apiKey, baseUrl, idOrSlug) {
7
+ const url = new URL(`/api/secrets/${encodeURIComponent(String(idOrSlug))}`, baseUrl);
8
+ const response = await adminFetch(url.toString(), {
9
+ method: 'GET',
10
+ headers: {
11
+ 'X-API-Key': apiKey,
12
+ Accept: 'application/json',
13
+ },
14
+ });
15
+ if (!response.ok) {
16
+ if (response.status === 404) {
17
+ return null;
18
+ }
19
+ if (response.status === 401) {
20
+ throw new Error('Invalid API key');
21
+ }
22
+ if (response.status === 403) {
23
+ throw new Error('User lacks admin privileges');
24
+ }
25
+ throw new Error(`Failed to fetch secret: ${response.status} ${response.statusText}`);
26
+ }
27
+ return (await response.json());
28
+ }
@@ -0,0 +1,50 @@
1
+ import { adminFetch } from './admin-fetch.js';
2
+ /**
3
+ * Link an MCP server to a secret by writing the mcp_servers_secrets join row.
4
+ * This join is what Proctor reads to inject the secret value at runtime, so it
5
+ * is the operation that actually scopes a stored secret to a server.
6
+ *
7
+ * Idempotent on the backend: relinking an already-linked server returns the
8
+ * existing join (updating onepassword_tag when a new value is supplied) instead
9
+ * of erroring or duplicating.
10
+ */
11
+ export async function linkSecretToServer(apiKey, baseUrl, params) {
12
+ const url = new URL(`/api/secrets/${encodeURIComponent(String(params.secret))}/servers`, baseUrl);
13
+ const body = {};
14
+ if (params.mcp_server_id !== undefined) {
15
+ body.mcp_server_id = params.mcp_server_id;
16
+ }
17
+ if (params.mcp_server_slug !== undefined) {
18
+ body.mcp_server_slug = params.mcp_server_slug;
19
+ }
20
+ if (params.onepassword_tag !== undefined) {
21
+ body.onepassword_tag = params.onepassword_tag;
22
+ }
23
+ const response = await adminFetch(url.toString(), {
24
+ method: 'POST',
25
+ headers: {
26
+ 'X-API-Key': apiKey,
27
+ 'Content-Type': 'application/json',
28
+ Accept: 'application/json',
29
+ },
30
+ body: JSON.stringify(body),
31
+ });
32
+ if (!response.ok) {
33
+ if (response.status === 401) {
34
+ throw new Error('Invalid API key');
35
+ }
36
+ if (response.status === 403) {
37
+ throw new Error('User lacks write privileges');
38
+ }
39
+ if (response.status === 404) {
40
+ const errorData = (await response.json().catch(() => ({})));
41
+ throw new Error(errorData.error || 'Secret or MCP server not found');
42
+ }
43
+ if (response.status === 422) {
44
+ const errorData = (await response.json());
45
+ throw new Error(`Validation failed: ${errorData.errors?.join(', ') || 'Unknown error'}`);
46
+ }
47
+ throw new Error(`Failed to link secret to server: ${response.status} ${response.statusText}`);
48
+ }
49
+ return (await response.json());
50
+ }
@@ -0,0 +1,38 @@
1
+ import { adminFetch } from './admin-fetch.js';
2
+ export async function setGithubRepositoryClassification(apiKey, baseUrl, id, classification) {
3
+ const url = new URL(`/api/github_repositories/${id}/classification`, baseUrl);
4
+ const body = {
5
+ classification,
6
+ };
7
+ const response = await adminFetch(url.toString(), {
8
+ method: 'POST',
9
+ headers: {
10
+ 'X-API-Key': apiKey,
11
+ Accept: 'application/json',
12
+ 'Content-Type': 'application/json',
13
+ },
14
+ body: JSON.stringify(body),
15
+ });
16
+ if (!response.ok) {
17
+ if (response.status === 401) {
18
+ throw new Error('Invalid API key');
19
+ }
20
+ if (response.status === 403) {
21
+ throw new Error('User lacks write privileges');
22
+ }
23
+ if (response.status === 404) {
24
+ throw new Error(`GitHub repository not found: ${id}`);
25
+ }
26
+ if (response.status === 400) {
27
+ const errBody = (await response.json().catch(() => ({})));
28
+ throw new Error(errBody.error ?? 'Bad request');
29
+ }
30
+ if (response.status === 422) {
31
+ const errBody = (await response.json().catch(() => ({})));
32
+ const detailStr = errBody.details?.length ? ` (${errBody.details.join(', ')})` : '';
33
+ throw new Error(`${errBody.error ?? 'Validation failed'}${detailStr}`);
34
+ }
35
+ throw new Error(`Failed to set github_repository classification: ${response.status} ${response.statusText}`);
36
+ }
37
+ return (await response.json());
38
+ }
@@ -1099,5 +1099,64 @@ export function createMockPulseMCPAdminClient(mockData) {
1099
1099
  : (knownMissingInitToolsListFilterTo ?? null),
1100
1100
  };
1101
1101
  },
1102
+ async setGithubRepositoryClassification(id, classification) {
1103
+ return {
1104
+ id,
1105
+ classification,
1106
+ };
1107
+ },
1108
+ // Secret REST API methods
1109
+ async getSecret(idOrSlug) {
1110
+ if (mockData.errors?.getSecret) {
1111
+ throw mockData.errors.getSecret;
1112
+ }
1113
+ const key = String(idOrSlug);
1114
+ return mockData.secretsBySlug?.[key] ?? null;
1115
+ },
1116
+ async createSecret(params) {
1117
+ if (mockData.errors?.createSecret) {
1118
+ throw mockData.errors.createSecret;
1119
+ }
1120
+ if (mockData.createSecretResponse) {
1121
+ return mockData.createSecretResponse;
1122
+ }
1123
+ return {
1124
+ id: 1,
1125
+ slug: params.slug,
1126
+ onepassword_item_id: params.onepassword_item_id,
1127
+ title: params.title ?? null,
1128
+ description: params.description ?? null,
1129
+ mcp_servers_count: 0,
1130
+ mcp_server_slugs: [],
1131
+ created_at: new Date().toISOString(),
1132
+ updated_at: new Date().toISOString(),
1133
+ };
1134
+ },
1135
+ async linkSecretToServer(params) {
1136
+ if (mockData.errors?.linkSecretToServer) {
1137
+ throw mockData.errors.linkSecretToServer;
1138
+ }
1139
+ if (mockData.linkSecretToServerResponse) {
1140
+ return mockData.linkSecretToServerResponse;
1141
+ }
1142
+ const slug = typeof params.secret === 'string' ? params.secret : `secret-${params.secret}`;
1143
+ const serverSlug = params.mcp_server_slug ?? 'mock-server';
1144
+ return {
1145
+ id: 1,
1146
+ slug,
1147
+ onepassword_item_id: 'op://Vault/Item/credential',
1148
+ title: null,
1149
+ description: null,
1150
+ mcp_servers_count: 1,
1151
+ mcp_server_slugs: [serverSlug],
1152
+ created_at: new Date().toISOString(),
1153
+ updated_at: new Date().toISOString(),
1154
+ link: {
1155
+ mcp_server_id: params.mcp_server_id ?? 1,
1156
+ mcp_server_slug: serverSlug,
1157
+ onepassword_tag: params.onepassword_tag ?? null,
1158
+ },
1159
+ };
1160
+ },
1102
1161
  };
1103
1162
  }
@@ -51,6 +51,9 @@ import { getRedirect } from './pulsemcp-admin-client/lib/get-redirect.js';
51
51
  import { createRedirect } from './pulsemcp-admin-client/lib/create-redirect.js';
52
52
  import { updateRedirect } from './pulsemcp-admin-client/lib/update-redirect.js';
53
53
  import { deleteRedirect } from './pulsemcp-admin-client/lib/delete-redirect.js';
54
+ import { getSecret } from './pulsemcp-admin-client/lib/get-secret.js';
55
+ import { createSecret } from './pulsemcp-admin-client/lib/create-secret.js';
56
+ import { linkSecretToServer } from './pulsemcp-admin-client/lib/link-secret-to-server.js';
54
57
  import { getGoodJobs } from './pulsemcp-admin-client/lib/get-good-jobs.js';
55
58
  import { getGoodJob } from './pulsemcp-admin-client/lib/get-good-job.js';
56
59
  import { getGoodJobCronSchedules } from './pulsemcp-admin-client/lib/get-good-job-cron-schedules.js';
@@ -74,6 +77,7 @@ import { getMozBacklinks } from './pulsemcp-admin-client/lib/get-moz-backlinks.j
74
77
  import { getMozStoredMetrics } from './pulsemcp-admin-client/lib/get-moz-stored-metrics.js';
75
78
  import { recacheMCPServer } from './pulsemcp-admin-client/lib/recache-mcp-server.js';
76
79
  import { setKnownMissingInitToolsList } from './pulsemcp-admin-client/lib/set-known-missing-init-tools-list.js';
80
+ import { setGithubRepositoryClassification } from './pulsemcp-admin-client/lib/set-github-repository-classification.js';
77
81
  import { createTenant } from './pulsemcp-admin-client/lib/create-tenant.js';
78
82
  import { createApiKey } from './pulsemcp-admin-client/lib/create-api-key.js';
79
83
  import { deleteTenant } from './pulsemcp-admin-client/lib/delete-tenant.js';
@@ -250,6 +254,9 @@ export class PulseMCPAdminClient {
250
254
  async setKnownMissingInitToolsList(id, knownMissingInitToolsList, knownMissingInitToolsListFilterTo) {
251
255
  return setKnownMissingInitToolsList(this.apiKey, this.baseUrl, id, knownMissingInitToolsList, knownMissingInitToolsListFilterTo);
252
256
  }
257
+ async setGithubRepositoryClassification(id, classification) {
258
+ return setGithubRepositoryClassification(this.apiKey, this.baseUrl, id, classification);
259
+ }
253
260
  // Redirect REST API methods
254
261
  async getRedirects(params) {
255
262
  return getRedirects(this.apiKey, this.baseUrl, params);
@@ -266,6 +273,16 @@ export class PulseMCPAdminClient {
266
273
  async deleteRedirect(id) {
267
274
  return deleteRedirect(this.apiKey, this.baseUrl, id);
268
275
  }
276
+ // Secret REST API methods
277
+ async getSecret(idOrSlug) {
278
+ return getSecret(this.apiKey, this.baseUrl, idOrSlug);
279
+ }
280
+ async createSecret(params) {
281
+ return createSecret(this.apiKey, this.baseUrl, params);
282
+ }
283
+ async linkSecretToServer(params) {
284
+ return linkSecretToServer(this.apiKey, this.baseUrl, params);
285
+ }
269
286
  // GoodJob REST API methods
270
287
  async getGoodJobs(params) {
271
288
  return getGoodJobs(this.apiKey, this.baseUrl, params);