@deplens/mcp 0.1.2 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,416 @@
1
+ // parse-dts.mjs - TypeScript Compiler API based .d.ts parser
2
+ import ts from "typescript"
3
+ import fs from "fs"
4
+ import path from "path"
5
+
6
+ function getScriptKind(filePath) {
7
+ if (typeof ts.getScriptKindFromFileName === "function") {
8
+ const kind = ts.getScriptKindFromFileName(filePath)
9
+ return kind === ts.ScriptKind.Unknown ? ts.ScriptKind.TS : kind
10
+ }
11
+ return ts.ScriptKind.TS
12
+ }
13
+
14
+ /**
15
+ * Find re-exported symbols and their source files
16
+ * @param {string} dtsPath - Path to the .d.ts file
17
+ * @param {string[]} filterList - List of symbol names to find
18
+ * @returns {Map<string, string>} Map of symbol name -> source file path
19
+ */
20
+ function findReExports(dtsPath, filterList) {
21
+ const content = fs.readFileSync(dtsPath, "utf-8")
22
+ const sourceFile = ts.createSourceFile(
23
+ dtsPath,
24
+ content,
25
+ ts.ScriptTarget.Latest,
26
+ true,
27
+ getScriptKind(dtsPath),
28
+ )
29
+
30
+ const dtsDir = path.dirname(dtsPath)
31
+ const reExports = new Map()
32
+ const wildcardSources = []
33
+ const filterSet = filterList ? new Set(filterList.map((n) => n.toLowerCase())) : null
34
+
35
+ function resolveDtsPath(moduleSpec) {
36
+ // Handle .cjs/.mjs/.js -> .d.cts/.d.mts/.d.ts
37
+ let sourceFile = moduleSpec
38
+ .replace(/\.cjs$/, ".d.cts")
39
+ .replace(/\.mjs$/, ".d.mts")
40
+ .replace(/\.js$/, ".d.ts")
41
+ if (!sourceFile.endsWith(".d.ts") && !sourceFile.endsWith(".d.cts") && !sourceFile.endsWith(".d.mts")) {
42
+ // Try all extensions
43
+ const dtsCandidate = path.resolve(dtsDir, sourceFile + ".d.ts")
44
+ const ctsCandidate = path.resolve(dtsDir, sourceFile + ".d.cts")
45
+ const mtsCandidate = path.resolve(dtsDir, sourceFile + ".d.mts")
46
+ if (fs.existsSync(dtsCandidate)) return dtsCandidate
47
+ if (fs.existsSync(ctsCandidate)) return ctsCandidate
48
+ if (fs.existsSync(mtsCandidate)) return mtsCandidate
49
+ return null
50
+ }
51
+ const fullPath = path.resolve(dtsDir, sourceFile)
52
+ return fs.existsSync(fullPath) ? fullPath : null
53
+ }
54
+
55
+ function visit(node) {
56
+ // Handle: export { foo } from './foo.js'
57
+ if (ts.isExportDeclaration(node) && node.moduleSpecifier) {
58
+ const moduleSpec = node.moduleSpecifier.text
59
+
60
+ if (node.exportClause && ts.isNamedExports(node.exportClause)) {
61
+ // Named exports
62
+ for (const elem of node.exportClause.elements) {
63
+ const exportedName = elem.name.text
64
+ if (!filterSet || filterSet.has(exportedName.toLowerCase())) {
65
+ const fullPath = resolveDtsPath(moduleSpec)
66
+ if (fullPath) {
67
+ reExports.set(exportedName, fullPath)
68
+ }
69
+ }
70
+ }
71
+ } else if (!node.exportClause) {
72
+ // Wildcard: export * from './module'
73
+ const fullPath = resolveDtsPath(moduleSpec)
74
+ if (fullPath) {
75
+ wildcardSources.push(fullPath)
76
+ }
77
+ }
78
+ }
79
+ ts.forEachChild(node, visit)
80
+ }
81
+
82
+ visit(sourceFile)
83
+
84
+ // For wildcard exports, we return them all as potential sources
85
+ return { named: reExports, wildcards: wildcardSources }
86
+ }
87
+
88
+ /**
89
+ * Parse a .d.ts file and extract type information for specified symbols
90
+ * @param {string} dtsPath - Path to the .d.ts file
91
+ * @param {string[]} filterList - List of symbol names to extract (null = all)
92
+ * @returns {object} Type information
93
+ */
94
+ export function parseDtsFile(dtsPath, filterList, visited = new Set()) {
95
+ if (!fs.existsSync(dtsPath)) {
96
+ return null
97
+ }
98
+ if (visited.has(dtsPath)) {
99
+ return null
100
+ }
101
+ visited.add(dtsPath)
102
+
103
+ const content = fs.readFileSync(dtsPath, "utf-8")
104
+ const sourceFile = ts.createSourceFile(
105
+ dtsPath,
106
+ content,
107
+ ts.ScriptTarget.Latest,
108
+ true,
109
+ getScriptKind(dtsPath),
110
+ )
111
+
112
+ const typeInfo = {
113
+ functions: {},
114
+ interfaces: {},
115
+ types: {},
116
+ classes: {},
117
+ enums: {},
118
+ namespaces: {},
119
+ defaults: [],
120
+ jsdoc: {},
121
+ }
122
+
123
+ const filterSet = filterList ? new Set(filterList.map((n) => n.toLowerCase())) : null
124
+
125
+ function shouldInclude(name) {
126
+ if (!filterSet) return true
127
+ return filterSet.has(name.toLowerCase())
128
+ }
129
+
130
+ function getNodeText(node) {
131
+ return node.getText(sourceFile)
132
+ }
133
+
134
+ function formatType(typeNode, maxLen = 80) {
135
+ if (!typeNode) return "any"
136
+ const text = getNodeText(typeNode).replace(/\s+/g, " ").trim()
137
+ return text.length > maxLen ? text.substring(0, maxLen) + "..." : text
138
+ }
139
+
140
+ function formatParams(params) {
141
+ if (!params || params.length === 0) return ""
142
+
143
+ // For complex destructuring, simplify
144
+ const paramStrs = params.map((p) => {
145
+ const name = p.name ? getNodeText(p.name) : "arg"
146
+
147
+ // Handle destructuring pattern - just show "options"
148
+ if (name.startsWith("{")) {
149
+ return "options: object"
150
+ }
151
+
152
+ const optional = p.questionToken ? "?" : ""
153
+ const type = p.type ? formatType(p.type, 40) : "any"
154
+
155
+ // Simplify long type names
156
+ const shortType = type.length > 40 ? type.split("<")[0] + "<...>" : type
157
+ return `${name}${optional}: ${shortType}`
158
+ })
159
+
160
+ // If total length too long, just show count
161
+ const joined = paramStrs.join(", ")
162
+ if (joined.length > 100) {
163
+ return `${params.length} param${params.length > 1 ? "s" : ""}`
164
+ }
165
+ return joined
166
+ }
167
+
168
+ function jsDocText(comment) {
169
+ if (!comment) return ""
170
+ if (typeof comment === "string") return comment.trim()
171
+ if (Array.isArray(comment)) {
172
+ return comment.map((part) => part.text || "").join("").trim()
173
+ }
174
+ return ""
175
+ }
176
+
177
+ function collectJSDocTags(tagNodes) {
178
+ const tags = {}
179
+ if (!tagNodes || tagNodes.length === 0) return tags
180
+ for (const tag of tagNodes) {
181
+ if (!tag?.tagName?.text) continue
182
+ const tagName = tag.tagName.text
183
+ const comment = jsDocText(tag.comment)
184
+ if (!tags[tagName]) tags[tagName] = []
185
+ tags[tagName].push(comment || "")
186
+ }
187
+ return tags
188
+ }
189
+
190
+ function extractJSDoc(node) {
191
+ const entries = ts.getJSDocCommentsAndTags(node)
192
+ if (!entries || entries.length === 0) return null
193
+ let summary = ""
194
+ const tags = {}
195
+ for (const entry of entries) {
196
+ if (ts.isJSDoc(entry)) {
197
+ if (!summary) summary = jsDocText(entry.comment)
198
+ const entryTags = collectJSDocTags(entry.tags)
199
+ for (const [name, values] of Object.entries(entryTags)) {
200
+ if (!tags[name]) tags[name] = []
201
+ tags[name].push(...values)
202
+ }
203
+ } else if (ts.isJSDocTag(entry)) {
204
+ const tagName = entry.tagName?.text
205
+ if (tagName) {
206
+ const comment = jsDocText(entry.comment)
207
+ if (!tags[tagName]) tags[tagName] = []
208
+ tags[tagName].push(comment || "")
209
+ }
210
+ }
211
+ }
212
+ if (!summary && Object.keys(tags).length === 0) return null
213
+ return { summary, tags }
214
+ }
215
+
216
+ function attachJSDoc(name, node) {
217
+ if (!name || !node) return
218
+ const doc = extractJSDoc(node)
219
+ if (!doc) return
220
+ typeInfo.jsdoc[name] = doc
221
+ }
222
+
223
+ function visit(node) {
224
+ // Function declarations
225
+ if (ts.isFunctionDeclaration(node)) {
226
+ const isDefault = node.modifiers?.some((m) => m.kind === ts.SyntaxKind.DefaultKeyword)
227
+ const name = node.name?.text || (isDefault ? "default" : null)
228
+ if (name && shouldInclude(name)) {
229
+ const params = formatParams(node.parameters)
230
+ const returnType = formatType(node.type, 60)
231
+ typeInfo.functions[name] = { params, returnType }
232
+ attachJSDoc(name, node)
233
+ }
234
+ }
235
+
236
+ // Interface declarations
237
+ if (ts.isInterfaceDeclaration(node)) {
238
+ const name = node.name.text
239
+ const isDefault = node.modifiers?.some((m) => m.kind === ts.SyntaxKind.DefaultKeyword)
240
+ const exportName = isDefault ? "default" : name
241
+ if (shouldInclude(exportName)) {
242
+ const props = []
243
+ node.members.slice(0, 5).forEach((member) => {
244
+ if (ts.isPropertySignature(member) && member.name) {
245
+ const propName = getNodeText(member.name)
246
+ const optional = member.questionToken ? "?" : ""
247
+ const propType = formatType(member.type, 30)
248
+ props.push(`${propName}${optional}: ${propType}`)
249
+ }
250
+ })
251
+ if (props.length > 0) {
252
+ typeInfo.interfaces[exportName] = props
253
+ attachJSDoc(exportName, node)
254
+ }
255
+ }
256
+ }
257
+
258
+ // Type aliases
259
+ if (ts.isTypeAliasDeclaration(node)) {
260
+ const name = node.name.text
261
+ const isDefault = node.modifiers?.some((m) => m.kind === ts.SyntaxKind.DefaultKeyword)
262
+ const exportName = isDefault ? "default" : name
263
+ if (shouldInclude(exportName)) {
264
+ typeInfo.types[exportName] = formatType(node.type, 80)
265
+ attachJSDoc(exportName, node)
266
+ }
267
+ }
268
+
269
+ // Class declarations
270
+ if (ts.isClassDeclaration(node)) {
271
+ const isDefault = node.modifiers?.some((m) => m.kind === ts.SyntaxKind.DefaultKeyword)
272
+ const name = node.name?.text || (isDefault ? "default" : null)
273
+ if (name && shouldInclude(name)) {
274
+ let extendsClause = null
275
+ if (node.heritageClauses) {
276
+ for (const clause of node.heritageClauses) {
277
+ if (clause.token === ts.SyntaxKind.ExtendsKeyword && clause.types.length > 0) {
278
+ extendsClause = getNodeText(clause.types[0].expression)
279
+ }
280
+ }
281
+ }
282
+ typeInfo.classes[name] = extendsClause
283
+ attachJSDoc(name, node)
284
+ }
285
+ }
286
+
287
+ // Enum declarations
288
+ if (ts.isEnumDeclaration(node)) {
289
+ const name = node.name.text
290
+ if (shouldInclude(name)) {
291
+ const members = node.members.slice(0, 5).map((member) => getNodeText(member.name))
292
+ typeInfo.enums[name] = members
293
+ attachJSDoc(name, node)
294
+ }
295
+ }
296
+
297
+ // Namespace/module declarations
298
+ if (ts.isModuleDeclaration(node) && node.name) {
299
+ const name = getNodeText(node.name)
300
+ if (shouldInclude(name)) {
301
+ typeInfo.namespaces[name] = true
302
+ attachJSDoc(name, node)
303
+ }
304
+ }
305
+
306
+ // Variable statements (export const foo: Type)
307
+ if (ts.isVariableStatement(node)) {
308
+ const isExported = node.modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword)
309
+ if (isExported && node.declarationList.declarations) {
310
+ for (const decl of node.declarationList.declarations) {
311
+ if (ts.isIdentifier(decl.name)) {
312
+ const name = decl.name.text
313
+ if (shouldInclude(name) && decl.type) {
314
+ // Check if it's a function type
315
+ if (ts.isFunctionTypeNode(decl.type)) {
316
+ const params = formatParams(decl.type.parameters)
317
+ const returnType = formatType(decl.type.type, 60)
318
+ typeInfo.functions[name] = { params, returnType }
319
+ attachJSDoc(name, node)
320
+ }
321
+ }
322
+ }
323
+ }
324
+ }
325
+ }
326
+
327
+ // export = Foo or export default Foo
328
+ if (ts.isExportAssignment(node)) {
329
+ const assignment = getNodeText(node.expression)
330
+ if (assignment) {
331
+ typeInfo.defaults.push(assignment)
332
+ }
333
+ }
334
+
335
+ ts.forEachChild(node, visit)
336
+ }
337
+
338
+ visit(sourceFile)
339
+
340
+ // If we have a filter, try to follow re-exports for missing symbols
341
+ if (filterList && filterList.length > 0) {
342
+ const found = new Set([
343
+ ...Object.keys(typeInfo.functions),
344
+ ...Object.keys(typeInfo.interfaces),
345
+ ...Object.keys(typeInfo.types),
346
+ ...Object.keys(typeInfo.classes),
347
+ ...Object.keys(typeInfo.enums),
348
+ ...Object.keys(typeInfo.namespaces),
349
+ ])
350
+ const missing = filterList.filter((name) => !found.has(name))
351
+
352
+ if (missing.length > 0) {
353
+ const { named, wildcards } = findReExports(dtsPath, missing)
354
+
355
+ // Try named exports first
356
+ for (const [symbolName, sourcePath] of named) {
357
+ const subResult = parseDtsFile(sourcePath, [symbolName], visited)
358
+ if (subResult) {
359
+ Object.assign(typeInfo.functions, subResult.functions)
360
+ Object.assign(typeInfo.interfaces, subResult.interfaces)
361
+ Object.assign(typeInfo.types, subResult.types)
362
+ Object.assign(typeInfo.classes, subResult.classes)
363
+ Object.assign(typeInfo.enums, subResult.enums)
364
+ Object.assign(typeInfo.namespaces, subResult.namespaces)
365
+ Object.assign(typeInfo.jsdoc, subResult.jsdoc)
366
+ typeInfo.defaults.push(...subResult.defaults)
367
+ }
368
+ }
369
+
370
+ // If still missing, try wildcard sources
371
+ if (wildcards.length > 0) {
372
+ for (const wildcardPath of wildcards) {
373
+ const subResult = parseDtsFile(wildcardPath, missing, visited)
374
+ if (subResult) {
375
+ Object.assign(typeInfo.functions, subResult.functions)
376
+ Object.assign(typeInfo.interfaces, subResult.interfaces)
377
+ Object.assign(typeInfo.types, subResult.types)
378
+ Object.assign(typeInfo.classes, subResult.classes)
379
+ Object.assign(typeInfo.enums, subResult.enums)
380
+ Object.assign(typeInfo.namespaces, subResult.namespaces)
381
+ Object.assign(typeInfo.jsdoc, subResult.jsdoc)
382
+ typeInfo.defaults.push(...subResult.defaults)
383
+ }
384
+ }
385
+ }
386
+ }
387
+ }
388
+
389
+ return typeInfo
390
+ }
391
+
392
+ const isMain =
393
+ typeof import.meta.main === "boolean"
394
+ ? import.meta.main
395
+ : Boolean(process.argv?.[1] && process.argv[1].endsWith("parse-dts.mjs"))
396
+
397
+ // CLI mode
398
+ if (isMain) {
399
+ const dtsPath = process.argv[2]
400
+ const filter = process.argv[3]
401
+
402
+ if (!dtsPath) {
403
+ console.error("Usage: node parse-dts.mjs <path-to-dts> [filter]")
404
+ process.exit(1)
405
+ }
406
+
407
+ const filterList = filter ? filter.split(",") : null
408
+ const result = parseDtsFile(dtsPath, filterList)
409
+
410
+ if (result) {
411
+ console.log(JSON.stringify(result, null, 2))
412
+ } else {
413
+ console.error("Failed to parse:", dtsPath)
414
+ process.exit(1)
415
+ }
416
+ }
package/src/server.mjs CHANGED
@@ -2,7 +2,24 @@
2
2
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
3
3
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
4
  import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
