@dynatrace-oss/dynatrace-mcp-server 0.4.0 → 0.5.0-rc.1

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 CHANGED
@@ -7,10 +7,10 @@ Bring real-time observability data directly into your development workflow.
7
7
 
8
8
  ## Use cases
9
9
 
10
- - Real-time observability, fetch production-level data for early detection.
11
- - Fix issues in the context from monitored exceptions, logs, and anomalies.
12
- - More context on security level issues
13
- - Natural language to query log data
10
+ - **Real-time observability** - Fetch production-level data for early detection and proactive monitoring
11
+ - **Contextual debugging** - Fix issues with full context from monitored exceptions, logs, and anomalies
12
+ - **Security insights** - Get detailed vulnerability analysis and security problem tracking
13
+ - **Natural language queries** - Use AI-powered DQL generation and explanation
14
14
 
15
15
  ## Capabilities
16
16
 
@@ -22,9 +22,16 @@ Bring real-time observability data directly into your development workflow.
22
22
  - Get more information about a monitored entity
23
23
  - Get Ownership of an entity
24
24
 
25
- ## Quickstart
25
+ ### AI-Powered Assistance (Preview)
26
+
27
+ - **Natural Language to DQL** - Convert plain English queries to Dynatrace Query Language
28
+ - **DQL Explanation** - Get plain English explanations of complex DQL queries
29
+ - **AI Chat Assistant** - Get contextual help and guidance for Dynatrace questions
30
+ - **Feedback System** - Provide feedback to improve AI responses over time
26
31
 
27
- **Work in progress**
32
+ > **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
+
34
+ ## Quickstart
28
35
 
29
36
  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`.
30
37
 
@@ -106,45 +113,77 @@ This configuration should be stored in `<your-repo>/.amazonq/mcp.json`.
106
113
 
107
114
  ## Environment Variables
108
115
 
109
- A **Dynatrace OAuth Client** is needed to communicate with your Dynatrace Environment. Please follow the documentation about
110
- [creating an Oauth Client in Dynatrace](https://docs.dynatrace.com/docs/manage/identity-access-management/access-tokens-and-oauth-clients/oauth-clients),
111
- and set up the following environment variables in order for this MCP to work:
116
+ You can set up authentication via **OAuth Client** or **Platform Tokens** (v0.5.0 and newer) via the following environment variables:
112
117
 
113
118
  - `DT_ENVIRONMENT` (string, e.g., https://abc12345.apps.dynatrace.com) - URL to your Dynatrace Platform (do not use Dynatrace classic URLs like `abc12345.live.dynatrace.com`)
114
119
  - `OAUTH_CLIENT_ID` (string, e.g., `dt0s02.SAMPLE`) - Dynatrace OAuth Client ID
115
120
  - `OAUTH_CLIENT_SECRET` (string, e.g., `dt0s02.SAMPLE.abcd1234`) - Dynatrace OAuth Client Secret
116
- - OAuth Client Scopes:
117
- - `app-engine:apps:run` - needed for environmentInformationClient
118
- - `app-engine:functions:run` - needed for environmentInformationClient
119
- - `hub:catalog:read` - get details about installed Apps on Dynatrace Environment
120
- - `environment-api:security-problems:read` - needed for reading security problems
121
- - `environment-api:entities:read` - read monitored entities
122
- - `environment-api:problems:read` - get problems
123
- - `environment-api:metrics:read` - read metrics
124
- - `environment-api:slo:read` - read SLOs
125
- - `storage:buckets:read` - Read all system data stored on Grail
126
- - `storage:logs:read` - Read logs for reliability guardian validations
127
- - `storage:metrics:read` - Read metrics for reliability guardian validations
128
- - `storage:bizevents:read` - Read bizevents for reliability guardian validations
129
- - `storage:spans:read` - Read spans from Grail
130
- - `storage:entities:read` - Read Entities from Grail
131
- - `storage:events:read` - Read Events from Grail
132
- - `storage:system:read` - Read System Data from Grail
133
- - `storage:user.events:read` - Read User events from Grail
134
- - `storage:user.sessions:read` - Read User sessions from Grail
135
- - `settings:objects:read` - needed for reading ownership information and Guardians (SRG) from settings
136
-
137
- **Note**: Please ensure that `settings:objects:read` is used, and _not_ the similarly named scope `app-settings:objects:read`.
121
+ - With v0.5.0 and newer: `DT_PLATFORM_TOKEN` (string, e.g., `dt0s16.SAMPLE.abcd1234`) - Dynatrace Platform Token (limited support, as not all scopes are available; see below)
122
+
123
+ For more information, please have a look at the documentation about
124
+ [creating an Oauth Client in Dynatrace](https://docs.dynatrace.com/docs/manage/identity-access-management/access-tokens-and-oauth-clients/oauth-clients), as well as
125
+ [creating a Platform Token in Dynatrace](https://docs.dynatrace.com/docs/manage/identity-access-management/access-tokens-and-oauth-clients/platform-tokens).
138
126
 
139
127
  In addition, depending on the features you use, the following variables can be configured:
140
128
 
141
129
  - `SLACK_CONNECTION_ID` (string) - connection ID of a [Slack Connection](https://docs.dynatrace.com/docs/analyze-explore-automate/workflows/actions/slack)
142
130
 
131
+ ### Scopes for Authentication
132
+
133
+ Depending on the features you are using, the following scopes are needed:
134
+
135
+ - `app-engine:apps:run` - needed for almost all tools
136
+ - `app-engine:functions:run` - needed for for almost all tools
137
+ - `environment-api:security-problems:read` - needed for reading security problems (_currently not available for Platform Tokens_)
138
+ - `environment-api:entities:read` - read monitored entities (_currently not available for Platform Tokens_)
139
+ - `environment-api:problems:read` - get problems (_currently not available for Platform Tokens_)
140
+ - `environment-api:metrics:read` - read metrics (_currently not available for Platform Tokens_)
141
+ - `environment-api:slo:read` - read SLOs (_currently not available for Platform Tokens_)
142
+ - `automation:workflows:read` - read Workflows
143
+ - `automation:workflows:write` - create and update Workflows
144
+ - `automation:workflows:run` - run Workflows
145
+ - `storage:buckets:read` - needed for `execute_dql` tool to read all system data stored on Grail
146
+ - `storage:logs:read` - needed for `execute_dql` tool to read logs for reliability guardian validations
147
+ - `storage:metrics:read` - needed for `execute_dql` tool to read metrics for reliability guardian validations
148
+ - `storage:bizevents:read` - needed for `execute_dql` tool to read bizevents for reliability guardian validations
149
+ - `storage:spans:read` - needed for `execute_dql` tool to read spans from Grail
150
+ - `storage:entities:read` - needed for `execute_dql` tool to read Entities from Grail
151
+ - `storage:events:read` - needed for `execute_dql` tool to read Events from Grail
152
+ - `storage:security.events:read`- needed for `execute_dql` tool to read Security Events from Grail
153
+ - `storage:system:read` - needed for `execute_dql` tool to read System Data from Grail
154
+ - `storage:user.events:read` - needed for `execute_dql` tool to read User events from Grail
155
+ - `storage:user.sessions:read` - needed for `execute_dql` tool to read User sessions from Grail
156
+ - `davis-copilot:conversations:execute` - execute conversational skill (chat with Copilot)
157
+ - `davis-copilot:nl2dql:execute` - execute Davis Copilot Natural Language (NL) to DQL skill
158
+ - `davis-copilot:dql2nl:execute` - execute DQL to Natural Language (NL) skill
159
+ - `settings:objects:read` - needed for reading ownership information and Guardians (SRG) from settings
160
+
161
+ **Note**: Please ensure that `settings:objects:read` is used, and _not_ the similarly named scope `app-settings:objects:read`.
162
+
143
163
  ## ✨ Example prompts ✨
144
164
 
145
165
  Use these example prompts as a starting point. Just copy them into your IDE or agent setup, adapt them to your services/stack/architecture,
146
166
  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.
147
167
 
168
+ **Write a DQL query from natural language:**
169
+
170
+ ```
171
+ Show me error rates for the payment service in the last hour
172
+ ```
173
+
174
+ **Explain a DQL query:**
175
+
176
+ ```
177
+ What does this DQL do?
178
+ fetch logs | filter dt.source_entity == 'SERVICE-123' | summarize count(), by:{severity} | sort count() desc
179
+ ```
180
+
181
+ **Chat with Davis CoPilot:**
182
+
183
+ ```
184
+ How can I investigate slow database queries in Dynatrace?
185
+ ```
186
+
148
187
  **Find open vulnerabilities on production, setup alert.**
149
188
 
150
189
  ```
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.createOAuthClient = exports.ExtendedOauthClient = void 0;
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
6
  const package_json_1 = require("../../package.json");
