@studiometa/forge-mcp 0.4.4 → 0.4.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/auth.js CHANGED
@@ -1,3 +1,4 @@
1
+ //#region src/auth.ts
1
2
  /**
2
3
  * Try to decode a base64-encoded token.
3
4
  * Returns the decoded string if re-encoding produces the original, null otherwise.
@@ -49,6 +50,7 @@ function parseAuthHeader(authHeader) {
49
50
  }
50
51
  return { apiToken: token };
51
52
  }
53
+ //#endregion
52
54
  export { parseAuthHeader };
53
55
 
54
56
  //# sourceMappingURL=auth.js.map
package/dist/auth.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"auth.js","names":[],"sources":["../src/auth.ts"],"sourcesContent":["/**\n * Authentication utilities for Forge MCP HTTP server\n *\n * Supports two token formats:\n * 1. Raw Forge API token (backwards compatible)\n * 2. Base64-encoded Forge API token (from OAuth flow)\n *\n * Detection heuristic: decode the token from base64, then re-encode.\n * If the re-encoded string matches the original, it's a real base64 token\n * and we use the decoded value. Otherwise, we treat it as a raw token.\n */\n\nexport interface ForgeCredentials {\n apiToken: string;\n organizationSlug?: string;\n}\n\n/**\n * Try to decode a base64-encoded token.\n * Returns the decoded string if re-encoding produces the original, null otherwise.\n *\n * Buffer.from(str, 'base64') never throws — it silently ignores invalid chars.\n * So this function cannot fail, no try/catch needed.\n */\nfunction tryDecodeBase64(token: string): string | null {\n const decoded = Buffer.from(token, \"base64\").toString(\"utf-8\");\n\n // decoded must be non-empty and different from input\n if (!decoded || decoded === token) {\n return null;\n }\n\n // Re-encode and compare — if it roundtrips, it was genuinely base64\n const reEncoded = Buffer.from(decoded).toString(\"base64\");\n if (reEncoded === token) {\n return decoded;\n }\n\n return null;\n}\n\nfunction tryParseCredentials(decoded: string): ForgeCredentials | null {\n try {\n const parsed: unknown = JSON.parse(decoded);\n if (\n typeof parsed === \"object\" &&\n parsed !== null &&\n \"apiToken\" in parsed &&\n typeof (parsed as Record<string, unknown>).apiToken === \"string\"\n ) {\n const obj = parsed as Record<string, unknown>;\n return {\n apiToken: obj.apiToken as string,\n organizationSlug:\n typeof obj.organizationSlug === \"string\" ? obj.organizationSlug : undefined,\n };\n }\n } catch {\n // Not valid JSON, fall through\n }\n return null;\n}\n\n/**\n * Parse Bearer token containing Forge API credentials.\n *\n * Token formats:\n * - Raw Forge API token (e.g., \"Bearer my-api-token\")\n * - Base64-encoded token from OAuth flow (e.g., \"Bearer base64(apiToken)\")\n * - Base64-encoded JSON from OAuth flow (e.g., \"Bearer base64({apiToken, organizationSlug})\")\n *\n * @param authHeader - Authorization header value (e.g., \"Bearer <token>\")\n * @returns Parsed credentials or null if invalid\n */\nexport function parseAuthHeader(authHeader: string | undefined | null): ForgeCredentials | null {\n if (!authHeader) {\n return null;\n }\n\n const match = authHeader.match(/^Bearer\\s+(.+)$/i);\n if (!match) {\n return null;\n }\n\n const token = match[1].trim();\n\n if (!token) {\n return null;\n }\n\n // Try to decode as base64 (OAuth access token)\n const decoded = tryDecodeBase64(token);\n if (decoded) {\n const credentials = tryParseCredentials(decoded);\n if (credentials) {\n return credentials;\n }\n\n return { apiToken: decoded };\n }\n\n // Treat as raw Forge API token (backwards compatible)\n return { apiToken: token };\n}\n"],"mappings":";;;;;;;AAwBA,SAAS,gBAAgB,OAA8B;CACrD,MAAM,UAAU,OAAO,KAAK,OAAO,SAAS,CAAC,SAAS,QAAQ;AAG9D,KAAI,CAAC,WAAW,YAAY,MAC1B,QAAO;AAKT,KADkB,OAAO,KAAK,QAAQ,CAAC,SAAS,SAAS,KACvC,MAChB,QAAO;AAGT,QAAO;;AAGT,SAAS,oBAAoB,SAA0C;AACrE,KAAI;EACF,MAAM,SAAkB,KAAK,MAAM,QAAQ;AAC3C,MACE,OAAO,WAAW,YAClB,WAAW,QACX,cAAc,UACd,OAAQ,OAAmC,aAAa,UACxD;GACA,MAAM,MAAM;AACZ,UAAO;IACL,UAAU,IAAI;IACd,kBACE,OAAO,IAAI,qBAAqB,WAAW,IAAI,mBAAmB,KAAA;IACrE;;SAEG;AAGR,QAAO;;;;;;;;;;;;;AAcT,SAAgB,gBAAgB,YAAgE;AAC9F,KAAI,CAAC,WACH,QAAO;CAGT,MAAM,QAAQ,WAAW,MAAM,mBAAmB;AAClD,KAAI,CAAC,MACH,QAAO;CAGT,MAAM,QAAQ,MAAM,GAAG,MAAM;AAE7B,KAAI,CAAC,MACH,QAAO;CAIT,MAAM,UAAU,gBAAgB,MAAM;AACtC,KAAI,SAAS;EACX,MAAM,cAAc,oBAAoB,QAAQ;AAChD,MAAI,YACF,QAAO;AAGT,SAAO,EAAE,UAAU,SAAS;;AAI9B,QAAO,EAAE,UAAU,OAAO"}
1
+ {"version":3,"file":"auth.js","names":[],"sources":["../src/auth.ts"],"sourcesContent":["/**\n * Authentication utilities for Forge MCP HTTP server\n *\n * Supports two token formats:\n * 1. Raw Forge API token (backwards compatible)\n * 2. Base64-encoded Forge API token (from OAuth flow)\n *\n * Detection heuristic: decode the token from base64, then re-encode.\n * If the re-encoded string matches the original, it's a real base64 token\n * and we use the decoded value. Otherwise, we treat it as a raw token.\n */\n\nexport interface ForgeCredentials {\n apiToken: string;\n organizationSlug?: string;\n}\n\n/**\n * Try to decode a base64-encoded token.\n * Returns the decoded string if re-encoding produces the original, null otherwise.\n *\n * Buffer.from(str, 'base64') never throws — it silently ignores invalid chars.\n * So this function cannot fail, no try/catch needed.\n */\nfunction tryDecodeBase64(token: string): string | null {\n const decoded = Buffer.from(token, \"base64\").toString(\"utf-8\");\n\n // decoded must be non-empty and different from input\n if (!decoded || decoded === token) {\n return null;\n }\n\n // Re-encode and compare — if it roundtrips, it was genuinely base64\n const reEncoded = Buffer.from(decoded).toString(\"base64\");\n if (reEncoded === token) {\n return decoded;\n }\n\n return null;\n}\n\nfunction tryParseCredentials(decoded: string): ForgeCredentials | null {\n try {\n const parsed: unknown = JSON.parse(decoded);\n if (\n typeof parsed === \"object\" &&\n parsed !== null &&\n \"apiToken\" in parsed &&\n typeof (parsed as Record<string, unknown>).apiToken === \"string\"\n ) {\n const obj = parsed as Record<string, unknown>;\n return {\n apiToken: obj.apiToken as string,\n organizationSlug:\n typeof obj.organizationSlug === \"string\" ? obj.organizationSlug : undefined,\n };\n }\n } catch {\n // Not valid JSON, fall through\n }\n return null;\n}\n\n/**\n * Parse Bearer token containing Forge API credentials.\n *\n * Token formats:\n * - Raw Forge API token (e.g., \"Bearer my-api-token\")\n * - Base64-encoded token from OAuth flow (e.g., \"Bearer base64(apiToken)\")\n * - Base64-encoded JSON from OAuth flow (e.g., \"Bearer base64({apiToken, organizationSlug})\")\n *\n * @param authHeader - Authorization header value (e.g., \"Bearer <token>\")\n * @returns Parsed credentials or null if invalid\n */\nexport function parseAuthHeader(authHeader: string | undefined | null): ForgeCredentials | null {\n if (!authHeader) {\n return null;\n }\n\n const match = authHeader.match(/^Bearer\\s+(.+)$/i);\n if (!match) {\n return null;\n }\n\n const token = match[1].trim();\n\n if (!token) {\n return null;\n }\n\n // Try to decode as base64 (OAuth access token)\n const decoded = tryDecodeBase64(token);\n if (decoded) {\n const credentials = tryParseCredentials(decoded);\n if (credentials) {\n return credentials;\n }\n\n return { apiToken: decoded };\n }\n\n // Treat as raw Forge API token (backwards compatible)\n return { apiToken: token };\n}\n"],"mappings":";;;;;;;;AAwBA,SAAS,gBAAgB,OAA8B;CACrD,MAAM,UAAU,OAAO,KAAK,OAAO,QAAQ,CAAC,CAAC,SAAS,OAAO;CAG7D,IAAI,CAAC,WAAW,YAAY,OAC1B,OAAO;CAKT,IADkB,OAAO,KAAK,OAAO,CAAC,CAAC,SAAS,QAC5C,MAAc,OAChB,OAAO;CAGT,OAAO;AACT;AAEA,SAAS,oBAAoB,SAA0C;CACrE,IAAI;EACF,MAAM,SAAkB,KAAK,MAAM,OAAO;EAC1C,IACE,OAAO,WAAW,YAClB,WAAW,QACX,cAAc,UACd,OAAQ,OAAmC,aAAa,UACxD;GACA,MAAM,MAAM;GACZ,OAAO;IACL,UAAU,IAAI;IACd,kBACE,OAAO,IAAI,qBAAqB,WAAW,IAAI,mBAAmB,KAAA;GACtE;EACF;CACF,QAAQ,CAER;CACA,OAAO;AACT;;;;;;;;;;;;AAaA,SAAgB,gBAAgB,YAAgE;CAC9F,IAAI,CAAC,YACH,OAAO;CAGT,MAAM,QAAQ,WAAW,MAAM,kBAAkB;CACjD,IAAI,CAAC,OACH,OAAO;CAGT,MAAM,QAAQ,MAAM,EAAE,CAAC,KAAK;CAE5B,IAAI,CAAC,OACH,OAAO;CAIT,MAAM,UAAU,gBAAgB,KAAK;CACrC,IAAI,SAAS;EACX,MAAM,cAAc,oBAAoB,OAAO;EAC/C,IAAI,aACF,OAAO;EAGT,OAAO,EAAE,UAAU,QAAQ;CAC7B;CAGA,OAAO,EAAE,UAAU,MAAM;AAC3B"}
package/dist/crypto.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { createCipheriv, createDecipheriv, randomBytes, scryptSync } from "node:crypto";
2
+ //#region src/crypto.ts
2
3
  /**
3
4
  * Cryptographic utilities for stateless OAuth tokens
4
5
  *
@@ -63,9 +64,9 @@ function decrypt(ciphertext, secret = getSecret()) {
63
64
  try {
64
65
  const combined = Buffer.from(ciphertext, "base64url");
65
66
  const salt = combined.subarray(0, SALT_LENGTH);
66
- const iv = combined.subarray(SALT_LENGTH, SALT_LENGTH + IV_LENGTH);
67
- const authTag = combined.subarray(SALT_LENGTH + IV_LENGTH, SALT_LENGTH + IV_LENGTH + AUTH_TAG_LENGTH);
68
- const encrypted = combined.subarray(SALT_LENGTH + IV_LENGTH + AUTH_TAG_LENGTH);
67
+ const iv = combined.subarray(SALT_LENGTH, 28);
68
+ const authTag = combined.subarray(28, 44);
69
+ const encrypted = combined.subarray(44);
69
70
  const decipher = createDecipheriv(ALGORITHM, deriveKey(secret, salt), iv, { authTagLength: AUTH_TAG_LENGTH });
70
71
  decipher.setAuthTag(authTag);
71
72
  return Buffer.concat([decipher.update(encrypted), decipher.final()]).toString("utf8");
@@ -106,6 +107,7 @@ function decodeAuthCode(code) {
106
107
  codeChallengeMethod
107
108
  };
108
109
  }
110
+ //#endregion
109
111
  export { createAuthCode, decodeAuthCode, decrypt, encrypt, getSecret };
110
112
 
111
113
  //# sourceMappingURL=crypto.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"crypto.js","names":[],"sources":["../src/crypto.ts"],"sourcesContent":["/**\n * Cryptographic utilities for stateless OAuth tokens\n *\n * Uses AES-256-GCM for authenticated encryption.\n * The authorization code contains encrypted credentials that can be\n * decrypted without server-side storage.\n */\n\nimport { createCipheriv, createDecipheriv, randomBytes, scryptSync } from \"node:crypto\";\n\nconst ALGORITHM = \"aes-256-gcm\";\nconst IV_LENGTH = 12; // GCM recommended IV length\nconst AUTH_TAG_LENGTH = 16;\nconst SALT_LENGTH = 16;\n\n/**\n * Derive a 256-bit key from a password using scrypt\n */\nfunction deriveKey(password: string, salt: Buffer): Buffer {\n return scryptSync(password, salt, 32);\n}\n\n/**\n * Get the encryption secret from environment or generate a default.\n * In production, OAUTH_SECRET should always be set.\n */\nexport function getSecret(): string {\n const secret = process.env.OAUTH_SECRET;\n if (!secret) {\n console.warn(\n \"WARNING: OAUTH_SECRET not set. Using default secret. Set OAUTH_SECRET in production!\",\n );\n return \"forge-mcp-default-secret-change-me\";\n }\n return secret;\n}\n\n/**\n * Encrypt data using AES-256-GCM\n *\n * Output format: base64url(salt + iv + authTag + ciphertext)\n *\n * @param plaintext - Data to encrypt\n * @param secret - Encryption secret (defaults to OAUTH_SECRET env var)\n * @returns Base64url-encoded encrypted data\n */\nexport function encrypt(plaintext: string, secret: string = getSecret()): string {\n const salt = randomBytes(SALT_LENGTH);\n const key = deriveKey(secret, salt);\n const iv = randomBytes(IV_LENGTH);\n\n const cipher = createCipheriv(ALGORITHM, key, iv);\n const encrypted = Buffer.concat([cipher.update(plaintext, \"utf8\"), cipher.final()]);\n const authTag = cipher.getAuthTag();\n\n // Combine: salt + iv + authTag + ciphertext\n const combined = Buffer.concat([salt, iv, authTag, encrypted]);\n\n return combined.toString(\"base64url\");\n}\n\n/**\n * Decrypt data encrypted with encrypt()\n *\n * @param ciphertext - Base64url-encoded encrypted data\n * @param secret - Encryption secret (defaults to OAUTH_SECRET env var)\n * @returns Decrypted plaintext\n * @throws Error if decryption fails (invalid data or wrong secret)\n */\nexport function decrypt(ciphertext: string, secret: string = getSecret()): string {\n try {\n const combined = Buffer.from(ciphertext, \"base64url\");\n\n // Extract components\n const salt = combined.subarray(0, SALT_LENGTH);\n const iv = combined.subarray(SALT_LENGTH, SALT_LENGTH + IV_LENGTH);\n const authTag = combined.subarray(\n SALT_LENGTH + IV_LENGTH,\n SALT_LENGTH + IV_LENGTH + AUTH_TAG_LENGTH,\n );\n const encrypted = combined.subarray(SALT_LENGTH + IV_LENGTH + AUTH_TAG_LENGTH);\n\n const key = deriveKey(secret, salt);\n\n const decipher = createDecipheriv(ALGORITHM, key, iv, { authTagLength: AUTH_TAG_LENGTH });\n decipher.setAuthTag(authTag);\n\n const decrypted = Buffer.concat([decipher.update(encrypted), decipher.final()]);\n\n return decrypted.toString(\"utf8\");\n } catch {\n throw new Error(\"Decryption failed: invalid token or secret\");\n }\n}\n\n/**\n * Authorization code payload structure\n */\nexport interface AuthCodePayload {\n apiToken: string;\n organizationSlug?: string;\n codeChallenge?: string;\n codeChallengeMethod?: string;\n}\n\n/**\n * Create an encrypted authorization code containing credentials and PKCE challenge\n *\n * @param credentials - Object with apiToken and optional PKCE params\n * @param expiresInSeconds - Code expiration time (default: 5 minutes)\n * @returns Encrypted authorization code\n */\nexport function createAuthCode(\n credentials: AuthCodePayload,\n expiresInSeconds: number = 300,\n): string {\n const payload = {\n ...credentials,\n exp: Date.now() + expiresInSeconds * 1000,\n };\n return encrypt(JSON.stringify(payload));\n}\n\n/**\n * Decode and validate an authorization code\n *\n * @param code - Encrypted authorization code\n * @returns Decoded payload with credentials and PKCE challenge\n * @throws Error if code is invalid or expired\n */\nexport function decodeAuthCode(code: string): AuthCodePayload {\n const payload = JSON.parse(decrypt(code));\n\n if (payload.exp && Date.now() > payload.exp) {\n throw new Error(\"Authorization code expired\");\n }\n\n const { apiToken, organizationSlug, codeChallenge, codeChallengeMethod } = payload;\n\n if (!apiToken) {\n throw new Error(\"Invalid authorization code: missing credentials\");\n }\n\n return { apiToken, organizationSlug, codeChallenge, codeChallengeMethod };\n}\n"],"mappings":";;;;;;;;AAUA,IAAM,YAAY;AAClB,IAAM,YAAY;AAClB,IAAM,kBAAkB;AACxB,IAAM,cAAc;;;;AAKpB,SAAS,UAAU,UAAkB,MAAsB;AACzD,QAAO,WAAW,UAAU,MAAM,GAAG;;;;;;AAOvC,SAAgB,YAAoB;CAClC,MAAM,SAAS,QAAQ,IAAI;AAC3B,KAAI,CAAC,QAAQ;AACX,UAAQ,KACN,uFACD;AACD,SAAO;;AAET,QAAO;;;;;;;;;;;AAYT,SAAgB,QAAQ,WAAmB,SAAiB,WAAW,EAAU;CAC/E,MAAM,OAAO,YAAY,YAAY;CACrC,MAAM,MAAM,UAAU,QAAQ,KAAK;CACnC,MAAM,KAAK,YAAY,UAAU;CAEjC,MAAM,SAAS,eAAe,WAAW,KAAK,GAAG;CACjD,MAAM,YAAY,OAAO,OAAO,CAAC,OAAO,OAAO,WAAW,OAAO,EAAE,OAAO,OAAO,CAAC,CAAC;CACnF,MAAM,UAAU,OAAO,YAAY;AAKnC,QAFiB,OAAO,OAAO;EAAC;EAAM;EAAI;EAAS;EAAU,CAAC,CAE9C,SAAS,YAAY;;;;;;;;;;AAWvC,SAAgB,QAAQ,YAAoB,SAAiB,WAAW,EAAU;AAChF,KAAI;EACF,MAAM,WAAW,OAAO,KAAK,YAAY,YAAY;EAGrD,MAAM,OAAO,SAAS,SAAS,GAAG,YAAY;EAC9C,MAAM,KAAK,SAAS,SAAS,aAAa,cAAc,UAAU;EAClE,MAAM,UAAU,SAAS,SACvB,cAAc,WACd,cAAc,YAAY,gBAC3B;EACD,MAAM,YAAY,SAAS,SAAS,cAAc,YAAY,gBAAgB;EAI9E,MAAM,WAAW,iBAAiB,WAFtB,UAAU,QAAQ,KAAK,EAEe,IAAI,EAAE,eAAe,iBAAiB,CAAC;AACzF,WAAS,WAAW,QAAQ;AAI5B,SAFkB,OAAO,OAAO,CAAC,SAAS,OAAO,UAAU,EAAE,SAAS,OAAO,CAAC,CAAC,CAE9D,SAAS,OAAO;SAC3B;AACN,QAAM,IAAI,MAAM,6CAA6C;;;;;;;;;;AAqBjE,SAAgB,eACd,aACA,mBAA2B,KACnB;CACR,MAAM,UAAU;EACd,GAAG;EACH,KAAK,KAAK,KAAK,GAAG,mBAAmB;EACtC;AACD,QAAO,QAAQ,KAAK,UAAU,QAAQ,CAAC;;;;;;;;;AAUzC,SAAgB,eAAe,MAA+B;CAC5D,MAAM,UAAU,KAAK,MAAM,QAAQ,KAAK,CAAC;AAEzC,KAAI,QAAQ,OAAO,KAAK,KAAK,GAAG,QAAQ,IACtC,OAAM,IAAI,MAAM,6BAA6B;CAG/C,MAAM,EAAE,UAAU,kBAAkB,eAAe,wBAAwB;AAE3E,KAAI,CAAC,SACH,OAAM,IAAI,MAAM,kDAAkD;AAGpE,QAAO;EAAE;EAAU;EAAkB;EAAe;EAAqB"}
