pulsemcp-cms-admin-mcp-server 0.9.17 → 0.9.26
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 +93 -74
- package/build/index.js +53 -1
- package/build/shared/src/elicitation-config.js +69 -0
- package/build/shared/src/pulsemcp-admin-client/lib/bulk-update-tenant-servers.js +41 -0
- package/build/shared/src/pulsemcp-admin-client/lib/create-mcp-implementation.js +7 -0
- package/build/shared/src/pulsemcp-admin-client/lib/delete-api-key.js +25 -0
- package/build/shared/src/pulsemcp-admin-client/lib/delete-tenant.js +31 -0
- package/build/shared/src/pulsemcp-admin-client/lib/get-unified-mcp-server.js +5 -1
- package/build/shared/src/pulsemcp-admin-client/lib/list-tenant-servers.js +57 -0
- package/build/shared/src/pulsemcp-admin-client/lib/save-mcp-implementation.js +8 -0
- package/build/shared/src/pulsemcp-admin-client/lib/set-known-missing-init-tools-list.js +42 -0
- package/build/shared/src/pulsemcp-admin-client/lib/unified-mcp-server-mapper.js +3 -0
- package/build/shared/src/pulsemcp-admin-client/lib/update-unified-mcp-server.js +14 -0
- package/build/shared/src/pulsemcp-admin-client/pulsemcp-admin-client.integration-mock.js +39 -0
- package/build/shared/src/server.js +20 -0
- package/build/shared/src/tools/add-servers-to-tenant.js +133 -0
- package/build/shared/src/tools/delete-api-key.js +119 -0
- package/build/shared/src/tools/delete-tenant.js +129 -0
- package/build/shared/src/tools/get-mcp-server.js +26 -8
- package/build/shared/src/tools/list-mcp-servers.js +3 -0
- package/build/shared/src/tools/list-tenant-servers.js +89 -0
- package/build/shared/src/tools/remove-servers-from-tenant.js +92 -0
- package/build/shared/src/tools/revoke-api-key.js +119 -0
- package/build/shared/src/tools/save-mcp-implementation.js +89 -2
- package/build/shared/src/tools/set-known-missing-init-tools-list.js +75 -0
- package/build/shared/src/tools/update-mcp-server.js +19 -0
- package/build/shared/src/tools.js +30 -1
- package/node_modules/@pulsemcp/mcp-elicitation/build/config.d.ts +15 -0
- package/node_modules/@pulsemcp/mcp-elicitation/build/config.js +41 -0
- package/node_modules/@pulsemcp/mcp-elicitation/build/elicitation.d.ts +24 -0
- package/node_modules/@pulsemcp/mcp-elicitation/build/elicitation.js +175 -0
- package/node_modules/@pulsemcp/mcp-elicitation/build/index.d.ts +3 -0
- package/node_modules/@pulsemcp/mcp-elicitation/build/index.js +2 -0
- package/node_modules/@pulsemcp/mcp-elicitation/build/types.d.ts +114 -0
- package/node_modules/@pulsemcp/mcp-elicitation/build/types.js +1 -0
- package/node_modules/@pulsemcp/mcp-elicitation/package.json +28 -0
- package/package.json +7 -1
- package/shared/elicitation-config.d.ts +61 -0
- package/shared/elicitation-config.js +69 -0
- package/shared/pulsemcp-admin-client/lib/bulk-update-tenant-servers.d.ts +3 -0
- package/shared/pulsemcp-admin-client/lib/bulk-update-tenant-servers.js +41 -0
- package/shared/pulsemcp-admin-client/lib/create-mcp-implementation.js +7 -0
- package/shared/pulsemcp-admin-client/lib/delete-api-key.d.ts +3 -0
- package/shared/pulsemcp-admin-client/lib/delete-api-key.js +25 -0
- package/shared/pulsemcp-admin-client/lib/delete-tenant.d.ts +3 -0
- package/shared/pulsemcp-admin-client/lib/delete-tenant.js +31 -0
- package/shared/pulsemcp-admin-client/lib/get-unified-mcp-server.js +5 -1
- package/shared/pulsemcp-admin-client/lib/list-tenant-servers.d.ts +7 -0
- package/shared/pulsemcp-admin-client/lib/list-tenant-servers.js +57 -0
- package/shared/pulsemcp-admin-client/lib/save-mcp-implementation.js +8 -0
- package/shared/pulsemcp-admin-client/lib/set-known-missing-init-tools-list.d.ts +3 -0
- package/shared/pulsemcp-admin-client/lib/set-known-missing-init-tools-list.js +42 -0
- package/shared/pulsemcp-admin-client/lib/unified-mcp-server-mapper.d.ts +2 -0
- package/shared/pulsemcp-admin-client/lib/unified-mcp-server-mapper.js +3 -0
- package/shared/pulsemcp-admin-client/lib/update-unified-mcp-server.js +14 -0
- package/shared/pulsemcp-admin-client/pulsemcp-admin-client.integration-mock.js +39 -0
- package/shared/server.d.ts +19 -1
- package/shared/server.js +20 -0
- package/shared/tools/add-servers-to-tenant.d.ts +57 -0
- package/shared/tools/add-servers-to-tenant.js +133 -0
- package/shared/tools/delete-api-key.d.ts +30 -0
- package/shared/tools/delete-api-key.js +119 -0
- package/shared/tools/delete-tenant.d.ts +36 -0
- package/shared/tools/delete-tenant.js +129 -0
- package/shared/tools/get-mcp-server.js +26 -8
- package/shared/tools/list-mcp-servers.js +3 -0
- package/shared/tools/list-tenant-servers.d.ts +45 -0
- package/shared/tools/list-tenant-servers.js +89 -0
- package/shared/tools/remove-servers-from-tenant.d.ts +42 -0
- package/shared/tools/remove-servers-from-tenant.js +92 -0
- package/shared/tools/revoke-api-key.d.ts +30 -0
- package/shared/tools/revoke-api-key.js +119 -0
- package/shared/tools/save-mcp-implementation.d.ts +9 -1
- package/shared/tools/save-mcp-implementation.js +89 -2
- package/shared/tools/set-known-missing-init-tools-list.d.ts +38 -0
- package/shared/tools/set-known-missing-init-tools-list.js +75 -0
- package/shared/tools/update-mcp-server.d.ts +6 -0
- package/shared/tools/update-mcp-server.js +19 -0
- package/shared/tools.d.ts +5 -3
- package/shared/tools.js +30 -1
- package/shared/types.d.ts +89 -0
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { requestConfirmation, createConfirmationSchema } from '@pulsemcp/mcp-elicitation';
|
|
3
|
+
import { readCmsAdminElicitationConfig } from '../elicitation-config.js';
|
|
4
|
+
const PARAM_DESCRIPTIONS = {
|
|
5
|
+
id: 'The numeric ID of the API key to revoke.',
|
|
6
|
+
};
|
|
7
|
+
const DeleteApiKeySchema = z.object({
|
|
8
|
+
id: z.number().int().positive().describe(PARAM_DESCRIPTIONS.id),
|
|
9
|
+
});
|
|
10
|
+
const TOOL_DESCRIPTION = `Permanently revoke (delete) an API key.
|
|
11
|
+
|
|
12
|
+
**This is a destructive operation.** Revoking an API key immediately invalidates it; any clients still using the key will start receiving 401 Unauthorized. The deletion is idempotent — calling delete on a non-existent or already-deleted key returns success.
|
|
13
|
+
|
|
14
|
+
By default, the request requires explicit user approval via MCP elicitation before being sent to the admin API.
|
|
15
|
+
|
|
16
|
+
**Parameters:**
|
|
17
|
+
- \`id\` (required): Numeric ID of the API key.
|
|
18
|
+
|
|
19
|
+
**Returns:**
|
|
20
|
+
- \`success\`: Boolean indicating whether the request succeeded.
|
|
21
|
+
- \`message\`: Human-readable confirmation message.
|
|
22
|
+
|
|
23
|
+
**Use cases:**
|
|
24
|
+
- Revoke a leaked or compromised API key.
|
|
25
|
+
- Roll a tenant's keys after a permission change.
|
|
26
|
+
- Remove a deprecated key during cleanup.`;
|
|
27
|
+
export function deleteApiKey(server, clientFactory) {
|
|
28
|
+
return {
|
|
29
|
+
name: 'delete_api_key',
|
|
30
|
+
description: TOOL_DESCRIPTION,
|
|
31
|
+
inputSchema: {
|
|
32
|
+
type: 'object',
|
|
33
|
+
properties: {
|
|
34
|
+
id: {
|
|
35
|
+
type: 'number',
|
|
36
|
+
description: PARAM_DESCRIPTIONS.id,
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
required: ['id'],
|
|
40
|
+
},
|
|
41
|
+
handler: async (args) => {
|
|
42
|
+
try {
|
|
43
|
+
const validatedArgs = DeleteApiKeySchema.parse(args);
|
|
44
|
+
const elicitConfig = readCmsAdminElicitationConfig();
|
|
45
|
+
if (elicitConfig.destructiveElicitationEnabled) {
|
|
46
|
+
const lines = [
|
|
47
|
+
'About to PERMANENTLY REVOKE an API key via the PulseMCP admin API:',
|
|
48
|
+
` API key ID: ${validatedArgs.id}`,
|
|
49
|
+
'',
|
|
50
|
+
'Any clients still using this key will start receiving 401 Unauthorized.',
|
|
51
|
+
'This action cannot be undone.',
|
|
52
|
+
];
|
|
53
|
+
const confirmation = await requestConfirmation({
|
|
54
|
+
server,
|
|
55
|
+
message: lines.join('\n') + '\n',
|
|
56
|
+
requestedSchema: createConfirmationSchema('Revoke this API key?', 'Confirm that you want to permanently revoke this API key.'),
|
|
57
|
+
meta: {
|
|
58
|
+
'com.pulsemcp/tool-name': 'delete_api_key',
|
|
59
|
+
},
|
|
60
|
+
}, elicitConfig.base);
|
|
61
|
+
if (confirmation.action !== 'accept') {
|
|
62
|
+
if (confirmation.action === 'expired') {
|
|
63
|
+
return {
|
|
64
|
+
content: [
|
|
65
|
+
{
|
|
66
|
+
type: 'text',
|
|
67
|
+
text: 'API key revocation confirmation expired. Please try again.',
|
|
68
|
+
},
|
|
69
|
+
],
|
|
70
|
+
isError: true,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
content: [
|
|
75
|
+
{
|
|
76
|
+
type: 'text',
|
|
77
|
+
text: 'API key revocation was cancelled by the user.',
|
|
78
|
+
},
|
|
79
|
+
],
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
if (confirmation.content &&
|
|
83
|
+
'confirm' in confirmation.content &&
|
|
84
|
+
confirmation.content.confirm === false) {
|
|
85
|
+
return {
|
|
86
|
+
content: [
|
|
87
|
+
{
|
|
88
|
+
type: 'text',
|
|
89
|
+
text: 'API key revocation was not confirmed.',
|
|
90
|
+
},
|
|
91
|
+
],
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
const client = clientFactory();
|
|
96
|
+
const result = await client.deleteApiKey(validatedArgs.id);
|
|
97
|
+
return {
|
|
98
|
+
content: [
|
|
99
|
+
{
|
|
100
|
+
type: 'text',
|
|
101
|
+
text: `${result.success ? 'API key revoked.' : 'API key revocation failed.'} ${result.message}`,
|
|
102
|
+
},
|
|
103
|
+
],
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
return {
|
|
108
|
+
content: [
|
|
109
|
+
{
|
|
110
|
+
type: 'text',
|
|
111
|
+
text: `Error revoking API key: ${error instanceof Error ? error.message : String(error)}`,
|
|
112
|
+
},
|
|
113
|
+
],
|
|
114
|
+
isError: true,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
2
|
+
import type { ClientFactory } from '../server.js';
|
|
3
|
+
export declare function deleteTenant(server: Server, clientFactory: ClientFactory): {
|
|
4
|
+
name: string;
|
|
5
|
+
description: string;
|
|
6
|
+
inputSchema: {
|
|
7
|
+
type: string;
|
|
8
|
+
properties: {
|
|
9
|
+
id_or_slug: {
|
|
10
|
+
oneOf: {
|
|
11
|
+
type: string;
|
|
12
|
+
}[];
|
|
13
|
+
description: "The ID (number) or slug (string) of the tenant to delete.";
|
|
14
|
+
};
|
|
15
|
+
force: {
|
|
16
|
+
type: string;
|
|
17
|
+
description: "When true, deletes the tenant and cascades to dependent records (API keys, enrichments, etc.). When false (default), the deletion will fail with a 422 if dependents exist.";
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
required: string[];
|
|
21
|
+
};
|
|
22
|
+
handler: (args: unknown) => Promise<{
|
|
23
|
+
content: {
|
|
24
|
+
type: string;
|
|
25
|
+
text: string;
|
|
26
|
+
}[];
|
|
27
|
+
isError: boolean;
|
|
28
|
+
} | {
|
|
29
|
+
content: {
|
|
30
|
+
type: string;
|
|
31
|
+
text: string;
|
|
32
|
+
}[];
|
|
33
|
+
isError?: undefined;
|
|
34
|
+
}>;
|
|
35
|
+
};
|
|
36
|
+
//# sourceMappingURL=delete-tenant.d.ts.map
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { requestConfirmation, createConfirmationSchema } from '@pulsemcp/mcp-elicitation';
|
|
3
|
+
import { readCmsAdminElicitationConfig } from '../elicitation-config.js';
|
|
4
|
+
const PARAM_DESCRIPTIONS = {
|
|
5
|
+
id_or_slug: 'The ID (number) or slug (string) of the tenant to delete.',
|
|
6
|
+
force: 'When true, deletes the tenant and cascades to dependent records (API keys, enrichments, etc.). When false (default), the deletion will fail with a 422 if dependents exist.',
|
|
7
|
+
};
|
|
8
|
+
const DeleteTenantSchema = z.object({
|
|
9
|
+
id_or_slug: z.union([z.number(), z.string()]).describe(PARAM_DESCRIPTIONS.id_or_slug),
|
|
10
|
+
force: z.boolean().optional().describe(PARAM_DESCRIPTIONS.force),
|
|
11
|
+
});
|
|
12
|
+
const TOOL_DESCRIPTION = `Permanently delete a tenant from the PulseMCP admin API.
|
|
13
|
+
|
|
14
|
+
**This is a destructive operation.** Deleting a tenant removes the tenant record and (with \`force: true\`) cascades to dependent records such as API keys and enrichments. By default, the request requires explicit user approval via MCP elicitation before being sent to the admin API.
|
|
15
|
+
|
|
16
|
+
**Parameters:**
|
|
17
|
+
- \`id_or_slug\` (required): Numeric ID or slug of the tenant.
|
|
18
|
+
- \`force\` (optional, default false): Cascade-delete dependents. Without this, deleting a tenant that owns API keys or other resources returns 422.
|
|
19
|
+
|
|
20
|
+
**Returns:**
|
|
21
|
+
- \`success\`: Boolean indicating whether the tenant was deleted.
|
|
22
|
+
- \`message\`: Human-readable confirmation message.
|
|
23
|
+
|
|
24
|
+
**Use cases:**
|
|
25
|
+
- Permanently remove a deprovisioned tenant.
|
|
26
|
+
- Delete a test tenant created during onboarding/QA.
|
|
27
|
+
- Cascading cleanup of a tenant and all of its API keys (\`force: true\`).`;
|
|
28
|
+
export function deleteTenant(server, clientFactory) {
|
|
29
|
+
return {
|
|
30
|
+
name: 'delete_tenant',
|
|
31
|
+
description: TOOL_DESCRIPTION,
|
|
32
|
+
inputSchema: {
|
|
33
|
+
type: 'object',
|
|
34
|
+
properties: {
|
|
35
|
+
id_or_slug: {
|
|
36
|
+
oneOf: [{ type: 'number' }, { type: 'string' }],
|
|
37
|
+
description: PARAM_DESCRIPTIONS.id_or_slug,
|
|
38
|
+
},
|
|
39
|
+
force: {
|
|
40
|
+
type: 'boolean',
|
|
41
|
+
description: PARAM_DESCRIPTIONS.force,
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
required: ['id_or_slug'],
|
|
45
|
+
},
|
|
46
|
+
handler: async (args) => {
|
|
47
|
+
try {
|
|
48
|
+
const validatedArgs = DeleteTenantSchema.parse(args);
|
|
49
|
+
const elicitConfig = readCmsAdminElicitationConfig();
|
|
50
|
+
if (elicitConfig.destructiveElicitationEnabled) {
|
|
51
|
+
const lines = [
|
|
52
|
+
'About to PERMANENTLY DELETE a tenant via the PulseMCP admin API:',
|
|
53
|
+
` Tenant: ${validatedArgs.id_or_slug}`,
|
|
54
|
+
` Cascade dependents (force): ${validatedArgs.force ? 'yes' : 'no'}`,
|
|
55
|
+
'',
|
|
56
|
+
'This action cannot be undone.',
|
|
57
|
+
];
|
|
58
|
+
const confirmation = await requestConfirmation({
|
|
59
|
+
server,
|
|
60
|
+
message: lines.join('\n') + '\n',
|
|
61
|
+
requestedSchema: createConfirmationSchema('Delete this tenant?', 'Confirm that you want to permanently delete this tenant.'),
|
|
62
|
+
meta: {
|
|
63
|
+
'com.pulsemcp/tool-name': 'delete_tenant',
|
|
64
|
+
},
|
|
65
|
+
}, elicitConfig.base);
|
|
66
|
+
if (confirmation.action !== 'accept') {
|
|
67
|
+
if (confirmation.action === 'expired') {
|
|
68
|
+
return {
|
|
69
|
+
content: [
|
|
70
|
+
{
|
|
71
|
+
type: 'text',
|
|
72
|
+
text: 'Tenant deletion confirmation expired. Please try again.',
|
|
73
|
+
},
|
|
74
|
+
],
|
|
75
|
+
isError: true,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
content: [
|
|
80
|
+
{
|
|
81
|
+
type: 'text',
|
|
82
|
+
text: 'Tenant deletion was cancelled by the user.',
|
|
83
|
+
},
|
|
84
|
+
],
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
// Defense-in-depth: some MCP clients may return action='accept' without the
|
|
88
|
+
// user actually checking the confirmation checkbox.
|
|
89
|
+
if (confirmation.content &&
|
|
90
|
+
'confirm' in confirmation.content &&
|
|
91
|
+
confirmation.content.confirm === false) {
|
|
92
|
+
return {
|
|
93
|
+
content: [
|
|
94
|
+
{
|
|
95
|
+
type: 'text',
|
|
96
|
+
text: 'Tenant deletion was not confirmed.',
|
|
97
|
+
},
|
|
98
|
+
],
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
const client = clientFactory();
|
|
103
|
+
const result = await client.deleteTenant({
|
|
104
|
+
id_or_slug: validatedArgs.id_or_slug,
|
|
105
|
+
force: validatedArgs.force,
|
|
106
|
+
});
|
|
107
|
+
return {
|
|
108
|
+
content: [
|
|
109
|
+
{
|
|
110
|
+
type: 'text',
|
|
111
|
+
text: `${result.success ? 'Tenant deleted.' : 'Tenant deletion failed.'} ${result.message}`,
|
|
112
|
+
},
|
|
113
|
+
],
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
return {
|
|
118
|
+
content: [
|
|
119
|
+
{
|
|
120
|
+
type: 'text',
|
|
121
|
+
text: `Error deleting tenant: ${error instanceof Error ? error.message : String(error)}`,
|
|
122
|
+
},
|
|
123
|
+
],
|
|
124
|
+
isError: true,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
}
|
|
@@ -105,6 +105,16 @@ Example response:
|
|
|
105
105
|
if (server.verified_no_remote_canonicals !== undefined) {
|
|
106
106
|
content += `**Verified No Remote Canonicals:** ${server.verified_no_remote_canonicals ? 'Yes' : 'No'}\n`;
|
|
107
107
|
}
|
|
108
|
+
if (server.owner_tenant_slug || server.owner_tenant_id) {
|
|
109
|
+
content += `**Owner Tenant:** ${server.owner_tenant_slug ?? '(no slug)'}`;
|
|
110
|
+
if (server.owner_tenant_id) {
|
|
111
|
+
content += ` (id: ${server.owner_tenant_id})`;
|
|
112
|
+
}
|
|
113
|
+
content += '\n';
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
content += `**Owner Tenant:** (none)\n`;
|
|
117
|
+
}
|
|
108
118
|
if (server.short_description) {
|
|
109
119
|
content += `\n**Short Description:**\n${server.short_description}\n`;
|
|
110
120
|
}
|
|
@@ -152,10 +162,11 @@ Example response:
|
|
|
152
162
|
content += `- **Status:** ${server.source_code.github_status}\n`;
|
|
153
163
|
}
|
|
154
164
|
}
|
|
155
|
-
// Canonical URLs
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
165
|
+
// Canonical URLs — always rendered so callers can see the field is empty vs. missing
|
|
166
|
+
const canonicalUrls = server.canonical_urls ?? [];
|
|
167
|
+
if (canonicalUrls.length > 0) {
|
|
168
|
+
content += `\n## Canonical URLs (${canonicalUrls.length})\n`;
|
|
169
|
+
for (const canonical of canonicalUrls) {
|
|
159
170
|
content += `- **${canonical.scope}:** ${canonical.url}`;
|
|
160
171
|
if (canonical.note) {
|
|
161
172
|
content += ` (${canonical.note})`;
|
|
@@ -163,10 +174,14 @@ Example response:
|
|
|
163
174
|
content += '\n';
|
|
164
175
|
}
|
|
165
176
|
}
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
177
|
+
else {
|
|
178
|
+
content += `\n## Canonical URLs\n(none)\n`;
|
|
179
|
+
}
|
|
180
|
+
// Remote endpoints — always rendered so callers can see the field is empty vs. missing
|
|
181
|
+
const remotes = server.remotes ?? [];
|
|
182
|
+
if (remotes.length > 0) {
|
|
183
|
+
content += `\n## Remote Endpoints (${remotes.length})\n`;
|
|
184
|
+
for (const [idx, remote] of remotes.entries()) {
|
|
170
185
|
content += `\n### ${idx + 1}. ${remote.display_name || 'Endpoint'}`;
|
|
171
186
|
if (remote.id) {
|
|
172
187
|
content += ` (ID: ${remote.id})`;
|
|
@@ -198,6 +213,9 @@ Example response:
|
|
|
198
213
|
}
|
|
199
214
|
}
|
|
200
215
|
}
|
|
216
|
+
else {
|
|
217
|
+
content += `\n## Remote Endpoints\n(none)\n`;
|
|
218
|
+
}
|
|
201
219
|
// Tags
|
|
202
220
|
if (server.tags && server.tags.length > 0) {
|
|
203
221
|
content += `\n## Tags\n`;
|
|
@@ -123,6 +123,9 @@ Use cases:
|
|
|
123
123
|
if (server.remotes && server.remotes.length > 0) {
|
|
124
124
|
content += ` Remote endpoints: ${server.remotes.length}\n`;
|
|
125
125
|
}
|
|
126
|
+
if (server.owner_tenant_slug || server.owner_tenant_id) {
|
|
127
|
+
content += ` Owner Tenant: ${server.owner_tenant_slug ?? `id ${server.owner_tenant_id}`}\n`;
|
|
128
|
+
}
|
|
126
129
|
if (server.downloads_estimate_total) {
|
|
127
130
|
content += ` Downloads (total): ${server.downloads_estimate_total.toLocaleString()}\n`;
|
|
128
131
|
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
2
|
+
import type { ClientFactory } from '../server.js';
|
|
3
|
+
export declare function listTenantServers(_server: Server, clientFactory: ClientFactory): {
|
|
4
|
+
name: string;
|
|
5
|
+
description: string;
|
|
6
|
+
inputSchema: {
|
|
7
|
+
type: string;
|
|
8
|
+
properties: {
|
|
9
|
+
tenant: {
|
|
10
|
+
oneOf: {
|
|
11
|
+
type: string;
|
|
12
|
+
}[];
|
|
13
|
+
description: "Tenant ID (number) or slug (string)";
|
|
14
|
+
};
|
|
15
|
+
status: {
|
|
16
|
+
type: string;
|
|
17
|
+
enum: string[];
|
|
18
|
+
description: "Association status filter: \"active\" (default), \"deleted\" (soft-deleted only), or \"all\"";
|
|
19
|
+
};
|
|
20
|
+
limit: {
|
|
21
|
+
type: string;
|
|
22
|
+
description: "Results per page, range 1-100 (default 30)";
|
|
23
|
+
};
|
|
24
|
+
offset: {
|
|
25
|
+
type: string;
|
|
26
|
+
description: "Pagination offset (default 0)";
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
required: string[];
|
|
30
|
+
};
|
|
31
|
+
handler: (args: unknown) => Promise<{
|
|
32
|
+
content: {
|
|
33
|
+
type: string;
|
|
34
|
+
text: string;
|
|
35
|
+
}[];
|
|
36
|
+
isError?: undefined;
|
|
37
|
+
} | {
|
|
38
|
+
content: {
|
|
39
|
+
type: string;
|
|
40
|
+
text: string;
|
|
41
|
+
}[];
|
|
42
|
+
isError: boolean;
|
|
43
|
+
}>;
|
|
44
|
+
};
|
|
45
|
+
//# sourceMappingURL=list-tenant-servers.d.ts.map
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
const PARAM_DESCRIPTIONS = {
|
|
3
|
+
tenant: 'Tenant ID (number) or slug (string)',
|
|
4
|
+
status: 'Association status filter: "active" (default), "deleted" (soft-deleted only), or "all"',
|
|
5
|
+
limit: 'Results per page, range 1-100 (default 30)',
|
|
6
|
+
offset: 'Pagination offset (default 0)',
|
|
7
|
+
};
|
|
8
|
+
const ListTenantServersSchema = z.object({
|
|
9
|
+
tenant: z.union([z.number(), z.string()]).describe(PARAM_DESCRIPTIONS.tenant),
|
|
10
|
+
status: z.enum(['active', 'deleted', 'all']).optional().describe(PARAM_DESCRIPTIONS.status),
|
|
11
|
+
limit: z.number().int().min(1).max(100).optional().describe(PARAM_DESCRIPTIONS.limit),
|
|
12
|
+
offset: z.number().int().min(0).optional().describe(PARAM_DESCRIPTIONS.offset),
|
|
13
|
+
});
|
|
14
|
+
export function listTenantServers(_server, clientFactory) {
|
|
15
|
+
return {
|
|
16
|
+
name: 'list_tenant_servers',
|
|
17
|
+
description: `List a tenant's recommended MCP server associations (TenantsMcpServer rows).
|
|
18
|
+
|
|
19
|
+
Use this to inspect which public MCP servers are currently part of a tenant's recommendation list, before/after calling add_servers_to_tenant or remove_servers_from_tenant.
|
|
20
|
+
|
|
21
|
+
Each association row includes:
|
|
22
|
+
- id: TenantsMcpServer association ID (use this for restore_association_ids in add_servers_to_tenant)
|
|
23
|
+
- mcp_server_id / mcp_server_slug: the underlying public MCP server
|
|
24
|
+
- status: "active" or "deleted" (soft-deleted)
|
|
25
|
+
- server_json_selection: which server.json variant the tenant pins (always "unofficial" for entries created via these tools)
|
|
26
|
+
- touched / first_touched_at: whether the tenant's customization touched this association
|
|
27
|
+
|
|
28
|
+
Use cases:
|
|
29
|
+
- Audit a tenant's recommendation list
|
|
30
|
+
- Find soft-deleted associations to restore
|
|
31
|
+
- Verify the result of a recent add/remove call`,
|
|
32
|
+
inputSchema: {
|
|
33
|
+
type: 'object',
|
|
34
|
+
properties: {
|
|
35
|
+
tenant: {
|
|
36
|
+
oneOf: [{ type: 'number' }, { type: 'string' }],
|
|
37
|
+
description: PARAM_DESCRIPTIONS.tenant,
|
|
38
|
+
},
|
|
39
|
+
status: {
|
|
40
|
+
type: 'string',
|
|
41
|
+
enum: ['active', 'deleted', 'all'],
|
|
42
|
+
description: PARAM_DESCRIPTIONS.status,
|
|
43
|
+
},
|
|
44
|
+
limit: { type: 'number', description: PARAM_DESCRIPTIONS.limit },
|
|
45
|
+
offset: { type: 'number', description: PARAM_DESCRIPTIONS.offset },
|
|
46
|
+
},
|
|
47
|
+
required: ['tenant'],
|
|
48
|
+
},
|
|
49
|
+
handler: async (args) => {
|
|
50
|
+
try {
|
|
51
|
+
const validatedArgs = ListTenantServersSchema.parse(args);
|
|
52
|
+
const client = clientFactory();
|
|
53
|
+
const response = await client.listTenantServers(validatedArgs.tenant, {
|
|
54
|
+
status: validatedArgs.status,
|
|
55
|
+
limit: validatedArgs.limit,
|
|
56
|
+
offset: validatedArgs.offset,
|
|
57
|
+
});
|
|
58
|
+
const { data, pagination } = response;
|
|
59
|
+
const statusLabel = validatedArgs.status || 'active';
|
|
60
|
+
let content = `**Tenant Server Associations** (status=${statusLabel})\n\n`;
|
|
61
|
+
content += `Showing ${data.length} of ${pagination.total_count} associations (page ${pagination.current_page} of ${pagination.total_pages}).\n\n`;
|
|
62
|
+
if (data.length === 0) {
|
|
63
|
+
content += `_No associations matching this filter._\n`;
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
for (const row of data) {
|
|
67
|
+
content += `- **${row.mcp_server_slug}** (server_id=${row.mcp_server_id}, association_id=${row.id})\n`;
|
|
68
|
+
content += ` status=${row.status}, server_json_selection=${row.server_json_selection}, touched=${row.touched}\n`;
|
|
69
|
+
if (row.first_touched_at) {
|
|
70
|
+
content += ` first_touched_at=${row.first_touched_at}\n`;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return { content: [{ type: 'text', text: content }] };
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
return {
|
|
78
|
+
content: [
|
|
79
|
+
{
|
|
80
|
+
type: 'text',
|
|
81
|
+
text: `Error listing tenant servers: ${error instanceof Error ? error.message : String(error)}`,
|
|
82
|
+
},
|
|
83
|
+
],
|
|
84
|
+
isError: true,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
2
|
+
import type { ClientFactory } from '../server.js';
|
|
3
|
+
export declare function removeServersFromTenant(_server: Server, clientFactory: ClientFactory): {
|
|
4
|
+
name: string;
|
|
5
|
+
description: string;
|
|
6
|
+
inputSchema: {
|
|
7
|
+
type: string;
|
|
8
|
+
properties: {
|
|
9
|
+
tenant: {
|
|
10
|
+
oneOf: {
|
|
11
|
+
type: string;
|
|
12
|
+
}[];
|
|
13
|
+
description: "Tenant ID (number) or slug (string) to remove servers from.";
|
|
14
|
+
};
|
|
15
|
+
mcp_servers: {
|
|
16
|
+
type: string;
|
|
17
|
+
items: {
|
|
18
|
+
oneOf: {
|
|
19
|
+
type: string;
|
|
20
|
+
}[];
|
|
21
|
+
};
|
|
22
|
+
description: "Array of MCP server IDs (numbers) or slugs (strings) to remove from the tenant's recommendation list. Touched associations are soft-deleted (preserving the customization history); untouched associations are hard-deleted.";
|
|
23
|
+
minItems: number;
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
required: string[];
|
|
27
|
+
};
|
|
28
|
+
handler: (args: unknown) => Promise<{
|
|
29
|
+
content: {
|
|
30
|
+
type: string;
|
|
31
|
+
text: string;
|
|
32
|
+
}[];
|
|
33
|
+
isError?: undefined;
|
|
34
|
+
} | {
|
|
35
|
+
content: {
|
|
36
|
+
type: string;
|
|
37
|
+
text: string;
|
|
38
|
+
}[];
|
|
39
|
+
isError: boolean;
|
|
40
|
+
}>;
|
|
41
|
+
};
|
|
42
|
+
//# sourceMappingURL=remove-servers-from-tenant.d.ts.map
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
const PARAM_DESCRIPTIONS = {
|
|
3
|
+
tenant: 'Tenant ID (number) or slug (string) to remove servers from.',
|
|
4
|
+
mcp_servers: "Array of MCP server IDs (numbers) or slugs (strings) to remove from the tenant's recommendation list. Touched associations are soft-deleted (preserving the customization history); untouched associations are hard-deleted.",
|
|
5
|
+
};
|
|
6
|
+
const RemoveServersFromTenantSchema = z.object({
|
|
7
|
+
tenant: z.union([z.number(), z.string()]).describe(PARAM_DESCRIPTIONS.tenant),
|
|
8
|
+
mcp_servers: z
|
|
9
|
+
.array(z.union([z.number(), z.string()]))
|
|
10
|
+
.min(1)
|
|
11
|
+
.describe(PARAM_DESCRIPTIONS.mcp_servers),
|
|
12
|
+
});
|
|
13
|
+
export function removeServersFromTenant(_server, clientFactory) {
|
|
14
|
+
return {
|
|
15
|
+
name: 'remove_servers_from_tenant',
|
|
16
|
+
description: `Remove MCP servers from a tenant's recommendation list.
|
|
17
|
+
|
|
18
|
+
This is a convenience inverse of add_servers_to_tenant — it accepts only the tenant and a list of servers to remove. For combined add/remove/restore in a single transactional call, use add_servers_to_tenant.
|
|
19
|
+
|
|
20
|
+
This tool only changes the tenant's recommendation list (TenantsMcpServer rows). The underlying MCP server's public listing is NOT affected.
|
|
21
|
+
|
|
22
|
+
Outcome behavior:
|
|
23
|
+
- Untouched associations: hard-deleted (row removed entirely; outcome "hard_deleted")
|
|
24
|
+
- Touched associations: soft-deleted (status set to "deleted", row preserved; outcome "soft_deleted")
|
|
25
|
+
|
|
26
|
+
The response includes:
|
|
27
|
+
- removed: servers detached, with outcome
|
|
28
|
+
- skipped/unresolved_identifiers: as for add_servers_to_tenant
|
|
29
|
+
|
|
30
|
+
Use cases:
|
|
31
|
+
- Curate a tenant's recommendation list by trimming servers
|
|
32
|
+
- Cleanly remove servers from a tenant without affecting public listings`,
|
|
33
|
+
inputSchema: {
|
|
34
|
+
type: 'object',
|
|
35
|
+
properties: {
|
|
36
|
+
tenant: {
|
|
37
|
+
oneOf: [{ type: 'number' }, { type: 'string' }],
|
|
38
|
+
description: PARAM_DESCRIPTIONS.tenant,
|
|
39
|
+
},
|
|
40
|
+
mcp_servers: {
|
|
41
|
+
type: 'array',
|
|
42
|
+
items: { oneOf: [{ type: 'number' }, { type: 'string' }] },
|
|
43
|
+
description: PARAM_DESCRIPTIONS.mcp_servers,
|
|
44
|
+
minItems: 1,
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
required: ['tenant', 'mcp_servers'],
|
|
48
|
+
},
|
|
49
|
+
handler: async (args) => {
|
|
50
|
+
try {
|
|
51
|
+
const validatedArgs = RemoveServersFromTenantSchema.parse(args);
|
|
52
|
+
const client = clientFactory();
|
|
53
|
+
const result = await client.bulkUpdateTenantServers(validatedArgs.tenant, {
|
|
54
|
+
remove_server_identifiers: validatedArgs.mcp_servers,
|
|
55
|
+
});
|
|
56
|
+
let content = `**Tenant servers updated** (tenant=${result.tenant.slug}, id=${result.tenant.id})\n\n`;
|
|
57
|
+
content += `- Removed: ${result.removed.length}\n`;
|
|
58
|
+
content += `- Skipped: ${result.skipped.length}\n`;
|
|
59
|
+
content += `- Unresolved identifiers: ${result.unresolved_identifiers.length}\n`;
|
|
60
|
+
if (result.removed.length > 0) {
|
|
61
|
+
content += `\n**Removed:**\n`;
|
|
62
|
+
for (const item of result.removed) {
|
|
63
|
+
const assocPart = item.association_id != null ? `, association_id=${item.association_id}` : '';
|
|
64
|
+
content += `- ${item.mcp_server_slug} (server_id=${item.mcp_server_id}${assocPart}, outcome=${item.outcome})\n`;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (result.skipped.length > 0) {
|
|
68
|
+
content += `\n**Skipped:**\n`;
|
|
69
|
+
for (const item of result.skipped) {
|
|
70
|
+
const label = item.mcp_server_slug || item.mcp_server_id || item.association_id;
|
|
71
|
+
content += `- ${label} — reason: ${item.reason}\n`;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (result.unresolved_identifiers.length > 0) {
|
|
75
|
+
content += `\n**Unresolved identifiers:** ${result.unresolved_identifiers.join(', ')}\n`;
|
|
76
|
+
}
|
|
77
|
+
return { content: [{ type: 'text', text: content }] };
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
return {
|
|
81
|
+
content: [
|
|
82
|
+
{
|
|
83
|
+
type: 'text',
|
|
84
|
+
text: `Error removing tenant servers: ${error instanceof Error ? error.message : String(error)}`,
|
|
85
|
+
},
|
|
86
|
+
],
|
|
87
|
+
isError: true,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
2
|
+
import type { ClientFactory } from '../server.js';
|
|
3
|
+
export declare function revokeApiKey(server: Server, clientFactory: ClientFactory): {
|
|
4
|
+
name: string;
|
|
5
|
+
description: string;
|
|
6
|
+
inputSchema: {
|
|
7
|
+
type: string;
|
|
8
|
+
properties: {
|
|
9
|
+
api_key_id: {
|
|
10
|
+
type: string;
|
|
11
|
+
description: "The numeric ID of the API key to revoke (the `id` field returned by `create_api_key`).";
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
required: string[];
|
|
15
|
+
};
|
|
16
|
+
handler: (args: unknown) => Promise<{
|
|
17
|
+
content: {
|
|
18
|
+
type: string;
|
|
19
|
+
text: string;
|
|
20
|
+
}[];
|
|
21
|
+
isError: boolean;
|
|
22
|
+
} | {
|
|
23
|
+
content: {
|
|
24
|
+
type: string;
|
|
25
|
+
text: string;
|
|
26
|
+
}[];
|
|
27
|
+
isError?: undefined;
|
|
28
|
+
}>;
|
|
29
|
+
};
|
|
30
|
+
//# sourceMappingURL=revoke-api-key.d.ts.map
|