@elliotding/ai-agent-mcp 0.1.24 → 0.1.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.
Files changed (233) hide show
  1. package/README.md +27 -0
  2. package/package.json +4 -1
  3. package/.prompt-cache/cmd-cmd-client-sdk-ai-hub-generate-testcase.md +0 -101
  4. package/.prompt-cache/cmd-cmd-client-sdk-ai-hub-submit_zct_job.md +0 -158
  5. package/.prompt-cache/skill-skill-client-sdk-ai-hub-analyze-conf-status.md +0 -311
  6. package/.prompt-cache/skill-skill-client-sdk-ai-hub-analyze-sdk-log.md +0 -64
  7. package/.prompt-cache/skill-skill-client-sdk-ai-hub-analyze-zmb-log-errors.md +0 -84
  8. package/ai-resource-telemetry.json +0 -40
  9. package/dist/api/cached-client.d.ts +0 -48
  10. package/dist/api/cached-client.d.ts.map +0 -1
  11. package/dist/api/cached-client.js +0 -126
  12. package/dist/api/cached-client.js.map +0 -1
  13. package/dist/api/client.d.ts +0 -281
  14. package/dist/api/client.d.ts.map +0 -1
  15. package/dist/api/client.js +0 -371
  16. package/dist/api/client.js.map +0 -1
  17. package/dist/auth/index.d.ts +0 -8
  18. package/dist/auth/index.d.ts.map +0 -1
  19. package/dist/auth/index.js +0 -26
  20. package/dist/auth/index.js.map +0 -1
  21. package/dist/auth/middleware.d.ts +0 -36
  22. package/dist/auth/middleware.d.ts.map +0 -1
  23. package/dist/auth/middleware.js +0 -194
  24. package/dist/auth/middleware.js.map +0 -1
  25. package/dist/auth/permissions.d.ts +0 -60
  26. package/dist/auth/permissions.d.ts.map +0 -1
  27. package/dist/auth/permissions.js +0 -262
  28. package/dist/auth/permissions.js.map +0 -1
  29. package/dist/auth/token-validator.d.ts +0 -52
  30. package/dist/auth/token-validator.d.ts.map +0 -1
  31. package/dist/auth/token-validator.js +0 -215
  32. package/dist/auth/token-validator.js.map +0 -1
  33. package/dist/cache/cache-manager.d.ts +0 -49
  34. package/dist/cache/cache-manager.d.ts.map +0 -1
  35. package/dist/cache/cache-manager.js +0 -191
  36. package/dist/cache/cache-manager.js.map +0 -1
  37. package/dist/cache/index.d.ts +0 -6
  38. package/dist/cache/index.d.ts.map +0 -1
  39. package/dist/cache/index.js +0 -12
  40. package/dist/cache/index.js.map +0 -1
  41. package/dist/cache/redis-client.d.ts +0 -45
  42. package/dist/cache/redis-client.d.ts.map +0 -1
  43. package/dist/cache/redis-client.js +0 -210
  44. package/dist/cache/redis-client.js.map +0 -1
  45. package/dist/config/constants.d.ts +0 -28
  46. package/dist/config/constants.d.ts.map +0 -1
  47. package/dist/config/constants.js +0 -31
  48. package/dist/config/constants.js.map +0 -1
  49. package/dist/config/index.d.ts +0 -71
  50. package/dist/config/index.d.ts.map +0 -1
  51. package/dist/config/index.js +0 -190
  52. package/dist/config/index.js.map +0 -1
  53. package/dist/filesystem/manager.d.ts +0 -45
  54. package/dist/filesystem/manager.d.ts.map +0 -1
  55. package/dist/filesystem/manager.js +0 -246
  56. package/dist/filesystem/manager.js.map +0 -1
  57. package/dist/git/multi-source-manager.d.ts +0 -78
  58. package/dist/git/multi-source-manager.d.ts.map +0 -1
  59. package/dist/git/multi-source-manager.js +0 -577
  60. package/dist/git/multi-source-manager.js.map +0 -1
  61. package/dist/git/operations.d.ts +0 -27
  62. package/dist/git/operations.d.ts.map +0 -1
  63. package/dist/git/operations.js +0 -83
  64. package/dist/git/operations.js.map +0 -1
  65. package/dist/index.d.ts +0 -6
  66. package/dist/index.d.ts.map +0 -1
  67. package/dist/index.js +0 -122
  68. package/dist/index.js.map +0 -1
  69. package/dist/monitoring/health.d.ts +0 -35
  70. package/dist/monitoring/health.d.ts.map +0 -1
  71. package/dist/monitoring/health.js +0 -105
  72. package/dist/monitoring/health.js.map +0 -1
  73. package/dist/prompts/cache.d.ts +0 -69
  74. package/dist/prompts/cache.d.ts.map +0 -1
  75. package/dist/prompts/cache.js +0 -163
  76. package/dist/prompts/cache.js.map +0 -1
  77. package/dist/prompts/generator.d.ts +0 -49
  78. package/dist/prompts/generator.d.ts.map +0 -1
  79. package/dist/prompts/generator.js +0 -160
  80. package/dist/prompts/generator.js.map +0 -1
  81. package/dist/prompts/index.d.ts +0 -13
  82. package/dist/prompts/index.d.ts.map +0 -1
  83. package/dist/prompts/index.js +0 -24
  84. package/dist/prompts/index.js.map +0 -1
  85. package/dist/prompts/manager.d.ts +0 -169
  86. package/dist/prompts/manager.d.ts.map +0 -1
  87. package/dist/prompts/manager.js +0 -488
  88. package/dist/prompts/manager.js.map +0 -1
  89. package/dist/resources/index.d.ts +0 -6
  90. package/dist/resources/index.d.ts.map +0 -1
  91. package/dist/resources/index.js +0 -10
  92. package/dist/resources/index.js.map +0 -1
  93. package/dist/resources/loader.d.ts +0 -88
  94. package/dist/resources/loader.d.ts.map +0 -1
  95. package/dist/resources/loader.js +0 -492
  96. package/dist/resources/loader.js.map +0 -1
  97. package/dist/server/http.d.ts +0 -57
  98. package/dist/server/http.d.ts.map +0 -1
  99. package/dist/server/http.js +0 -435
  100. package/dist/server/http.js.map +0 -1
  101. package/dist/server.d.ts +0 -13
  102. package/dist/server.d.ts.map +0 -1
  103. package/dist/server.js +0 -200
  104. package/dist/server.js.map +0 -1
  105. package/dist/session/manager.d.ts +0 -91
  106. package/dist/session/manager.d.ts.map +0 -1
  107. package/dist/session/manager.js +0 -251
  108. package/dist/session/manager.js.map +0 -1
  109. package/dist/telemetry/index.d.ts +0 -3
  110. package/dist/telemetry/index.d.ts.map +0 -1
  111. package/dist/telemetry/index.js +0 -7
  112. package/dist/telemetry/index.js.map +0 -1
  113. package/dist/telemetry/manager.d.ts +0 -151
  114. package/dist/telemetry/manager.d.ts.map +0 -1
  115. package/dist/telemetry/manager.js +0 -367
  116. package/dist/telemetry/manager.js.map +0 -1
  117. package/dist/tools/index.d.ts +0 -12
  118. package/dist/tools/index.d.ts.map +0 -1
  119. package/dist/tools/index.js +0 -28
  120. package/dist/tools/index.js.map +0 -1
  121. package/dist/tools/manage-subscription.d.ts +0 -47
  122. package/dist/tools/manage-subscription.d.ts.map +0 -1
  123. package/dist/tools/manage-subscription.js +0 -314
  124. package/dist/tools/manage-subscription.js.map +0 -1
  125. package/dist/tools/registry.d.ts +0 -40
  126. package/dist/tools/registry.d.ts.map +0 -1
  127. package/dist/tools/registry.js +0 -85
  128. package/dist/tools/registry.js.map +0 -1
  129. package/dist/tools/search-resources.d.ts +0 -35
  130. package/dist/tools/search-resources.d.ts.map +0 -1
  131. package/dist/tools/search-resources.js +0 -159
  132. package/dist/tools/search-resources.js.map +0 -1
  133. package/dist/tools/sync-resources.d.ts +0 -54
  134. package/dist/tools/sync-resources.d.ts.map +0 -1
  135. package/dist/tools/sync-resources.js +0 -733
  136. package/dist/tools/sync-resources.js.map +0 -1
  137. package/dist/tools/track-usage.d.ts +0 -63
  138. package/dist/tools/track-usage.d.ts.map +0 -1
  139. package/dist/tools/track-usage.js +0 -90
  140. package/dist/tools/track-usage.js.map +0 -1
  141. package/dist/tools/uninstall-resource.d.ts +0 -30
  142. package/dist/tools/uninstall-resource.d.ts.map +0 -1
  143. package/dist/tools/uninstall-resource.js +0 -174
  144. package/dist/tools/uninstall-resource.js.map +0 -1
  145. package/dist/tools/upload-resource.d.ts +0 -81
  146. package/dist/tools/upload-resource.d.ts.map +0 -1
  147. package/dist/tools/upload-resource.js +0 -393
  148. package/dist/tools/upload-resource.js.map +0 -1
  149. package/dist/transport/sse.d.ts +0 -29
  150. package/dist/transport/sse.d.ts.map +0 -1
  151. package/dist/transport/sse.js +0 -271
  152. package/dist/transport/sse.js.map +0 -1
  153. package/dist/types/errors.d.ts +0 -60
  154. package/dist/types/errors.d.ts.map +0 -1
  155. package/dist/types/errors.js +0 -112
  156. package/dist/types/errors.js.map +0 -1
  157. package/dist/types/index.d.ts +0 -7
  158. package/dist/types/index.d.ts.map +0 -1
  159. package/dist/types/index.js +0 -23
  160. package/dist/types/index.js.map +0 -1
  161. package/dist/types/mcp.d.ts +0 -50
  162. package/dist/types/mcp.d.ts.map +0 -1
  163. package/dist/types/mcp.js +0 -6
  164. package/dist/types/mcp.js.map +0 -1
  165. package/dist/types/resources.d.ts +0 -109
  166. package/dist/types/resources.d.ts.map +0 -1
  167. package/dist/types/resources.js +0 -7
  168. package/dist/types/resources.js.map +0 -1
  169. package/dist/types/tools.d.ts +0 -235
  170. package/dist/types/tools.d.ts.map +0 -1
  171. package/dist/types/tools.js +0 -6
  172. package/dist/types/tools.js.map +0 -1
  173. package/dist/utils/cursor-paths.d.ts +0 -84
  174. package/dist/utils/cursor-paths.d.ts.map +0 -1
  175. package/dist/utils/cursor-paths.js +0 -166
  176. package/dist/utils/cursor-paths.js.map +0 -1
  177. package/dist/utils/log-cleaner.d.ts +0 -18
  178. package/dist/utils/log-cleaner.d.ts.map +0 -1
  179. package/dist/utils/log-cleaner.js +0 -112
  180. package/dist/utils/log-cleaner.js.map +0 -1
  181. package/dist/utils/logger.d.ts +0 -59
  182. package/dist/utils/logger.d.ts.map +0 -1
  183. package/dist/utils/logger.js +0 -292
  184. package/dist/utils/logger.js.map +0 -1
  185. package/dist/utils/validation.d.ts +0 -58
  186. package/dist/utils/validation.d.ts.map +0 -1
  187. package/dist/utils/validation.js +0 -214
  188. package/dist/utils/validation.js.map +0 -1
  189. package/src/api/cached-client.ts +0 -144
  190. package/src/api/client.ts +0 -697
  191. package/src/auth/index.ts +0 -11
  192. package/src/auth/middleware.ts +0 -244
  193. package/src/auth/permissions.ts +0 -323
  194. package/src/auth/token-validator.ts +0 -292
  195. package/src/cache/cache-manager.ts +0 -243
  196. package/src/cache/index.ts +0 -6
  197. package/src/cache/redis-client.ts +0 -249
  198. package/src/config/constants.ts +0 -33
  199. package/src/config/index.ts +0 -269
  200. package/src/filesystem/manager.ts +0 -235
  201. package/src/git/multi-source-manager.ts +0 -654
  202. package/src/git/operations.ts +0 -93
  203. package/src/index.ts +0 -157
  204. package/src/monitoring/health.ts +0 -132
  205. package/src/prompts/cache.ts +0 -140
  206. package/src/prompts/generator.ts +0 -143
  207. package/src/prompts/index.ts +0 -20
  208. package/src/prompts/manager.ts +0 -613
  209. package/src/resources/index.ts +0 -13
  210. package/src/resources/loader.ts +0 -563
  211. package/src/server/http.ts +0 -549
  212. package/src/server.ts +0 -204
  213. package/src/session/manager.ts +0 -296
  214. package/src/telemetry/index.ts +0 -10
  215. package/src/telemetry/manager.ts +0 -419
  216. package/src/tools/index.ts +0 -12
  217. package/src/tools/manage-subscription.ts +0 -385
  218. package/src/tools/registry.ts +0 -97
  219. package/src/tools/search-resources.ts +0 -185
  220. package/src/tools/sync-resources.ts +0 -827
  221. package/src/tools/track-usage.ts +0 -113
  222. package/src/tools/uninstall-resource.ts +0 -199
  223. package/src/tools/upload-resource.ts +0 -431
  224. package/src/transport/sse.ts +0 -308
  225. package/src/types/errors.ts +0 -146
  226. package/src/types/index.ts +0 -7
  227. package/src/types/mcp.ts +0 -61
  228. package/src/types/resources.ts +0 -141
  229. package/src/types/tools.ts +0 -284
  230. package/src/utils/cursor-paths.ts +0 -135
  231. package/src/utils/log-cleaner.ts +0 -92
  232. package/src/utils/logger.ts +0 -333
  233. package/src/utils/validation.ts +0 -262
