@elliotding/ai-agent-mcp 0.1.1 → 0.1.3
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/dist/api/client.d.ts +37 -8
- package/dist/api/client.d.ts.map +1 -1
- package/dist/api/client.js +68 -40
- package/dist/api/client.js.map +1 -1
- package/dist/tools/manage-subscription.d.ts +4 -0
- package/dist/tools/manage-subscription.d.ts.map +1 -1
- package/dist/tools/manage-subscription.js +20 -6
- package/dist/tools/manage-subscription.js.map +1 -1
- package/dist/tools/search-resources.d.ts +4 -0
- package/dist/tools/search-resources.d.ts.map +1 -1
- package/dist/tools/search-resources.js +7 -1
- package/dist/tools/search-resources.js.map +1 -1
- package/dist/tools/sync-resources.d.ts +4 -0
- package/dist/tools/sync-resources.d.ts.map +1 -1
- package/dist/tools/sync-resources.js +9 -2
- package/dist/tools/sync-resources.js.map +1 -1
- package/dist/tools/upload-resource.d.ts +4 -0
- package/dist/tools/upload-resource.d.ts.map +1 -1
- package/dist/tools/upload-resource.js +120 -23
- package/dist/tools/upload-resource.js.map +1 -1
- package/dist/types/tools.d.ts +17 -2
- package/dist/types/tools.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/api/client.ts +142 -71
- package/src/tools/manage-subscription.ts +25 -6
- package/src/tools/search-resources.ts +15 -5
- package/src/tools/sync-resources.ts +13 -5
- package/src/tools/upload-resource.ts +157 -31
- package/src/types/tools.ts +17 -2
|
@@ -21,12 +21,80 @@ import { MCPServerError, createValidationError } from '../types/errors';
|
|
|
21
21
|
import type { UploadResourceParams, UploadResourceResult, ToolResult, FileEntry } from '../types/tools';
|
|
22
22
|
import type { ResourceType } from '../types/resources';
|
|
23
23
|
|
|
24
|
+
type ResourceCategory = 'command' | 'skill' | 'rule' | 'mcp';
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Infer the resource type from the uploaded file list ONLY when the user has
|
|
28
|
+
* not explicitly stated a type. If the user declared a type, that always wins.
|
|
29
|
+
*
|
|
30
|
+
* Auto-detection rules (in priority order, applied only when declaredType is absent):
|
|
31
|
+
* 1. Any file named "mcp-config.json" → mcp
|
|
32
|
+
* 2. Any file named "SKILL.md" → skill
|
|
33
|
+
* 3. Single file ending with ".mdc" → rule
|
|
34
|
+
* 4. Single file ending with ".md" → command
|
|
35
|
+
* 5. Cannot determine → throw validation error
|
|
36
|
+
*/
|
|
37
|
+
function inferResourceType(
|
|
38
|
+
declaredType: ResourceCategory | undefined,
|
|
39
|
+
files: FileEntry[]
|
|
40
|
+
): ResourceCategory {
|
|
41
|
+
// User explicitly specified the type — honour it unconditionally.
|
|
42
|
+
if (declaredType) return declaredType;
|
|
43
|
+
|
|
44
|
+
const names = files.map((f) => path.basename(f.path).toLowerCase());
|
|
45
|
+
|
|
46
|
+
if (names.includes('mcp-config.json')) return 'mcp';
|
|
47
|
+
if (names.includes('skill.md')) return 'skill';
|
|
48
|
+
if (files.length === 1) {
|
|
49
|
+
if (names[0]!.endsWith('.mdc')) return 'rule';
|
|
50
|
+
if (names[0]!.endsWith('.md')) return 'command';
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
throw createValidationError(
|
|
54
|
+
'type',
|
|
55
|
+
'required',
|
|
56
|
+
'Cannot auto-detect the resource type from the provided files. ' +
|
|
57
|
+
'Please specify "type" explicitly: "command" (single .md), "skill" (contains SKILL.md), ' +
|
|
58
|
+
'"rule" (single .mdc), or "mcp" (contains mcp-config.json).'
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Derive a human-readable resource name from the primary file in the upload list.
|
|
64
|
+
* The original filename (without extension) is used as-is — never renamed.
|
|
65
|
+
*
|
|
66
|
+
* - Single-file upload: strip extension from the filename.
|
|
67
|
+
* "code-review.md" → "code-review"
|
|
68
|
+
* "csp-agent.mdc" → "csp-agent"
|
|
69
|
+
* - Multi-file upload: use the top-level directory name when the first
|
|
70
|
+
* path contains a directory component.
|
|
71
|
+
* "code-review/SKILL.md" → "code-review"
|
|
72
|
+
* Falls back to the first file's base name otherwise.
|
|
73
|
+
*
|
|
74
|
+
* Returns undefined when the files array is empty (caller should error).
|
|
75
|
+
*/
|
|
76
|
+
function deriveNameFromFiles(files: FileEntry[]): string | undefined {
|
|
77
|
+
if (!files || files.length === 0) return undefined;
|
|
78
|
+
|
|
79
|
+
const first = files[0]!.path;
|
|
80
|
+
|
|
81
|
+
// For paths like "code-review/SKILL.md", use the top-level directory.
|
|
82
|
+
const dir = path.dirname(first);
|
|
83
|
+
if (dir && dir !== '.') return dir;
|
|
84
|
+
|
|
85
|
+
// Strip extension from bare filename.
|
|
86
|
+
const base = path.basename(first, path.extname(first));
|
|
87
|
+
return base || undefined;
|
|
88
|
+
}
|
|
89
|
+
|
|
24
90
|
/**
|
|
25
91
|
* Validate and return the files[] array.
|
|
26
92
|
* Each entry path must be a relative path with no traversal.
|
|
27
93
|
* For MCP resources, mcp-config.json must be present.
|
|
94
|
+
*
|
|
95
|
+
* @param resolvedType The already-inferred resource type (never undefined here).
|
|
28
96
|
*/
|
|
29
|
-
function collectFiles(typedParams: UploadResourceParams): FileEntry[] {
|
|
97
|
+
function collectFiles(typedParams: UploadResourceParams, resolvedType: string): FileEntry[] {
|
|
30
98
|
if (!typedParams.files || typedParams.files.length === 0) {
|
|
31
99
|
throw createValidationError(
|
|
32
100
|
'files',
|
|
@@ -48,17 +116,34 @@ function collectFiles(typedParams: UploadResourceParams): FileEntry[] {
|
|
|
48
116
|
|
|
49
117
|
// MCP resources must include mcp-config.json so the client can auto-register
|
|
50
118
|
// the server in ~/.cursor/mcp.json after sync_resources installs it.
|
|
51
|
-
if (
|
|
119
|
+
if (resolvedType === 'mcp') {
|
|
52
120
|
const hasMcpConfig = typedParams.files.some(
|
|
53
121
|
(f) => path.basename(f.path) === 'mcp-config.json'
|
|
54
122
|
);
|
|
55
123
|
if (!hasMcpConfig) {
|
|
124
|
+
// Look for other files that might already describe the server configuration
|
|
125
|
+
// (e.g. pyproject.toml, package.json, README.md, config.json, server.py).
|
|
126
|
+
// If found, surface a targeted hint so the user knows exactly what to create.
|
|
127
|
+
const configHints = typedParams.files
|
|
128
|
+
.map((f) => path.basename(f.path))
|
|
129
|
+
.filter((n) =>
|
|
130
|
+
/\.(toml|yaml|yml|json|cfg|ini|conf|py|js|ts|md)$/i.test(n) &&
|
|
131
|
+
n !== 'mcp-config.json'
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
const hintNote =
|
|
135
|
+
configHints.length > 0
|
|
136
|
+
? `\nFound related files (${configHints.join(', ')}) that may already describe the server ` +
|
|
137
|
+
`configuration — please create "mcp-config.json" based on those files.`
|
|
138
|
+
: '';
|
|
139
|
+
|
|
56
140
|
throw createValidationError(
|
|
57
141
|
'files',
|
|
58
142
|
'required',
|
|
59
143
|
'MCP resources must include a "mcp-config.json" file. ' +
|
|
60
|
-
'This file tells the client how to start the MCP server after installation.
|
|
61
|
-
|
|
144
|
+
'This file tells the client how to start the MCP server after installation.' +
|
|
145
|
+
hintNote +
|
|
146
|
+
'\n\nRequired format:\n' +
|
|
62
147
|
'{\n' +
|
|
63
148
|
' "name": "<server-name>",\n' +
|
|
64
149
|
' "command": "python3", // or "node", "uvx", etc.\n' +
|
|
@@ -79,11 +164,26 @@ export async function uploadResource(params: unknown): Promise<ToolResult<Upload
|
|
|
79
164
|
logger.info({ tool: 'upload_resource', params }, 'upload_resource called');
|
|
80
165
|
|
|
81
166
|
try {
|
|
82
|
-
const
|
|
83
|
-
const
|
|
84
|
-
const
|
|
85
|
-
const
|
|
86
|
-
|
|
167
|
+
const resourceId = typedParams.resource_id;
|
|
168
|
+
const userToken = typedParams.user_token;
|
|
169
|
+
const targetSource = typedParams.target_source ?? 'csp';
|
|
170
|
+
const force = (typedParams as any).force || false;
|
|
171
|
+
|
|
172
|
+
// User-declared type always wins; auto-detect only when omitted.
|
|
173
|
+
const resourceType = inferResourceType(typedParams.type, typedParams.files);
|
|
174
|
+
|
|
175
|
+
// Name: explicit user value > derived from filename (no extension).
|
|
176
|
+
// Never fall back to resource_id — that is an internal identifier, not a name.
|
|
177
|
+
const derivedName = typedParams.name ?? deriveNameFromFiles(typedParams.files);
|
|
178
|
+
if (!derivedName) {
|
|
179
|
+
throw createValidationError(
|
|
180
|
+
'name',
|
|
181
|
+
'required',
|
|
182
|
+
'Could not determine a resource name from the provided files. ' +
|
|
183
|
+
'Please provide a "name" field explicitly.'
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
const resourceName = derivedName;
|
|
87
187
|
|
|
88
188
|
logger.info({ resourceId, resourceType, targetSource }, 'Upload target resolved');
|
|
89
189
|
|
|
@@ -124,25 +224,28 @@ export async function uploadResource(params: unknown): Promise<ToolResult<Upload
|
|
|
124
224
|
}
|
|
125
225
|
|
|
126
226
|
// ========== Step 4: Collect files ==========
|
|
127
|
-
const fileEntries = collectFiles(typedParams);
|
|
227
|
+
const fileEntries = collectFiles(typedParams, resourceType);
|
|
128
228
|
logger.info({ resourceId, fileCount: fileEntries.length }, 'Files collected for upload');
|
|
129
229
|
|
|
130
230
|
// ========== Step 5: Call CSP API — upload (staging) ==========
|
|
131
231
|
logger.info({ resourceName, resourceType, targetSource }, 'Calling CSP upload API...');
|
|
132
|
-
const uploadResp = await apiClient.uploadResourceFiles(
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
232
|
+
const uploadResp = await apiClient.uploadResourceFiles(
|
|
233
|
+
{
|
|
234
|
+
type: resourceType,
|
|
235
|
+
name: resourceName,
|
|
236
|
+
files: fileEntries,
|
|
237
|
+
target_source: targetSource,
|
|
238
|
+
force,
|
|
239
|
+
},
|
|
240
|
+
userToken
|
|
241
|
+
);
|
|
139
242
|
|
|
140
243
|
const uploadId = uploadResp.upload_id;
|
|
141
244
|
logger.info({ uploadId, expiresAt: uploadResp.expires_at }, 'Upload staged successfully');
|
|
142
245
|
|
|
143
246
|
// ========== Step 6: Call CSP API — finalize (Git commit) ==========
|
|
144
247
|
logger.info({ uploadId }, 'Calling CSP finalize API...');
|
|
145
|
-
const finalizeResp = await apiClient.finalizeResourceUpload(uploadId, typedParams.message);
|
|
248
|
+
const finalizeResp = await apiClient.finalizeResourceUpload(uploadId, typedParams.message, userToken);
|
|
146
249
|
|
|
147
250
|
const finalResourceId = finalizeResp.resource_id;
|
|
148
251
|
const version = finalizeResp.version ?? '1.0.0';
|
|
@@ -182,15 +285,31 @@ export const uploadResourceTool = {
|
|
|
182
285
|
name: 'upload_resource',
|
|
183
286
|
description:
|
|
184
287
|
'Upload a new AI resource (command, skill, rule, or mcp) to a CSP source repository. ' +
|
|
185
|
-
'The user selects files from
|
|
186
|
-
'ALWAYS confirm
|
|
187
|
-
|
|
188
|
-
'
|
|
189
|
-
'
|
|
190
|
-
'
|
|
191
|
-
'
|
|
192
|
-
'
|
|
193
|
-
'
|
|
288
|
+
'The user selects files from their local machine — read each file and pass its content in files[]. ' +
|
|
289
|
+
'ALWAYS confirm the target source repo with the user (e.g. "csp" (default) or "client-sdk-ai-hub"). ' +
|
|
290
|
+
|
|
291
|
+
'\n\nResource type rules:\n' +
|
|
292
|
+
' • If the user explicitly states the type, use it as-is — no overriding.\n' +
|
|
293
|
+
' • If the user does NOT state a type, auto-detect from file structure:\n' +
|
|
294
|
+
' - Contains mcp-config.json → type="mcp"\n' +
|
|
295
|
+
' - Contains SKILL.md → type="skill"\n' +
|
|
296
|
+
' - Single .mdc file → type="rule"\n' +
|
|
297
|
+
' - Single .md file → type="command"\n' +
|
|
298
|
+
' • If the user says type="mcp" but mcp-config.json is missing, the tool will\n' +
|
|
299
|
+
' return an error with a hint about creating mcp-config.json.\n' +
|
|
300
|
+
|
|
301
|
+
'\n\nResource name rules:\n' +
|
|
302
|
+
' • If the user provides a name, use it.\n' +
|
|
303
|
+
' • Otherwise derive the name from the filename WITHOUT its extension.\n' +
|
|
304
|
+
' Keep the original filename — NEVER rename files (e.g. do not rename any .md file).\n' +
|
|
305
|
+
' Examples: "code-review.md" → name="code-review"; "code-review/SKILL.md" → name="code-review".\n' +
|
|
306
|
+
|
|
307
|
+
'\n\nPass files[] — an array of {path, content} entries. ' +
|
|
308
|
+
'path must be the original filename as-is (relative, no path traversal). ' +
|
|
309
|
+
'No restriction on file extensions — mcp packages may include .py, .js, package.json, etc.\n' +
|
|
310
|
+
|
|
311
|
+
'\nIMPORTANT: Always read the CSP_API_TOKEN from the user\'s environment and pass it as user_token ' +
|
|
312
|
+
'so that each user\'s API calls use their own identity.',
|
|
194
313
|
inputSchema: {
|
|
195
314
|
type: 'object' as const,
|
|
196
315
|
properties: {
|
|
@@ -206,11 +325,11 @@ export const uploadResourceTool = {
|
|
|
206
325
|
type: 'string',
|
|
207
326
|
enum: ['command', 'skill', 'rule', 'mcp'],
|
|
208
327
|
description:
|
|
209
|
-
'Resource category —
|
|
328
|
+
'Resource category. Auto-detected from file structure — only set explicitly when detection is ambiguous. ' +
|
|
210
329
|
'command: single .md slash-command file; ' +
|
|
211
|
-
'skill: directory
|
|
330
|
+
'skill: directory or file set containing SKILL.md; ' +
|
|
212
331
|
'rule: single .mdc Cursor rule file; ' +
|
|
213
|
-
'mcp: MCP server package — MUST include mcp-config.json
|
|
332
|
+
'mcp: MCP server package — MUST include mcp-config.json.',
|
|
214
333
|
},
|
|
215
334
|
message: {
|
|
216
335
|
type: 'string',
|
|
@@ -251,8 +370,15 @@ export const uploadResourceTool = {
|
|
|
251
370
|
description: 'Overwrite if a resource with the same name already exists',
|
|
252
371
|
default: false,
|
|
253
372
|
},
|
|
373
|
+
user_token: {
|
|
374
|
+
type: 'string',
|
|
375
|
+
description:
|
|
376
|
+
'CSP API token for the current user. Read this from the CSP_API_TOKEN environment ' +
|
|
377
|
+
'variable configured in the user\'s mcp.json. When provided, this token is used ' +
|
|
378
|
+
'for all CSP API calls in this request instead of the server-level fallback token.',
|
|
379
|
+
},
|
|
254
380
|
},
|
|
255
|
-
required: ['resource_id', '
|
|
381
|
+
required: ['resource_id', 'message', 'files'],
|
|
256
382
|
},
|
|
257
383
|
handler: uploadResource,
|
|
258
384
|
};
|
package/src/types/tools.ts
CHANGED
|
@@ -35,6 +35,12 @@ export interface SyncResourcesParams {
|
|
|
35
35
|
mode?: 'check' | 'incremental' | 'full';
|
|
36
36
|
scope?: 'global' | 'workspace' | 'all';
|
|
37
37
|
types?: string[];
|
|
38
|
+
/**
|
|
39
|
+
* CSP API token from the user's mcp.json env configuration.
|
|
40
|
+
* Overrides the server-level fallback token so that each user
|
|
41
|
+
* makes API calls with their own identity.
|
|
42
|
+
*/
|
|
43
|
+
user_token?: string;
|
|
38
44
|
}
|
|
39
45
|
|
|
40
46
|
export interface McpSetupItem {
|
|
@@ -83,6 +89,8 @@ export interface ManageSubscriptionParams {
|
|
|
83
89
|
auto_sync?: boolean;
|
|
84
90
|
scope?: 'global' | 'workspace';
|
|
85
91
|
notify?: boolean;
|
|
92
|
+
/** CSP API token from the user's mcp.json env configuration. */
|
|
93
|
+
user_token?: string;
|
|
86
94
|
}
|
|
87
95
|
|
|
88
96
|
export interface ManageSubscriptionResult {
|
|
@@ -106,6 +114,8 @@ export interface SearchResourcesParams {
|
|
|
106
114
|
team?: string;
|
|
107
115
|
type?: string;
|
|
108
116
|
keyword: string;
|
|
117
|
+
/** CSP API token from the user's mcp.json env configuration. */
|
|
118
|
+
user_token?: string;
|
|
109
119
|
}
|
|
110
120
|
|
|
111
121
|
export interface SearchResourcesResult {
|
|
@@ -131,9 +141,10 @@ export interface FileEntry {
|
|
|
131
141
|
|
|
132
142
|
export interface UploadResourceParams {
|
|
133
143
|
resource_id: string;
|
|
134
|
-
|
|
144
|
+
/** Resource category. Optional — auto-detected from file structure when omitted. */
|
|
145
|
+
type?: 'command' | 'skill' | 'rule' | 'mcp';
|
|
135
146
|
message: string;
|
|
136
|
-
/** Human-readable resource name sent to the CSP API. Defaults to
|
|
147
|
+
/** Human-readable resource name sent to the CSP API. Defaults to the primary file name (without extension). */
|
|
137
148
|
name?: string;
|
|
138
149
|
/** Target source repo from ai-resources-config.json (e.g. "csp", "client-sdk-ai-hub"). Defaults to default_source. */
|
|
139
150
|
target_source?: string;
|
|
@@ -147,6 +158,8 @@ export interface UploadResourceParams {
|
|
|
147
158
|
// ---- Optional fields ----
|
|
148
159
|
title?: string;
|
|
149
160
|
metadata?: Record<string, unknown>;
|
|
161
|
+
/** CSP API token from the user's mcp.json env configuration. */
|
|
162
|
+
user_token?: string;
|
|
150
163
|
}
|
|
151
164
|
|
|
152
165
|
export interface UploadResourceResult {
|
|
@@ -161,6 +174,8 @@ export interface UploadResourceResult {
|
|
|
161
174
|
export interface UninstallResourceParams {
|
|
162
175
|
resource_id_or_name: string;
|
|
163
176
|
remove_from_account?: boolean;
|
|
177
|
+
/** CSP API token from the user's mcp.json env configuration. */
|
|
178
|
+
user_token?: string;
|
|
164
179
|
}
|
|
165
180
|
|
|
166
181
|
export interface UninstallResourceResult {
|