@@ -34,29 +34,40 @@ const requestToken = async (clientId, clientSecret, ssoAuthUrl, scopes) => {
34
34
  return await res.json();
35
35
  };
36
36
  /**
37
- * ExtendedOAuthClient that takes parameters for clientId, secret, scopes, environmentUrl, authUrl, and the version of the dynatrace-mcp-server
37
+ * Create a Dynatrace Http Client (from the http-client SDK) based on the provided authentication credentails
38
+ * @param environmentUrl
39
+ * @param scopes
40
+ * @param clientId
41
+ * @param clientSecret
42
+ * @param dtPlatformToken
43
+ * @returns
38
44
  */
39
- class ExtendedOauthClient extends http_client_1._OAuthHttpClient {
40
- userAgent;
41
- constructor(config, userAgent) {
42
- super(config);
43
- this.userAgent = userAgent;
45
+ const createDtHttpClient = async (environmentUrl, scopes, clientId, clientSecret, dtPlatformToken) => {
46
+ if (clientId && clientSecret) {
47
+ // create an OAuth client if clientId and clientSecret are provided
48
+ return createOAuthHttpClient(environmentUrl, scopes, clientId, clientSecret);
44
49
  }
45
- send(options) {
46
- // add the user-agent header to the request
47
- options.headers = {
48
- ...options.headers,
49
- 'User-Agent': this.userAgent,
50
- };
51
- // call the parent send method
52
- return super.send(options);
50
+ if (dtPlatformToken) {
51
+ // create a simple HTTP client if only the platform token is provided
52
+ return createBearerTokenHttpClient(environmentUrl, dtPlatformToken);
53
53
  }
54
- }
55
- exports.ExtendedOauthClient = ExtendedOauthClient;
54
+ throw new Error('Failed to create Dynatrace HTTP Client: Please provide either clientId and clientSecret or dtPlatformToken');
55
+ };
56
+ exports.createDtHttpClient = createDtHttpClient;
57
+ /** Creates an HTTP Client based on environmentUrl and a platform token */
58
+ const createBearerTokenHttpClient = async (environmentUrl, dtPlatformToken) => {
59
+ return new http_client_1.PlatformHttpClient({
60
+ baseUrl: environmentUrl,
61
+ defaultHeaders: {
62
+ 'Authorization': `Bearer ${dtPlatformToken}`,
63
+ 'User-Agent': `dynatrace-mcp-server/v${package_json_1.version} (${process.platform}-${process.arch})`,
64
+ },
65
+ });
66
+ };
56
67
  /** Create an Oauth Client based on clientId, clientSecret, environmentUrl and scopes
57
68
  * This uses a client-credentials flow to request a token from the SSO endpoint.
58
69
  */
59
- const createOAuthClient = async (clientId, clientSecret, environmentUrl, scopes) => {
70
+ const createOAuthHttpClient = async (environmentUrl, scopes, clientId, clientSecret) => {
60
71
  if (!clientId) {
61
72
  throw new Error('Failed to retrieve OAuth client id from env "DT_APP_OAUTH_CLIENT_ID"');
62
73
  }
@@ -77,13 +88,5 @@ const createOAuthClient = async (clientId, clientSecret, environmentUrl, scopes)
77
88
  throw new Error(`Failed to retrieve OAuth token (IssueId: ${tokenResponse.issueId}): ${tokenResponse.error} - ${tokenResponse.error_description}. Note: Your OAuth client is most likely not configured correctly and/or is missing scopes.`);
78
89
  }
79
90
  console.error(`Successfully retrieved token from SSO!`);
80
- const userAgent = `dynatrace-mcp-server/v${package_json_1.version} (${process.platform}-${process.arch})`;
81
- return new ExtendedOauthClient({
82
- scopes,
83
- clientId,
84
- secret: clientSecret,
85
- environmentUrl,
86
- authUrl: ssoAuthUrl,
87
- }, userAgent);
91
+ return createBearerTokenHttpClient(environmentUrl, tokenResponse.access_token);
88
92
  };
89
- exports.createOAuthClient = createOAuthClient;
@@ -0,0 +1,189 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const dynatrace_clients_1 = require("./dynatrace-clients");
4
+ const http_client_1 = require("@dynatrace-sdk/http-client");
5
+ const dt_app_1 = require("dt-app");
6
+ // Mock external dependencies
7
+ jest.mock('@dynatrace-sdk/http-client');
8
+ jest.mock('dt-app');
9
+ jest.mock('../../package.json', () => ({
10
+ version: '1.0.0-test',
11
+ }));
12
+ // Mock fetch globally
13
+ global.fetch = jest.fn();
14
+ const mockPlatformHttpClient = http_client_1.PlatformHttpClient;
15
+ const mockGetSSOUrl = dt_app_1.getSSOUrl;
16
+ const mockFetch = global.fetch;
17
+ describe('dynatrace-clients', () => {
18
+ beforeEach(() => {
19
+ jest.clearAllMocks();
20
+ // Reset console.error mock
21
+ jest.spyOn(console, 'error').mockImplementation(() => { });
22
+ });
23
+ afterEach(() => {
24
+ jest.restoreAllMocks();
25
+ });
26
+ describe('createDtHttpClient', () => {
27
+ const environmentUrl = 'https://test123.apps.dynatrace.com';
28
+ const scopes = ['scope1', 'scope2'];
29
+ describe('with OAuth credentials', () => {
30
+ const clientId = 'test-client-id';
31
+ const clientSecret = 'test-client-secret';
32
+ const platformToken = 'test-platform-token';
33
+ beforeEach(() => {
34
+ mockGetSSOUrl.mockResolvedValue('https://sso.dynatrace.com');
35
+ });
36
+ it('should create OAuth client successfully', async () => {
37
+ const mockTokenResponse = {
38
+ access_token: 'test-access-token',
39
+ token_type: 'Bearer',
40
+ expires_in: 3600,
41
+ scope: 'scope1 scope2',
42
+ };
43
+ mockFetch.mockResolvedValueOnce({
44
+ ok: true,
45
+ json: async () => mockTokenResponse,
46
+ });
47
+ const result = await (0, dynatrace_clients_1.createDtHttpClient)(environmentUrl, scopes, clientId, clientSecret);
48
+ expect(mockGetSSOUrl).toHaveBeenCalledWith(environmentUrl);
49
+ expect(mockFetch).toHaveBeenCalledWith('https://sso.dynatrace.com/sso/oauth2/token', {
50
+ method: 'POST',
51
+ headers: {
52
+ 'Content-Type': 'application/x-www-form-urlencoded',
53
+ },
54
+ body: new URLSearchParams({
55
+ grant_type: 'client_credentials',
56
+ client_id: clientId,
57
+ client_secret: clientSecret,
58
+ scope: scopes.join(' '),
59
+ }),
60
+ });
61
+ expect(mockPlatformHttpClient).toHaveBeenCalledWith({
62
+ baseUrl: environmentUrl,
63
+ defaultHeaders: {
64
+ 'Authorization': 'Bearer test-access-token',
65
+ 'User-Agent': 'dynatrace-mcp-server/v1.0.0-test (linux-x64)',
66
+ },
67
+ });
68
+ expect(result).toBeInstanceOf(http_client_1.PlatformHttpClient);
69
+ });
70
+ it('should throw error when clientId, clientSecret and platformToken are missing', async () => {
71
+ await expect((0, dynatrace_clients_1.createDtHttpClient)(environmentUrl, scopes, undefined, undefined, undefined)).rejects.toThrow('Failed to create Dynatrace HTTP Client: Please provide either clientId and clientSecret or dtPlatformToken');
72
+ });
73
+ it('should throw error when environmentUrl is missing', async () => {
74
+ await expect((0, dynatrace_clients_1.createDtHttpClient)('', scopes, clientId, clientSecret)).rejects.toThrow('Failed to retrieve environment URL from env "DT_ENVIRONMENT"');
75
+ });
76
+ it('should throw error when token request fails with HTTP error', async () => {
77
+ mockFetch.mockResolvedValueOnce({
78
+ ok: false,
79
+ status: 401,
80
+ statusText: 'Unauthorized',
81
+ json: async () => ({
82
+ error: 'invalid_client',
83
+ error_description: 'Invalid client credentials',
84
+ }),
85
+ });
86
+ await expect((0, dynatrace_clients_1.createDtHttpClient)(environmentUrl, scopes, clientId, clientSecret)).rejects.toThrow('Failed to retrieve OAuth token');
87
+ expect(console.error).toHaveBeenCalledWith('Failed to fetch token: 401 Unauthorized');
88
+ });
89
+ it('should throw error when token response contains error', async () => {
90
+ const mockErrorResponse = {
91
+ error: 'invalid_scope',
92
+ error_description: 'The requested scope is invalid',
93
+ issueId: 'issue-123',
94
+ };
95
+ mockFetch.mockResolvedValueOnce({
96
+ ok: true,
97
+ json: async () => mockErrorResponse,
98
+ });
99
+ await expect((0, dynatrace_clients_1.createDtHttpClient)(environmentUrl, scopes, clientId, clientSecret)).rejects.toThrow('Failed to retrieve OAuth token (IssueId: issue-123): invalid_scope - The requested scope is invalid');
100
+ });
101
+ it('should throw error when token response is missing access_token', async () => {
102
+ const mockIncompleteResponse = {
103
+ token_type: 'Bearer',
104
+ expires_in: 3600,
105
+ };
106
+ mockFetch.mockResolvedValueOnce({
107
+ ok: true,
108
+ json: async () => mockIncompleteResponse,
109
+ });
110
+ await expect((0, dynatrace_clients_1.createDtHttpClient)(environmentUrl, scopes, clientId, clientSecret)).rejects.toThrow('Failed to retrieve OAuth token');
111
+ });
112
+ it('should log authentication details', async () => {
113
+ const mockTokenResponse = {
114
+ access_token: 'test-access-token',
115
+ token_type: 'Bearer',
116
+ expires_in: 3600,
117
+ };
118
+ mockFetch.mockResolvedValueOnce({
119
+ ok: true,
120
+ json: async () => mockTokenResponse,
121
+ });
122
+ await (0, dynatrace_clients_1.createDtHttpClient)(environmentUrl, scopes, clientId, clientSecret);
123
+ expect(console.error).toHaveBeenCalledWith(`Trying to authenticate API Calls to ${environmentUrl} via OAuthClientId ${clientId} with the following scopes: ${scopes.join(', ')}`);
124
+ });
125
+ });
126
+ describe('with Bearer token', () => {
127
+ const dtPlatformToken = 'test-platform-token';
128
+ it('should create Bearer token client successfully', async () => {
129
+ const result = await (0, dynatrace_clients_1.createDtHttpClient)(environmentUrl, scopes, undefined, undefined, dtPlatformToken);
130
+ expect(mockPlatformHttpClient).toHaveBeenCalledWith({
131
+ baseUrl: environmentUrl,
132
+ defaultHeaders: {
133
+ 'Authorization': `Bearer ${dtPlatformToken}`,
134
+ 'User-Agent': 'dynatrace-mcp-server/v1.0.0-test (linux-x64)',
135
+ },
136
+ });
137
+ expect(result).toBeInstanceOf(http_client_1.PlatformHttpClient);
138
+ });
139
+ });
140
+ describe('with no authentication', () => {
141
+ it('should throw error when no authentication method is provided', async () => {
142
+ await expect((0, dynatrace_clients_1.createDtHttpClient)(environmentUrl, scopes)).rejects.toThrow('Failed to create Dynatrace HTTP Client: Please provide either clientId and clientSecret or dtPlatformToken');
143
+ });
144
+ });
145
+ });
146
+ describe('requestToken function (indirectly tested)', () => {
147
+ it('should handle fetch errors gracefully', async () => {
148
+ mockGetSSOUrl.mockResolvedValue('https://sso.dynatrace.com');
149
+ // Mock fetch to throw an error
150
+ mockFetch.mockRejectedValueOnce(new Error('Network error'));
151
+ await expect((0, dynatrace_clients_1.createDtHttpClient)('https://test.apps.dynatrace.com', ['scope1'], 'client-id', 'client-secret')).rejects.toThrow('Network error');
152
+ });
153
+ it('should format request body correctly', async () => {
154
+ mockGetSSOUrl.mockResolvedValue('https://sso.dynatrace.com');
155
+ const mockTokenResponse = {
156
+ access_token: 'test-token',
157
+ };
158
+ mockFetch.mockResolvedValueOnce({
159
+ ok: true,
160
+ json: async () => mockTokenResponse,
161
+ });
162
+ await (0, dynatrace_clients_1.createDtHttpClient)('https://test.apps.dynatrace.com', ['scope1', 'scope2'], 'test-client', 'test-secret');
163
+ const expectedBody = new URLSearchParams({
164
+ grant_type: 'client_credentials',
165
+ client_id: 'test-client',
166
+ client_secret: 'test-secret',
167
+ scope: 'scope1 scope2',
168
+ });
169
+ expect(mockFetch).toHaveBeenCalledWith(expect.any(String), expect.objectContaining({
170
+ method: 'POST',
171
+ headers: {
172
+ 'Content-Type': 'application/x-www-form-urlencoded',
173
+ },
174
+ body: expectedBody,
175
+ }));
176
+ });
177
+ });
178
+ describe('User-Agent header', () => {
179
+ it('should include correct User-Agent format', async () => {
180
+ const dtPlatformToken = 'test-token';
181
+ await (0, dynatrace_clients_1.createDtHttpClient)('https://test.apps.dynatrace.com', ['scope1'], undefined, undefined, dtPlatformToken);
182
+ expect(mockPlatformHttpClient).toHaveBeenCalledWith(expect.objectContaining({
183
+ defaultHeaders: expect.objectContaining({
184
+ 'User-Agent': expect.stringMatching(/^dynatrace-mcp-server\/v\d+\.\d+\.\d+(-\w+)? \(\w+-\w+\)$/),
185
+ }),
186
+ }));
187
+ });
188
+ });
189
+ });
@@ -0,0 +1,63 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.chatWithDavisCopilot = exports.explainDqlInNaturalLanguage = exports.generateDqlFromNaturalLanguage = void 0;
4
+ // API Functions
5
+ /**
6
+ * Generate DQL from natural language
7
+ * Converts plain English descriptions into powerful Dynatrace Query Language (DQL) statements.
8
+ * DQL is the most powerful way to query any data in Dynatrace, including problem events,
9
+ * security issues, logs, metrics, spans, and custom data.
10
+ */
11
+ const generateDqlFromNaturalLanguage = async (dtClient, text) => {
12
+ const request = { text };
13
+ const response = await dtClient.send({
14
+ method: 'POST',
15
+ url: '/platform/davis/copilot/v0.2/skills/nl2dql:generate',
16
+ headers: {
17
+ 'Content-Type': 'application/json',
18
+ 'Accept': 'application/json',
19
+ },
20
+ body: JSON.stringify(request),
21
+ });
22
+ return await response.body('json');
23
+ };
24
+ exports.generateDqlFromNaturalLanguage = generateDqlFromNaturalLanguage;
25
+ /**
26
+ * Explain DQL in natural language
27
+ * Provides plain English explanations of complex DQL queries.
28
+ * Helps users understand what powerful DQL statements do, including
29
+ * queries for problem events, security issues, and performance metrics.
30
+ */
31
+ const explainDqlInNaturalLanguage = async (dtClient, dql) => {
32
+ const request = { dql };
33
+ const response = await dtClient.send({
34
+ method: 'POST',
35
+ url: '/platform/davis/copilot/v0.2/skills/dql2nl:explain',
36
+ headers: {
37
+ 'Content-Type': 'application/json',
38
+ 'Accept': 'application/json',
39
+ },
40
+ body: request, // Not sure why this does not need JSON.stringify, but it only works like this; once we have the SDK, this will be consistent
41
+ });
42
+ return await response.body('json');
43
+ };
44
+ exports.explainDqlInNaturalLanguage = explainDqlInNaturalLanguage;
45
+ const chatWithDavisCopilot = async (dtClient, text, context, annotations, state) => {
46
+ const request = {
47
+ text,
48
+ context,
49
+ annotations,
50
+ state,
51
+ };
52
+ const response = await dtClient.send({
53
+ method: 'POST',
54
+ url: '/platform/davis/copilot/v0.2/skills/conversations:message',
55
+ headers: {
56
+ 'Content-Type': 'application/json',
57
+ 'Accept': 'application/json',
58
+ },
59
+ body: JSON.stringify(request),
60
+ });
61
+ return await response.body('json');
62
+ };
63
+ exports.chatWithDavisCopilot = chatWithDavisCopilot;
@@ -6,12 +6,16 @@ exports.getDynatraceEnv = getDynatraceEnv;
6
6
  * Throws an Error if validation fails.
7
7
  */
8
8
  function getDynatraceEnv(env = process.env) {
9
- const oauthClient = env.OAUTH_CLIENT_ID;
9
+ const oauthClientId = env.OAUTH_CLIENT_ID;
10
10
  const oauthClientSecret = env.OAUTH_CLIENT_SECRET;
11
+ const dtPlatformToken = env.DT_PLATFORM_TOKEN;
11
12
  const dtEnvironment = env.DT_ENVIRONMENT;
12
13
  const slackConnectionId = env.SLACK_CONNECTION_ID || 'fake-slack-connection-id';
13
- if (!oauthClient || !oauthClientSecret || !dtEnvironment) {
14
- throw new Error('Please set OAUTH_CLIENT_ID and OAUTH_CLIENT_SECRET and DT_ENVIRONMENT environment variables');
14
+ if (!dtEnvironment) {
15
+ throw new Error('Please set DT_ENVIRONMENT environment variable to your Dynatrace Platform Environment');
16
+ }
17
+ if (!oauthClientId && !oauthClientSecret && !dtPlatformToken) {
18
+ throw new Error('Please set either OAUTH_CLIENT_ID and OAUTH_CLIENT_SECRET, or DT_PLATFORM_TOKEN environment variables');
15
19
  }
16
20
  if (!dtEnvironment.startsWith('https://')) {
17
21
  throw new Error('Please set DT_ENVIRONMENT to a valid Dynatrace Environment URL (e.g., https://<environment-id>.apps.dynatrace.com)');
@@ -19,5 +23,5 @@ function getDynatraceEnv(env = process.env) {
19
23
  if (!dtEnvironment.includes('apps.dynatrace.com') && !dtEnvironment.includes('apps.dynatracelabs.com')) {
20
24
  throw new Error('Please set DT_ENVIRONMENT to a valid Dynatrace Platform Environment URL (e.g., https://<environment-id>.apps.dynatrace.com)');
21
25
  }
22
- return { oauthClient, oauthClientSecret, dtEnvironment, slackConnectionId };
26
+ return { oauthClientId, oauthClientSecret, dtPlatformToken, dtEnvironment, slackConnectionId };
23
27
  }
@@ -6,15 +6,17 @@ describe('getDynatraceEnv', () => {
6
6
  OAUTH_CLIENT_ID: 'dt0s02.SAMPLE',
7
7
  OAUTH_CLIENT_SECRET: 'dt0s02.SAMPLE.abcd1234',
8
8
  DT_ENVIRONMENT: 'https://abc123.apps.dynatrace.com',
9
+ DT_PLATFORM_TOKEN: 'dt0s16.SAMPLE.abcd1234',
9
10
  SLACK_CONNECTION_ID: 'slack-conn-id',
10
11
  };
11
12
  it('returns all required values when environment is valid', () => {
12
13
  const env = { ...baseEnv };
13
14
  const result = (0, getDynatraceEnv_1.getDynatraceEnv)(env);
14
15
  expect(result).toEqual({
15
- oauthClient: env.OAUTH_CLIENT_ID,
16
+ oauthClientId: env.OAUTH_CLIENT_ID,
16
17
  oauthClientSecret: env.OAUTH_CLIENT_SECRET,
17
18
  dtEnvironment: env.DT_ENVIRONMENT,
19
+ dtPlatformToken: env.DT_PLATFORM_TOKEN,
18
20
  slackConnectionId: env.SLACK_CONNECTION_ID,
19
21
  });
20
22
  });
@@ -23,14 +25,15 @@ describe('getDynatraceEnv', () => {
23
25
  const result = (0, getDynatraceEnv_1.getDynatraceEnv)(env);
24
26
  expect(result.slackConnectionId).toBe('fake-slack-connection-id');
25
27
  });
26
- it('throws if OAUTH_CLIENT_ID is missing', () => {
27
- const env = { ...baseEnv, OAUTH_CLIENT_ID: undefined };
28
+ it('throws if environment variables for auth credentials are missing', () => {
29
+ const env = {
30
+ ...baseEnv,
31
+ OAUTH_CLIENT_ID: undefined,
32
+ OAUTH_CLIENT_SECRET: undefined,
33
+ DT_PLATFORM_TOKEN: undefined,
34
+ };
28
35
  expect(() => (0, getDynatraceEnv_1.getDynatraceEnv)(env)).toThrow(/OAUTH_CLIENT_ID/);
29
36
  });
30
- it('throws if OAUTH_CLIENT_SECRET is missing', () => {
31
- const env = { ...baseEnv, OAUTH_CLIENT_SECRET: undefined };
32
- expect(() => (0, getDynatraceEnv_1.getDynatraceEnv)(env)).toThrow(/OAUTH_CLIENT_SECRET/);
33
- });
34
37
  it('throws if DT_ENVIRONMENT is missing', () => {
35
38
  const env = { ...baseEnv, DT_ENVIRONMENT: undefined };
36
39
  expect(() => (0, getDynatraceEnv_1.getDynatraceEnv)(env)).toThrow(/DT_ENVIRONMENT/);
package/dist/index.js CHANGED
@@ -22,6 +22,7 @@ const get_vulnerability_details_1 = require("./capabilities/get-vulnerability-de
22
22
  const execute_dql_1 = require("./capabilities/execute-dql");
23
23
  const send_slack_message_1 = require("./capabilities/send-slack-message");
24
24
  const find_monitored_entity_by_name_1 = require("./capabilities/find-monitored-entity-by-name");
25
+ const davis_copilot_1 = require("./capabilities/davis-copilot");
25
26
  const getDynatraceEnv_1 = require("./getDynatraceEnv");
26
27
  (0, dotenv_1.config)();
27
28
  let scopesBase = [
@@ -38,7 +39,8 @@ const main = async () => {
38
39
  console.error(err.message);
39
40
  process.exit(1);
40
41
  }
41
- const { oauthClient, oauthClientSecret, dtEnvironment, slackConnectionId } = dynatraceEnv;
42
+ console.error(`Initializing Dynatrace MCP Server v${package_json_1.version}...`);
43
+ const { oauthClientId, oauthClientSecret, dtEnvironment, dtPlatformToken, slackConnectionId } = dynatraceEnv;
42
44
  console.error(`Starting Dynatrace MCP Server v${package_json_1.version}...`);
43
45
  const server = new mcp_js_1.McpServer({
44
46
  name: 'Dynatrace MCP Server',
@@ -88,7 +90,7 @@ const main = async () => {
88
90
  };
89
91
  tool('get_environment_info', 'Get information about the connected Dynatrace Environment (Tenant)', {}, async ({}) => {
90
92
  // create an oauth-client
91
- const dtClient = await (0, dynatrace_clients_1.createOAuthClient)(oauthClient, oauthClientSecret, dtEnvironment, scopesBase);
93
+ const dtClient = await (0, dynatrace_clients_1.createDtHttpClient)(dtEnvironment, scopesBase, oauthClientId, oauthClientSecret, dtPlatformToken);
92
94
  const environmentInformationClient = new client_platform_management_service_1.EnvironmentInformationClient(dtClient);
93
95
  const environmentInfo = await environmentInformationClient.getEnvironmentInformation();
94
96
  let resp = `Environment Information (also referred to as tenant):
@@ -97,7 +99,7 @@ const main = async () => {
97
99
  return resp;
98
100
  });
99
101
  tool('list_vulnerabilities', 'List all vulnerabilities from Dynatrace', {}, async ({}) => {
100
- const dtClient = await (0, dynatrace_clients_1.createOAuthClient)(oauthClient, oauthClientSecret, dtEnvironment, scopesBase.concat('environment-api:security-problems:read'));
102
+ const dtClient = await (0, dynatrace_clients_1.createDtHttpClient)(dtEnvironment, scopesBase.concat('environment-api:security-problems:read'), oauthClientId, oauthClientSecret, dtPlatformToken);
101
103
  const result = await (0, list_vulnerabilities_1.listVulnerabilities)(dtClient);
102
104
  if (!result || result.length === 0) {
103
105
  return 'No vulnerabilities found';
@@ -112,7 +114,7 @@ const main = async () => {
112
114
  tool('get_vulnerabilty_details', 'Get details of a vulnerability by `securityProblemId` on Dynatrace', {
113
115
  securityProblemId: zod_1.z.string().optional(),
114
116
  }, async ({ securityProblemId }) => {
115
- const dtClient = await (0, dynatrace_clients_1.createOAuthClient)(oauthClient, oauthClientSecret, dtEnvironment, scopesBase.concat('environment-api:security-problems:read'));
117
+ const dtClient = await (0, dynatrace_clients_1.createDtHttpClient)(dtEnvironment, scopesBase.concat('environment-api:security-problems:read'), oauthClientId, oauthClientSecret, dtPlatformToken);
116
118
  const result = await (0, get_vulnerability_details_1.getVulnerabilityDetails)(dtClient, securityProblemId);
117
119
  let resp = `The Security Problem (Vulnerability) ${result.displayId} with securityProblemId ${result.securityProblemId} has the title ${result.title}.\n`;
118
120
  resp += `The related CVEs are ${result.cveIds?.join(',') || 'unknown'}.\n`;
@@ -158,7 +160,7 @@ const main = async () => {
158
160
  return resp;
159
161
  });
160
162
  tool('list_problems', 'List all problems known on Dynatrace', {}, async ({}) => {
161
- const dtClient = await (0, dynatrace_clients_1.createOAuthClient)(oauthClient, oauthClientSecret, dtEnvironment, scopesBase.concat('environment-api:problems:read'));
163
+ const dtClient = await (0, dynatrace_clients_1.createDtHttpClient)(dtEnvironment, scopesBase.concat('environment-api:problems:read'), oauthClientId, oauthClientSecret, dtPlatformToken);
162
164
  const result = await (0, list_problems_1.listProblems)(dtClient);
163
165
  if (!result || result.length === 0) {
164
166
  return 'No problems found';
@@ -168,7 +170,7 @@ const main = async () => {
168
170
  tool('get_problem_details', 'Get details of a problem on Dynatrace', {
169
171
  problemId: zod_1.z.string().optional(),
170
172
  }, async ({ problemId }) => {
171
- const dtClient = await (0, dynatrace_clients_1.createOAuthClient)(oauthClient, oauthClientSecret, dtEnvironment, scopesBase.concat('environment-api:problems:read'));
173
+ const dtClient = await (0, dynatrace_clients_1.createDtHttpClient)(dtEnvironment, scopesBase.concat('environment-api:problems:read'), oauthClientId, oauthClientSecret, dtPlatformToken);
172
174
  const result = await (0, get_problem_details_1.getProblemDetails)(dtClient, problemId);
173
175
  let resp = `The problem ${result.displayId} with the title ${result.title} (ID: ${result.problemId}).` +
174
176
  `The severity is ${result.severityLevel}, and it affects ${result.affectedEntities.length} entities:`;
@@ -192,14 +194,14 @@ const main = async () => {
192
194
  tool('find_entity_by_name', 'Get the entityId of a monitored entity based on the name of the entity on Dynatrace', {
193
195
  entityName: zod_1.z.string(),
194
196
  }, async ({ entityName }) => {
195
- const dtClient = await (0, dynatrace_clients_1.createOAuthClient)(oauthClient, oauthClientSecret, dtEnvironment, scopesBase.concat('environment-api:entities:read', 'storage:entities:read'));
197
+ const dtClient = await (0, dynatrace_clients_1.createDtHttpClient)(dtEnvironment, scopesBase.concat('environment-api:entities:read', 'storage:entities:read'), oauthClientId, oauthClientSecret, dtPlatformToken);
196
198
  const entityResponse = await (0, find_monitored_entity_by_name_1.findMonitoredEntityByName)(dtClient, entityName);
197
199
  return entityResponse;
198
200
  });
199
201
  tool('get_entity_details', 'Get details of a monitored entity based on the entityId on Dynatrace', {
200
202
  entityId: zod_1.z.string().optional(),
201
203
  }, async ({ entityId }) => {
202
- const dtClient = await (0, dynatrace_clients_1.createOAuthClient)(oauthClient, oauthClientSecret, dtEnvironment, scopesBase.concat('environment-api:entities:read'));
204
+ const dtClient = await (0, dynatrace_clients_1.createDtHttpClient)(dtEnvironment, scopesBase.concat('environment-api:entities:read'), oauthClientId, oauthClientSecret, dtPlatformToken);
203
205
  const entityDetails = await (0, get_monitored_entity_details_1.getMonitoredEntityDetails)(dtClient, entityId);
204
206
  let resp = `Entity ${entityDetails.displayName} of type ${entityDetails.type} with \`entityId\` ${entityDetails.entityId}\n` +
205
207
  `Properties: ${JSON.stringify(entityDetails.properties)}\n`;
@@ -221,21 +223,21 @@ const main = async () => {
221
223
  channel: zod_1.z.string().optional(),
222
224
  message: zod_1.z.string().optional(),
223
225
  }, async ({ channel, message }) => {
224
- const dtClient = await (0, dynatrace_clients_1.createOAuthClient)(oauthClient, oauthClientSecret, dtEnvironment, scopesBase.concat('app-settings:objects:read'));
226
+ const dtClient = await (0, dynatrace_clients_1.createDtHttpClient)(dtEnvironment, scopesBase.concat('app-settings:objects:read'), oauthClientId, oauthClientSecret, dtPlatformToken);
225
227
  const response = await (0, send_slack_message_1.sendSlackMessage)(dtClient, slackConnectionId, channel, message);
226
228
  return `Message sent to Slack channel: ${JSON.stringify(response)}`;
227
229
  });
228
230
  tool('get_logs_for_entity', 'Get Logs for a monitored entity based on name of the entity on Dynatrace', {
229
231
  entityName: zod_1.z.string().optional(),
230
232
  }, async ({ entityName }) => {
231
- const dtClient = await (0, dynatrace_clients_1.createOAuthClient)(oauthClient, oauthClientSecret, dtEnvironment, scopesBase.concat('storage:logs:read'));
233
+ const dtClient = await (0, dynatrace_clients_1.createDtHttpClient)(dtEnvironment, scopesBase.concat('storage:logs:read'), oauthClientId, oauthClientSecret, dtPlatformToken);
232
234
  const logs = await (0, get_logs_for_entity_1.getLogsForEntity)(dtClient, entityName);
233
235
  return `Logs:\n${JSON.stringify(logs?.map((logLine) => (logLine ? logLine.content : 'Empty log')))}`;
234
236
  });
235
237
  tool('verify_dql', 'Verify a Dynatrace Query Language (DQL) statement on Dynatrace GRAIL before executing it. This is useful to ensure that the DQL statement is valid and can be executed without errors.', {
236
238
  dqlStatement: zod_1.z.string(),
237
239
  }, async ({ dqlStatement }) => {
238
- const dtClient = await (0, dynatrace_clients_1.createOAuthClient)(oauthClient, oauthClientSecret, dtEnvironment, scopesBase);
240
+ const dtClient = await (0, dynatrace_clients_1.createDtHttpClient)(dtEnvironment, scopesBase, oauthClientId, oauthClientSecret, dtPlatformToken);
239
241
  const response = await (0, execute_dql_1.verifyDqlStatement)(dtClient, dqlStatement);
240
242
  let resp = 'DQL Statement Verification:\n';
241
243
  if (response.notifications && response.notifications.length > 0) {
@@ -255,7 +257,7 @@ const main = async () => {
255
257
  tool('execute_dql', 'Get Logs, Metrics, Spans or Events from Dynatrace GRAIL by executing a Dynatrace Query Language (DQL) statement. Always use "verify_dql" tool before you execute a DQL statement. A valid statement looks like this: "fetch [logs, metrics, spans, events] | filter <some-filter> | summarize count(), by:{some-fields}. Adapt filters for certain attributes: `traceId` could be `trace_id` or `trace.id`.', {
256
258
  dqlStatement: zod_1.z.string(),
257
259
  }, async ({ dqlStatement }) => {
258
- const dtClient = await (0, dynatrace_clients_1.createOAuthClient)(oauthClient, oauthClientSecret, dtEnvironment, scopesBase.concat('storage:buckets:read', // Read all system data stored on Grail
260
+ const dtClient = await (0, dynatrace_clients_1.createDtHttpClient)(dtEnvironment, scopesBase.concat('storage:buckets:read', // Read all system data stored on Grail
259
261
  'storage:logs:read', // Read logs for reliability guardian validations
260
262
  'storage:metrics:read', // Read metrics for reliability guardian validations
261
263
  'storage:bizevents:read', // Read bizevents for reliability guardian validations
@@ -265,17 +267,102 @@ const main = async () => {
265
267
  'storage:system:read', // Read System Data from Grail
266
268
  'storage:user.events:read', // Read User events from Grail
267
269
  'storage:user.sessions:read', // Read User sessions from Grail
268
- 'storage:security.events:read'));
270
+ 'storage:security.events:read'), oauthClientId, oauthClientSecret, dtPlatformToken);
269
271
  const response = await (0, execute_dql_1.executeDql)(dtClient, dqlStatement);
270
272
  return `DQL Response: ${JSON.stringify(response)}`;
271
273
  });
274
+ tool('generate_dql_from_natural_language', "Convert natural language queries to Dynatrace Query Language (DQL) using Davis CoPilot AI. You can ask for problem events, security issues, logs, metrics, spans, and custom data. Workflow: 1) Generate DQL, 2) Verify with verify_dql tool, 3) Execute with execute_dql tool, 4) Iterate if results don't match expectations.", {
275
+ text: zod_1.z
276
+ .string()
277
+ .describe('Natural language description of what you want to query. Be specific and include time ranges, entities, and metrics of interest.'),
278
+ }, async ({ text }) => {
279
+ const dtClient = await (0, dynatrace_clients_1.createDtHttpClient)(dtEnvironment, scopesBase.concat('davis-copilot:nl2dql:execute'), oauthClientId, oauthClientSecret, dtPlatformToken);
280
+ const response = await (0, davis_copilot_1.generateDqlFromNaturalLanguage)(dtClient, text);
281
+ let resp = `🔤 Natural Language to DQL:\n\n`;
282
+ resp += `**Query:** "${text}"\n\n`;
283
+ resp += `**Generated DQL:**\n\`\`\`\n${response.dql}\n\`\`\`\n\n`;
284
+ resp += `**Status:** ${response.status}\n`;
285
+ resp += `**Message Token:** ${response.messageToken}\n`;
286
+ if (response.metadata?.notifications && response.metadata.notifications.length > 0) {
287
+ resp += `\n**Notifications:**\n`;
288
+ response.metadata.notifications.forEach((notification) => {
289
+ resp += `- ${notification.severity}: ${notification.message}\n`;
290
+ });
291
+ }
292
+ resp += `\n💡 **Next Steps:**\n`;
293
+ resp += `1. Use "verify_dql" tool to validate this query\n`;
294
+ resp += `2. Use "execute_dql" tool to run the query\n`;
295
+ resp += `3. If results don't match expectations, refine your natural language description and try again\n`;
296
+ return resp;
297
+ });
298
+ tool('explain_dql_in_natural_language', 'Explain Dynatrace Query Language (DQL) statements in natural language using Davis CoPilot AI.', {
299
+ dql: zod_1.z.string().describe('The DQL statement to explain'),
300
+ }, async ({ dql }) => {
301
+ const dtClient = await (0, dynatrace_clients_1.createDtHttpClient)(dtEnvironment, scopesBase.concat('davis-copilot:dql2nl:execute'), oauthClientId, oauthClientSecret, dtPlatformToken);
302
+ const response = await (0, davis_copilot_1.explainDqlInNaturalLanguage)(dtClient, dql);
303
+ let resp = `📝 DQL to Natural Language:\n\n`;
304
+ resp += `**DQL Query:**\n\`\`\`\n${dql}\n\`\`\`\n\n`;
305
+ resp += `**Summary:** ${response.summary}\n\n`;
306
+ resp += `**Detailed Explanation:**\n${response.explanation}\n\n`;
307
+ resp += `**Status:** ${response.status}\n`;
308
+ resp += `**Message Token:** ${response.messageToken}\n`;
309
+ if (response.metadata?.notifications && response.metadata.notifications.length > 0) {
310
+ resp += `\n**Notifications:**\n`;
311
+ response.metadata.notifications.forEach((notification) => {
312
+ resp += `- ${notification.severity}: ${notification.message}\n`;
313
+ });
314
+ }
315
+ return resp;
316
+ });
317
+ tool('chat_with_davis_copilot', 'Use this tool in case no specific tool is available. Get an answer to any Dynatrace related question as well as troubleshooting, and guidance. *(Note: Davis CoPilot AI is GA, but the Davis CoPilot APIs are in preview)*', {
318
+ text: zod_1.z.string().describe('Your question or request for Davis CoPilot'),
319
+ context: zod_1.z.string().optional().describe('Optional context to provide additional information'),
320
+ instruction: zod_1.z.string().optional().describe('Optional instruction for how to format the response'),
321
+ }, async ({ text, context, instruction }) => {
322
+ const dtClient = await (0, dynatrace_clients_1.createDtHttpClient)(dtEnvironment, scopesBase.concat('davis-copilot:conversations:execute'), oauthClientId, oauthClientSecret, dtPlatformToken);
323
+ const conversationContext = [];
324
+ if (context) {
325
+ conversationContext.push({
326
+ type: 'supplementary',
327
+ value: context,
328
+ });
329
+ }
330
+ if (instruction) {
331
+ conversationContext.push({
332
+ type: 'instruction',
333
+ value: instruction,
334
+ });
335
+ }
336
+ const response = await (0, davis_copilot_1.chatWithDavisCopilot)(dtClient, text, conversationContext);
337
+ let resp = `🤖 Davis CoPilot Response:\n\n`;
338
+ resp += `**Your Question:** "${text}"\n\n`;
339
+ resp += `**Answer:**\n${response.text}\n\n`;
340
+ resp += `**Status:** ${response.status}\n`;
341
+ resp += `**Message Token:** ${response.messageToken}\n`;
342
+ if (response.metadata?.sources && response.metadata.sources.length > 0) {
343
+ resp += `\n**Sources:**\n`;
344
+ response.metadata.sources.forEach((source) => {
345
+ resp += `- ${source.title || 'Untitled'}: ${source.url || 'No URL'}\n`;
346
+ });
347
+ }
348
+ if (response.metadata?.notifications && response.metadata.notifications.length > 0) {
349
+ resp += `\n**Notifications:**\n`;
350
+ response.metadata.notifications.forEach((notification) => {
351
+ resp += `- ${notification.severity}: ${notification.message}\n`;
352
+ });
353
+ }
354
+ if (response.state?.conversationId) {
355
+ resp += `\n**Conversation ID:** ${response.state.conversationId}`;
356
+ }
357
+ return resp;
358
+ });
272
359
  tool('create_workflow_for_notification', 'Create a notification for a team based on a problem type within Workflows in Dynatrace', {
273
360
  problemType: zod_1.z.string().optional(),
274
361
  teamName: zod_1.z.string().optional(),
275
362
  channel: zod_1.z.string().optional(),
276
363
  isPrivate: zod_1.z.boolean().optional().default(false),
277
364
  }, async ({ problemType, teamName, channel, isPrivate }) => {
278
- const dtClient = await (0, dynatrace_clients_1.createOAuthClient)(oauthClient, oauthClientSecret, dtEnvironment, scopesBase.concat('automation:workflows:write', 'automation:workflows:read', 'automation:workflows:run'));
365
+ const dtClient = await (0, dynatrace_clients_1.createDtHttpClient)(dtEnvironment, scopesBase.concat('automation:workflows:write', 'automation:workflows:read', 'automation:workflows:run'), oauthClientId, oauthClientSecret, dtPlatformToken);
279
366
  const response = await (0, create_workflow_for_problem_notification_1.createWorkflowForProblemNotification)(dtClient, teamName, channel, problemType, isPrivate);
280
367
  let resp = `Workflow Created: ${response?.id} with name ${response?.title}.\nYou can access the Workflow via the following link: ${dtEnvironment}/ui/apps/dynatrace.automations/workflows/${response?.id}.\nTell the user to inspect the Workflow by visiting the link.\n`;
281
368
  if (response.type == 'SIMPLE') {
@@ -292,7 +379,7 @@ const main = async () => {
292
379
  tool('make_workflow_public', 'Modify a workflow and make it publicly available to everyone on the Dynatrace Environment', {
293
380
  workflowId: zod_1.z.string().optional(),
294
381
  }, async ({ workflowId }) => {
295
- const dtClient = await (0, dynatrace_clients_1.createOAuthClient)(oauthClient, oauthClientSecret, dtEnvironment, scopesBase.concat('automation:workflows:write', 'automation:workflows:read', 'automation:workflows:run'));
382
+ const dtClient = await (0, dynatrace_clients_1.createDtHttpClient)(dtEnvironment, scopesBase.concat('automation:workflows:write', 'automation:workflows:read', 'automation:workflows:run'), oauthClientId, oauthClientSecret, dtPlatformToken);
296
383
  const response = await (0, update_workflow_1.updateWorkflow)(dtClient, workflowId, {
297
384
  isPrivate: false,
298
385
  });
@@ -304,14 +391,14 @@ const main = async () => {
304
391
  .optional()
305
392
  .describe(`The Kubernetes (K8s) Cluster Id, referred to as k8s.cluster.uid (this is NOT the Dynatrace environment)`),
306
393
  }, async ({ clusterId }) => {
307
- const dtClient = await (0, dynatrace_clients_1.createOAuthClient)(oauthClient, oauthClientSecret, dtEnvironment, scopesBase.concat('storage:events:read'));
394
+ const dtClient = await (0, dynatrace_clients_1.createDtHttpClient)(dtEnvironment, scopesBase.concat('storage:events:read'), oauthClientId, oauthClientSecret, dtPlatformToken);
308
395
  const events = await (0, get_events_for_cluster_1.getEventsForCluster)(dtClient, clusterId);
309
396
  return `Kubernetes Events:\n${JSON.stringify(events)}`;
310
397
  });
311
398
  tool('get_ownership', 'Get detailed Ownership information for one or multiple entities on Dynatrace', {
312
399
  entityIds: zod_1.z.string().optional().describe('Comma separated list of entityIds'),
313
400
  }, async ({ entityIds }) => {
314
- const dtClient = await (0, dynatrace_clients_1.createOAuthClient)(oauthClient, oauthClientSecret, dtEnvironment, scopesBase.concat('environment-api:entities:read', 'settings:objects:read'));
401
+ const dtClient = await (0, dynatrace_clients_1.createDtHttpClient)(dtEnvironment, scopesBase.concat('environment-api:entities:read', 'settings:objects:read'), oauthClientId, oauthClientSecret, dtPlatformToken);
315
402
  console.error(`Fetching ownership for ${entityIds}`);
316
403
  const ownershipInformation = await (0, get_ownership_information_1.getOwnershipInformation)(dtClient, entityIds);
317
404
  console.error(`Done!`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dynatrace-oss/dynatrace-mcp-server",
3
- "version": "0.4.0",
3
+ "version": "0.5.0-rc.1+build1",
4
4
  "description": "Model Context Protocol (MCP) server for Dynatrace",
5
5
  "keywords": [
6
6
  "Dynatrace",
@@ -38,6 +38,8 @@
38
38
  "prepare": "npm run build",
39
39
  "watch": "tsc --watch",
40
40
  "test": "jest",
41
+ "test:unit": "jest --selectProjects unit",
42
+ "test:integration": "jest --selectProjects integration --runInBand",
41
43
  "prettier": "prettier --check .",
42
44
  "prettier:fix": "prettier --write ."
43
45
  },