@productmaker/mcp 8.3.0 → 8.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -268,6 +268,10 @@ The `base64` field was removed. MCP JSON-RPC transports truncate large tool argu
268
268
 
269
269
  ## Version history
270
270
 
271
+ ### 8.3.1 — Refresh token garantizado
272
+
273
+ - OAuth alojado: el proxy `/oauth/authorize` añade `offline_access` al scope antes de redirigir a Auth0. Los hosts que siguen el spec piden solo `mcp:access` (lo que anuncian el challenge y la metadata del recurso) y sin esto Auth0 nunca emitía refresh token.
274
+
271
275
  ### 8.3.0 — Idempotencia de tools pagadas + refresh OAuth
272
276
 
273
277
  - Tools pagadas (`create_product_task`, `create_service_video`, `generate_video_creative`, `generate_free_video`, `generate_image_creatives`, `generate_landing`, `create_creative_variant`): envían un `Idempotency-Key` derivado de los argumentos semánticos (ventana de 15 min). Un reintento con los mismos argumentos devuelve la misma tarea (`replayed: true`) sin segundo cargo; una tarea en `error` sí se reintenta.
package/dist/http.js CHANGED
@@ -16815,6 +16815,121 @@ var Auth0TokenVerifier = class {
16815
16815
  }
16816
16816
  };
16817
16817
 