5
- import { runInspect } from "@deplens/core";
5
+ let corePromise = null;
6
+
7
+ async function loadCore() {
8
+ if (!corePromise) {
9
+ corePromise = import("@deplens/core").catch(async (error) => {
10
+ try {
11
+ const fallbackUrl = new URL("./core/inspect.mjs", import.meta.url);
12
+ return await import(fallbackUrl.href);
13
+ } catch (fallbackError) {
14
+ const message = fallbackError instanceof Error ? fallbackError.message : String(fallbackError);
15
+ const err = new Error(`Failed to load @deplens/core. Fallback also failed: ${message}`);
16
+ err.cause = error;
17
+ throw err;
18
+ }
19
+ });
20
+ }
21
+ return corePromise;
22
+ }
6
23
 
7
24
  const tools = [
8
25
  {
@@ -80,6 +97,11 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
80
97
  const target = args?.subpath ? `${args.target}/${args.subpath}` : args?.target;
81
98
  if (!target) throw new Error("Missing required field: target");
82
99
 
100
+ const core = await loadCore();
101
+ const runInspect = core.runInspect || core.default?.runInspect;
102
+ if (!runInspect) {
103
+ throw new Error("Failed to load runInspect from @deplens/core");
104
+ }
83
105
  const output = await runInspect({
84
106
  target,
85
107
  filter: args?.filter,