@mcp-consultant-tools/log-analytics 28.0.0 → 29.0.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/build/cli/commands/function-commands.d.ts +7 -0
- package/build/cli/commands/function-commands.d.ts.map +1 -0
- package/build/cli/commands/function-commands.js +120 -0
- package/build/cli/commands/function-commands.js.map +1 -0
- package/build/cli/commands/index.d.ts +10 -0
- package/build/cli/commands/index.d.ts.map +1 -0
- package/build/cli/commands/index.js +15 -0
- package/build/cli/commands/index.js.map +1 -0
- package/build/cli/commands/query-commands.d.ts +7 -0
- package/build/cli/commands/query-commands.d.ts.map +1 -0
- package/build/cli/commands/query-commands.js +486 -0
- package/build/cli/commands/query-commands.js.map +1 -0
- package/build/cli/commands/workspace-commands.d.ts +7 -0
- package/build/cli/commands/workspace-commands.d.ts.map +1 -0
- package/build/cli/commands/workspace-commands.js +47 -0
- package/build/cli/commands/workspace-commands.js.map +1 -0
- package/build/cli/output.d.ts +11 -0
- package/build/cli/output.d.ts.map +1 -0
- package/build/cli/output.js +10 -0
- package/build/cli/output.js.map +1 -0
- package/build/cli.d.ts +9 -0
- package/build/cli.d.ts.map +1 -0
- package/build/cli.js +27 -0
- package/build/cli.js.map +1 -0
- package/build/context-factory.d.ts +4 -0
- package/build/context-factory.d.ts.map +1 -0
- package/build/context-factory.js +45 -0
- package/build/context-factory.js.map +1 -0
- package/build/index.d.ts +14 -2
- package/build/index.d.ts.map +1 -1
- package/build/index.js +35 -1121
- package/build/index.js.map +1 -1
- package/build/prompts/index.d.ts +7 -0
- package/build/prompts/index.d.ts.map +1 -0
- package/build/prompts/index.js +6 -0
- package/build/prompts/index.js.map +1 -0
- package/build/prompts/templates.d.ts +3 -0
- package/build/prompts/templates.d.ts.map +1 -0
- package/build/prompts/templates.js +195 -0
- package/build/prompts/templates.js.map +1 -0
- package/build/services/index.d.ts +3 -0
- package/build/services/index.d.ts.map +1 -0
- package/build/services/index.js +2 -0
- package/build/services/index.js.map +1 -0
- package/build/services/log-analytics-service.d.ts +117 -0
- package/build/services/log-analytics-service.d.ts.map +1 -0
- package/build/services/log-analytics-service.js +419 -0
- package/build/services/log-analytics-service.js.map +1 -0
- package/build/tool-examples.d.ts +1 -8
- package/build/tool-examples.d.ts.map +1 -1
- package/build/tool-examples.js +1 -12
- package/build/tool-examples.js.map +1 -1
- package/build/tools/function-tools.d.ts +3 -0
- package/build/tools/function-tools.d.ts.map +1 -0
- package/build/tools/function-tools.js +536 -0
- package/build/tools/function-tools.js.map +1 -0
- package/build/tools/index.d.ts +9 -0
- package/build/tools/index.d.ts.map +1 -0
- package/build/tools/index.js +12 -0
- package/build/tools/index.js.map +1 -0
- package/build/tools/query-tools.d.ts +3 -0
- package/build/tools/query-tools.d.ts.map +1 -0
- package/build/tools/query-tools.js +105 -0
- package/build/tools/query-tools.js.map +1 -0
- package/build/tools/workspace-tools.d.ts +3 -0
- package/build/tools/workspace-tools.d.ts.map +1 -0
- package/build/tools/workspace-tools.js +53 -0
- package/build/tools/workspace-tools.js.map +1 -0
- package/build/types.d.ts +9 -0
- package/build/types.d.ts.map +1 -0
- package/build/types.js +2 -0
- package/build/types.js.map +1 -0
- package/package.json +5 -3
package/build/index.js
CHANGED
|
@@ -1,17 +1,24 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* @mcp-consultant-tools/log-analytics
|
|
4
|
+
*
|
|
5
|
+
* MCP server for Azure Log Analytics integration.
|
|
6
|
+
* Entry point: MCP server startup + backward-compatible registerLogAnalyticsTools().
|
|
7
|
+
*/
|
|
2
8
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
9
|
import { pathToFileURL } from "node:url";
|
|
4
10
|
import { realpathSync } from "node:fs";
|
|
5
11
|
import { createMcpServer, createEnvLoader } from "@mcp-consultant-tools/core";
|
|
6
|
-
import { LogAnalyticsService } from
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
12
|
+
import { LogAnalyticsService } from './services/log-analytics-service.js';
|
|
13
|
+
import { registerAllTools } from './tools/index.js';
|
|
14
|
+
import { registerAllPrompts } from './prompts/index.js';
|
|
15
|
+
/**
|
|
16
|
+
* Build a ServiceContext from environment variables (lazy service initialization).
|
|
17
|
+
*/
|
|
18
|
+
function createServiceContext() {
|
|
19
|
+
let service = null;
|
|
20
|
+
function getService() {
|
|
13
21
|
if (!service) {
|
|
14
|
-
const missingConfig = [];
|
|
15
22
|
let resources = [];
|
|
16
23
|
if (process.env.LOGANALYTICS_RESOURCES) {
|
|
17
24
|
try {
|
|
@@ -30,10 +37,7 @@ export function registerLogAnalyticsTools(server, loganalyticsService) {
|
|
|
30
37
|
}];
|
|
31
38
|
}
|
|
32
39
|
else {
|
|
33
|
-
|
|
34
|
-
}
|
|
35
|
-
if (missingConfig.length > 0) {
|
|
36
|
-
throw new Error(`Missing Log Analytics configuration: ${missingConfig.join(", ")}`);
|
|
40
|
+
throw new Error("Missing Log Analytics configuration: LOGANALYTICS_RESOURCES or LOGANALYTICS_WORKSPACE_ID");
|
|
37
41
|
}
|
|
38
42
|
const config = {
|
|
39
43
|
resources,
|
|
@@ -47,1122 +51,32 @@ export function registerLogAnalyticsTools(server, loganalyticsService) {
|
|
|
47
51
|
}
|
|
48
52
|
return service;
|
|
49
53
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
const statsResult = await service.getFunctionStats(resourceId, undefined, timespanValue);
|
|
63
|
-
// Format results
|
|
64
|
-
const errorsTable = errorsResult.tables[0] ? formatTableAsMarkdown(errorsResult.tables[0]) : '*No errors*';
|
|
65
|
-
const statsTable = statsResult.tables[0] ? formatTableAsMarkdown(statsResult.tables[0]) : '*No statistics*';
|
|
66
|
-
// Analyze
|
|
67
|
-
const errorsAnalysis = analyzeFunctionErrors(errorsResult.tables[0]);
|
|
68
|
-
const statsAnalysis = analyzeFunctionStats(statsResult.tables[0]);
|
|
69
|
-
const report = `# Log Analytics Workspace Health Summary\n\n` +
|
|
70
|
-
`**Resource**: ${resourceId}\n` +
|
|
71
|
-
`**Time Range**: ${timespanValue}\n` +
|
|
72
|
-
`**Generated**: ${new Date().toISOString()}\n\n` +
|
|
73
|
-
`## Function Statistics\n\n${statsTable}\n\n` +
|
|
74
|
-
`### Key Insights\n${statsAnalysis}\n\n` +
|
|
75
|
-
`## Recent Errors\n\n${errorsTable}\n\n` +
|
|
76
|
-
`### Error Analysis\n${errorsAnalysis}\n\n` +
|
|
77
|
-
`## Recommendations\n\n` +
|
|
78
|
-
generateRecommendations({
|
|
79
|
-
errorCount: errorsResult.tables[0]?.rows.length || 0,
|
|
80
|
-
}).join('\n');
|
|
81
|
-
return {
|
|
82
|
-
messages: [
|
|
83
|
-
{
|
|
84
|
-
role: "assistant",
|
|
85
|
-
content: {
|
|
86
|
-
type: "text",
|
|
87
|
-
text: report,
|
|
88
|
-
},
|
|
89
|
-
},
|
|
90
|
-
],
|
|
91
|
-
};
|
|
92
|
-
}
|
|
93
|
-
catch (error) {
|
|
94
|
-
console.error("Error generating workspace summary:", error);
|
|
95
|
-
return {
|
|
96
|
-
messages: [
|
|
97
|
-
{
|
|
98
|
-
role: "assistant",
|
|
99
|
-
content: {
|
|
100
|
-
type: "text",
|
|
101
|
-
text: `Failed to generate workspace summary: ${error.message}`,
|
|
102
|
-
},
|
|
103
|
-
},
|
|
104
|
-
],
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
|
-
});
|
|
108
|
-
server.prompt("la-fn-troubleshooting", "Generate a comprehensive troubleshooting guide for an Azure Function", {
|
|
109
|
-
resourceId: z.string().describe("Resource ID"),
|
|
110
|
-
functionName: z.string().describe("Function name to analyze"),
|
|
111
|
-
timespan: z.string().optional().describe("Time range (default: PT1H)"),
|
|
112
|
-
}, async ({ resourceId, functionName, timespan }) => {
|
|
113
|
-
try {
|
|
114
|
-
const service = getLogAnalyticsService();
|
|
115
|
-
const timespanValue = timespan || 'PT1H';
|
|
116
|
-
// Get comprehensive data
|
|
117
|
-
const logsResult = await service.getFunctionLogs(resourceId, functionName, timespanValue, undefined, 100);
|
|
118
|
-
const errorsResult = await service.getFunctionErrors(resourceId, functionName, timespanValue, 50);
|
|
119
|
-
const statsResult = await service.getFunctionStats(resourceId, functionName, timespanValue);
|
|
120
|
-
const invocationsResult = await service.getFunctionInvocations(resourceId, functionName, timespanValue, 50);
|
|
121
|
-
// Format results
|
|
122
|
-
const logsTable = logsResult.tables[0] ? formatTableAsMarkdown(logsResult.tables[0]) : '*No logs*';
|
|
123
|
-
const errorsTable = errorsResult.tables[0] ? formatTableAsMarkdown(errorsResult.tables[0]) : '*No errors*';
|
|
124
|
-
const statsTable = statsResult.tables[0] ? formatTableAsMarkdown(statsResult.tables[0]) : '*No statistics*';
|
|
125
|
-
const invocationsTable = invocationsResult.tables[0] ? formatTableAsMarkdown(invocationsResult.tables[0]) : '*No invocations*';
|
|
126
|
-
// Analyze
|
|
127
|
-
const logsAnalysis = analyzeFunctionLogs(logsResult.tables[0]);
|
|
128
|
-
const errorsAnalysis = analyzeFunctionErrors(errorsResult.tables[0]);
|
|
129
|
-
const statsAnalysis = analyzeFunctionStats(statsResult.tables[0]);
|
|
130
|
-
const report = `# Azure Function Troubleshooting Guide\n\n` +
|
|
131
|
-
`**Function**: ${functionName}\n` +
|
|
132
|
-
`**Resource**: ${resourceId}\n` +
|
|
133
|
-
`**Time Range**: ${timespanValue}\n` +
|
|
134
|
-
`**Generated**: ${new Date().toISOString()}\n\n` +
|
|
135
|
-
`## Executive Summary\n\n${statsTable}\n\n` +
|
|
136
|
-
`### Statistics Insights\n${statsAnalysis}\n\n` +
|
|
137
|
-
`## Error Analysis\n\n${errorsTable}\n\n` +
|
|
138
|
-
`### Error Insights\n${errorsAnalysis}\n\n` +
|
|
139
|
-
`## Recent Logs\n\n${logsTable}\n\n` +
|
|
140
|
-
`### Log Insights\n${logsAnalysis}\n\n` +
|
|
141
|
-
`## Recent Invocations\n\n${invocationsTable}\n\n` +
|
|
142
|
-
`## Recommendations\n\n` +
|
|
143
|
-
generateRecommendations({
|
|
144
|
-
errorCount: errorsResult.tables[0]?.rows.length || 0,
|
|
145
|
-
}).join('\n');
|
|
146
|
-
return {
|
|
147
|
-
messages: [
|
|
148
|
-
{
|
|
149
|
-
role: "assistant",
|
|
150
|
-
content: {
|
|
151
|
-
type: "text",
|
|
152
|
-
text: report,
|
|
153
|
-
},
|
|
154
|
-
},
|
|
155
|
-
],
|
|
156
|
-
};
|
|
157
|
-
}
|
|
158
|
-
catch (error) {
|
|
159
|
-
console.error("Error generating function troubleshooting guide:", error);
|
|
160
|
-
return {
|
|
161
|
-
messages: [
|
|
162
|
-
{
|
|
163
|
-
role: "assistant",
|
|
164
|
-
content: {
|
|
165
|
-
type: "text",
|
|
166
|
-
text: `Failed to generate troubleshooting guide: ${error.message}`,
|
|
167
|
-
},
|
|
168
|
-
},
|
|
169
|
-
],
|
|
170
|
-
};
|
|
171
|
-
}
|
|
172
|
-
});
|
|
173
|
-
server.prompt("la-fn-performance", "Generate a performance analysis report for Azure Functions", {
|
|
174
|
-
resourceId: z.string().describe("Resource ID"),
|
|
175
|
-
functionName: z.string().optional().describe("Function name (optional, analyzes all if not specified)"),
|
|
176
|
-
timespan: z.string().optional().describe("Time range (default: PT1H)"),
|
|
177
|
-
}, async ({ resourceId, functionName, timespan }) => {
|
|
178
|
-
try {
|
|
179
|
-
const service = getLogAnalyticsService();
|
|
180
|
-
const timespanValue = timespan || 'PT1H';
|
|
181
|
-
// Get performance data
|
|
182
|
-
const statsResult = await service.getFunctionStats(resourceId, functionName, timespanValue);
|
|
183
|
-
const invocationsResult = await service.getFunctionInvocations(resourceId, functionName, timespanValue, 100);
|
|
184
|
-
// Format results
|
|
185
|
-
const statsTable = statsResult.tables[0] ? formatTableAsMarkdown(statsResult.tables[0]) : '*No statistics*';
|
|
186
|
-
const invocationsTable = invocationsResult.tables[0] ? formatTableAsMarkdown(invocationsResult.tables[0]) : '*No invocations*';
|
|
187
|
-
// Analyze
|
|
188
|
-
const statsAnalysis = analyzeFunctionStats(statsResult.tables[0]);
|
|
189
|
-
const report = `# Azure Function Performance Report\n\n` +
|
|
190
|
-
`**Function**: ${functionName || 'All Functions'}\n` +
|
|
191
|
-
`**Resource**: ${resourceId}\n` +
|
|
192
|
-
`**Time Range**: ${timespanValue}\n` +
|
|
193
|
-
`**Generated**: ${new Date().toISOString()}\n\n` +
|
|
194
|
-
`## Execution Statistics\n\n${statsTable}\n\n` +
|
|
195
|
-
`### Performance Insights\n${statsAnalysis}\n\n` +
|
|
196
|
-
`## Recent Invocations\n\n${invocationsTable}\n\n` +
|
|
197
|
-
`## Recommendations\n\n` +
|
|
198
|
-
`- Monitor success rates and investigate functions below 95%\n` +
|
|
199
|
-
`- Review invocation patterns for optimization opportunities\n` +
|
|
200
|
-
`- Consider implementing retry logic for transient failures\n`;
|
|
201
|
-
return {
|
|
202
|
-
messages: [
|
|
203
|
-
{
|
|
204
|
-
role: "assistant",
|
|
205
|
-
content: {
|
|
206
|
-
type: "text",
|
|
207
|
-
text: report,
|
|
208
|
-
},
|
|
209
|
-
},
|
|
210
|
-
],
|
|
211
|
-
};
|
|
212
|
-
}
|
|
213
|
-
catch (error) {
|
|
214
|
-
console.error("Error generating performance report:", error);
|
|
215
|
-
return {
|
|
216
|
-
messages: [
|
|
217
|
-
{
|
|
218
|
-
role: "assistant",
|
|
219
|
-
content: {
|
|
220
|
-
type: "text",
|
|
221
|
-
text: `Failed to generate performance report: ${error.message}`,
|
|
222
|
-
},
|
|
223
|
-
},
|
|
224
|
-
],
|
|
225
|
-
};
|
|
226
|
-
}
|
|
227
|
-
});
|
|
228
|
-
server.prompt("la-logs-report", "Generate a formatted logs report with insights and analysis", {
|
|
229
|
-
resourceId: z.string().describe("Resource ID"),
|
|
230
|
-
tableName: z.string().describe("Table name to query"),
|
|
231
|
-
timespan: z.string().optional().describe("Time range (default: PT1H)"),
|
|
232
|
-
limit: z.string().optional().describe("Maximum number of logs (default: 100)"),
|
|
233
|
-
}, async ({ resourceId, tableName, timespan, limit }) => {
|
|
234
|
-
try {
|
|
235
|
-
const service = getLogAnalyticsService();
|
|
236
|
-
const timespanValue = timespan || 'PT1H';
|
|
237
|
-
const limitValue = limit ? parseInt(limit, 10) : 100;
|
|
238
|
-
// Get logs
|
|
239
|
-
const logsResult = await service.getRecentEvents(resourceId, tableName, timespanValue, limitValue);
|
|
240
|
-
// Format results
|
|
241
|
-
const logsTable = logsResult.tables[0] ? formatTableAsMarkdown(logsResult.tables[0]) : '*No logs*';
|
|
242
|
-
// Analyze
|
|
243
|
-
const analysis = analyzeLogs(logsResult.tables[0], tableName);
|
|
244
|
-
const report = `# Log Analytics Report\n\n` +
|
|
245
|
-
`**Table**: ${tableName}\n` +
|
|
246
|
-
`**Resource**: ${resourceId}\n` +
|
|
247
|
-
`**Time Range**: ${timespanValue}\n` +
|
|
248
|
-
`**Limit**: ${limitValue}\n` +
|
|
249
|
-
`**Generated**: ${new Date().toISOString()}\n\n` +
|
|
250
|
-
`## Log Entries\n\n${logsTable}\n\n` +
|
|
251
|
-
`### Analysis\n${analysis}\n\n` +
|
|
252
|
-
`## Recommendations\n\n` +
|
|
253
|
-
`- Review log patterns for anomalies\n` +
|
|
254
|
-
`- Investigate any error or warning entries\n` +
|
|
255
|
-
`- Consider adjusting log retention policies\n`;
|
|
256
|
-
return {
|
|
257
|
-
messages: [
|
|
258
|
-
{
|
|
259
|
-
role: "assistant",
|
|
260
|
-
content: {
|
|
261
|
-
type: "text",
|
|
262
|
-
text: report,
|
|
263
|
-
},
|
|
264
|
-
},
|
|
265
|
-
],
|
|
266
|
-
};
|
|
267
|
-
}
|
|
268
|
-
catch (error) {
|
|
269
|
-
console.error("Error generating logs report:", error);
|
|
270
|
-
return {
|
|
271
|
-
messages: [
|
|
272
|
-
{
|
|
273
|
-
role: "assistant",
|
|
274
|
-
content: {
|
|
275
|
-
type: "text",
|
|
276
|
-
text: `Failed to generate logs report: ${error.message}`,
|
|
277
|
-
},
|
|
278
|
-
},
|
|
279
|
-
],
|
|
280
|
-
};
|
|
281
|
-
}
|
|
282
|
-
});
|
|
283
|
-
// ========================================
|
|
284
|
-
// TOOLS
|
|
285
|
-
// ========================================
|
|
286
|
-
server.tool("la-list-workspaces", "List all configured Log Analytics workspaces (active and inactive)", {}, async () => {
|
|
287
|
-
try {
|
|
288
|
-
const service = getLogAnalyticsService();
|
|
289
|
-
const resources = service.getAllResources();
|
|
290
|
-
return {
|
|
291
|
-
content: [
|
|
292
|
-
{
|
|
293
|
-
type: "text",
|
|
294
|
-
text: JSON.stringify(resources, null, 2),
|
|
295
|
-
},
|
|
296
|
-
],
|
|
297
|
-
};
|
|
298
|
-
}
|
|
299
|
-
catch (error) {
|
|
300
|
-
console.error("Error listing Log Analytics workspaces:", error);
|
|
301
|
-
return {
|
|
302
|
-
content: [
|
|
303
|
-
{
|
|
304
|
-
type: "text",
|
|
305
|
-
text: `Failed to list workspaces: ${error.message}`,
|
|
306
|
-
},
|
|
307
|
-
],
|
|
308
|
-
isError: true
|
|
309
|
-
};
|
|
310
|
-
}
|
|
311
|
-
});
|
|
312
|
-
server.tool("la-get-metadata", "Get schema metadata (tables and columns) for a Log Analytics workspace", {
|
|
313
|
-
resourceId: z.string().describe("Resource ID (use la-list-workspaces to find IDs)"),
|
|
314
|
-
}, async ({ resourceId }) => {
|
|
315
|
-
try {
|
|
316
|
-
const service = getLogAnalyticsService();
|
|
317
|
-
const metadata = await service.getMetadata(resourceId);
|
|
318
|
-
return {
|
|
319
|
-
content: [
|
|
320
|
-
{
|
|
321
|
-
type: "text",
|
|
322
|
-
text: JSON.stringify(metadata, null, 2),
|
|
323
|
-
},
|
|
324
|
-
],
|
|
325
|
-
};
|
|
326
|
-
}
|
|
327
|
-
catch (error) {
|
|
328
|
-
console.error("Error getting Log Analytics metadata:", error);
|
|
329
|
-
return {
|
|
330
|
-
content: [
|
|
331
|
-
{
|
|
332
|
-
type: "text",
|
|
333
|
-
text: `Failed to get metadata: ${error.message}`,
|
|
334
|
-
},
|
|
335
|
-
],
|
|
336
|
-
isError: true
|
|
337
|
-
};
|
|
338
|
-
}
|
|
339
|
-
});
|
|
340
|
-
server.tool("la-execute-query", "Execute a custom KQL query against Log Analytics workspace", {
|
|
341
|
-
resourceId: z.string().describe("Resource ID"),
|
|
342
|
-
query: z.string().describe(descWithExamples("KQL query string", KQL_EXAMPLES)),
|
|
343
|
-
timespan: z.string().optional().describe(descWithExamples("Time range (default: PT1H)", TIMESPAN_EXAMPLES)),
|
|
344
|
-
columnPreset: z.enum(["minimal", "investigation", "full"]).optional()
|
|
345
|
-
.describe(descWithExamples("Column preset for filtering results. 'minimal' reduces token count significantly", COLUMN_PRESET_EXAMPLES)),
|
|
346
|
-
columns: z.array(z.string()).optional()
|
|
347
|
-
.describe("Custom columns to include (overrides columnPreset). E.g., ['TimeGenerated', 'Message']"),
|
|
348
|
-
outputFormat: z.enum(["json", "markdown"]).optional()
|
|
349
|
-
.describe(descWithExamples("Output format (default: json)", OUTPUT_FORMAT_EXAMPLES)),
|
|
350
|
-
}, async ({ resourceId, query, timespan, columnPreset, columns, outputFormat }) => {
|
|
351
|
-
try {
|
|
352
|
-
const service = getLogAnalyticsService();
|
|
353
|
-
const result = await service.executeQuery(resourceId, query, timespan);
|
|
354
|
-
// Apply column filtering
|
|
355
|
-
const columnsToInclude = resolveColumnPreset(columnPreset, columns);
|
|
356
|
-
const filteredTables = result.tables.map((t) => filterColumns(t, columnsToInclude));
|
|
357
|
-
const filteredResult = { ...result, tables: filteredTables };
|
|
358
|
-
// Format output
|
|
359
|
-
if (outputFormat === 'markdown' && filteredTables.length > 0) {
|
|
360
|
-
const markdown = filteredTables.map((t) => formatTableAsMarkdown(t)).join('\n\n');
|
|
361
|
-
return {
|
|
362
|
-
content: [{ type: "text", text: markdown }],
|
|
363
|
-
};
|
|
364
|
-
}
|
|
365
|
-
return {
|
|
366
|
-
content: [
|
|
367
|
-
{
|
|
368
|
-
type: "text",
|
|
369
|
-
text: JSON.stringify(filteredResult, null, 2),
|
|
370
|
-
},
|
|
371
|
-
],
|
|
372
|
-
};
|
|
373
|
-
}
|
|
374
|
-
catch (error) {
|
|
375
|
-
console.error("Error executing Log Analytics query:", error);
|
|
376
|
-
return {
|
|
377
|
-
content: [
|
|
378
|
-
{
|
|
379
|
-
type: "text",
|
|
380
|
-
text: `Failed to execute query: ${error.message}`,
|
|
381
|
-
},
|
|
382
|
-
],
|
|
383
|
-
isError: true
|
|
384
|
-
};
|
|
385
|
-
}
|
|
386
|
-
});
|
|
387
|
-
server.tool("la-test-access", "Test access to a Log Analytics workspace by executing a simple query", {
|
|
388
|
-
resourceId: z.string().describe("Resource ID"),
|
|
389
|
-
}, async ({ resourceId }) => {
|
|
390
|
-
try {
|
|
391
|
-
const service = getLogAnalyticsService();
|
|
392
|
-
const result = await service.testWorkspaceAccess(resourceId);
|
|
393
|
-
return {
|
|
394
|
-
content: [
|
|
395
|
-
{
|
|
396
|
-
type: "text",
|
|
397
|
-
text: JSON.stringify(result, null, 2),
|
|
398
|
-
},
|
|
399
|
-
],
|
|
400
|
-
};
|
|
401
|
-
}
|
|
402
|
-
catch (error) {
|
|
403
|
-
console.error("Error testing workspace access:", error);
|
|
404
|
-
return {
|
|
405
|
-
content: [
|
|
406
|
-
{
|
|
407
|
-
type: "text",
|
|
408
|
-
text: `Failed to test workspace access: ${error.message}`,
|
|
409
|
-
},
|
|
410
|
-
],
|
|
411
|
-
isError: true
|
|
412
|
-
};
|
|
413
|
-
}
|
|
414
|
-
});
|
|
415
|
-
server.tool("la-get-recent-events", "Get recent events from a specific Log Analytics table", {
|
|
416
|
-
resourceId: z.string().describe("Resource ID"),
|
|
417
|
-
tableName: z.string().describe(descWithExamples("Table name to query", TABLE_NAME_EXAMPLES)),
|
|
418
|
-
timespan: z.string().optional().describe(descWithExamples("Time range (default: PT1H)", TIMESPAN_EXAMPLES)),
|
|
419
|
-
limit: z.number().optional().describe("Maximum number of results (default: 100)"),
|
|
420
|
-
columnPreset: z.enum(["minimal", "investigation", "full"]).optional()
|
|
421
|
-
.describe(descWithExamples("Column preset for filtering results", COLUMN_PRESET_EXAMPLES)),
|
|
422
|
-
columns: z.array(z.string()).optional()
|
|
423
|
-
.describe("Custom columns to include (overrides columnPreset)"),
|
|
424
|
-
outputFormat: z.enum(["json", "markdown"]).optional()
|
|
425
|
-
.describe(descWithExamples("Output format (default: json)", OUTPUT_FORMAT_EXAMPLES)),
|
|
426
|
-
}, async ({ resourceId, tableName, timespan, limit, columnPreset, columns, outputFormat }) => {
|
|
427
|
-
try {
|
|
428
|
-
const service = getLogAnalyticsService();
|
|
429
|
-
const result = await service.getRecentEvents(resourceId, tableName, timespan || 'PT1H', limit || 100);
|
|
430
|
-
// Apply column filtering
|
|
431
|
-
const columnsToInclude = resolveColumnPreset(columnPreset, columns);
|
|
432
|
-
const filteredTables = result.tables.map((t) => filterColumns(t, columnsToInclude));
|
|
433
|
-
const filteredResult = { ...result, tables: filteredTables };
|
|
434
|
-
// Format output
|
|
435
|
-
if (outputFormat === 'markdown' && filteredTables.length > 0) {
|
|
436
|
-
const markdown = filteredTables.map((t) => formatTableAsMarkdown(t)).join('\n\n');
|
|
437
|
-
return {
|
|
438
|
-
content: [{ type: "text", text: markdown }],
|
|
439
|
-
};
|
|
440
|
-
}
|
|
441
|
-
return {
|
|
442
|
-
content: [
|
|
443
|
-
{
|
|
444
|
-
type: "text",
|
|
445
|
-
text: JSON.stringify(filteredResult, null, 2),
|
|
446
|
-
},
|
|
447
|
-
],
|
|
448
|
-
};
|
|
449
|
-
}
|
|
450
|
-
catch (error) {
|
|
451
|
-
console.error("Error getting recent events:", error);
|
|
452
|
-
return {
|
|
453
|
-
content: [
|
|
454
|
-
{
|
|
455
|
-
type: "text",
|
|
456
|
-
text: `Failed to get recent events: ${error.message}`,
|
|
457
|
-
},
|
|
458
|
-
],
|
|
459
|
-
isError: true
|
|
460
|
-
};
|
|
461
|
-
}
|
|
462
|
-
});
|
|
463
|
-
server.tool("la-search-logs", "Search logs by text content across tables or a specific table", {
|
|
464
|
-
resourceId: z.string().describe("Resource ID"),
|
|
465
|
-
searchText: z.string().describe("Text to search for (case-insensitive)"),
|
|
466
|
-
tableName: z.string().optional().describe(descWithExamples("Table name to search in (optional, searches all if not specified)", TABLE_NAME_EXAMPLES)),
|
|
467
|
-
timespan: z.string().optional().describe(descWithExamples("Time range (default: PT1H)", TIMESPAN_EXAMPLES)),
|
|
468
|
-
limit: z.number().optional().describe("Maximum number of results (default: 100)"),
|
|
469
|
-
columnPreset: z.enum(["minimal", "investigation", "full"]).optional()
|
|
470
|
-
.describe(descWithExamples("Column preset for filtering results", COLUMN_PRESET_EXAMPLES)),
|
|
471
|
-
columns: z.array(z.string()).optional()
|
|
472
|
-
.describe("Custom columns to include (overrides columnPreset)"),
|
|
473
|
-
outputFormat: z.enum(["json", "markdown"]).optional()
|
|
474
|
-
.describe(descWithExamples("Output format (default: json)", OUTPUT_FORMAT_EXAMPLES)),
|
|
475
|
-
}, async ({ resourceId, searchText, tableName, timespan, limit, columnPreset, columns, outputFormat }) => {
|
|
476
|
-
try {
|
|
477
|
-
const service = getLogAnalyticsService();
|
|
478
|
-
const result = await service.searchLogs(resourceId, searchText, tableName, timespan || 'PT1H', limit || 100);
|
|
479
|
-
// Apply column filtering
|
|
480
|
-
const columnsToInclude = resolveColumnPreset(columnPreset, columns);
|
|
481
|
-
const filteredTables = result.tables.map((t) => filterColumns(t, columnsToInclude));
|
|
482
|
-
const filteredResult = { ...result, tables: filteredTables };
|
|
483
|
-
// Format output
|
|
484
|
-
if (outputFormat === 'markdown' && filteredTables.length > 0) {
|
|
485
|
-
const markdown = filteredTables.map((t) => formatTableAsMarkdown(t)).join('\n\n');
|
|
486
|
-
return {
|
|
487
|
-
content: [{ type: "text", text: markdown }],
|
|
488
|
-
};
|
|
489
|
-
}
|
|
490
|
-
return {
|
|
491
|
-
content: [
|
|
492
|
-
{
|
|
493
|
-
type: "text",
|
|
494
|
-
text: JSON.stringify(filteredResult, null, 2),
|
|
495
|
-
},
|
|
496
|
-
],
|
|
497
|
-
};
|
|
498
|
-
}
|
|
499
|
-
catch (error) {
|
|
500
|
-
console.error("Error searching logs:", error);
|
|
501
|
-
return {
|
|
502
|
-
content: [
|
|
503
|
-
{
|
|
504
|
-
type: "text",
|
|
505
|
-
text: `Failed to search logs: ${error.message}`,
|
|
506
|
-
},
|
|
507
|
-
],
|
|
508
|
-
isError: true
|
|
509
|
-
};
|
|
510
|
-
}
|
|
511
|
-
});
|
|
512
|
-
server.tool("la-get-fn-logs", "Get Azure Function logs from FunctionAppLogs table with optional filtering", {
|
|
513
|
-
resourceId: z.string().describe("Resource ID"),
|
|
514
|
-
functionName: z.string().optional().describe(descWithExamples("Function name to filter by (optional)", APP_NAME_EXAMPLES)),
|
|
515
|
-
timespan: z.string().optional().describe(descWithExamples("Time range (default: PT1H)", TIMESPAN_EXAMPLES)),
|
|
516
|
-
severityLevel: z.number().optional().describe(descWithExamples("Minimum severity level", SEVERITY_EXAMPLES)),
|
|
517
|
-
limit: z.number().optional().describe("Maximum number of results (default: 100)"),
|
|
518
|
-
columnPreset: z.enum(["minimal", "investigation", "full"]).optional()
|
|
519
|
-
.describe(descWithExamples("Column preset for filtering results", COLUMN_PRESET_EXAMPLES)),
|
|
520
|
-
columns: z.array(z.string()).optional()
|
|
521
|
-
.describe("Custom columns to include (overrides columnPreset)"),
|
|
522
|
-
outputFormat: z.enum(["json", "markdown"]).optional()
|
|
523
|
-
.describe(descWithExamples("Output format (default: json)", OUTPUT_FORMAT_EXAMPLES)),
|
|
524
|
-
}, async ({ resourceId, functionName, timespan, severityLevel, limit, columnPreset, columns, outputFormat }) => {
|
|
525
|
-
try {
|
|
526
|
-
const service = getLogAnalyticsService();
|
|
527
|
-
const result = await service.getFunctionLogs(resourceId, functionName, timespan || 'PT1H', severityLevel, limit || 100);
|
|
528
|
-
// Apply column filtering
|
|
529
|
-
const columnsToInclude = resolveColumnPreset(columnPreset, columns);
|
|
530
|
-
const filteredTables = result.tables.map((t) => filterColumns(t, columnsToInclude));
|
|
531
|
-
const filteredResult = { ...result, tables: filteredTables };
|
|
532
|
-
// Format output
|
|
533
|
-
if (outputFormat === 'markdown' && filteredTables.length > 0) {
|
|
534
|
-
const markdown = filteredTables.map((t) => formatTableAsMarkdown(t)).join('\n\n');
|
|
535
|
-
return {
|
|
536
|
-
content: [{ type: "text", text: markdown }],
|
|
537
|
-
};
|
|
538
|
-
}
|
|
539
|
-
return {
|
|
540
|
-
content: [
|
|
541
|
-
{
|
|
542
|
-
type: "text",
|
|
543
|
-
text: JSON.stringify(filteredResult, null, 2),
|
|
544
|
-
},
|
|
545
|
-
],
|
|
546
|
-
};
|
|
547
|
-
}
|
|
548
|
-
catch (error) {
|
|
549
|
-
console.error("Error getting function logs:", error);
|
|
550
|
-
return {
|
|
551
|
-
content: [
|
|
552
|
-
{
|
|
553
|
-
type: "text",
|
|
554
|
-
text: `Failed to get function logs: ${error.message}`,
|
|
555
|
-
},
|
|
556
|
-
],
|
|
557
|
-
isError: true
|
|
558
|
-
};
|
|
559
|
-
}
|
|
560
|
-
});
|
|
561
|
-
server.tool("la-get-fn-errors", "Get Azure Function error logs with exception details", {
|
|
562
|
-
resourceId: z.string().describe("Resource ID"),
|
|
563
|
-
functionName: z.string().optional().describe(descWithExamples("Function name to filter by (optional)", APP_NAME_EXAMPLES)),
|
|
564
|
-
timespan: z.string().optional().describe(descWithExamples("Time range (default: PT1H)", TIMESPAN_EXAMPLES)),
|
|
565
|
-
limit: z.number().optional().describe("Maximum number of results (default: 100)"),
|
|
566
|
-
columnPreset: z.enum(["minimal", "investigation", "full"]).optional()
|
|
567
|
-
.describe(descWithExamples("Column preset for filtering results", COLUMN_PRESET_EXAMPLES)),
|
|
568
|
-
columns: z.array(z.string()).optional()
|
|
569
|
-
.describe("Custom columns to include (overrides columnPreset)"),
|
|
570
|
-
outputFormat: z.enum(["json", "markdown"]).optional()
|
|
571
|
-
.describe(descWithExamples("Output format (default: json)", OUTPUT_FORMAT_EXAMPLES)),
|
|
572
|
-
}, async ({ resourceId, functionName, timespan, limit, columnPreset, columns, outputFormat }) => {
|
|
573
|
-
try {
|
|
574
|
-
const service = getLogAnalyticsService();
|
|
575
|
-
const result = await service.getFunctionErrors(resourceId, functionName, timespan || 'PT1H', limit || 100);
|
|
576
|
-
// Apply column filtering
|
|
577
|
-
const columnsToInclude = resolveColumnPreset(columnPreset, columns);
|
|
578
|
-
const filteredTables = result.tables.map((t) => filterColumns(t, columnsToInclude));
|
|
579
|
-
const filteredResult = { ...result, tables: filteredTables };
|
|
580
|
-
// Format output
|
|
581
|
-
if (outputFormat === 'markdown' && filteredTables.length > 0) {
|
|
582
|
-
const markdown = filteredTables.map((t) => formatTableAsMarkdown(t)).join('\n\n');
|
|
583
|
-
return {
|
|
584
|
-
content: [{ type: "text", text: markdown }],
|
|
585
|
-
};
|
|
586
|
-
}
|
|
587
|
-
return {
|
|
588
|
-
content: [
|
|
589
|
-
{
|
|
590
|
-
type: "text",
|
|
591
|
-
text: JSON.stringify(filteredResult, null, 2),
|
|
592
|
-
},
|
|
593
|
-
],
|
|
594
|
-
};
|
|
595
|
-
}
|
|
596
|
-
catch (error) {
|
|
597
|
-
console.error("Error getting function errors:", error);
|
|
598
|
-
return {
|
|
599
|
-
content: [
|
|
600
|
-
{
|
|
601
|
-
type: "text",
|
|
602
|
-
text: `Failed to get function errors: ${error.message}`,
|
|
603
|
-
},
|
|
604
|
-
],
|
|
605
|
-
isError: true
|
|
606
|
-
};
|
|
607
|
-
}
|
|
608
|
-
});
|
|
609
|
-
server.tool("la-get-fn-stats", "Get execution statistics for Azure Functions (count, success rate, errors). Returns aggregated data - no column filtering needed.", {
|
|
610
|
-
resourceId: z.string().describe("Resource ID"),
|
|
611
|
-
functionName: z.string().optional().describe(descWithExamples("Function name (optional, returns stats for all functions if not specified)", APP_NAME_EXAMPLES)),
|
|
612
|
-
timespan: z.string().optional().describe(descWithExamples("Time range (default: PT1H)", TIMESPAN_EXAMPLES)),
|
|
613
|
-
outputFormat: z.enum(["json", "markdown"]).optional()
|
|
614
|
-
.describe(descWithExamples("Output format (default: json)", OUTPUT_FORMAT_EXAMPLES)),
|
|
615
|
-
}, async ({ resourceId, functionName, timespan, outputFormat }) => {
|
|
616
|
-
try {
|
|
617
|
-
const service = getLogAnalyticsService();
|
|
618
|
-
const result = await service.getFunctionStats(resourceId, functionName, timespan || 'PT1H');
|
|
619
|
-
// Format output
|
|
620
|
-
if (outputFormat === 'markdown' && result.tables && result.tables.length > 0) {
|
|
621
|
-
const markdown = result.tables.map((t) => formatTableAsMarkdown(t)).join('\n\n');
|
|
622
|
-
return {
|
|
623
|
-
content: [{ type: "text", text: markdown }],
|
|
624
|
-
};
|
|
625
|
-
}
|
|
626
|
-
return {
|
|
627
|
-
content: [
|
|
628
|
-
{
|
|
629
|
-
type: "text",
|
|
630
|
-
text: JSON.stringify(result, null, 2),
|
|
631
|
-
},
|
|
632
|
-
],
|
|
633
|
-
};
|
|
634
|
-
}
|
|
635
|
-
catch (error) {
|
|
636
|
-
console.error("Error getting function stats:", error);
|
|
637
|
-
return {
|
|
638
|
-
content: [
|
|
639
|
-
{
|
|
640
|
-
type: "text",
|
|
641
|
-
text: `Failed to get function stats: ${error.message}`,
|
|
642
|
-
},
|
|
643
|
-
],
|
|
644
|
-
isError: true
|
|
645
|
-
};
|
|
646
|
-
}
|
|
647
|
-
});
|
|
648
|
-
server.tool("la-get-fn-invocations", "Get Azure Function invocation history from requests/traces tables", {
|
|
649
|
-
resourceId: z.string().describe("Resource ID"),
|
|
650
|
-
functionName: z.string().optional().describe(descWithExamples("Function name to filter by (optional)", APP_NAME_EXAMPLES)),
|
|
651
|
-
timespan: z.string().optional().describe(descWithExamples("Time range (default: PT1H)", TIMESPAN_EXAMPLES)),
|
|
652
|
-
limit: z.number().optional().describe("Maximum number of results (default: 100)"),
|
|
653
|
-
columnPreset: z.enum(["minimal", "investigation", "full"]).optional()
|
|
654
|
-
.describe(descWithExamples("Column preset for filtering results", COLUMN_PRESET_EXAMPLES)),
|
|
655
|
-
columns: z.array(z.string()).optional()
|
|
656
|
-
.describe("Custom columns to include (overrides columnPreset)"),
|
|
657
|
-
outputFormat: z.enum(["json", "markdown"]).optional()
|
|
658
|
-
.describe(descWithExamples("Output format (default: json)", OUTPUT_FORMAT_EXAMPLES)),
|
|
659
|
-
}, async ({ resourceId, functionName, timespan, limit, columnPreset, columns, outputFormat }) => {
|
|
660
|
-
try {
|
|
661
|
-
const service = getLogAnalyticsService();
|
|
662
|
-
const result = await service.getFunctionInvocations(resourceId, functionName, timespan || 'PT1H', limit || 100);
|
|
663
|
-
// Apply column filtering
|
|
664
|
-
const columnsToInclude = resolveColumnPreset(columnPreset, columns);
|
|
665
|
-
const filteredTables = result.tables.map((t) => filterColumns(t, columnsToInclude));
|
|
666
|
-
const filteredResult = { ...result, tables: filteredTables };
|
|
667
|
-
// Format output
|
|
668
|
-
if (outputFormat === 'markdown' && filteredTables.length > 0) {
|
|
669
|
-
const markdown = filteredTables.map((t) => formatTableAsMarkdown(t)).join('\n\n');
|
|
670
|
-
return {
|
|
671
|
-
content: [{ type: "text", text: markdown }],
|
|
672
|
-
};
|
|
673
|
-
}
|
|
674
|
-
return {
|
|
675
|
-
content: [
|
|
676
|
-
{
|
|
677
|
-
type: "text",
|
|
678
|
-
text: JSON.stringify(filteredResult, null, 2),
|
|
679
|
-
},
|
|
680
|
-
],
|
|
681
|
-
};
|
|
682
|
-
}
|
|
683
|
-
catch (error) {
|
|
684
|
-
console.error("Error getting function invocations:", error);
|
|
685
|
-
return {
|
|
686
|
-
content: [
|
|
687
|
-
{
|
|
688
|
-
type: "text",
|
|
689
|
-
text: `Failed to get function invocations: ${error.message}`,
|
|
690
|
-
},
|
|
691
|
-
],
|
|
692
|
-
isError: true
|
|
693
|
-
};
|
|
694
|
-
}
|
|
695
|
-
});
|
|
696
|
-
// ========================================
|
|
697
|
-
// NEW INVESTIGATION TOOLS
|
|
698
|
-
// ========================================
|
|
699
|
-
server.tool("la-get-error-summary", "Get aggregated error summary by type - ideal for starting investigations. Returns counts, first/last seen, and sample messages.", {
|
|
700
|
-
resourceId: z.string().describe("Resource ID"),
|
|
701
|
-
timespan: z.string().optional().describe(descWithExamples("Time range (default: PT1H)", TIMESPAN_EXAMPLES)),
|
|
702
|
-
tableName: z.enum(["AppExceptions", "AppTraces", "FunctionAppLogs"]).optional()
|
|
703
|
-
.describe("Table to analyze (default: AppExceptions)"),
|
|
704
|
-
minCount: z.number().optional().describe("Minimum error count to include (default: 1)"),
|
|
705
|
-
deduplicateRetries: z.boolean().optional()
|
|
706
|
-
.describe("Group by OperationId to deduplicate retry attempts (default: true). Shows UniqueErrors count and total RetryCount."),
|
|
707
|
-
outputFormat: z.enum(["json", "markdown"]).optional()
|
|
708
|
-
.describe(descWithExamples("Output format (default: markdown for readability)", OUTPUT_FORMAT_EXAMPLES)),
|
|
709
|
-
}, async ({ resourceId, timespan, tableName, minCount, deduplicateRetries, outputFormat }) => {
|
|
710
|
-
try {
|
|
711
|
-
const service = getLogAnalyticsService();
|
|
712
|
-
const table = tableName || 'AppExceptions';
|
|
713
|
-
const timespanValue = timespan || 'PT1H';
|
|
714
|
-
const minCountValue = minCount || 1;
|
|
715
|
-
const dedupe = deduplicateRetries !== false; // default true
|
|
716
|
-
// Build aggregation query based on table type
|
|
717
|
-
let query;
|
|
718
|
-
if (table === 'AppExceptions') {
|
|
719
|
-
if (dedupe) {
|
|
720
|
-
// Two-stage aggregation: first by OperationId to dedupe retries, then by error type
|
|
721
|
-
query = `
|
|
722
|
-
AppExceptions
|
|
723
|
-
| summarize
|
|
724
|
-
RetryCount = count(),
|
|
725
|
-
FirstSeen = min(TimeGenerated),
|
|
726
|
-
LastSeen = max(TimeGenerated),
|
|
727
|
-
SampleMessage = take_any(OuterMessage)
|
|
728
|
-
by OperationId, ExceptionType, AppRoleName
|
|
729
|
-
| summarize
|
|
730
|
-
UniqueErrors = count(),
|
|
731
|
-
TotalRetries = sum(RetryCount),
|
|
732
|
-
FirstSeen = min(FirstSeen),
|
|
733
|
-
LastSeen = max(LastSeen),
|
|
734
|
-
SampleMessage = take_any(SampleMessage)
|
|
735
|
-
by ExceptionType, AppRoleName
|
|
736
|
-
| where UniqueErrors >= ${minCountValue}
|
|
737
|
-
| order by UniqueErrors desc
|
|
738
|
-
`;
|
|
739
|
-
}
|
|
740
|
-
else {
|
|
741
|
-
query = `
|
|
742
|
-
AppExceptions
|
|
743
|
-
| summarize
|
|
744
|
-
Count = count(),
|
|
745
|
-
FirstSeen = min(TimeGenerated),
|
|
746
|
-
LastSeen = max(TimeGenerated),
|
|
747
|
-
SampleMessage = take_any(OuterMessage)
|
|
748
|
-
by ExceptionType, AppRoleName
|
|
749
|
-
| where Count >= ${minCountValue}
|
|
750
|
-
| order by Count desc
|
|
751
|
-
`;
|
|
752
|
-
}
|
|
753
|
-
}
|
|
754
|
-
else if (table === 'AppTraces') {
|
|
755
|
-
if (dedupe) {
|
|
756
|
-
query = `
|
|
757
|
-
AppTraces
|
|
758
|
-
| where SeverityLevel >= 3
|
|
759
|
-
| summarize
|
|
760
|
-
RetryCount = count(),
|
|
761
|
-
FirstSeen = min(TimeGenerated),
|
|
762
|
-
LastSeen = max(TimeGenerated),
|
|
763
|
-
SampleMessage = take_any(Message)
|
|
764
|
-
by OperationId, AppRoleName, SeverityLevel
|
|
765
|
-
| summarize
|
|
766
|
-
UniqueErrors = count(),
|
|
767
|
-
TotalRetries = sum(RetryCount),
|
|
768
|
-
FirstSeen = min(FirstSeen),
|
|
769
|
-
LastSeen = max(LastSeen),
|
|
770
|
-
SampleMessage = take_any(SampleMessage)
|
|
771
|
-
by AppRoleName, SeverityLevel
|
|
772
|
-
| where UniqueErrors >= ${minCountValue}
|
|
773
|
-
| order by UniqueErrors desc
|
|
774
|
-
`;
|
|
775
|
-
}
|
|
776
|
-
else {
|
|
777
|
-
query = `
|
|
778
|
-
AppTraces
|
|
779
|
-
| where SeverityLevel >= 3
|
|
780
|
-
| summarize
|
|
781
|
-
Count = count(),
|
|
782
|
-
FirstSeen = min(TimeGenerated),
|
|
783
|
-
LastSeen = max(TimeGenerated),
|
|
784
|
-
SampleMessage = take_any(Message)
|
|
785
|
-
by AppRoleName, SeverityLevel
|
|
786
|
-
| where Count >= ${minCountValue}
|
|
787
|
-
| order by Count desc
|
|
788
|
-
`;
|
|
789
|
-
}
|
|
790
|
-
}
|
|
791
|
-
else {
|
|
792
|
-
// FunctionAppLogs - no OperationId, use InvocationId instead
|
|
793
|
-
if (dedupe) {
|
|
794
|
-
query = `
|
|
795
|
-
FunctionAppLogs
|
|
796
|
-
| where ExceptionDetails != ''
|
|
797
|
-
| summarize
|
|
798
|
-
RetryCount = count(),
|
|
799
|
-
FirstSeen = min(TimeGenerated),
|
|
800
|
-
LastSeen = max(TimeGenerated),
|
|
801
|
-
SampleMessage = take_any(Message)
|
|
802
|
-
by InvocationId, FunctionName
|
|
803
|
-
| summarize
|
|
804
|
-
UniqueErrors = count(),
|
|
805
|
-
TotalRetries = sum(RetryCount),
|
|
806
|
-
FirstSeen = min(FirstSeen),
|
|
807
|
-
LastSeen = max(LastSeen),
|
|
808
|
-
SampleMessage = take_any(SampleMessage)
|
|
809
|
-
by FunctionName
|
|
810
|
-
| where UniqueErrors >= ${minCountValue}
|
|
811
|
-
| order by UniqueErrors desc
|
|
812
|
-
`;
|
|
813
|
-
}
|
|
814
|
-
else {
|
|
815
|
-
query = `
|
|
816
|
-
FunctionAppLogs
|
|
817
|
-
| where ExceptionDetails != ''
|
|
818
|
-
| summarize
|
|
819
|
-
Count = count(),
|
|
820
|
-
FirstSeen = min(TimeGenerated),
|
|
821
|
-
LastSeen = max(TimeGenerated),
|
|
822
|
-
SampleMessage = take_any(Message)
|
|
823
|
-
by FunctionName
|
|
824
|
-
| where Count >= ${minCountValue}
|
|
825
|
-
| order by Count desc
|
|
826
|
-
`;
|
|
827
|
-
}
|
|
828
|
-
}
|
|
829
|
-
const result = await service.executeQuery(resourceId, query, timespanValue);
|
|
830
|
-
// Default to markdown for this aggregation tool
|
|
831
|
-
const format = outputFormat || 'markdown';
|
|
832
|
-
if (format === 'markdown' && result.tables && result.tables.length > 0) {
|
|
833
|
-
const dedupeNote = dedupe ? ' (deduplicated by OperationId)' : '';
|
|
834
|
-
const markdown = `## Error Summary (${table})${dedupeNote}\n\n**Time range:** ${timespanValue}\n\n` +
|
|
835
|
-
result.tables.map((t) => formatTableAsMarkdown(t)).join('\n\n');
|
|
836
|
-
return {
|
|
837
|
-
content: [{ type: "text", text: markdown }],
|
|
838
|
-
};
|
|
839
|
-
}
|
|
840
|
-
return {
|
|
841
|
-
content: [
|
|
842
|
-
{
|
|
843
|
-
type: "text",
|
|
844
|
-
text: JSON.stringify(result, null, 2),
|
|
845
|
-
},
|
|
846
|
-
],
|
|
847
|
-
};
|
|
848
|
-
}
|
|
849
|
-
catch (error) {
|
|
850
|
-
console.error("Error getting error summary:", error);
|
|
851
|
-
return {
|
|
852
|
-
content: [
|
|
853
|
-
{
|
|
854
|
-
type: "text",
|
|
855
|
-
text: `Failed to get error summary: ${error.message}`,
|
|
856
|
-
},
|
|
857
|
-
],
|
|
858
|
-
isError: true
|
|
859
|
-
};
|
|
860
|
-
}
|
|
861
|
-
});
|
|
862
|
-
server.tool("la-investigate-app", "Combined investigation tool: searches AppTraces + AppExceptions, returns summary + recent error details. Best starting point for app investigations.", {
|
|
863
|
-
resourceId: z.string().describe("Resource ID"),
|
|
864
|
-
appNamePattern: z.string().optional().describe(descWithExamples("Filter by app name (searches AppRoleName). Partial match supported.", APP_NAME_EXAMPLES)),
|
|
865
|
-
timespan: z.string().optional().describe(descWithExamples("Time range (default: PT1H)", TIMESPAN_EXAMPLES)),
|
|
866
|
-
includeDetails: z.boolean().optional().describe("Include recent error details (default: true)"),
|
|
867
|
-
detailsLimit: z.number().optional().describe("Max recent errors to include (default: 20)"),
|
|
868
|
-
deduplicateRetries: z.boolean().optional()
|
|
869
|
-
.describe("Group by OperationId to deduplicate retry attempts (default: true)"),
|
|
870
|
-
}, async ({ resourceId, appNamePattern, timespan, includeDetails, detailsLimit, deduplicateRetries }) => {
|
|
871
|
-
try {
|
|
872
|
-
const service = getLogAnalyticsService();
|
|
873
|
-
const timespanValue = timespan || 'PT1H';
|
|
874
|
-
const showDetails = includeDetails !== false;
|
|
875
|
-
const limit = detailsLimit || 20;
|
|
876
|
-
const dedupe = deduplicateRetries !== false; // default true
|
|
877
|
-
const appFilter = appNamePattern
|
|
878
|
-
? `| where AppRoleName contains "${appNamePattern}"`
|
|
879
|
-
: '';
|
|
880
|
-
// Query 1: Exception summary (with optional deduplication)
|
|
881
|
-
const exceptionSummaryQuery = dedupe ? `
|
|
882
|
-
AppExceptions
|
|
883
|
-
${appFilter}
|
|
884
|
-
| summarize
|
|
885
|
-
RetryCount = count(),
|
|
886
|
-
FirstSeen = min(TimeGenerated),
|
|
887
|
-
LastSeen = max(TimeGenerated)
|
|
888
|
-
by OperationId, ExceptionType, AppRoleName
|
|
889
|
-
| summarize
|
|
890
|
-
UniqueErrors = count(),
|
|
891
|
-
TotalRetries = sum(RetryCount),
|
|
892
|
-
FirstSeen = min(FirstSeen),
|
|
893
|
-
LastSeen = max(LastSeen)
|
|
894
|
-
by ExceptionType, AppRoleName
|
|
895
|
-
| order by UniqueErrors desc
|
|
896
|
-
| take 20
|
|
897
|
-
` : `
|
|
898
|
-
AppExceptions
|
|
899
|
-
${appFilter}
|
|
900
|
-
| summarize
|
|
901
|
-
Count = count(),
|
|
902
|
-
FirstSeen = min(TimeGenerated),
|
|
903
|
-
LastSeen = max(TimeGenerated)
|
|
904
|
-
by ExceptionType, AppRoleName
|
|
905
|
-
| order by Count desc
|
|
906
|
-
| take 20
|
|
907
|
-
`;
|
|
908
|
-
// Query 2: Trace severity distribution (with optional deduplication)
|
|
909
|
-
const traceSeverityQuery = dedupe ? `
|
|
910
|
-
AppTraces
|
|
911
|
-
${appFilter}
|
|
912
|
-
| summarize RetryCount = count() by OperationId, SeverityLevel, AppRoleName
|
|
913
|
-
| summarize UniqueTraces = count(), TotalCount = sum(RetryCount) by SeverityLevel, AppRoleName
|
|
914
|
-
| order by SeverityLevel desc
|
|
915
|
-
` : `
|
|
916
|
-
AppTraces
|
|
917
|
-
${appFilter}
|
|
918
|
-
| summarize Count = count() by SeverityLevel, AppRoleName
|
|
919
|
-
| order by SeverityLevel desc
|
|
920
|
-
`;
|
|
921
|
-
// Query 3: Recent errors - deduplicated by showing one per OperationId
|
|
922
|
-
const recentErrorsQuery = showDetails ? (dedupe ? `
|
|
923
|
-
AppExceptions
|
|
924
|
-
${appFilter}
|
|
925
|
-
| summarize
|
|
926
|
-
TimeGenerated = max(TimeGenerated),
|
|
927
|
-
RetryCount = count(),
|
|
928
|
-
OuterMessage = take_any(OuterMessage)
|
|
929
|
-
by OperationId, AppRoleName, ExceptionType
|
|
930
|
-
| project TimeGenerated, AppRoleName, ExceptionType, OuterMessage, RetryCount
|
|
931
|
-
| order by TimeGenerated desc
|
|
932
|
-
| take ${limit}
|
|
933
|
-
` : `
|
|
934
|
-
AppExceptions
|
|
935
|
-
${appFilter}
|
|
936
|
-
| project TimeGenerated, AppRoleName, ExceptionType, OuterMessage
|
|
937
|
-
| order by TimeGenerated desc
|
|
938
|
-
| take ${limit}
|
|
939
|
-
`) : null;
|
|
940
|
-
// Execute queries
|
|
941
|
-
const [exceptionSummary, traceSeverity, recentErrors] = await Promise.all([
|
|
942
|
-
service.executeQuery(resourceId, exceptionSummaryQuery, timespanValue),
|
|
943
|
-
service.executeQuery(resourceId, traceSeverityQuery, timespanValue),
|
|
944
|
-
recentErrorsQuery ? service.executeQuery(resourceId, recentErrorsQuery, timespanValue) : null,
|
|
945
|
-
]);
|
|
946
|
-
// Build markdown report
|
|
947
|
-
let markdown = `# App Investigation Report\n\n`;
|
|
948
|
-
markdown += `**Filter:** ${appNamePattern || '(all apps)'}\n`;
|
|
949
|
-
markdown += `**Time range:** ${timespanValue}\n`;
|
|
950
|
-
markdown += dedupe ? `**Deduplication:** enabled (grouped by OperationId)\n\n` : '\n';
|
|
951
|
-
markdown += `## Exception Summary\n\n`;
|
|
952
|
-
if (exceptionSummary.tables && exceptionSummary.tables.length > 0 && exceptionSummary.tables[0].rows.length > 0) {
|
|
953
|
-
markdown += formatTableAsMarkdown(exceptionSummary.tables[0]);
|
|
954
|
-
}
|
|
955
|
-
else {
|
|
956
|
-
markdown += '*No exceptions found*';
|
|
957
|
-
}
|
|
958
|
-
markdown += '\n\n';
|
|
959
|
-
markdown += `## Trace Severity Distribution\n\n`;
|
|
960
|
-
if (traceSeverity.tables && traceSeverity.tables.length > 0 && traceSeverity.tables[0].rows.length > 0) {
|
|
961
|
-
markdown += formatTableAsMarkdown(traceSeverity.tables[0]);
|
|
962
|
-
}
|
|
963
|
-
else {
|
|
964
|
-
markdown += '*No traces found*';
|
|
965
|
-
}
|
|
966
|
-
markdown += '\n\n';
|
|
967
|
-
if (showDetails && recentErrors) {
|
|
968
|
-
markdown += `## Recent Errors (${limit} max)\n\n`;
|
|
969
|
-
if (recentErrors.tables && recentErrors.tables.length > 0 && recentErrors.tables[0].rows.length > 0) {
|
|
970
|
-
markdown += formatTableAsMarkdown(recentErrors.tables[0]);
|
|
971
|
-
}
|
|
972
|
-
else {
|
|
973
|
-
markdown += '*No recent errors*';
|
|
974
|
-
}
|
|
975
|
-
}
|
|
976
|
-
return {
|
|
977
|
-
content: [{ type: "text", text: markdown }],
|
|
978
|
-
};
|
|
979
|
-
}
|
|
980
|
-
catch (error) {
|
|
981
|
-
console.error("Error investigating app:", error);
|
|
982
|
-
return {
|
|
983
|
-
content: [
|
|
984
|
-
{
|
|
985
|
-
type: "text",
|
|
986
|
-
text: `Failed to investigate app: ${error.message}`,
|
|
987
|
-
},
|
|
988
|
-
],
|
|
989
|
-
isError: true
|
|
990
|
-
};
|
|
991
|
-
}
|
|
992
|
-
});
|
|
993
|
-
server.tool("la-investigate-sync", "Investigate SmartConnectorCloud (SCC) sync failures. Only useful for clients using SmartConnectorCloud. Auto-derives sync app name from workspace ID (log-{env}-{client}-... → func-{env}-{client}-sc-sync-...). Best tool for SCC sync debugging.", {
|
|
994
|
-
resourceId: z.string().describe("Resource ID (e.g., 'log-dev-rtpi-uks-01'). Environment and client are auto-extracted."),
|
|
995
|
-
timespan: z.string().optional().describe(descWithExamples("Time range (default: PT8H - typical work day)", TIMESPAN_EXAMPLES)),
|
|
996
|
-
includeDetails: z.boolean().optional().describe("Include recent error details (default: true)"),
|
|
997
|
-
detailsLimit: z.number().optional().describe("Max recent errors to include (default: 10)"),
|
|
998
|
-
}, async ({ resourceId, timespan, includeDetails, detailsLimit }) => {
|
|
999
|
-
try {
|
|
1000
|
-
const service = getLogAnalyticsService();
|
|
1001
|
-
const timespanValue = timespan || 'PT8H';
|
|
1002
|
-
const showDetails = includeDetails !== false;
|
|
1003
|
-
const limit = detailsLimit || 10;
|
|
1004
|
-
// Parse resourceId to extract environment and client
|
|
1005
|
-
// Pattern: log-{environment}-{client}-...
|
|
1006
|
-
const match = resourceId.match(/^log-([^-]+)-([^-]+)/);
|
|
1007
|
-
if (!match) {
|
|
1008
|
-
return {
|
|
1009
|
-
content: [{
|
|
1010
|
-
type: "text",
|
|
1011
|
-
text: `Could not parse environment/client from resourceId '${resourceId}'. Expected format: log-{environment}-{client}-...`,
|
|
1012
|
-
}],
|
|
1013
|
-
isError: true,
|
|
1014
|
-
};
|
|
1015
|
-
}
|
|
1016
|
-
const environment = match[1];
|
|
1017
|
-
const client = match[2];
|
|
1018
|
-
const syncAppPattern = `func-${environment}-${client}-sc-sync`;
|
|
1019
|
-
// Query 1: Error summary by FunctionName (deduplicated)
|
|
1020
|
-
// FunctionName is in Properties.AzureFunctions_FunctionName
|
|
1021
|
-
const errorsByFunctionQuery = `
|
|
1022
|
-
AppExceptions
|
|
1023
|
-
| where AppRoleName contains "${syncAppPattern}"
|
|
1024
|
-
| extend FunctionName = tostring(Properties.AzureFunctions_FunctionName)
|
|
1025
|
-
| summarize
|
|
1026
|
-
RetryCount = count(),
|
|
1027
|
-
FirstSeen = min(TimeGenerated),
|
|
1028
|
-
LastSeen = max(TimeGenerated),
|
|
1029
|
-
SampleMessage = take_any(OuterMessage)
|
|
1030
|
-
by OperationId, FunctionName, ExceptionType
|
|
1031
|
-
| summarize
|
|
1032
|
-
UniqueErrors = count(),
|
|
1033
|
-
TotalRetries = sum(RetryCount),
|
|
1034
|
-
FirstSeen = min(FirstSeen),
|
|
1035
|
-
LastSeen = max(LastSeen),
|
|
1036
|
-
SampleMessage = take_any(SampleMessage)
|
|
1037
|
-
by FunctionName, ExceptionType
|
|
1038
|
-
| order by UniqueErrors desc
|
|
1039
|
-
`;
|
|
1040
|
-
// Query 2: Error category summary (Dataverse, ServiceBus, SQL, etc.)
|
|
1041
|
-
const errorCategoryQuery = `
|
|
1042
|
-
AppExceptions
|
|
1043
|
-
| where AppRoleName contains "${syncAppPattern}"
|
|
1044
|
-
| extend ErrorCategory = case(
|
|
1045
|
-
ExceptionType contains "FaultException" or ExceptionType contains "OrganizationService", "Dataverse",
|
|
1046
|
-
ExceptionType contains "ServiceBus", "ServiceBus",
|
|
1047
|
-
ExceptionType contains "Sql", "Database",
|
|
1048
|
-
ExceptionType contains "Timeout", "Timeout",
|
|
1049
|
-
ExceptionType contains "Socket" or ExceptionType contains "Http", "Network",
|
|
1050
|
-
"Other"
|
|
1051
|
-
)
|
|
1052
|
-
| summarize
|
|
1053
|
-
RetryCount = count(),
|
|
1054
|
-
UniqueOps = dcount(OperationId)
|
|
1055
|
-
by ErrorCategory
|
|
1056
|
-
| order by UniqueOps desc
|
|
1057
|
-
`;
|
|
1058
|
-
// Query 3: Recent errors with details (deduplicated)
|
|
1059
|
-
const recentErrorsQuery = showDetails ? `
|
|
1060
|
-
AppExceptions
|
|
1061
|
-
| where AppRoleName contains "${syncAppPattern}"
|
|
1062
|
-
| extend FunctionName = tostring(Properties.AzureFunctions_FunctionName)
|
|
1063
|
-
| summarize
|
|
1064
|
-
TimeGenerated = max(TimeGenerated),
|
|
1065
|
-
RetryCount = count(),
|
|
1066
|
-
OuterMessage = take_any(OuterMessage)
|
|
1067
|
-
by OperationId, FunctionName, ExceptionType
|
|
1068
|
-
| project TimeGenerated, FunctionName, ExceptionType, OuterMessage, RetryCount
|
|
1069
|
-
| order by TimeGenerated desc
|
|
1070
|
-
| take ${limit}
|
|
1071
|
-
` : null;
|
|
1072
|
-
// Query 4: Severity 3 (Error) traces for additional context
|
|
1073
|
-
const errorTracesQuery = `
|
|
1074
|
-
AppTraces
|
|
1075
|
-
| where AppRoleName contains "${syncAppPattern}"
|
|
1076
|
-
| where SeverityLevel >= 3
|
|
1077
|
-
| summarize
|
|
1078
|
-
RetryCount = count()
|
|
1079
|
-
by OperationId, Message
|
|
1080
|
-
| summarize
|
|
1081
|
-
UniqueErrors = count(),
|
|
1082
|
-
TotalCount = sum(RetryCount)
|
|
1083
|
-
by Message
|
|
1084
|
-
| order by UniqueErrors desc
|
|
1085
|
-
| take 10
|
|
1086
|
-
`;
|
|
1087
|
-
// Execute queries in parallel
|
|
1088
|
-
const [errorsByFunction, errorCategory, recentErrors, errorTraces] = await Promise.all([
|
|
1089
|
-
service.executeQuery(resourceId, errorsByFunctionQuery, timespanValue),
|
|
1090
|
-
service.executeQuery(resourceId, errorCategoryQuery, timespanValue),
|
|
1091
|
-
recentErrorsQuery ? service.executeQuery(resourceId, recentErrorsQuery, timespanValue) : null,
|
|
1092
|
-
service.executeQuery(resourceId, errorTracesQuery, timespanValue),
|
|
1093
|
-
]);
|
|
1094
|
-
// Build markdown report
|
|
1095
|
-
let markdown = `# SmartConnector Sync Investigation\n\n`;
|
|
1096
|
-
markdown += `**Environment:** ${environment}\n`;
|
|
1097
|
-
markdown += `**Client:** ${client}\n`;
|
|
1098
|
-
markdown += `**Sync App:** ${syncAppPattern}-*\n`;
|
|
1099
|
-
markdown += `**Time range:** ${timespanValue}\n`;
|
|
1100
|
-
markdown += `**Deduplication:** enabled (grouped by OperationId)\n\n`;
|
|
1101
|
-
// Error category summary (quick overview)
|
|
1102
|
-
markdown += `## Error Categories\n\n`;
|
|
1103
|
-
if (errorCategory.tables?.[0]?.rows?.length > 0) {
|
|
1104
|
-
markdown += formatTableAsMarkdown(errorCategory.tables[0]);
|
|
1105
|
-
}
|
|
1106
|
-
else {
|
|
1107
|
-
markdown += '*No errors found* ✅';
|
|
1108
|
-
}
|
|
1109
|
-
markdown += '\n\n';
|
|
1110
|
-
// Errors by function (which sync operations are failing?)
|
|
1111
|
-
markdown += `## Errors by Sync Operation\n\n`;
|
|
1112
|
-
if (errorsByFunction.tables?.[0]?.rows?.length > 0) {
|
|
1113
|
-
markdown += formatTableAsMarkdown(errorsByFunction.tables[0]);
|
|
1114
|
-
}
|
|
1115
|
-
else {
|
|
1116
|
-
markdown += '*No errors found* ✅';
|
|
1117
|
-
}
|
|
1118
|
-
markdown += '\n\n';
|
|
1119
|
-
// Error traces (additional context from logs)
|
|
1120
|
-
markdown += `## Error Traces (Severity 3+)\n\n`;
|
|
1121
|
-
if (errorTraces.tables?.[0]?.rows?.length > 0) {
|
|
1122
|
-
markdown += formatTableAsMarkdown(errorTraces.tables[0]);
|
|
1123
|
-
}
|
|
1124
|
-
else {
|
|
1125
|
-
markdown += '*No error traces found*';
|
|
1126
|
-
}
|
|
1127
|
-
markdown += '\n\n';
|
|
1128
|
-
// Recent errors with details
|
|
1129
|
-
if (showDetails && recentErrors) {
|
|
1130
|
-
markdown += `## Recent Errors (${limit} max)\n\n`;
|
|
1131
|
-
if (recentErrors.tables?.[0]?.rows?.length > 0) {
|
|
1132
|
-
markdown += formatTableAsMarkdown(recentErrors.tables[0]);
|
|
1133
|
-
}
|
|
1134
|
-
else {
|
|
1135
|
-
markdown += '*No recent errors*';
|
|
1136
|
-
}
|
|
1137
|
-
}
|
|
1138
|
-
return {
|
|
1139
|
-
content: [{ type: "text", text: markdown }],
|
|
1140
|
-
};
|
|
1141
|
-
}
|
|
1142
|
-
catch (error) {
|
|
1143
|
-
console.error("Error investigating sync:", error);
|
|
1144
|
-
return {
|
|
1145
|
-
content: [
|
|
1146
|
-
{
|
|
1147
|
-
type: "text",
|
|
1148
|
-
text: `Failed to investigate sync: ${error.message}`,
|
|
1149
|
-
},
|
|
1150
|
-
],
|
|
1151
|
-
isError: true
|
|
1152
|
-
};
|
|
1153
|
-
}
|
|
1154
|
-
});
|
|
1155
|
-
console.error("Log Analytics tools registered: 13 tools, 4 prompts");
|
|
54
|
+
return {
|
|
55
|
+
get logAnalytics() { return getService(); },
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Register Log Analytics tools and prompts to an MCP server.
|
|
60
|
+
* Backward-compatible API for the meta package.
|
|
61
|
+
*/
|
|
62
|
+
export function registerLogAnalyticsTools(server) {
|
|
63
|
+
const ctx = createServiceContext();
|
|
64
|
+
registerAllTools(server, ctx);
|
|
65
|
+
registerAllPrompts(server, ctx);
|
|
1156
66
|
}
|
|
1157
|
-
//
|
|
1158
|
-
|
|
67
|
+
// Backward-compatible exports
|
|
68
|
+
export { LogAnalyticsService } from './services/log-analytics-service.js';
|
|
69
|
+
/**
|
|
70
|
+
* Standalone CLI server (when run directly)
|
|
71
|
+
* Uses realpathSync to resolve symlinks created by npx
|
|
72
|
+
*/
|
|
1159
73
|
if (import.meta.url === pathToFileURL(realpathSync(process.argv[1])).href) {
|
|
1160
74
|
const loadEnv = createEnvLoader();
|
|
1161
75
|
loadEnv();
|
|
1162
76
|
const server = createMcpServer({
|
|
1163
77
|
name: "mcp-log-analytics",
|
|
1164
78
|
version: "1.0.0",
|
|
1165
|
-
capabilities: { tools: {}, prompts: {} }
|
|
79
|
+
capabilities: { tools: {}, prompts: {} },
|
|
1166
80
|
});
|
|
1167
81
|
registerLogAnalyticsTools(server);
|
|
1168
82
|
const transport = new StdioServerTransport();
|