@elliotding/ai-agent-mcp 0.1.2 → 0.1.4

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 (104) hide show
  1. package/.prompt-cache/cmd-cmd-client-sdk-ai-hub-generate-testcase.md +101 -0
  2. package/.prompt-cache/cmd-cmd-client-sdk-ai-hub-submit_zct_job.md +158 -0
  3. package/.prompt-cache/skill-skill-client-sdk-ai-hub-analyze-conf-status.md +311 -0
  4. package/.prompt-cache/skill-skill-client-sdk-ai-hub-analyze-sdk-log.md +64 -0
  5. package/.prompt-cache/skill-skill-client-sdk-ai-hub-analyze-zmb-log-errors.md +84 -0
  6. package/ai-resource-telemetry.json +22 -0
  7. package/dist/api/client.d.ts +76 -8
  8. package/dist/api/client.d.ts.map +1 -1
  9. package/dist/api/client.js +86 -40
  10. package/dist/api/client.js.map +1 -1
  11. package/dist/auth/permissions.d.ts.map +1 -1
  12. package/dist/auth/permissions.js +6 -0
  13. package/dist/auth/permissions.js.map +1 -1
  14. package/dist/config/index.d.ts +6 -1
  15. package/dist/config/index.d.ts.map +1 -1
  16. package/dist/config/index.js +1 -3
  17. package/dist/config/index.js.map +1 -1
  18. package/dist/index.js +12 -0
  19. package/dist/index.js.map +1 -1
  20. package/dist/prompts/cache.d.ts +69 -0
  21. package/dist/prompts/cache.d.ts.map +1 -0
  22. package/dist/prompts/cache.js +163 -0
  23. package/dist/prompts/cache.js.map +1 -0
  24. package/dist/prompts/generator.d.ts +49 -0
  25. package/dist/prompts/generator.d.ts.map +1 -0
  26. package/dist/prompts/generator.js +158 -0
  27. package/dist/prompts/generator.js.map +1 -0
  28. package/dist/prompts/index.d.ts +13 -0
  29. package/dist/prompts/index.d.ts.map +1 -0
  30. package/dist/prompts/index.js +24 -0
  31. package/dist/prompts/index.js.map +1 -0
  32. package/dist/prompts/manager.d.ts +106 -0
  33. package/dist/prompts/manager.d.ts.map +1 -0
  34. package/dist/prompts/manager.js +263 -0
  35. package/dist/prompts/manager.js.map +1 -0
  36. package/dist/server/http.d.ts.map +1 -1
  37. package/dist/server/http.js +61 -17
  38. package/dist/server/http.js.map +1 -1
  39. package/dist/server.d.ts.map +1 -1
  40. package/dist/server.js +43 -0
  41. package/dist/server.js.map +1 -1
  42. package/dist/telemetry/index.d.ts +3 -0
  43. package/dist/telemetry/index.d.ts.map +1 -0
  44. package/dist/telemetry/index.js +7 -0
  45. package/dist/telemetry/index.js.map +1 -0
  46. package/dist/telemetry/manager.d.ts +149 -0
  47. package/dist/telemetry/manager.d.ts.map +1 -0
  48. package/dist/telemetry/manager.js +368 -0
  49. package/dist/telemetry/manager.js.map +1 -0
  50. package/dist/tools/index.d.ts +1 -0
  51. package/dist/tools/index.d.ts.map +1 -1
  52. package/dist/tools/index.js +1 -0
  53. package/dist/tools/index.js.map +1 -1
  54. package/dist/tools/manage-subscription.d.ts +4 -0
  55. package/dist/tools/manage-subscription.d.ts.map +1 -1
  56. package/dist/tools/manage-subscription.js +36 -7
  57. package/dist/tools/manage-subscription.js.map +1 -1
  58. package/dist/tools/search-resources.d.ts +4 -0
  59. package/dist/tools/search-resources.d.ts.map +1 -1
  60. package/dist/tools/search-resources.js +6 -1
  61. package/dist/tools/search-resources.js.map +1 -1
  62. package/dist/tools/sync-resources.d.ts +13 -4
  63. package/dist/tools/sync-resources.d.ts.map +1 -1
  64. package/dist/tools/sync-resources.js +127 -6
  65. package/dist/tools/sync-resources.js.map +1 -1
  66. package/dist/tools/track-usage.d.ts +63 -0
  67. package/dist/tools/track-usage.d.ts.map +1 -0
  68. package/dist/tools/track-usage.js +90 -0
  69. package/dist/tools/track-usage.js.map +1 -0
  70. package/dist/tools/uninstall-resource.d.ts.map +1 -1
  71. package/dist/tools/uninstall-resource.js +53 -3
  72. package/dist/tools/uninstall-resource.js.map +1 -1
  73. package/dist/tools/upload-resource.d.ts +4 -0
  74. package/dist/tools/upload-resource.d.ts.map +1 -1
  75. package/dist/tools/upload-resource.js +164 -23
  76. package/dist/tools/upload-resource.js.map +1 -1
  77. package/dist/types/tools.d.ts +17 -2
  78. package/dist/types/tools.d.ts.map +1 -1
  79. package/dist/utils/cursor-paths.d.ts +10 -0
  80. package/dist/utils/cursor-paths.d.ts.map +1 -1
  81. package/dist/utils/cursor-paths.js +13 -0
  82. package/dist/utils/cursor-paths.js.map +1 -1
  83. package/package.json +1 -1
  84. package/src/api/client.ts +191 -71
  85. package/src/auth/permissions.ts +6 -0
  86. package/src/config/index.ts +11 -5
  87. package/src/index.ts +18 -0
  88. package/src/prompts/cache.ts +140 -0
  89. package/src/prompts/generator.ts +142 -0
  90. package/src/prompts/index.ts +20 -0
  91. package/src/prompts/manager.ts +342 -0
  92. package/src/server/http.ts +69 -17
  93. package/src/server.ts +13 -0
  94. package/src/telemetry/index.ts +10 -0
  95. package/src/telemetry/manager.ts +419 -0
  96. package/src/tools/index.ts +1 -0
  97. package/src/tools/manage-subscription.ts +41 -7
  98. package/src/tools/search-resources.ts +14 -6
  99. package/src/tools/sync-resources.ts +141 -9
  100. package/src/tools/track-usage.ts +113 -0
  101. package/src/tools/uninstall-resource.ts +62 -4
  102. package/src/tools/upload-resource.ts +204 -31
  103. package/src/types/tools.ts +17 -2
  104. package/src/utils/cursor-paths.ts +13 -0
