@ezmodo/mcp-server 0.13.0

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 (98) hide show
  1. package/README.md +305 -0
  2. package/config/development.js +20 -0
  3. package/config/endpoint-map.js +351 -0
  4. package/config/index.js +34 -0
  5. package/config/production.js +18 -0
  6. package/config/staging.js +18 -0
  7. package/handlers/access.js +141 -0
  8. package/handlers/activity.js +112 -0
  9. package/handlers/agents.js +95 -0
  10. package/handlers/ai-intelligence.js +55 -0
  11. package/handlers/attachments.js +30 -0
  12. package/handlers/catalogs.js +169 -0
  13. package/handlers/components.js +282 -0
  14. package/handlers/context-manifest.js +1150 -0
  15. package/handlers/decisions.js +114 -0
  16. package/handlers/designs.js +118 -0
  17. package/handlers/documents.js +227 -0
  18. package/handlers/entities.js +95 -0
  19. package/handlers/epics.js +190 -0
  20. package/handlers/facts.js +62 -0
  21. package/handlers/feature-flags.js +142 -0
  22. package/handlers/features.js +137 -0
  23. package/handlers/folders.js +127 -0
  24. package/handlers/git-context.js +917 -0
  25. package/handlers/github.js +72 -0
  26. package/handlers/graph.js +23 -0
  27. package/handlers/index.js +205 -0
  28. package/handlers/links.js +156 -0
  29. package/handlers/milestones.js +131 -0
  30. package/handlers/organizations.js +14 -0
  31. package/handlers/projects.js +122 -0
  32. package/handlers/recurring-tasks.js +33 -0
  33. package/handlers/tags.js +124 -0
  34. package/handlers/tasks.js +561 -0
  35. package/handlers/testing.js +116 -0
  36. package/handlers/todos.js +43 -0
  37. package/handlers/watchers.js +54 -0
  38. package/handlers/work-templates.js +32 -0
  39. package/index.js +175 -0
  40. package/lib/active-session.js +86 -0
  41. package/lib/auto-assign.js +93 -0
  42. package/lib/autolink.js +176 -0
  43. package/lib/changed-files.js +22 -0
  44. package/lib/env.js +45 -0
  45. package/lib/git-helpers.js +553 -0
  46. package/lib/git-utils.js +73 -0
  47. package/lib/http-client.js +164 -0
  48. package/lib/links-at-create.js +94 -0
  49. package/lib/local-cache.js +140 -0
  50. package/lib/logger.js +109 -0
  51. package/lib/manifest-loader.js +182 -0
  52. package/lib/manifest-query.js +686 -0
  53. package/lib/repo-config-dir.js +118 -0
  54. package/lib/version.js +10 -0
  55. package/lib/web-url.js +69 -0
  56. package/lib/worktree-tools.js +950 -0
  57. package/package.json +62 -0
  58. package/prompts/ai-workflow-automation.js +96 -0
  59. package/prompts/index.js +39 -0
  60. package/prompts/zephly-usage-guide-content.txt +631 -0
  61. package/prompts/zephly-usage-guide.js +119 -0
  62. package/tools/access-entity-types.js +28 -0
  63. package/tools/access.js +152 -0
  64. package/tools/activity.js +38 -0
  65. package/tools/agents.js +208 -0
  66. package/tools/ai-intelligence.js +111 -0
  67. package/tools/attachments.js +92 -0
  68. package/tools/catalogs.js +341 -0
  69. package/tools/components.js +249 -0
  70. package/tools/context-manifest.js +236 -0
  71. package/tools/decisions.js +168 -0
  72. package/tools/designs.js +222 -0
  73. package/tools/documents.js +287 -0
  74. package/tools/entities.js +223 -0
  75. package/tools/epics.js +267 -0
  76. package/tools/facts.js +70 -0
  77. package/tools/feature-flags.js +300 -0
  78. package/tools/features.js +246 -0
  79. package/tools/folders.js +122 -0
  80. package/tools/git-context.js +109 -0
  81. package/tools/github.js +172 -0
  82. package/tools/graph.js +70 -0
  83. package/tools/index.js +77 -0
  84. package/tools/link-params.js +93 -0
  85. package/tools/linkable-types.js +36 -0
  86. package/tools/links.js +199 -0
  87. package/tools/milestones.js +176 -0
  88. package/tools/organizations.js +23 -0
  89. package/tools/projects.js +172 -0
  90. package/tools/recurring-tasks.js +115 -0
  91. package/tools/tags.js +219 -0
  92. package/tools/task-item-schema.js +57 -0
  93. package/tools/task-type.js +33 -0
  94. package/tools/tasks.js +680 -0
  95. package/tools/testing.js +344 -0
  96. package/tools/todos.js +69 -0
  97. package/tools/watchers.js +81 -0
  98. package/tools/work-templates.js +96 -0
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Activity Timeline Handler
3
+ * Handles get_project_changes MCP tool calls
4
+ */
5
+
6
+ import { callZephlyAPI } from '../lib/http-client.js';
7
+
8
+ /**
9
+ * Parse a relative duration string (e.g., "7d", "2w") into an ISO date string.
10
+ * Returns the input unchanged if it's already an ISO date.
11
+ */
12
+ function parseSinceParam(since) {
13
+ if (!since) {
14
+ // Default: 7 days ago
15
+ const d = new Date();
16
+ d.setDate(d.getDate() - 7);
17
+ return d.toISOString();
18
+ }
19
+
20
+ // Check for relative duration: Nd, Nw, Nm
21
+ const match = since.match(/^(\d+)([dwm])$/);
22
+ if (match) {
23
+ const n = parseInt(match[1], 10);
24
+ const unit = match[2];
25
+ const d = new Date();
26
+ if (unit === 'd') d.setDate(d.getDate() - n);
27
+ else if (unit === 'w') d.setDate(d.getDate() - n * 7);
28
+ else if (unit === 'm') d.setMonth(d.getMonth() - n);
29
+ return d.toISOString();
30
+ }
31
+
32
+ // Assume ISO date
33
+ return since;
34
+ }
35
+
36
+ /**
37
+ * Format an event into a human-readable description for AI agents.
38
+ */
39
+ function formatEventDescription(event) {
40
+ const actor = event.actor?.name || 'Someone';
41
+ const title = event.entityTitle || event.entityId;
42
+ const taskNum = event.metadata?.taskNumber ? `#${event.metadata.taskNumber}` : '';
43
+ const epicNum = event.metadata?.epicNumber ? `E-${event.metadata.epicNumber}` : '';
44
+ const entityLabel = taskNum || epicNum || '';
45
+ const titleWithNum = entityLabel ? `${entityLabel} '${title}'` : `'${title}'`;
46
+
47
+ switch (event.eventType) {
48
+ case 'task_created': return `${actor} created task ${titleWithNum}`;
49
+ case 'task_completed': return `${actor} completed task ${titleWithNum}`;
50
+ case 'task_status_changed': {
51
+ const newStatus = event.metadata?.newStatus || '';
52
+ return `${actor} moved task ${titleWithNum} to ${newStatus}`;
53
+ }
54
+ case 'task_assigned': return `${actor} assigned task ${titleWithNum}`;
55
+ case 'task_updated': return `${actor} updated task ${titleWithNum}`;
56
+ case 'task_deleted': return `${actor} deleted task ${titleWithNum}`;
57
+ case 'task_comment_added': return `${actor} commented on task ${titleWithNum}`;
58
+ case 'task_priority_changed': return `${actor} changed priority on task ${titleWithNum}`;
59
+ case 'epic_created': return `${actor} created epic ${titleWithNum}`;
60
+ case 'epic_completed': return `${actor} completed epic ${titleWithNum}`;
61
+ case 'epic_status_changed': {
62
+ const newStatus = event.metadata?.newStatus || '';
63
+ return `${actor} moved epic ${titleWithNum} to ${newStatus}`;
64
+ }
65
+ case 'epic_progress_changed': return `Progress changed on epic ${titleWithNum}`;
66
+ case 'goal_created': return `${actor} created goal '${title}'`;
67
+ case 'goal_completed': return `${actor} completed goal '${title}'`;
68
+ case 'milestone_created': return `${actor} created milestone '${title}'`;
69
+ case 'milestone_completed': return `${actor} completed milestone '${title}'`;
70
+ case 'milestone_status_changed': {
71
+ const newStatus = event.metadata?.newStatus || '';
72
+ return `${actor} moved milestone '${title}' to ${newStatus}`;
73
+ }
74
+ case 'milestone_updated': return `${actor} updated milestone '${title}'`;
75
+ case 'milestone_deleted': return `${actor} deleted milestone '${title}'`;
76
+ case 'document_created': return `${actor} created document '${title}'`;
77
+ case 'document_updated': return `${actor} updated document '${title}'`;
78
+ default: return event.description || `${actor} performed ${event.eventType} on ${event.entityType} '${title}'`;
79
+ }
80
+ }
81
+
82
+ export async function getProjectChanges(args) {
83
+ const { projectId, since, entityTypes, eventTypes, limit } = args;
84
+
85
+ if (!projectId) {
86
+ throw new Error('projectId is required');
87
+ }
88
+
89
+ const params = {
90
+ projectId,
91
+ since: parseSinceParam(since),
92
+ limit: Math.min(limit || 20, 100),
93
+ includeSummary: true,
94
+ };
95
+
96
+ if (entityTypes) params.entityType = entityTypes;
97
+ if (eventTypes) params.eventType = eventTypes;
98
+
99
+ const result = await callZephlyAPI('mcpGetProjectChanges', params);
100
+
101
+ // Enrich events with human-readable descriptions
102
+ const events = (result.events || []).map(event => ({
103
+ ...event,
104
+ humanDescription: formatEventDescription(event),
105
+ }));
106
+
107
+ return {
108
+ summary: result.summary || null,
109
+ totalCount: result.totalCount || 0,
110
+ events,
111
+ };
112
+ }
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Background Agents Handlers (E-150)
3
+ *
4
+ * Thin wrappers over /api/mcp/v1/agent-* — all real logic lives in
5
+ * api/internal/core/agents. These tools let an MCP client review and act on
6
+ * what the background agents staged in agent_suggestions.
7
+ */
8
+
9
+ import { callZephlyAPI } from '../lib/http-client.js';
10
+
11
+ export async function listAgentSuggestions({
12
+ limit, entityType, entityId, action, agentType, minConfidence,
13
+ } = {}) {
14
+ const params = {};
15
+ if (limit !== undefined) params.limit = limit;
16
+ if (entityType) params.entityType = entityType;
17
+ if (entityId) params.entityId = entityId;
18
+ if (action) params.action = action;
19
+ if (agentType) params.agentType = agentType;
20
+ if (minConfidence !== undefined) params.minConfidence = minConfidence;
21
+ return callZephlyAPI('mcpListAgentSuggestions', params);
22
+ }
23
+
24
+ /**
25
+ * Accept and/or reject a batch of suggestions in one tool call.
26
+ *
27
+ * The API has no bulk endpoint, so this fans out sequentially — but from the
28
+ * agent's side it is ONE call, which is the point. Clearing a queue item by
29
+ * item costs a round trip each, and anything that expensive gets skipped;
30
+ * suggestions then pile up until someone bulk-dismisses them unread.
31
+ *
32
+ * Sequential rather than parallel on purpose: accepting runs a real side effect
33
+ * (inserting the link), and the dedup/quota bookkeeping behind it is easier to
34
+ * reason about serialized. These batches are small — a handful per task.
35
+ *
36
+ * A failure is recorded against its id and the batch continues. One bad id must
37
+ * not cost the rest.
38
+ */
39
+ export async function resolveLinkSuggestions({ accept = [], reject = [], reviewNote } = {}) {
40
+ const accepted = [];
41
+ const rejected = [];
42
+ const failed = [];
43
+
44
+ for (const id of accept) {
45
+ if (!id) continue;
46
+ try {
47
+ await acceptAgentSuggestion({ id, reviewNote });
48
+ accepted.push(id);
49
+ } catch (err) {
50
+ failed.push({ id, action: 'accept', error: err?.message || String(err) });
51
+ }
52
+ }
53
+
54
+ for (const item of reject) {
55
+ const id = typeof item === 'string' ? item : item?.id;
56
+ if (!id) continue;
57
+ try {
58
+ await rejectAgentSuggestion({ id, reviewNote: (typeof item === 'object' && item.reason) || reviewNote });
59
+ rejected.push(id);
60
+ } catch (err) {
61
+ failed.push({ id, action: 'reject', error: err?.message || String(err) });
62
+ }
63
+ }
64
+
65
+ return { accepted, rejected, failed, resolved: accepted.length + rejected.length };
66
+ }
67
+
68
+ export async function acceptAgentSuggestion({ id, reviewNote } = {}) {
69
+ if (!id) throw new Error('accept_agent_suggestion: id is required');
70
+ const body = { id };
71
+ if (reviewNote) body.reviewNote = reviewNote;
72
+ return callZephlyAPI('mcpAcceptAgentSuggestion', body);
73
+ }
74
+
75
+ export async function rejectAgentSuggestion({ id, reviewNote } = {}) {
76
+ if (!id) throw new Error('reject_agent_suggestion: id is required');
77
+ const body = { id };
78
+ if (reviewNote) body.reviewNote = reviewNote;
79
+ return callZephlyAPI('mcpRejectAgentSuggestion', body);
80
+ }
81
+
82
+ export async function runAgentNow({ agentType, scope } = {}) {
83
+ if (!agentType) throw new Error('run_agent_now: agentType is required');
84
+ const body = { agentType };
85
+ if (scope) body.scope = scope;
86
+ return callZephlyAPI('mcpRunAgentNow', body);
87
+ }
88
+
89
+ export async function configureAgent({ agentType, enabled, cadenceSeconds } = {}) {
90
+ if (!agentType) throw new Error('configure_agent: agentType is required');
91
+ const body = { agentType };
92
+ if (enabled !== undefined) body.enabled = enabled;
93
+ if (cadenceSeconds !== undefined) body.cadenceSeconds = cadenceSeconds;
94
+ return callZephlyAPI('mcpConfigureAgent', body);
95
+ }
@@ -0,0 +1,55 @@
1
+ /**
2
+ * AI Intelligence Handlers
3
+ * Handler functions for project insights and intelligent analysis
4
+ *
5
+ * Consolidated: getAiInsights dispatches by type param.
6
+ */
7
+
8
+ import { callZephlyAPI } from '../lib/http-client.js';
9
+
10
+ /**
11
+ * Dispatch get_ai_insights by type to the appropriate handler
12
+ */
13
+ export async function getAiInsights(args) {
14
+ const { type, ...params } = args;
15
+ switch (type) {
16
+ case 'project_insights': return getProjectInsights(params);
17
+ case 'suggest_next': return suggestNextActions(params);
18
+ case 'dependency_graph': return analyzeDependencyGraph(params);
19
+ case 'build_failure': return analyzeBuildFailure(params);
20
+ case 'deployment_risk': return predictDeploymentRisk(params);
21
+ default: throw new Error(`Unknown insight type: ${type}`);
22
+ }
23
+ }
24
+
25
+ // --- Private helpers ---
26
+
27
+ async function getProjectInsights(args) {
28
+ return callZephlyAPI('mcpGetProjectInsights', args);
29
+ }
30
+
31
+ async function suggestNextActions(args) {
32
+ return callZephlyAPI('mcpSuggestNextActions', args);
33
+ }
34
+
35
+ async function analyzeDependencyGraph(args) {
36
+ return callZephlyAPI('mcpAnalyzeDependencyGraph', args);
37
+ }
38
+
39
+ async function analyzeBuildFailure(args) {
40
+ return callZephlyAPI('mcpAnalyzeBuildFailure', args);
41
+ }
42
+
43
+ async function predictDeploymentRisk(args) {
44
+ return callZephlyAPI('mcpPredictDeploymentRisk', args);
45
+ }
46
+
47
+ export async function estimateTask(args) {
48
+ const { taskId } = args;
49
+ return callZephlyAPI('mcpEstimateTask', { taskId });
50
+ }
51
+
52
+ export async function inferDependencies(args) {
53
+ const { projectId } = args;
54
+ return callZephlyAPI('mcpGetInferredDependencies', { projectId });
55
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Attachment Handlers (E-21 #158)
3
+ * Handler functions for attachment-related MCP tools. Read/manage only —
4
+ * uploads go through the app's two-phase signed-URL flow, not MCP.
5
+ */
6
+
7
+ import { callZephlyAPI } from '../lib/http-client.js';
8
+
9
+ /**
10
+ * List attachments for a single entity (entityType + entityId) or the
11
+ * project-wide aggregate (projectId). Validation of the mutually-exclusive
12
+ * modes is enforced server-side.
13
+ */
14
+ export async function listAttachments(args) {
15
+ return callZephlyAPI('mcpListAttachments', args);
16
+ }
17
+
18
+ /**
19
+ * Get a signed download/preview URL for an attachment.
20
+ */
21
+ export async function getAttachmentUrl(args) {
22
+ return callZephlyAPI('mcpGetAttachmentUrl', args);
23
+ }
24
+
25
+ /**
26
+ * Delete an attachment (also removes the stored file and releases usage).
27
+ */
28
+ export async function deleteAttachment(args) {
29
+ return callZephlyAPI('mcpDeleteAttachment', args);
30
+ }
@@ -0,0 +1,169 @@
1
+ /**
2
+ * Catalog Handlers (E-215)
3
+ * Handler functions for the catalog MCP tools. These are thin wrappers over
4
+ * /api/mcp/v1/catalogs; validation, checksum-gating, and version bumping all
5
+ * happen server-side in core/catalogs.Service.
6
+ *
7
+ * A Catalog is a generalized, code-derived catalog; its versions are immutable
8
+ * { columns, items } snapshots. Agents read the current snapshot (get_catalog)
9
+ * to understand a catalog and push new snapshots after changing its source of
10
+ * truth in code (capture-at-build).
11
+ */
12
+
13
+ import { callZephlyAPI } from '../lib/http-client.js';
14
+
15
+ /**
16
+ * Dispatch manage_catalog actions.
17
+ */
18
+ export async function manageCatalog(args) {
19
+ const { action, ...params } = args;
20
+ switch (action) {
21
+ case 'create': return createCatalog(params);
22
+ case 'update': return updateCatalog(params);
23
+ case 'delete': return deleteCatalog(params);
24
+ case 'link': return params.itemKey ? linkCatalogItem(params) : linkCatalogArtifact(params);
25
+ case 'unlink': return params.itemKey ? unlinkCatalogItem(params) : unlinkCatalogArtifact(params);
26
+ case 'snapshot': return snapshotCatalog(params);
27
+ default:
28
+ throw new Error(`Unknown action: ${action}. Expected create, update, delete, link, unlink, or snapshot.`);
29
+ }
30
+ }
31
+
32
+ /**
33
+ * Retrieve a single catalog and, by default, its current snapshot.
34
+ * - version → that specific version's full snapshot (result.version)
35
+ * - else (unless metadataOnly) → the current version's full snapshot (result.currentVersion)
36
+ * - includeVersions → version metadata history (result.versions)
37
+ *
38
+ * The current-version fetch is best-effort: a freshly created catalog has no
39
+ * snapshot yet, so a 404 there leaves currentVersion null instead of failing.
40
+ */
41
+ export async function getCatalog(args) {
42
+ const { catalogId, version, includeVersions, versionsLimit, metadataOnly } = args;
43
+ if (!catalogId) {
44
+ throw new Error('catalogId is required');
45
+ }
46
+
47
+ const result = await callZephlyAPI('mcpGetCatalog', { catalogId });
48
+
49
+ if (version != null) {
50
+ result.version = await callZephlyAPI('mcpGetCatalogVersion', { catalogId, version });
51
+ } else if (!metadataOnly) {
52
+ try {
53
+ result.currentVersion = await callZephlyAPI('mcpGetCurrentCatalog', { catalogId });
54
+ } catch {
55
+ // No snapshot taken yet — leave the live catalog empty rather than erroring.
56
+ result.currentVersion = null;
57
+ }
58
+ }
59
+
60
+ if (includeVersions) {
61
+ const params = { catalogId };
62
+ if (versionsLimit != null) params.limit = versionsLimit;
63
+ const versions = await callZephlyAPI('mcpListCatalogVersions', params);
64
+ result.versions = versions?.versions ?? versions;
65
+ }
66
+
67
+ return result;
68
+ }
69
+
70
+ /**
71
+ * List catalogs for an organization, optionally filtered by project/kind, or the
72
+ * catalogs linked to a given entity (linkedType + linkedId).
73
+ */
74
+ export async function listCatalogs(args) {
75
+ const params = {};
76
+ if (args.organizationId) params.organizationId = args.organizationId;
77
+ if (args.projectId) params.projectId = args.projectId;
78
+ if (args.kind) params.kind = args.kind;
79
+ if (args.linkedType) params.linkedType = args.linkedType;
80
+ if (args.linkedId) params.linkedId = args.linkedId;
81
+ if (args.limit != null) params.limit = args.limit;
82
+ return callZephlyAPI('mcpListCatalogs', params);
83
+ }
84
+
85
+ /**
86
+ * Diff two versions of a catalog (added/removed items + per-item changes).
87
+ */
88
+ export async function getCatalogDiff(args) {
89
+ const { catalogId, from, to } = args;
90
+ return callZephlyAPI('mcpDiffCatalog', { catalogId, from, to });
91
+ }
92
+
93
+ /**
94
+ * List a catalog's entries that carry item-level links (E-218), each with its
95
+ * work links and external URLs attached (compact).
96
+ */
97
+ export async function listCatalogItems(args) {
98
+ const { catalogId } = args;
99
+ if (!catalogId) {
100
+ throw new Error('catalogId is required');
101
+ }
102
+ return callZephlyAPI('mcpListCatalogItems', { catalogId });
103
+ }
104
+
105
+ // --- Private helpers ---
106
+
107
+ async function createCatalog(args) {
108
+ return callZephlyAPI('mcpCreateCatalog', args);
109
+ }
110
+
111
+ async function updateCatalog(args) {
112
+ return callZephlyAPI('mcpUpdateCatalog', args);
113
+ }
114
+
115
+ async function deleteCatalog({ catalogId }) {
116
+ return callZephlyAPI('mcpDeleteCatalog', { catalogId });
117
+ }
118
+
119
+ async function linkCatalogArtifact({ catalogId, targetType, targetId }) {
120
+ return callZephlyAPI('mcpLinkCatalog', { catalogId, targetType, targetId });
121
+ }
122
+
123
+ async function unlinkCatalogArtifact({ catalogId, targetType, targetId }) {
124
+ return callZephlyAPI('mcpUnlinkCatalog', { catalogId, targetType, targetId });
125
+ }
126
+
127
+ // Item-level links (E-218): itemKey targets a single entry. With a url it's an
128
+ // external-URL link; otherwise it's an internal work link (targetType/targetId).
129
+ async function linkCatalogItem({ catalogId, itemKey, targetType, targetId, url, label }) {
130
+ const params = { catalogId, itemKey };
131
+ if (url) {
132
+ params.url = url;
133
+ if (label) params.label = label;
134
+ } else {
135
+ params.targetType = targetType;
136
+ params.targetId = targetId;
137
+ }
138
+ return callZephlyAPI('mcpLinkCatalogItem', params);
139
+ }
140
+
141
+ async function unlinkCatalogItem({ catalogId, itemKey, targetType, targetId, url }) {
142
+ const params = { catalogId, itemKey };
143
+ if (url) {
144
+ params.url = url;
145
+ } else {
146
+ params.targetType = targetType;
147
+ params.targetId = targetId;
148
+ }
149
+ return callZephlyAPI('mcpUnlinkCatalogItem', params);
150
+ }
151
+
152
+ /**
153
+ * Push a new version of a catalog's contents.
154
+ *
155
+ * Two modes (E-226). Replace (the default) sends the whole { columns, items }.
156
+ * Patch sends only upsertItems/removeKeys and the server merges them onto the
157
+ * current version — the cheap path for incremental capture, since a full
158
+ * snapshot has to be generated token by token. `snapshot` is still forwarded in
159
+ * patch mode because its columns/meta are honoured there.
160
+ */
161
+ async function snapshotCatalog({ catalogId, mode, snapshot, upsertItems, removeKeys, source }) {
162
+ const params = { catalogId };
163
+ if (mode) params.mode = mode;
164
+ if (snapshot) params.snapshot = snapshot;
165
+ if (upsertItems) params.upsertItems = upsertItems;
166
+ if (removeKeys) params.removeKeys = removeKeys;
167
+ if (source) params.source = source;
168
+ return callZephlyAPI('mcpSnapshotCatalog', params);
169
+ }