@mcp-consultant-tools/powerplatform-customization 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,241 @@
1
+ /**
2
+ * Rate Limiter Module
3
+ *
4
+ * Implements rate limiting with exponential backoff for PowerPlatform API calls.
5
+ * Prevents API throttling errors (429) and manages concurrent request limits.
6
+ */
7
+ /**
8
+ * Rate Limiter Class
9
+ */
10
+ export class RateLimiter {
11
+ options;
12
+ requestTimestamps = [];
13
+ activeRequests = 0;
14
+ queue = [];
15
+ processing = false;
16
+ constructor(options = {}) {
17
+ this.options = {
18
+ maxRequestsPerMinute: options.maxRequestsPerMinute || 60,
19
+ maxConcurrentRequests: options.maxConcurrentRequests || 10,
20
+ retryAttempts: options.retryAttempts || 3,
21
+ initialBackoffMs: options.initialBackoffMs || 1000,
22
+ maxBackoffMs: options.maxBackoffMs || 60000,
23
+ backoffMultiplier: options.backoffMultiplier || 2
24
+ };
25
+ }
26
+ /**
27
+ * Execute a function with rate limiting
28
+ */
29
+ async execute(fn) {
30
+ return new Promise((resolve, reject) => {
31
+ this.queue.push({
32
+ fn,
33
+ resolve,
34
+ reject,
35
+ retryCount: 0
36
+ });
37
+ this.processQueue();
38
+ });
39
+ }
40
+ /**
41
+ * Process the request queue
42
+ */
43
+ async processQueue() {
44
+ if (this.processing) {
45
+ return;
46
+ }
47
+ this.processing = true;
48
+ while (this.queue.length > 0) {
49
+ // Wait if we've hit the concurrent request limit
50
+ while (this.activeRequests >= this.options.maxConcurrentRequests) {
51
+ await this.sleep(100);
52
+ }
53
+ // Wait if we've hit the rate limit
54
+ while (!this.canMakeRequest()) {
55
+ await this.sleep(1000);
56
+ }
57
+ const item = this.queue.shift();
58
+ if (!item)
59
+ break;
60
+ // Execute the request
61
+ this.executeRequest(item);
62
+ }
63
+ this.processing = false;
64
+ }
65
+ /**
66
+ * Execute a single request with retry logic
67
+ */
68
+ async executeRequest(item) {
69
+ this.activeRequests++;
70
+ this.recordRequest();
71
+ try {
72
+ const result = await item.fn();
73
+ item.resolve(result);
74
+ }
75
+ catch (error) {
76
+ // Check if it's a rate limit error (429)
77
+ const isRateLimitError = this.isRateLimitError(error);
78
+ if (isRateLimitError && item.retryCount < this.options.retryAttempts) {
79
+ // Retry with exponential backoff
80
+ const backoffMs = this.calculateBackoff(item.retryCount);
81
+ console.error(`[RATE-LIMITER] Rate limit hit, retrying in ${backoffMs}ms (attempt ${item.retryCount + 1}/${this.options.retryAttempts})`);
82
+ await this.sleep(backoffMs);
83
+ // Re-queue the request
84
+ item.retryCount++;
85
+ this.queue.unshift(item);
86
+ }
87
+ else {
88
+ // Max retries exceeded or non-retriable error
89
+ item.reject(error);
90
+ }
91
+ }
92
+ finally {
93
+ this.activeRequests--;
94
+ // Continue processing queue
95
+ if (this.queue.length > 0) {
96
+ this.processQueue();
97
+ }
98
+ }
99
+ }
100
+ /**
101
+ * Check if we can make a request without exceeding rate limits
102
+ */
103
+ canMakeRequest() {
104
+ this.cleanupOldTimestamps();
105
+ return this.requestTimestamps.length < this.options.maxRequestsPerMinute;
106
+ }
107
+ /**
108
+ * Record a request timestamp
109
+ */
110
+ recordRequest() {
111
+ this.requestTimestamps.push(Date.now());
112
+ }
113
+ /**
114
+ * Remove timestamps older than 1 minute
115
+ */
116
+ cleanupOldTimestamps() {
117
+ const oneMinuteAgo = Date.now() - 60000;
118
+ this.requestTimestamps = this.requestTimestamps.filter(ts => ts > oneMinuteAgo);
119
+ }
120
+ /**
121
+ * Calculate backoff time for retry
122
+ */
123
+ calculateBackoff(retryCount) {
124
+ const backoff = this.options.initialBackoffMs * Math.pow(this.options.backoffMultiplier, retryCount);
125
+ return Math.min(backoff, this.options.maxBackoffMs);
126
+ }
127
+ /**
128
+ * Check if error is a rate limit error
129
+ */
130
+ isRateLimitError(error) {
131
+ // Check for 429 status code
132
+ if (error.status === 429 || error.statusCode === 429) {
133
+ return true;
134
+ }
135
+ // Check for rate limit in error message
136
+ const errorMessage = error.message?.toLowerCase() || '';
137
+ return (errorMessage.includes('rate limit') ||
138
+ errorMessage.includes('throttl') ||
139
+ errorMessage.includes('too many requests'));
140
+ }
141
+ /**
142
+ * Sleep helper
143
+ */
144
+ sleep(ms) {
145
+ return new Promise(resolve => setTimeout(resolve, ms));
146
+ }
147
+ /**
148
+ * Get current rate limiter stats
149
+ */
150
+ getStats() {
151
+ this.cleanupOldTimestamps();
152
+ return {
153
+ activeRequests: this.activeRequests,
154
+ queuedRequests: this.queue.length,
155
+ requestsLastMinute: this.requestTimestamps.length,
156
+ maxRequestsPerMinute: this.options.maxRequestsPerMinute,
157
+ maxConcurrentRequests: this.options.maxConcurrentRequests,
158
+ utilizationPercentage: (this.requestTimestamps.length / this.options.maxRequestsPerMinute) * 100
159
+ };
160
+ }
161
+ /**
162
+ * Wait until rate limiter has capacity
163
+ */
164
+ async waitForCapacity() {
165
+ while (!this.canMakeRequest() || this.activeRequests >= this.options.maxConcurrentRequests) {
166
+ await this.sleep(100);
167
+ }
168
+ }
169
+ /**
170
+ * Clear the queue
171
+ */
172
+ clearQueue() {
173
+ // Reject all queued requests
174
+ for (const item of this.queue) {
175
+ item.reject(new Error('Queue cleared'));
176
+ }
177
+ this.queue = [];
178
+ }
179
+ /**
180
+ * Reset rate limiter
181
+ */
182
+ reset() {
183
+ this.clearQueue();
184
+ this.requestTimestamps = [];
185
+ this.activeRequests = 0;
186
+ this.processing = false;
187
+ }
188
+ /**
189
+ * Update rate limiter options
190
+ */
191
+ updateOptions(options) {
192
+ this.options = {
193
+ ...this.options,
194
+ ...options
195
+ };
196
+ }
197
+ }
198
+ // Export singleton instance with default settings
199
+ export const rateLimiter = new RateLimiter({
200
+ maxRequestsPerMinute: 60,
201
+ maxConcurrentRequests: 10,
202
+ retryAttempts: 3,
203
+ initialBackoffMs: 1000,
204
+ maxBackoffMs: 60000,
205
+ backoffMultiplier: 2
206
+ });
207
+ /**
208
+ * Helper function to wrap API calls with rate limiting
209
+ */
210
+ export async function withRateLimit(fn) {
211
+ return rateLimiter.execute(fn);
212
+ }
213
+ /**
214
+ * Batch execution with rate limiting
215
+ * Processes an array of functions with rate limiting
216
+ */
217
+ export async function batchExecute(fns, options) {
218
+ const results = [];
219
+ const errors = [];
220
+ for (let i = 0; i < fns.length; i++) {
221
+ try {
222
+ const result = await rateLimiter.execute(fns[i]);
223
+ results.push(result);
224
+ if (options?.onProgress) {
225
+ options.onProgress(i + 1, fns.length);
226
+ }
227
+ }
228
+ catch (error) {
229
+ errors.push({ index: i, error });
230
+ if (options?.onError) {
231
+ options.onError(error, i);
232
+ }
233
+ // Re-throw if all requests have failed
234
+ if (errors.length === fns.length) {
235
+ throw new Error(`All batch requests failed: ${errors.length} errors`);
236
+ }
237
+ }
238
+ }
239
+ return results;
240
+ }
241
+ //# sourceMappingURL=rate-limiter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rate-limiter.js","sourceRoot":"","sources":["../../src/utils/rate-limiter.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "@mcp-consultant-tools/powerplatform-customization",
3
+ "version": "2.0.0",
4
+ "description": "MCP server for PowerPlatform schema/metadata customizations (dev/config environments)",
5
+ "type": "module",
6
+ "main": "./build/index.js",
7
+ "types": "./build/index.d.ts",
8
+ "bin": {
9
+ "mcp-powerplatform-customization": "./build/index.js"
10
+ },
11
+ "exports": {
12
+ ".": {
13
+ "import": "./build/index.js",
14
+ "types": "./build/index.d.ts"
15
+ }
16
+ },
17
+ "files": [
18
+ "build",
19
+ "README.md"
20
+ ],
21
+ "scripts": {
22
+ "build": "tsc",
23
+ "clean": "rm -rf build *.tsbuildinfo",
24
+ "prepublishOnly": "npm run build"
25
+ },
26
+ "keywords": [
27
+ "mcp",
28
+ "model-context-protocol",
29
+ "powerplatform",
30
+ "dynamics",
31
+ "dataverse",
32
+ "customization"
33
+ ],
34
+ "author": "Michal Sobieraj",
35
+ "license": "MIT",
36
+ "repository": {
37
+ "type": "git",
38
+ "url": "git+https://github.com/klemensms/mcp-consultant-tools.git",
39
+ "directory": "packages/powerplatform-customization"
40
+ },
41
+ "engines": {
42
+ "node": ">=16.0.0"
43
+ },
44
+ "dependencies": {
45
+ "@azure/msal-node": "^3.3.0",
46
+ "@mcp-consultant-tools/core": "^1.0.0",
47
+ "@modelcontextprotocol/sdk": "^1.0.4",
48
+ "axios": "^1.8.3",
49
+ "zod": "^3.24.1"
50
+ },
51
+ "devDependencies": {
52
+ "@types/node": "^22.10.5",
53
+ "typescript": "^5.8.2"
54
+ }
55
+ }