@velt-js/mcp-installer 0.1.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.
- package/README.md +373 -0
- package/bin/mcp-server.js +22 -0
- package/package.json +42 -0
- package/src/index.js +754 -0
- package/src/tools/orchestrator.js +299 -0
- package/src/tools/unified-installer.js +886 -0
- package/src/utils/cli.js +380 -0
- package/src/utils/comment-detector.js +305 -0
- package/src/utils/config.js +149 -0
- package/src/utils/framework-detection.js +262 -0
- package/src/utils/header-positioning.js +146 -0
- package/src/utils/host-app-discovery.js +1000 -0
- package/src/utils/integration.js +803 -0
- package/src/utils/plan-formatter.js +1698 -0
- package/src/utils/screenshot.js +151 -0
- package/src/utils/use-client.js +366 -0
- package/src/utils/validation.js +556 -0
- package/src/utils/velt-docs-fetcher.js +288 -0
- package/src/utils/velt-docs-urls.js +140 -0
- package/src/utils/velt-mcp-client.js +202 -0
- package/src/utils/velt-mcp.js +718 -0
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Velt Docs Fetcher
|
|
3
|
+
*
|
|
4
|
+
* Fetches Velt documentation from markdown (.md) URLs with fallback to Velt Docs MCP.
|
|
5
|
+
* For post-installation questions/troubleshooting, AI should query Velt Docs MCP directly.
|
|
6
|
+
*
|
|
7
|
+
* Source Priority:
|
|
8
|
+
* 1. Agent Skills Libraries (installed via `npx skills add velt-js/agent-skills`)
|
|
9
|
+
* - Referenced by name in plan output; AI editor loads them automatically
|
|
10
|
+
* 2. Docs URLs (docs.velt.dev markdown) - for features without skills coverage
|
|
11
|
+
* 3. Velt Docs MCP - only on explicit user follow-up questions
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { getDocUrl, getDocMarkdownUrl } from './velt-docs-urls.js';
|
|
15
|
+
import { queryVeltDocsMCP } from './velt-mcp-client.js';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Maps Velt features to their corresponding Agent Skill names.
|
|
19
|
+
* Skills are installed via `npx skills add velt-js/agent-skills` and
|
|
20
|
+
* loaded into the AI editor's context automatically.
|
|
21
|
+
*
|
|
22
|
+
* The MCP references these by name in plan output — it does NOT read skill files.
|
|
23
|
+
*/
|
|
24
|
+
export const FEATURE_SKILL_MAP = {
|
|
25
|
+
// Setup / Provider / Auth / Document
|
|
26
|
+
setup: 'velt-setup-best-practices',
|
|
27
|
+
provider: 'velt-setup-best-practices',
|
|
28
|
+
auth: 'velt-setup-best-practices',
|
|
29
|
+
document: 'velt-setup-best-practices',
|
|
30
|
+
|
|
31
|
+
// Comments (all types)
|
|
32
|
+
comments: 'velt-comments-best-practices',
|
|
33
|
+
|
|
34
|
+
// CRDT (all editor types)
|
|
35
|
+
crdt: 'velt-crdt-best-practices',
|
|
36
|
+
|
|
37
|
+
// Notifications
|
|
38
|
+
notifications: 'velt-notifications-best-practices',
|
|
39
|
+
|
|
40
|
+
// Features WITHOUT skills coverage — use docs URLs
|
|
41
|
+
// presence: null,
|
|
42
|
+
// cursors: null,
|
|
43
|
+
// recorder: null,
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Returns the Agent Skill name for a given feature, or null if no skill covers it.
|
|
48
|
+
*
|
|
49
|
+
* @param {string} feature - Feature name (comments, crdt, notifications, presence, etc.)
|
|
50
|
+
* @param {string} [subtype] - Optional subtype (freestyle, tiptap, etc.) — unused for mapping but kept for API consistency
|
|
51
|
+
* @returns {string|null} Skill name or null
|
|
52
|
+
*/
|
|
53
|
+
export function getSkillForFeature(feature, subtype = null) {
|
|
54
|
+
return FEATURE_SKILL_MAP[feature] || null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Returns an array of { feature, skillName } objects for all features that have skills,
|
|
59
|
+
* plus { feature, docsUrl } for features that fall back to docs.
|
|
60
|
+
*
|
|
61
|
+
* @param {string[]} features - Array of feature names
|
|
62
|
+
* @param {Object} [options] - Options
|
|
63
|
+
* @param {string} [options.commentType] - Comment subtype
|
|
64
|
+
* @param {string} [options.crdtEditorType] - CRDT editor subtype
|
|
65
|
+
* @returns {Array<{feature: string, skillName?: string, docsUrl?: string}>}
|
|
66
|
+
*/
|
|
67
|
+
export function getSkillReferences(features, options = {}) {
|
|
68
|
+
const { commentType, crdtEditorType } = options;
|
|
69
|
+
const refs = [];
|
|
70
|
+
|
|
71
|
+
// Always include setup skill
|
|
72
|
+
refs.push({
|
|
73
|
+
feature: 'setup',
|
|
74
|
+
skillName: FEATURE_SKILL_MAP.setup,
|
|
75
|
+
description: 'VeltProvider setup, authentication, document identity, project structure',
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
for (const feature of features) {
|
|
79
|
+
const skill = getSkillForFeature(feature);
|
|
80
|
+
if (skill) {
|
|
81
|
+
const subtype = feature === 'comments' ? commentType : feature === 'crdt' ? crdtEditorType : null;
|
|
82
|
+
refs.push({
|
|
83
|
+
feature: subtype ? `${feature} (${subtype})` : feature,
|
|
84
|
+
skillName: skill,
|
|
85
|
+
});
|
|
86
|
+
} else {
|
|
87
|
+
const subtype = feature === 'comments' ? commentType : feature === 'crdt' ? crdtEditorType : null;
|
|
88
|
+
refs.push({
|
|
89
|
+
feature: subtype ? `${feature} (${subtype})` : feature,
|
|
90
|
+
docsUrl: getDocMarkdownUrl(feature, subtype),
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return refs;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Fetches implementation details for a comment type
|
|
100
|
+
*
|
|
101
|
+
* Strategy:
|
|
102
|
+
* 1. Fetch from docs.velt.dev markdown URLs directly (primary)
|
|
103
|
+
* 2. If that fails, use Velt Docs MCP (fallback)
|
|
104
|
+
* 3. If both fail, return basic structure with doc URL reference
|
|
105
|
+
*
|
|
106
|
+
* @param {Object} options - Fetch options
|
|
107
|
+
* @param {string} options.commentType - Comment type (freestyle, popover, etc.)
|
|
108
|
+
* @param {Object} [options.mcpClient] - Optional MCP client for fallback
|
|
109
|
+
* @returns {Promise<Object>} Implementation details
|
|
110
|
+
*/
|
|
111
|
+
export async function fetchCommentImplementation(options) {
|
|
112
|
+
const { commentType, mcpClient } = options;
|
|
113
|
+
|
|
114
|
+
console.error(`🔍 Fetching ${commentType} implementation from .md URL...`);
|
|
115
|
+
|
|
116
|
+
// Step 1: Fetch from markdown URL (primary)
|
|
117
|
+
console.error(' 📄 Fetching from docs.velt.dev markdown...');
|
|
118
|
+
try {
|
|
119
|
+
const urlResult = await fetchFromMarkdownUrl('comments', commentType);
|
|
120
|
+
console.error(' ✅ Got documentation from markdown URL');
|
|
121
|
+
return urlResult;
|
|
122
|
+
} catch (error) {
|
|
123
|
+
console.error(` ⚠️ Markdown fetch failed: ${error.message}`);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Step 2: Fallback to Velt Docs MCP if .md URL failed
|
|
127
|
+
console.error(' 📚 Falling back to Velt Docs MCP...');
|
|
128
|
+
try {
|
|
129
|
+
const query = `How do I implement ${commentType} comments in Next.js?`;
|
|
130
|
+
const mcpResult = await queryVeltDocsMCP(query);
|
|
131
|
+
|
|
132
|
+
if (mcpResult.success) {
|
|
133
|
+
console.error(' ✅ Got implementation from Velt Docs MCP');
|
|
134
|
+
return {
|
|
135
|
+
success: true,
|
|
136
|
+
source: 'velt-docs-mcp',
|
|
137
|
+
docUrl: getDocUrl('comments', commentType),
|
|
138
|
+
mdUrl: getDocMarkdownUrl('comments', commentType),
|
|
139
|
+
data: mcpResult.data,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
} catch (error) {
|
|
143
|
+
console.error(` ⚠️ Velt Docs MCP failed: ${error.message}`);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Step 3: Return basic structure with doc URL
|
|
147
|
+
console.error(' 📖 Returning basic structure with doc URL reference');
|
|
148
|
+
return getBasicStructure('comments', commentType);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Fetches documentation from markdown URL
|
|
153
|
+
*/
|
|
154
|
+
async function fetchFromMarkdownUrl(feature, subtype = null) {
|
|
155
|
+
const docUrl = getDocUrl(feature, subtype);
|
|
156
|
+
const mdUrl = getDocMarkdownUrl(feature, subtype);
|
|
157
|
+
|
|
158
|
+
try {
|
|
159
|
+
console.error(` Fetching from: ${mdUrl}`);
|
|
160
|
+
const response = await fetch(mdUrl);
|
|
161
|
+
|
|
162
|
+
if (!response.ok) {
|
|
163
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const markdown = await response.text();
|
|
167
|
+
|
|
168
|
+
return {
|
|
169
|
+
success: true,
|
|
170
|
+
source: 'docs-md-url',
|
|
171
|
+
docUrl,
|
|
172
|
+
mdUrl,
|
|
173
|
+
data: {
|
|
174
|
+
markdown,
|
|
175
|
+
docUrl,
|
|
176
|
+
},
|
|
177
|
+
};
|
|
178
|
+
} catch (error) {
|
|
179
|
+
throw new Error(`Failed to fetch from ${mdUrl}: ${error.message}`);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Returns a basic structure with doc URL reference
|
|
185
|
+
*/
|
|
186
|
+
function getBasicStructure(feature, subtype = null) {
|
|
187
|
+
const docUrl = getDocUrl(feature, subtype);
|
|
188
|
+
const mdUrl = getDocMarkdownUrl(feature, subtype);
|
|
189
|
+
|
|
190
|
+
return {
|
|
191
|
+
success: true,
|
|
192
|
+
source: 'basic-structure',
|
|
193
|
+
docUrl,
|
|
194
|
+
mdUrl,
|
|
195
|
+
data: {
|
|
196
|
+
docUrl,
|
|
197
|
+
mdUrl,
|
|
198
|
+
note: 'Please refer to the documentation URL for implementation details',
|
|
199
|
+
},
|
|
200
|
+
warning: 'Could not fetch documentation - providing URL references only',
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Fetches implementation details for any Velt feature
|
|
206
|
+
*
|
|
207
|
+
* Strategy:
|
|
208
|
+
* 1. Fetch from docs.velt.dev markdown URLs directly (primary)
|
|
209
|
+
* 2. If that fails, use Velt Docs MCP (fallback)
|
|
210
|
+
* 3. If both fail, return basic structure with doc URL reference
|
|
211
|
+
*
|
|
212
|
+
* @param {Object} options - Fetch options
|
|
213
|
+
* @param {string} options.feature - Feature name (comments, presence, cursors, notifications, recorder, crdt)
|
|
214
|
+
* @param {string} [options.subtype] - Optional subtype (e.g., 'freestyle' for comments, 'tiptap' for crdt)
|
|
215
|
+
* @param {Object} [options.mcpClient] - Optional MCP client for fallback
|
|
216
|
+
* @returns {Promise<Object>} Implementation details
|
|
217
|
+
*/
|
|
218
|
+
export async function fetchFeatureImplementation(options) {
|
|
219
|
+
const { feature, subtype = null, mcpClient } = options;
|
|
220
|
+
|
|
221
|
+
const featureDisplay = subtype ? `${feature}/${subtype}` : feature;
|
|
222
|
+
console.error(`🔍 Fetching ${featureDisplay} implementation from .md URL...`);
|
|
223
|
+
|
|
224
|
+
// Step 1: Fetch from markdown URL (primary)
|
|
225
|
+
console.error(' 📄 Fetching from docs.velt.dev markdown...');
|
|
226
|
+
try {
|
|
227
|
+
const urlResult = await fetchFromMarkdownUrl(feature, subtype);
|
|
228
|
+
console.error(' ✅ Got documentation from markdown URL');
|
|
229
|
+
return urlResult;
|
|
230
|
+
} catch (error) {
|
|
231
|
+
console.error(` ⚠️ Markdown fetch failed: ${error.message}`);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Step 2: Fallback to Velt Docs MCP if .md URL failed
|
|
235
|
+
console.error(' 📚 Falling back to Velt Docs MCP...');
|
|
236
|
+
try {
|
|
237
|
+
const query = subtype
|
|
238
|
+
? `How do I implement ${subtype} ${feature} in Next.js?`
|
|
239
|
+
: `How do I implement ${feature} in Next.js?`;
|
|
240
|
+
const mcpResult = await queryVeltDocsMCP(query);
|
|
241
|
+
|
|
242
|
+
if (mcpResult.success) {
|
|
243
|
+
console.error(' ✅ Got implementation from Velt Docs MCP');
|
|
244
|
+
return {
|
|
245
|
+
success: true,
|
|
246
|
+
source: 'velt-docs-mcp',
|
|
247
|
+
docUrl: getDocUrl(feature, subtype),
|
|
248
|
+
mdUrl: getDocMarkdownUrl(feature, subtype),
|
|
249
|
+
data: mcpResult.data,
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
} catch (error) {
|
|
253
|
+
console.error(` ⚠️ Velt Docs MCP failed: ${error.message}`);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Step 3: Return basic structure with doc URL
|
|
257
|
+
console.error(' 📖 Returning basic structure with doc URL reference');
|
|
258
|
+
return getBasicStructure(feature, subtype);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Fetches implementation details for CRDT feature
|
|
263
|
+
*
|
|
264
|
+
* @param {Object} options - Fetch options
|
|
265
|
+
* @param {string} options.editorType - Editor type (tiptap, codemirror, blocknote)
|
|
266
|
+
* @param {Object} [options.mcpClient] - Optional MCP client for fallback
|
|
267
|
+
* @returns {Promise<Object>} Implementation details
|
|
268
|
+
*/
|
|
269
|
+
export async function fetchCrdtImplementation(options) {
|
|
270
|
+
const { editorType, mcpClient } = options;
|
|
271
|
+
|
|
272
|
+
console.error(`🔍 Fetching CRDT ${editorType} implementation from .md URL...`);
|
|
273
|
+
|
|
274
|
+
return fetchFeatureImplementation({
|
|
275
|
+
feature: 'crdt',
|
|
276
|
+
subtype: editorType,
|
|
277
|
+
mcpClient,
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
export default {
|
|
282
|
+
fetchCommentImplementation,
|
|
283
|
+
fetchFeatureImplementation,
|
|
284
|
+
fetchCrdtImplementation,
|
|
285
|
+
FEATURE_SKILL_MAP,
|
|
286
|
+
getSkillForFeature,
|
|
287
|
+
getSkillReferences,
|
|
288
|
+
};
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Velt Documentation URLs
|
|
3
|
+
*
|
|
4
|
+
* Comprehensive mapping of Velt feature documentation URLs for fallback when
|
|
5
|
+
* Velt Docs MCP is unavailable.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* All Velt feature documentation URLs
|
|
10
|
+
*/
|
|
11
|
+
export const VELT_DOCS_URLS = {
|
|
12
|
+
// Getting Started
|
|
13
|
+
quickstart: 'https://docs.velt.dev/get-started/quickstart',
|
|
14
|
+
keyConcepts: 'https://docs.velt.dev/key-concepts/overview',
|
|
15
|
+
|
|
16
|
+
// Comments
|
|
17
|
+
comments: {
|
|
18
|
+
setup: {
|
|
19
|
+
freestyle: 'https://docs.velt.dev/async-collaboration/comments/setup/freestyle',
|
|
20
|
+
popover: 'https://docs.velt.dev/async-collaboration/comments/setup/popover',
|
|
21
|
+
page: 'https://docs.velt.dev/async-collaboration/comments/setup/page',
|
|
22
|
+
text: 'https://docs.velt.dev/async-collaboration/comments/setup/text',
|
|
23
|
+
inline: 'https://docs.velt.dev/async-collaboration/comments/setup/inline-comments',
|
|
24
|
+
// Purpose-built library integrations
|
|
25
|
+
tiptap: 'https://docs.velt.dev/async-collaboration/comments/setup/tiptap',
|
|
26
|
+
lexical: 'https://docs.velt.dev/async-collaboration/comments/setup/lexical',
|
|
27
|
+
slate: 'https://docs.velt.dev/async-collaboration/comments/setup/slatejs',
|
|
28
|
+
},
|
|
29
|
+
customizeBehavior: 'https://docs.velt.dev/async-collaboration/comments/customize-behavior',
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
// Presence
|
|
33
|
+
presence: {
|
|
34
|
+
setup: 'https://docs.velt.dev/realtime-collaboration/presence/setup',
|
|
35
|
+
customizeBehavior: 'https://docs.velt.dev/realtime-collaboration/presence/customize-behavior',
|
|
36
|
+
},
|
|
37
|
+
|
|
38
|
+
// Notifications
|
|
39
|
+
notifications: {
|
|
40
|
+
setup: 'https://docs.velt.dev/async-collaboration/notifications/setup',
|
|
41
|
+
customizeBehavior: 'https://docs.velt.dev/async-collaboration/notifications/customize-behavior',
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
// Recorder
|
|
45
|
+
recorder: {
|
|
46
|
+
setup: 'https://docs.velt.dev/async-collaboration/recorder/setup',
|
|
47
|
+
customizeBehavior: 'https://docs.velt.dev/async-collaboration/recorder/customize-behavior',
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
// Cursors
|
|
51
|
+
cursors: {
|
|
52
|
+
setup: 'https://docs.velt.dev/realtime-collaboration/cursors/setup',
|
|
53
|
+
customizeBehavior: 'https://docs.velt.dev/realtime-collaboration/cursors/customize-behavior',
|
|
54
|
+
},
|
|
55
|
+
|
|
56
|
+
// CRDT (Collaborative Real-Time Document editing)
|
|
57
|
+
crdt: {
|
|
58
|
+
setup: {
|
|
59
|
+
tiptap: 'https://docs.velt.dev/realtime-collaboration/crdt/setup/tiptap',
|
|
60
|
+
codemirror: 'https://docs.velt.dev/realtime-collaboration/crdt/setup/codemirror',
|
|
61
|
+
blocknote: 'https://docs.velt.dev/realtime-collaboration/crdt/setup/blocknote',
|
|
62
|
+
reactflow: 'https://docs.velt.dev/realtime-collaboration/crdt/setup/reactflow',
|
|
63
|
+
},
|
|
64
|
+
overview: 'https://docs.velt.dev/multiplayer-editing/overview',
|
|
65
|
+
},
|
|
66
|
+
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Gets the documentation URL for a specific feature or comment type
|
|
71
|
+
*
|
|
72
|
+
* @param {string} feature - Feature name (comments, presence, cursors, notifications, recorder)
|
|
73
|
+
* @param {string} [subtype] - Optional subtype (for comments: freestyle, popover, page, text, inline, tiptap, lexical, slate)
|
|
74
|
+
* @param {string} [page='setup'] - Page type (setup, customizeBehavior)
|
|
75
|
+
* @returns {string} Documentation URL
|
|
76
|
+
*/
|
|
77
|
+
export function getDocUrl(feature, subtype = null, page = 'setup') {
|
|
78
|
+
const featureConfig = VELT_DOCS_URLS[feature];
|
|
79
|
+
|
|
80
|
+
if (!featureConfig) {
|
|
81
|
+
console.warn(`Unknown feature: ${feature}, using quickstart`);
|
|
82
|
+
return VELT_DOCS_URLS.quickstart;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// If it's a simple string URL, return it
|
|
86
|
+
if (typeof featureConfig === 'string') {
|
|
87
|
+
return featureConfig;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Handle comments with subtypes (freestyle, popover, etc.)
|
|
91
|
+
if (feature === 'comments' && subtype) {
|
|
92
|
+
if (featureConfig.setup && featureConfig.setup[subtype]) {
|
|
93
|
+
return featureConfig.setup[subtype];
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Handle CRDT with subtypes (tiptap, codemirror, blocknote)
|
|
98
|
+
if (feature === 'crdt' && subtype) {
|
|
99
|
+
if (featureConfig.setup && featureConfig.setup[subtype]) {
|
|
100
|
+
return featureConfig.setup[subtype];
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Handle page navigation (setup, customizeBehavior)
|
|
105
|
+
if (featureConfig[page]) {
|
|
106
|
+
return featureConfig[page];
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Fallback to setup
|
|
110
|
+
return featureConfig.setup || VELT_DOCS_URLS.quickstart;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Gets the markdown URL for a specific feature or comment type
|
|
115
|
+
*
|
|
116
|
+
* @param {string} feature - Feature name
|
|
117
|
+
* @param {string} [subtype] - Optional subtype (for comments)
|
|
118
|
+
* @param {string} [page='setup'] - Page type
|
|
119
|
+
* @returns {string} Markdown documentation URL
|
|
120
|
+
*/
|
|
121
|
+
export function getDocMarkdownUrl(feature, subtype = null, page = 'setup') {
|
|
122
|
+
const baseUrl = getDocUrl(feature, subtype, page);
|
|
123
|
+
return `${baseUrl}.md`;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Gets all available comment types
|
|
128
|
+
*
|
|
129
|
+
* @returns {string[]} Array of comment type names
|
|
130
|
+
*/
|
|
131
|
+
export function getAvailableCommentTypes() {
|
|
132
|
+
return Object.keys(VELT_DOCS_URLS.comments.setup);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export default {
|
|
136
|
+
VELT_DOCS_URLS,
|
|
137
|
+
getDocUrl,
|
|
138
|
+
getDocMarkdownUrl,
|
|
139
|
+
getAvailableCommentTypes,
|
|
140
|
+
};
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Velt Docs MCP Client
|
|
3
|
+
*
|
|
4
|
+
* Attempts to query Velt Docs MCP server through proper MCP protocol
|
|
5
|
+
* Falls back to direct documentation queries if MCP is unavailable
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import https from 'https';
|
|
9
|
+
import { URL } from 'url';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Queries Velt Docs MCP server properly
|
|
13
|
+
*
|
|
14
|
+
* Since HTTP-based MCP servers require IDE client access,
|
|
15
|
+
* we try multiple strategies to get documentation patterns.
|
|
16
|
+
*
|
|
17
|
+
* @param {string} query - Query string
|
|
18
|
+
* @param {number} maxRetries - Maximum number of retries (default: 2)
|
|
19
|
+
* @returns {Promise<Object>} Patterns result
|
|
20
|
+
*/
|
|
21
|
+
export async function queryVeltDocsMCP(query, maxRetries = 2) {
|
|
22
|
+
const veltDocsMCPUrl = 'https://docs.velt.dev/mcp';
|
|
23
|
+
|
|
24
|
+
// Strategy 1: Try MCP server with proper protocol and retry logic
|
|
25
|
+
let lastError = null;
|
|
26
|
+
|
|
27
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
28
|
+
try {
|
|
29
|
+
if (attempt > 0) {
|
|
30
|
+
const backoffDelay = Math.min(1000 * Math.pow(2, attempt - 1), 3000);
|
|
31
|
+
console.error(` Retry attempt ${attempt}/${maxRetries} after ${backoffDelay}ms...`);
|
|
32
|
+
await new Promise(resolve => setTimeout(resolve, backoffDelay));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
console.error(' Attempting MCP protocol query...');
|
|
36
|
+
|
|
37
|
+
// First, get available tools with timeout
|
|
38
|
+
const toolsResponse = await makeMCPRequestWithTimeout(veltDocsMCPUrl, {
|
|
39
|
+
jsonrpc: '2.0',
|
|
40
|
+
method: 'tools/list',
|
|
41
|
+
id: 1,
|
|
42
|
+
}, 5000);
|
|
43
|
+
|
|
44
|
+
if (toolsResponse.error) {
|
|
45
|
+
throw new Error(toolsResponse.error.message);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const tools = toolsResponse.result?.tools || [];
|
|
49
|
+
console.error(` ✓ Found ${tools.length} tools`);
|
|
50
|
+
|
|
51
|
+
// Find search/query tool
|
|
52
|
+
const searchTool = tools.find(t =>
|
|
53
|
+
t.name.toLowerCase().includes('search') ||
|
|
54
|
+
t.name.toLowerCase().includes('query') ||
|
|
55
|
+
t.name.toLowerCase().includes('docs')
|
|
56
|
+
) || tools[0];
|
|
57
|
+
|
|
58
|
+
if (!searchTool) {
|
|
59
|
+
throw new Error('No search tool available');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
console.error(` Calling tool: ${searchTool.name}`);
|
|
63
|
+
|
|
64
|
+
// Call the tool with timeout
|
|
65
|
+
const toolResponse = await makeMCPRequestWithTimeout(veltDocsMCPUrl, {
|
|
66
|
+
jsonrpc: '2.0',
|
|
67
|
+
method: 'tools/call',
|
|
68
|
+
id: 2,
|
|
69
|
+
params: {
|
|
70
|
+
name: searchTool.name,
|
|
71
|
+
arguments: {
|
|
72
|
+
query: query,
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
}, 5000);
|
|
76
|
+
|
|
77
|
+
if (toolResponse.error) {
|
|
78
|
+
throw new Error(toolResponse.error.message);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
success: true,
|
|
83
|
+
data: toolResponse.result,
|
|
84
|
+
source: 'velt-docs-mcp',
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
} catch (error) {
|
|
88
|
+
lastError = error;
|
|
89
|
+
console.error(` MCP query failed (attempt ${attempt + 1}/${maxRetries + 1}): ${error.message}`);
|
|
90
|
+
|
|
91
|
+
// Don't retry on certain errors
|
|
92
|
+
if (error.message.includes('No search tool available') ||
|
|
93
|
+
error.message.includes('Failed to parse response')) {
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// All retries failed
|
|
100
|
+
throw lastError || new Error('MCP query failed after all retries');
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Makes MCP protocol request with guaranteed timeout using Promise.race()
|
|
105
|
+
*
|
|
106
|
+
* This ensures the request will ALWAYS timeout, even if the server
|
|
107
|
+
* is sending data slowly or the connection hangs.
|
|
108
|
+
*
|
|
109
|
+
* @param {string} url - MCP server URL
|
|
110
|
+
* @param {Object} data - JSON-RPC request data
|
|
111
|
+
* @param {number} timeoutMs - Timeout in milliseconds (default: 5000)
|
|
112
|
+
* @returns {Promise<Object>} MCP response
|
|
113
|
+
*/
|
|
114
|
+
function makeMCPRequestWithTimeout(url, data, timeoutMs = 5000) {
|
|
115
|
+
// Create a promise that rejects after timeout
|
|
116
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
117
|
+
setTimeout(() => {
|
|
118
|
+
reject(new Error(`Request timeout after ${timeoutMs}ms`));
|
|
119
|
+
}, timeoutMs);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
// Create the actual request promise
|
|
123
|
+
const requestPromise = makeMCPRequest(url, data, timeoutMs);
|
|
124
|
+
|
|
125
|
+
// Race between request and timeout
|
|
126
|
+
return Promise.race([requestPromise, timeoutPromise]);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Makes MCP protocol request (internal)
|
|
131
|
+
*
|
|
132
|
+
* @param {string} url - MCP server URL
|
|
133
|
+
* @param {Object} data - JSON-RPC request data
|
|
134
|
+
* @param {number} socketTimeout - Socket-level timeout (default: 5000)
|
|
135
|
+
* @returns {Promise<Object>} MCP response
|
|
136
|
+
*/
|
|
137
|
+
function makeMCPRequest(url, data, socketTimeout = 5000) {
|
|
138
|
+
return new Promise((resolve, reject) => {
|
|
139
|
+
const urlObj = new URL(url);
|
|
140
|
+
const postData = JSON.stringify(data);
|
|
141
|
+
|
|
142
|
+
const options = {
|
|
143
|
+
hostname: urlObj.hostname,
|
|
144
|
+
port: urlObj.port || 443,
|
|
145
|
+
path: urlObj.pathname,
|
|
146
|
+
method: 'POST',
|
|
147
|
+
headers: {
|
|
148
|
+
'Content-Type': 'application/json',
|
|
149
|
+
'Accept': 'application/json',
|
|
150
|
+
'Content-Length': Buffer.byteLength(postData),
|
|
151
|
+
},
|
|
152
|
+
timeout: socketTimeout,
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
const req = https.request(options, (res) => {
|
|
156
|
+
let responseData = '';
|
|
157
|
+
let responseSize = 0;
|
|
158
|
+
const maxResponseSize = 10 * 1024 * 1024; // 10MB max response size
|
|
159
|
+
|
|
160
|
+
res.on('data', (chunk) => {
|
|
161
|
+
responseSize += chunk.length;
|
|
162
|
+
|
|
163
|
+
// Prevent memory exhaustion from huge responses
|
|
164
|
+
if (responseSize > maxResponseSize) {
|
|
165
|
+
req.destroy();
|
|
166
|
+
reject(new Error(`Response too large (exceeded ${maxResponseSize} bytes)`));
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
responseData += chunk;
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
res.on('end', () => {
|
|
174
|
+
// Check for HTTP errors
|
|
175
|
+
if (res.statusCode !== 200) {
|
|
176
|
+
reject(new Error(`HTTP ${res.statusCode}: ${res.statusMessage}`));
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
try {
|
|
181
|
+
const parsed = JSON.parse(responseData);
|
|
182
|
+
resolve(parsed);
|
|
183
|
+
} catch (error) {
|
|
184
|
+
reject(new Error(`Failed to parse response: ${error.message}`));
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
req.on('error', (error) => {
|
|
190
|
+
reject(new Error(`Request error: ${error.message}`));
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
req.on('timeout', () => {
|
|
194
|
+
req.destroy();
|
|
195
|
+
reject(new Error('Socket timeout'));
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
req.write(postData);
|
|
199
|
+
req.end();
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
|