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
@@ -0,0 +1,140 @@
1
+ import { z } from 'zod';
2
+ const PARAM_DESCRIPTIONS = {
3
+ slug: 'Unique slug identifier for the secret (lowercase, hyphenated, e.g. "linear-api-key"). Used to look up an existing secret or to create a new one.',
4
+ onepassword_item_id: 'The 1Password item reference the secret value lives in (e.g. "op://Vault/Item/credential"). The raw secret value is NEVER passed through this tool — only this reference. If a secret with this slug already exists, this must match its stored reference.',
5
+ mcp_server_slug: 'Slug of the MCP server to grant the secret to (e.g. "linear"). The link is what Proctor reads to inject the secret value when running that server.',
6
+ title: 'Optional human-readable title for the secret (only used when creating a new secret).',
7
+ description: 'Optional description of what the secret is for (only used when creating a new secret).',
8
+ onepassword_tag: 'Optional tag scoping which field of the 1Password item to inject for this server (e.g. "production"). Stored on the server↔secret link, so the same secret can inject different fields per server.',
9
+ };
10
+ const LinkSecretToMcpServerSchema = z.object({
11
+ slug: z.string().describe(PARAM_DESCRIPTIONS.slug),
12
+ onepassword_item_id: z.string().describe(PARAM_DESCRIPTIONS.onepassword_item_id),
13
+ mcp_server_slug: z.string().describe(PARAM_DESCRIPTIONS.mcp_server_slug),
14
+ title: z.string().optional().describe(PARAM_DESCRIPTIONS.title),
15
+ description: z.string().optional().describe(PARAM_DESCRIPTIONS.description),
16
+ onepassword_tag: z.string().optional().describe(PARAM_DESCRIPTIONS.onepassword_tag),
17
+ });
18
+ export function linkSecretToMcpServer(_server, clientFactory) {
19
+ return {
20
+ name: 'link_secret_to_mcp_server',
21
+ description: `Make an auth secret available to an MCP server. This upserts a Secret (by slug) that references a 1Password item, then writes the server↔secret link that Proctor reads to inject the secret value at runtime.
22
+
23
+ The raw secret value stays in 1Password — this tool only deals in the 1Password item reference ("onepassword_item_id") and slug, never a credential value.
24
+
25
+ Behavior:
26
+ - If no secret with "slug" exists, it is created from "onepassword_item_id" (plus optional title/description).
27
+ - If a secret with "slug" already exists, it is reused. Its stored "onepassword_item_id" must match the one you pass, otherwise the tool errors instead of silently rebinding a shared secret.
28
+ - The link is idempotent: re-running for an already-linked server updates the "onepassword_tag" (when provided) without creating a duplicate.
29
+
30
+ Example request:
31
+ {
32
+ "slug": "linear-api-key",
33
+ "onepassword_item_id": "op://Shared/Linear API Key/credential",
34
+ "mcp_server_slug": "linear",
35
+ "onepassword_tag": "production"
36
+ }
37
+
38
+ Use cases:
39
+ - Onboarding an auth-gated MCP server: register its credential reference and scope it to the server so Proctor can run it.
40
+ - Granting an already-registered secret to an additional server.`,
41
+ inputSchema: {
42
+ type: 'object',
43
+ properties: {
44
+ slug: { type: 'string', description: PARAM_DESCRIPTIONS.slug },
45
+ onepassword_item_id: {
46
+ type: 'string',
47
+ description: PARAM_DESCRIPTIONS.onepassword_item_id,
48
+ },
49
+ mcp_server_slug: { type: 'string', description: PARAM_DESCRIPTIONS.mcp_server_slug },
50
+ title: { type: 'string', description: PARAM_DESCRIPTIONS.title },
51
+ description: { type: 'string', description: PARAM_DESCRIPTIONS.description },
52
+ onepassword_tag: { type: 'string', description: PARAM_DESCRIPTIONS.onepassword_tag },
53
+ },
54
+ required: ['slug', 'onepassword_item_id', 'mcp_server_slug'],
55
+ },
56
+ handler: async (args) => {
57
+ const validatedArgs = LinkSecretToMcpServerSchema.parse(args);
58
+ const client = clientFactory();
59
+ try {
60
+ const existing = await client.getSecret(validatedArgs.slug);
61
+ if (existing && existing.onepassword_item_id !== validatedArgs.onepassword_item_id) {
62
+ return {
63
+ content: [
64
+ {
65
+ type: 'text',
66
+ text: `Error: a secret with slug "${validatedArgs.slug}" already exists but references a different 1Password item ` +
67
+ `("${existing.onepassword_item_id}", not "${validatedArgs.onepassword_item_id}"). ` +
68
+ `Refusing to rebind it, since other servers may depend on it. ` +
69
+ `Use a different slug, or update the existing secret explicitly if the reference really changed.`,
70
+ },
71
+ ],
72
+ isError: true,
73
+ };
74
+ }
75
+ let created = false;
76
+ if (!existing) {
77
+ try {
78
+ await client.createSecret({
79
+ slug: validatedArgs.slug,
80
+ onepassword_item_id: validatedArgs.onepassword_item_id,
81
+ title: validatedArgs.title,
82
+ description: validatedArgs.description,
83
+ });
84
+ }
85
+ catch (createError) {
86
+ // The backend also enforces uniqueness on onepassword_item_id, so the
87
+ // item may already be registered under a *different* slug than the one
88
+ // requested. getSecret(slug) can't surface that (it looks up by slug),
89
+ // so translate the opaque uniqueness 422 into actionable guidance
90
+ // instead of letting the raw validation message reach the agent.
91
+ const message = createError instanceof Error ? createError.message : String(createError);
92
+ if (/onepassword item/i.test(message) && /taken/i.test(message)) {
93
+ return {
94
+ content: [
95
+ {
96
+ type: 'text',
97
+ text: `Error: the 1Password item "${validatedArgs.onepassword_item_id}" is already registered under a different secret slug, ` +
98
+ `so it cannot be created again as "${validatedArgs.slug}". ` +
99
+ `To link it to "${validatedArgs.mcp_server_slug}", re-run this tool with the existing secret's slug ` +
100
+ `(find it via the secrets list), or register a different 1Password item under "${validatedArgs.slug}".`,
101
+ },
102
+ ],
103
+ isError: true,
104
+ };
105
+ }
106
+ throw createError;
107
+ }
108
+ created = true;
109
+ }
110
+ const result = await client.linkSecretToServer({
111
+ secret: validatedArgs.slug,
112
+ mcp_server_slug: validatedArgs.mcp_server_slug,
113
+ onepassword_tag: validatedArgs.onepassword_tag,
114
+ });
115
+ let content = `Successfully linked secret "${result.slug}" to MCP server "${result.link.mcp_server_slug}".\n\n`;
116
+ content += `**Secret:** ${result.slug} (id ${result.id}) — ${created ? 'created' : 'reused existing'}\n`;
117
+ content += `**1Password item:** ${result.onepassword_item_id}\n`;
118
+ content += `**Linked server:** ${result.link.mcp_server_slug} (id ${result.link.mcp_server_id})\n`;
119
+ if (result.link.onepassword_tag) {
120
+ content += `**1Password tag:** ${result.link.onepassword_tag}\n`;
121
+ }
122
+ if (result.mcp_server_slugs && result.mcp_server_slugs.length > 0) {
123
+ content += `**All servers using this secret:** ${result.mcp_server_slugs.join(', ')}\n`;
124
+ }
125
+ return { content: [{ type: 'text', text: content }] };
126
+ }
127
+ catch (error) {
128
+ return {
129
+ content: [
130
+ {
131
+ type: 'text',
132
+ text: `Error linking secret to MCP server: ${error instanceof Error ? error.message : String(error)}`,
133
+ },
134
+ ],
135
+ isError: true,
136
+ };
137
+ }
138
+ },
139
+ };
140
+ }
@@ -0,0 +1,73 @@
1
+ import { z } from 'zod';
2
+ const CLASSIFICATION_VALUES = [
3
+ 'single_mcp_server',
4
+ 'single_mcp_client',
5
+ 'multiple_mcp_servers',
6
+ 'multiple_mcp_clients',
7
+ 'other',
8
+ ];
9
+ const PARAM_DESCRIPTIONS = {
10
+ id: 'The integer id of the GitHub repository record (github_repositories.id).',
11
+ classification: `The classification to set on the GitHub repository. One of: ${CLASSIFICATION_VALUES.join(', ')}.
12
+
13
+ - single_mcp_server: the repo is a single MCP server (counts toward the gh_stars popularity path).
14
+ - single_mcp_client: the repo is a single MCP client.
15
+ - multiple_mcp_servers: the repo hosts multiple MCP servers.
16
+ - multiple_mcp_clients: the repo hosts multiple MCP clients.
17
+ - other: NOT a single MCP server (e.g. a broader platform where MCP is one incidental feature). Setting "other" excludes the repo from the gh_stars popularity path — int_github_repositories.sql maps it to mcp_server_count = 0, dropping it from the normalized downloads estimate.`,
18
+ };
19
+ const SetGithubRepositoryClassificationSchema = z.object({
20
+ id: z.number().int().describe(PARAM_DESCRIPTIONS.id),
21
+ classification: z.enum(CLASSIFICATION_VALUES).describe(PARAM_DESCRIPTIONS.classification),
22
+ });
23
+ export function setGithubRepositoryClassification(_server, clientFactory) {
24
+ return {
25
+ name: 'set_github_repository_classification',
26
+ description: `Set the \`classification\` field on a GitHub repository record (github_repositories). Identifies the repository by its integer id.
27
+
28
+ The primary use case is tagging a repository as \`other\` ("not a single MCP server") so it stops inflating PulseMCP popularity estimates. When a broad platform repo (e.g. heyputer/puter) carries a large GitHub-star count, the gh_stars path overstates MCP adoption; setting \`classification: "other"\` drops the repo out of that path (int_github_repositories.sql maps "other" to mcp_server_count = 0). The corrected estimate propagates on the next BigQuery warehouse rebuild and the subsequent UpdatePopularityEstimatesFromBigqueryJob run.
29
+
30
+ Example request:
31
+ {
32
+ "id": 12345,
33
+ "classification": "other"
34
+ }
35
+
36
+ Use cases:
37
+ - Tag a non-MCP-driven platform repo as "other" to exclude it from gh_stars-based popularity
38
+ - Correct a misclassified repository (e.g. a client repo tagged as a server)`,
39
+ inputSchema: {
40
+ type: 'object',
41
+ properties: {
42
+ id: { type: 'integer', description: PARAM_DESCRIPTIONS.id },
43
+ classification: {
44
+ type: 'string',
45
+ enum: [...CLASSIFICATION_VALUES],
46
+ description: PARAM_DESCRIPTIONS.classification,
47
+ },
48
+ },
49
+ required: ['id', 'classification'],
50
+ },
51
+ handler: async (args) => {
52
+ const validatedArgs = SetGithubRepositoryClassificationSchema.parse(args);
53
+ const client = clientFactory();
54
+ try {
55
+ const result = await client.setGithubRepositoryClassification(validatedArgs.id, validatedArgs.classification);
56
+ const text = `Updated GitHub repository (id: ${result.id}):
57
+ - classification: ${result.classification}`;
58
+ return { content: [{ type: 'text', text }] };
59
+ }
60
+ catch (error) {
61
+ return {
62
+ content: [
63
+ {
64
+ type: 'text',
65
+ text: `Error setting github_repository classification: ${error instanceof Error ? error.message : String(error)}`,
66
+ },
67
+ ],
68
+ isError: true,
69
+ };
70
+ }
71
+ },
72
+ };
73
+ }
@@ -48,12 +48,15 @@ import { listMCPServers } from './tools/list-mcp-servers.js';
48
48
  import { getMCPServer } from './tools/get-mcp-server.js';
