@privacyscrubber/mcp-server 2.2.1 → 2.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/scrubber-core.cjs CHANGED
@@ -119,6 +119,32 @@ function scrubText(text, customRules = [], tokenLabelMap = {}, profile = 'Genera
119
119
  });
120
120
  }
121
121
 
122
+ // Pre-scan input text for pre-existing tokens ([TYPE_N], <TYPE_N>, {{TYPE_N}}, __TYPE_N__)
123
+ // to advance counters and avoid counter collisions or overwriting literal quoted tokens
124
+ if (typeof textToProcess === 'string' && textToProcess) {
125
+ const existingTokenRegex = /(?:\[|<|\{\{|__)([A-Za-z0-9_]+)_(\d+)(?:\]|>|\}\}|__)/g;
126
+ let tMatch;
127
+ while ((tMatch = existingTokenRegex.exec(textToProcess)) !== null) {
128
+ const rawLabel = tMatch[1].toUpperCase();
129
+ const idx = parseInt(tMatch[2], 10);
130
+ if (!isNaN(idx)) {
131
+ let mappedType = rawLabel;
132
+ for (const [baseType, customLabel] of Object.entries(tokenLabelMap || {})) {
133
+ if (String(customLabel).replace(/[^A-Za-z0-9_]/g, '_').toUpperCase() === rawLabel) {
134
+ mappedType = baseType;
135
+ break;
136
+ }
137
+ }
138
+ mappedType = resolveTypeFromTokenLabel(mappedType);
139
+ if (counters[mappedType] !== undefined) {
140
+ counters[mappedType] = Math.max(counters[mappedType], idx);
141
+ } else {
142
+ customCounters[mappedType] = Math.max(customCounters[mappedType] || 0, idx);
143
+ }
144
+ }
145
+ }
146
+ }
147
+
122
148
 
123
149
  // Default labels
124
150
  const labels = {
@@ -289,6 +315,15 @@ function getLabelAliases(label) {
289
315
  return Array.from(aliases);
290
316
  }
291
317
 
318
+ function resolveTypeFromTokenLabel(label) {
319
+ if (!label) return 'CUSTOM';
320
+ const upper = String(label).toUpperCase();
321
+ for (const [type, aliases] of Object.entries(LABEL_ALIASES)) {
322
+ if (aliases.includes(upper)) return type;
323
+ }
324
+ return upper;
325
+ }
326
+
292
327
  function formatToken(label, index, format = 'brackets') {
293
328
  const cleanLabel = String(label || 'PII').replace(/[^A-Za-z0-9_]/g, '_').toUpperCase();
294
329
  switch(format) {
@@ -1090,6 +1125,16 @@ function hydrateRegex(rule) {
1090
1125
  }
1091
1126
  }
1092
1127
 
1128
+ // Fuse adjacent text nodes that were split across chunk boundaries during LLM streaming mutations
1129
+ try {
1130
+ const assistantContainers = document.querySelectorAll(ASSISTANT_SELECTORS);
1131
+ for (let i = 0; i < assistantContainers.length; i++) {
1132
+ if (typeof assistantContainers[i].normalize === 'function') {
1133
+ assistantContainers[i].normalize();
1134
+ }
1135
+ }
1136
+ } catch (_) {}
1137
+
1093
1138
  walkTextNodes(document.documentElement);
1094
1139
 
1095
1140
  // TOGGLE RE-MASK: If no unmasked tokens were found on screen, but there are already revealed .ps-restored-data spans, re-mask them back to tokens
package/server.json CHANGED
@@ -6,12 +6,12 @@
6
6
  "url": "https://github.com/moxno/privacyscrubber-mcp",
7
7
  "source": "github"
8
8
  },
9
- "version": "2.2.1",
9
+ "version": "2.2.2",
10
10
  "packages": [
11
11
  {
12
12
  "registryType": "npm",
13
13
  "identifier": "@privacyscrubber/mcp-server",
14
- "version": "2.2.1",
14
+ "version": "2.2.2",
15
15
  "transport": {
16
16
  "type": "stdio"
17
17
  },