@dynatrace-oss/dynatrace-mcp-server 0.5.0-rc.3 → 0.5.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 +283 -2
- package/dist/authentication/dynatrace-clients.js +2 -2
- package/dist/authentication/dynatrace-clients.test.js +2 -2
- package/dist/capabilities/execute-dql.js +6 -1
- package/dist/capabilities/find-monitored-entity-by-name.js +32 -6
- package/dist/capabilities/find-monitored-entity-by-name.test.js +31 -0
- package/dist/capabilities/get-monitored-entity-details.js +40 -6
- package/dist/index.js +128 -32
- package/dist/utils/dynatrace-entity-types.js +101 -0
- package/dist/utils/dynatrace-entity-types.test.js +161 -0
- package/dist/utils/user-agent.js +12 -0
- package/package.json +4 -4
- package/dist/capabilities/get-logs-for-entity.js +0 -9
package/README.md
CHANGED
|
@@ -11,6 +11,11 @@ Bring real-time observability data directly into your development workflow.
|
|
|
11
11
|
- **Contextual debugging** - Fix issues with full context from monitored exceptions, logs, and anomalies
|
|
12
12
|
- **Security insights** - Get detailed vulnerability analysis and security problem tracking
|
|
13
13
|
- **Natural language queries** - Use AI-powered DQL generation and explanation
|
|
14
|
+
- **Multi-phase incident investigation** - Systematic 4-phase approach with automated impact assessment
|
|
15
|
+
- **Advanced transaction analysis** - Precise root cause identification with file/line-level accuracy
|
|
16
|
+
- **Cross-data source correlation** - Connect problems → spans → logs with trace ID correlation
|
|
17
|
+
- **DevOps automation** - Deployment health gates with automated promotion/rollback logic
|
|
18
|
+
- **Security compliance monitoring** - Multi-cloud compliance assessment with evidence-based investigation
|
|
14
19
|
|
|
15
20
|
## Capabilities
|
|
16
21
|
|
|
@@ -22,6 +27,20 @@ Bring real-time observability data directly into your development workflow.
|
|
|
22
27
|
- Get more information about a monitored entity
|
|
23
28
|
- Get Ownership of an entity
|
|
24
29
|
|
|
30
|
+
## Costs
|
|
31
|
+
|
|
32
|
+
**Important:** While this local MCP server is provided for free, using it to access data in Dynatrace Grail may incur additional costs based
|
|
33
|
+
on your Dynatrace consumption model. This affects `execute_dql` tool and other capabilities that **query** Dynatrace Grail storage, and costs
|
|
34
|
+
depend on the volume (GB scanned/billed).
|
|
35
|
+
|
|
36
|
+
**Before using this MCP server extensively, please:**
|
|
37
|
+
|
|
38
|
+
1. Review your current Dynatrace consumption model and pricing
|
|
39
|
+
2. Understand the cost implications of the specific data you plan to query (logs, events, metrics) - see [Dynatrace Pricing and Rate Card](https://www.dynatrace.com/pricing/)
|
|
40
|
+
3. Start with smaller timeframes (e.g., 12h-24h) and make use of [buckets](https://docs.dynatrace.com/docs/discover-dynatrace/platform/grail/data-model#built-in-grail-buckets) to reduce the cost impact
|
|
41
|
+
|
|
42
|
+
**Note**: We will be providing a way to monitor Query Usage of the dynatrace-mcp-server in the future.
|
|
43
|
+
|
|
25
44
|
### AI-Powered Assistance (Preview)
|
|
26
45
|
|
|
27
46
|
- **Natural Language to DQL** - Convert plain English queries to Dynatrace Query Language
|
|
@@ -31,6 +50,97 @@ Bring real-time observability data directly into your development workflow.
|
|
|
31
50
|
|
|
32
51
|
> **Note:** While Davis CoPilot AI is generally available (GA), the Davis CoPilot APIs are currently in preview. For more information, visit the [Davis CoPilot Preview Community](https://dt-url.net/copilot-community).
|
|
33
52
|
|
|
53
|
+
## 🎯 AI-Powered Observability Workshop Rules
|
|
54
|
+
|
|
55
|
+
Enhance your AI assistant with comprehensive Dynatrace observability analysis capabilities through our streamlined workshop rules. These rules provide hierarchical workflows for security, compliance, incident response, and distributed systems investigation.
|
|
56
|
+
|
|
57
|
+
### **🚀 Quick Setup for AI Assistants**
|
|
58
|
+
|
|
59
|
+
Copy the comprehensive rule files from the [`rules/`](./rules/) directory to your AI assistant's rules directory:
|
|
60
|
+
|
|
61
|
+
**IDE-Specific Locations:**
|
|
62
|
+
|
|
63
|
+
- **Amazon Q**: `.amazonq/rules/` (project) or `~/.aws/amazonq/rules/` (global)
|
|
64
|
+
- **Cursor**: `.cursor/rules/` (project) or via Settings → Rules (global)
|
|
65
|
+
- **Windsurf**: `.windsurfrules/` (project) or via Customizations → Rules (global)
|
|
66
|
+
- **Cline**: `.clinerules/` (project) or `~/Documents/Cline/Rules/` (global)
|
|
67
|
+
- **GitHub Copilot**: `.github/copilot-instructions.md` (project only)
|
|
68
|
+
|
|
69
|
+
Then initialize the agent in your AI chat:
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
load dynatrace mcp
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### **🏗️ Enhanced Analysis Capabilities**
|
|
76
|
+
|
|
77
|
+
The workshop rules unlock advanced observability analysis modes:
|
|
78
|
+
|
|
79
|
+
#### **🚨 Incident Response & Problem Investigation**
|
|
80
|
+
|
|
81
|
+
- **4-phase structured investigation** workflow (Detection → Impact → Root Cause → Resolution)
|
|
82
|
+
- **Cross-data source correlation** (problems → logs → spans → metrics)
|
|
83
|
+
- **Kubernetes-aware incident analysis** with namespace and pod context
|
|
84
|
+
- **User impact assessment** with Davis AI integration
|
|
85
|
+
|
|
86
|
+
#### **📊 Comprehensive Data Investigation**
|
|
87
|
+
|
|
88
|
+
- **Unified log-service-process analysis** in single workflow
|
|
89
|
+
- **Business logic error detection** patterns
|
|
90
|
+
- **Deployment correlation analysis** with ArgoCD/GitOps integration
|
|
91
|
+
- **Golden signals monitoring** (Rate, Errors, Duration, Saturation)
|
|
92
|
+
|
|
93
|
+
#### **🔗 Advanced Transaction Analysis**
|
|
94
|
+
|
|
95
|
+
- **Precise root cause identification** with file/line numbers
|
|
96
|
+
- **Exception stack trace analysis** with business context
|
|
97
|
+
- **Multi-service cascade failure analysis**
|
|
98
|
+
- **Performance impact correlation** across distributed systems
|
|
99
|
+
|
|
100
|
+
#### **🛡️ Enhanced Security & Compliance**
|
|
101
|
+
|
|
102
|
+
- **Latest-scan analysis** prevents outdated data aggregation
|
|
103
|
+
- **Multi-cloud compliance** (AWS, Azure, GCP, Kubernetes)
|
|
104
|
+
- **Evidence-based investigation** with detailed remediation paths
|
|
105
|
+
- **Risk-based scoring** with team-specific guidance
|
|
106
|
+
|
|
107
|
+
#### **⚡ DevOps Automation & SRE**
|
|
108
|
+
|
|
109
|
+
- **Deployment health gates** with automated promotion/rollback
|
|
110
|
+
- **SLO/SLI automation** with error budget calculations
|
|
111
|
+
- **Infrastructure as Code remediation** with auto-generated templates
|
|
112
|
+
- **Alert optimization workflows** with pattern recognition
|
|
113
|
+
|
|
114
|
+
### **📁 Hierarchical Rule Architecture**
|
|
115
|
+
|
|
116
|
+
The rules are organized in a context-window optimized structure:
|
|
117
|
+
|
|
118
|
+
```
|
|
119
|
+
rules/
|
|
120
|
+
├── DynatraceMcpIntegration.md # 🎯 MAIN ORCHESTRATOR
|
|
121
|
+
├── workflows/ # 🔧 ANALYSIS WORKFLOWS
|
|
122
|
+
│ ├── incidentResponse.md # Core incident investigation
|
|
123
|
+
│ ├── DynatraceSecurityCompliance.md # Security & compliance analysis
|
|
124
|
+
│ ├── DynatraceDevOpsIntegration.md # CI/CD automation
|
|
125
|
+
│ └── dataSourceGuides/ # 📊 DATA ANALYSIS GUIDES
|
|
126
|
+
│ ├── dataInvestigation.md # Logs, services, processes
|
|
127
|
+
│ └── DynatraceSpanAnalysis.md # Transaction tracing
|
|
128
|
+
└── reference/ # 📚 TECHNICAL DOCUMENTATION
|
|
129
|
+
├── DynatraceQueryLanguage.md # DQL syntax foundation
|
|
130
|
+
├── DynatraceExplore.md # Field discovery patterns
|
|
131
|
+
├── DynatraceSecurityEvents.md # Security events schema
|
|
132
|
+
└── DynatraceProblemsSpec.md # Problems schema reference
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
**Key Architectural Benefits:**
|
|
136
|
+
|
|
137
|
+
- **All files under 6,500 tokens** - Compatible with most LLM context limits
|
|
138
|
+
- **Hierarchical organization** - Clear entry points and specialized guides
|
|
139
|
+
- **Eliminated circular references** - No more confusing cross-referencing webs
|
|
140
|
+
- **DQL-first approach** - Prefer flexible queries over rigid MCP calls
|
|
141
|
+
|
|
142
|
+
For detailed information about the workshop rules, see the [Rules README](./rules/README.md).
|
|
143
|
+
|
|
34
144
|
## Quickstart
|
|
35
145
|
|
|
36
146
|
You can add this MCP server (using STDIO) to your MCP Client like VS Code, Claude, Cursor, Amazon Q Developer CLI, Windsurf Github Copilot via the package `@dynatrace-oss/dynatrace-mcp-server`.
|
|
@@ -111,6 +221,82 @@ The [Amazon Q Developer CLI](https://docs.aws.amazon.com/amazonq/latest/qdevelop
|
|
|
111
221
|
|
|
112
222
|
This configuration should be stored in `<your-repo>/.amazonq/mcp.json`.
|
|
113
223
|
|
|
224
|
+
### HTTP Server Mode (Alternative)
|
|
225
|
+
|
|
226
|
+
For scenarios where you need to run the MCP server as an HTTP service instead of using stdio (e.g., for stateful sessions, load balancing, or integration with web clients), you can use the HTTP server mode:
|
|
227
|
+
|
|
228
|
+
**Running as HTTP server:**
|
|
229
|
+
|
|
230
|
+
```bash
|
|
231
|
+
# Get help and see all available options
|
|
232
|
+
npx -y @dynatrace-oss/dynatrace-mcp-server --help
|
|
233
|
+
|
|
234
|
+
# Run with HTTP server on default port 3000
|
|
235
|
+
npx -y @dynatrace-oss/dynatrace-mcp-server --http
|
|
236
|
+
|
|
237
|
+
# Run with custom port (using short or long flag)
|
|
238
|
+
npx -y @dynatrace-oss/dynatrace-mcp-server --server -p 8080
|
|
239
|
+
npx -y @dynatrace-oss/dynatrace-mcp-server --http --port 3001
|
|
240
|
+
|
|
241
|
+
# Run with custom host/IP (using short or long flag)
|
|
242
|
+
npx -y @dynatrace-oss/dynatrace-mcp-server --http --host 127.0.0.1
|
|
243
|
+
npx -y @dynatrace-oss/dynatrace-mcp-server --http -H 192.168.0.1
|
|
244
|
+
|
|
245
|
+
# Check version
|
|
246
|
+
npx -y @dynatrace-oss/dynatrace-mcp-server --version
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
**Configuration for MCP clients that support HTTP transport:**
|
|
250
|
+
|
|
251
|
+
```json
|
|
252
|
+
{
|
|
253
|
+
"mcpServers": {
|
|
254
|
+
"dynatrace-http": {
|
|
255
|
+
"url": "http://localhost:3000",
|
|
256
|
+
"transport": "http"
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
**Configuration for MCP clients that support HTTP transport:**
|
|
263
|
+
|
|
264
|
+
```json
|
|
265
|
+
{
|
|
266
|
+
"mcpServers": {
|
|
267
|
+
"dynatrace-http": {
|
|
268
|
+
"url": "http://localhost:3000",
|
|
269
|
+
"transport": "http"
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
### Rule File
|
|
276
|
+
|
|
277
|
+
For efficient result retrieval from Dynatrace, please consider creating a rule file (e.g., [.github/copilot-instructions.md](https://docs.github.com/en/copilot/how-tos/configure-custom-instructions/add-repository-instructions), [.amazonq/rules/](https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/context-project-rules.html)), instructing coding agents on how to get more details for your component/app/service. Here is an example for [easytrade](https://github.com/Dynatrace/easytrade), please adapt the names and filters to fit your use-cases and components:
|
|
278
|
+
|
|
279
|
+
```
|
|
280
|
+
# Observability
|
|
281
|
+
|
|
282
|
+
We use Dynatrace as an Observability solution. This document provides instructions on how to get data for easytrade from Dynatrace using DQL.
|
|
283
|
+
|
|
284
|
+
## How to get any data for my App
|
|
285
|
+
|
|
286
|
+
Depending on the query and tool used, the following filters can be applied to narrow down results:
|
|
287
|
+
|
|
288
|
+
* `contains(entity.name, "easytrade")`
|
|
289
|
+
* `contains(affected_entity.name, "easytrade")`
|
|
290
|
+
* `contains(container.name, "easytrade")`
|
|
291
|
+
|
|
292
|
+
For best results, you can combine these filters with an `OR` operator.
|
|
293
|
+
|
|
294
|
+
## Logs
|
|
295
|
+
|
|
296
|
+
To fetch logs for easytrade, execute `fetch logs | filter contains(container.name, "easyatrade")`.
|
|
297
|
+
For fetching just error-logs, add `| filter loglevel == "ERROR"`.
|
|
298
|
+
```
|
|
299
|
+
|
|
114
300
|
## Environment Variables
|
|
115
301
|
|
|
116
302
|
You can set up authentication via **OAuth Client** or **Platform Tokens** (v0.5.0 and newer) via the following environment variables:
|
|
@@ -134,7 +320,7 @@ Depending on the features you are using, the following scopes are needed:
|
|
|
134
320
|
|
|
135
321
|
- `app-engine:apps:run` - needed for almost all tools
|
|
136
322
|
- `app-engine:functions:run` - needed for for almost all tools
|
|
137
|
-
- `environment-api:entities:read` -
|
|
323
|
+
- `environment-api:entities:read` - for retrieving ownership details from monitored entities (_currently not available for Platform Tokens_)
|
|
138
324
|
- `automation:workflows:read` - read Workflows
|
|
139
325
|
- `automation:workflows:write` - create and update Workflows
|
|
140
326
|
- `automation:workflows:run` - run Workflows
|
|
@@ -159,7 +345,9 @@ Depending on the features you are using, the following scopes are needed:
|
|
|
159
345
|
## ✨ Example prompts ✨
|
|
160
346
|
|
|
161
347
|
Use these example prompts as a starting point. Just copy them into your IDE or agent setup, adapt them to your services/stack/architecture,
|
|
162
|
-
and extend them as needed. They
|
|
348
|
+
and extend them as needed. They're here to help you imagine how real-time observability and automation work together in the MCP context in your IDE.
|
|
349
|
+
|
|
350
|
+
### **Basic Queries & AI Assistance**
|
|
163
351
|
|
|
164
352
|
**Write a DQL query from natural language:**
|
|
165
353
|
|
|
@@ -180,6 +368,85 @@ fetch logs | filter dt.source_entity == 'SERVICE-123' | summarize count(), by:{s
|
|
|
180
368
|
How can I investigate slow database queries in Dynatrace?
|
|
181
369
|
```
|
|
182
370
|
|
|
371
|
+
### **Advanced Incident Investigation**
|
|
372
|
+
|
|
373
|
+
**Multi-phase incident response:**
|
|
374
|
+
|
|
375
|
+
```
|
|
376
|
+
Our checkout service is experiencing high error rates. Start a systematic 4-phase incident investigation:
|
|
377
|
+
1. Detect and triage the active problems
|
|
378
|
+
2. Assess user impact and affected services
|
|
379
|
+
3. Perform cross-data source analysis (problems → spans → logs)
|
|
380
|
+
4. Identify root cause with file/line-level precision
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
**Cross-service failure analysis:**
|
|
384
|
+
|
|
385
|
+
```
|
|
386
|
+
We have cascading failures across our microservices architecture.
|
|
387
|
+
Analyze the entity relationships and trace the failure propagation from the initial problem
|
|
388
|
+
through all downstream services. Show me the correlation timeline.
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
### **Security & Compliance Analysis**
|
|
392
|
+
|
|
393
|
+
**Latest-scan vulnerability assessment:**
|
|
394
|
+
|
|
395
|
+
```
|
|
396
|
+
Perform a comprehensive security analysis using the latest scan data:
|
|
397
|
+
- Check for new vulnerabilities in our production environment
|
|
398
|
+
- Focus on critical and high-severity findings
|
|
399
|
+
- Provide evidence-based remediation paths
|
|
400
|
+
- Generate risk scores with team-specific guidance
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
**Multi-cloud compliance monitoring:**
|
|
404
|
+
|
|
405
|
+
```
|
|
406
|
+
Run a compliance assessment across our AWS, Azure, and Kubernetes environments.
|
|
407
|
+
Check for configuration drift and security posture changes in the last 24 hours.
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
### **DevOps & SRE Automation**
|
|
411
|
+
|
|
412
|
+
**Deployment health gate analysis:**
|
|
413
|
+
|
|
414
|
+
```
|
|
415
|
+
Our latest deployment is showing performance degradation.
|
|
416
|
+
Run deployment health gate analysis with:
|
|
417
|
+
- Golden signals monitoring (Rate, Errors, Duration, Saturation)
|
|
418
|
+
- SLO/SLI validation with error budget calculations
|
|
419
|
+
- Generate automated rollback recommendation if needed
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
**Infrastructure as Code remediation:**
|
|
423
|
+
|
|
424
|
+
```
|
|
425
|
+
Generate Infrastructure as Code templates to remediate the current alert patterns.
|
|
426
|
+
Include automated scaling policies and resource optimization recommendations.
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
### **Deep Transaction Analysis**
|
|
430
|
+
|
|
431
|
+
**Business logic error investigation:**
|
|
432
|
+
|
|
433
|
+
```
|
|
434
|
+
Our payment processing is showing intermittent failures.
|
|
435
|
+
Perform advanced transaction analysis:
|
|
436
|
+
- Extract exception details with full stack traces
|
|
437
|
+
- Correlate with deployment events and ArgoCD changes
|
|
438
|
+
- Identify the exact code location causing the issue
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
**Performance correlation analysis:**
|
|
442
|
+
|
|
443
|
+
```
|
|
444
|
+
Analyze the performance impact across our distributed system for the slow checkout flow.
|
|
445
|
+
Show me the complete trace analysis with business context and identify bottlenecks.
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
### **Traditional Use Cases (Enhanced)**
|
|
449
|
+
|
|
183
450
|
**Find open vulnerabilities on production, setup alert:**
|
|
184
451
|
|
|
185
452
|
```
|
|
@@ -301,6 +568,20 @@ Third, create a `.env` file in this repository (you can copy from `.env.template
|
|
|
301
568
|
|
|
302
569
|
Finally, make changes to your code and compile it with `npm run build` or just run `npm run watch` and it auto-compiles.
|
|
303
570
|
|
|
571
|
+
## Releasing
|
|
572
|
+
|
|
573
|
+
When you are preparing for a release, you can use GitHub Copilot to guide you through the preparations.
|
|
574
|
+
|
|
575
|
+
In Visual Studio Code, you can use `/release` in the chat with Copilot in Agent Mode, which will execute [release.prompt.md](.github/prompts/release.prompt.md).
|
|
576
|
+
|
|
577
|
+
You may include additional information such as the version number. If not specified, you will be asked.
|
|
578
|
+
|
|
579
|
+
This will
|
|
580
|
+
|
|
581
|
+
- prepare the [changelog](CHANGELOG.md),
|
|
582
|
+
- update the version number in [package.json](package.json),
|
|
583
|
+
- commit the changes.
|
|
584
|
+
|
|
304
585
|
## Notes
|
|
305
586
|
|
|
306
587
|
This product is not officially supported by Dynatrace.
|
|
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.createDtHttpClient = void 0;
|
|
4
4
|
const http_client_1 = require("@dynatrace-sdk/http-client");
|
|
5
5
|
const dt_app_1 = require("dt-app");
|
|
6
|
-
const
|
|
6
|
+
const user_agent_1 = require("../utils/user-agent");
|
|
7
7
|
/**
|
|
8
8
|
* Uses the provided oauth Client ID and Secret and requests a token via client-credentials flow
|
|
9
9
|
* @param clientId - OAuth Client ID for Dynatrace
|
|
@@ -60,7 +60,7 @@ const createBearerTokenHttpClient = async (environmentUrl, dtPlatformToken) => {
|
|
|
60
60
|
baseUrl: environmentUrl,
|
|
61
61
|
defaultHeaders: {
|
|
62
62
|
'Authorization': `Bearer ${dtPlatformToken}`,
|
|
63
|
-
'User-Agent':
|
|
63
|
+
'User-Agent': (0, user_agent_1.getUserAgent)(),
|
|
64
64
|
},
|
|
65
65
|
});
|
|
66
66
|
};
|
|
@@ -62,7 +62,7 @@ describe('dynatrace-clients', () => {
|
|
|
62
62
|
baseUrl: environmentUrl,
|
|
63
63
|
defaultHeaders: {
|
|
64
64
|
'Authorization': 'Bearer test-access-token',
|
|
65
|
-
'User-Agent':
|
|
65
|
+
'User-Agent': expect.stringMatching(/^dynatrace-mcp-server\/v1\.0\.0-test \(\w+-\w+\)$/),
|
|
66
66
|
},
|
|
67
67
|
});
|
|
68
68
|
expect(result).toBeInstanceOf(http_client_1.PlatformHttpClient);
|
|
@@ -131,7 +131,7 @@ describe('dynatrace-clients', () => {
|
|
|
131
131
|
baseUrl: environmentUrl,
|
|
132
132
|
defaultHeaders: {
|
|
133
133
|
'Authorization': `Bearer ${dtPlatformToken}`,
|
|
134
|
-
'User-Agent':
|
|
134
|
+
'User-Agent': expect.stringMatching(/^dynatrace-mcp-server\/v1\.0\.0-test \(\w+-\w+\)$/),
|
|
135
135
|
},
|
|
136
136
|
});
|
|
137
137
|
expect(result).toBeInstanceOf(http_client_1.PlatformHttpClient);
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.executeDql = exports.verifyDqlStatement = void 0;
|
|
4
4
|
const client_query_1 = require("@dynatrace-sdk/client-query");
|
|
5
|
+
const user_agent_1 = require("../utils/user-agent");
|
|
5
6
|
const verifyDqlStatement = async (dtClient, dqlStatement) => {
|
|
6
7
|
const queryAssistanceClient = new client_query_1.QueryAssistanceClient(dtClient);
|
|
7
8
|
const response = await queryAssistanceClient.queryVerify({
|
|
@@ -22,7 +23,10 @@ exports.verifyDqlStatement = verifyDqlStatement;
|
|
|
22
23
|
*/
|
|
23
24
|
const executeDql = async (dtClient, body) => {
|
|
24
25
|
const queryExecutionClient = new client_query_1.QueryExecutionClient(dtClient);
|
|
25
|
-
const response = await queryExecutionClient.queryExecute({
|
|
26
|
+
const response = await queryExecutionClient.queryExecute({
|
|
27
|
+
body,
|
|
28
|
+
dtClientContext: (0, user_agent_1.getUserAgent)(),
|
|
29
|
+
});
|
|
26
30
|
if (response.result) {
|
|
27
31
|
// return response result immediately
|
|
28
32
|
return response.result.records;
|
|
@@ -36,6 +40,7 @@ const executeDql = async (dtClient, body) => {
|
|
|
36
40
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
37
41
|
pollResponse = await queryExecutionClient.queryPoll({
|
|
38
42
|
requestToken: response.requestToken,
|
|
43
|
+
dtClientContext: (0, user_agent_1.getUserAgent)(),
|
|
39
44
|
});
|
|
40
45
|
// done - let's return it
|
|
41
46
|
if (pollResponse.result) {
|
|
@@ -1,13 +1,39 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.findMonitoredEntityByName = void 0;
|
|
3
|
+
exports.findMonitoredEntityByName = exports.generateDqlSearchEntityCommand = void 0;
|
|
4
4
|
const execute_dql_1 = require("./execute-dql");
|
|
5
|
+
const dynatrace_entity_types_1 = require("../utils/dynatrace-entity-types");
|
|
6
|
+
/**
|
|
7
|
+
* Construct a DQL statement like "fetch <entityType> | search "*<entityName>*" | fieldsAdd entity.type" for each entity type,
|
|
8
|
+
* and join them with " | append [ ... ]"
|
|
9
|
+
* @param entityName
|
|
10
|
+
* @returns DQL Statement for searching all entity types
|
|
11
|
+
*/
|
|
12
|
+
const generateDqlSearchEntityCommand = (entityName) => {
|
|
13
|
+
const fetchDqlCommands = dynatrace_entity_types_1.DYNATRACE_ENTITY_TYPES.map((entityType, index) => {
|
|
14
|
+
const dql = `fetch ${entityType} | search "*${entityName}*" | fieldsAdd entity.type`;
|
|
15
|
+
if (index === 0) {
|
|
16
|
+
return dql;
|
|
17
|
+
}
|
|
18
|
+
return ` | append [ ${dql} ]\n`;
|
|
19
|
+
});
|
|
20
|
+
return fetchDqlCommands.join('');
|
|
21
|
+
};
|
|
22
|
+
exports.generateDqlSearchEntityCommand = generateDqlSearchEntityCommand;
|
|
23
|
+
/**
|
|
24
|
+
* Find a monitored entity by name via DQL
|
|
25
|
+
* @param dtClient
|
|
26
|
+
* @param entityName
|
|
27
|
+
* @returns A string with the entity details like id, name and type, or an error message if no entity was found
|
|
28
|
+
*/
|
|
5
29
|
const findMonitoredEntityByName = async (dtClient, entityName) => {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
30
|
+
if (!entityName) {
|
|
31
|
+
return 'You need to provide an entity name to search for.';
|
|
32
|
+
}
|
|
33
|
+
// construct a DQL statement for searching the entityName over all entity types
|
|
34
|
+
const dql = (0, exports.generateDqlSearchEntityCommand)(entityName);
|
|
35
|
+
// Get response from API
|
|
36
|
+
// Note: This may be slow, as we are appending multiple entity types above
|
|
11
37
|
const dqlResponse = await (0, execute_dql_1.executeDql)(dtClient, { query: dql });
|
|
12
38
|
if (dqlResponse && dqlResponse.length > 0) {
|
|
13
39
|
let resp = 'The following monitored entities were found:\n';
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const dynatrace_entity_types_1 = require("../utils/dynatrace-entity-types");
|
|
4
|
+
const find_monitored_entity_by_name_1 = require("./find-monitored-entity-by-name");
|
|
5
|
+
describe('generateDqlSearchCommand', () => {
|
|
6
|
+
beforeEach(() => {
|
|
7
|
+
// Ensure we have at least some entity types for testing
|
|
8
|
+
expect(dynatrace_entity_types_1.DYNATRACE_ENTITY_TYPES.length).toBeGreaterThan(0);
|
|
9
|
+
});
|
|
10
|
+
it('should include all entity types from DYNATRACE_ENTITY_TYPES', () => {
|
|
11
|
+
const entityName = 'test';
|
|
12
|
+
const result = (0, find_monitored_entity_by_name_1.generateDqlSearchEntityCommand)(entityName);
|
|
13
|
+
console.log(result);
|
|
14
|
+
// Check that all entity types are included in the DQL
|
|
15
|
+
dynatrace_entity_types_1.DYNATRACE_ENTITY_TYPES.forEach((entityType) => {
|
|
16
|
+
expect(result).toContain(`fetch ${entityType}`);
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
it('should structure the DQL correctly with first fetch and subsequent appends', () => {
|
|
20
|
+
const entityName = 'test';
|
|
21
|
+
const result = (0, find_monitored_entity_by_name_1.generateDqlSearchEntityCommand)(entityName);
|
|
22
|
+
// First entity type should not have append prefix
|
|
23
|
+
const firstEntityType = dynatrace_entity_types_1.DYNATRACE_ENTITY_TYPES[0];
|
|
24
|
+
expect(result).toContain(`fetch ${firstEntityType} | search "*${entityName}*" | fieldsAdd entity.type`);
|
|
25
|
+
// Subsequent entity types should have append prefix (if there are more than 1)
|
|
26
|
+
if (dynatrace_entity_types_1.DYNATRACE_ENTITY_TYPES.length > 1) {
|
|
27
|
+
const secondEntityType = dynatrace_entity_types_1.DYNATRACE_ENTITY_TYPES[1];
|
|
28
|
+
expect(result).toContain(` | append [ fetch ${secondEntityType} | search "*${entityName}*" | fieldsAdd entity.type ]`);
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
});
|
|
@@ -1,12 +1,46 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.getMonitoredEntityDetails = void 0;
|
|
4
|
-
const
|
|
4
|
+
const execute_dql_1 = require("./execute-dql");
|
|
5
|
+
const dynatrace_entity_types_1 = require("../utils/dynatrace-entity-types");
|
|
6
|
+
/**
|
|
7
|
+
* Get monitored entity details by entity ID via DQL
|
|
8
|
+
* @param dtClient
|
|
9
|
+
* @param entityId
|
|
10
|
+
* @returns Details about the monitored entity, or undefined in case we couldn't find it
|
|
11
|
+
*/
|
|
5
12
|
const getMonitoredEntityDetails = async (dtClient, entityId) => {
|
|
6
|
-
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
13
|
+
// Try to determine the entity type directly from the entity ID (e.g., PROCESS_GROUP-F84E4759809ADA84 -> dt.entity.process_group)
|
|
14
|
+
const entityType = (0, dynatrace_entity_types_1.getEntityTypeFromId)(entityId);
|
|
15
|
+
if (!entityType) {
|
|
16
|
+
console.error(`Couldn't determine entity type for ID: ${entityId}. Please raise an issue at https://github.com/dynatrace-oss/dynatrace-mcp/issues if you believe this is a bug.`);
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
// construct DQL statement like `fetch dt.entity.hosts | filter id == "HOST-1234"`
|
|
20
|
+
const dql = `fetch ${entityType} | filter id == "${entityId}" | expand tags | fieldsAdd entity.type`;
|
|
21
|
+
// Get response from API
|
|
22
|
+
const dqlResponse = await (0, execute_dql_1.executeDql)(dtClient, { query: dql });
|
|
23
|
+
// verify response and length
|
|
24
|
+
if (!dqlResponse || dqlResponse.length === 0) {
|
|
25
|
+
console.error(`No entity found for ID: ${entityId}`);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
// in case we have more than one entity -> log it
|
|
29
|
+
if (dqlResponse.length > 1) {
|
|
30
|
+
console.error(`Multiple entities (${dqlResponse.length}) found for entity ID: ${entityId}. Returning the first one.`);
|
|
31
|
+
}
|
|
32
|
+
const entity = dqlResponse[0];
|
|
33
|
+
// make typescript happy; entity should never be null though
|
|
34
|
+
if (!entity) {
|
|
35
|
+
console.error(`No entity found for ID: ${entityId}`);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
// return entity details
|
|
39
|
+
return {
|
|
40
|
+
entityId: String(entity.id),
|
|
41
|
+
displayName: String(entity['entity.name']),
|
|
42
|
+
type: String(entity['entity.type']),
|
|
43
|
+
allProperties: entity || undefined,
|
|
44
|
+
};
|
|
11
45
|
};
|
|
12
46
|
exports.getMonitoredEntityDetails = getMonitoredEntityDetails;
|
package/dist/index.js
CHANGED
|
@@ -4,8 +4,12 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
4
4
|
const client_platform_management_service_1 = require("@dynatrace-sdk/client-platform-management-service");
|
|
5
5
|
const shared_errors_1 = require("@dynatrace-sdk/shared-errors");
|
|
6
6
|
const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
7
|
+
const streamableHttp_js_1 = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
|
|
7
8
|
const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
8
9
|
const dotenv_1 = require("dotenv");
|
|
10
|
+
const node_http_1 = require("node:http");
|
|
11
|
+
const node_crypto_1 = require("node:crypto");
|
|
12
|
+
const commander_1 = require("commander");
|
|
9
13
|
const zod_1 = require("zod");
|
|
10
14
|
const package_json_1 = require("../package.json");
|
|
11
15
|
const dynatrace_clients_1 = require("./authentication/dynatrace-clients");
|
|
@@ -13,7 +17,6 @@ const list_vulnerabilities_1 = require("./capabilities/list-vulnerabilities");
|
|
|
13
17
|
const list_problems_1 = require("./capabilities/list-problems");
|
|
14
18
|
const get_monitored_entity_details_1 = require("./capabilities/get-monitored-entity-details");
|
|
15
19
|
const get_ownership_information_1 = require("./capabilities/get-ownership-information");
|
|
16
|
-
const get_logs_for_entity_1 = require("./capabilities/get-logs-for-entity");
|
|
17
20
|
const get_events_for_cluster_1 = require("./capabilities/get-events-for-cluster");
|
|
18
21
|
const create_workflow_for_problem_notification_1 = require("./capabilities/create-workflow-for-problem-notification");
|
|
19
22
|
const update_workflow_1 = require("./capabilities/update-workflow");
|
|
@@ -27,6 +30,24 @@ let scopesBase = [
|
|
|
27
30
|
'app-engine:apps:run', // needed for environmentInformationClient
|
|
28
31
|
'app-engine:functions:run', // needed for environmentInformationClient
|
|
29
32
|
];
|
|
33
|
+
/**
|
|
34
|
+
* Performs a connection test to the Dynatrace environment.
|
|
35
|
+
* Throws an error if the connection or authentication fails.
|
|
36
|
+
*/
|
|
37
|
+
async function testDynatraceConnection(dtEnvironment, oauthClientId, oauthClientSecret, dtPlatformToken) {
|
|
38
|
+
const dtClient = await (0, dynatrace_clients_1.createDtHttpClient)(dtEnvironment, scopesBase, oauthClientId, oauthClientSecret, dtPlatformToken);
|
|
39
|
+
const environmentInformationClient = new client_platform_management_service_1.EnvironmentInformationClient(dtClient);
|
|
40
|
+
// This call will fail if authentication is incorrect.
|
|
41
|
+
await environmentInformationClient.getEnvironmentInformation();
|
|
42
|
+
}
|
|
43
|
+
function handleClientRequestError(error) {
|
|
44
|
+
let additionalErrorInformation = '';
|
|
45
|
+
if (error.response.status === 403) {
|
|
46
|
+
additionalErrorInformation =
|
|
47
|
+
'Note: Your user or service-user is most likely lacking the necessary permissions/scopes for this API Call.';
|
|
48
|
+
}
|
|
49
|
+
return `Client Request Error: ${error.message} with HTTP status: ${error.response.status}. ${additionalErrorInformation} (body: ${JSON.stringify(error.body)})`;
|
|
50
|
+
}
|
|
30
51
|
const main = async () => {
|
|
31
52
|
// read Environment variables
|
|
32
53
|
let dynatraceEnv;
|
|
@@ -39,6 +60,34 @@ const main = async () => {
|
|
|
39
60
|
}
|
|
40
61
|
console.error(`Initializing Dynatrace MCP Server v${package_json_1.version}...`);
|
|
41
62
|
const { oauthClientId, oauthClientSecret, dtEnvironment, dtPlatformToken, slackConnectionId } = dynatraceEnv;
|
|
63
|
+
// Test connection on startup
|
|
64
|
+
let retryCount = 0;
|
|
65
|
+
const maxRetries = 5;
|
|
66
|
+
while (true) {
|
|
67
|
+
try {
|
|
68
|
+
console.error(`Testing connection to Dynatrace environment: ${dtEnvironment}... (Attempt ${retryCount + 1} of ${maxRetries})`);
|
|
69
|
+
await testDynatraceConnection(dtEnvironment, oauthClientId, oauthClientSecret, dtPlatformToken);
|
|
70
|
+
console.error(`Successfully connected to the Dynatrace environment at ${dtEnvironment}.`);
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
console.error(`Error: Could not connect to the Dynatrace environment.`);
|
|
75
|
+
if ((0, shared_errors_1.isClientRequestError)(error)) {
|
|
76
|
+
console.error(handleClientRequestError(error));
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
console.error(`Error: ${error.message}`);
|
|
80
|
+
}
|
|
81
|
+
retryCount++;
|
|
82
|
+
if (retryCount >= maxRetries) {
|
|
83
|
+
console.error(`Fatal: Maximum number of connection retries (${maxRetries}) exceeded. Exiting.`);
|
|
84
|
+
process.exit(1);
|
|
85
|
+
}
|
|
86
|
+
const delay = Math.pow(2, retryCount) * 1000; // Exponential backoff
|
|
87
|
+
console.error(`Retrying in ${delay / 1000} seconds...`);
|
|
88
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
89
|
+
}
|
|
90
|
+
}
|
|
42
91
|
console.error(`Starting Dynatrace MCP Server v${package_json_1.version}...`);
|
|
43
92
|
const server = new mcp_js_1.McpServer({
|
|
44
93
|
name: 'Dynatrace MCP Server',
|
|
@@ -60,19 +109,8 @@ const main = async () => {
|
|
|
60
109
|
catch (error) {
|
|
61
110
|
// check if it's an error originating from the Dynatrace SDK / API Gateway and provide an appropriate message to the user
|
|
62
111
|
if ((0, shared_errors_1.isClientRequestError)(error)) {
|
|
63
|
-
const e = error;
|
|
64
|
-
let additionalErrorInformation = '';
|
|
65
|
-
if (e.response.status == 403) {
|
|
66
|
-
additionalErrorInformation =
|
|
67
|
-
'Note: Your user or service-user is most likely lacking the necessary permissions/scopes for this API Call.';
|
|
68
|
-
}
|
|
69
112
|
return {
|
|
70
|
-
content: [
|
|
71
|
-
{
|
|
72
|
-
type: 'text',
|
|
73
|
-
text: `Client Request Error: ${e.message} with HTTP status: ${e.response.status}. ${additionalErrorInformation} (body: ${JSON.stringify(e.body)})`,
|
|
74
|
-
},
|
|
75
|
-
],
|
|
113
|
+
content: [{ type: 'text', text: handleClientRequestError(error) }],
|
|
76
114
|
isError: true,
|
|
77
115
|
};
|
|
78
116
|
}
|
|
@@ -86,7 +124,7 @@ const main = async () => {
|
|
|
86
124
|
};
|
|
87
125
|
server.tool(name, description, paramsSchema, (args) => wrappedCb(args));
|
|
88
126
|
};
|
|
89
|
-
tool('get_environment_info', 'Get information about the connected Dynatrace Environment (Tenant)', {}, async ({}) => {
|
|
127
|
+
tool('get_environment_info', 'Get information about the connected Dynatrace Environment (Tenant) and verify the connection and authentication.', {}, async ({}) => {
|
|
90
128
|
// create an oauth-client
|
|
91
129
|
const dtClient = await (0, dynatrace_clients_1.createDtHttpClient)(dtEnvironment, scopesBase, oauthClientId, oauthClientSecret, dtPlatformToken);
|
|
92
130
|
const environmentInformationClient = new client_platform_management_service_1.EnvironmentInformationClient(dtClient);
|
|
@@ -183,20 +221,23 @@ const main = async () => {
|
|
|
183
221
|
return 'No problems found';
|
|
184
222
|
}
|
|
185
223
|
});
|
|
186
|
-
tool('find_entity_by_name', 'Get the entityId of a monitored entity based on the name of the entity on Dynatrace', {
|
|
187
|
-
entityName: zod_1.z.string(),
|
|
224
|
+
tool('find_entity_by_name', 'Get the entityId of a monitored entity (service, host, process-group, application, kubernetes-node, ...) within the topology based on the name of the entity on Dynatrace', {
|
|
225
|
+
entityName: zod_1.z.string().describe('Name of the entity to search for, e.g., "my-service" or "my-host"'),
|
|
188
226
|
}, async ({ entityName }) => {
|
|
189
|
-
const dtClient = await (0, dynatrace_clients_1.createDtHttpClient)(dtEnvironment, scopesBase.concat('
|
|
227
|
+
const dtClient = await (0, dynatrace_clients_1.createDtHttpClient)(dtEnvironment, scopesBase.concat('storage:entities:read'), oauthClientId, oauthClientSecret, dtPlatformToken);
|
|
190
228
|
const entityResponse = await (0, find_monitored_entity_by_name_1.findMonitoredEntityByName)(dtClient, entityName);
|
|
191
229
|
return entityResponse;
|
|
192
230
|
});
|
|
193
231
|
tool('get_entity_details', 'Get details of a monitored entity based on the entityId on Dynatrace', {
|
|
194
232
|
entityId: zod_1.z.string().optional(),
|
|
195
233
|
}, async ({ entityId }) => {
|
|
196
|
-
const dtClient = await (0, dynatrace_clients_1.createDtHttpClient)(dtEnvironment, scopesBase.concat('
|
|
234
|
+
const dtClient = await (0, dynatrace_clients_1.createDtHttpClient)(dtEnvironment, scopesBase.concat('storage:entities:read'), oauthClientId, oauthClientSecret, dtPlatformToken);
|
|
197
235
|
const entityDetails = await (0, get_monitored_entity_details_1.getMonitoredEntityDetails)(dtClient, entityId);
|
|
236
|
+
if (!entityDetails) {
|
|
237
|
+
return `No entity found with entityId: ${entityId}`;
|
|
238
|
+
}
|
|
198
239
|
let resp = `Entity ${entityDetails.displayName} of type ${entityDetails.type} with \`entityId\` ${entityDetails.entityId}\n` +
|
|
199
|
-
`Properties: ${JSON.stringify(entityDetails.
|
|
240
|
+
`Properties: ${JSON.stringify(entityDetails.allProperties)}\n`;
|
|
200
241
|
if (entityDetails.type == 'SERVICE') {
|
|
201
242
|
resp += `You can find more information about the service at ${dtEnvironment}/ui/apps/dynatrace.services/explorer?detailsId=${entityDetails.entityId}&sidebarOpen=false`;
|
|
202
243
|
}
|
|
@@ -219,13 +260,6 @@ const main = async () => {
|
|
|
219
260
|
const response = await (0, send_slack_message_1.sendSlackMessage)(dtClient, slackConnectionId, channel, message);
|
|
220
261
|
return `Message sent to Slack channel: ${JSON.stringify(response)}`;
|
|
221
262
|
});
|
|
222
|
-
tool('get_logs_for_entity', 'Get Logs for a monitored entity based on name of the entity on Dynatrace', {
|
|
223
|
-
entityName: zod_1.z.string().optional(),
|
|
224
|
-
}, async ({ entityName }) => {
|
|
225
|
-
const dtClient = await (0, dynatrace_clients_1.createDtHttpClient)(dtEnvironment, scopesBase.concat('storage:logs:read'), oauthClientId, oauthClientSecret, dtPlatformToken);
|
|
226
|
-
const logs = await (0, get_logs_for_entity_1.getLogsForEntity)(dtClient, entityName);
|
|
227
|
-
return `Logs:\n${JSON.stringify(logs?.map((logLine) => (logLine ? logLine.content : 'Empty log')))}`;
|
|
228
|
-
});
|
|
229
263
|
tool('verify_dql', 'Verify a Dynatrace Query Language (DQL) statement on Dynatrace GRAIL before executing it. This step is recommended for DQL statements that have been dynamically created by non-expert tools. For statements coming from the `generate_dql_from_natural_language` tool as well as from documentation, this step can be omitted.', {
|
|
230
264
|
dqlStatement: zod_1.z.string(),
|
|
231
265
|
}, async ({ dqlStatement }) => {
|
|
@@ -246,9 +280,14 @@ const main = async () => {
|
|
|
246
280
|
}
|
|
247
281
|
return resp;
|
|
248
282
|
});
|
|
249
|
-
tool('execute_dql', 'Get Logs, Metrics, Spans or Events from Dynatrace GRAIL by executing a Dynatrace Query Language (DQL) statement.
|
|
250
|
-
|
|
283
|
+
tool('execute_dql', 'Get Logs, Metrics, Spans or Events from Dynatrace GRAIL by executing a Dynatrace Query Language (DQL) statement. ' +
|
|
284
|
+
'You can also use "generate_dql_from_natural_language" to generate a DQL statement based on your request. ' +
|
|
285
|
+
'Note: For more information about available fields for filters and aggregation, use the query "fetch dt.semantic_dictionary.models | filter data_object == \"logs\""', {
|
|
286
|
+
dqlStatement: zod_1.z
|
|
287
|
+
.string()
|
|
288
|
+
.describe('DQL Statement (Ex: "fetch [logs, spans, events] | filter <some-filter> | summarize count(), by:{some-fields}.", "timeseries { avg(<metric-name>), value.A = avg(<metric-name>, scalar: true) }")'),
|
|
251
289
|
}, async ({ dqlStatement }) => {
|
|
290
|
+
// Create a HTTP Client that has all storage:*:read scopes
|
|
252
291
|
const dtClient = await (0, dynatrace_clients_1.createDtHttpClient)(dtEnvironment, scopesBase.concat('storage:buckets:read', // Read all system data stored on Grail
|
|
253
292
|
'storage:logs:read', // Read logs for reliability guardian validations
|
|
254
293
|
'storage:metrics:read', // Read metrics for reliability guardian validations
|
|
@@ -397,10 +436,67 @@ const main = async () => {
|
|
|
397
436
|
resp += JSON.stringify(ownershipInformation);
|
|
398
437
|
return resp;
|
|
399
438
|
});
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
439
|
+
// Parse command line arguments using commander
|
|
440
|
+
const program = new commander_1.Command();
|
|
441
|
+
program
|
|
442
|
+
.name('dynatrace-mcp-server')
|
|
443
|
+
.description('Dynatrace Model Context Protocol (MCP) Server')
|
|
444
|
+
.version(package_json_1.version)
|
|
445
|
+
.option('--http', 'enable HTTP server mode instead of stdio')
|
|
446
|
+
.option('--server', 'enable HTTP server mode (alias for --http)')
|
|
447
|
+
.option('-p, --port <number>', 'port for HTTP server', '3000')
|
|
448
|
+
.option('-H, --host <host>', 'host for HTTP server', '0.0.0.0')
|
|
449
|
+
.parse();
|
|
450
|
+
const options = program.opts();
|
|
451
|
+
const httpMode = options.http || options.server;
|
|
452
|
+
const httpPort = parseInt(options.port, 10);
|
|
453
|
+
const host = options.host || '0.0.0.0';
|
|
454
|
+
if (httpMode) {
|
|
455
|
+
// HTTP server mode
|
|
456
|
+
const httpTransport = new streamableHttp_js_1.StreamableHTTPServerTransport({
|
|
457
|
+
sessionIdGenerator: () => (0, node_crypto_1.randomUUID)(),
|
|
458
|
+
});
|
|
459
|
+
const httpServer = (0, node_http_1.createServer)(async (req, res) => {
|
|
460
|
+
// Parse request body for POST requests
|
|
461
|
+
let body;
|
|
462
|
+
if (req.method === 'POST') {
|
|
463
|
+
const chunks = [];
|
|
464
|
+
for await (const chunk of req) {
|
|
465
|
+
chunks.push(chunk);
|
|
466
|
+
}
|
|
467
|
+
const rawBody = Buffer.concat(chunks).toString();
|
|
468
|
+
try {
|
|
469
|
+
body = JSON.parse(rawBody);
|
|
470
|
+
}
|
|
471
|
+
catch (error) {
|
|
472
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
473
|
+
res.end(JSON.stringify({ error: 'Invalid JSON' }));
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
await httpTransport.handleRequest(req, res, body);
|
|
478
|
+
});
|
|
479
|
+
console.error('Connecting server to HTTP transport...');
|
|
480
|
+
await server.connect(httpTransport);
|
|
481
|
+
// Start HTTP Server on the specified host and port
|
|
482
|
+
httpServer.listen(httpPort, host, () => {
|
|
483
|
+
console.error(`Dynatrace MCP Server running on HTTP at http://${host}:${httpPort}`);
|
|
484
|
+
});
|
|
485
|
+
// Handle graceful shutdown
|
|
486
|
+
process.on('SIGINT', () => {
|
|
487
|
+
console.error('Shutting down HTTP server...');
|
|
488
|
+
httpServer.close(() => {
|
|
489
|
+
process.exit(0);
|
|
490
|
+
});
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
else {
|
|
494
|
+
// Default stdio mode
|
|
495
|
+
const transport = new stdio_js_1.StdioServerTransport();
|
|
496
|
+
console.error('Connecting server to transport...');
|
|
497
|
+
await server.connect(transport);
|
|
498
|
+
console.error('Dynatrace MCP Server running on stdio');
|
|
499
|
+
}
|
|
404
500
|
};
|
|
405
501
|
main().catch((error) => {
|
|
406
502
|
console.error('Fatal error in main():', error);
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Dynatrace Entity Types
|
|
4
|
+
*
|
|
5
|
+
* Complete list of Dynatrace entity types for querying monitored entities.
|
|
6
|
+
* Generated from ENTITY_ID_PREFIX_TO_TYPE_MAP values to ensure consistency.
|
|
7
|
+
* Last Updated: 2025-08-14
|
|
8
|
+
* @see https://docs.dynatrace.com/docs/discover-dynatrace/references/semantic-dictionary/model/dt-entities
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.DYNATRACE_ENTITY_TYPES = void 0;
|
|
12
|
+
exports.getEntityTypeFromId = getEntityTypeFromId;
|
|
13
|
+
/**
|
|
14
|
+
* Entity ID prefixes mapped to their corresponding Dynatrace entity types
|
|
15
|
+
* When adding a new Entity, there should also be a corresponding test in dynatrace-entity-types.test.ts
|
|
16
|
+
* to ensure that the entity type can be correctly derived from the entity ID.
|
|
17
|
+
*
|
|
18
|
+
* Disclaimer: This mapping is based on the current Dynatrace API and may change in future versions. Use at your own risk!
|
|
19
|
+
*
|
|
20
|
+
* Recommendation: Use `verify_dql` and/or `execute_dql` to ensure that the entity type can be queried correctly.
|
|
21
|
+
*/
|
|
22
|
+
const ENTITY_ID_PREFIX_TO_TYPE_MAP = {
|
|
23
|
+
// Core Applications and Services
|
|
24
|
+
APPLICATION: 'dt.entity.application', // Verified!
|
|
25
|
+
SERVICE: 'dt.entity.service', // Verified!
|
|
26
|
+
SERVICE_INSTANCE: 'dt.entity.service_instance', // Verified!
|
|
27
|
+
MOBILE_APPLICATION: 'dt.entity.mobile_application', // Verified! (0 rows found, manually verified that entity exists)
|
|
28
|
+
CUSTOM_APPLICATION: 'dt.entity.custom_application', // Verified!
|
|
29
|
+
// Infrastructure
|
|
30
|
+
HOST: 'dt.entity.host', // Verified!
|
|
31
|
+
HOST_GROUP: 'dt.entity.host_group', // Verified!
|
|
32
|
+
PROCESS_GROUP: 'dt.entity.process_group', // Verified!
|
|
33
|
+
PROCESS_GROUP_INSTANCE: 'dt.entity.process_group_instance', // Verified!
|
|
34
|
+
DISK: 'dt.entity.disk', // Verified!
|
|
35
|
+
NETWORK_INTERFACE: 'dt.entity.network_interface', // Verified!
|
|
36
|
+
// Cloud Services
|
|
37
|
+
CLOUD_APPLICATION: 'dt.entity.cloud_application', // Verified!
|
|
38
|
+
CLOUD_APPLICATION_INSTANCE: 'dt.entity.cloud_application_instance', // Verified!
|
|
39
|
+
CLOUD_APPLICATION_NAMESPACE: 'dt.entity.cloud_application_namespace', // Verified!
|
|
40
|
+
// Containers and Container Groups
|
|
41
|
+
CONTAINER_GROUP: 'dt.entity.container_group', // Verified!
|
|
42
|
+
CONTAINER_GROUP_INSTANCE: 'dt.entity.container_group_instance', // Verified!
|
|
43
|
+
DCG_INSTANCE: 'dt.entity.docker_container_group_instance', // Verified! (0 rows found, manually verified that entity exists, but this might be deprecated / old)
|
|
44
|
+
// Environment
|
|
45
|
+
ENVIRONMENT: 'dt.entity.environment', // Verified!
|
|
46
|
+
// Operating System
|
|
47
|
+
OS: 'dt.entity.os', // Verified!
|
|
48
|
+
// Synthetic Monitoring
|
|
49
|
+
SYNTHETIC_TEST: 'dt.entity.synthetic_test', // Verified!
|
|
50
|
+
SYNTHETIC_LOCATION: 'dt.entity.synthetic_location', // Verified!
|
|
51
|
+
// Custom Devices and Entities
|
|
52
|
+
CUSTOM_DEVICE: 'dt.entity.custom_device', // Verified!
|
|
53
|
+
CUSTOM_DEVICE_GROUP: 'dt.entity.custom_device_group', // Verified!
|
|
54
|
+
// Geolocation
|
|
55
|
+
GEOLOCATION: 'dt.entity.geolocation', // Verified!
|
|
56
|
+
// Database Services
|
|
57
|
+
RELATIONAL_DATABASE_SERVICE: 'dt.entity.relational_database_service', // Verified - might need an additional integration/config to work properly though
|
|
58
|
+
// AWS Services
|
|
59
|
+
EC2_INSTANCE: 'dt.entity.ec2_instance', // Verified!
|
|
60
|
+
AWS_LAMBDA_FUNCTION: 'dt.entity.aws_lambda_function', // Verified!
|
|
61
|
+
AWS_AVAILABILITY_ZONE: 'dt.entity.aws_availability_zone', // Verified!
|
|
62
|
+
AWS_APPLICATION_LOAD_BALANCER: 'dt.entity.aws_application_load_balancer', // Verified!
|
|
63
|
+
AWS_NETWORK_LOAD_BALANCER: 'dt.entity.aws_network_load_balancer', // Verified!
|
|
64
|
+
// GCP Services
|
|
65
|
+
GCP_ZONE: 'dt.entity.gcp_zone', // Verified!
|
|
66
|
+
// Virtual Machines
|
|
67
|
+
AZURE_VM: 'dt.entity.azure_vm', // Verified
|
|
68
|
+
OPENSTACK_VM: 'dt.entity.openstack_vm', // Needs manual verification - available only if OpenStack integration is configured
|
|
69
|
+
// Kubernetes Entities
|
|
70
|
+
KUBERNETES_NODE: 'dt.entity.kubernetes_node', // Verified!
|
|
71
|
+
KUBERNETES_CLUSTER: 'dt.entity.kubernetes_cluster', // Verified!
|
|
72
|
+
KUBERNETES_SERVICE: 'dt.entity.kubernetes_service', // Verified!
|
|
73
|
+
};
|
|
74
|
+
exports.DYNATRACE_ENTITY_TYPES = Object.values(ENTITY_ID_PREFIX_TO_TYPE_MAP).sort();
|
|
75
|
+
/**
|
|
76
|
+
* Maps a Dynatrace entity ID to its corresponding entity type.
|
|
77
|
+
*
|
|
78
|
+
* @param entityId - The Dynatrace entity ID (e.g., "PROCESS_GROUP-F84E4759809ADA84")
|
|
79
|
+
* @returns The corresponding entity type (e.g., "dt.entity.process_group") or null if no mapping exists
|
|
80
|
+
*
|
|
81
|
+
* @example
|
|
82
|
+
* ```typescript
|
|
83
|
+
* getEntityTypeFromId("PROCESS_GROUP-F84E4759809ADA84"); // Returns "dt.entity.process_group"
|
|
84
|
+
* getEntityTypeFromId("APPLICATION-1234567890ABCDEF"); // Returns "dt.entity.application"
|
|
85
|
+
* getEntityTypeFromId("KUBERNETES_SERVICE-ABCDEF1234567890"); // Returns "dt.entity.kubernetes_service"
|
|
86
|
+
* getEntityTypeFromId("INVALID_ID"); // Returns null
|
|
87
|
+
* ```
|
|
88
|
+
*/
|
|
89
|
+
function getEntityTypeFromId(entityId) {
|
|
90
|
+
if (!entityId || typeof entityId !== 'string') {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
// Extract the prefix (everything before the first hyphen)
|
|
94
|
+
const hyphenIndex = entityId.indexOf('-');
|
|
95
|
+
if (hyphenIndex === -1) {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
const prefix = entityId.substring(0, hyphenIndex);
|
|
99
|
+
// Look up the entity type in our mapping
|
|
100
|
+
return ENTITY_ID_PREFIX_TO_TYPE_MAP[prefix] || null;
|
|
101
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const dynatrace_entity_types_1 = require("./dynatrace-entity-types");
|
|
4
|
+
describe('DYNATRACE_ENTITY_TYPES', () => {
|
|
5
|
+
it('should be sorted alphabetically', () => {
|
|
6
|
+
const sortedTypes = [...dynatrace_entity_types_1.DYNATRACE_ENTITY_TYPES].sort();
|
|
7
|
+
expect(dynatrace_entity_types_1.DYNATRACE_ENTITY_TYPES).toEqual(sortedTypes);
|
|
8
|
+
});
|
|
9
|
+
it('should have unique values', () => {
|
|
10
|
+
const uniqueTypes = [...new Set(dynatrace_entity_types_1.DYNATRACE_ENTITY_TYPES)];
|
|
11
|
+
expect(dynatrace_entity_types_1.DYNATRACE_ENTITY_TYPES.length).toBe(uniqueTypes.length);
|
|
12
|
+
});
|
|
13
|
+
});
|
|
14
|
+
describe('getEntityTypeFromId', () => {
|
|
15
|
+
it('should map PROCESS_GROUP entity ID to dt.entity.process_group', () => {
|
|
16
|
+
const result = (0, dynatrace_entity_types_1.getEntityTypeFromId)('PROCESS_GROUP-F84E4759809ADA84');
|
|
17
|
+
expect(result).toBe('dt.entity.process_group');
|
|
18
|
+
});
|
|
19
|
+
it('should map APPLICATION entity ID to dt.entity.application', () => {
|
|
20
|
+
const result = (0, dynatrace_entity_types_1.getEntityTypeFromId)('APPLICATION-1234567890ABCDEF');
|
|
21
|
+
expect(result).toBe('dt.entity.application');
|
|
22
|
+
});
|
|
23
|
+
it('should map SERVICE entity ID to dt.entity.service', () => {
|
|
24
|
+
const result = (0, dynatrace_entity_types_1.getEntityTypeFromId)('SERVICE-ABCDEF1234567890');
|
|
25
|
+
expect(result).toBe('dt.entity.service');
|
|
26
|
+
});
|
|
27
|
+
it('should map HOST entity ID to dt.entity.host', () => {
|
|
28
|
+
const result = (0, dynatrace_entity_types_1.getEntityTypeFromId)('HOST-1234567890ABCDEF');
|
|
29
|
+
expect(result).toBe('dt.entity.host');
|
|
30
|
+
});
|
|
31
|
+
it('should map KUBERNETES_CLUSTER entity ID to dt.entity.kubernetes_cluster', () => {
|
|
32
|
+
const result = (0, dynatrace_entity_types_1.getEntityTypeFromId)('KUBERNETES_CLUSTER-1234567890ABCDEF');
|
|
33
|
+
expect(result).toBe('dt.entity.kubernetes_cluster');
|
|
34
|
+
});
|
|
35
|
+
it('should map CLOUD_APPLICATION entity ID to dt.entity.cloud_application', () => {
|
|
36
|
+
const result = (0, dynatrace_entity_types_1.getEntityTypeFromId)('CLOUD_APPLICATION-FEDCBA0987654321');
|
|
37
|
+
expect(result).toBe('dt.entity.cloud_application');
|
|
38
|
+
});
|
|
39
|
+
it('should return null for entity ID without hyphen', () => {
|
|
40
|
+
const result = (0, dynatrace_entity_types_1.getEntityTypeFromId)('INVALID_ENTITY_ID');
|
|
41
|
+
expect(result).toBeNull();
|
|
42
|
+
});
|
|
43
|
+
it('should return null for unknown entity prefix', () => {
|
|
44
|
+
const result = (0, dynatrace_entity_types_1.getEntityTypeFromId)('UNKNOWN_PREFIX-1234567890ABCDEF');
|
|
45
|
+
expect(result).toBeNull();
|
|
46
|
+
});
|
|
47
|
+
it('should return null for empty string', () => {
|
|
48
|
+
const result = (0, dynatrace_entity_types_1.getEntityTypeFromId)('');
|
|
49
|
+
expect(result).toBeNull();
|
|
50
|
+
});
|
|
51
|
+
it('should return null for null input', () => {
|
|
52
|
+
const result = (0, dynatrace_entity_types_1.getEntityTypeFromId)(null);
|
|
53
|
+
expect(result).toBeNull();
|
|
54
|
+
});
|
|
55
|
+
it('should return null for undefined input', () => {
|
|
56
|
+
const result = (0, dynatrace_entity_types_1.getEntityTypeFromId)(undefined);
|
|
57
|
+
expect(result).toBeNull();
|
|
58
|
+
});
|
|
59
|
+
it('should return null for non-string input', () => {
|
|
60
|
+
const result = (0, dynatrace_entity_types_1.getEntityTypeFromId)(123);
|
|
61
|
+
expect(result).toBeNull();
|
|
62
|
+
});
|
|
63
|
+
it('should handle entity ID with multiple hyphens correctly', () => {
|
|
64
|
+
const result = (0, dynatrace_entity_types_1.getEntityTypeFromId)('PROCESS_GROUP-F84E-4759-809A-DA84');
|
|
65
|
+
expect(result).toBe('dt.entity.process_group');
|
|
66
|
+
});
|
|
67
|
+
// Tests for verified entity types found in the environment
|
|
68
|
+
describe('Verified Entity Types', () => {
|
|
69
|
+
const verifiedEntityTypesTestCases = [
|
|
70
|
+
{
|
|
71
|
+
entityId: 'ENVIRONMENT-2D0F07E264ABBB14',
|
|
72
|
+
expectedType: 'dt.entity.environment',
|
|
73
|
+
description: 'ENVIRONMENT entity ID',
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
entityId: 'KUBERNETES_NODE-03169E17F0A60BE7',
|
|
77
|
+
expectedType: 'dt.entity.kubernetes_node',
|
|
78
|
+
description: 'KUBERNETES_NODE entity ID',
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
entityId: 'SERVICE_INSTANCE-02072B90BE476A9D',
|
|
82
|
+
expectedType: 'dt.entity.service_instance',
|
|
83
|
+
description: 'SERVICE_INSTANCE entity ID',
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
entityId: 'PROCESS_GROUP_INSTANCE-02072B90BE476A9D',
|
|
87
|
+
expectedType: 'dt.entity.process_group_instance',
|
|
88
|
+
description: 'PROCESS_GROUP_INSTANCE entity ID',
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
entityId: 'EC2_INSTANCE-4F09B64F7C5EC72D',
|
|
92
|
+
expectedType: 'dt.entity.ec2_instance',
|
|
93
|
+
description: 'EC2_INSTANCE entity ID',
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
entityId: 'AWS_LAMBDA_FUNCTION-65CB1C926C7A1C03',
|
|
97
|
+
expectedType: 'dt.entity.aws_lambda_function',
|
|
98
|
+
description: 'AWS_LAMBDA_FUNCTION entity ID',
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
entityId: 'CLOUD_APPLICATION_INSTANCE-00BAA09E6EC91E39',
|
|
102
|
+
expectedType: 'dt.entity.cloud_application_instance',
|
|
103
|
+
description: 'CLOUD_APPLICATION_INSTANCE entity ID',
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
entityId: 'DCG_INSTANCE-02072B90BE476A9D',
|
|
107
|
+
expectedType: 'dt.entity.docker_container_group_instance',
|
|
108
|
+
description: 'DCG_INSTANCE entity ID',
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
entityId: 'OS-006B30874F10C837',
|
|
112
|
+
expectedType: 'dt.entity.os',
|
|
113
|
+
description: 'OS entity ID',
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
entityId: 'AWS_AVAILABILITY_ZONE-0BAF92AC03BF3E25',
|
|
117
|
+
expectedType: 'dt.entity.aws_availability_zone',
|
|
118
|
+
description: 'AWS_AVAILABILITY_ZONE entity ID',
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
entityId: 'AWS_APPLICATION_LOAD_BALANCER-52D56F3FD851FF0C',
|
|
122
|
+
expectedType: 'dt.entity.aws_application_load_balancer',
|
|
123
|
+
description: 'AWS_APPLICATION_LOAD_BALANCER entity ID',
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
entityId: 'AWS_NETWORK_LOAD_BALANCER-617A06EFED8F4AB4',
|
|
127
|
+
expectedType: 'dt.entity.aws_network_load_balancer',
|
|
128
|
+
description: 'AWS_NETWORK_LOAD_BALANCER entity ID',
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
entityId: 'SYNTHETIC_LOCATION-0000000000000010',
|
|
132
|
+
expectedType: 'dt.entity.synthetic_location',
|
|
133
|
+
description: 'SYNTHETIC_LOCATION entity ID',
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
entityId: 'GEOLOCATION-0000000000000000',
|
|
137
|
+
expectedType: 'dt.entity.geolocation',
|
|
138
|
+
description: 'GEOLOCATION entity ID',
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
entityId: 'GCP_ZONE-0C3C7E567EAAB95B',
|
|
142
|
+
expectedType: 'dt.entity.gcp_zone',
|
|
143
|
+
description: 'GCP_ZONE entity ID',
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
entityId: 'AZURE_VM-42EA5AB8F028E280',
|
|
147
|
+
expectedType: 'dt.entity.azure_vm',
|
|
148
|
+
description: 'AZURE_VM entity ID',
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
entityId: 'RELATIONAL_DATABASE_SERVICE-B4EDC0E1E1279494',
|
|
152
|
+
expectedType: 'dt.entity.relational_database_service',
|
|
153
|
+
description: 'RELATIONAL_DATABASE_SERVICE entity ID',
|
|
154
|
+
},
|
|
155
|
+
];
|
|
156
|
+
it.each(verifiedEntityTypesTestCases)('should map $description to $expectedType', ({ entityId, expectedType }) => {
|
|
157
|
+
const result = (0, dynatrace_entity_types_1.getEntityTypeFromId)(entityId);
|
|
158
|
+
expect(result).toBe(expectedType);
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getUserAgent = void 0;
|
|
4
|
+
const package_json_1 = require("../../package.json");
|
|
5
|
+
/**
|
|
6
|
+
* Generate a user agent string for Dynatrace MCP Server
|
|
7
|
+
* @returns User agent string in format: dynatrace-mcp-server/vX.X.X (platform-arch)
|
|
8
|
+
*/
|
|
9
|
+
const getUserAgent = () => {
|
|
10
|
+
return `dynatrace-mcp-server/v${package_json_1.version} (${process.platform}-${process.arch})`;
|
|
11
|
+
};
|
|
12
|
+
exports.getUserAgent = getUserAgent;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dynatrace-oss/dynatrace-mcp-server",
|
|
3
|
-
"version": "0.5.0
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Model Context Protocol (MCP) server for Dynatrace",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"Dynatrace",
|
|
@@ -47,13 +47,13 @@
|
|
|
47
47
|
"license": "MIT",
|
|
48
48
|
"dependencies": {
|
|
49
49
|
"@dynatrace-sdk/client-automation": "^5.3.0",
|
|
50
|
-
"@dynatrace-sdk/client-classic-environment-v2": "^3.6.8",
|
|
51
50
|
"@dynatrace-sdk/client-platform-management-service": "^1.6.3",
|
|
52
51
|
"@dynatrace-sdk/client-query": "^1.18.1",
|
|
53
52
|
"@dynatrace-sdk/shared-errors": "^1.0.0",
|
|
54
53
|
"@modelcontextprotocol/sdk": "^1.8.0",
|
|
55
|
-
"
|
|
56
|
-
"
|
|
54
|
+
"commander": "^14.0.0",
|
|
55
|
+
"dotenv": "^17.2.1",
|
|
56
|
+
"dt-app": "^0.148.1",
|
|
57
57
|
"zod-to-json-schema": "^3.24.5"
|
|
58
58
|
},
|
|
59
59
|
"devDependencies": {
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.getLogsForEntity = void 0;
|
|
4
|
-
const execute_dql_1 = require("./execute-dql");
|
|
5
|
-
const getLogsForEntity = async (dtClient, entityId) => {
|
|
6
|
-
const dql = `fetch logs | filter dt.source_entity == "${entityId}"`;
|
|
7
|
-
return (0, execute_dql_1.executeDql)(dtClient, { query: dql });
|
|
8
|
-
};
|
|
9
|
-
exports.getLogsForEntity = getLogsForEntity;
|