Skip to content

Commit eb94cde

Browse files
committed
Phase 3: LSP/MCP retry and debug symbol source
- LSP: retry createLspClient once after 1.5s on failure - MCP: retry getSymbols once after 1s on failure - DEBUG_SYSMLEGRAPH_SYMBOLS=1 logs per-file LSP vs MCP source - PLAN: Phase 3 done; MCP_INTERACTION_GUIDE §7 Debugging Made-with: Cursor
1 parent 7d78020 commit eb94cde

4 files changed

Lines changed: 42 additions & 18 deletions

File tree

docs/MCP_INTERACTION_GUIDE.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,14 @@ Edges come from **symbols** (Package, PartDef, etc.). The indexer gets symbols v
170170

171171
---
172172

173-
## 7. Summary
173+
## 7. Debugging
174+
175+
- **`DEBUG_SYSMLEGRAPH_SYMBOLS=1`** — Log to stderr per file whether symbols came from **LSP** or **MCP** (and count for MCP). Use when indexing to see which path is used.
176+
- **`DEBUG_LSP_NOTIFICATIONS=1`** — Log LSP `window/logMessage` and `window/showMessage` to stderr when using the LSP client.
177+
178+
---
179+
180+
## 8. Summary
174181

175182
| Goal | Approach |
176183
|-----------------------------|---------------------------------------------------------------------------|

docs/PLAN.md

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -30,15 +30,10 @@
3030
4. **LSP notification handler** — Handler added in `lsp-client.ts`; `window/logMessage` and `window/showMessage` logged when `DEBUG_LSP_NOTIFICATIONS=1`.
3131
5. **Real SysML files** — Indexed `modelbase-development/models/` (4 files); map shows Action, Block, Package, PartDef, PartUsage, RequirementDef and IN_DOCUMENT/IN_PACKAGE edges.
3232

33-
### Phase 3: Robustness (If Needed)
33+
### Phase 3: Robustness — ✅ Done
3434

35-
6. **Connection retry logic**
36-
- If LSP client fails to initialize, retry once (transient failures)
37-
- If MCP init times out, retry with longer timeout or skip gracefully
38-
39-
7. **Better error reporting**
40-
- Map LSP errors to specific types (like template maps `UnauthorizedError`)
41-
- Log which path succeeded (LSP vs MCP) for debugging
35+
6. **Connection retry logic** — LSP: `getDocumentSymbolsFromLsp` retries `createLspClient()` once after 1.5s on failure. MCP: `getSymbolsFromMcp` retries the getSymbols call once after 1s on failure; still returns [] if unavailable.
36+
7. **Better error reporting** — When **`DEBUG_SYSMLEGRAPH_SYMBOLS=1`**, log to stderr per file whether symbols came from LSP or MCP (and count for MCP). LSP errors still fall through to MCP; no custom error types added.
4237

4338
### Phase 4: Validation & Documentation
4439

@@ -48,10 +43,9 @@
4843

4944
9. **Documentation** — Done in v0.4.3: fixes in `MCP_INTERACTION_GUIDE.md`, "no edges" and troubleshooting, `lsp/` usage in README and release notes. Further troubleshooting can be added as needed.
5045

51-
## Next Steps (Phase 3 / 4)
46+
## Next Steps (Phase 4)
5247

53-
1. **Optional:** Connection retry and clearer error reporting (Phase 3).
54-
2. **Optional:** MCP `validate` on a SysML file; expand troubleshooting in docs (Phase 4).
48+
1. **Optional:** MCP `validate` on a SysML file; expand troubleshooting in docs (Phase 4).
5549

5650
## Success Criteria
5751

src/parser/lsp-client.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -260,10 +260,17 @@ export async function createLspClient(): Promise<{
260260

261261
let sharedClient: Awaited<ReturnType<typeof createLspClient>> | null = null;
262262

263-
/** Spawns external LSP (SYSMLLSP_SERVER_PATH). Throws if unset or startup fails. */
263+
const LSP_INIT_RETRY_DELAY_MS = 1500;
264+
265+
/** Spawns external LSP (SYSMLLSP_SERVER_PATH). Throws if unset or startup fails. Retries once on transient failure. */
264266
export async function getDocumentSymbolsFromLsp(filePath: string, content: string): Promise<DocumentSymbolLsp[]> {
265267
if (!sharedClient) {
266-
sharedClient = await createLspClient();
268+
try {
269+
sharedClient = await createLspClient();
270+
} catch (firstErr) {
271+
await new Promise((r) => setTimeout(r, LSP_INIT_RETRY_DELAY_MS));
272+
sharedClient = await createLspClient();
273+
}
267274
}
268275
const uri = pathToFileURL(filePath).href;
269276
return await sharedClient.getDocumentSymbols(uri, content);

src/parser/symbols.ts

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -86,18 +86,31 @@ async function createMcpClient(): Promise<{
8686
};
8787
}
8888

89-
/** Use shared MCP client for getSymbols (one init per index run). Returns [] if MCP unavailable or fails. */
89+
const MCP_RETRY_DELAY_MS = 1000;
90+
91+
/** Use shared MCP client for getSymbols (one init per index run). Returns [] if MCP unavailable or fails. Retries once on failure. */
9092
async function getSymbolsFromMcp(filePath: string, content: string): Promise<NormalizedSymbol[]> {
91-
try {
93+
const attempt = async (): Promise<NormalizedSymbol[]> => {
9294
if (!sharedMcpClient) sharedMcpClient = await createMcpClient();
9395
if (!sharedMcpClient) return [];
9496
const uri = `file:///${filePath.replace(/\\/g, '/')}`;
9597
return await sharedMcpClient.getSymbols(content, uri, filePath);
98+
};
99+
try {
100+
return await attempt();
96101
} catch {
97-
return [];
102+
await new Promise((r) => setTimeout(r, MCP_RETRY_DELAY_MS));
103+
try {
104+
return await attempt();
105+
} catch {
106+
return [];
107+
}
98108
}
99109
}
100110

111+
/** When set, log to stderr whether symbols came from LSP or MCP (per file). */
112+
const DEBUG_SYMBOL_SOURCE = process.env.DEBUG_SYSMLEGRAPH_SYMBOLS === '1';
113+
101114
/**
102115
* Get symbols and relations for a single file. Tries LSP documentSymbol first when SYSMLLSP_SERVER_PATH is set;
103116
* if LSP returns no symbols (or is unset), uses MCP getSymbols (one shared client per index run).
@@ -114,6 +127,7 @@ export async function getSymbolsForFile(filePath: string, content?: string): Pro
114127
),
115128
]);
116129
if (lspSymbols.length > 0) {
130+
if (DEBUG_SYMBOL_SOURCE) process.stderr.write(`[sysmledgraph] symbols from LSP: ${filePath}\n`);
117131
const flat = flattenSymbols(lspSymbols, null);
118132
const normalized: NormalizedSymbol[] = [];
119133
for (const item of flat) {
@@ -130,7 +144,9 @@ export async function getSymbolsForFile(filePath: string, content?: string): Pro
130144
// LSP failed or unavailable; fall through to MCP
131145
}
132146
}
133-
return getSymbolsFromMcp(filePath, text);
147+
const mcpSymbols = await getSymbolsFromMcp(filePath, text);
148+
if (DEBUG_SYMBOL_SOURCE) process.stderr.write(`[sysmledgraph] symbols from MCP: ${filePath} (${mcpSymbols.length})\n`);
149+
return mcpSymbols;
134150
}
135151

136152
/** Close shared MCP client (used when LSP returned no symbols). */

0 commit comments

Comments
 (0)