@@ -18,15 +18,97 @@ import { logger, logToolCall } from '../utils/logger';
18
18
  import { apiClient } from '../api/client';
19
19
  import { resourceLoader } from '../resources';
20
20
  import { MCPServerError, createValidationError } from '../types/errors';
21
+ import { promptManager } from '../prompts/index.js';
21
22
  import type { UploadResourceParams, UploadResourceResult, ToolResult, FileEntry } from '../types/tools';
22
23
  import type { ResourceType } from '../types/resources';
23
24
 
25
+ type ResourceCategory = 'command' | 'skill' | 'rule' | 'mcp';
26
+
27
+ /** Extract the `description` field from YAML frontmatter (--- ... ---) in Markdown content. */
28
+ function extractFrontmatterDescription(content: string): string | undefined {
29
+ if (!content.startsWith('---')) return undefined;
30
+ const end = content.indexOf('\n---', 3);
31
+ if (end === -1) return undefined;
32
+ const frontmatter = content.slice(3, end);
33
+ for (const line of frontmatter.split('\n')) {
34
+ const match = /^description:\s*(.+)$/.exec(line.trim());
35
+ if (match) return match[1]!.trim().replace(/^['"]|['"]$/g, '');
36
+ }
37
+ return undefined;
38
+ }
39
+
40
+ /**
41
+ * Infer the resource type from the uploaded file list ONLY when the user has
42
+ * not explicitly stated a type. If the user declared a type, that always wins.
43
+ *
44
+ * Auto-detection rules (in priority order, applied only when declaredType is absent):
45
+ * 1. Any file named "mcp-config.json" → mcp
46
+ * 2. Any file named "SKILL.md" → skill
47
+ * 3. Single file ending with ".mdc" → rule
48
+ * 4. Single file ending with ".md" → command
49
+ * 5. Cannot determine → throw validation error
50
+ */
51
+ function inferResourceType(
52
+ declaredType: ResourceCategory | undefined,
53
+ files: FileEntry[]
54
+ ): ResourceCategory {
55
+ // User explicitly specified the type — honour it unconditionally.
56
+ if (declaredType) return declaredType;
57
+
58
+ const names = files.map((f) => path.basename(f.path).toLowerCase());
59
+
60
+ if (names.includes('mcp-config.json')) return 'mcp';
61
+ if (names.includes('skill.md')) return 'skill';
62
+ if (files.length === 1) {
63
+ if (names[0]!.endsWith('.mdc')) return 'rule';
64
+ if (names[0]!.endsWith('.md')) return 'command';
65
+ }
66
+
67
+ throw createValidationError(
68
+ 'type',
69
+ 'required',
70
+ 'Cannot auto-detect the resource type from the provided files. ' +
71
+ 'Please specify "type" explicitly: "command" (single .md), "skill" (contains SKILL.md), ' +
72
+ '"rule" (single .mdc), or "mcp" (contains mcp-config.json).'
73
+ );
74
+ }
75
+
76
+ /**
77
+ * Derive a human-readable resource name from the primary file in the upload list.
78
+ * The original filename (without extension) is used as-is — never renamed.
79
+ *
80
+ * - Single-file upload: strip extension from the filename.
81
+ * "code-review.md" → "code-review"
82
+ * "csp-agent.mdc" → "csp-agent"
83
+ * - Multi-file upload: use the top-level directory name when the first
84
+ * path contains a directory component.
85
+ * "code-review/SKILL.md" → "code-review"
86
+ * Falls back to the first file's base name otherwise.
87
+ *
88
+ * Returns undefined when the files array is empty (caller should error).
89
+ */
90
+ function deriveNameFromFiles(files: FileEntry[]): string | undefined {
91
+ if (!files || files.length === 0) return undefined;
92
+
93
+ const first = files[0]!.path;
94
+
95
+ // For paths like "code-review/SKILL.md", use the top-level directory.
96
+ const dir = path.dirname(first);
97
+ if (dir && dir !== '.') return dir;
98
+
99
+ // Strip extension from bare filename.
100
+ const base = path.basename(first, path.extname(first));
101
+ return base || undefined;
102
+ }
103
+
24
104
  /**
25
105
  * Validate and return the files[] array.
26
106
  * Each entry path must be a relative path with no traversal.
27
107
  * For MCP resources, mcp-config.json must be present.
108
+ *
109
+ * @param resolvedType The already-inferred resource type (never undefined here).
28
110
  */
29
- function collectFiles(typedParams: UploadResourceParams): FileEntry[] {
111
+ function collectFiles(typedParams: UploadResourceParams, resolvedType: string): FileEntry[] {
30
112
  if (!typedParams.files || typedParams.files.length === 0) {
31
113
  throw createValidationError(
32
114
  'files',
@@ -48,17 +130,34 @@ function collectFiles(typedParams: UploadResourceParams): FileEntry[] {
48
130
 
49
131
  // MCP resources must include mcp-config.json so the client can auto-register
50
132
  // the server in ~/.cursor/mcp.json after sync_resources installs it.
51
- if (typedParams.type === 'mcp') {
133
+ if (resolvedType === 'mcp') {
52
134
  const hasMcpConfig = typedParams.files.some(
53
135
  (f) => path.basename(f.path) === 'mcp-config.json'
54
136
  );
55
137
  if (!hasMcpConfig) {
138
+ // Look for other files that might already describe the server configuration
139
+ // (e.g. pyproject.toml, package.json, README.md, config.json, server.py).
140
+ // If found, surface a targeted hint so the user knows exactly what to create.
141
+ const configHints = typedParams.files
142
+ .map((f) => path.basename(f.path))
143
+ .filter((n) =>
144
+ /\.(toml|yaml|yml|json|cfg|ini|conf|py|js|ts|md)$/i.test(n) &&
145
+ n !== 'mcp-config.json'
146
+ );
147
+
148
+ const hintNote =
149
+ configHints.length > 0
150
+ ? `\nFound related files (${configHints.join(', ')}) that may already describe the server ` +
151
+ `configuration — please create "mcp-config.json" based on those files.`
152
+ : '';
153
+
56
154
  throw createValidationError(
57
155
  'files',
58
156
  'required',
59
157
  'MCP resources must include a "mcp-config.json" file. ' +
60
- 'This file tells the client how to start the MCP server after installation. ' +
61
- 'Required format:\n' +
158
+ 'This file tells the client how to start the MCP server after installation.' +
159
+ hintNote +
160
+ '\n\nRequired format:\n' +
62
161
  '{\n' +
63
162
  ' "name": "<server-name>",\n' +
64
163
  ' "command": "python3", // or "node", "uvx", etc.\n' +
@@ -79,11 +178,26 @@ export async function uploadResource(params: unknown): Promise<ToolResult<Upload
79
178
  logger.info({ tool: 'upload_resource', params }, 'upload_resource called');
80
179
 
81
180
  try {
82
- const resourceType = typedParams.type;
83
- const resourceId = typedParams.resource_id;
84
- const resourceName = typedParams.name ?? resourceId; // API uses "name" field
85
- const targetSource = typedParams.target_source ?? 'csp'; // default to "csp" repo
86
- const force = (typedParams as any).force || false;
181
+ const resourceId = typedParams.resource_id;
182
+ const userToken = typedParams.user_token;
183
+ const targetSource = typedParams.target_source ?? 'csp';
184
+ const force = (typedParams as any).force || false;
185
+
186
+ // User-declared type always wins; auto-detect only when omitted.
187
+ const resourceType = inferResourceType(typedParams.type, typedParams.files);
188
+
189
+ // Name: explicit user value > derived from filename (no extension).
190
+ // Never fall back to resource_id — that is an internal identifier, not a name.
191
+ const derivedName = typedParams.name ?? deriveNameFromFiles(typedParams.files);
192
+ if (!derivedName) {
193
+ throw createValidationError(
194
+ 'name',
195
+ 'required',
196
+ 'Could not determine a resource name from the provided files. ' +
197
+ 'Please provide a "name" field explicitly.'
198
+ );
199
+ }
200
+ const resourceName = derivedName;
87
201
 
88
202
  logger.info({ resourceId, resourceType, targetSource }, 'Upload target resolved');
89
203
 
@@ -124,25 +238,28 @@ export async function uploadResource(params: unknown): Promise<ToolResult<Upload
124
238
  }
125
239
 
126
240
  // ========== Step 4: Collect files ==========
127
- const fileEntries = collectFiles(typedParams);
241
+ const fileEntries = collectFiles(typedParams, resourceType);
128
242
  logger.info({ resourceId, fileCount: fileEntries.length }, 'Files collected for upload');
129
243
 
130
244
  // ========== Step 5: Call CSP API — upload (staging) ==========
131
245
  logger.info({ resourceName, resourceType, targetSource }, 'Calling CSP upload API...');
132
- const uploadResp = await apiClient.uploadResourceFiles({
133
- type: resourceType,
134
- name: resourceName,
135
- files: fileEntries,
136
- target_source: targetSource,
137
- force,
138
- });
246
+ const uploadResp = await apiClient.uploadResourceFiles(
247
+ {
248
+ type: resourceType,
249
+ name: resourceName,
250
+ files: fileEntries,
251
+ target_source: targetSource,
252
+ force,
253
+ },
254
+ userToken
255
+ );
139
256
 
140
257
  const uploadId = uploadResp.upload_id;
141
258
  logger.info({ uploadId, expiresAt: uploadResp.expires_at }, 'Upload staged successfully');
142
259
 
143
260
  // ========== Step 6: Call CSP API — finalize (Git commit) ==========
144
261
  logger.info({ uploadId }, 'Calling CSP finalize API...');
145
- const finalizeResp = await apiClient.finalizeResourceUpload(uploadId, typedParams.message);
262
+ const finalizeResp = await apiClient.finalizeResourceUpload(uploadId, typedParams.message, userToken);
146
263
 
147
264
  const finalResourceId = finalizeResp.resource_id;
148
265
  const version = finalizeResp.version ?? '1.0.0';
@@ -151,6 +268,41 @@ export async function uploadResource(params: unknown): Promise<ToolResult<Upload
151
268
 
152
269
  logger.info({ finalResourceId, version, commitHash }, 'Upload finalized successfully');
153
270
 
271
+ // For Command / Skill: register the uploaded content as an MCP Prompt immediately,
272
+ // so the user can invoke it via slash-command without a full sync cycle.
273
+ if (resourceType === 'command' || resourceType === 'skill') {
274
+ try {
275
+ const primaryFile = resourceType === 'skill'
276
+ ? (fileEntries.find((f) => path.basename(f.path) === 'SKILL.md') ??
277
+ fileEntries.find((f) => f.path.endsWith('.md')) ??
278
+ fileEntries[0])
279
+ : (fileEntries.find((f) => path.basename(f.path).replace(/\.md$/, '') === resourceName) ??
280
+ fileEntries.find((f) => f.path.endsWith('.md')) ??
281
+ fileEntries[0]);
282
+
283
+ const rawContent = primaryFile?.content ?? '';
284
+ const team = (typedParams as any).team ?? 'general';
285
+ const frontmatterDesc = extractFrontmatterDescription(rawContent);
286
+ const description = frontmatterDesc ?? typedParams.message ?? resourceName;
287
+
288
+ await promptManager.registerPrompt({
289
+ resource_id: finalResourceId,
290
+ resource_type: resourceType as 'command' | 'skill',
291
+ resource_name: resourceName,
292
+ team,
293
+ description,
294
+ rawContent,
295
+ });
296
+ logger.info({ finalResourceId, resourceType }, 'MCP Prompt registered after upload');
297
+ } catch (promptErr) {
298
+ // Non-fatal: the resource is uploaded; Prompt registration is best-effort.
299
+ logger.warn(
300
+ { finalResourceId, error: (promptErr as Error).message },
301
+ 'MCP Prompt registration after upload failed (non-fatal)',
302
+ );
303
+ }
304
+ }
305
+
154
306
  const result: UploadResourceResult = {
155
307
  resource_id: finalResourceId,
156
308
  version,
@@ -182,15 +334,30 @@ export const uploadResourceTool = {
182
334
  name: 'upload_resource',
183
335
  description:
184
336
  'Upload a new AI resource (command, skill, rule, or mcp) to a CSP source repository. ' +
185
- 'The user selects files from anywhere on their local machine — read each file and pass its content in files[]. ' +
186
- 'ALWAYS confirm two things with the user before uploading: ' +
187
- '(1) the resource type (command/skill/rule/mcp), and ' +
188
- '(2) the target source repo (e.g. "csp" (default) or "client-sdk-ai-hub"). ' +
189
- 'The tool uses a two-step CSP API: first stages files and gets an upload_id, then finalizes the Git commit. ' +
190
- 'Pass files[] an array of {path, content} entries. ' +
191
- 'path is the filename the resource should be stored as (e.g. "csp-ai-agent.mdc", "SKILL.md"). ' +
192
- 'Single-file upload: one entry in files[]. Multi-file upload: multiple entries. ' +
193
- 'No restriction on file extensions — mcp packages may include .py, .js, package.json, etc.',
337
+ 'The user selects files from their local machine — read each file and pass its content in files[]. ' +
338
+ 'ALWAYS confirm the target source repo with the user (e.g. "csp" (default) or "client-sdk-ai-hub"). ' +
339
+
340
+ '\n\nResource type rules:\n' +
341
+ ' If the user explicitly states the type, use it as-is no overriding.\n' +
342
+ ' If the user does NOT state a type, auto-detect from file structure:\n' +
343
+ ' - Contains mcp-config.json → type="mcp"\n' +
344
+ ' - Contains SKILL.md → type="skill"\n' +
345
+ ' - Single .mdc file type="rule"\n' +
346
+ ' - Single .md file → type="command"\n' +
347
+ ' • If the user says type="mcp" but mcp-config.json is missing, the tool will\n' +
348
+ ' return an error with a hint about creating mcp-config.json.\n' +
349
+
350
+ '\n\nResource name rules:\n' +
351
+ ' • If the user provides a name, use it.\n' +
352
+ ' • Otherwise derive the name from the filename WITHOUT its extension.\n' +
353
+ ' Keep the original filename — NEVER rename files (e.g. do not rename any .md file).\n' +
354
+ ' Examples: "code-review.md" → name="code-review"; "code-review/SKILL.md" → name="code-review".\n' +
355
+
356
+ '\n\nPass files[] — an array of {path, content} entries. ' +
357
+ 'path must be the original filename as-is (relative, no path traversal). ' +
358
+ 'No restriction on file extensions — mcp packages may include .py, .js, package.json, etc.\n' +
359
+
360
+ '\nThe user_token is injected automatically by the server — do NOT read or pass it manually.',
194
361
  inputSchema: {
195
362
  type: 'object' as const,
196
363
  properties: {
@@ -206,11 +373,11 @@ export const uploadResourceTool = {
206
373
  type: 'string',
207
374
  enum: ['command', 'skill', 'rule', 'mcp'],
208
375
  description:
209
- 'Resource category — MUST be confirmed with the user. ' +
376
+ 'Resource category. Auto-detected from file structure only set explicitly when detection is ambiguous. ' +
210
377
  'command: single .md slash-command file; ' +
211
- 'skill: directory with SKILL.md + supporting files; ' +
378
+ 'skill: directory or file set containing SKILL.md; ' +
212
379
  'rule: single .mdc Cursor rule file; ' +
213
- 'mcp: MCP server package — MUST include mcp-config.json (defines command/args/env for auto-registration into ~/.cursor/mcp.json).',
380
+ 'mcp: MCP server package — MUST include mcp-config.json.',
214
381
  },
215
382
  message: {
216
383
  type: 'string',
@@ -251,8 +418,14 @@ export const uploadResourceTool = {
251
418
  description: 'Overwrite if a resource with the same name already exists',
252
419
  default: false,
253
420
  },
421
+ user_token: {
422
+ type: 'string',
423
+ description:
424
+ 'DO NOT set this field — it is automatically injected by the MCP server from ' +
425
+ 'the authenticated SSE connection. The server always provides the correct token.',
426
+ },
254
427
  },
255
- required: ['resource_id', 'type', 'message', 'files'],
428
+ required: ['resource_id', 'message', 'files'],
256
429
  },
257
430
  handler: uploadResource,
258
431
  };
@@ -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
- type: 'command' | 'skill' | 'rule' | 'mcp';
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 resource_id. */
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 {
@@ -81,3 +81,16 @@ export function getCursorTypeDir(resourceType: string): string {
81
81
  export function getCursorResourcePath(resourceType: string, resourceName: string): string {
82
82
  return path.join(getCursorTypeDir(resourceType), resourceName);
83
83
  }
84
+
85
+ /**
86
+ * Returns the path to the local AI resource telemetry file.
87
+ *
88
+ * Stored at the Cursor root level (not inside a resource-type subdirectory)
89
+ * so it persists independently of individual resource installs/uninstalls.
90
+ *
91
+ * macOS / Linux : ~/.cursor/ai-resource-telemetry.json
92
+ * Windows : %APPDATA%\Cursor\User\ai-resource-telemetry.json
93
+ */
94
+ export function getTelemetryFilePath(): string {
95
+ return path.join(getCursorRootDir(), 'ai-resource-telemetry.json');
96
+ }