@@ -1,733 +0,0 @@
1
- "use strict";
2
- /**
3
- * sync_resources Tool
4
- *
5
- * Synchronises the user's subscribed AI resources.
6
- *
7
- * Resource delivery strategy (v1.5):
8
- * - Command / Skill : registered as MCP Prompts (NOT written to local filesystem).
9
- * Content is generated into .prompt-cache/ and registered via PromptManager.
10
- * - Rule : downloaded to ~/.cursor/rules/ (Cursor engine requires local files).
11
- * - MCP : downloaded to ~/.cursor/mcp-servers/ and registered in mcp.json.
12
- *
13
- * Flow:
14
- * 1. Fetch subscription list from CSP server (REST API).
15
- * 2. (non-check) Trigger Git sync on server side via multiSourceGitManager.
16
- * 3. For each subscription: handle per type as above.
17
- * 4. Update telemetry: subscribed_rules + configured_mcps lists.
18
- */
19
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
20
- if (k2 === undefined) k2 = k;
21
- var desc = Object.getOwnPropertyDescriptor(m, k);
22
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
23
- desc = { enumerable: true, get: function() { return m[k]; } };
24
- }
25
- Object.defineProperty(o, k2, desc);
26
- }) : (function(o, m, k, k2) {
27
- if (k2 === undefined) k2 = k;
28
- o[k2] = m[k];
29
- }));
30
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
31
- Object.defineProperty(o, "default", { enumerable: true, value: v });
32
- }) : function(o, v) {
33
- o["default"] = v;
34
- });
35
- var __importStar = (this && this.__importStar) || (function () {
36
- var ownKeys = function(o) {
37
- ownKeys = Object.getOwnPropertyNames || function (o) {
38
- var ar = [];
39
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
40
- return ar;
41
- };
42
- return ownKeys(o);
43
- };
44
- return function (mod) {
45
- if (mod && mod.__esModule) return mod;
46
- var result = {};
47
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
48
- __setModuleDefault(result, mod);
49
- return result;
50
- };
51
- })();
52
- Object.defineProperty(exports, "__esModule", { value: true });
53
- exports.syncResourcesTool = void 0;
54
- exports.syncResources = syncResources;
55
- const fs = __importStar(require("fs/promises"));
56
- const path = __importStar(require("path"));
57
- const logger_1 = require("../utils/logger");
58
- const client_1 = require("../api/client");
59
- const multi_source_manager_1 = require("../git/multi-source-manager");
60
- const cursor_paths_1 = require("../utils/cursor-paths");
61
- const errors_1 = require("../types/errors");
62
- const index_js_1 = require("../telemetry/index.js");
63
- const index_js_2 = require("../prompts/index.js");
64
- const downloadCache = new Map();
65
- function syncCacheKey(userToken, resourceId) {
66
- return `${userToken}::${resourceId}`;
67
- }
68
- /**
69
- * Extract the `description` field from YAML frontmatter in a Markdown file.
70
- * Frontmatter is delimited by leading `---` and closing `---` lines.
71
- * Returns undefined if no frontmatter or no description key is found.
72
- */
73
- function extractFrontmatterDescription(content) {
74
- if (!content.startsWith('---'))
75
- return undefined;
76
- const end = content.indexOf('\n---', 3);
77
- if (end === -1)
78
- return undefined;
79
- const frontmatter = content.slice(3, end);
80
- for (const line of frontmatter.split('\n')) {
81
- const match = /^description:\s*(.+)$/.exec(line.trim());
82
- if (match)
83
- return match[1].trim().replace(/^['"]|['"]$/g, '');
84
- }
85
- return undefined;
86
- }
87
- async function syncResources(params) {
88
- const startTime = Date.now();
89
- const typedParams = params;
90
- logger_1.logger.info({
91
- tool: 'sync_resources',
92
- params: typedParams,
93
- timestamp: new Date().toISOString()
94
- }, 'sync_resources tool invoked');
95
- (0, logger_1.logToolStep)('sync_resources', 'Tool invocation started', { params: typedParams });
96
- try {
97
- const mode = typedParams.mode || 'incremental';
98
- const scope = typedParams.scope || 'global';
99
- const types = typedParams.types;
100
- const userToken = typedParams.user_token;
101
- const configuredMcpServers = new Set(typedParams.configured_mcp_servers || []);
102
- (0, logger_1.logToolStep)('sync_resources', 'Parameters validated', {
103
- mode,
104
- scope,
105
- types,
106
- configuredMcpCount: configuredMcpServers.size,
107
- });
108
- // ── Step 1: Fetch subscription list ────────────────────────────────────
109
- (0, logger_1.logToolStep)('sync_resources', 'Step 1: Fetching subscriptions from API', { scope, types });
110
- const t1 = Date.now();
111
- const subscriptions = await client_1.apiClient.getSubscriptions({ types }, userToken);
112
- (0, logger_1.logToolStep)('sync_resources', 'Subscriptions fetched', {
113
- total: subscriptions.total,
114
- duration: Date.now() - t1,
115
- ids: subscriptions.subscriptions.map(s => s.id),
116
- });
117
- // ── Step 2: Server-side Git sync (skip in check mode) ──────────────────
118
- (0, logger_1.logToolStep)('sync_resources', 'Step 2: Server-side Git sync');
119
- if (mode === 'check') {
120
- const statuses = await multi_source_manager_1.multiSourceGitManager.checkAllSources();
121
- (0, logger_1.logToolStep)('sync_resources', 'Repository status check completed', {
122
- sources: statuses.map(s => ({ name: s.source, exists: s.exists, hasRemote: s.hasRemote })),
123
- });
124
- }
125
- else {
126
- const t2 = Date.now();
127
- const gitResults = await multi_source_manager_1.multiSourceGitManager.syncAllSources();
128
- (0, logger_1.logToolStep)('sync_resources', 'Server-side Git sync completed', {
129
- duration: Date.now() - t2,
130
- summary: {
131
- cloned: gitResults.filter(r => r.action === 'cloned').length,
132
- pulled: gitResults.filter(r => r.action === 'pulled').length,
133
- upToDate: gitResults.filter(r => r.action === 'up-to-date').length,
134
- skipped: gitResults.filter(r => r.action === 'skipped').length,
135
- },
136
- });
137
- }
138
- // ── Step 3: Download each subscribed resource ──────────────────────────
139
- // Command / Skill → registered as MCP Prompts on the server (no local I/O)
140
- // Rule / MCP → file content is returned as LocalAction instructions
141
- // so that the AI Agent executes the writes on the user's
142
- // LOCAL machine (not on this potentially remote server).
143
- (0, logger_1.logToolStep)('sync_resources', 'Step 3: Processing subscribed resources', {
144
- count: subscriptions.total,
145
- });
146
- const tally = { total: subscriptions.total, synced: 0, cached: 0, failed: 0 };
147
- const details = [];
148
- // Accumulated local file-system actions the AI must perform on the user's machine.
149
- const localActions = [];
150
- // Track which prompt names are expected from the current subscription list.
151
- // After the loop, any prompt registered in PromptManager but NOT in this set
152
- // is stale (from a previous connection / subscription change) and will be pruned.
153
- const expectedPromptNames = new Set();
154
- for (let i = 0; i < subscriptions.subscriptions.length; i++) {
155
- const sub = subscriptions.subscriptions[i];
156
- if (!sub)
157
- continue;
158
- // Safe access — `resource` metadata is only present when detail=true was requested.
159
- const resourceVersion = sub.resource?.version ?? 'unknown';
160
- (0, logger_1.logToolStep)('sync_resources', `Processing ${i + 1}/${tally.total}`, {
161
- resourceId: sub.id,
162
- resourceName: sub.name,
163
- resourceType: sub.type,
164
- });
165
- try {
166
- // Resolve the destination path inside the Cursor directory.
167
- // getCursorResourcePath throws for unrecognised types, caught below.
168
- const destPath = (0, cursor_paths_1.getCursorResourcePath)(sub.type, sub.name);
169
- // In check mode: report whether the resource is already available.
170
- // Command/Skill: check the in-memory Prompt registry (no local files).
171
- // Rule/MCP: check whether the local file / mcp.json entry exists.
172
- if (mode === 'check') {
173
- if (sub.type === 'command' || sub.type === 'skill') {
174
- const meta = {
175
- resource_id: sub.id,
176
- resource_type: sub.type,
177
- resource_name: sub.name,
178
- team: sub.team ?? 'general',
179
- };
180
- const isRegistered = index_js_2.promptManager.has(index_js_2.promptManager.buildPromptName(meta), userToken ?? '');
181
- if (isRegistered) {
182
- tally.cached++;
183
- details.push({ id: sub.id, name: sub.name, action: 'cached', version: resourceVersion });
184
- }
185
- else {
186
- tally.failed++;
187
- details.push({ id: sub.id, name: sub.name, action: 'failed', version: resourceVersion });
188
- }
189
- }
190
- else {
191
- try {
192
- await fs.access(destPath);
193
- tally.cached++;
194
- details.push({ id: sub.id, name: sub.name, action: 'cached', version: resourceVersion });
195
- (0, logger_1.logToolStep)('sync_resources', 'Resource already present (check mode)', {
196
- resourceId: sub.id, destPath,
197
- });
198
- }
199
- catch {
200
- tally.failed++;
201
- details.push({ id: sub.id, name: sub.name, action: 'failed', version: resourceVersion });
202
- (0, logger_1.logToolStep)('sync_resources', 'Resource missing (check mode)', {
203
- resourceId: sub.id, destPath,
204
- });
205
- }
206
- }
207
- continue;
208
- }
209
- // ── Command / Skill: MCP Prompt mode (no local file write) ──────────
210
- // Download content → generate intermediate cache → register as MCP Prompt.
211
- if (sub.type === 'command' || sub.type === 'skill') {
212
- (0, logger_1.logToolStep)('sync_resources', `Registering ${sub.type} as MCP Prompt`, {
213
- resourceId: sub.id,
214
- resourceName: sub.name,
215
- });
216
- try {
217
- const tDl = Date.now();
218
- const downloadResult = await client_1.apiClient.downloadResource(sub.id, userToken);
219
- (0, logger_1.logToolStep)('sync_resources', 'Download complete (Prompt mode)', {
220
- resourceId: sub.id,
221
- fileCount: downloadResult.files.length,
222
- duration: Date.now() - tDl,
223
- });
224
- // When the API returns no files (expected for Command/Skill in MCP Prompt
225
- // mode — content lives in the server-side git repo, not the API), fall back
226
- // to reading the files directly from the local git checkout.
227
- let sourceFiles = downloadResult.files;
228
- if (sourceFiles.length === 0) {
229
- sourceFiles = await multi_source_manager_1.multiSourceGitManager.readResourceFiles(sub.name, sub.type);
230
- if (sourceFiles.length > 0) {
231
- (0, logger_1.logToolStep)('sync_resources', 'Loaded resource files from local git checkout', {
232
- resourceId: sub.id,
233
- fileCount: sourceFiles.length,
234
- });
235
- }
236
- else {
237
- logger_1.logger.warn({ resourceId: sub.id, resourceName: sub.name }, 'No files found via API or local git — prompt will have empty content');
238
- }
239
- }
240
- // Primary Markdown content selection:
241
- // - skill: prefer SKILL.md (canonical entrypoint for all skill content)
242
- // - command: prefer the file whose name matches the resource name
243
- // - fallback: first .md file, then first file of any type
244
- const isSkill = sub.type === 'skill';
245
- const primaryFile = isSkill
246
- ? (sourceFiles.find((f) => path.basename(f.path) === 'SKILL.md') ??
247
- sourceFiles.find((f) => f.path.endsWith('.md')) ??
248
- sourceFiles[0])
249
- : (sourceFiles.find((f) => path.basename(f.path).replace(/\.md$/, '') === sub.name) ??
250
- sourceFiles.find((f) => f.path.endsWith('.md')) ??
251
- sourceFiles[0]);
252
- const rawContent = primaryFile?.content ?? '';
253
- // Extract description from frontmatter (---\ndescription: ...\n---)
254
- // falling back to the subscription's description field or resource name.
255
- const frontmatterDesc = extractFrontmatterDescription(rawContent);
256
- const description = frontmatterDesc ??
257
- sub.description ??
258
- sub.name;
259
- const meta = {
260
- resource_id: sub.id,
261
- resource_type: sub.type,
262
- resource_name: sub.name,
263
- team: sub.team ?? 'general',
264
- description,
265
- rawContent,
266
- };
267
- // userToken is required so the prompt is scoped to this user's namespace.
268
- const effectiveToken = userToken ?? '';
269
- await index_js_2.promptManager.registerPrompt(meta, effectiveToken);
270
- // Track this prompt name so stale prompts can be pruned after the loop.
271
- expectedPromptNames.add(index_js_2.promptManager.buildPromptName(meta));
272
- // Clean up any legacy local files that may have been written by an
273
- // older version of sync_resources. Command/Skill resources are now
274
- // served exclusively as MCP Prompts; stale local files would cause
275
- // the AI to read outdated content (without the track_usage header).
276
- try {
277
- const legacyPath = (0, cursor_paths_1.getCursorResourcePath)(sub.type, `${sub.name}.md`);
278
- await fs.unlink(legacyPath);
279
- logger_1.logger.info({ resourceId: sub.id, legacyPath }, 'Removed legacy local file for Command/Skill resource');
280
- }
281
- catch {
282
- // File didn't exist — nothing to clean up.
283
- }
284
- tally.synced++;
285
- details.push({ id: sub.id, name: sub.name, action: 'synced', version: resourceVersion });
286
- (0, logger_1.logToolStep)('sync_resources', `${sub.type} registered as MCP Prompt`, {
287
- resourceId: sub.id,
288
- promptCount: index_js_2.promptManager.sizeFor(userToken ?? ''),
289
- });
290
- }
291
- catch (promptErr) {
292
- logger_1.logger.error({ resourceId: sub.id, error: promptErr.message }, 'Failed to register Command/Skill as MCP Prompt');
293
- tally.failed++;
294
- details.push({ id: sub.id, name: sub.name, action: 'failed', version: resourceVersion });
295
- }
296
- continue;
297
- }
298
- // ── Download (with server-session cache) ─────────────────────────────
299
- // The download cache avoids redundant API calls when the same resource
300
- // is synced multiple times within one server session without any content
301
- // change. It ONLY caches the network response; LocalAction generation
302
- // always proceeds so that users can recover deleted local files by
303
- // re-running sync — even when the resource content is unchanged.
304
- const cacheKey = syncCacheKey(userToken ?? '', sub.id);
305
- let downloadResult;
306
- const cached = downloadCache.get(cacheKey);
307
- if (mode === 'incremental' && cached) {
308
- // Reuse the previously downloaded content without hitting the API.
309
- // full mode always bypasses this branch to guarantee a fresh download.
310
- downloadResult = cached;
311
- (0, logger_1.logToolStep)('sync_resources', 'Using cached download (no API call)', {
312
- resourceId: sub.id,
313
- cachedHash: cached.hash,
314
- });
315
- }
316
- else {
317
- (0, logger_1.logToolStep)('sync_resources', 'Downloading resource', {
318
- resourceId: sub.id,
319
- resourceType: sub.type,
320
- });
321
- const tDl = Date.now();
322
- const apiResult = await client_1.apiClient.downloadResource(sub.id, userToken);
323
- (0, logger_1.logToolStep)('sync_resources', 'Download complete', {
324
- resourceId: sub.id,
325
- fileCount: apiResult.files.length,
326
- duration: Date.now() - tDl,
327
- });
328
- downloadResult = { hash: apiResult.hash, files: apiResult.files };
329
- // Refresh cache with the latest download.
330
- downloadCache.set(cacheKey, downloadResult);
331
- }
332
- // When the API returns no files (expected when the MCP server is deployed
333
- // remotely and content lives in the server-side git repo), fall back to
334
- // reading the files directly from the local git checkout.
335
- let resourceFiles = downloadResult.files;
336
- if (resourceFiles.length === 0) {
337
- logger_1.logger.info({ resourceId: sub.id, resourceName: sub.name, type: sub.type }, 'sync_resources: API returned no files — triggering git-checkout fallback');
338
- const gitType = sub.type;
339
- const gitFiles = await multi_source_manager_1.multiSourceGitManager.readResourceFiles(sub.name, gitType);
340
- if (gitFiles.length > 0) {
341
- resourceFiles = gitFiles;
342
- logger_1.logger.info({
343
- resourceId: sub.id,
344
- resourceName: sub.name,
345
- type: sub.type,
346
- fileCount: resourceFiles.length,
347
- files: resourceFiles.map((f) => f.path),
348
- }, 'sync_resources: git-checkout fallback succeeded');
349
- (0, logger_1.logToolStep)('sync_resources', 'Loaded resource files from local git checkout', {
350
- resourceId: sub.id,
351
- fileCount: resourceFiles.length,
352
- });
353
- }
354
- else {
355
- logger_1.logger.warn({ resourceId: sub.id, resourceName: sub.name, type: sub.type }, 'sync_resources: git-checkout fallback found no files — marking resource failed');
356
- tally.failed++;
357
- details.push({ id: sub.id, name: sub.name, action: 'failed', version: resourceVersion });
358
- continue;
359
- }
360
- }
361
- // ── MCP resource ──────────────────────────────────────────────────────
362
- // Read mcp-config.json to determine Format A (local executable, has
363
- // "command" field) vs Format B (remote URL map, no "command" field).
364
- //
365
- // IMPORTANT: all paths in LocalAction instructions must use the CLIENT-side
366
- // helper (tilde-based) so they resolve correctly on the user's machine,
367
- // not on this (possibly remote Linux) server.
368
- if (sub.type === 'mcp') {
369
- const mcpConfigFile = resourceFiles.find((f) => path.basename(f.path) === 'mcp-config.json');
370
- // ~/.cursor/mcp.json on the user's machine
371
- const mcpJsonPath = `${(0, cursor_paths_1.getCursorRootDirForClient)()}/mcp.json`;
372
- // ── Optimization: skip if already configured (incremental mode only) ────
373
- // In incremental mode, if the AI Agent reports this MCP server is already
374
- // in ~/.cursor/mcp.json, skip downloading and generating write_file actions.
375
- // This reduces API calls, network traffic, and AI Agent execution overhead.
376
- // In full mode, always proceed to allow file recovery.
377
- if (mode === 'incremental' && mcpConfigFile) {
378
- let cfg = {};
379
- try {
380
- cfg = JSON.parse(mcpConfigFile.content);
381
- }
382
- catch { /* ignore parse errors, proceed to download */ }
383
- // Format A: check if the single server is configured
384
- if (typeof cfg['command'] === 'string') {
385
- const serverName = cfg['name'] ?? sub.name;
386
- if (configuredMcpServers.has(serverName)) {
387
- logger_1.logger.info({ resourceId: sub.id, resourceName: sub.name, serverName }, 'sync_resources: MCP server already configured — skipping download');
388
- tally.cached++;
389
- details.push({ id: sub.id, name: sub.name, action: 'cached', version: resourceVersion });
390
- continue;
391
- }
392
- }
393
- // Format B: check if all servers in the map are configured
394
- else if (Object.keys(cfg).length > 0) {
395
- const allConfigured = Object.keys(cfg).every((k) => configuredMcpServers.has(k));
396
- if (allConfigured) {
397
- logger_1.logger.info({ resourceId: sub.id, resourceName: sub.name, serverKeys: Object.keys(cfg) }, 'sync_resources: All MCP servers already configured — skipping download');
398
- tally.cached++;
399
- details.push({ id: sub.id, name: sub.name, action: 'cached', version: resourceVersion });
400
- continue;
401
- }
402
- }
403
- }
404
- logger_1.logger.info({
405
- resourceId: sub.id,
406
- resourceName: sub.name,
407
- mcpJsonPath,
408
- hasMcpConfigFile: !!mcpConfigFile,
409
- availableFiles: resourceFiles.map((f) => f.path),
410
- }, 'sync_resources: processing MCP resource');
411
- if (mcpConfigFile) {
412
- let cfg = {};
413
- try {
414
- cfg = JSON.parse(mcpConfigFile.content);
415
- }
416
- catch {
417
- logger_1.logger.warn({ resourceId: sub.id, resourceName: sub.name }, 'sync_resources: failed to parse mcp-config.json — treating as empty config');
418
- }
419
- if (typeof cfg['command'] === 'string') {
420
- // ── Format A: local executable ──────────────────────────────────
421
- const installDir = `${(0, cursor_paths_1.getCursorTypeDirForClient)('mcp')}/${sub.name}`;
422
- const writeActions = [];
423
- for (const file of resourceFiles) {
424
- const normalised = path.normalize(file.path);
425
- if (normalised.startsWith('..'))
426
- continue;
427
- const fileDest = `${installDir}/${normalised}`;
428
- localActions.push({
429
- action: 'write_file',
430
- path: fileDest,
431
- content: file.content,
432
- });
433
- writeActions.push(fileDest);
434
- }
435
- const env = (cfg['env'] ?? {});
436
- const missingEnv = Object.entries(env).filter(([, v]) => v === '').map(([k]) => k);
437
- const looksLikePath = (a) => a.startsWith('./') || a.startsWith('../') || a.includes('/') || /\.\w+$/.test(a);
438
- const args = (cfg['args'] ?? []).map((a) => path.isAbsolute(a) || !looksLikePath(a) ? a : `${installDir}/${a.replace(/^\.\//, '')}`);
439
- const serverName = cfg['name'] ?? sub.name;
440
- localActions.push({
441
- action: 'merge_mcp_json',
442
- mcp_json_path: mcpJsonPath,
443
- server_name: serverName,
444
- entry: { ...cfg, args },
445
- // skip_if_exists: preserve user-edited env values; the entry
446
- // is already configured if the key exists in mcp.json.
447
- skip_if_exists: true,
448
- ...(missingEnv.length > 0 ? {
449
- missing_env: missingEnv,
450
- setup_hint: `Fill in env vars in ${mcpJsonPath} under mcpServers["${sub.name}"]: ${missingEnv.join(', ')}.`,
451
- } : {}),
452
- });
453
- logger_1.logger.info({
454
- resourceId: sub.id,
455
- resourceName: sub.name,
456
- format: 'A',
457
- installDir,
458
- mcpJsonPath,
459
- serverName,
460
- writeFiles: writeActions,
461
- missingEnv,
462
- }, 'sync_resources: MCP Format A — write_file + merge_mcp_json actions queued');
463
- (0, logger_1.logToolStep)('sync_resources', 'Local-executable MCP: write_file + merge_mcp_json queued', { resourceId: sub.id });
464
- }
465
- else {
466
- // ── Format B: remote URL map ────────────────────────────────────
467
- const queuedServers = [];
468
- for (const [serverName, entry] of Object.entries(cfg)) {
469
- const e = entry;
470
- const env = (e['env'] ?? {});
471
- const missingEnv = Object.entries(env).filter(([, v]) => v === '').map(([k]) => k);
472
- localActions.push({
473
- action: 'merge_mcp_json',
474
- mcp_json_path: mcpJsonPath,
475
- server_name: serverName,
476
- entry: e,
477
- // skip_if_exists: user may have customised env values; do
478
- // not overwrite an existing entry on every incremental sync.
479
- skip_if_exists: true,
480
- ...(missingEnv.length > 0 ? {
481
- missing_env: missingEnv,
482
- setup_hint: `Fill in env vars in ${mcpJsonPath} under mcpServers["${serverName}"]: ${missingEnv.join(', ')}.`,
483
- } : {}),
484
- });
485
- queuedServers.push(serverName);
486
- }
487
- logger_1.logger.info({
488
- resourceId: sub.id,
489
- resourceName: sub.name,
490
- format: 'B',
491
- mcpJsonPath,
492
- serverKeys: queuedServers,
493
- }, 'sync_resources: MCP Format B — merge_mcp_json actions queued');
494
- (0, logger_1.logToolStep)('sync_resources', 'Remote-URL MCP: merge_mcp_json queued', {
495
- resourceId: sub.id, serverKeys: Object.keys(cfg),
496
- });
497
- }
498
- }
499
- else {
500
- // No mcp-config.json: heuristic fallback
501
- const installDir = `${(0, cursor_paths_1.getCursorTypeDirForClient)('mcp')}/${sub.name}`;
502
- const writeActions = [];
503
- for (const file of resourceFiles) {
504
- const normalised = path.normalize(file.path);
505
- if (normalised.startsWith('..'))
506
- continue;
507
- const fileDest = `${installDir}/${normalised}`;
508
- localActions.push({
509
- action: 'write_file',
510
- path: fileDest,
511
- content: file.content,
512
- });
513
- writeActions.push(fileDest);
514
- }
515
- const jsEntry = resourceFiles.find((f) => f.path.endsWith('.js'));
516
- const pyEntry = resourceFiles.find((f) => f.path.endsWith('.py'));
517
- const entryFile = jsEntry ?? pyEntry ?? resourceFiles[0];
518
- const cmd = jsEntry ? 'node' : 'python3';
519
- const entryPath = `${installDir}/${entryFile?.path ?? ''}`;
520
- localActions.push({
521
- action: 'merge_mcp_json',
522
- mcp_json_path: mcpJsonPath,
523
- server_name: sub.name,
524
- entry: { command: cmd, args: [entryPath] },
525
- skip_if_exists: true,
526
- });
527
- logger_1.logger.info({
528
- resourceId: sub.id,
529
- resourceName: sub.name,
530
- format: 'heuristic',
531
- installDir,
532
- mcpJsonPath,
533
- cmd,
534
- entryPath,
535
- writeFiles: writeActions,
536
- }, 'sync_resources: MCP heuristic fallback — write_file + merge_mcp_json actions queued');
537
- (0, logger_1.logToolStep)('sync_resources', 'MCP heuristic fallback: write_file + merge_mcp_json queued', { resourceId: sub.id });
538
- }
539
- tally.synced++;
540
- details.push({ id: sub.id, name: sub.name, action: 'synced', version: resourceVersion });
541
- continue;
542
- }
543
- // ── Rule resource ─────────────────────────────────────────────────────
544
- // Return write_file actions; the AI Agent executes them on the user's
545
- // LOCAL machine. The AI compares file content directly (string equality)
546
- // against the existing local file and skips the write when content is
547
- // identical — avoiding unnecessary disk I/O. If the local file is missing
548
- // or has different content, the AI writes it unconditionally, which also
549
- // recovers files that were accidentally deleted by the user.
550
- if (sub.type === 'rule') {
551
- const typeDir = (0, cursor_paths_1.getCursorTypeDirForClient)(sub.type);
552
- const writeActions = [];
553
- for (const file of resourceFiles) {
554
- const normalised = path.normalize(file.path);
555
- if (normalised.startsWith('..')) {
556
- logger_1.logger.warn({ resourceId: sub.id, filePath: file.path }, 'Skipping suspicious file path');
557
- continue;
558
- }
559
- const destPath = `${typeDir}/${normalised}`;
560
- localActions.push({
561
- action: 'write_file',
562
- path: destPath,
563
- content: file.content,
564
- });
565
- writeActions.push({ destPath, contentLength: file.content.length });
566
- }
567
- logger_1.logger.info({
568
- resourceId: sub.id,
569
- resourceName: sub.name,
570
- typeDir,
571
- fileCount: writeActions.length,
572
- files: writeActions,
573
- clientSideNote: 'AI will compare file content directly; write is skipped if content is identical',
574
- }, 'sync_resources: Rule — write_file actions queued for AI (client-side content comparison)');
575
- tally.synced++;
576
- details.push({ id: sub.id, name: sub.name, action: 'synced', version: resourceVersion });
577
- (0, logger_1.logToolStep)('sync_resources', 'Rule: write_file actions queued for AI', {
578
- resourceId: sub.id,
579
- fileCount: resourceFiles.length,
580
- });
581
- continue;
582
- }
583
- // Fallback for any unrecognised types (should not happen in practice).
584
- logger_1.logger.warn({ resourceId: sub.id, type: sub.type }, 'Unrecognised resource type — skipping');
585
- tally.failed++;
586
- details.push({ id: sub.id, name: sub.name, action: 'failed', version: resourceVersion });
587
- }
588
- catch (error) {
589
- logger_1.logger.error({
590
- resourceId: sub.id,
591
- resourceName: sub.name,
592
- error: error instanceof Error ? error.message : String(error),
593
- }, 'Failed to sync resource');
594
- tally.failed++;
595
- details.push({ id: sub.id, name: sub.name, action: 'failed', version: sub.resource?.version ?? 'unknown' });
596
- }
597
- }
598
- // ── Step 4: Prune stale prompts ────────────────────────────────────────
599
- // Remove any prompt registered in a previous session that is no longer in
600
- // the current subscription list. This prevents prompt count from growing
601
- // unboundedly across reconnections.
602
- // In 'check' mode we skip pruning — we never registered any prompts above.
603
- if (mode !== 'check') {
604
- index_js_2.promptManager.pruneStalePrompts(expectedPromptNames, userToken ?? '');
605
- }
606
- // ── Step 5: Health score ───────────────────────────────────────────────
607
- const healthScore = tally.total > 0
608
- ? Math.round(((tally.synced + tally.cached) / tally.total) * 100)
609
- : 100;
610
- const result = {
611
- mode,
612
- health_score: healthScore,
613
- summary: tally,
614
- details,
615
- ...(localActions.length > 0 ? { local_actions_required: localActions } : {}),
616
- };
617
- const duration = Date.now() - startTime;
618
- (0, logger_1.logToolCall)('sync_resources', 'user-id', params, duration);
619
- (0, logger_1.logToolResult)('sync_resources', true, result);
620
- logger_1.logger.info({
621
- tool: 'sync_resources',
622
- mode,
623
- total: tally.total,
624
- synced: tally.synced,
625
- cached: tally.cached,
626
- failed: tally.failed,
627
- healthScore,
628
- duration,
629
- timestamp: new Date().toISOString()
630
- }, 'sync_resources completed successfully');
631
- // Update telemetry snapshot lists (fire-and-forget).
632
- // Rules: cannot track individual invocations; report subscription list only.
633
- const subscribedRules = subscriptions.subscriptions
634
- .filter((s) => s.type === 'rule')
635
- .map((s) => ({
636
- resource_id: s.id,
637
- resource_name: s.name,
638
- subscribed_at: s.subscribed_at ?? new Date().toISOString(),
639
- }));
640
- if (userToken)
641
- index_js_1.telemetry.updateSubscribedRules(subscribedRules, userToken).catch(() => { });
642
- // MCPs: individual invocation tracking is each MCP server's own responsibility.
643
- const configuredMcps = subscriptions.subscriptions
644
- .filter((s) => s.type === 'mcp')
645
- .map((s) => ({
646
- resource_id: s.id,
647
- resource_name: s.name,
648
- configured_at: s.subscribed_at ?? new Date().toISOString(),
649
- }));
650
- if (userToken)
651
- index_js_1.telemetry.updateConfiguredMcps(configuredMcps, userToken).catch(() => { });
652
- return { success: true, data: result };
653
- }
654
- catch (error) {
655
- const duration = Date.now() - startTime;
656
- logger_1.logger.error({
657
- tool: 'sync_resources',
658
- error: error instanceof Error
659
- ? { message: error.message, stack: error.stack, name: error.name }
660
- : String(error),
661
- duration,
662
- timestamp: new Date().toISOString()
663
- }, 'sync_resources failed with error');
664
- (0, logger_1.logToolResult)('sync_resources', false, undefined, error instanceof Error ? error : new Error(String(error)));
665
- return {
666
- success: false,
667
- error: {
668
- code: error instanceof errors_1.MCPServerError ? error.code : 'UNKNOWN_ERROR',
669
- message: error instanceof Error ? error.message : String(error),
670
- },
671
- };
672
- }
673
- }
674
- // Tool definition for registry
675
- exports.syncResourcesTool = {
676
- name: 'sync_resources',
677
- description: 'Synchronize subscribed AI resources. ' +
678
- 'Command and Skill resources are registered as MCP Prompts on the server. ' +
679
- 'Rule and MCP resources are returned as `local_actions_required` — an ordered list of ' +
680
- 'write_file, merge_mcp_json, or other actions that the AI Agent MUST execute on the ' +
681
- 'USER\'S LOCAL MACHINE after receiving the response. ' +
682
- 'Execute every action in the list in order before reporting success to the user. ' +
683
- 'OPTIMIZATION: Before calling this tool in incremental mode, read ~/.cursor/mcp.json ' +
684
- 'and pass Object.keys(mcpServers || {}) as `configured_mcp_servers` parameter. ' +
685
- 'This allows the server to skip downloading MCP resources that are already configured, ' +
686
- 'significantly reducing API calls, network traffic, and local action overhead. ' +
687
- 'For write_file actions: ' +
688
- ' (1) Read the existing file at `path` (if it exists) using fs.readFile() or equivalent. ' +
689
- ' (2) Compare the file content directly (string equality) against the `content` field. ' +
690
- ' (3) SKIP the write if they are EXACTLY equal — the file is already up-to-date. ' +
691
- ' (4) Otherwise, create any missing parent directories and write the file. ' +
692
- 'For merge_mcp_json actions: ' +
693
- ' (1) If the action has `skip_if_exists: true` AND `mcpServers[server_name]` already exists ' +
694
- ' in ~/.cursor/mcp.json, SKIP this action entirely — the server is already configured ' +
695
- ' and any user-edited env values must not be overwritten. ' +
696
- ' (2) Otherwise read the existing ~/.cursor/mcp.json, smart-merge the provided entry ' +
697
- ' (preserve existing user env values), then write the file back.',
698
- inputSchema: {
699
- type: 'object',
700
- properties: {
701
- mode: {
702
- type: 'string',
703
- description: 'Sync mode: check (status only), incremental (updates only), full (all)',
704
- enum: ['check', 'incremental', 'full'],
705
- default: 'incremental',
706
- },
707
- scope: {
708
- type: 'string',
709
- description: 'Installation scope: global (~/.cursor/), workspace (.cursor/), or all',
710
- enum: ['global', 'workspace', 'all'],
711
- default: 'global',
712
- },
713
- types: {
714
- type: 'array',
715
- description: 'Filter by resource types (empty = all types)',
716
- },
717
- user_token: {
718
- type: 'string',
719
- description: 'DO NOT set this field — it is automatically injected by the MCP server from ' +
720
- 'the authenticated SSE connection. The server always provides the correct token.',
721
- },
722
- configured_mcp_servers: {
723
- type: 'array',
724
- description: 'List of MCP server names already configured in ~/.cursor/mcp.json. ' +
725
- 'In incremental mode, the server skips downloading these MCP resources to reduce overhead. ' +
726
- 'To populate this: read ~/.cursor/mcp.json and extract Object.keys(mcpServers || {}). ' +
727
- 'Example: ["github", "gitlab", "postgres"]. Ignored in full mode (always downloads).',
728
- },
729
- },
730
- },
731
- handler: syncResources,
732
- };
733
- //# sourceMappingURL=sync-resources.js.map