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.
- package/README.md +83 -78
- package/build/shared/src/pulsemcp-admin-client/lib/create-secret.js +41 -0
- package/build/shared/src/pulsemcp-admin-client/lib/get-secret.js +28 -0
- package/build/shared/src/pulsemcp-admin-client/lib/link-secret-to-server.js +50 -0
- package/build/shared/src/pulsemcp-admin-client/lib/set-github-repository-classification.js +38 -0
- package/build/shared/src/pulsemcp-admin-client/pulsemcp-admin-client.integration-mock.js +59 -0
- package/build/shared/src/server.js +17 -0
- package/build/shared/src/tools/link-secret-to-mcp-server.js +140 -0
- package/build/shared/src/tools/set-github-repository-classification.js +73 -0
- package/build/shared/src/tools.js +17 -0
- package/node_modules/@pulsemcp/mcp-elicitation/package.json +1 -1
- package/package.json +1 -1
- package/shared/pulsemcp-admin-client/lib/create-secret.d.ts +7 -0
- package/shared/pulsemcp-admin-client/lib/create-secret.js +41 -0
- package/shared/pulsemcp-admin-client/lib/get-secret.d.ts +7 -0
- package/shared/pulsemcp-admin-client/lib/get-secret.js +28 -0
- package/shared/pulsemcp-admin-client/lib/link-secret-to-server.d.ts +12 -0
- package/shared/pulsemcp-admin-client/lib/link-secret-to-server.js +50 -0
- package/shared/pulsemcp-admin-client/lib/set-github-repository-classification.d.ts +3 -0
- package/shared/pulsemcp-admin-client/lib/set-github-repository-classification.js +38 -0
- package/shared/pulsemcp-admin-client/pulsemcp-admin-client.integration-mock.d.ts +7 -1
- package/shared/pulsemcp-admin-client/pulsemcp-admin-client.integration-mock.js +59 -0
- package/shared/server.d.ts +9 -1
- package/shared/server.js +17 -0
- package/shared/tools/link-secret-to-mcp-server.d.ts +50 -0
- package/shared/tools/link-secret-to-mcp-server.js +140 -0
- package/shared/tools/set-github-repository-classification.d.ts +41 -0
- package/shared/tools/set-github-repository-classification.js +73 -0
- package/shared/tools.d.ts +2 -1
- package/shared/tools.js +17 -0
- package/shared/types.d.ts +36 -0
package/shared/server.js
CHANGED
|
@@ -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);
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
2
|
+
import type { ClientFactory } from '../server.js';
|
|
3
|
+
export declare function linkSecretToMcpServer(_server: Server, clientFactory: ClientFactory): {
|
|
4
|
+
name: string;
|
|
5
|
+
description: string;
|
|
6
|
+
inputSchema: {
|
|
7
|
+
type: string;
|
|
8
|
+
properties: {
|
|
9
|
+
slug: {
|
|
10
|
+
type: string;
|
|
11
|
+
description: "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.";
|
|
12
|
+
};
|
|
13
|
+
onepassword_item_id: {
|
|
14
|
+
type: string;
|
|
15
|
+
description: "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.";
|
|
16
|
+
};
|
|
17
|
+
mcp_server_slug: {
|
|
18
|
+
type: string;
|
|
19
|
+
description: "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.";
|
|
20
|
+
};
|
|
21
|
+
title: {
|
|
22
|
+
type: string;
|
|
23
|
+
description: "Optional human-readable title for the secret (only used when creating a new secret).";
|
|
24
|
+
};
|
|
25
|
+
description: {
|
|
26
|
+
type: string;
|
|
27
|
+
description: "Optional description of what the secret is for (only used when creating a new secret).";
|
|
28
|
+
};
|
|
29
|
+
onepassword_tag: {
|
|
30
|
+
type: string;
|
|
31
|
+
description: "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.";
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
required: string[];
|
|
35
|
+
};
|
|
36
|
+
handler: (args: unknown) => Promise<{
|
|
37
|
+
content: {
|
|
38
|
+
type: string;
|
|
39
|
+
text: string;
|
|
40
|
+
}[];
|
|
41
|
+
isError: boolean;
|
|
42
|
+
} | {
|
|
43
|
+
content: {
|
|
44
|
+
type: string;
|
|
45
|
+
text: string;
|
|
46
|
+
}[];
|
|
47
|
+
isError?: undefined;
|
|
48
|
+
}>;
|
|
49
|
+
};
|
|
50
|
+
//# sourceMappingURL=link-secret-to-mcp-server.d.ts.map
|
|
@@ -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,41 @@
|
|
|
1
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
2
|
+
import type { ClientFactory } from '../server.js';
|
|
3
|
+
export declare function setGithubRepositoryClassification(_server: Server, clientFactory: ClientFactory): {
|
|
4
|
+
name: string;
|
|
5
|
+
description: string;
|
|
6
|
+
inputSchema: {
|
|
7
|
+
type: string;
|
|
8
|
+
properties: {
|
|
9
|
+
id: {
|
|
10
|
+
type: string;
|
|
11
|
+
description: "The integer id of the GitHub repository record (github_repositories.id).";
|
|
12
|
+
};
|
|
13
|
+
classification: {
|
|
14
|
+
type: string;
|
|
15
|
+
enum: ("other" | "single_mcp_server" | "single_mcp_client" | "multiple_mcp_servers" | "multiple_mcp_clients")[];
|
|
16
|
+
description: `The classification to set on the GitHub repository. One of: ${string}.
|
|
17
|
+
|
|
18
|
+
- single_mcp_server: the repo is a single MCP server (counts toward the gh_stars popularity path).
|
|
19
|
+
- single_mcp_client: the repo is a single MCP client.
|
|
20
|
+
- multiple_mcp_servers: the repo hosts multiple MCP servers.
|
|
21
|
+
- multiple_mcp_clients: the repo hosts multiple MCP clients.
|
|
22
|
+
- 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 \u2014 int_github_repositories.sql maps it to mcp_server_count = 0, dropping it from the normalized downloads estimate.`;
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
required: string[];
|
|
26
|
+
};
|
|
27
|
+
handler: (args: unknown) => Promise<{
|
|
28
|
+
content: {
|
|
29
|
+
type: string;
|
|
30
|
+
text: string;
|
|
31
|
+
}[];
|
|
32
|
+
isError?: undefined;
|
|
33
|
+
} | {
|
|
34
|
+
content: {
|
|
35
|
+
type: string;
|
|
36
|
+
text: string;
|
|
37
|
+
}[];
|
|
38
|
+
isError: boolean;
|
|
39
|
+
}>;
|
|
40
|
+
};
|
|
41
|
+
//# sourceMappingURL=set-github-repository-classification.d.ts.map
|
|
@@ -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
|
+
}
|
package/shared/tools.d.ts
CHANGED
|
@@ -23,13 +23,14 @@ import { ClientFactory } from './server.js';
|
|
|
23
23
|
* - mcp_jsons / mcp_jsons_readonly: MCP JSON configuration tools
|
|
24
24
|
* - mcp_servers / mcp_servers_readonly: Unified MCP server tools (abstracted interface)
|
|
25
25
|
* - redirects / redirects_readonly: URL redirect management tools
|
|
26
|
+
* - secrets: Auth-secret tools (link_secret_to_mcp_server). Upserts a Secret referencing a 1Password item and writes the mcp_servers_secrets join Proctor reads to inject the value. Write-only, so there is no _readonly variant.
|
|
26
27
|
* - good_jobs / good_jobs_readonly: GoodJob background job management tools
|
|
27
28
|
* - proctor / proctor_readonly: Proctor exam execution and result storage tools. The readonly variant includes get_exam_result, list_proctor_runs, and get_proctor_metadata for retrieving stored results and metadata without running exams or saving
|
|
28
29
|
* - discovered_urls / discovered_urls_readonly: Discovered URL management tools for processing URLs into MCP implementations
|
|
29
30
|
* - moz / moz_readonly: MOZ SEO metrics tools (live URL metrics, backlinks, stored historical data)
|
|
30
31
|
* - notifications: Notification email tools (send_impl_posted_notif). Separated from server_directory so notification capability can be granted independently.
|
|
31
32
|
*/
|
|
32
|
-
export type ToolGroup = 'newsletter' | 'newsletter_readonly' | 'server_directory' | 'server_directory_readonly' | 'official_queue' | 'official_queue_readonly' | 'unofficial_mirrors' | 'unofficial_mirrors_readonly' | 'official_mirrors' | 'official_mirrors_readonly' | 'tenants' | 'tenants_readonly' | 'tenants_destructive' | 'mcp_jsons' | 'mcp_jsons_readonly' | 'mcp_servers' | 'mcp_servers_readonly' | 'redirects' | 'redirects_readonly' | 'good_jobs' | 'good_jobs_readonly' | 'proctor' | 'proctor_readonly' | 'discovered_urls' | 'discovered_urls_readonly' | 'moz' | 'moz_readonly' | 'notifications';
|
|
33
|
+
export type ToolGroup = 'newsletter' | 'newsletter_readonly' | 'server_directory' | 'server_directory_readonly' | 'official_queue' | 'official_queue_readonly' | 'unofficial_mirrors' | 'unofficial_mirrors_readonly' | 'official_mirrors' | 'official_mirrors_readonly' | 'tenants' | 'tenants_readonly' | 'tenants_destructive' | 'mcp_jsons' | 'mcp_jsons_readonly' | 'mcp_servers' | 'mcp_servers_readonly' | 'redirects' | 'redirects_readonly' | 'secrets' | 'good_jobs' | 'good_jobs_readonly' | 'proctor' | 'proctor_readonly' | 'discovered_urls' | 'discovered_urls_readonly' | 'moz' | 'moz_readonly' | 'notifications';
|
|
33
34
|
/**
|
|
34
35
|
* Parse enabled tool groups from environment variable or parameter
|
|
35
36
|
* @param enabledGroupsParam - Comma-separated list of tool groups (e.g., "newsletter,server_directory_readonly")
|
package/shared/tools.js
CHANGED
|
@@ -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',
|
package/shared/types.d.ts
CHANGED
|
@@ -676,6 +676,37 @@ export interface UpdateRedirectParams {
|
|
|
676
676
|
to?: string;
|
|
677
677
|
status?: RedirectStatus;
|
|
678
678
|
}
|
|
679
|
+
export interface Secret {
|
|
680
|
+
id: number;
|
|
681
|
+
slug: string;
|
|
682
|
+
onepassword_item_id: string;
|
|
683
|
+
title?: string | null;
|
|
684
|
+
description?: string | null;
|
|
685
|
+
mcp_servers_count?: number;
|
|
686
|
+
mcp_server_slugs?: string[];
|
|
687
|
+
created_at?: string;
|
|
688
|
+
updated_at?: string;
|
|
689
|
+
}
|
|
690
|
+
export interface CreateSecretParams {
|
|
691
|
+
slug: string;
|
|
692
|
+
onepassword_item_id: string;
|
|
693
|
+
title?: string;
|
|
694
|
+
description?: string;
|
|
695
|
+
}
|
|
696
|
+
export interface SecretServerLink {
|
|
697
|
+
mcp_server_id: number;
|
|
698
|
+
mcp_server_slug: string;
|
|
699
|
+
onepassword_tag?: string | null;
|
|
700
|
+
}
|
|
701
|
+
export interface LinkSecretToServerParams {
|
|
702
|
+
secret: string | number;
|
|
703
|
+
mcp_server_id?: number;
|
|
704
|
+
mcp_server_slug?: string;
|
|
705
|
+
onepassword_tag?: string;
|
|
706
|
+
}
|
|
707
|
+
export interface SecretWithLink extends Secret {
|
|
708
|
+
link: SecretServerLink;
|
|
709
|
+
}
|
|
679
710
|
export type GoodJobStatus = 'scheduled' | 'queued' | 'running' | 'succeeded' | 'failed' | 'discarded';
|
|
680
711
|
export interface GoodJob {
|
|
681
712
|
id: string;
|
|
@@ -897,6 +928,11 @@ export interface SetKnownMissingInitToolsListResponse {
|
|
|
897
928
|
known_missing_init_tools_list: boolean;
|
|
898
929
|
known_missing_init_tools_list_filter_to: string | null;
|
|
899
930
|
}
|
|
931
|
+
export type GithubRepositoryClassification = 'single_mcp_server' | 'single_mcp_client' | 'multiple_mcp_servers' | 'multiple_mcp_clients' | 'other';
|
|
932
|
+
export interface SetGithubRepositoryClassificationResponse {
|
|
933
|
+
id: number;
|
|
934
|
+
classification: GithubRepositoryClassification;
|
|
935
|
+
}
|
|
900
936
|
export interface PopularityDropBypassStatus {
|
|
901
937
|
enabled: boolean;
|
|
902
938
|
enabled_at: string | null;
|