16818
+ // src/oauth-analytics.ts
16819
+ import { PostHog } from "posthog-node";
16820
+ var DEFAULT_POSTHOG_HOST = "https://us.i.posthog.com";
16821
+ var REDACTED = "[REDACTED]";
16822
+ var SENSITIVE_KEYS = /* @__PURE__ */ new Set([
16823
+ "token",
16824
+ "authorization",
16825
+ "apikey",
16826
+ "clientsecret",
16827
+ "subjecttoken",
16828
+ "accesstoken",
16829
+ "refreshtoken",
16830
+ "idtoken",
16831
+ "code",
16832
+ "secret",
16833
+ "password"
16834
+ ]);
16835
+ var SENSITIVE_ASSIGNMENT_PATTERN = /(\b(?:access[_-]?token|authorization|client[_-]?secret|subject[_-]?token|refresh[_-]?token|id[_-]?token|api[_-]?key|token|secret|password|code)\b\s*[:=]\s*)(?:(bearer)\s+)?(?:(['"])(.*?)\3|([^\s,;&}"']+))/gi;
16836
+ var BEARER_TOKEN_PATTERN = /\b(bearer\s+)([A-Za-z0-9._~+/=-]+)/gi;
16837
+ var client = null;
16838
+ var KNOWN_MCP_OAUTH_ANALYTICS_EVENTS = [
16839
+ MCP_OAUTH_ANALYTICS_EVENTS.STARTED,
16840
+ MCP_OAUTH_ANALYTICS_EVENTS.CONNECTED,
16841
+ MCP_OAUTH_ANALYTICS_EVENTS.FAILED,
16842
+ MCP_OAUTH_ANALYTICS_EVENTS.SAFE_VARIANT
16843
+ ];
16844
+ function getPostHogClient() {
16845
+ if (client) return client;
16846
+ const apiKey = process.env.POSTHOG_API_KEY;
16847
+ if (!apiKey) return null;
16848
+ client = new PostHog(apiKey, {
16849
+ host: process.env.POSTHOG_HOST || DEFAULT_POSTHOG_HOST,
16850
+ flushAt: 20,
16851
+ flushInterval: 1e4
16852
+ });
16853
+ return client;
16854
+ }
16855
+ __name(getPostHogClient, "getPostHogClient");
16856
+ function normalizeKey(key) {
16857
+ return key.replace(/[^a-z0-9]/gi, "").toLowerCase();
16858
+ }
16859
+ __name(normalizeKey, "normalizeKey");
16860
+ function isSensitiveKey(key) {
16861
+ const normalizedKey = normalizeKey(key);
16862
+ return SENSITIVE_KEYS.has(normalizedKey) || normalizedKey.endsWith("token") || normalizedKey.endsWith("secret");
16863
+ }
16864
+ __name(isSensitiveKey, "isSensitiveKey");
16865
+ function sanitizeUrl(value) {
16866
+ try {
16867
+ const parsed = new URL(value);
16868
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
16869
+ return value;
16870
+ }
16871
+ parsed.username = "";
16872
+ parsed.password = "";
16873
+ parsed.search = "";
16874
+ parsed.hash = "";
16875
+ return parsed.toString();
16876
+ } catch {
16877
+ return value;
16878
+ }
16879
+ }
16880
+ __name(sanitizeUrl, "sanitizeUrl");
16881
+ function sanitizeString(value) {
16882
+ const redacted = value.replace(SENSITIVE_ASSIGNMENT_PATTERN, (_match, prefix, scheme, quote) => {
16883
+ const replacement = quote ? `${quote}${REDACTED}${quote}` : REDACTED;
16884
+ return `${prefix}${scheme ? `${scheme} ` : ""}${replacement}`;
16885
+ }).replace(BEARER_TOKEN_PATTERN, `$1${REDACTED}`);
16886
+ const sanitizedUrl = sanitizeUrl(redacted);
16887
+ if (sanitizedUrl !== redacted) return sanitizedUrl;
16888
+ return redacted.replace(/https?:\/\/[^\s"'<>]+/gi, sanitizeUrl);
16889
+ }
16890
+ __name(sanitizeString, "sanitizeString");
16891
+ function sanitizeValue(value) {
16892
+ if (Array.isArray(value)) {
16893
+ return value.map((entry) => sanitizeValue(entry));
16894
+ }
16895
+ if (value && typeof value === "object") {
16896
+ return Object.fromEntries(Object.entries(value).map(([key, entry]) => {
16897
+ if (isSensitiveKey(key)) {
16898
+ return [
16899
+ key,
16900
+ REDACTED
16901
+ ];
16902
+ }
16903
+ return [
16904
+ key,
16905
+ sanitizeValue(entry)
16906
+ ];
16907
+ }));
16908
+ }
16909
+ if (typeof value === "string") {
16910
+ return sanitizeString(value);
16911
+ }
16912
+ return value;
16913
+ }
16914
+ __name(sanitizeValue, "sanitizeValue");
16915
+ var OAuthAnalytics = class {
16916
+ static {
16917
+ __name(this, "OAuthAnalytics");
16918
+ }
16919
+ capture(event, subject, properties) {
16920
+ const posthog = getPostHogClient();
16921
+ if (!posthog) return;
16922
+ try {
16923
+ posthog.capture({
16924
+ distinctId: subject ?? "mcp_oauth_aggregate",
16925
+ event,
16926
+ properties: sanitizeValue(properties)
16927
+ });
16928
+ } catch {
16929
+ }
16930
+ }
16931
+ };
16932
+
16818
16933
  // src/auth/oauth-proxy.ts
16819
16934
  import { createHmac as createHmac2, hkdfSync, timingSafeEqual as timingSafeEqual2 } from "crypto";
16820
16935
  import express from "express";
@@ -16901,6 +17016,15 @@ var OAUTH_PROXY_PATHS = {
16901
17016
  token: "/oauth/token"
16902
17017
  };
16903
17018
  var PROXY_CALLBACK_URL = new URL(OAUTH_PROXY_PATHS.callback.slice(1), STATIC_CLIENT_ISSUER).href;
17019
+ function responseIssuesRefreshToken(payload) {
17020
+ try {
17021
+ const parsed = JSON.parse(payload);
17022
+ return typeof parsed === "object" && parsed !== null && "refresh_token" in parsed && typeof parsed.refresh_token === "string" && parsed.refresh_token !== "";
17023
+ } catch {
17024
+ return false;
17025
+ }
17026
+ }
17027
+ __name(responseIssuesRefreshToken, "responseIssuesRefreshToken");
16904
17028
  function firstQueryValue(value) {
16905
17029
  if (typeof value === "string") return value;
16906
17030
  if (Array.isArray(value) && typeof value[0] === "string") return value[0];
@@ -16914,6 +17038,12 @@ function badRequest(res, description) {
16914
17038
  });
16915
17039
  }
16916
17040
  __name(badRequest, "badRequest");
17041
+ function withRefreshScope(requested) {
17042
+ const scopes = (requested?.trim() ? requested : MCP_SCOPE).split(/\s+/).filter(Boolean);
17043
+ if (!scopes.includes(REFRESH_SCOPE)) scopes.push(REFRESH_SCOPE);
17044
+ return scopes.join(" ");
17045
+ }
17046
+ __name(withRefreshScope, "withRefreshScope");
16917
17047
  function createOAuthProxyRouter(options) {
16918
17048
  const router = express.Router();
16919
17049
  const stateKey3 = deriveStateKey(options.staticClient.clientSecret);
@@ -16937,13 +17067,14 @@ function createOAuthProxyRouter(options) {
16937
17067
  }
16938
17068
  const upstream = new URL(options.authorizationEndpoint);
16939
17069
  for (const [key, value] of Object.entries(req.query)) {
16940
- if (key === "redirect_uri" || key === "state") continue;
17070
+ if (key === "redirect_uri" || key === "state" || key === "scope") continue;
16941
17071
  for (const v of Array.isArray(value) ? value : [
16942
17072
  value
16943
17073
  ]) {
16944
17074
  if (typeof v === "string") upstream.searchParams.append(key, v);
16945
17075
  }
16946
17076
  }
17077
+ upstream.searchParams.set("scope", withRefreshScope(firstQueryValue(req.query.scope)));
16947
17078
  upstream.searchParams.set("redirect_uri", PROXY_CALLBACK_URL);
16948
17079
  upstream.searchParams.set("state", sealState(stateKey3, {
16949
17080
  redirectUri,
@@ -17023,6 +17154,14 @@ function createOAuthProxyRouter(options) {
17023
17154
  });
17024
17155
  return;
17025
17156
  }
17157
+ try {
17158
+ options.onTokenExchange?.({
17159
+ grantType,
17160
+ status: upstream.status,
17161
+ refreshTokenIssued: responseIssuesRefreshToken(payload)
17162
+ });
17163
+ } catch {
17164
+ }
17026
17165
  res.status(upstream.status).type(upstream.headers.get("content-type") ?? "application/json").send(payload);
17027
17166
  });
17028
17167
  return router;
@@ -17164,11 +17303,23 @@ function createMetadataRouter(config, metadata, fetchFn) {
17164
17303
  staticClient: config.staticClient,
17165
17304
  authorizationEndpoint: metadata.authorization_endpoint,
17166
17305
  tokenEndpoint: metadata.token_endpoint,
17167
- fetchFn
17306
+ fetchFn,
17307
+ onTokenExchange: captureTokenExchange
17168
17308
  }));