1
+ {"version":3,"file":"crypto.js","names":[],"sources":["../src/crypto.ts"],"sourcesContent":["/**\n * Cryptographic utilities for stateless OAuth tokens\n *\n * Uses AES-256-GCM for authenticated encryption.\n * The authorization code contains encrypted credentials that can be\n * decrypted without server-side storage.\n */\n\nimport { createCipheriv, createDecipheriv, randomBytes, scryptSync } from \"node:crypto\";\n\nconst ALGORITHM = \"aes-256-gcm\";\nconst IV_LENGTH = 12; // GCM recommended IV length\nconst AUTH_TAG_LENGTH = 16;\nconst SALT_LENGTH = 16;\n\n/**\n * Derive a 256-bit key from a password using scrypt\n */\nfunction deriveKey(password: string, salt: Buffer): Buffer {\n return scryptSync(password, salt, 32);\n}\n\n/**\n * Get the encryption secret from environment or generate a default.\n * In production, OAUTH_SECRET should always be set.\n */\nexport function getSecret(): string {\n const secret = process.env.OAUTH_SECRET;\n if (!secret) {\n console.warn(\n \"WARNING: OAUTH_SECRET not set. Using default secret. Set OAUTH_SECRET in production!\",\n );\n return \"forge-mcp-default-secret-change-me\";\n }\n return secret;\n}\n\n/**\n * Encrypt data using AES-256-GCM\n *\n * Output format: base64url(salt + iv + authTag + ciphertext)\n *\n * @param plaintext - Data to encrypt\n * @param secret - Encryption secret (defaults to OAUTH_SECRET env var)\n * @returns Base64url-encoded encrypted data\n */\nexport function encrypt(plaintext: string, secret: string = getSecret()): string {\n const salt = randomBytes(SALT_LENGTH);\n const key = deriveKey(secret, salt);\n const iv = randomBytes(IV_LENGTH);\n\n const cipher = createCipheriv(ALGORITHM, key, iv);\n const encrypted = Buffer.concat([cipher.update(plaintext, \"utf8\"), cipher.final()]);\n const authTag = cipher.getAuthTag();\n\n // Combine: salt + iv + authTag + ciphertext\n const combined = Buffer.concat([salt, iv, authTag, encrypted]);\n\n return combined.toString(\"base64url\");\n}\n\n/**\n * Decrypt data encrypted with encrypt()\n *\n * @param ciphertext - Base64url-encoded encrypted data\n * @param secret - Encryption secret (defaults to OAUTH_SECRET env var)\n * @returns Decrypted plaintext\n * @throws Error if decryption fails (invalid data or wrong secret)\n */\nexport function decrypt(ciphertext: string, secret: string = getSecret()): string {\n try {\n const combined = Buffer.from(ciphertext, \"base64url\");\n\n // Extract components\n const salt = combined.subarray(0, SALT_LENGTH);\n const iv = combined.subarray(SALT_LENGTH, SALT_LENGTH + IV_LENGTH);\n const authTag = combined.subarray(\n SALT_LENGTH + IV_LENGTH,\n SALT_LENGTH + IV_LENGTH + AUTH_TAG_LENGTH,\n );\n const encrypted = combined.subarray(SALT_LENGTH + IV_LENGTH + AUTH_TAG_LENGTH);\n\n const key = deriveKey(secret, salt);\n\n const decipher = createDecipheriv(ALGORITHM, key, iv, { authTagLength: AUTH_TAG_LENGTH });\n decipher.setAuthTag(authTag);\n\n const decrypted = Buffer.concat([decipher.update(encrypted), decipher.final()]);\n\n return decrypted.toString(\"utf8\");\n } catch {\n throw new Error(\"Decryption failed: invalid token or secret\");\n }\n}\n\n/**\n * Authorization code payload structure\n */\nexport interface AuthCodePayload {\n apiToken: string;\n organizationSlug?: string;\n codeChallenge?: string;\n codeChallengeMethod?: string;\n}\n\n/**\n * Create an encrypted authorization code containing credentials and PKCE challenge\n *\n * @param credentials - Object with apiToken and optional PKCE params\n * @param expiresInSeconds - Code expiration time (default: 5 minutes)\n * @returns Encrypted authorization code\n */\nexport function createAuthCode(\n credentials: AuthCodePayload,\n expiresInSeconds: number = 300,\n): string {\n const payload = {\n ...credentials,\n exp: Date.now() + expiresInSeconds * 1000,\n };\n return encrypt(JSON.stringify(payload));\n}\n\n/**\n * Decode and validate an authorization code\n *\n * @param code - Encrypted authorization code\n * @returns Decoded payload with credentials and PKCE challenge\n * @throws Error if code is invalid or expired\n */\nexport function decodeAuthCode(code: string): AuthCodePayload {\n const payload = JSON.parse(decrypt(code));\n\n if (payload.exp && Date.now() > payload.exp) {\n throw new Error(\"Authorization code expired\");\n }\n\n const { apiToken, organizationSlug, codeChallenge, codeChallengeMethod } = payload;\n\n if (!apiToken) {\n throw new Error(\"Invalid authorization code: missing credentials\");\n }\n\n return { apiToken, organizationSlug, codeChallenge, codeChallengeMethod };\n}\n"],"mappings":";;;;;;;;;AAUA,IAAM,YAAY;AAClB,IAAM,YAAY;AAClB,IAAM,kBAAkB;AACxB,IAAM,cAAc;;;;AAKpB,SAAS,UAAU,UAAkB,MAAsB;CACzD,OAAO,WAAW,UAAU,MAAM,EAAE;AACtC;;;;;AAMA,SAAgB,YAAoB;CAClC,MAAM,SAAS,QAAQ,IAAI;CAC3B,IAAI,CAAC,QAAQ;EACX,QAAQ,KACN,sFACF;EACA,OAAO;CACT;CACA,OAAO;AACT;;;;;;;;;;AAWA,SAAgB,QAAQ,WAAmB,SAAiB,UAAU,GAAW;CAC/E,MAAM,OAAO,YAAY,WAAW;CACpC,MAAM,MAAM,UAAU,QAAQ,IAAI;CAClC,MAAM,KAAK,YAAY,SAAS;CAEhC,MAAM,SAAS,eAAe,WAAW,KAAK,EAAE;CAChD,MAAM,YAAY,OAAO,OAAO,CAAC,OAAO,OAAO,WAAW,MAAM,GAAG,OAAO,MAAM,CAAC,CAAC;CAClF,MAAM,UAAU,OAAO,WAAW;CAKlC,OAFiB,OAAO,OAAO;EAAC;EAAM;EAAI;EAAS;CAAS,CAErD,CAAA,CAAS,SAAS,WAAW;AACtC;;;;;;;;;AAUA,SAAgB,QAAQ,YAAoB,SAAiB,UAAU,GAAW;CAChF,IAAI;EACF,MAAM,WAAW,OAAO,KAAK,YAAY,WAAW;EAGpD,MAAM,OAAO,SAAS,SAAS,GAAG,WAAW;EAC7C,MAAM,KAAK,SAAS,SAAS,aAAa,EAAuB;EACjE,MAAM,UAAU,SAAS,SACvB,IACA,EACF;EACA,MAAM,YAAY,SAAS,SAAS,EAAyC;EAI7E,MAAM,WAAW,iBAAiB,WAFtB,UAAU,QAAQ,IAEe,GAAK,IAAI,EAAE,eAAe,gBAAgB,CAAC;EACxF,SAAS,WAAW,OAAO;EAI3B,OAFkB,OAAO,OAAO,CAAC,SAAS,OAAO,SAAS,GAAG,SAAS,MAAM,CAAC,CAEtE,CAAA,CAAU,SAAS,MAAM;CAClC,QAAQ;EACN,MAAM,IAAI,MAAM,4CAA4C;CAC9D;AACF;;;;;;;;AAmBA,SAAgB,eACd,aACA,mBAA2B,KACnB;CACR,MAAM,UAAU;EACd,GAAG;EACH,KAAK,KAAK,IAAI,IAAI,mBAAmB;CACvC;CACA,OAAO,QAAQ,KAAK,UAAU,OAAO,CAAC;AACxC;;;;;;;;AASA,SAAgB,eAAe,MAA+B;CAC5D,MAAM,UAAU,KAAK,MAAM,QAAQ,IAAI,CAAC;CAExC,IAAI,QAAQ,OAAO,KAAK,IAAI,IAAI,QAAQ,KACtC,MAAM,IAAI,MAAM,4BAA4B;CAG9C,MAAM,EAAE,UAAU,kBAAkB,eAAe,wBAAwB;CAE3E,IAAI,CAAC,UACH,MAAM,IAAI,MAAM,iDAAiD;CAGnE,OAAO;EAAE;EAAU;EAAkB;EAAe;CAAoB;AAC1E"}
@@ -1,3 +1,4 @@
1
+ //#region src/flags.ts
1
2
  /**
2
3
  * CLI flag parsers shared between stdio and HTTP entry points.
3
4
  *
@@ -18,6 +19,7 @@
18
19
  function parseReadOnlyFlag() {
19
20
  return process.argv.includes("--read-only") || process.env.FORGE_READ_ONLY === "true";
20
21
  }
22
+ //#endregion
21
23
  export { parseReadOnlyFlag as t };
22
24
 
23
- //# sourceMappingURL=flags-LFbdErsZ.js.map
25
+ //# sourceMappingURL=flags-CIh56cQ7.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"flags-LFbdErsZ.js","names":[],"sources":["../src/flags.ts"],"sourcesContent":["/**\n * CLI flag parsers shared between stdio and HTTP entry points.\n *\n * Extracted to a separate module so that both `index.ts` (stdio) and\n * `server.ts` (HTTP) can import it without creating a shared dependency\n * that triggers Vite code-splitting — which would break the `isMainModule`\n * guard in `index.ts`.\n *\n * See: https://github.com/studiometa/forge-tools/issues/63\n */\n\n/**\n * Parse read-only flag from process.argv and environment.\n *\n * Supports:\n * - `--read-only` CLI flag\n * - `FORGE_READ_ONLY=true` environment variable\n */\nexport function parseReadOnlyFlag(): boolean {\n return process.argv.includes(\"--read-only\") || process.env.FORGE_READ_ONLY === \"true\";\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAkBA,SAAgB,oBAA6B;AAC3C,QAAO,QAAQ,KAAK,SAAS,cAAc,IAAI,QAAQ,IAAI,oBAAoB"}
1
+ {"version":3,"file":"flags-CIh56cQ7.js","names":[],"sources":["../src/flags.ts"],"sourcesContent":["/**\n * CLI flag parsers shared between stdio and HTTP entry points.\n *\n * Extracted to a separate module so that both `index.ts` (stdio) and\n * `server.ts` (HTTP) can import it without creating a shared dependency\n * that triggers Vite code-splitting — which would break the `isMainModule`\n * guard in `index.ts`.\n *\n * See: https://github.com/studiometa/forge-tools/issues/63\n */\n\n/**\n * Parse read-only flag from process.argv and environment.\n *\n * Supports:\n * - `--read-only` CLI flag\n * - `FORGE_READ_ONLY=true` environment variable\n */\nexport function parseReadOnlyFlag(): boolean {\n return process.argv.includes(\"--read-only\") || process.env.FORGE_READ_ONLY === \"true\";\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAkBA,SAAgB,oBAA6B;CAC3C,OAAO,QAAQ,KAAK,SAAS,aAAa,KAAK,QAAQ,IAAI,oBAAoB;AACjF"}
@@ -107,6 +107,10 @@ export declare function formatDaemonList(daemons: (BackgroundProcessAttributes &
107
107
  export declare function formatDaemon(daemon: BackgroundProcessAttributes & {
108
108
  id: number;
109
109
  }): string;
110
+ /**
111
+ * Format a daemon log.
112
+ */
113
+ export declare function formatDaemonLog(daemonId: string, log: string): string;
110
114
  /**
111
115
  * Format a list of firewall rules.
112
116
  */
@@ -241,6 +245,14 @@ export declare function formatEnv(content: string): string;
241
245
  export declare function formatUser(user: UserAttributes & {
242
246
  id: number;
243
247
  }): string;
248
+ /**
249
+ * Format a list of services with their derived availability.
250
+ */
251
+ export declare function formatServiceList(services: {
252
+ service: string;
253
+ available: boolean;
254
+ detail: string | null;
255
+ }[]): string;
244
256
  /**
245
257
  * Format a deleted resource confirmation.
246
258
  */
@@ -1 +1 @@
1
- {"version":3,"file":"formatters.d.ts","sourceRoot":"","sources":["../src/formatters.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"formatters.d.ts","sourceRoot":"","sources":["../src/formatters.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"daemons.d.ts","sourceRoot":"","sources":["../../src/handlers/daemons.ts"],"names":[],"mappings":"AAcA,eAAO,MAAM,aAAa,+IA4CxB,CAAC"}
1
+ {"version":3,"file":"daemons.d.ts","sourceRoot":"","sources":["../../src/handlers/daemons.ts"],"names":[],"mappings":"AAeA,eAAO,MAAM,aAAa,+IAgDxB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"help.d.ts","sourceRoot":"","sources":["../../src/handlers/help.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AA+rB7C;;GAEG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,UAAU,CAQvD;AAED;;GAEG;AACH,wBAAgB,kBAAkB,IAAI,UAAU,CAa/C"}
1
+ {"version":3,"file":"help.d.ts","sourceRoot":"","sources":["../../src/handlers/help.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAyuB7C;;GAEG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,UAAU,CAQvD;AAED;;GAEG;AACH,wBAAgB,kBAAkB,IAAI,UAAU,CAa/C"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/handlers/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAOH,OAAO,KAAK,EAA8B,UAAU,EAAE,MAAM,YAAY,CAAC;AAgCzE,YAAY,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AA0E7C;;;;;GAKG;AACH,wBAAsB,0BAA0B,CAC9C,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,WAAW,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAAE,GAC3D,OAAO,CAAC,UAAU,CAAC,CA+HrB"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/handlers/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAOH,OAAO,KAAK,EAA8B,UAAU,EAAE,MAAM,YAAY,CAAC;AAiCzE,YAAY,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AA4E7C;;;;;GAKG;AACH,wBAAsB,0BAA0B,CAC9C,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,WAAW,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAAE,GAC3D,OAAO,CAAC,UAAU,CAAC,CA+HrB"}
@@ -1 +1 @@
1
- {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/handlers/schema.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAkV7C;;GAEG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,UAAU,CAWzD;AAED;;GAEG;AACH,wBAAgB,oBAAoB,IAAI,UAAU,CAWjD"}
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/handlers/schema.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AA4V7C;;GAEG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,UAAU,CAWzD;AAED;;GAEG;AACH,wBAAgB,oBAAoB,IAAI,UAAU,CAWjD"}
@@ -0,0 +1,2 @@
1
+ export declare const handleServices: (action: string, args: import("./types.ts").CommonArgs, ctx: import("./types.ts").HandlerContext) => Promise<import("./types.ts").ToolResult>;
2
+ //# sourceMappingURL=services.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"services.d.ts","sourceRoot":"","sources":["../../src/handlers/services.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,cAAc,+IA0BzB,CAAC"}
@@ -1,4 +1,4 @@
1
- import { a as INSTRUCTIONS, i as getTools, n as executeToolWithCredentials, t as VERSION } from "./version-DNuuHvcD.js";
1
+ import { a as INSTRUCTIONS, i as getTools, n as executeToolWithCredentials, t as VERSION } from "./version-BS77BDou.js";
2
2
  import { parseAuthHeader } from "./auth.js";
3
3
  import { authorizeGetHandler, authorizePostHandler, oauthMetadataHandler, protectedResourceHandler, registerHandler, tokenHandler } from "./oauth.js";
4
4
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
@@ -7,6 +7,7 @@ import { getOrganizationSlug } from "@studiometa/forge-api";
7
7
  import { randomUUID } from "node:crypto";
8
8
  import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
9
9
  import { H3, defineEventHandler } from "h3";
10
+ //#region src/sessions.ts
10
11
  var DEFAULT_TTL = 1800 * 1e3;
11
12
  var DEFAULT_SWEEP_INTERVAL = 60 * 1e3;
12
13
  var SessionManager = class {
@@ -94,6 +95,8 @@ var SessionManager = class {
94
95
  this.sessions.clear();
95
96
  }
96
97
  };
98
+ //#endregion
99
+ //#region src/http.ts
97
100
  /**
98
101
  * Streamable HTTP transport for Forge MCP Server
99
102
  *
@@ -296,6 +299,7 @@ function createHealthApp() {
296
299
  app.post("/token", tokenHandler);
297
300
  return app;
298
301
  }
302
+ //#endregion
299
303
  export { SessionManager as a, handleMcpRequest as i, createMcpRequestHandler as n, createMcpServer as r, createHealthApp as t };
300
304
 
301
- //# sourceMappingURL=http-BxoFD5eP.js.map
305
+ //# sourceMappingURL=http-CwlifGMb.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"http-BxoFD5eP.js","names":[],"sources":["../src/sessions.ts","../src/http.ts"],"sourcesContent":["/* eslint-disable typescript-eslint/no-deprecated -- Using low-level Server type for session management */\n/**\n * Session manager for multi-tenant Streamable HTTP transport.\n *\n * Each MCP client session gets its own transport + server pair.\n * Sessions are identified by UUID and tracked in a Map.\n *\n * Supports automatic TTL-based cleanup of idle sessions to prevent\n * memory leaks from abandoned clients.\n */\n\n// Using low-level Server type for advanced transport handling\n// eslint-disable-next-line typescript-eslint/no-deprecated\nimport type { Server } from \"@modelcontextprotocol/sdk/server/index.js\";\nimport type { StreamableHTTPServerTransport } from \"@modelcontextprotocol/sdk/server/streamableHttp.js\";\n\n/**\n * A managed session: transport + MCP server pair.\n */\nexport interface ManagedSession {\n transport: StreamableHTTPServerTransport;\n server: Server;\n createdAt: number;\n lastActiveAt: number;\n}\n\nexport interface SessionManagerOptions {\n /**\n * Maximum idle time in milliseconds before a session is reaped.\n * Default: 30 minutes. Set to 0 to disable automatic cleanup.\n */\n ttl?: number;\n\n /**\n * How often to check for expired sessions, in milliseconds.\n * Default: 60 seconds.\n */\n sweepInterval?: number;\n}\n\nconst DEFAULT_TTL = 30 * 60 * 1000; // 30 minutes\nconst DEFAULT_SWEEP_INTERVAL = 60 * 1000; // 60 seconds\n\nexport class SessionManager {\n private sessions = new Map<string, ManagedSession>();\n // eslint-disable-next-line typescript-eslint/no-redundant-type-constituents -- NodeJS.Timeout resolves correctly at runtime\n private sweepTimer: ReturnType<typeof setInterval> | undefined;\n private readonly ttl: number;\n\n constructor(options?: SessionManagerOptions) {\n this.ttl = options?.ttl ?? DEFAULT_TTL;\n\n if (this.ttl > 0) {\n const interval = options?.sweepInterval ?? DEFAULT_SWEEP_INTERVAL;\n this.sweepTimer = setInterval(() => {\n this.sweep();\n }, interval);\n // Don't keep the process alive just for the sweep timer\n this.sweepTimer.unref();\n }\n }\n\n /**\n * Register a session after its ID has been assigned by the transport.\n */\n register(transport: StreamableHTTPServerTransport, server: Server): void {\n const sessionId = transport.sessionId;\n if (sessionId) {\n const now = Date.now();\n this.sessions.set(sessionId, {\n transport,\n server,\n createdAt: now,\n lastActiveAt: now,\n });\n }\n }\n\n /**\n * Look up a session by its ID and refresh its activity timestamp.\n */\n get(sessionId: string): ManagedSession | undefined {\n const session = this.sessions.get(sessionId);\n if (session) {\n session.lastActiveAt = Date.now();\n }\n return session;\n }\n\n /**\n * Remove a session and close its transport + server.\n */\n async remove(sessionId: string): Promise<void> {\n const session = this.sessions.get(sessionId);\n if (session) {\n this.sessions.delete(sessionId);\n await session.transport.close();\n await session.server.close();\n }\n }\n\n /**\n * Get the number of active sessions.\n */\n get size(): number {\n return this.sessions.size;\n }\n\n /**\n * Sweep expired sessions. Called automatically by the sweep timer.\n * Returns the number of sessions reaped.\n */\n sweep(): number {\n if (this.ttl <= 0) return 0;\n\n const now = Date.now();\n const expired: string[] = [];\n\n for (const [id, session] of this.sessions) {\n if (now - session.lastActiveAt > this.ttl) {\n expired.push(id);\n }\n }\n\n for (const id of expired) {\n // Fire-and-forget cleanup — don't block the sweep\n /* v8 ignore start */\n this.remove(id).catch(() => {});\n /* v8 ignore stop */\n }\n\n return expired.length;\n }\n\n /**\n * Close all sessions, stop the sweep timer, and clean up.\n */\n async closeAll(): Promise<void> {\n if (this.sweepTimer) {\n clearInterval(this.sweepTimer);\n this.sweepTimer = undefined;\n }\n\n const promises: Promise<void>[] = [];\n for (const [, session] of this.sessions) {\n promises.push(session.transport.close());\n promises.push(session.server.close());\n }\n await Promise.all(promises);\n this.sessions.clear();\n }\n}\n","/* eslint-disable typescript-eslint/no-deprecated -- Using low-level Server for StreamableHTTPServerTransport */\n/**\n * Streamable HTTP transport for Forge MCP Server\n *\n * Implements the official MCP Streamable HTTP transport specification (2025-03-26)\n * using the SDK's StreamableHTTPServerTransport.\n *\n * Architecture:\n * - Stateful mode with per-session transport+server pairs (multi-tenant)\n * - Auth via Bearer token → authInfo.token → handler extra.authInfo\n * - Session manager (injected) maps session IDs to transport+server instances\n * - Health/status endpoints handled by h3, MCP endpoint by the SDK transport\n */\n\nimport { randomUUID } from \"node:crypto\";\nimport type { IncomingMessage, ServerResponse } from \"node:http\";\n\n// Using low-level Server for advanced transport handling (StreamableHTTPServerTransport)\n// eslint-disable-next-line typescript-eslint/no-deprecated\nimport { Server } from \"@modelcontextprotocol/sdk/server/index.js\";\nimport { StreamableHTTPServerTransport } from \"@modelcontextprotocol/sdk/server/streamableHttp.js\";\nimport {\n CallToolRequestSchema,\n ListToolsRequestSchema,\n type CallToolResult,\n} from \"@modelcontextprotocol/sdk/types.js\";\nimport { getOrganizationSlug } from \"@studiometa/forge-api\";\nimport { H3, defineEventHandler } from \"h3\";\n\nimport { parseAuthHeader } from \"./auth.ts\";\nimport { executeToolWithCredentials } from \"./handlers/index.ts\";\nimport { INSTRUCTIONS } from \"./instructions.ts\";\nimport {\n oauthMetadataHandler,\n protectedResourceHandler,\n registerHandler,\n authorizeGetHandler,\n authorizePostHandler,\n tokenHandler,\n} from \"./oauth.ts\";\nimport { SessionManager } from \"./sessions.ts\";\nimport { getTools } from \"./tools.ts\";\nimport { VERSION } from \"./version.ts\";\n\nexport { SessionManager } from \"./sessions.ts\";\n\n/**\n * Options for the HTTP MCP server.\n */\nexport interface HttpServerOptions {\n /** When true, forge_write tool is not registered and write operations are rejected. */\n readOnly?: boolean;\n}\n\n/**\n * Create a configured MCP Server instance for HTTP transport.\n *\n * Unlike stdio, HTTP mode does NOT include forge_configure/forge_get_config\n * because API tokens come from the Authorization header per-request.\n * Organization slug still falls back to FORGE_ORG/config when omitted.\n */\nexport function createMcpServer(options?: HttpServerOptions): Server {\n const readOnly = options?.readOnly ?? false;\n const tools = getTools({ readOnly });\n\n const server = new Server(\n {\n name: \"forge-mcp\",\n version: VERSION,\n },\n {\n capabilities: {\n tools: {},\n },\n instructions: INSTRUCTIONS,\n },\n );\n\n server.setRequestHandler(ListToolsRequestSchema, async () => {\n return { tools };\n });\n\n server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {\n const { name, arguments: args } = request.params;\n const authInfo = extra.authInfo as { token?: string; organizationSlug?: string } | undefined;\n const token = authInfo?.token;\n const organizationSlugFromAuth =\n typeof authInfo?.organizationSlug === \"string\" ? authInfo.organizationSlug : undefined;\n\n /* v8 ignore start */\n if (!token) {\n return {\n content: [\n {\n type: \"text\" as const,\n text: \"Error: Authentication required. No token found in request.\",\n },\n ],\n structuredContent: {\n success: false,\n error: \"Authentication required. No token found in request.\",\n },\n isError: true,\n };\n }\n /* v8 ignore stop */\n\n // Reject write operations in read-only mode\n if (readOnly && name === \"forge_write\") {\n return {\n content: [\n {\n type: \"text\" as const,\n text: \"Error: Server is running in read-only mode. Write operations are disabled.\",\n },\n ],\n structuredContent: {\n success: false,\n error: \"Server is running in read-only mode. Write operations are disabled.\",\n },\n isError: true,\n };\n }\n\n try {\n // Organization slug can come from:\n // 1. Tool args (per-request override, highest priority)\n // 2. Auth token (configured during OAuth flow)\n // 3. FORGE_ORG / config fallback\n /* v8 ignore next 2 -- defensive type guard */\n const orgSlugFromArgs =\n typeof args?.organizationSlug === \"string\" ? args.organizationSlug : undefined;\n const result = await executeToolWithCredentials(name, /* v8 ignore next */ args ?? {}, {\n apiToken: token,\n organizationSlug:\n orgSlugFromArgs ?? organizationSlugFromAuth ?? getOrganizationSlug() ?? undefined,\n });\n return result as CallToolResult;\n } catch (error) {\n /* v8 ignore start */\n const message = error instanceof Error ? error.message : String(error);\n /* v8 ignore stop */\n return {\n content: [{ type: \"text\" as const, text: `Error: ${message}` }],\n structuredContent: { success: false, error: message },\n isError: true,\n };\n }\n });\n\n return server;\n}\n\n/**\n * Handle an MCP request using the Streamable HTTP transport.\n *\n * Routes requests based on whether they have a session ID:\n * - No session ID + initialize request → create new session\n * - Has session ID → route to existing session's transport\n *\n * @param req - Node.js IncomingMessage\n * @param res - Node.js ServerResponse\n * @param sessions - Session manager instance (injected)\n * @param options - Server options (read-only mode, etc.)\n */\nexport async function handleMcpRequest(\n req: IncomingMessage,\n res: ServerResponse,\n sessions: SessionManager,\n options?: HttpServerOptions,\n): Promise<void> {\n // Extract and validate auth\n const authHeader = req.headers.authorization;\n const credentials = parseAuthHeader(authHeader);\n\n if (!credentials) {\n // Build resource_metadata URL for the WWW-Authenticate header (RFC 9728)\n const host = req.headers.host || \"localhost:3000\";\n const proto = req.headers[\"x-forwarded-proto\"];\n const protocol = (typeof proto === \"string\" ? proto : undefined) || \"http\";\n const resourceMetadataUrl = `${protocol}://${host}/.well-known/oauth-protected-resource`;\n\n res.writeHead(401, {\n \"Content-Type\": \"application/json\",\n \"WWW-Authenticate\": `Bearer resource_metadata=\"${resourceMetadataUrl}\"`,\n });\n res.end(\n JSON.stringify({\n jsonrpc: \"2.0\",\n error: {\n code: -32001,\n message: \"Authentication required. Provide a Bearer token with your Forge API token.\",\n },\n id: null,\n }),\n );\n return;\n }\n\n // Inject auth info for the SDK transport (MCP SDK expects auth on request)\n // Include organizationSlug in extra for downstream handlers\n Object.assign(req, {\n auth: {\n token: credentials.apiToken,\n organizationSlug: credentials.organizationSlug,\n clientId: \"forge-http-client\",\n scopes: [],\n extra: {\n organizationSlug: credentials.organizationSlug,\n },\n },\n });\n\n const sessionHeader = req.headers[\"mcp-session-id\"];\n const sessionId = typeof sessionHeader === \"string\" ? sessionHeader : undefined;\n\n if (sessionId) {\n // Existing session — route to its transport\n const session = sessions.get(sessionId);\n if (!session) {\n res.writeHead(404, { \"Content-Type\": \"application/json\" });\n res.end(\n JSON.stringify({\n jsonrpc: \"2.0\",\n error: {\n code: -32000,\n message: \"Session not found. The session may have expired or been terminated.\",\n },\n id: null,\n }),\n );\n return;\n }\n\n await session.transport.handleRequest(req, res);\n return;\n }\n\n // No session ID — this should be an initialize request.\n // Create a new transport + server pair.\n const transport = new StreamableHTTPServerTransport({\n sessionIdGenerator: () => randomUUID(),\n });\n\n const server = createMcpServer(options);\n await server.connect(transport);\n\n // Set up cleanup on close\n // eslint-disable-next-line unicorn/prefer-add-event-listener -- MCP SDK uses property assignment\n transport.onclose = () => {\n const sid = transport.sessionId;\n /* v8 ignore start */\n if (sid) {\n sessions.remove(sid).catch(() => {\n // Ignore cleanup errors\n });\n }\n /* v8 ignore stop */\n };\n\n // Handle the request (this will set transport.sessionId during initialize)\n await transport.handleRequest(req, res);\n\n // After handling, register the session if the transport got a session ID\n /* v8 ignore start */\n if (transport.sessionId) {\n sessions.register(transport, server);\n } else {\n // No session was created (e.g., invalid request) — clean up\n await transport.close();\n await server.close();\n }\n /* v8 ignore stop */\n}\n\n/**\n * Create a request handler bound to a SessionManager instance.\n * Convenience factory for server.ts.\n */\nexport function createMcpRequestHandler(\n sessions: SessionManager,\n options?: HttpServerOptions,\n): (req: IncomingMessage, res: ServerResponse) => Promise<void> {\n /* v8 ignore start */\n return (req, res) => handleMcpRequest(req, res, sessions, options);\n /* v8 ignore stop */\n}\n\n/**\n * Create h3 app for health check, service info, and OAuth endpoints.\n * The MCP endpoint is handled separately by handleMcpRequest.\n */\nexport function createHealthApp(): H3 {\n const app = new H3();\n\n // Service info & health\n app.get(\n \"/\",\n defineEventHandler(() => {\n return { status: \"ok\", service: \"forge-mcp\", version: VERSION };\n }),\n );\n\n app.get(\n \"/health\",\n defineEventHandler(() => {\n return { status: \"ok\" };\n }),\n );\n\n // OAuth 2.1 endpoints\n app.get(\"/.well-known/oauth-authorization-server\", oauthMetadataHandler);\n app.get(\"/.well-known/oauth-protected-resource\", protectedResourceHandler);\n app.post(\"/register\", registerHandler);\n app.get(\"/authorize\", authorizeGetHandler);\n app.post(\"/authorize\", authorizePostHandler);\n app.post(\"/token\", tokenHandler);\n\n return app;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"http-CwlifGMb.js","names":[],"sources":["../src/sessions.ts","../src/http.ts"],"sourcesContent":["/* eslint-disable typescript-eslint/no-deprecated -- Using low-level Server type for session management */\n/**\n * Session manager for multi-tenant Streamable HTTP transport.\n *\n * Each MCP client session gets its own transport + server pair.\n * Sessions are identified by UUID and tracked in a Map.\n *\n * Supports automatic TTL-based cleanup of idle sessions to prevent\n * memory leaks from abandoned clients.\n */\n\n// Using low-level Server type for advanced transport handling\n// eslint-disable-next-line typescript-eslint/no-deprecated\nimport type { Server } from \"@modelcontextprotocol/sdk/server/index.js\";\nimport type { StreamableHTTPServerTransport } from \"@modelcontextprotocol/sdk/server/streamableHttp.js\";\n\n/**\n * A managed session: transport + MCP server pair.\n */\nexport interface ManagedSession {\n transport: StreamableHTTPServerTransport;\n server: Server;\n createdAt: number;\n lastActiveAt: number;\n}\n\nexport interface SessionManagerOptions {\n /**\n * Maximum idle time in milliseconds before a session is reaped.\n * Default: 30 minutes. Set to 0 to disable automatic cleanup.\n */\n ttl?: number;\n\n /**\n * How often to check for expired sessions, in milliseconds.\n * Default: 60 seconds.\n */\n sweepInterval?: number;\n}\n\nconst DEFAULT_TTL = 30 * 60 * 1000; // 30 minutes\nconst DEFAULT_SWEEP_INTERVAL = 60 * 1000; // 60 seconds\n\nexport class SessionManager {\n private sessions = new Map<string, ManagedSession>();\n // eslint-disable-next-line typescript-eslint/no-redundant-type-constituents -- NodeJS.Timeout resolves correctly at runtime\n private sweepTimer: ReturnType<typeof setInterval> | undefined;\n private readonly ttl: number;\n\n constructor(options?: SessionManagerOptions) {\n this.ttl = options?.ttl ?? DEFAULT_TTL;\n\n if (this.ttl > 0) {\n const interval = options?.sweepInterval ?? DEFAULT_SWEEP_INTERVAL;\n this.sweepTimer = setInterval(() => {\n this.sweep();\n }, interval);\n // Don't keep the process alive just for the sweep timer\n this.sweepTimer.unref();\n }\n }\n\n /**\n * Register a session after its ID has been assigned by the transport.\n */\n register(transport: StreamableHTTPServerTransport, server: Server): void {\n const sessionId = transport.sessionId;\n if (sessionId) {\n const now = Date.now();\n this.sessions.set(sessionId, {\n transport,\n server,\n createdAt: now,\n lastActiveAt: now,\n });\n }\n }\n\n /**\n * Look up a session by its ID and refresh its activity timestamp.\n */\n get(sessionId: string): ManagedSession | undefined {\n const session = this.sessions.get(sessionId);\n if (session) {\n session.lastActiveAt = Date.now();\n }\n return session;\n }\n\n /**\n * Remove a session and close its transport + server.\n */\n async remove(sessionId: string): Promise<void> {\n const session = this.sessions.get(sessionId);\n if (session) {\n this.sessions.delete(sessionId);\n await session.transport.close();\n await session.server.close();\n }\n }\n\n /**\n * Get the number of active sessions.\n */\n get size(): number {\n return this.sessions.size;\n }\n\n /**\n * Sweep expired sessions. Called automatically by the sweep timer.\n * Returns the number of sessions reaped.\n */\n sweep(): number {\n if (this.ttl <= 0) return 0;\n\n const now = Date.now();\n const expired: string[] = [];\n\n for (const [id, session] of this.sessions) {\n if (now - session.lastActiveAt > this.ttl) {\n expired.push(id);\n }\n }\n\n for (const id of expired) {\n // Fire-and-forget cleanup — don't block the sweep\n /* v8 ignore start */\n this.remove(id).catch(() => {});\n /* v8 ignore stop */\n }\n\n return expired.length;\n }\n\n /**\n * Close all sessions, stop the sweep timer, and clean up.\n */\n async closeAll(): Promise<void> {\n if (this.sweepTimer) {\n clearInterval(this.sweepTimer);\n this.sweepTimer = undefined;\n }\n\n const promises: Promise<void>[] = [];\n for (const [, session] of this.sessions) {\n promises.push(session.transport.close());\n promises.push(session.server.close());\n }\n await Promise.all(promises);\n this.sessions.clear();\n }\n}\n","/* eslint-disable typescript-eslint/no-deprecated -- Using low-level Server for StreamableHTTPServerTransport */\n/**\n * Streamable HTTP transport for Forge MCP Server\n *\n * Implements the official MCP Streamable HTTP transport specification (2025-03-26)\n * using the SDK's StreamableHTTPServerTransport.\n *\n * Architecture:\n * - Stateful mode with per-session transport+server pairs (multi-tenant)\n * - Auth via Bearer token → authInfo.token → handler extra.authInfo\n * - Session manager (injected) maps session IDs to transport+server instances\n * - Health/status endpoints handled by h3, MCP endpoint by the SDK transport\n */\n\nimport { randomUUID } from \"node:crypto\";\nimport type { IncomingMessage, ServerResponse } from \"node:http\";\n\n// Using low-level Server for advanced transport handling (StreamableHTTPServerTransport)\n// eslint-disable-next-line typescript-eslint/no-deprecated\nimport { Server } from \"@modelcontextprotocol/sdk/server/index.js\";\nimport { StreamableHTTPServerTransport } from \"@modelcontextprotocol/sdk/server/streamableHttp.js\";\nimport {\n CallToolRequestSchema,\n ListToolsRequestSchema,\n type CallToolResult,\n} from \"@modelcontextprotocol/sdk/types.js\";\nimport { getOrganizationSlug } from \"@studiometa/forge-api\";\nimport { H3, defineEventHandler } from \"h3\";\n\nimport { parseAuthHeader } from \"./auth.ts\";\nimport { executeToolWithCredentials } from \"./handlers/index.ts\";\nimport { INSTRUCTIONS } from \"./instructions.ts\";\nimport {\n oauthMetadataHandler,\n protectedResourceHandler,\n registerHandler,\n authorizeGetHandler,\n authorizePostHandler,\n tokenHandler,\n} from \"./oauth.ts\";\nimport { SessionManager } from \"./sessions.ts\";\nimport { getTools } from \"./tools.ts\";\nimport { VERSION } from \"./version.ts\";\n\nexport { SessionManager } from \"./sessions.ts\";\n\n/**\n * Options for the HTTP MCP server.\n */\nexport interface HttpServerOptions {\n /** When true, forge_write tool is not registered and write operations are rejected. */\n readOnly?: boolean;\n}\n\n/**\n * Create a configured MCP Server instance for HTTP transport.\n *\n * Unlike stdio, HTTP mode does NOT include forge_configure/forge_get_config\n * because API tokens come from the Authorization header per-request.\n * Organization slug still falls back to FORGE_ORG/config when omitted.\n */\nexport function createMcpServer(options?: HttpServerOptions): Server {\n const readOnly = options?.readOnly ?? false;\n const tools = getTools({ readOnly });\n\n const server = new Server(\n {\n name: \"forge-mcp\",\n version: VERSION,\n },\n {\n capabilities: {\n tools: {},\n },\n instructions: INSTRUCTIONS,\n },\n );\n\n server.setRequestHandler(ListToolsRequestSchema, async () => {\n return { tools };\n });\n\n server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {\n const { name, arguments: args } = request.params;\n const authInfo = extra.authInfo as { token?: string; organizationSlug?: string } | undefined;\n const token = authInfo?.token;\n const organizationSlugFromAuth =\n typeof authInfo?.organizationSlug === \"string\" ? authInfo.organizationSlug : undefined;\n\n /* v8 ignore start */\n if (!token) {\n return {\n content: [\n {\n type: \"text\" as const,\n text: \"Error: Authentication required. No token found in request.\",\n },\n ],\n structuredContent: {\n success: false,\n error: \"Authentication required. No token found in request.\",\n },\n isError: true,\n };\n }\n /* v8 ignore stop */\n\n // Reject write operations in read-only mode\n if (readOnly && name === \"forge_write\") {\n return {\n content: [\n {\n type: \"text\" as const,\n text: \"Error: Server is running in read-only mode. Write operations are disabled.\",\n },\n ],\n structuredContent: {\n success: false,\n error: \"Server is running in read-only mode. Write operations are disabled.\",\n },\n isError: true,\n };\n }\n\n try {\n // Organization slug can come from:\n // 1. Tool args (per-request override, highest priority)\n // 2. Auth token (configured during OAuth flow)\n // 3. FORGE_ORG / config fallback\n /* v8 ignore next 2 -- defensive type guard */\n const orgSlugFromArgs =\n typeof args?.organizationSlug === \"string\" ? args.organizationSlug : undefined;\n const result = await executeToolWithCredentials(name, /* v8 ignore next */ args ?? {}, {\n apiToken: token,\n organizationSlug:\n orgSlugFromArgs ?? organizationSlugFromAuth ?? getOrganizationSlug() ?? undefined,\n });\n return result as CallToolResult;\n } catch (error) {\n /* v8 ignore start */\n const message = error instanceof Error ? error.message : String(error);\n /* v8 ignore stop */\n return {\n content: [{ type: \"text\" as const, text: `Error: ${message}` }],\n structuredContent: { success: false, error: message },\n isError: true,\n };\n }\n });\n\n return server;\n}\n\n/**\n * Handle an MCP request using the Streamable HTTP transport.\n *\n * Routes requests based on whether they have a session ID:\n * - No session ID + initialize request → create new session\n * - Has session ID → route to existing session's transport\n *\n * @param req - Node.js IncomingMessage\n * @param res - Node.js ServerResponse\n * @param sessions - Session manager instance (injected)\n * @param options - Server options (read-only mode, etc.)\n */\nexport async function handleMcpRequest(\n req: IncomingMessage,\n res: ServerResponse,\n sessions: SessionManager,\n options?: HttpServerOptions,\n): Promise<void> {\n // Extract and validate auth\n const authHeader = req.headers.authorization;\n const credentials = parseAuthHeader(authHeader);\n\n if (!credentials) {\n // Build resource_metadata URL for the WWW-Authenticate header (RFC 9728)\n const host = req.headers.host || \"localhost:3000\";\n const proto = req.headers[\"x-forwarded-proto\"];\n const protocol = (typeof proto === \"string\" ? proto : undefined) || \"http\";\n const resourceMetadataUrl = `${protocol}://${host}/.well-known/oauth-protected-resource`;\n\n res.writeHead(401, {\n \"Content-Type\": \"application/json\",\n \"WWW-Authenticate\": `Bearer resource_metadata=\"${resourceMetadataUrl}\"`,\n });\n res.end(\n JSON.stringify({\n jsonrpc: \"2.0\",\n error: {\n code: -32001,\n message: \"Authentication required. Provide a Bearer token with your Forge API token.\",\n },\n id: null,\n }),\n );\n return;\n }\n\n // Inject auth info for the SDK transport (MCP SDK expects auth on request)\n // Include organizationSlug in extra for downstream handlers\n Object.assign(req, {\n auth: {\n token: credentials.apiToken,\n organizationSlug: credentials.organizationSlug,\n clientId: \"forge-http-client\",\n scopes: [],\n extra: {\n organizationSlug: credentials.organizationSlug,\n },\n },\n });\n\n const sessionHeader = req.headers[\"mcp-session-id\"];\n const sessionId = typeof sessionHeader === \"string\" ? sessionHeader : undefined;\n\n if (sessionId) {\n // Existing session — route to its transport\n const session = sessions.get(sessionId);\n if (!session) {\n res.writeHead(404, { \"Content-Type\": \"application/json\" });\n res.end(\n JSON.stringify({\n jsonrpc: \"2.0\",\n error: {\n code: -32000,\n message: \"Session not found. The session may have expired or been terminated.\",\n },\n id: null,\n }),\n );\n return;\n }\n\n await session.transport.handleRequest(req, res);\n return;\n }\n\n // No session ID — this should be an initialize request.\n // Create a new transport + server pair.\n const transport = new StreamableHTTPServerTransport({\n sessionIdGenerator: () => randomUUID(),\n });\n\n const server = createMcpServer(options);\n await server.connect(transport);\n\n // Set up cleanup on close\n // eslint-disable-next-line unicorn/prefer-add-event-listener -- MCP SDK uses property assignment\n transport.onclose = () => {\n const sid = transport.sessionId;\n /* v8 ignore start */\n if (sid) {\n sessions.remove(sid).catch(() => {\n // Ignore cleanup errors\n });\n }\n /* v8 ignore stop */\n };\n\n // Handle the request (this will set transport.sessionId during initialize)\n await transport.handleRequest(req, res);\n\n // After handling, register the session if the transport got a session ID\n /* v8 ignore start */\n if (transport.sessionId) {\n sessions.register(transport, server);\n } else {\n // No session was created (e.g., invalid request) — clean up\n await transport.close();\n await server.close();\n }\n /* v8 ignore stop */\n}\n\n/**\n * Create a request handler bound to a SessionManager instance.\n * Convenience factory for server.ts.\n */\nexport function createMcpRequestHandler(\n sessions: SessionManager,\n options?: HttpServerOptions,\n): (req: IncomingMessage, res: ServerResponse) => Promise<void> {\n /* v8 ignore start */\n return (req, res) => handleMcpRequest(req, res, sessions, options);\n /* v8 ignore stop */\n}\n\n/**\n * Create h3 app for health check, service info, and OAuth endpoints.\n * The MCP endpoint is handled separately by handleMcpRequest.\n */\nexport function createHealthApp(): H3 {\n const app = new H3();\n\n // Service info & health\n app.get(\n \"/\",\n defineEventHandler(() => {\n return { status: \"ok\", service: \"forge-mcp\", version: VERSION };\n }),\n );\n\n app.get(\n \"/health\",\n defineEventHandler(() => {\n return { status: \"ok\" };\n }),\n );\n\n // OAuth 2.1 endpoints\n app.get(\"/.well-known/oauth-authorization-server\", oauthMetadataHandler);\n app.get(\"/.well-known/oauth-protected-resource\", protectedResourceHandler);\n app.post(\"/register\", registerHandler);\n app.get(\"/authorize\", authorizeGetHandler);\n app.post(\"/authorize\", authorizePostHandler);\n app.post(\"/token\", tokenHandler);\n\n return app;\n}\n"],"mappings":"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"}
package/dist/http.js CHANGED
@@ -1,3 +1,2 @@
1
- import "./version-DNuuHvcD.js";
2
- import { a as SessionManager, i as handleMcpRequest, n as createMcpRequestHandler, r as createMcpServer, t as createHealthApp } from "./http-BxoFD5eP.js";
1
+ import { a as SessionManager, i as handleMcpRequest, n as createMcpRequestHandler, r as createMcpServer, t as createHealthApp } from "./http-CwlifGMb.js";
3
2
  export { SessionManager, createHealthApp, createMcpRequestHandler, createMcpServer, handleMcpRequest };
package/dist/index.js CHANGED
@@ -1,10 +1,11 @@
1
1
  #!/usr/bin/env node
2
- import { t as parseReadOnlyFlag } from "./flags-LFbdErsZ.js";
3
- import { a as INSTRUCTIONS, i as getTools, n as executeToolWithCredentials, r as STDIO_ONLY_TOOLS, t as VERSION } from "./version-DNuuHvcD.js";
2
+ import { t as parseReadOnlyFlag } from "./flags-CIh56cQ7.js";
3
+ import { a as INSTRUCTIONS, i as getTools, n as executeToolWithCredentials, r as STDIO_ONLY_TOOLS, t as VERSION } from "./version-BS77BDou.js";
4
4
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
5
5
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
6
6
  import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
7
7
  import { getOrganizationSlug, getToken, setOrganizationSlug, setToken } from "@studiometa/forge-api";
8
+ //#region src/stdio.ts
8
9
  /**
9
10
  * Get all available tools (including stdio-only configuration tools).
10
11
  *
@@ -121,6 +122,8 @@ async function handleToolCall(name, args, options) {
121
122
  isError: true
122
123
  };
123
124
  }
125
+ //#endregion
126
+ //#region src/index.ts
124
127
  /**
125
128
  * Forge MCP Server — Stdio Transport
126
129
  *
@@ -193,6 +196,7 @@ if (import.meta.url === `file://${process.argv[1]}` || process.argv[1]?.endsWith
193
196
  console.error("Fatal error:", error);
194
197
  process.exit(1);
195
198
  });
199
+ //#endregion
196
200
  export { createStdioServer, parseReadOnlyFlag, startStdioServer };
197
201
 
198
202
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/stdio.ts","../src/index.ts"],"sourcesContent":["import {\n getToken,\n getOrganizationSlug,\n setToken,\n setOrganizationSlug,\n} from \"@studiometa/forge-api\";\n\nimport type { ToolResult } from \"./handlers/types.ts\";\n\nimport { executeToolWithCredentials } from \"./handlers/index.ts\";\nimport type { Tool } from \"@modelcontextprotocol/sdk/types.js\";\nimport { getTools, STDIO_ONLY_TOOLS } from \"./tools.ts\";\nimport type { GetToolsOptions } from \"./tools.ts\";\n\nexport type { ToolResult };\n\n/**\n * Get all available tools (including stdio-only configuration tools).\n *\n * @param options - Optional filtering. When `readOnly` is true, forge_write is excluded.\n */\nexport function getAvailableTools(options?: GetToolsOptions): Tool[] {\n return [...getTools(options), ...STDIO_ONLY_TOOLS];\n}\n\n/**\n * Handle the forge_configure tool.\n */\nexport function handleConfigureTool(args: {\n apiToken?: string;\n organizationSlug?: string;\n}): ToolResult {\n if (!args.apiToken && !args.organizationSlug) {\n return {\n content: [\n {\n type: \"text\",\n text: \"Error: at least one of apiToken or organizationSlug is required.\",\n },\n ],\n structuredContent: {\n success: false,\n error: \"at least one of apiToken or organizationSlug is required.\",\n },\n isError: true,\n };\n }\n\n if (args.apiToken) {\n setToken(args.apiToken);\n }\n if (args.organizationSlug) {\n setOrganizationSlug(args.organizationSlug);\n }\n\n const maskedToken = args.apiToken ? `***${args.apiToken.slice(-4)}` : undefined;\n const data: Record<string, unknown> = {\n success: true,\n message: \"Laravel Forge configuration updated successfully\",\n };\n if (maskedToken) data.apiToken = maskedToken;\n if (args.organizationSlug) data.organizationSlug = args.organizationSlug;\n\n return {\n content: [\n {\n type: \"text\",\n text: JSON.stringify(data, null, 2),\n },\n ],\n structuredContent: data,\n };\n}\n\n/**\n * Handle the forge_get_config tool.\n */\nexport function handleGetConfigTool(): ToolResult {\n const token = getToken();\n const orgSlug = getOrganizationSlug();\n\n const data = {\n apiToken: token ? `***${token.slice(-4)}` : \"not configured\",\n organizationSlug: orgSlug ?? \"not configured\",\n configured: !!token,\n };\n\n return {\n content: [\n {\n type: \"text\",\n text: JSON.stringify(data, null, 2),\n },\n ],\n structuredContent: data,\n };\n}\n\n/**\n * Options for handleToolCall.\n */\nexport interface HandleToolCallOptions {\n /** When true, forge_write is rejected with an error. */\n readOnly?: boolean;\n}\n\n/**\n * Handle a tool call request.\n *\n * Routes to the appropriate handler based on tool name:\n * - forge_configure / forge_get_config — stdio-only config tools\n * - forge — read-only operations (list, get, help, schema)\n * - forge_write — write operations (create, update, delete, deploy, etc.)\n */\nexport async function handleToolCall(\n name: string,\n args: Record<string, unknown>,\n options?: HandleToolCallOptions,\n): Promise<ToolResult> {\n if (name === \"forge_configure\") {\n return handleConfigureTool({\n apiToken: typeof args.apiToken === \"string\" ? args.apiToken : undefined,\n organizationSlug:\n typeof args.organizationSlug === \"string\" ? args.organizationSlug : undefined,\n });\n }\n\n if (name === \"forge_get_config\") {\n return handleGetConfigTool();\n }\n\n // Reject forge_write in read-only mode\n if (name === \"forge_write\" && options?.readOnly) {\n return {\n content: [\n {\n type: \"text\",\n text: \"Error: Server is running in read-only mode. Write operations are disabled.\",\n },\n ],\n structuredContent: {\n success: false,\n error: \"Server is running in read-only mode. Write operations are disabled.\",\n },\n isError: true,\n };\n }\n\n // Both forge and forge_write require authentication\n if (name === \"forge\" || name === \"forge_write\") {\n const apiToken = getToken();\n if (!apiToken) {\n return {\n content: [\n {\n type: \"text\",\n text: 'Error: Forge API token not configured. Use \"forge_configure\" tool or set FORGE_API_TOKEN environment variable.',\n },\n ],\n structuredContent: {\n success: false,\n error:\n 'Forge API token not configured. Use \"forge_configure\" tool or set FORGE_API_TOKEN environment variable.',\n },\n isError: true,\n };\n }\n\n // Organization slug can come from:\n // 1. Tool args (per-request override, highest priority)\n // 2. Config/env (via getOrganizationSlug)\n const orgSlugFromArgs =\n typeof args.organizationSlug === \"string\" ? args.organizationSlug : undefined;\n const orgSlugFromConfig = getOrganizationSlug() ?? undefined;\n\n return executeToolWithCredentials(name, args, {\n apiToken,\n organizationSlug: orgSlugFromArgs ?? orgSlugFromConfig,\n });\n }\n\n return {\n content: [\n {\n type: \"text\",\n text: `Error: Unknown tool \"${name}\".`,\n },\n ],\n structuredContent: { success: false, error: `Unknown tool \"${name}\".` },\n isError: true,\n };\n}\n","#!/usr/bin/env node\n/* eslint-disable typescript-eslint/no-deprecated -- Using low-level Server for StdioServerTransport */\n\n/**\n * Forge MCP Server — Stdio Transport\n *\n * This is the local execution mode using stdio transport.\n * For remote HTTP deployment, use server.ts instead.\n *\n * Usage:\n * npx @studiometa/forge-mcp\n * npx @studiometa/forge-mcp --read-only\n * FORGE_READ_ONLY=true npx @studiometa/forge-mcp\n *\n * Or in Claude Desktop config:\n * {\n * \"mcpServers\": {\n * \"forge\": {\n * \"command\": \"forge-mcp\",\n * \"args\": [\"--read-only\"],\n * \"env\": { \"FORGE_API_TOKEN\": \"your-token\" }\n * }\n * }\n * }\n */\n\n// Using low-level Server for advanced transport handling (StdioServerTransport)\n// eslint-disable-next-line typescript-eslint/no-deprecated\nimport { Server } from \"@modelcontextprotocol/sdk/server/index.js\";\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport {\n CallToolRequestSchema,\n ListToolsRequestSchema,\n type CallToolResult,\n} from \"@modelcontextprotocol/sdk/types.js\";\n\nimport { parseReadOnlyFlag } from \"./flags.ts\";\nimport { INSTRUCTIONS } from \"./instructions.ts\";\nimport { getAvailableTools, handleToolCall } from \"./stdio.ts\";\nimport { VERSION } from \"./version.ts\";\n\n// Re-export so consumers can still import from the main entry point\nexport { parseReadOnlyFlag } from \"./flags.ts\";\n\n/**\n * Options for the stdio MCP server.\n */\nexport interface StdioServerOptions {\n /** When true, forge_write tool is not registered and write operations are rejected. */\n readOnly?: boolean;\n}\n\n/**\n * Create and configure the MCP server.\n */\nexport function createStdioServer(options?: StdioServerOptions): Server {\n const readOnly = options?.readOnly ?? false;\n\n const server = new Server(\n {\n name: \"forge-mcp\",\n version: VERSION,\n },\n {\n capabilities: {\n tools: {},\n },\n instructions: INSTRUCTIONS,\n },\n );\n\n server.setRequestHandler(ListToolsRequestSchema, async () => {\n return { tools: getAvailableTools({ readOnly }) };\n });\n\n server.setRequestHandler(CallToolRequestSchema, async (request) => {\n const { name, arguments: args } = request.params;\n\n try {\n const result = await handleToolCall(name, args ?? {}, {\n readOnly,\n });\n return result as CallToolResult;\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n return {\n content: [{ type: \"text\" as const, text: `Error: ${message}` }],\n structuredContent: { success: false, error: message },\n isError: true,\n };\n }\n });\n\n return server;\n}\n\n/**\n * Start the stdio server.\n */\nexport async function startStdioServer(options?: StdioServerOptions): Promise<void> {\n const server = createStdioServer(options);\n const transport = new StdioServerTransport();\n await server.connect(transport);\n const mode = options?.readOnly ? \" (read-only)\" : \"\";\n console.error(`Forge MCP server v${VERSION} running on stdio${mode}`);\n}\n\n// Start server when run directly\nconst isMainModule =\n import.meta.url === `file://${process.argv[1]}` ||\n process.argv[1]?.endsWith(\"/forge-mcp\") ||\n process.argv[1]?.endsWith(\"\\\\forge-mcp\");\n\nif (isMainModule) {\n const readOnly = parseReadOnlyFlag();\n startStdioServer({ readOnly }).catch((error) => {\n console.error(\"Fatal error:\", error);\n process.exit(1);\n });\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/stdio.ts","../src/index.ts"],"sourcesContent":["import {\n getToken,\n getOrganizationSlug,\n setToken,\n setOrganizationSlug,\n} from \"@studiometa/forge-api\";\n\nimport type { ToolResult } from \"./handlers/types.ts\";\n\nimport { executeToolWithCredentials } from \"./handlers/index.ts\";\nimport type { Tool } from \"@modelcontextprotocol/sdk/types.js\";\nimport { getTools, STDIO_ONLY_TOOLS } from \"./tools.ts\";\nimport type { GetToolsOptions } from \"./tools.ts\";\n\nexport type { ToolResult };\n\n/**\n * Get all available tools (including stdio-only configuration tools).\n *\n * @param options - Optional filtering. When `readOnly` is true, forge_write is excluded.\n */\nexport function getAvailableTools(options?: GetToolsOptions): Tool[] {\n return [...getTools(options), ...STDIO_ONLY_TOOLS];\n}\n\n/**\n * Handle the forge_configure tool.\n */\nexport function handleConfigureTool(args: {\n apiToken?: string;\n organizationSlug?: string;\n}): ToolResult {\n if (!args.apiToken && !args.organizationSlug) {\n return {\n content: [\n {\n type: \"text\",\n text: \"Error: at least one of apiToken or organizationSlug is required.\",\n },\n ],\n structuredContent: {\n success: false,\n error: \"at least one of apiToken or organizationSlug is required.\",\n },\n isError: true,\n };\n }\n\n if (args.apiToken) {\n setToken(args.apiToken);\n }\n if (args.organizationSlug) {\n setOrganizationSlug(args.organizationSlug);\n }\n\n const maskedToken = args.apiToken ? `***${args.apiToken.slice(-4)}` : undefined;\n const data: Record<string, unknown> = {\n success: true,\n message: \"Laravel Forge configuration updated successfully\",\n };\n if (maskedToken) data.apiToken = maskedToken;\n if (args.organizationSlug) data.organizationSlug = args.organizationSlug;\n\n return {\n content: [\n {\n type: \"text\",\n text: JSON.stringify(data, null, 2),\n },\n ],\n structuredContent: data,\n };\n}\n\n/**\n * Handle the forge_get_config tool.\n */\nexport function handleGetConfigTool(): ToolResult {\n const token = getToken();\n const orgSlug = getOrganizationSlug();\n\n const data = {\n apiToken: token ? `***${token.slice(-4)}` : \"not configured\",\n organizationSlug: orgSlug ?? \"not configured\",\n configured: !!token,\n };\n\n return {\n content: [\n {\n type: \"text\",\n text: JSON.stringify(data, null, 2),\n },\n ],\n structuredContent: data,\n };\n}\n\n/**\n * Options for handleToolCall.\n */\nexport interface HandleToolCallOptions {\n /** When true, forge_write is rejected with an error. */\n readOnly?: boolean;\n}\n\n/**\n * Handle a tool call request.\n *\n * Routes to the appropriate handler based on tool name:\n * - forge_configure / forge_get_config — stdio-only config tools\n * - forge — read-only operations (list, get, help, schema)\n * - forge_write — write operations (create, update, delete, deploy, etc.)\n */\nexport async function handleToolCall(\n name: string,\n args: Record<string, unknown>,\n options?: HandleToolCallOptions,\n): Promise<ToolResult> {\n if (name === \"forge_configure\") {\n return handleConfigureTool({\n apiToken: typeof args.apiToken === \"string\" ? args.apiToken : undefined,\n organizationSlug:\n typeof args.organizationSlug === \"string\" ? args.organizationSlug : undefined,\n });\n }\n\n if (name === \"forge_get_config\") {\n return handleGetConfigTool();\n }\n\n // Reject forge_write in read-only mode\n if (name === \"forge_write\" && options?.readOnly) {\n return {\n content: [\n {\n type: \"text\",\n text: \"Error: Server is running in read-only mode. Write operations are disabled.\",\n },\n ],\n structuredContent: {\n success: false,\n error: \"Server is running in read-only mode. Write operations are disabled.\",\n },\n isError: true,\n };\n }\n\n // Both forge and forge_write require authentication\n if (name === \"forge\" || name === \"forge_write\") {\n const apiToken = getToken();\n if (!apiToken) {\n return {\n content: [\n {\n type: \"text\",\n text: 'Error: Forge API token not configured. Use \"forge_configure\" tool or set FORGE_API_TOKEN environment variable.',\n },\n ],\n structuredContent: {\n success: false,\n error:\n 'Forge API token not configured. Use \"forge_configure\" tool or set FORGE_API_TOKEN environment variable.',\n },\n isError: true,\n };\n }\n\n // Organization slug can come from:\n // 1. Tool args (per-request override, highest priority)\n // 2. Config/env (via getOrganizationSlug)\n const orgSlugFromArgs =\n typeof args.organizationSlug === \"string\" ? args.organizationSlug : undefined;\n const orgSlugFromConfig = getOrganizationSlug() ?? undefined;\n\n return executeToolWithCredentials(name, args, {\n apiToken,\n organizationSlug: orgSlugFromArgs ?? orgSlugFromConfig,\n });\n }\n\n return {\n content: [\n {\n type: \"text\",\n text: `Error: Unknown tool \"${name}\".`,\n },\n ],\n structuredContent: { success: false, error: `Unknown tool \"${name}\".` },\n isError: true,\n };\n}\n","#!/usr/bin/env node\n/* eslint-disable typescript-eslint/no-deprecated -- Using low-level Server for StdioServerTransport */\n\n/**\n * Forge MCP Server — Stdio Transport\n *\n * This is the local execution mode using stdio transport.\n * For remote HTTP deployment, use server.ts instead.\n *\n * Usage:\n * npx @studiometa/forge-mcp\n * npx @studiometa/forge-mcp --read-only\n * FORGE_READ_ONLY=true npx @studiometa/forge-mcp\n *\n * Or in Claude Desktop config:\n * {\n * \"mcpServers\": {\n * \"forge\": {\n * \"command\": \"forge-mcp\",\n * \"args\": [\"--read-only\"],\n * \"env\": { \"FORGE_API_TOKEN\": \"your-token\" }\n * }\n * }\n * }\n */\n\n// Using low-level Server for advanced transport handling (StdioServerTransport)\n// eslint-disable-next-line typescript-eslint/no-deprecated\nimport { Server } from \"@modelcontextprotocol/sdk/server/index.js\";\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport {\n CallToolRequestSchema,\n ListToolsRequestSchema,\n type CallToolResult,\n} from \"@modelcontextprotocol/sdk/types.js\";\n\nimport { parseReadOnlyFlag } from \"./flags.ts\";\nimport { INSTRUCTIONS } from \"./instructions.ts\";\nimport { getAvailableTools, handleToolCall } from \"./stdio.ts\";\nimport { VERSION } from \"./version.ts\";\n\n// Re-export so consumers can still import from the main entry point\nexport { parseReadOnlyFlag } from \"./flags.ts\";\n\n/**\n * Options for the stdio MCP server.\n */\nexport interface StdioServerOptions {\n /** When true, forge_write tool is not registered and write operations are rejected. */\n readOnly?: boolean;\n}\n\n/**\n * Create and configure the MCP server.\n */\nexport function createStdioServer(options?: StdioServerOptions): Server {\n const readOnly = options?.readOnly ?? false;\n\n const server = new Server(\n {\n name: \"forge-mcp\",\n version: VERSION,\n },\n {\n capabilities: {\n tools: {},\n },\n instructions: INSTRUCTIONS,\n },\n );\n\n server.setRequestHandler(ListToolsRequestSchema, async () => {\n return { tools: getAvailableTools({ readOnly }) };\n });\n\n server.setRequestHandler(CallToolRequestSchema, async (request) => {\n const { name, arguments: args } = request.params;\n\n try {\n const result = await handleToolCall(name, args ?? {}, {\n readOnly,\n });\n return result as CallToolResult;\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n return {\n content: [{ type: \"text\" as const, text: `Error: ${message}` }],\n structuredContent: { success: false, error: message },\n isError: true,\n };\n }\n });\n\n return server;\n}\n\n/**\n * Start the stdio server.\n */\nexport async function startStdioServer(options?: StdioServerOptions): Promise<void> {\n const server = createStdioServer(options);\n const transport = new StdioServerTransport();\n await server.connect(transport);\n const mode = options?.readOnly ? \" (read-only)\" : \"\";\n console.error(`Forge MCP server v${VERSION} running on stdio${mode}`);\n}\n\n// Start server when run directly\nconst isMainModule =\n import.meta.url === `file://${process.argv[1]}` ||\n process.argv[1]?.endsWith(\"/forge-mcp\") ||\n process.argv[1]?.endsWith(\"\\\\forge-mcp\");\n\nif (isMainModule) {\n const readOnly = parseReadOnlyFlag();\n startStdioServer({ readOnly }).catch((error) => {\n console.error(\"Fatal error:\", error);\n process.exit(1);\n });\n}\n"],"mappings":"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"}
package/dist/oauth.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { createAuthCode, decodeAuthCode } from "./crypto.js";
2
2
  import { createHash } from "node:crypto";
3
3
  import { defineEventHandler, getQuery, readBody } from "h3";
4
+ //#region src/oauth.ts
4
5
  /**
5
6
  * OAuth 2.1 endpoints for Claude Desktop integration
6
7
  *
@@ -37,7 +38,7 @@ function createAccessToken(apiToken, organizationSlug) {
37
38
  *
38
39
  * MCP clients MUST check this endpoint first for server capabilities.
39
40
  */
40
- const oauthMetadataHandler = defineEventHandler((event) => {
41
+ var oauthMetadataHandler = defineEventHandler((event) => {
41
42
  const host = event.req.headers.get("host") || "localhost:3000";
42
43
  const baseUrl = `${event.req.headers.get("x-forwarded-proto") || "http"}://${host}`;
43
44
  event.res.headers.set("Content-Type", "application/json");
@@ -61,7 +62,7 @@ const oauthMetadataHandler = defineEventHandler((event) => {
61
62
  *
62
63
  * Tells MCP clients where to find the OAuth authorization server.
63
64
  */
64
- const protectedResourceHandler = defineEventHandler((event) => {
65
+ var protectedResourceHandler = defineEventHandler((event) => {
65
66
  const host = event.req.headers.get("host") || "localhost:3000";
66
67
  const baseUrl = `${event.req.headers.get("x-forwarded-proto") || "http"}://${host}`;
67
68
  event.res.headers.set("Content-Type", "application/json");
@@ -81,7 +82,7 @@ const protectedResourceHandler = defineEventHandler((event) => {
81
82
  * Since we use stateless tokens, we accept any registration and return
82
83
  * a generated client_id.
83
84
  */
84
- const registerHandler = defineEventHandler(async (event) => {
85
+ var registerHandler = defineEventHandler(async (event) => {
85
86
  event.res.headers.set("Content-Type", "application/json");
86
87
  let body;
87
88
  try {
@@ -114,7 +115,7 @@ const registerHandler = defineEventHandler(async (event) => {
114
115
  * Authorization endpoint — shows login form
115
116
  * GET /authorize
116
117
  */
117
- const authorizeGetHandler = defineEventHandler((event) => {
118
+ var authorizeGetHandler = defineEventHandler((event) => {
118
119
  const query = getQuery(event);
119
120
  const redirectUri = String(query.redirect_uri ?? "");
120
121
  const state = String(query.state ?? "");
@@ -151,7 +152,7 @@ const authorizeGetHandler = defineEventHandler((event) => {
151
152
  * Authorization endpoint — process login
152
153
  * POST /authorize
153
154
  */
154
- const authorizePostHandler = defineEventHandler(async (event) => {
155
+ var authorizePostHandler = defineEventHandler(async (event) => {
155
156
  const { apiToken, organizationSlug, redirectUri, state, codeChallenge, codeChallengeMethod } = await readBody(event) ?? {};
156
157
  if (!redirectUri) {
157
158
  event.res.headers.set("Content-Type", "text/html; charset=utf-8");
@@ -201,7 +202,7 @@ const authorizePostHandler = defineEventHandler(async (event) => {
201
202
  * - authorization_code grant (with PKCE validation)
202
203
  * - refresh_token grant
203
204
  */
204
- const tokenHandler = defineEventHandler(async (event) => {
205
+ var tokenHandler = defineEventHandler(async (event) => {
205
206
  event.res.headers.set("Content-Type", "application/json");
206
207
  const { grant_type, code, code_verifier, refresh_token } = await readBody(event) ?? {};
207
208
  if (grant_type === "refresh_token") return handleRefreshToken(event, refresh_token);
@@ -587,6 +588,7 @@ function renderErrorPage(message) {
587
588
  </body>
588
589
  </html>`;
589
590
  }
591
+ //#endregion
590
592
  export { authorizeGetHandler, authorizePostHandler, createAccessToken, createS256Challenge, oauthMetadataHandler, protectedResourceHandler, registerHandler, tokenHandler };
591
593
 
592
594
  //# sourceMappingURL=oauth.js.map