@automattic/mcp-wordpress-remote 0.2.0 → 0.2.2

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.
Files changed (3) hide show
  1. package/README.md +425 -62
  2. package/dist/proxy.js +2024 -94
  3. package/package.json +55 -5
package/dist/proxy.js CHANGED
@@ -4,94 +4,2011 @@
4
4
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
5
5
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
6
6
 
7
+ // src/lib/wordpress-api.ts
8
+ import { EventEmitter as EventEmitter3 } from "node:events";
9
+
7
10
  // src/lib/utils.ts
8
11
  import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
12
+
13
+ // src/lib/config.ts
14
+ import * as os from "os";
15
+ import * as path from "path";
16
+ var CONFIG = {
17
+ // API Configuration
18
+ WP_API_URL: process.env.WP_API_URL || "https://example.com",
19
+ // OAuth Configuration (MCP Authorization specification 2025-06-18 compliant)
20
+ OAUTH_ENABLED: process.env.OAUTH_ENABLED === "true",
21
+ // Disabled by default, enable with 'true'
22
+ OAUTH_CALLBACK_PORT: parseInt(process.env.OAUTH_CALLBACK_PORT || "3000"),
23
+ OAUTH_HOST: process.env.OAUTH_HOST || "127.0.0.1",
24
+ WP_OAUTH_CLIENT_ID: process.env.WP_OAUTH_CLIENT_ID || "",
25
+ // No default - site-specific
26
+ // MCP OAuth 2.1 specific settings
27
+ OAUTH_FLOW_TYPE: process.env.OAUTH_FLOW_TYPE || "authorization_code",
28
+ OAUTH_USE_PKCE: process.env.OAUTH_USE_PKCE !== "false",
29
+ // PKCE required for OAuth 2.1
30
+ OAUTH_DYNAMIC_REGISTRATION: process.env.OAUTH_DYNAMIC_REGISTRATION !== "false",
31
+ // Dynamic client registration
32
+ // Resource Indicators (RFC 8707)
33
+ OAUTH_RESOURCE_INDICATOR: process.env.OAUTH_RESOURCE_INDICATOR !== "false",
34
+ // Resource parameter support
35
+ // Timeout Configuration (in milliseconds)
36
+ OAUTH_TIMEOUT: 3e4,
37
+ // 30 seconds
38
+ LOCK_TIMEOUT: 3e5,
39
+ // 5 minutes
40
+ // Directory Configuration
41
+ WP_MCP_CONFIG_DIR: process.env.WP_MCP_CONFIG_DIR || path.join(os.homedir(), ".mcp-auth"),
42
+ // Logging Configuration
43
+ LOG_FILE: process.env.LOG_FILE || null,
44
+ // Authentication Configuration (legacy support)
45
+ WP_API_USERNAME: process.env.WP_API_USERNAME,
46
+ WP_API_PASSWORD: process.env.WP_API_PASSWORD,
47
+ JWT_TOKEN: process.env.JWT_TOKEN,
48
+ WOO_CUSTOMER_KEY: process.env.WOO_CUSTOMER_KEY,
49
+ WOO_CUSTOMER_SECRET: process.env.WOO_CUSTOMER_SECRET,
50
+ // Environment
51
+ NODE_ENV: process.env.NODE_ENV || "development"
52
+ };
53
+ function isWordPressComSite(url) {
54
+ try {
55
+ const siteUrl = new URL(url);
56
+ const hostname2 = siteUrl.hostname.toLowerCase();
57
+ return hostname2.endsWith(".wordpress.com") || hostname2 === "wordpress.com" || // Jetpack sites often use WordPress.com OAuth
58
+ hostname2.endsWith(".wpcomstaging.com") || hostname2.endsWith(".wpcomstaging.net");
59
+ } catch {
60
+ return false;
61
+ }
62
+ }
63
+ function getRecommendedOAuthConfig(siteUrl) {
64
+ if (isWordPressComSite(siteUrl)) {
65
+ return {
66
+ flowType: "implicit",
67
+ usePKCE: false,
68
+ useResourceIndicator: false,
69
+ authorizationEndpoint: "https://public-api.wordpress.com/oauth2/authorize",
70
+ tokenEndpoint: "https://public-api.wordpress.com/oauth2/token",
71
+ description: "WordPress.com OAuth2 (compatible mode)"
72
+ };
73
+ } else {
74
+ return {
75
+ flowType: "authorization_code",
76
+ usePKCE: true,
77
+ useResourceIndicator: true,
78
+ authorizationEndpoint: void 0,
79
+ // Will be discovered
80
+ tokenEndpoint: void 0,
81
+ // Will be discovered
82
+ description: "MCP-compliant OAuth 2.1"
83
+ };
84
+ }
85
+ }
86
+ function validateConfig() {
87
+ const errors = [];
88
+ const hasJWT = !!CONFIG.JWT_TOKEN;
89
+ const hasBasicAuth = !!(CONFIG.WP_API_USERNAME && CONFIG.WP_API_PASSWORD);
90
+ const hasOAuth = CONFIG.OAUTH_ENABLED;
91
+ if (!hasJWT && !hasBasicAuth && !hasOAuth) {
92
+ errors.push(
93
+ "No authentication method configured. Please set one of: JWT_TOKEN, WP_API_USERNAME+WP_API_PASSWORD, or enable OAuth"
94
+ );
95
+ }
96
+ if (!CONFIG.WP_API_URL || CONFIG.WP_API_URL === "https://example.com") {
97
+ errors.push("WP_API_URL must be set to your WordPress site URL");
98
+ }
99
+ if (CONFIG.OAUTH_ENABLED) {
100
+ if (isNaN(CONFIG.OAUTH_CALLBACK_PORT) || CONFIG.OAUTH_CALLBACK_PORT < 1 || CONFIG.OAUTH_CALLBACK_PORT > 65535) {
101
+ errors.push("OAUTH_CALLBACK_PORT must be a valid port number (1-65535)");
102
+ }
103
+ if (CONFIG.OAUTH_FLOW_TYPE === "implicit") {
104
+ errors.push(
105
+ "OAUTH_FLOW_TYPE=implicit is deprecated. MCP Authorization specification 2025-06-18 requires OAuth 2.1 with authorization_code flow"
106
+ );
107
+ }
108
+ if (CONFIG.OAUTH_FLOW_TYPE === "authorization_code" && !CONFIG.OAUTH_USE_PKCE) {
109
+ errors.push(
110
+ "PKCE is required for OAuth 2.1 authorization_code flow (MCP Authorization specification 2025-06-18)"
111
+ );
112
+ }
113
+ }
114
+ return {
115
+ isValid: errors.length === 0,
116
+ errors
117
+ };
118
+ }
119
+
120
+ // src/lib/utils.ts
9
121
  import * as fs from "node:fs";