17169
17309
  return router;
17170
17310
  }
17171
17311
  __name(createMetadataRouter, "createMetadataRouter");
17312
+ var tokenExchangeAnalytics = new OAuthAnalytics();
17313
+ function captureTokenExchange(outcome) {
17314
+ const ok = outcome.status >= 200 && outcome.status < 300;
17315
+ tokenExchangeAnalytics.capture(ok ? MCP_OAUTH_ANALYTICS_EVENTS.CONNECTED : MCP_OAUTH_ANALYTICS_EVENTS.FAILED, null, {
17316
+ stage: "token_exchange",
17317
+ grantType: outcome.grantType,
17318
+ status: outcome.status,
17319
+ refreshTokenIssued: outcome.refreshTokenIssued
17320
+ });
17321
+ }
17322
+ __name(captureTokenExchange, "captureTokenExchange");
17172
17323
  function createHttpOAuth(config, metadata, fetchFn) {
17173
17324
  const verifier = new Auth0TokenVerifier(config, metadata.jwks_uri);
17174
17325
  const oboClient = new Auth0OboClient(config, {
@@ -17447,121 +17598,6 @@ function registerProductMakerPrompts(server) {
17447
17598
  }
17448
17599
  __name(registerProductMakerPrompts, "registerProductMakerPrompts");
17449
17600
 
17450
- // src/oauth-analytics.ts
17451
- import { PostHog } from "posthog-node";
17452
- var DEFAULT_POSTHOG_HOST = "https://us.i.posthog.com";
17453
- var REDACTED = "[REDACTED]";
17454
- var SENSITIVE_KEYS = /* @__PURE__ */ new Set([
17455
- "token",
17456
- "authorization",
17457
- "apikey",
17458
- "clientsecret",
17459
- "subjecttoken",
17460
- "accesstoken",
17461
- "refreshtoken",
17462
- "idtoken",
17463
- "code",
17464
- "secret",
17465
- "password"
17466
- ]);
17467
- var SENSITIVE_ASSIGNMENT_PATTERN = /(\b(?:access[_-]?token|authorization|client[_-]?secret|subject[_-]?token|refresh[_-]?token|id[_-]?token|api[_-]?key|token|secret|password|code)\b\s*[:=]\s*)(?:(bearer)\s+)?(?:(['"])(.*?)\3|([^\s,;&}"']+))/gi;
17468
- var BEARER_TOKEN_PATTERN = /\b(bearer\s+)([A-Za-z0-9._~+/=-]+)/gi;
17469
- var client = null;
17470
- var KNOWN_MCP_OAUTH_ANALYTICS_EVENTS = [
17471
- MCP_OAUTH_ANALYTICS_EVENTS.STARTED,
17472
- MCP_OAUTH_ANALYTICS_EVENTS.CONNECTED,
17473
- MCP_OAUTH_ANALYTICS_EVENTS.FAILED,
17474
- MCP_OAUTH_ANALYTICS_EVENTS.SAFE_VARIANT
17475
- ];
17476
- function getPostHogClient() {
17477
- if (client) return client;
17478
- const apiKey = process.env.POSTHOG_API_KEY;
17479
- if (!apiKey) return null;
17480
- client = new PostHog(apiKey, {
17481
- host: process.env.POSTHOG_HOST || DEFAULT_POSTHOG_HOST,
17482
- flushAt: 20,
17483
- flushInterval: 1e4
17484
- });
17485
- return client;
17486
- }
17487
- __name(getPostHogClient, "getPostHogClient");
17488
- function normalizeKey(key) {
17489
- return key.replace(/[^a-z0-9]/gi, "").toLowerCase();
17490
- }
17491
- __name(normalizeKey, "normalizeKey");
17492
- function isSensitiveKey(key) {
17493
- const normalizedKey = normalizeKey(key);
17494
- return SENSITIVE_KEYS.has(normalizedKey) || normalizedKey.endsWith("token") || normalizedKey.endsWith("secret");
17495
- }
17496
- __name(isSensitiveKey, "isSensitiveKey");
17497
- function sanitizeUrl(value) {
17498
- try {
17499
- const parsed = new URL(value);
17500
- if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
17501
- return value;
17502
- }
17503
- parsed.username = "";
17504
- parsed.password = "";
17505
- parsed.search = "";
17506
- parsed.hash = "";
17507
- return parsed.toString();
17508
- } catch {
17509
- return value;
17510
- }
17511
- }
17512
- __name(sanitizeUrl, "sanitizeUrl");
17513
- function sanitizeString(value) {
17514
- const redacted = value.replace(SENSITIVE_ASSIGNMENT_PATTERN, (_match, prefix, scheme, quote) => {
17515
- const replacement = quote ? `${quote}${REDACTED}${quote}` : REDACTED;
17516
- return `${prefix}${scheme ? `${scheme} ` : ""}${replacement}`;
17517
- }).replace(BEARER_TOKEN_PATTERN, `$1${REDACTED}`);
17518
- const sanitizedUrl = sanitizeUrl(redacted);
17519
- if (sanitizedUrl !== redacted) return sanitizedUrl;
17520
- return redacted.replace(/https?:\/\/[^\s"'<>]+/gi, sanitizeUrl);
17521
- }
17522
- __name(sanitizeString, "sanitizeString");
17523
- function sanitizeValue(value) {
17524
- if (Array.isArray(value)) {
17525
- return value.map((entry) => sanitizeValue(entry));
17526
- }
17527
- if (value && typeof value === "object") {
17528
- return Object.fromEntries(Object.entries(value).map(([key, entry]) => {
17529
- if (isSensitiveKey(key)) {
17530
- return [
17531
- key,
17532
- REDACTED
17533
- ];
17534
- }
17535
- return [
17536
- key,
17537
- sanitizeValue(entry)
17538
- ];
17539
- }));
17540
- }
17541
- if (typeof value === "string") {
17542
- return sanitizeString(value);
17543
- }
17544
- return value;
17545
- }
17546
- __name(sanitizeValue, "sanitizeValue");
17547
- var OAuthAnalytics = class {
17548
- static {
17549
- __name(this, "OAuthAnalytics");
17550
- }
17551
- capture(event, subject, properties) {
17552
- const posthog = getPostHogClient();
17553
- if (!posthog) return;
17554
- try {
17555
- posthog.capture({
17556
- distinctId: subject ?? "mcp_oauth_aggregate",
17557
- event,
17558
- properties: sanitizeValue(properties)
17559
- });
17560
- } catch {
17561
- }
17562
- }
17563
- };
17564
-
17565
17601
  // src/server.ts
17566
17602
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
17567
17603
 
@@ -17749,7 +17785,7 @@ var ApiClient = class {
17749
17785
  };
17750
17786
 
17751
17787
  // src/index.ts
17752
- var MCP_VERSION = "8.3.0";
17788
+ var MCP_VERSION = "8.3.1";
17753
17789
 
17754
17790
  // src/meta-upload-app.ts
17755
17791
  import { readFileSync } from "fs";
package/dist/stdio.js CHANGED
@@ -16117,7 +16117,7 @@ var ApiClient = class {
16117
16117
  };
16118
16118
 
16119
16119
  // src/index.ts
16120
- var MCP_VERSION = "8.3.0";
16120
+ var MCP_VERSION = "8.3.1";
16121
16121
 
16122
16122
  // src/meta-upload-app.ts
16123
16123
  import { readFileSync } from "fs";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@productmaker/mcp",
3
- "version": "8.3.0",
3
+ "version": "8.3.1",
4
4
  "type": "module",
5
5
  "private": false,
6
6
  "description": "ProductMaker MCP server — cost products, generate landing pages, UGC videos, and ad creatives, and publish to Shopify, Meta Ads, and TikTok Ads.",