49
49
  import { updateMCPServer } from './tools/update-mcp-server.js';
50
50
  import { recacheMCPServer } from './tools/recache-mcp-server.js';
51
+ import { setGithubRepositoryClassification } from './tools/set-github-repository-classification.js';
51
52
  // Redirect tools
52
53
  import { getRedirects } from './tools/get-redirects.js';
53
54
  import { getRedirect } from './tools/get-redirect.js';
54
55
  import { createRedirect } from './tools/create-redirect.js';
55
56
  import { updateRedirect } from './tools/update-redirect.js';
56
57
  import { deleteRedirect } from './tools/delete-redirect.js';
58
+ // Secret tools
59
+ import { linkSecretToMcpServer } from './tools/link-secret-to-mcp-server.js';
57
60
  // GoodJob tools
58
61
  import { listGoodJobs } from './tools/list-good-jobs.js';
59
62
  import { getGoodJob } from './tools/get-good-job.js';
@@ -253,6 +256,8 @@ const ALL_TOOLS = [
253
256
  { factory: createRedirect, groups: ['redirects'], isWriteOperation: true },
254
257
  { factory: updateRedirect, groups: ['redirects'], isWriteOperation: true },
255
258
  { factory: deleteRedirect, groups: ['redirects'], isWriteOperation: true },
259
+ // Secret tools (write-only: upsert a secret + write the mcp_servers_secrets join Proctor reads)
260
+ { factory: linkSecretToMcpServer, groups: ['secrets'], isWriteOperation: true },
256
261
  // GoodJob tools
257
262
  { factory: listGoodJobs, groups: ['good_jobs'], isWriteOperation: false },
258
263
  { factory: getGoodJob, groups: ['good_jobs'], isWriteOperation: false },
@@ -281,6 +286,16 @@ const ALL_TOOLS = [
281
286
  groups: ['mcp_servers', 'server_directory'],
282
287
  isWriteOperation: true,
283
288
  },
289
+ // setGithubRepositoryClassification writes the `classification` field on a
290
+ // `github_repository` record (e.g. tagging a non-MCP-driven platform repo as
291
+ // `other` so it drops out of the gh_stars popularity path). It lives in the
292
+ // mcp_servers / server_directory groups alongside the other server-directory
293
+ // data-correction tools.
294
+ {
295
+ factory: setGithubRepositoryClassification,
296
+ groups: ['mcp_servers', 'server_directory'],
297
+ isWriteOperation: true,
298
+ },
284
299
  // Discovered URLs tools
285
300
  { factory: listDiscoveredUrls, groups: ['discovered_urls'], isWriteOperation: false },
286
301
  {
@@ -317,6 +332,7 @@ const VALID_TOOL_GROUPS = [
317
332
  'mcp_servers_readonly',
318
333
  'redirects',
319
334
  'redirects_readonly',
335
+ 'secrets',
320
336
  'good_jobs',
321
337
  'good_jobs_readonly',
322
338
  'proctor',
@@ -340,6 +356,7 @@ const BASE_TOOL_GROUPS = [
340
356
  'mcp_jsons',
341
357
  'mcp_servers',
342
358
  'redirects',
359
+ 'secrets',
343
360
  'good_jobs',
344
361
  'proctor',
345
362
  'discovered_urls',
@@ -23,6 +23,6 @@
23
23
  "devDependencies": {
24
24
  "@types/node": "^24.10.12",
25
25
  "typescript": "^5.7.3",
26
- "vitest": "^4.1.8"
26
+ "vitest": "^4.1.9"
27
27
  }
28
28
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pulsemcp-cms-admin-mcp-server",
3
- "version": "0.10.3",
3
+ "version": "0.10.6",
4
4
  "description": "Local implementation of PulseMCP CMS Admin MCP server",
5
5
  "mcpName": "com.pulsemcp/pulsemcp-cms-admin",
6
6
  "main": "build/index.js",
@@ -0,0 +1,7 @@
1
+ import type { Secret, CreateSecretParams } from '../../types.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 declare function createSecret(apiKey: string, baseUrl: string, params: CreateSecretParams): Promise<Secret>;
7
+ //# sourceMappingURL=create-secret.d.ts.map
@@ -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,7 @@
1
+ import type { Secret } from '../../types.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 declare function getSecret(apiKey: string, baseUrl: string, idOrSlug: string | number): Promise<Secret | null>;
7
+ //# sourceMappingURL=get-secret.d.ts.map
@@ -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,12 @@
1
+ import type { LinkSecretToServerParams, SecretWithLink } from '../../types.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 declare function linkSecretToServer(apiKey: string, baseUrl: string, params: LinkSecretToServerParams): Promise<SecretWithLink>;
12
+ //# sourceMappingURL=link-secret-to-server.d.ts.map
@@ -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,3 @@
1
+ import type { GithubRepositoryClassification, SetGithubRepositoryClassificationResponse } from '../../types.js';
2
+ export declare function setGithubRepositoryClassification(apiKey: string, baseUrl: string, id: number, classification: GithubRepositoryClassification): Promise<SetGithubRepositoryClassificationResponse>;
3
+ //# sourceMappingURL=set-github-repository-classification.d.ts.map
@@ -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
+ }
@@ -1,5 +1,5 @@
1
1
  import type { IPulseMCPAdminClient } from '../server.js';
2
- import type { Post, ImageUploadResponse, Author, MCPServer, MCPClient, MCPImplementation, MCPImplementationsResponse, Provider, ProvidersResponse, OfficialMirrorQueueItem, OfficialMirrorQueueItemDetail, OfficialMirrorQueueResponse, OfficialMirrorQueueActionResponse } from '../types.js';
2
+ import type { Post, ImageUploadResponse, Author, MCPServer, MCPClient, MCPImplementation, MCPImplementationsResponse, Provider, ProvidersResponse, OfficialMirrorQueueItem, OfficialMirrorQueueItemDetail, OfficialMirrorQueueResponse, OfficialMirrorQueueActionResponse, Secret, SecretWithLink } from '../types.js';
3
3
  interface MockData {
4
4
  posts?: Post[];
5
5
  postsBySlug?: Record<string, Post>;
@@ -19,6 +19,9 @@ interface MockData {
19
19
  officialMirrorQueueItemsResponse?: OfficialMirrorQueueResponse;
20
20
  officialMirrorQueueItemDetail?: OfficialMirrorQueueItemDetail;
21
21
  officialMirrorQueueActionResponse?: OfficialMirrorQueueActionResponse;
22
+ secretsBySlug?: Record<string, Secret>;
23
+ createSecretResponse?: Secret;
24
+ linkSecretToServerResponse?: SecretWithLink;
22
25
  errors?: {
23
26
  getPosts?: Error;
24
27
  getPost?: Error;
@@ -42,6 +45,9 @@ interface MockData {
42
45
  rejectOfficialMirrorQueueItem?: Error;
43
46
  addOfficialMirrorToRegularQueue?: Error;
44
47
  unlinkOfficialMirrorQueueItem?: Error;
48
+ getSecret?: Error;
49
+ createSecret?: Error;
50
+ linkSecretToServer?: Error;
45
51
  };
46
52
  }
47
53
  export declare function createMockPulseMCPAdminClient(mockData: MockData): IPulseMCPAdminClient;
@@ -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
  }
@@ -1,5 +1,5 @@
1
1
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
2
- import type { Post, PostsResponse, CreatePostParams, UpdatePostParams, ImageUploadResponse, Author, AuthorsResponse, MCPServer, MCPClient, MCPImplementation, MCPImplementationsResponse, SaveMCPImplementationParams, CreateMCPImplementationParams, Provider, ProvidersResponse, OfficialMirrorQueueStatus, OfficialMirrorQueueResponse, OfficialMirrorQueueItemDetail, OfficialMirrorQueueActionResponse, UnofficialMirror, UnofficialMirrorsResponse, CreateUnofficialMirrorParams, UpdateUnofficialMirrorParams, OfficialMirrorRest, OfficialMirrorsResponse, Tenant, TenantsResponse, ListTenantServersResponse, BulkUpdateTenantServersParams, BulkUpdateTenantServersResponse, McpJson, McpJsonsResponse, CreateMcpJsonParams, UpdateMcpJsonParams, UnifiedMCPServer, UnifiedMCPServersResponse, UpdateUnifiedMCPServerParams, Redirect, RedirectsResponse, RedirectStatus, CreateRedirectParams, UpdateRedirectParams, GoodJob, GoodJobsResponse, GoodJobStatus, GoodJobCronSchedule, GoodJobProcess, GoodJobStatistics, GoodJobActionResponse, GoodJobCleanupResponse, PopularityDropBypassStatus, ProctorRunExamParams, ProctorRunExamResponse, ProctorSaveResultsParams, ProctorSaveResultsResponse, ProctorRunsResponse, GetProctorRunsParams, ProctorMetadataResponse, DiscoveredUrlsResponse, MarkDiscoveredUrlProcessedParams, MarkDiscoveredUrlProcessedResponse, DiscoveredUrlStats, MozMetricsResponse, MozBacklinksResponse, MozStoredMetricsResponse, CreateTenantParams, ApiKey, CreateApiKeyParams, DeleteTenantParams, DeleteTenantResponse, DeleteApiKeyResponse, RecacheMCPServerResponse, SetKnownMissingInitToolsListResponse } from './types.js';
2
+ import type { Post, PostsResponse, CreatePostParams, UpdatePostParams, ImageUploadResponse, Author, AuthorsResponse, MCPServer, MCPClient, MCPImplementation, MCPImplementationsResponse, SaveMCPImplementationParams, CreateMCPImplementationParams, Provider, ProvidersResponse, OfficialMirrorQueueStatus, OfficialMirrorQueueResponse, OfficialMirrorQueueItemDetail, OfficialMirrorQueueActionResponse, UnofficialMirror, UnofficialMirrorsResponse, CreateUnofficialMirrorParams, UpdateUnofficialMirrorParams, OfficialMirrorRest, OfficialMirrorsResponse, Tenant, TenantsResponse, ListTenantServersResponse, BulkUpdateTenantServersParams, BulkUpdateTenantServersResponse, McpJson, McpJsonsResponse, CreateMcpJsonParams, UpdateMcpJsonParams, UnifiedMCPServer, UnifiedMCPServersResponse, UpdateUnifiedMCPServerParams, Redirect, RedirectsResponse, RedirectStatus, CreateRedirectParams, UpdateRedirectParams, Secret, CreateSecretParams, LinkSecretToServerParams, SecretWithLink, GoodJob, GoodJobsResponse, GoodJobStatus, GoodJobCronSchedule, GoodJobProcess, GoodJobStatistics, GoodJobActionResponse, GoodJobCleanupResponse, PopularityDropBypassStatus, ProctorRunExamParams, ProctorRunExamResponse, ProctorSaveResultsParams, ProctorSaveResultsResponse, ProctorRunsResponse, GetProctorRunsParams, ProctorMetadataResponse, DiscoveredUrlsResponse, MarkDiscoveredUrlProcessedParams, MarkDiscoveredUrlProcessedResponse, DiscoveredUrlStats, MozMetricsResponse, MozBacklinksResponse, MozStoredMetricsResponse, CreateTenantParams, ApiKey, CreateApiKeyParams, DeleteTenantParams, DeleteTenantResponse, DeleteApiKeyResponse, RecacheMCPServerResponse, SetKnownMissingInitToolsListResponse, GithubRepositoryClassification, SetGithubRepositoryClassificationResponse } from './types.js';
3
3
  export interface IPulseMCPAdminClient {
4
4
  getPosts(params?: {
5
5
  search?: string;
@@ -136,6 +136,7 @@ export interface IPulseMCPAdminClient {
136
136
  updateUnifiedMCPServer(implementationId: number, params: UpdateUnifiedMCPServerParams): Promise<UnifiedMCPServer>;
137
137
  recacheMCPServer(slug: string): Promise<RecacheMCPServerResponse>;
138
138
  setKnownMissingInitToolsList(id: number, knownMissingInitToolsList: boolean, knownMissingInitToolsListFilterTo?: string | null): Promise<SetKnownMissingInitToolsListResponse>;
139
+ setGithubRepositoryClassification(id: number, classification: GithubRepositoryClassification): Promise<SetGithubRepositoryClassificationResponse>;
139
140
  getRedirects(params?: {
140
141
  q?: string;
141
142
  status?: RedirectStatus;
@@ -149,6 +150,9 @@ export interface IPulseMCPAdminClient {
149
150
  success: boolean;
150
151
  message: string;
151
152
  }>;
153
+ getSecret(idOrSlug: string | number): Promise<Secret | null>;
154
+ createSecret(params: CreateSecretParams): Promise<Secret>;
155
+ linkSecretToServer(params: LinkSecretToServerParams): Promise<SecretWithLink>;
152
156
  getGoodJobs(params?: {
153
157
  queue_name?: string;
154
158
  status?: GoodJobStatus;
@@ -337,6 +341,7 @@ export declare class PulseMCPAdminClient implements IPulseMCPAdminClient {
337
341
  updateUnifiedMCPServer(implementationId: number, params: UpdateUnifiedMCPServerParams): Promise<UnifiedMCPServer>;
338
342
  recacheMCPServer(slug: string): Promise<RecacheMCPServerResponse>;
339
343
  setKnownMissingInitToolsList(id: number, knownMissingInitToolsList: boolean, knownMissingInitToolsListFilterTo?: string | null): Promise<SetKnownMissingInitToolsListResponse>;
344
+ setGithubRepositoryClassification(id: number, classification: GithubRepositoryClassification): Promise<SetGithubRepositoryClassificationResponse>;
340
345
  getRedirects(params?: {
341
346
  q?: string;
342
347
  status?: RedirectStatus;
@@ -350,6 +355,9 @@ export declare class PulseMCPAdminClient implements IPulseMCPAdminClient {
350
355
  success: boolean;
351
356
  message: string;
352
357
  }>;
358
+ getSecret(idOrSlug: string | number): Promise<Secret | null>;
359
+ createSecret(params: CreateSecretParams): Promise<Secret>;
360
+ linkSecretToServer(params: LinkSecretToServerParams): Promise<SecretWithLink>;
353
361
  getGoodJobs(params?: {
354
362
  queue_name?: string;
355
363
  status?: GoodJobStatus;