@dynatrace-oss/dynatrace-mcp-server 0.9.0 → 0.9.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.
@@ -80,7 +80,8 @@ const createOAuthClientCredentialsHttpClient = async (environmentUrl, scopes, cl
80
80
  };
81
81
  /** Create an OAuth Client using authorization code flow (interactive authentication)
82
82
  * This starts a local HTTP server to handle the OAuth redirect and requires user interaction.
83
- * Implements token caching (via .dt-mcp/token.json) to avoid repeated OAuth flows.
83
+ * Implements an in-memory token cache (not persisted to disk). After every server restart a new
84
+ * authentication flow (or token refresh) may be required.
84
85
  * Note: Always requests a complete set of scopes for maximum token reusability. Else the user will end up having to approve multiple requests.
85
86
  */
86
87
  const createOAuthAuthCodeFlowHttpClient = async (environmentUrl, scopes, clientId) => {
@@ -1,149 +1,54 @@
1
1
  "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
2
  Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.globalTokenCache = exports.FileTokenCache = void 0;
37
- const fs = __importStar(require("fs"));
38
- const path = __importStar(require("path"));
3
+ exports.globalTokenCache = exports.InMemoryTokenCache = void 0;
39
4
  /**
40
- * File-based token cache implementation that persists tokens to disk
41
- * Stores tokens in .dt-mcp/token.json for persistence across dynatrace-mcp-server restarts
5
+ * In-memory token cache implementation (no persistence across process restarts).
6
+ * The previous implementation stored tokens on disk in `.dt-mcp/token.json` this has been
7
+ * intentionally removed to avoid writing credentials to the local filesystem. A new login /
8
+ * OAuth authorization code flow (or token retrieval) will be required after every server restart.
42
9
  */
43
- class FileTokenCache {
44
- tokenFilePath;
10
+ class InMemoryTokenCache {
45
11
  token = null;
46
- constructor() {
47
- // Create .dt-mcp directory in the current working directory
48
- const tokenDir = path.join(process.cwd(), '.dt-mcp');
49
- this.tokenFilePath = path.join(tokenDir, 'token.json');
50
- // Ensure the directory exists
51
- if (!fs.existsSync(tokenDir)) {
52
- fs.mkdirSync(tokenDir, { recursive: true });
53
- }
54
- this.loadToken();
55
- }
56
- /**
57
- * Loads the token from the file system
58
- */
59
- loadToken() {
60
- try {
61
- if (fs.existsSync(this.tokenFilePath)) {
62
- const tokenData = fs.readFileSync(this.tokenFilePath, 'utf8');
63
- this.token = JSON.parse(tokenData);
64
- console.error(`🔍 Loaded token from file: ${this.tokenFilePath}`);
65
- }
66
- else {
67
- console.error(`🔍 No token file found at: ${this.tokenFilePath}`);
68
- this.token = null;
69
- }
70
- }
71
- catch (error) {
72
- console.error(`❌ Failed to load token from file: ${error}`);
73
- this.token = null;
74
- }
75
- }
76
- /**
77
- * Saves the token to the file system
78
- */
79
- saveToken() {
80
- try {
81
- if (this.token) {
82
- fs.writeFileSync(this.tokenFilePath, JSON.stringify(this.token, null, 2), 'utf8');
83
- console.error(`✅ Saved token to file: ${this.tokenFilePath}`);
84
- }
85
- else {
86
- // Remove the file if no token exists
87
- if (fs.existsSync(this.tokenFilePath)) {
88
- fs.unlinkSync(this.tokenFilePath);
89
- console.error(`🗑️ Removed token file: ${this.tokenFilePath}`);
90
- }
91
- }
92
- }
93
- catch (error) {
94
- console.error(`❌ Failed to save token to file: ${error}`);
95
- }
96
- }
97
12
  /**
98
13
  * Retrieves the cached token (ignores scopes since we use a global token)
99
14
  */
100
15
  getToken(scopes) {
101
- // We ignore the scopes parameter since we use a single token with all scopes
16
+ // Scopes parameter ignored single global token covers all requested scopes.
102
17
  return this.token;
103
18
  }
104
19
  /**
105
20
  * Stores the global token in the cache and persists it to file
106
21
  */
107
22
  setToken(scopes, token) {
108
- // We ignore the scopes parameter since we use a single token with all scopes
109
23
  this.token = {
110
24
  access_token: token.access_token,
111
25
  refresh_token: token.refresh_token,
112
26
  expires_at: token.expires_in ? Date.now() + token.expires_in * 1000 : undefined,
113
- scopes: [...scopes], // Store the actual scopes that were granted
27
+ scopes: [...scopes],
114
28
  };
115
- this.saveToken();
116
29
  }
117
30
  /**
118
31
  * Removes the cached token and deletes the file
119
32
  */
120
33
  clearToken(scopes) {
121
- // We ignore the scopes parameter since we use a single global token
122
34
  this.token = null;
123
- this.saveToken();
124
35
  }
125
36
  /**
126
37
  * Checks if the token exists and is still valid (not expired)
127
38
  */
128
39
  isTokenValid(scopes) {
129
40
  // We ignore the scopes parameter since we use a single token with all scopes
130
- if (!this.token) {
131
- console.error(`🔍 Token validation: No token in cache`);
41
+ if (!this.token)
132
42
  return false;
133
- }
134
- // If no expiration time is set, assume token is valid
135
- if (!this.token.expires_at) {
136
- console.error(`🔍 Token validation: Token has no expiration, assuming valid`);
137
- return true;
138
- }
43
+ if (!this.token.expires_at)
44
+ return true; // treat as non-expiring
139
45
  // Add a 30-second buffer to avoid using tokens that are about to expire
140
46
  const bufferMs = 30 * 1000; // 30 seconds
141
47
  const now = Date.now();
142
48
  const expiresAt = this.token.expires_at;
143
- const isValid = now + bufferMs < expiresAt;
144
- return isValid;
49
+ return now + bufferMs < expiresAt;
145
50
  }
146
51
  }
147
- exports.FileTokenCache = FileTokenCache;
148
- // Global token cache instance - uses file-based persistence
149
- exports.globalTokenCache = new FileTokenCache();
52
+ exports.InMemoryTokenCache = InMemoryTokenCache;
53
+ // Global token cache instance - In-memory only
54
+ exports.globalTokenCache = new InMemoryTokenCache();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dynatrace-oss/dynatrace-mcp-server",
3
- "version": "0.9.0",
3
+ "version": "0.9.1",
4
4
  "mcpName": "io.github.dynatrace-oss/Dynatrace-mcp",
5
5
  "description": "Model Context Protocol (MCP) server for Dynatrace",
6
6
  "keywords": [