10
- import * as path from "node:path";
11
- var LOG_FILE = process.env.LOG_FILE || null;
12
- if (LOG_FILE) {
13
- const logDir = path.dirname(LOG_FILE);
122
+ import * as path2 from "node:path";
123
+ var LogLevel = /* @__PURE__ */ ((LogLevel4) => {
124
+ LogLevel4[LogLevel4["ERROR"] = 0] = "ERROR";
125
+ LogLevel4[LogLevel4["WARN"] = 1] = "WARN";
126
+ LogLevel4[LogLevel4["INFO"] = 2] = "INFO";
127
+ LogLevel4[LogLevel4["DEBUG"] = 3] = "DEBUG";
128
+ return LogLevel4;
129
+ })(LogLevel || {});
130
+ var CURRENT_LOG_LEVEL = process.env.LOG_LEVEL ? parseInt(process.env.LOG_LEVEL) : CONFIG.NODE_ENV === "development" ? 3 /* DEBUG */ : 2 /* INFO */;
131
+ if (CONFIG.LOG_FILE) {
132
+ const logDir = path2.dirname(CONFIG.LOG_FILE);
14
133
  if (!fs.existsSync(logDir)) {
15
134
  fs.mkdirSync(logDir, { recursive: true });
16
135
  }
17
136
  }
18
- function log(message, ...args) {
137
+ function log(message, level = 2 /* INFO */, category = "GENERAL", ...args) {
138
+ if (level > CURRENT_LOG_LEVEL) {
139
+ return;
140
+ }
19
141
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
20
- const formattedArgs = args.length > 0 ? args.map((arg) => typeof arg === "object" ? JSON.stringify(arg) : arg).join(" ") : "";
21
- const logMessage = `${timestamp}: ${message}${formattedArgs ? " " + formattedArgs : ""}
142
+ const levelName = LogLevel[level];
143
+ const formattedArgs = args.length > 0 ? args.map((arg) => typeof arg === "object" ? JSON.stringify(arg, null, 2) : arg).join(" ") : "";
144
+ const logMessage = `${timestamp} [${levelName}] [${category}] ${message}${formattedArgs ? "\n" + formattedArgs : ""}
22
145
  `;
23
- console.error(logMessage.trim());
24
- if (LOG_FILE) {
25
- fs.appendFileSync(LOG_FILE, logMessage);
146
+ process.stderr.write(logMessage);
147
+ if (CONFIG.LOG_FILE) {
148
+ fs.appendFileSync(CONFIG.LOG_FILE, logMessage);
26
149
  }
27
150
  }
151
+ var logger = {
152
+ error: (message, category = "ERROR", ...args) => log(message, 0 /* ERROR */, category, ...args),
153
+ warn: (message, category = "WARN", ...args) => log(message, 1 /* WARN */, category, ...args),
154
+ info: (message, category = "INFO", ...args) => log(message, 2 /* INFO */, category, ...args),
155
+ debug: (message, category = "DEBUG", ...args) => log(message, 3 /* DEBUG */, category, ...args),
156
+ // Specialized category loggers
157
+ auth: (message, level = 2 /* INFO */, ...args) => log(message, level, "AUTH", ...args),
158
+ oauth: (message, level = 2 /* INFO */, ...args) => log(message, level, "OAUTH", ...args),
159
+ api: (message, level = 2 /* INFO */, ...args) => log(message, level, "API", ...args),
160
+ config: (message, level = 2 /* INFO */, ...args) => log(message, level, "CONFIG", ...args)
161
+ };
28
162
 
29
- // src/lib/wordpress-api.ts
30
- function validateEnvironment() {
31
- if (process.env.JWT_TOKEN) {
32
- const requiredEnvVars = ["WP_API_URL"];
33
- const missingVars = requiredEnvVars.filter((varName) => !process.env[varName]);
34
- if (missingVars.length > 0) {
35
- throw new Error(
36
- `Missing required environment variables: ${missingVars.join(
37
- ", "
38
- )}. Please set these variables before starting the server.`
163
+ // src/lib/oauth-types.ts
164
+ var OAuthError = class _OAuthError extends Error {
165
+ constructor(message, code = "OAUTH_ERROR", details) {
166
+ super(message);
167
+ this.name = "OAuthError";
168
+ this.code = code;
169
+ this.details = details;
170
+ Object.setPrototypeOf(this, _OAuthError.prototype);
171
+ }
172
+ };
173
+ var AuthError = class _AuthError extends Error {
174
+ constructor(message, method, statusCode) {
175
+ super(message);
176
+ this.name = "AuthError";
177
+ this.method = method;
178
+ this.statusCode = statusCode;
179
+ Object.setPrototypeOf(this, _AuthError.prototype);
180
+ }
181
+ };
182
+ var APIError = class _APIError extends Error {
183
+ constructor(message, statusCode, endpoint, response) {
184
+ super(message);
185
+ this.name = "APIError";
186
+ this.statusCode = statusCode;
187
+ this.endpoint = endpoint;
188
+ this.response = response;
189
+ Object.setPrototypeOf(this, _APIError.prototype);
190
+ }
191
+ };
192
+
193
+ // src/lib/persistent-auth-config.ts
194
+ import path3 from "path";
195
+ import fs2 from "fs/promises";
196
+ import fsSync from "fs";
197
+ import crypto from "crypto";
198
+ var VERSION = "0.2.1";
199
+ function getConfigDir() {
200
+ const baseConfigDir = CONFIG.WP_MCP_CONFIG_DIR;
201
+ return path3.join(baseConfigDir, `wordpress-remote-${VERSION}`);
202
+ }
203
+ async function ensureConfigDir() {
204
+ try {
205
+ const configDir = getConfigDir();
206
+ await fs2.mkdir(configDir, { recursive: true });
207
+ await fs2.chmod(configDir, 448);
208
+ } catch (error) {
209
+ logger.error("Error creating config directory", "AUTH", error);
210
+ throw error;
211
+ }
212
+ }
213
+ function getConfigFilePath(serverUrlHash, filename) {
214
+ const configDir = getConfigDir();
215
+ return path3.join(configDir, `${serverUrlHash}_${filename}`);
216
+ }
217
+ async function deleteConfigFile(serverUrlHash, filename) {
218
+ try {
219
+ const filePath = getConfigFilePath(serverUrlHash, filename);
220
+ await fs2.unlink(filePath);
221
+ } catch (error) {
222
+ if (error.code !== "ENOENT") {
223
+ logger.error(`Error deleting ${filename}`, "AUTH", error);
224
+ }
225
+ }
226
+ }
227
+ async function readJsonFile(serverUrlHash, filename) {
228
+ try {
229
+ await ensureConfigDir();
230
+ const filePath = getConfigFilePath(serverUrlHash, filename);
231
+ const content = await fs2.readFile(filePath, "utf-8");
232
+ return JSON.parse(content);
233
+ } catch (error) {
234
+ if (error.code === "ENOENT") {
235
+ return void 0;
236
+ }
237
+ logger.error(`Error reading ${filename}`, "AUTH", error);
238
+ return void 0;
239
+ }
240
+ }
241
+ async function writeJsonFile(serverUrlHash, filename, data) {
242
+ try {
243
+ await ensureConfigDir();
244
+ const filePath = getConfigFilePath(serverUrlHash, filename);
245
+ await fs2.writeFile(filePath, JSON.stringify(data, null, 2), "utf-8");
246
+ await fs2.chmod(filePath, 384);
247
+ } catch (error) {
248
+ logger.error(`Error writing ${filename}`, "AUTH", error);
249
+ throw error;
250
+ }
251
+ }
252
+ async function readTextFile(serverUrlHash, filename, errorMessage) {
253
+ try {
254
+ await ensureConfigDir();
255
+ const filePath = getConfigFilePath(serverUrlHash, filename);
256
+ return await fs2.readFile(filePath, "utf-8");
257
+ } catch (error) {
258
+ throw new Error(errorMessage || `Error reading ${filename}`);
259
+ }
260
+ }
261
+ async function writeTextFile(serverUrlHash, filename, text) {
262
+ try {
263
+ await ensureConfigDir();
264
+ const filePath = getConfigFilePath(serverUrlHash, filename);
265
+ await fs2.writeFile(filePath, text, "utf-8");
266
+ await fs2.chmod(filePath, 384);
267
+ } catch (error) {
268
+ logger.error(`Error writing ${filename}`, "AUTH", error);
269
+ throw error;
270
+ }
271
+ }
272
+ function generateServerUrlHash(serverUrl) {
273
+ return crypto.createHash("md5").update(serverUrl).digest("hex");
274
+ }
275
+ async function readTokens(serverUrlHash) {
276
+ try {
277
+ const tokens = await readJsonFile(serverUrlHash, "tokens.json");
278
+ if (tokens) {
279
+ logger.debug(`Loaded tokens for server hash: ${serverUrlHash}`, "AUTH");
280
+ return tokens;
281
+ }
282
+ return null;
283
+ } catch (error) {
284
+ logger.error(`Error reading tokens for ${serverUrlHash}`, "AUTH", error);
285
+ return null;
286
+ }
287
+ }
288
+ async function writeTokens(serverUrlHash, tokens) {
289
+ try {
290
+ const tokensWithTimestamp = {
291
+ ...tokens,
292
+ obtained_at: Date.now()
293
+ };
294
+ await writeJsonFile(serverUrlHash, "tokens.json", tokensWithTimestamp);
295
+ logger.info(`Stored tokens for server hash: ${serverUrlHash}`, "AUTH");
296
+ } catch (error) {
297
+ logger.error(`Error writing tokens for ${serverUrlHash}`, "AUTH", error);
298
+ throw error;
299
+ }
300
+ }
301
+ async function deleteTokens(serverUrlHash) {
302
+ try {
303
+ await deleteConfigFile(serverUrlHash, "tokens.json");
304
+ logger.info(`Deleted tokens for server hash: ${serverUrlHash}`, "AUTH");
305
+ } catch (error) {
306
+ logger.error(`Error deleting tokens for ${serverUrlHash}`, "AUTH", error);
307
+ }
308
+ }
309
+ async function readClientInfo(serverUrlHash) {
310
+ try {
311
+ const clientInfo = await readJsonFile(serverUrlHash, "client_info.json");
312
+ if (clientInfo) {
313
+ logger.debug(`Loaded client info for server hash: ${serverUrlHash}`, "AUTH");
314
+ return clientInfo;
315
+ }
316
+ return null;
317
+ } catch (error) {
318
+ logger.error(`Error reading client info for ${serverUrlHash}`, "AUTH", error);
319
+ return null;
320
+ }
321
+ }
322
+ async function writeClientInfo(serverUrlHash, clientInfo) {
323
+ try {
324
+ await writeJsonFile(serverUrlHash, "client_info.json", clientInfo);
325
+ logger.info(`Stored client info for server hash: ${serverUrlHash}`, "AUTH");
326
+ } catch (error) {
327
+ logger.error(`Error writing client info for ${serverUrlHash}`, "AUTH", error);
328
+ throw error;
329
+ }
330
+ }
331
+ function isTokenValid(tokens) {
332
+ if (!tokens || !tokens.access_token) {
333
+ return { isValid: false, error: "No access token" };
334
+ }
335
+ if (!tokens.expires_in || !tokens.obtained_at) {
336
+ return { isValid: true };
337
+ }
338
+ const now = Date.now();
339
+ const expiresAt = tokens.obtained_at + tokens.expires_in * 1e3;
340
+ const expiresIn = Math.floor((expiresAt - now) / 1e3);
341
+ const isValid = expiresIn > 60;
342
+ return {
343
+ isValid,
344
+ expiresIn: Math.max(0, expiresIn),
345
+ error: isValid ? void 0 : "Token expired"
346
+ };
347
+ }
348
+ async function getValidTokens(serverUrlHash) {
349
+ const tokens = await readTokens(serverUrlHash);
350
+ if (!tokens) {
351
+ return null;
352
+ }
353
+ const validation = isTokenValid(tokens);
354
+ if (!validation.isValid) {
355
+ logger.warn(`Tokens for ${serverUrlHash} are invalid: ${validation.error}`, "AUTH");
356
+ return null;
357
+ }
358
+ return tokens;
359
+ }
360
+ async function cleanupExpiredTokens() {
361
+ try {
362
+ const configDir = getConfigDir();
363
+ if (!fsSync.existsSync(configDir)) {
364
+ return;
365
+ }
366
+ const files = await fs2.readdir(configDir);
367
+ const tokenFiles = files.filter((file) => file.endsWith("_tokens.json"));
368
+ let cleaned = 0;
369
+ for (const file of tokenFiles) {
370
+ const serverHash = file.replace("_tokens.json", "");
371
+ const tokens = await readTokens(serverHash);
372
+ if (tokens && !isTokenValid(tokens).isValid) {
373
+ await deleteTokens(serverHash);
374
+ cleaned++;
375
+ }
376
+ }
377
+ if (cleaned > 0) {
378
+ logger.info(`Cleaned up ${cleaned} expired token files`, "AUTH");
379
+ }
380
+ } catch (error) {
381
+ logger.error("Error during token cleanup", "AUTH", error);
382
+ }
383
+ }
384
+
385
+ // src/lib/mcp-oauth-provider.ts
386
+ import open from "open";
387
+ import { EventEmitter } from "node:events";
388
+
389
+ // src/lib/mcp-oauth-utils.ts
390
+ import crypto2 from "crypto";
391
+ function generatePKCE() {
392
+ const codeVerifier = crypto2.randomBytes(64).toString("base64url").substring(0, 128);
393
+ const codeChallenge = crypto2.createHash("sha256").update(codeVerifier).digest("base64url");
394
+ return {
395
+ codeVerifier,
396
+ codeChallenge,
397
+ codeChallengeMethod: "S256"
398
+ };
399
+ }
400
+ function generateCanonicalResourceURI(serverUrl) {
401
+ try {
402
+ const url = new URL(serverUrl);
403
+ url.protocol = url.protocol.toLowerCase();
404
+ url.hostname = url.hostname.toLowerCase();
405
+ url.hash = "";
406
+ if (url.pathname.endsWith("/") && url.pathname !== "/") {
407
+ url.pathname = url.pathname.slice(0, -1);
408
+ }
409
+ return url.toString();
410
+ } catch (error) {
411
+ throw new OAuthError(
412
+ `Invalid server URL for resource indicator: ${serverUrl}`,
413
+ "INVALID_RESOURCE_URI"
414
+ );
415
+ }
416
+ }
417
+ async function discoverAuthorizationServerMetadata(authorizationServerUrl) {
418
+ try {
419
+ const metadataUrl = new URL("/.well-known/oauth-authorization-server", authorizationServerUrl);
420
+ logger.oauth(`Discovering authorization server metadata: ${metadataUrl}`);
421
+ const response = await fetch(metadataUrl.toString());
422
+ if (!response.ok) {
423
+ throw new OAuthError(
424
+ `Failed to fetch authorization server metadata: ${response.status}`,
425
+ "METADATA_DISCOVERY_FAILED"
39
426
  );
40
427
  }
41
- } else {
42
- const requiredEnvVars = ["WP_API_URL", "WP_API_USERNAME", "WP_API_PASSWORD"];
43
- const missingVars = requiredEnvVars.filter((varName) => !process.env[varName]);
44
- if (missingVars.length > 0) {
45
- throw new Error(
46
- `Missing required environment variables: ${missingVars.join(
47
- ", "
48
- )}. Please set JWT_TOKEN or provide ${missingVars.join(", ")}.`
428
+ const metadata = await response.json();
429
+ if (!metadata.authorization_endpoint || !metadata.token_endpoint) {
430
+ throw new OAuthError(
431
+ "Authorization server metadata missing required endpoints",
432
+ "INVALID_METADATA"
433
+ );
434
+ }
435
+ logger.oauth("Authorization server metadata discovered successfully");
436
+ return metadata;
437
+ } catch (error) {
438
+ if (error instanceof OAuthError) {
439
+ throw error;
440
+ }
441
+ throw new OAuthError(
442
+ `Error discovering authorization server metadata: ${error instanceof Error ? error.message : String(error)}`,
443
+ "METADATA_DISCOVERY_ERROR"
444
+ );
445
+ }
446
+ }
447
+ async function discoverProtectedResourceMetadata(resourceUrl) {
448
+ try {
449
+ const url = new URL(resourceUrl);
450
+ const metadataUrl = new URL("/.well-known/oauth-protected-resource", url.origin);
451
+ logger.oauth(`Discovering protected resource metadata: ${metadataUrl}`);
452
+ const response = await fetch(metadataUrl.toString());
453
+ if (!response.ok) {
454
+ throw new OAuthError(
455
+ `Failed to fetch protected resource metadata: ${response.status}`,
456
+ "RESOURCE_METADATA_DISCOVERY_FAILED"
457
+ );
458
+ }
459
+ const metadata = await response.json();
460
+ if (!metadata.authorization_servers || metadata.authorization_servers.length === 0) {
461
+ throw new OAuthError(
462
+ "Protected resource metadata missing authorization servers",
463
+ "INVALID_RESOURCE_METADATA"
49
464
  );
50
465
  }
466
+ logger.oauth("Protected resource metadata discovered successfully");
467
+ return metadata;
468
+ } catch (error) {
469
+ if (error instanceof OAuthError) {
470
+ throw error;
471
+ }
472
+ throw new OAuthError(
473
+ `Error discovering protected resource metadata: ${error instanceof Error ? error.message : String(error)}`,
474
+ "RESOURCE_METADATA_DISCOVERY_ERROR"
475
+ );
476
+ }
477
+ }
478
+ function parseWWWAuthenticateHeader(headerValue) {
479
+ const parts = headerValue.split(/\s+/);
480
+ const scheme = parts[0];
481
+ const result = { scheme };
482
+ const paramString = parts.slice(1).join(" ");
483
+ const paramMatches = paramString.matchAll(/(\w+)="([^"]+)"/g);
484
+ for (const match of paramMatches) {
485
+ const [, key, value] = match;
486
+ switch (key) {
487
+ case "realm":
488
+ result.realm = value;
489
+ break;
490
+ case "scope":
491
+ result.scope = value;
492
+ break;
493
+ case "error":
494
+ result.error = value;
495
+ break;
496
+ case "error_description":
497
+ result.error_description = value;
498
+ break;
499
+ case "error_uri":
500
+ result.error_uri = value;
501
+ break;
502
+ case "resource_metadata_url":
503
+ result.resource_metadata_url = value;
504
+ break;
505
+ }
506
+ }
507
+ return result;
508
+ }
509
+ async function registerDynamicClient(registrationEndpoint, registrationRequest) {
510
+ try {
511
+ logger.oauth(`Attempting dynamic client registration: ${registrationEndpoint}`);
512
+ const response = await fetch(registrationEndpoint, {
513
+ method: "POST",
514
+ headers: {
515
+ "Content-Type": "application/json"
516
+ },
517
+ body: JSON.stringify(registrationRequest)
518
+ });
519
+ if (!response.ok) {
520
+ const errorText = await response.text();
521
+ throw new OAuthError(
522
+ `Dynamic client registration failed: ${response.status} - ${errorText}`,
523
+ "CLIENT_REGISTRATION_FAILED"
524
+ );
525
+ }
526
+ const registrationResponse = await response.json();
527
+ if (!registrationResponse.client_id) {
528
+ throw new OAuthError(
529
+ "Dynamic client registration response missing client_id",
530
+ "INVALID_REGISTRATION_RESPONSE"
531
+ );
532
+ }
533
+ logger.oauth("Dynamic client registration successful");
534
+ logger.debug("Client ID obtained", "OAUTH", {
535
+ client_id: registrationResponse.client_id,
536
+ expires_at: registrationResponse.client_secret_expires_at
537
+ });
538
+ return registrationResponse;
539
+ } catch (error) {
540
+ if (error instanceof OAuthError) {
541
+ throw error;
542
+ }
543
+ throw new OAuthError(
544
+ `Error during dynamic client registration: ${error instanceof Error ? error.message : String(error)}`,
545
+ "CLIENT_REGISTRATION_ERROR"
546
+ );
547
+ }
548
+ }
549
+ async function exchangeAuthorizationCode(tokenEndpoint, code, redirectUri, clientId, codeVerifier, resource) {
550
+ try {
551
+ logger.oauth("Exchanging authorization code for access token");
552
+ const params = new URLSearchParams({
553
+ grant_type: "authorization_code",
554
+ code,
555
+ redirect_uri: redirectUri,
556
+ client_id: clientId,
557
+ code_verifier: codeVerifier
558
+ });
559
+ if (resource) {
560
+ params.set("resource", resource);
561
+ }
562
+ const response = await fetch(tokenEndpoint, {
563
+ method: "POST",
564
+ headers: {
565
+ "Content-Type": "application/x-www-form-urlencoded"
566
+ },
567
+ body: params.toString()
568
+ });
569
+ if (!response.ok) {
570
+ const errorText = await response.text();
571
+ throw new OAuthError(
572
+ `Token exchange failed: ${response.status} - ${errorText}`,
573
+ "TOKEN_EXCHANGE_FAILED"
574
+ );
575
+ }
576
+ const tokenResponse = await response.json();
577
+ if (!tokenResponse.access_token) {
578
+ throw new OAuthError("Token response missing access_token", "INVALID_TOKEN_RESPONSE");
579
+ }
580
+ logger.oauth("Authorization code exchange successful");
581
+ return {
582
+ access_token: tokenResponse.access_token,
583
+ token_type: tokenResponse.token_type || "Bearer",
584
+ expires_in: tokenResponse.expires_in,
585
+ scope: tokenResponse.scope,
586
+ refresh_token: tokenResponse.refresh_token,
587
+ obtained_at: Date.now()
588
+ };
589
+ } catch (error) {
590
+ if (error instanceof OAuthError) {
591
+ throw error;
592
+ }
593
+ throw new OAuthError(
594
+ `Error exchanging authorization code: ${error instanceof Error ? error.message : String(error)}`,
595
+ "TOKEN_EXCHANGE_ERROR"
596
+ );
597
+ }
598
+ }
599
+ function buildAuthorizationUrl(authorizationEndpoint, clientId, redirectUri, scopes, state, codeChallenge, resource) {
600
+ const params = new URLSearchParams({
601
+ response_type: "code",
602
+ // OAuth 2.1 uses authorization code flow
603
+ client_id: clientId,
604
+ redirect_uri: redirectUri,
605
+ scope: scopes.join(" "),
606
+ state,
607
+ code_challenge: codeChallenge,
608
+ code_challenge_method: "S256"
609
+ });
610
+ if (resource) {
611
+ params.set("resource", resource);
612
+ }
613
+ return `${authorizationEndpoint}?${params.toString()}`;
614
+ }
615
+ function generateSecureState() {
616
+ return crypto2.randomBytes(32).toString("base64url");
617
+ }
618
+
619
+ // src/lib/oauth-callback-server.ts
620
+ import express from "express";
621
+ var AUTHORIZATION_CODE_HTML = `
622
+ <!DOCTYPE html>
623
+ <html>
624
+ <head>
625
+ <title>WordPress MCP OAuth Authorization</title>
626
+ <style>
627
+ body {
628
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
629
+ margin: 0;
630
+ padding: 40px;
631
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
632
+ min-height: 100vh;
633
+ display: flex;
634
+ align-items: center;
635
+ justify-content: center;
636
+ }
637
+ .container {
638
+ background: white;
639
+ padding: 40px;
640
+ border-radius: 12px;
641
+ box-shadow: 0 20px 40px rgba(0,0,0,0.1);
642
+ text-align: center;
643
+ max-width: 500px;
644
+ width: 100%;
645
+ }
646
+ h1 {
647
+ color: #333;
648
+ margin-bottom: 20px;
649
+ }
650
+ .success {
651
+ color: #27ae60;
652
+ font-size: 18px;
653
+ margin: 20px 0;
654
+ }
655
+ .error {
656
+ color: #e74c3c;
657
+ font-size: 18px;
658
+ margin: 20px 0;
659
+ }
660
+ .loading {
661
+ color: #3498db;
662
+ font-size: 18px;
663
+ margin: 20px 0;
664
+ }
665
+ .details {
666
+ color: #666;
667
+ margin-top: 10px;
668
+ font-size: 14px;
669
+ }
670
+ .spinner {
671
+ border: 3px solid #f3f3f3;
672
+ border-top: 3px solid #3498db;
673
+ border-radius: 50%;
674
+ width: 30px;
675
+ height: 30px;
676
+ animation: spin 1s linear infinite;
677
+ margin: 20px auto;
678
+ }
679
+ @keyframes spin {
680
+ 0% { transform: rotate(0deg); }
681
+ 100% { transform: rotate(360deg); }
682
+ }
683
+ </style>
684
+ </head>
685
+ <body>
686
+ <div class="container">
687
+ <h1>WordPress MCP OAuth Authorization</h1>
688
+ <div class="spinner" id="spinner"></div>
689
+ <div id="status" class="loading">Processing authorization code...</div>
690
+ <div id="details" class="details"></div>
691
+ </div>
692
+
693
+ <script>
694
+ function displayStatus(message, type = 'loading') {
695
+ const statusEl = document.getElementById('status');
696
+ const spinnerEl = document.getElementById('spinner');
697
+
698
+ statusEl.textContent = message;
699
+ statusEl.className = type;
700
+
701
+ if (type !== 'loading') {
702
+ spinnerEl.style.display = 'none';
703
+ }
704
+ }
705
+
706
+ function displayDetails(message) {
707
+ document.getElementById('details').textContent = message;
708
+ }
709
+
710
+ // Extract authorization code and state from URL query parameters (OAuth 2.1)
711
+ const urlParams = new URLSearchParams(window.location.search);
712
+
713
+ if (urlParams.has('code')) {
714
+ const authData = {
715
+ code: urlParams.get('code'),
716
+ state: urlParams.get('state'),
717
+ // Additional parameters for validation
718
+ iss: urlParams.get('iss'),
719
+ };
720
+
721
+ // Send authorization code to server for token exchange
722
+ fetch('/oauth/callback', {
723
+ method: 'POST',
724
+ headers: {
725
+ 'Content-Type': 'application/json'
726
+ },
727
+ body: JSON.stringify(authData)
728
+ })
729
+ .then(response => {
730
+ if (response.ok) {
731
+ displayStatus('\u2705 Authorization successful!', 'success');
732
+ displayDetails('OAuth 2.1 authentication completed. You can now close this window.');
733
+ // Auto-close after 3 seconds
734
+ setTimeout(() => {
735
+ displayDetails('Closing window...');
736
+ setTimeout(() => window.close(), 1000);
737
+ }, 3000);
738
+ } else {
739
+ return response.json().then(err => {
740
+ throw new Error(err.error || 'Failed to exchange authorization code');
741
+ });
742
+ }
743
+ })
744
+ .catch(error => {
745
+ displayStatus('\u274C Error completing authorization', 'error');
746
+ displayDetails(error.message + '. Please try again.');
747
+ });
748
+ } else if (urlParams.has('error')) {
749
+ const error = urlParams.get('error');
750
+ const errorDescription = urlParams.get('error_description');
751
+ displayStatus('\u274C Authorization failed', 'error');
752
+ displayDetails(\`Error: \${error}\${errorDescription ? ' - ' + errorDescription : ''}\`);
753
+ } else {
754
+ displayStatus('\u274C No authorization code received', 'error');
755
+ displayDetails('Please try the authorization process again.');
756
+ }
757
+ </script>
758
+ </body>
759
+ </html>
760
+ `;
761
+ var OAuthCallbackServer = class {
762
+ constructor(options, events) {
763
+ this.server = null;
764
+ this.options = options;
765
+ this.events = events;
766
+ this.app = express();
767
+ this.setupRoutes();
768
+ }
769
+ setupRoutes() {
770
+ this.app.use(express.json());
771
+ this.app.get("/oauth/callback", (req, res) => {
772
+ logger.oauth("OAuth 2.1 authorization callback page requested");
773
+ res.send(AUTHORIZATION_CODE_HTML);
774
+ });
775
+ this.app.post("/oauth/callback", async (req, res) => {
776
+ try {
777
+ const { code, state, iss } = req.body;
778
+ if (!code) {
779
+ throw new OAuthError("No authorization code received");
780
+ }
781
+ logger.oauth("OAuth 2.1 authorization code received");
782
+ logger.debug("Authorization code details", "OAUTH", {
783
+ codeLength: code.length,
784
+ state: state || "none",
785
+ issuer: iss || "none"
786
+ });
787
+ this.events.emit("oauth-code-received", { code, state, iss });
788
+ res.json({ success: true, message: "Authorization code received successfully" });
789
+ } catch (error) {
790
+ logger.error("Error processing authorization code", "OAUTH", error);
791
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
792
+ this.events.emit("oauth-error", new OAuthError(errorMessage));
793
+ res.status(400).json({ error: errorMessage });
794
+ }
795
+ });
796
+ this.app.post("/oauth/tokens", async (req, res) => {
797
+ try {
798
+ const tokens = req.body;
799
+ if (!tokens.access_token) {
800
+ throw new OAuthError("No access token received");
801
+ }
802
+ logger.oauth("Legacy OAuth tokens received (implicit flow)");
803
+ logger.debug("Token details", "OAUTH", {
804
+ tokenType: tokens.token_type,
805
+ expiresIn: tokens.expires_in,
806
+ scope: tokens.scope,
807
+ tokenLength: tokens.access_token.length
808
+ });
809
+ await writeTokens(this.options.serverUrlHash, tokens);
810
+ this.events.emit("oauth-success", tokens);
811
+ res.json({ success: true, message: "Tokens saved successfully" });
812
+ } catch (error) {
813
+ logger.error("Error processing OAuth tokens", "OAUTH", error);
814
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
815
+ this.events.emit("oauth-error", new OAuthError(errorMessage));
816
+ res.status(400).json({ error: errorMessage });
817
+ }
818
+ });
819
+ this.app.get("/oauth/health", (req, res) => {
820
+ res.json({
821
+ status: "ok",
822
+ serverHash: this.options.serverUrlHash,
823
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
824
+ });
825
+ });
826
+ this.app.use(
827
+ (error, req, res, next) => {
828
+ logger.error("Express server error", "OAUTH", error);
829
+ res.status(500).json({ error: "Internal server error" });
830
+ }
831
+ );
832
+ }
833
+ async start() {
834
+ return new Promise((resolve, reject) => {
835
+ try {
836
+ this.server = this.app.listen(this.options.port, this.options.host, () => {
837
+ logger.oauth(
838
+ `OAuth callback server listening on http://${this.options.host}:${this.options.port}`
839
+ );
840
+ resolve();
841
+ });
842
+ this.server.on("error", (error) => {
843
+ logger.error("OAuth callback server error", "OAUTH", error);
844
+ reject(error);
845
+ });
846
+ if (this.options.timeout) {
847
+ setTimeout(() => {
848
+ if (!this.server?.listening) {
849
+ reject(new OAuthError("OAuth callback server startup timeout", "TIMEOUT"));
850
+ }
851
+ }, this.options.timeout);
852
+ }
853
+ } catch (error) {
854
+ reject(error);
855
+ }
856
+ });
857
+ }
858
+ async stop() {
859
+ return new Promise((resolve) => {
860
+ if (this.server) {
861
+ this.server.close(() => {
862
+ logger.oauth("OAuth callback server stopped");
863
+ this.server = null;
864
+ resolve();
865
+ });
866
+ } else {
867
+ resolve();
868
+ }
869
+ });
870
+ }
871
+ getCallbackUrl() {
872
+ return `http://${this.options.host}:${this.options.port}/oauth/callback`;
873
+ }
874
+ isRunning() {
875
+ return this.server?.listening ?? false;
876
+ }
877
+ };
878
+ function setupWPOAuthCallbackServer(options, events) {
879
+ return new OAuthCallbackServer(options, events);
880
+ }
881
+
882
+ // src/lib/mcp-oauth-provider.ts
883
+ var MCPOAuthProvider = class {
884
+ constructor(options) {
885
+ this.authPromise = null;
886
+ // OAuth 2.1 flow state
887
+ this.currentState = null;
888
+ this.currentPKCE = null;
889
+ // Discovered metadata
890
+ this.authServerMetadata = null;
891
+ this.resourceMetadata = null;
892
+ this.config = {
893
+ serverUrl: options.serverUrl || CONFIG.WP_API_URL,
894
+ resource: generateCanonicalResourceURI(options.serverUrl || CONFIG.WP_API_URL),
895
+ responseType: "code",
896
+ // OAuth 2.1 uses authorization code flow
897
+ usePKCE: true,
898
+ // PKCE is required for OAuth 2.1
899
+ redirectUri: `http://${CONFIG.OAUTH_HOST}:${CONFIG.OAUTH_CALLBACK_PORT}/oauth/callback`,
900
+ scopes: options.scopes || ["read", "write"],
901
+ clientId: options.clientId || CONFIG.WP_OAUTH_CLIENT_ID,
902
+ callbackPort: CONFIG.OAUTH_CALLBACK_PORT,
903
+ host: CONFIG.OAUTH_HOST,
904
+ timeout: CONFIG.OAUTH_TIMEOUT,
905
+ ...options
906
+ };
907
+ this.serverUrlHash = generateServerUrlHash(this.config.serverUrl);
908
+ this.events = new EventEmitter();
909
+ this.events.setMaxListeners(10);
910
+ logger.oauth("Initialized MCP-compliant OAuth 2.1 provider");
911
+ logger.debug("OAuth configuration", "OAUTH", {
912
+ serverUrl: this.config.serverUrl,
913
+ resource: this.config.resource,
914
+ serverHash: this.serverUrlHash,
915
+ flowType: this.config.responseType,
916
+ usePKCE: this.config.usePKCE,
917
+ callbackPort: this.config.callbackPort
918
+ });
919
+ }
920
+ /**
921
+ * Get current tokens if available and valid from persistent storage
922
+ */
923
+ async tokens() {
924
+ try {
925
+ const tokens = await readTokens(this.serverUrlHash);
926
+ if (tokens) {
927
+ const validation = isTokenValid(tokens);
928
+ if (validation.isValid) {
929
+ logger.oauth("Found valid tokens in persistent storage");
930
+ if (validation.expiresIn) {
931
+ logger.debug(`Tokens expire in ${validation.expiresIn} seconds`, "OAUTH");
932
+ }
933
+ return tokens;
934
+ } else {
935
+ logger.warn(`Tokens in persistent storage are invalid: ${validation.error}`, "OAUTH");
936
+ return null;
937
+ }
938
+ }
939
+ logger.debug("No tokens found in persistent storage", "OAUTH");
940
+ return null;
941
+ } catch (error) {
942
+ logger.error("Error retrieving tokens from persistent storage", "OAUTH", error);
943
+ return null;
944
+ }
945
+ }
946
+ /**
947
+ * Discover OAuth 2.1 server endpoints using MCP-compliant discovery
948
+ */
949
+ async discoverOAuthEndpoints() {
950
+ try {
951
+ logger.oauth("Starting MCP-compliant OAuth endpoint discovery");
952
+ try {
953
+ this.resourceMetadata = await discoverProtectedResourceMetadata(this.config.resource);
954
+ logger.oauth("Protected resource metadata discovered");
955
+ } catch (error) {
956
+ logger.warn(
957
+ "Protected resource metadata discovery failed, trying 401 response method",
958
+ "OAUTH"
959
+ );
960
+ await this.discoverVia401Response();
961
+ }
962
+ const authServerUrl = this.getAuthorizationServerUrl();
963
+ if (authServerUrl) {
964
+ this.authServerMetadata = await discoverAuthorizationServerMetadata(authServerUrl);
965
+ logger.oauth("Authorization server metadata discovered");
966
+ this.config.authorizationEndpoint = this.authServerMetadata.authorization_endpoint;
967
+ this.config.tokenEndpoint = this.authServerMetadata.token_endpoint;
968
+ this.config.registrationEndpoint = this.authServerMetadata.registration_endpoint;
969
+ } else {
970
+ throw new OAuthError("Could not determine authorization server URL", "DISCOVERY_FAILED");
971
+ }
972
+ } catch (error) {
973
+ logger.error("OAuth endpoint discovery failed", "OAUTH", error);
974
+ throw error;
975
+ }
976
+ }
977
+ /**
978
+ * Discover endpoints via 401 Unauthorized response (RFC 9728 Section 5.1)
979
+ */
980
+ async discoverVia401Response() {
981
+ try {
982
+ logger.oauth("Attempting discovery via 401 response method");
983
+ const response = await fetch(this.config.serverUrl, {
984
+ headers: {
985
+ Accept: "application/json"
986
+ }
987
+ });
988
+ if (response.status === 401) {
989
+ const wwwAuthHeader = response.headers.get("WWW-Authenticate");
990
+ if (wwwAuthHeader) {
991
+ const authInfo = parseWWWAuthenticateHeader(wwwAuthHeader);
992
+ logger.oauth("WWW-Authenticate header parsed successfully");
993
+ if (authInfo.resource_metadata_url) {
994
+ const metadataResponse = await fetch(authInfo.resource_metadata_url);
995
+ if (metadataResponse.ok) {
996
+ this.resourceMetadata = await metadataResponse.json();
997
+ logger.oauth("Protected resource metadata obtained via 401 response");
998
+ }
999
+ }
1000
+ }
1001
+ }
1002
+ } catch (error) {
1003
+ logger.warn("401 response discovery method failed", "OAUTH", error);
1004
+ }
1005
+ }
1006
+ /**
1007
+ * Get authorization server URL from discovered metadata
1008
+ */
1009
+ getAuthorizationServerUrl() {
1010
+ if (this.resourceMetadata?.authorization_servers?.length) {
1011
+ return this.resourceMetadata.authorization_servers[0];
1012
+ }
1013
+ try {
1014
+ const url = new URL(this.config.serverUrl);
1015
+ return url.origin;
1016
+ } catch {
1017
+ return null;
1018
+ }
1019
+ }
1020
+ /**
1021
+ * Perform dynamic client registration if needed and supported
1022
+ */
1023
+ async ensureClientRegistration() {
1024
+ if (this.config.clientId) {
1025
+ logger.oauth("Using provided client ID, skipping dynamic registration");
1026
+ return;
1027
+ }
1028
+ if (!CONFIG.OAUTH_DYNAMIC_REGISTRATION) {
1029
+ throw new OAuthError(
1030
+ "No client ID provided and dynamic registration is disabled",
1031
+ "NO_CLIENT_ID"
1032
+ );
1033
+ }
1034
+ if (!this.authServerMetadata?.registration_endpoint) {
1035
+ throw new OAuthError(
1036
+ "Dynamic client registration not supported by authorization server",
1037
+ "NO_DYNAMIC_REGISTRATION"
1038
+ );
1039
+ }
1040
+ try {
1041
+ logger.oauth("Performing dynamic client registration");
1042
+ const registrationRequest = {
1043
+ redirect_uris: [this.config.redirectUri],
1044
+ token_endpoint_auth_method: "none",
1045
+ // Public client
1046
+ grant_types: ["authorization_code"],
1047
+ response_types: ["code"],
1048
+ client_name: "WordPress MCP Remote",
1049
+ scope: this.config.scopes.join(" ")
1050
+ };
1051
+ const registrationResponse = await registerDynamicClient(
1052
+ this.authServerMetadata.registration_endpoint,
1053
+ registrationRequest
1054
+ );
1055
+ const clientInfo = {
1056
+ client_id: registrationResponse.client_id,
1057
+ client_secret: registrationResponse.client_secret
1058
+ };
1059
+ await writeClientInfo(this.serverUrlHash, clientInfo);
1060
+ this.config.clientId = registrationResponse.client_id;
1061
+ logger.oauth("Dynamic client registration completed successfully");
1062
+ } catch (error) {
1063
+ logger.error("Dynamic client registration failed", "OAUTH", error);
1064
+ throw error;
1065
+ }
1066
+ }
1067
+ /**
1068
+ * Initiate MCP-compliant OAuth 2.1 authorization flow
1069
+ */
1070
+ async authorize() {
1071
+ if (this.authPromise) {
1072
+ logger.oauth("Authorization already in progress, waiting...");
1073
+ await this.authPromise;
1074
+ return;
1075
+ }
1076
+ const existingTokens = await this.tokens();
1077
+ if (existingTokens) {
1078
+ logger.oauth("Already have valid tokens, skipping authorization");
1079
+ return;
1080
+ }
1081
+ logger.oauth("Starting MCP-compliant OAuth 2.1 authorization flow");
1082
+ this.authPromise = this.performAuthorization();
1083
+ try {
1084
+ await this.authPromise;
1085
+ logger.oauth("OAuth 2.1 authorization completed successfully");
1086
+ } catch (error) {
1087
+ logger.error("OAuth 2.1 authorization failed", "OAUTH", error);
1088
+ throw error;
1089
+ } finally {
1090
+ this.authPromise = null;
1091
+ }
1092
+ }
1093
+ /**
1094
+ * Perform the complete OAuth 2.1 authorization flow
1095
+ */
1096
+ async performAuthorization() {
1097
+ try {
1098
+ await this.discoverOAuthEndpoints();
1099
+ await this.ensureClientRegistration();
1100
+ this.currentPKCE = generatePKCE();
1101
+ this.currentState = generateSecureState();
1102
+ await writeTextFile(this.serverUrlHash, "pkce_verifier.txt", this.currentPKCE.codeVerifier);
1103
+ await writeTextFile(this.serverUrlHash, "oauth_state.txt", this.currentState);
1104
+ const callbackServer = setupWPOAuthCallbackServer(
1105
+ {
1106
+ port: this.config.callbackPort,
1107
+ host: this.config.host,
1108
+ serverUrlHash: this.serverUrlHash,
1109
+ timeout: this.config.timeout
1110
+ },
1111
+ this.events
1112
+ );
1113
+ await callbackServer.start();
1114
+ logger.oauth("OAuth callback server started");
1115
+ const authUrl = buildAuthorizationUrl(
1116
+ this.config.authorizationEndpoint,
1117
+ this.config.clientId,
1118
+ this.config.redirectUri,
1119
+ this.config.scopes,
1120
+ this.currentState,
1121
+ this.currentPKCE.codeChallenge,
1122
+ CONFIG.OAUTH_RESOURCE_INDICATOR ? this.config.resource : void 0
1123
+ );
1124
+ logger.oauth("Built OAuth 2.1 authorization URL");
1125
+ logger.debug("Authorization URL", "OAUTH", { url: authUrl });
1126
+ try {
1127
+ await open(authUrl);
1128
+ logger.oauth("Browser opened successfully");
1129
+ } catch (browserError) {
1130
+ logger.error("Failed to open browser automatically", "OAUTH", browserError);
1131
+ logger.info("\n=== MANUAL ACTION REQUIRED ===");
1132
+ logger.info("Please manually open the following URL in your browser:");
1133
+ logger.info(`${authUrl}`);
1134
+ logger.info("===============================\n");
1135
+ }
1136
+ const authCode = await this.waitForAuthorizationCode();
1137
+ const tokens = await this.exchangeCodeForTokens(authCode);
1138
+ await writeTokens(this.serverUrlHash, tokens);
1139
+ logger.oauth("OAuth 2.1 tokens stored successfully");
1140
+ await callbackServer.stop();
1141
+ return tokens;
1142
+ } catch (error) {
1143
+ logger.error("Error during OAuth 2.1 authorization flow", "OAUTH", error);
1144
+ throw error;
1145
+ }
1146
+ }
1147
+ /**
1148
+ * Wait for authorization code from callback
1149
+ */
1150
+ async waitForAuthorizationCode() {
1151
+ return new Promise((resolve, reject) => {
1152
+ const timeout = setTimeout(() => {
1153
+ cleanup();
1154
+ reject(new OAuthError("Authorization timeout", "TIMEOUT"));
1155
+ }, this.config.timeout);
1156
+ const cleanup = () => {
1157
+ this.events.removeAllListeners("oauth-code-received");
1158
+ this.events.removeAllListeners("oauth-error");
1159
+ clearTimeout(timeout);
1160
+ };
1161
+ this.events.once("oauth-code-received", async ({ code, state }) => {
1162
+ cleanup();
1163
+ if (state !== this.currentState) {
1164
+ reject(new OAuthError("State parameter mismatch", "INVALID_STATE"));
1165
+ return;
1166
+ }
1167
+ logger.oauth("Valid authorization code received");
1168
+ resolve(code);
1169
+ });
1170
+ this.events.once("oauth-error", (error) => {
1171
+ cleanup();
1172
+ reject(error);
1173
+ });
1174
+ });
1175
+ }
1176
+ /**
1177
+ * Exchange authorization code for access tokens
1178
+ */
1179
+ async exchangeCodeForTokens(code) {
1180
+ try {
1181
+ const codeVerifier = await readTextFile(
1182
+ this.serverUrlHash,
1183
+ "pkce_verifier.txt",
1184
+ "PKCE code verifier not found"
1185
+ );
1186
+ const tokenResponse = await exchangeAuthorizationCode(
1187
+ this.config.tokenEndpoint,
1188
+ code,
1189
+ this.config.redirectUri,
1190
+ this.config.clientId,
1191
+ codeVerifier,
1192
+ CONFIG.OAUTH_RESOURCE_INDICATOR ? this.config.resource : void 0
1193
+ );
1194
+ await deleteConfigFile(this.serverUrlHash, "pkce_verifier.txt");
1195
+ await deleteConfigFile(this.serverUrlHash, "oauth_state.txt");
1196
+ return tokenResponse;
1197
+ } catch (error) {
1198
+ logger.error("Token exchange failed", "OAUTH", error);
1199
+ throw error;
1200
+ }
1201
+ }
1202
+ /**
1203
+ * Clear stored credentials and state
1204
+ */
1205
+ async invalidateCredentials(scope) {
1206
+ logger.oauth(`Invalidating credentials: ${scope}`);
1207
+ switch (scope) {
1208
+ case "all":
1209
+ await Promise.all([
1210
+ deleteConfigFile(this.serverUrlHash, "client_info.json"),
1211
+ deleteConfigFile(this.serverUrlHash, "tokens.json"),
1212
+ deleteConfigFile(this.serverUrlHash, "pkce_verifier.txt"),
1213
+ deleteConfigFile(this.serverUrlHash, "oauth_state.txt")
1214
+ ]);
1215
+ break;
1216
+ case "client":
1217
+ await deleteConfigFile(this.serverUrlHash, "client_info.json");
1218
+ break;
1219
+ case "tokens":
1220
+ await deleteConfigFile(this.serverUrlHash, "tokens.json");
1221
+ break;
1222
+ case "state":
1223
+ await Promise.all([
1224
+ deleteConfigFile(this.serverUrlHash, "pkce_verifier.txt"),
1225
+ deleteConfigFile(this.serverUrlHash, "oauth_state.txt")
1226
+ ]);
1227
+ break;
1228
+ }
1229
+ logger.oauth(`Credentials invalidated: ${scope}`);
1230
+ }
1231
+ /**
1232
+ * Check if currently authorized
1233
+ */
1234
+ async isAuthorized() {
1235
+ const tokens = await this.tokens();
1236
+ return tokens !== null;
1237
+ }
1238
+ /**
1239
+ * Get server URL hash
1240
+ */
1241
+ getServerUrlHash() {
1242
+ return this.serverUrlHash;
1243
+ }
1244
+ /**
1245
+ * Get OAuth configuration
1246
+ */
1247
+ getConfig() {
1248
+ return { ...this.config };
1249
+ }
1250
+ };
1251
+
1252
+ // src/lib/coordination.ts
1253
+ import * as fs3 from "node:fs";
1254
+ import * as path4 from "node:path";
1255
+ import * as os2 from "node:os";
1256
+
1257
+ // src/lib/persistent-oauth-client-provider.ts
1258
+ import open2 from "open";
1259
+ import { EventEmitter as EventEmitter2 } from "node:events";
1260
+ var WP_PERSISTENT_CONFIG = {
1261
+ authorizeEndpoint: "/wp-admin/admin.php?page=mcp-oauth-authorize",
1262
+ scopes: ["read", "write"],
1263
+ callbackPort: CONFIG.OAUTH_CALLBACK_PORT,
1264
+ host: CONFIG.OAUTH_HOST
1265
+ };
1266
+ var PersistentWPOAuthClientProvider = class {
1267
+ constructor(options) {
1268
+ this.authPromise = null;
1269
+ this.options = {
1270
+ ...WP_PERSISTENT_CONFIG,
1271
+ ...options
1272
+ };
1273
+ this.serverUrlHash = generateServerUrlHash(this.options.serverUrl);
1274
+ this.events = new EventEmitter2();
1275
+ this.events.setMaxListeners(10);
1276
+ logger.oauth("Initialized Persistent WordPress OAuth provider");
1277
+ logger.debug("OAuth provider options", "OAUTH", {
1278
+ serverUrl: this.options.serverUrl,
1279
+ serverHash: this.serverUrlHash,
1280
+ clientId: this.options.clientId || "auto-generated",
1281
+ callbackPort: this.options.callbackPort
1282
+ });
1283
+ }
1284
+ /**
1285
+ * Get current tokens if available and valid from persistent storage
1286
+ */
1287
+ async tokens() {
1288
+ try {
1289
+ const tokens = await readTokens(this.serverUrlHash);
1290
+ if (tokens) {
1291
+ const validation = isTokenValid(tokens);
1292
+ if (validation.isValid) {
1293
+ logger.oauth("Found valid tokens in persistent storage");
1294
+ if (validation.expiresIn) {
1295
+ logger.debug(`Tokens expire in ${validation.expiresIn} seconds`, "OAUTH");
1296
+ }
1297
+ return tokens;
1298
+ } else {
1299
+ logger.warn(`Tokens in persistent storage are invalid: ${validation.error}`, "OAUTH");
1300
+ return null;
1301
+ }
1302
+ }
1303
+ logger.debug("No tokens found in persistent storage", "OAUTH");
1304
+ return null;
1305
+ } catch (error) {
1306
+ logger.error("Error retrieving tokens from persistent storage", "OAUTH", error);
1307
+ return null;
1308
+ }
1309
+ }
1310
+ /**
1311
+ * Save tokens to persistent storage
1312
+ */
1313
+ async saveTokens(tokens) {
1314
+ try {
1315
+ await writeTokens(this.serverUrlHash, tokens);
1316
+ logger.oauth("Tokens saved to persistent storage");
1317
+ } catch (error) {
1318
+ logger.error("Error saving tokens to persistent storage", "OAUTH", error);
1319
+ throw error;
1320
+ }
1321
+ }
1322
+ /**
1323
+ * Get client information from persistent storage
1324
+ */
1325
+ async clientInformation() {
1326
+ try {
1327
+ const clientInfo = await readClientInfo(this.serverUrlHash);
1328
+ if (clientInfo) {
1329
+ logger.debug("Found client information in persistent storage", "OAUTH");
1330
+ return clientInfo;
1331
+ }
1332
+ logger.debug("No client information found in persistent storage", "OAUTH");
1333
+ return null;
1334
+ } catch (error) {
1335
+ logger.error("Error retrieving client information", "OAUTH", error);
1336
+ return null;
1337
+ }
1338
+ }
1339
+ /**
1340
+ * Save client information to persistent storage
1341
+ */
1342
+ async saveClientInformation(clientInfo) {
1343
+ try {
1344
+ await writeClientInfo(this.serverUrlHash, clientInfo);
1345
+ logger.oauth("Client information saved to persistent storage");
1346
+ } catch (error) {
1347
+ logger.error("Error saving client information", "OAUTH", error);
1348
+ throw error;
1349
+ }
1350
+ }
1351
+ /**
1352
+ * Save PKCE code verifier
1353
+ */
1354
+ async saveCodeVerifier(codeVerifier) {
1355
+ try {
1356
+ await writeTextFile(this.serverUrlHash, "code_verifier.txt", codeVerifier);
1357
+ logger.debug("Code verifier saved", "OAUTH");
1358
+ } catch (error) {
1359
+ logger.error("Error saving code verifier", "OAUTH", error);
1360
+ throw error;
1361
+ }
1362
+ }
1363
+ /**
1364
+ * Get PKCE code verifier
1365
+ */
1366
+ async codeVerifier() {
1367
+ try {
1368
+ const verifier = await readTextFile(
1369
+ this.serverUrlHash,
1370
+ "code_verifier.txt",
1371
+ "No code verifier saved for session"
1372
+ );
1373
+ logger.debug("Code verifier retrieved", "OAUTH");
1374
+ return verifier;
1375
+ } catch (error) {
1376
+ logger.error("Error retrieving code verifier", "OAUTH", error);
1377
+ throw error;
1378
+ }
1379
+ }
1380
+ /**
1381
+ * Initiate OAuth authorization flow
1382
+ */
1383
+ async authorize() {
1384
+ if (this.authPromise) {
1385
+ logger.oauth("Authorization already in progress, waiting...");
1386
+ await this.authPromise;
1387
+ return;
1388
+ }
1389
+ const existingTokens = await this.tokens();
1390
+ if (existingTokens) {
1391
+ logger.oauth("Already have valid tokens in persistent storage, skipping authorization");
1392
+ return;
1393
+ }
1394
+ logger.oauth("Starting OAuth authorization flow for WordPress (persistent storage)");
1395
+ this.authPromise = this.performAuthorization();
1396
+ try {
1397
+ await this.authPromise;
1398
+ logger.oauth("OAuth authorization completed successfully");
1399
+ } catch (error) {
1400
+ logger.error("OAuth authorization failed", "OAUTH", error);
1401
+ throw error;
1402
+ } finally {
1403
+ this.authPromise = null;
1404
+ }
1405
+ }
1406
+ /**
1407
+ * Perform the actual OAuth authorization
1408
+ */
1409
+ async performAuthorization() {
1410
+ const callbackServerOptions = {
1411
+ port: this.options.callbackPort,
1412
+ host: this.options.host,
1413
+ serverUrlHash: this.serverUrlHash,
1414
+ timeout: CONFIG.OAUTH_TIMEOUT
1415
+ };
1416
+ logger.oauth(`Setting up callback server on ${this.options.host}:${this.options.callbackPort}`);
1417
+ const callbackServer = setupWPOAuthCallbackServer(callbackServerOptions, this.events);
1418
+ try {
1419
+ logger.oauth("Starting callback server...");
1420
+ await callbackServer.start();
1421
+ logger.oauth("Callback server started successfully");
1422
+ const state = this.generateState();
1423
+ logger.debug(`Generated state parameter: ${state}`, "OAUTH");
1424
+ const authUrl = this.buildAuthorizationUrl(callbackServer.getCallbackUrl(), state);
1425
+ logger.oauth(`Built authorization URL: ${authUrl}`);
1426
+ logger.debug(`Callback URL: ${callbackServer.getCallbackUrl()}`, "OAUTH");
1427
+ logger.oauth("Attempting to open browser...");
1428
+ try {
1429
+ await open2(authUrl);
1430
+ logger.oauth("Browser opened successfully");
1431
+ } catch (browserError) {
1432
+ logger.error("Failed to open browser automatically", "OAUTH", browserError);
1433
+ logger.info("\n=== MANUAL ACTION REQUIRED ===");
1434
+ logger.info("Please manually open the following URL in your browser:");
1435
+ logger.info(`${authUrl}`);
1436
+ logger.info("===============================\n");
1437
+ }
1438
+ logger.oauth("Waiting for authorization result...");
1439
+ const tokens = await this.waitForAuthorizationResult();
1440
+ logger.oauth("Authorization result received");
1441
+ logger.oauth("Saving tokens to persistent storage...");
1442
+ await this.saveTokens(tokens);
1443
+ logger.oauth("Tokens saved successfully");
1444
+ return tokens;
1445
+ } catch (error) {
1446
+ logger.error("Error during authorization flow", "OAUTH", error);
1447
+ if (error instanceof Error && error.stack) {
1448
+ logger.debug("Stack trace", "OAUTH", error.stack);
1449
+ }
1450
+ throw error;
1451
+ } finally {
1452
+ logger.oauth("Stopping callback server...");
1453
+ try {
1454
+ await callbackServer.stop();
1455
+ logger.oauth("Callback server stopped");
1456
+ } catch (stopError) {
1457
+ logger.error("Error stopping callback server", "OAUTH", stopError);
1458
+ }
1459
+ }
1460
+ }
1461
+ /**
1462
+ * Build the WordPress authorization URL
1463
+ */
1464
+ buildAuthorizationUrl(callbackUrl, state) {
1465
+ const baseUrl = this.options.serverUrl.replace(/\/+$/, "");
1466
+ const authEndpoint = this.options.authorizeEndpoint || WP_PERSISTENT_CONFIG.authorizeEndpoint;
1467
+ const params = new URLSearchParams({
1468
+ response_type: "token",
1469
+ // Implicit flow
1470
+ redirect_uri: callbackUrl,
1471
+ scope: this.options.scopes?.join(" ") || WP_PERSISTENT_CONFIG.scopes.join(" "),
1472
+ state
1473
+ });
1474
+ if (this.options.clientId) {
1475
+ params.set("client_id", this.options.clientId);
1476
+ }
1477
+ return `${baseUrl}${authEndpoint}?${params.toString()}`;
1478
+ }
1479
+ /**
1480
+ * Generate a secure state parameter
1481
+ */
1482
+ generateState() {
1483
+ return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
1484
+ }
1485
+ /**
1486
+ * Wait for authorization result from callback server
1487
+ */
1488
+ async waitForAuthorizationResult() {
1489
+ return new Promise((resolve, reject) => {
1490
+ const timeout = setTimeout(() => {
1491
+ cleanup();
1492
+ reject(new OAuthError("Authorization timeout", "TIMEOUT"));
1493
+ }, CONFIG.LOCK_TIMEOUT);
1494
+ const cleanup = () => {
1495
+ this.events.removeAllListeners("oauth-success");
1496
+ this.events.removeAllListeners("oauth-error");
1497
+ clearTimeout(timeout);
1498
+ };
1499
+ this.events.once("oauth-success", (tokens) => {
1500
+ cleanup();
1501
+ logger.oauth("OAuth authorization successful for WordPress (persistent storage)");
1502
+ resolve(tokens);
1503
+ });
1504
+ this.events.once("oauth-error", (error) => {
1505
+ cleanup();
1506
+ logger.error("OAuth authorization error", "OAUTH", error.message);
1507
+ reject(error);
1508
+ });
1509
+ });
1510
+ }
1511
+ /**
1512
+ * Clear stored tokens and credentials
1513
+ */
1514
+ async invalidateCredentials(scope) {
1515
+ logger.oauth(`Invalidating credentials: ${scope}`);
1516
+ switch (scope) {
1517
+ case "all":
1518
+ await Promise.all([
1519
+ deleteConfigFile(this.serverUrlHash, "client_info.json"),
1520
+ deleteConfigFile(this.serverUrlHash, "tokens.json"),
1521
+ deleteConfigFile(this.serverUrlHash, "code_verifier.txt")
1522
+ ]);
1523
+ logger.oauth("All credentials invalidated");
1524
+ break;
1525
+ case "client":
1526
+ await deleteConfigFile(this.serverUrlHash, "client_info.json");
1527
+ logger.oauth("Client information invalidated");
1528
+ break;
1529
+ case "tokens":
1530
+ await deleteConfigFile(this.serverUrlHash, "tokens.json");
1531
+ logger.oauth("OAuth tokens invalidated");
1532
+ break;
1533
+ case "verifier":
1534
+ await deleteConfigFile(this.serverUrlHash, "code_verifier.txt");
1535
+ logger.oauth("Code verifier invalidated");
1536
+ break;
1537
+ default:
1538
+ throw new Error(`Unknown credential scope: ${scope}`);
1539
+ }
1540
+ }
1541
+ /**
1542
+ * Get authorization status
1543
+ */
1544
+ async isAuthorized() {
1545
+ const tokens = await this.tokens();
1546
+ return tokens !== null;
1547
+ }
1548
+ /**
1549
+ * Get the server URL hash
1550
+ */
1551
+ getServerUrlHash() {
1552
+ return this.serverUrlHash;
1553
+ }
1554
+ /**
1555
+ * Get OAuth options
1556
+ */
1557
+ getOptions() {
1558
+ return { ...this.options };
1559
+ }
1560
+ /**
1561
+ * Redirect to authorization URL (for browser opening)
1562
+ */
1563
+ async redirectToAuthorization(authorizationUrl) {
1564
+ logger.oauth(`
1565
+ Please authorize this client by visiting:
1566
+ ${authorizationUrl.toString()}
1567
+ `);
1568
+ try {
1569
+ await open2(authorizationUrl.toString());
1570
+ logger.oauth("Browser opened automatically.");
1571
+ } catch (error) {
1572
+ logger.warn(
1573
+ "Could not open browser automatically. Please copy and paste the URL above into your browser.",
1574
+ "OAUTH"
1575
+ );
1576
+ }
1577
+ }
1578
+ };
1579
+
1580
+ // src/lib/coordination.ts
1581
+ var LockfileManager = class {
1582
+ constructor(serverUrlHash) {
1583
+ this.isOwner = false;
1584
+ this.checkInterval = null;
1585
+ const authDir = getConfigDir();
1586
+ this.lockfilePath = path4.join(authDir, `${serverUrlHash}_auth.lock`);
1587
+ }
1588
+ /**
1589
+ * Try to acquire the lock
1590
+ */
1591
+ tryAcquire() {
1592
+ try {
1593
+ if (fs3.existsSync(this.lockfilePath)) {
1594
+ const lockData2 = this.readLockfile();
1595
+ if (lockData2 && this.isLockValid(lockData2)) {
1596
+ logger.debug(`Lock is held by PID ${lockData2.pid}`, "COORDINATION");
1597
+ return false;
1598
+ }
1599
+ this.release();
1600
+ }
1601
+ const lockData = {
1602
+ pid: process.pid,
1603
+ port: CONFIG.OAUTH_CALLBACK_PORT,
1604
+ timestamp: Date.now(),
1605
+ hostname: os2.hostname()
1606
+ };
1607
+ fs3.writeFileSync(this.lockfilePath, JSON.stringify(lockData), { mode: 384 });
1608
+ this.isOwner = true;
1609
+ this.startMonitoring();
1610
+ logger.debug(`Acquired auth lock: ${this.lockfilePath}`, "COORDINATION");
1611
+ return true;
1612
+ } catch (error) {
1613
+ logger.error("Error acquiring lock", "COORDINATION", error);
1614
+ return false;
1615
+ }
1616
+ }
1617
+ /**
1618
+ * Release the lock
1619
+ */
1620
+ release() {
1621
+ try {
1622
+ if (this.checkInterval) {
1623
+ clearInterval(this.checkInterval);
1624
+ this.checkInterval = null;
1625
+ }
1626
+ if (this.isOwner && fs3.existsSync(this.lockfilePath)) {
1627
+ fs3.unlinkSync(this.lockfilePath);
1628
+ logger.debug(`Released auth lock: ${this.lockfilePath}`, "COORDINATION");
1629
+ }
1630
+ this.isOwner = false;
1631
+ } catch (error) {
1632
+ logger.error("Error releasing lock", "COORDINATION", error);
1633
+ }
1634
+ }
1635
+ /**
1636
+ * Check if we own the lock
1637
+ */
1638
+ isLockOwner() {
1639
+ return this.isOwner;
1640
+ }
1641
+ /**
1642
+ * Wait for lock to be released
1643
+ */
1644
+ async waitForRelease(timeout = CONFIG.LOCK_TIMEOUT) {
1645
+ const startTime = Date.now();
1646
+ return new Promise((resolve, reject) => {
1647
+ const checkLock = () => {
1648
+ if (!fs3.existsSync(this.lockfilePath)) {
1649
+ logger.debug("Lock file no longer exists", "COORDINATION");
1650
+ resolve();
1651
+ return;
1652
+ }
1653
+ const lockData = this.readLockfile();
1654
+ if (!lockData || !this.isLockValid(lockData)) {
1655
+ logger.debug("Lock is stale, cleaning up", "COORDINATION");
1656
+ this.release();
1657
+ resolve();
1658
+ return;
1659
+ }
1660
+ if (Date.now() - startTime > timeout) {
1661
+ reject(new OAuthError("Timeout waiting for auth lock", "LOCK_TIMEOUT"));
1662
+ return;
1663
+ }
1664
+ setTimeout(checkLock, 1e3);
1665
+ };
1666
+ logger.info("Waiting for other instance to complete authentication...", "COORDINATION");
1667
+ checkLock();
1668
+ });
1669
+ }
1670
+ /**
1671
+ * Read lockfile data
1672
+ */
1673
+ readLockfile() {
1674
+ try {
1675
+ const data = fs3.readFileSync(this.lockfilePath, "utf8");
1676
+ return JSON.parse(data);
1677
+ } catch {
1678
+ return null;
1679
+ }
1680
+ }
1681
+ /**
1682
+ * Check if lock is still valid (process is running)
1683
+ */
1684
+ isLockValid(lockData) {
1685
+ try {
1686
+ process.kill(lockData.pid, 0);
1687
+ const age = Date.now() - lockData.timestamp;
1688
+ const maxAge = 6e5;
1689
+ if (age > maxAge) {
1690
+ logger.debug(
1691
+ `Lock is too old (${Math.round(age / 1e3)}s), considering invalid`,
1692
+ "COORDINATION"
1693
+ );
1694
+ return false;
1695
+ }
1696
+ return true;
1697
+ } catch {
1698
+ logger.debug(`Process ${lockData.pid} is not running`, "COORDINATION");
1699
+ return false;
1700
+ }
1701
+ }
1702
+ /**
1703
+ * Monitor lock validity
1704
+ */
1705
+ startMonitoring() {
1706
+ this.checkInterval = setInterval(() => {
1707
+ if (this.isOwner && !fs3.existsSync(this.lockfilePath)) {
1708
+ logger.warn("Lock file was removed externally", "COORDINATION");
1709
+ this.isOwner = false;
1710
+ if (this.checkInterval) {
1711
+ clearInterval(this.checkInterval);
1712
+ this.checkInterval = null;
1713
+ }
1714
+ }
1715
+ }, 5e3);
1716
+ }
1717
+ };
1718
+ var WPAuthCoordinator = class {
1719
+ constructor(serverUrlHash, serverUrl, callbackPort, events) {
1720
+ this.oauthProvider = null;
1721
+ this.isStarted = false;
1722
+ this.serverUrlHash = serverUrlHash;
1723
+ this.serverUrl = serverUrl;
1724
+ this.callbackPort = callbackPort;
1725
+ this.events = events;
1726
+ this.lockManager = new LockfileManager(serverUrlHash);
1727
+ }
1728
+ async start() {
1729
+ if (this.isStarted) {
1730
+ return;
1731
+ }
1732
+ logger.debug("Starting WordPress auth coordinator", "COORDINATION");
1733
+ this.isStarted = true;
1734
+ process.on("exit", () => this.cleanup());
1735
+ process.on("SIGINT", () => this.cleanup());
1736
+ process.on("SIGTERM", () => this.cleanup());
1737
+ process.on("uncaughtException", () => this.cleanup());
1738
+ process.on("unhandledRejection", () => this.cleanup());
1739
+ }
1740
+ async stop() {
1741
+ logger.debug("Stopping WordPress auth coordinator", "COORDINATION");
1742
+ this.cleanup();
1743
+ this.isStarted = false;
1744
+ }
1745
+ async waitForAuth() {
1746
+ if (!this.isStarted) {
1747
+ throw new OAuthError("Auth coordinator not started");
1748
+ }
1749
+ const existingTokens = await getValidTokens(this.serverUrlHash);
1750
+ if (existingTokens) {
1751
+ logger.debug("Found existing valid tokens", "COORDINATION");
1752
+ return existingTokens;
1753
+ }
1754
+ if (this.lockManager.tryAcquire()) {
1755
+ return await this.performAuthentication();
1756
+ } else {
1757
+ return await this.waitForOtherInstanceAuth();
1758
+ }
1759
+ }
1760
+ async performAuthentication() {
1761
+ try {
1762
+ logger.info("Performing OAuth authentication as lock owner", "COORDINATION");
1763
+ if (!this.oauthProvider) {
1764
+ this.oauthProvider = new PersistentWPOAuthClientProvider({
1765
+ serverUrl: this.serverUrl,
1766
+ callbackPort: this.callbackPort,
1767
+ host: CONFIG.OAUTH_HOST,
1768
+ clientId: CONFIG.WP_OAUTH_CLIENT_ID
1769
+ });
1770
+ }
1771
+ await this.oauthProvider.authorize();
1772
+ const tokens = await this.oauthProvider.tokens();
1773
+ if (!tokens) {
1774
+ throw new OAuthError("Authentication completed but no tokens available");
1775
+ }
1776
+ logger.info("Authentication successful, tokens obtained", "COORDINATION");
1777
+ return tokens;
1778
+ } finally {
1779
+ this.lockManager.release();
1780
+ }
1781
+ }
1782
+ async waitForOtherInstanceAuth() {
1783
+ logger.info("Waiting for another instance to complete authentication", "COORDINATION");
1784
+ try {
1785
+ await this.lockManager.waitForRelease();
1786
+ const tokens = await getValidTokens(this.serverUrlHash);
1787
+ if (tokens) {
1788
+ logger.info("Tokens are now available from other instance", "COORDINATION");
1789
+ return tokens;
1790
+ }
1791
+ logger.debug(
1792
+ "No tokens found after waiting, trying to authenticate ourselves",
1793
+ "COORDINATION"
1794
+ );
1795
+ return await this.waitForAuth();
1796
+ } catch (error) {
1797
+ logger.error("Error waiting for other instance auth", "COORDINATION", error);
1798
+ throw error;
1799
+ }
1800
+ }
1801
+ cleanup() {
1802
+ this.lockManager.release();
1803
+ }
1804
+ };
1805
+ var LazyWPAuthCoordinator = class {
1806
+ constructor(serverUrlHash, serverUrl, callbackPort, events) {
1807
+ this.coordinator = null;
1808
+ this.serverUrlHash = serverUrlHash;
1809
+ this.serverUrl = serverUrl;
1810
+ this.callbackPort = callbackPort;
1811
+ this.events = events;
1812
+ }
1813
+ async start() {
1814
+ }
1815
+ async stop() {
1816
+ if (this.coordinator) {
1817
+ await this.coordinator.stop();
1818
+ this.coordinator = null;
1819
+ }
1820
+ }
1821
+ async waitForAuth() {
1822
+ const existingTokens = await getValidTokens(this.serverUrlHash);
1823
+ if (existingTokens) {
1824
+ return existingTokens;
1825
+ }
1826
+ if (!this.coordinator) {
1827
+ this.coordinator = new WPAuthCoordinator(
1828
+ this.serverUrlHash,
1829
+ this.serverUrl,
1830
+ this.callbackPort,
1831
+ this.events
1832
+ );
1833
+ await this.coordinator.start();
1834
+ }
1835
+ return await this.coordinator.waitForAuth();
1836
+ }
1837
+ };
1838
+ function createLazyWPAuthCoordinator(serverUrlHash, serverUrl, callbackPort, events) {
1839
+ return new LazyWPAuthCoordinator(serverUrlHash, serverUrl, callbackPort, events);
1840
+ }
1841
+
1842
+ // src/lib/wordpress-api.ts
1843
+ var mcpOAuthProvider = null;
1844
+ var authCoordinator = null;
1845
+ var globalEvents = null;
1846
+ function validateEnvironment() {
1847
+ const validation = validateConfig();
1848
+ if (!validation.isValid) {
1849
+ throw new AuthError(
1850
+ `Configuration validation failed: ${validation.errors.join(", ")}`,
1851
+ "CONFIG_VALIDATION"
1852
+ );
51
1853
  }
52
1854
  }
53
1855
  function removeTrailingSlash(url) {
54
1856
  return url.replace(/\/+$/, "");
55
1857
  }
1858
+ function constructApiUrl(baseUrl, defaultEndpoint) {
1859
+ const cleanUrl = removeTrailingSlash(baseUrl);
1860
+ try {
1861
+ const urlObj = new URL(cleanUrl);
1862
+ const hasCustomPath = urlObj.pathname && urlObj.pathname !== "/" && urlObj.pathname.length > 0;
1863
+ if (hasCustomPath) {
1864
+ return cleanUrl;
1865
+ } else {
1866
+ return new URL(`/?rest_route=${defaultEndpoint}`, cleanUrl).toString();
1867
+ }
1868
+ } catch (error) {
1869
+ return new URL(`/?rest_route=${defaultEndpoint}`, cleanUrl).toString();
1870
+ }
1871
+ }
1872
+ async function getOAuthTokens() {
1873
+ try {
1874
+ if (!CONFIG.OAUTH_ENABLED) {
1875
+ logger.debug("OAuth is disabled via configuration", "AUTH");
1876
+ return null;
1877
+ }
1878
+ logger.auth("Attempting to get OAuth tokens for WordPress API (MCP-compliant)...");
1879
+ const serverUrl = CONFIG.WP_API_URL;
1880
+ const serverUrlHash = generateServerUrlHash(serverUrl);
1881
+ const existingTokens = await getValidTokens(serverUrlHash);
1882
+ if (existingTokens) {
1883
+ logger.auth("Using existing valid tokens from persistent storage");
1884
+ return existingTokens;
1885
+ }
1886
+ logger.auth("No existing valid tokens found in persistent storage");
1887
+ const isWPCom = isWordPressComSite(serverUrl);
1888
+ const recommendedConfig = getRecommendedOAuthConfig(serverUrl);
1889
+ if (isWPCom) {
1890
+ logger.auth(`Detected WordPress.com site - using ${recommendedConfig.description}`);
1891
+ logger.auth("This will use WordPress.com OAuth2 endpoints for compatibility");
1892
+ } else {
1893
+ logger.auth(`Detected self-hosted WordPress site - using ${recommendedConfig.description}`);
1894
+ logger.auth("This will trigger the MCP-compliant OAuth 2.1 authentication flow");
1895
+ }
1896
+ logger.auth("Your browser should open automatically for authentication");
1897
+ const shouldUseMCPFlow = !isWPCom && CONFIG.OAUTH_FLOW_TYPE === "authorization_code" && CONFIG.OAUTH_USE_PKCE;
1898
+ if (shouldUseMCPFlow) {
1899
+ if (!mcpOAuthProvider) {
1900
+ mcpOAuthProvider = new MCPOAuthProvider({
1901
+ serverUrl,
1902
+ clientId: CONFIG.WP_OAUTH_CLIENT_ID,
1903
+ scopes: ["read", "write"]
1904
+ });
1905
+ }
1906
+ logger.auth("Using MCP-compliant OAuth 2.1 authorization code flow with PKCE");
1907
+ await mcpOAuthProvider.authorize();
1908
+ const tokens = await mcpOAuthProvider.tokens();
1909
+ if (tokens) {
1910
+ logger.auth("MCP OAuth 2.1 tokens obtained for WordPress API access");
1911
+ return tokens;
1912
+ } else {
1913
+ logger.warn("No tokens available after MCP OAuth 2.1 authentication", "AUTH");
1914
+ return null;
1915
+ }
1916
+ } else {
1917
+ if (isWPCom) {
1918
+ logger.auth("Using WordPress.com OAuth2 provider for full compatibility");
1919
+ } else {
1920
+ logger.warn(
1921
+ "Using legacy OAuth provider. Consider upgrading to OAuth 2.1 for MCP compliance",
1922
+ "AUTH"
1923
+ );
1924
+ }
1925
+ if (!authCoordinator) {
1926
+ if (!globalEvents) {
1927
+ globalEvents = new EventEmitter3();
1928
+ }
1929
+ authCoordinator = createLazyWPAuthCoordinator(
1930
+ serverUrlHash,
1931
+ serverUrl,
1932
+ CONFIG.OAUTH_CALLBACK_PORT,
1933
+ globalEvents
1934
+ );
1935
+ }
1936
+ logger.auth("Starting legacy authentication via coordinator...");
1937
+ try {
1938
+ const tokens = await authCoordinator.waitForAuth();
1939
+ if (tokens) {
1940
+ logger.auth("Legacy tokens obtained for WordPress API access");
1941
+ return tokens;
1942
+ } else {
1943
+ logger.warn("No tokens available after legacy authentication", "AUTH");
1944
+ return null;
1945
+ }
1946
+ } catch (authError) {
1947
+ logger.error("Legacy authentication via coordinator failed", "AUTH", authError);
1948
+ throw authError;
1949
+ }
1950
+ }
1951
+ } catch (error) {
1952
+ logger.error("Error getting OAuth tokens", "AUTH", error);
1953
+ return null;
1954
+ }
1955
+ }
56
1956
  async function wpRequest(params = { method: "init" }) {
57
1957
  validateEnvironment();
58
1958
  const endpoint = "/wp/v2/wpmcp";
59
1959
  const method = "POST";
60
- const baseUrl = removeTrailingSlash(process.env.WP_API_URL);
61
- log(`Request method: ${params.method || "init"}`);
62
- log(`Request args: ${JSON.stringify(params.args || {})}`);
63
- let authHeader;
64
- if (process.env.JWT_TOKEN) {
65
- authHeader = `Bearer ${process.env.JWT_TOKEN}`;
66
- log(`Using JWT token authentication`);
67
- log(`Token length: ${process.env.JWT_TOKEN.length}`);
68
- } else {
1960
+ logger.api(`Request method: ${params.method || "init"}`);
1961
+ logger.debug(`Request args: ${JSON.stringify(params.args || {})}`, "API");
1962
+ let authHeader = "";
1963
+ if (CONFIG.JWT_TOKEN) {
1964
+ authHeader = `Bearer ${CONFIG.JWT_TOKEN}`;
1965
+ logger.auth("Using JWT token authentication");
1966
+ logger.debug(`Token length: ${CONFIG.JWT_TOKEN.length}`, "AUTH");
1967
+ } else if (CONFIG.OAUTH_ENABLED) {
1968
+ logger.auth("OAuth is the primary authentication method - attempting to get tokens...");
1969
+ const oauthTokens = await getOAuthTokens();
1970
+ if (oauthTokens) {
1971
+ authHeader = `Bearer ${oauthTokens.access_token}`;
1972
+ logger.auth("Using OAuth token authentication for WordPress API");
1973
+ logger.debug(`Token length: ${oauthTokens.access_token.length}`, "AUTH");
1974
+ } else {
1975
+ logger.warn("OAuth authentication failed, trying Basic Auth fallback", "AUTH");
1976
+ }
1977
+ }
1978
+ if (!authHeader && CONFIG.WP_API_USERNAME && CONFIG.WP_API_PASSWORD) {
69
1979
  let username;
70
1980
  let password;
71
1981
  if (params.method === "tools/call" && params.args && params.args.tool && params.args.tool.startsWith("wc_reports_")) {
72
- username = process.env.WOO_CUSTOMER_KEY;
73
- password = process.env.WOO_CUSTOMER_SECRET;
74
- log(`Using WooCommerce credentials for tool: ${params.args.tool}`);
1982
+ username = CONFIG.WOO_CUSTOMER_KEY;
1983
+ password = CONFIG.WOO_CUSTOMER_SECRET;
1984
+ logger.auth(`Using WooCommerce credentials for tool: ${params.args.tool}`);
75
1985
  if (!username || !password) {
76
- throw new Error(
77
- "Missing WooCommerce credentials. Please set WOO_CUSTOMER_KEY and WOO_CUSTOMER_SECRET environment variables."
1986
+ throw new AuthError(
1987
+ "Missing WooCommerce credentials. Please set WOO_CUSTOMER_KEY and WOO_CUSTOMER_SECRET environment variables.",
1988
+ "WOOCOMMERCE_CREDENTIALS"
78
1989
  );
79
1990
  }
80
1991
  } else {
81
- username = process.env.WP_API_USERNAME;
82
- password = process.env.WP_API_PASSWORD;
83
- log(`Using WordPress credentials for method: ${params.method || "init"}`);
1992
+ username = CONFIG.WP_API_USERNAME;
1993
+ password = CONFIG.WP_API_PASSWORD;
1994
+ logger.auth(`Using WordPress Basic Auth for method: ${params.method || "init"}`);
84
1995
  }
85
- log(`Username length: ${username ? username.length : 0}`);
86
- log(`Password length: ${password ? password.length : 0}`);
1996
+ logger.debug(`Username length: ${username ? username.length : 0}`, "AUTH");
1997
+ logger.debug(`Password length: ${password ? password.length : 0}`, "AUTH");
87
1998
  const auth = Buffer.from(`${username}:${password}`).toString("base64");
88
1999
  authHeader = `Basic ${auth}`;
89
- log(`Auth header length: ${auth.length}`);
2000
+ logger.debug(`Auth header length: ${auth.length}`, "AUTH");
90
2001
  }
91
- log(`Environment: ${process.env.NODE_ENV || "development"}`);
92
- log(`API URL: ${baseUrl}`);
93
- const url = new URL(`/?rest_route=${endpoint}`, baseUrl).toString();
94
- log(`Requesting URL: ${url}`);
2002
+ if (!authHeader) {
2003
+ throw new AuthError(
2004
+ "No authentication method available. Please configure JWT_TOKEN, OAuth, or Basic Auth (WP_API_USERNAME+WP_API_PASSWORD).",
2005
+ "NO_AUTH_METHOD"
2006
+ );
2007
+ }
2008
+ logger.debug(`Environment: ${CONFIG.NODE_ENV}`, "API");
2009
+ logger.debug(`Base API URL: ${CONFIG.WP_API_URL}`, "API");
2010
+ const url = constructApiUrl(CONFIG.WP_API_URL, endpoint);
2011
+ logger.debug(`Final requesting URL: ${url}`, "API");
95
2012
  const headers = {
96
2013
  Authorization: authHeader,
97
2014
  "Content-Type": "application/json"
@@ -102,20 +2019,29 @@ async function wpRequest(params = { method: "init" }) {
102
2019
  body: JSON.stringify(params)
103
2020
  };
104
2021
  try {
105
- log(`Sending request to WordPress API...`);
2022
+ logger.api("Sending request to WordPress API...");
106
2023
  const response = await fetch(url, fetchOptions);
107
- log(`Response status: ${response.status}`);
2024
+ logger.debug(`Response status: ${response.status}`, "API");
108
2025
  if (!response.ok) {
109
2026
  const errorText = await response.text();
110
- log(`Error response: ${errorText}`);
111
- throw new Error(`WordPress API error (${response.status}): ${errorText}`);
2027
+ logger.error(`API error response: ${errorText}`, "API");
2028
+ throw new APIError(
2029
+ `WordPress API error (${response.status}): ${errorText}`,
2030
+ response.status,
2031
+ url,
2032
+ errorText
2033
+ );
112
2034
  }
113
2035
  const responseData = await response.json();
114
- log(`Response received successfully`);
2036
+ logger.api("Response received successfully");
115
2037
  return responseData;
116
2038
  } catch (error) {
117
- log(`Error in wpRequest: ${error instanceof Error ? error.message : String(error)}`);
118
- throw error;
2039
+ if (error instanceof APIError) {
2040
+ throw error;
2041
+ }
2042
+ const errorMessage = error instanceof Error ? error.message : String(error);
2043
+ logger.error(`Error in wpRequest: ${errorMessage}`, "API");
2044
+ throw new APIError(errorMessage, 0, url);
119
2045
  }
120
2046
  }
121
2047
 
@@ -137,18 +2063,29 @@ import {
137
2063
  var requiredNodeVersion = 22;
138
2064
  var currentNodeVersion = parseInt(process.version.slice(1).split(".")[0]);
139
2065
  if (currentNodeVersion < requiredNodeVersion) {
140
- log(`Error: This application requires Node.js version ${requiredNodeVersion} or higher.`);
141
- log(`Current version: ${process.version}`);
2066
+ logger.error(
2067
+ `This application requires Node.js version ${requiredNodeVersion} or higher.`,
2068
+ "SYSTEM"
2069
+ );
2070
+ logger.error(`Current version: ${process.version}`, "SYSTEM");
142
2071
  process.exit(1);
143
2072
  }
144
2073
  if (typeof globalThis.fetch !== "function") {
145
- log(
146
- "Error: This application requires the fetch API, which is not available in your Node.js environment."
2074
+ logger.error(
2075
+ "This application requires the fetch API, which is not available in your Node.js environment.",
2076
+ "SYSTEM"
147
2077
  );
148
- log("Please ensure you are using Node.js 22 or later, or install node-fetch.");
2078
+ logger.error("Please ensure you are using Node.js 22 or later, or install node-fetch.", "SYSTEM");
149
2079
  process.exit(1);
150
2080
  }
151
2081
  async function WordPressProxy() {
2082
+ logger.info("Starting WordPress MCP Proxy with enhanced authentication", "PROXY");
2083
+ try {
2084
+ await cleanupExpiredTokens();
2085
+ } catch (error) {
2086
+ logger.warn("Error cleaning up expired tokens", "PROXY", error);
2087
+ }
2088
+ logger.info("Initializing connection to WordPress API...", "PROXY");
152
2089
  const init = await wpRequest({ method: "initialize" });
153
2090
  const server = new Server(
154
2091
  {
@@ -161,16 +2098,20 @@ async function WordPressProxy() {
161
2098
  }
162
2099
  );
163
2100
  const withLogging = (schema, handler) => async (request) => {
164
- log(`Received ${schema} request:`, JSON.stringify(request));
165
- const response = await handler(request);
166
- log(`${schema} response:`, JSON.stringify(response));
167
- return response;
2101
+ logger.debug(`Received ${schema} request`, "MCP", request);
2102
+ try {
2103
+ const response = await handler(request);
2104
+ logger.debug(`${schema} response sent`, "MCP");
2105
+ return response;
2106
+ } catch (error) {
2107
+ logger.error(`Error handling ${schema} request`, "MCP", error);
2108
+ throw error;
2109
+ }
168
2110
  };
169
2111
  server.setRequestHandler(
170
2112
  ListToolsRequestSchema,
171
2113
  withLogging("ListTools", async (request) => {
172
- log("Processing ListToolsRequest");
173
- log(JSON.stringify(request));
2114
+ logger.debug("Processing ListToolsRequest", "MCP");
174
2115
  const response = await wpRequest({
175
2116
  method: "tools/list",
176
2117
  cursor: request.params?.cursor
@@ -181,8 +2122,7 @@ async function WordPressProxy() {
181
2122
  server.setRequestHandler(
182
2123
  CallToolRequestSchema,
183
2124
  withLogging("CallTool", async (request) => {
184
- log("Processing CallToolRequest");
185
- log(JSON.stringify(request));
2125
+ logger.debug("Processing CallToolRequest", "MCP");
186
2126
  const response = await wpRequest({
187
2127
  method: "tools/call",
188
2128
  name: request.params.name,
@@ -194,8 +2134,7 @@ async function WordPressProxy() {
194
2134
  server.setRequestHandler(
195
2135
  ListResourcesRequestSchema,
196
2136
  withLogging("ListResources", async (request) => {
197
- log("Processing ListResourcesRequest");
198
- log(JSON.stringify(request));
2137
+ logger.debug("Processing ListResourcesRequest", "MCP");
199
2138
  const response = await wpRequest({
200
2139
  method: "resources/list",
201
2140
  cursor: request.params?.cursor
@@ -206,8 +2145,7 @@ async function WordPressProxy() {
206
2145
  server.setRequestHandler(
207
2146
  ListResourceTemplatesRequestSchema,
208
2147
  withLogging("ListResourceTemplates", async (request) => {
209
- log("Processing ListResourceTemplatesRequest");
210
- log(JSON.stringify(request));
2148
+ logger.debug("Processing ListResourceTemplatesRequest", "MCP");
211
2149
  const response = await wpRequest({
212
2150
  method: "resources/templates/list",
213
2151
  cursor: request.params?.cursor
@@ -218,8 +2156,7 @@ async function WordPressProxy() {
218
2156
  server.setRequestHandler(
219
2157
  ReadResourceRequestSchema,
220
2158
  withLogging("ReadResource", async (request) => {
221
- log("Processing ReadResourceRequest");
222
- log(JSON.stringify(request));
2159
+ logger.debug("Processing ReadResourceRequest", "MCP");
223
2160
  const response = await wpRequest({
224
2161
  method: "resources/read",
225
2162
  uri: request.params.uri
@@ -230,8 +2167,7 @@ async function WordPressProxy() {
230
2167
  server.setRequestHandler(
231
2168
  SubscribeRequestSchema,
232
2169
  withLogging("Subscribe", async (request) => {
233
- log("Processing SubscribeRequest");
234
- log(JSON.stringify(request));
2170
+ logger.debug("Processing SubscribeRequest", "MCP");
235
2171
  const response = await wpRequest({
236
2172
  method: "resources/subscribe",
237
2173
  uri: request.params.uri
@@ -242,8 +2178,7 @@ async function WordPressProxy() {
242
2178
  server.setRequestHandler(
243
2179
  UnsubscribeRequestSchema,
244
2180
  withLogging("Unsubscribe", async (request) => {
245
- log("Processing UnsubscribeRequest");
246
- log(JSON.stringify(request));
2181
+ logger.debug("Processing UnsubscribeRequest", "MCP");
247
2182
  const response = await wpRequest({
248
2183
  method: "resources/unsubscribe",
249
2184
  uri: request.params.uri
@@ -254,8 +2189,7 @@ async function WordPressProxy() {
254
2189
  server.setRequestHandler(
255
2190
  ListPromptsRequestSchema,
256
2191
  withLogging("ListPrompts", async (request) => {
257
- log("Processing ListPromptsRequest");
258
- log(JSON.stringify(request));
2192
+ logger.debug("Processing ListPromptsRequest", "MCP");
259
2193
  const response = await wpRequest({
260
2194
  method: "prompts/list",
261
2195
  cursor: request.params?.cursor
@@ -266,8 +2200,7 @@ async function WordPressProxy() {
266
2200
  server.setRequestHandler(
267
2201
  GetPromptRequestSchema,
268
2202
  withLogging("GetPrompt", async (request) => {
269
- log("Processing GetPromptRequest");
270
- log(JSON.stringify(request));
2203
+ logger.debug("Processing GetPromptRequest", "MCP");
271
2204
  const response = await wpRequest({
272
2205
  method: "prompts/get",
273
2206
  name: request.params.name,
@@ -279,8 +2212,7 @@ async function WordPressProxy() {
279
2212
  server.setRequestHandler(
280
2213
  SetLevelRequestSchema,
281
2214
  withLogging("SetLevel", async (request) => {
282
- log("Processing SetLevelRequest");
283
- log(JSON.stringify(request));
2215
+ logger.debug("Processing SetLevelRequest", "MCP");
284
2216
  const response = await wpRequest({
285
2217
  method: "logging/setLevel",
286
2218
  level: request.params.level
@@ -291,8 +2223,7 @@ async function WordPressProxy() {
291
2223
  server.setRequestHandler(
292
2224
  CompleteRequestSchema,
293
2225
  withLogging("Complete", async (request) => {
294
- log("Processing CompleteRequest");
295
- log(JSON.stringify(request));
2226
+ logger.debug("Processing CompleteRequest", "MCP");
296
2227
  const response = await wpRequest({
297
2228
  method: "completion/complete",
298
2229
  ref: request.params.ref,
@@ -304,8 +2235,7 @@ async function WordPressProxy() {
304
2235
  server.setRequestHandler(
305
2236
  ListRootsRequestSchema,
306
2237
  withLogging("ListRoots", async (request) => {
307
- log("Processing ListRootsRequest");
308
- log(JSON.stringify(request));
2238
+ logger.debug("Processing ListRootsRequest", "MCP");
309
2239
  const response = await wpRequest({
310
2240
  method: "roots/list"
311
2241
  });
@@ -314,9 +2244,9 @@ async function WordPressProxy() {
314
2244
  );
315
2245
  const transport = new StdioServerTransport();
316
2246
  server.connect(transport).then(() => {
317
- log("MCP server connected to transport successfully");
2247
+ logger.info("MCP server connected to transport successfully", "PROXY");
318
2248
  }).catch((error) => {
319
- log(`Error starting MCP server:`, error);
2249
+ logger.error("Error starting MCP server", "PROXY", error);
320
2250
  process.exit(1);
321
2251
  });
322
2252
  }