@mentagen/mcp 0.8.5 → 0.8.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.
@@ -1,6 +1,7 @@
1
1
  import { z } from 'zod';
2
2
  import { nodeColorSchema } from '../utils/colors.js';
3
3
  import { formatError } from '../utils/errors.js';
4
+ import { normalizeTextEscapes } from '../utils/normalize.js';
4
5
  import { unitsToPixels } from '../utils/units.js';
5
6
  import { snapToGrid } from './grid-calc.js';
6
7
  import { validateTypeChange } from './update.js';
@@ -83,7 +84,14 @@ function buildNodeUpdateData(data, migrationFields) {
83
84
  cleanData[key] = snapToGrid(unitsToPixels(value));
84
85
  }
85
86
  else if (key === 'todos') {
86
- cleanData[key] = value;
87
+ const todos = value.map(t => ({
88
+ ...t,
89
+ text: normalizeTextEscapes(t.text),
90
+ }));
91
+ cleanData[key] = todos;
92
+ }
93
+ else if (key === 'content') {
94
+ cleanData[key] = normalizeTextEscapes(value);
87
95
  }
88
96
  else {
89
97
  cleanData[key] = value;
@@ -1,6 +1,7 @@
1
1
  import { z } from 'zod';
2
2
  import { nodeColorSchema } from '../utils/colors.js';
3
3
  import { formatError } from '../utils/errors.js';
4
+ import { normalizeTextEscapes } from '../utils/normalize.js';
4
5
  import { pixelsToUnits, unitsToPixels } from '../utils/units.js';
5
6
  import { applyExtension, generateId, normalizeTodos } from './create.js';
6
7
  import { snapToGrid } from './grid-calc.js';
@@ -81,7 +82,8 @@ async function createNode(client, boardId, nodeInput, idMap) {
81
82
  const autoSize = calculateNodeSize(finalName, type);
82
83
  const dims = calcNodeDimensions(nodeInput, autoSize);
83
84
  const isCodeNode = type === 'code';
84
- const nodeContent = nodeInput.content ?? '';
85
+ const rawContent = nodeInput.content ?? '';
86
+ const nodeContent = isCodeNode ? rawContent : normalizeTextEscapes(rawContent);
85
87
  // Only pass todos for non-code nodes
86
88
  const todos = !isCodeNode && nodeInput.todos ? normalizeTodos(nodeInput.todos) : undefined;
87
89
  try {
@@ -2,6 +2,7 @@ import { randomUUID } from 'crypto';
2
2
  import { z } from 'zod';
3
3
  import { nodeColorSchema } from '../utils/colors.js';
4
4
  import { formatError } from '../utils/errors.js';
5
+ import { normalizeTextEscapes } from '../utils/normalize.js';
5
6
  import { pixelsToUnits, unitsToPixels } from '../utils/units.js';
6
7
  import { getNodeUrl } from '../utils/urls.js';
7
8
  import { snapToGrid } from './grid-calc.js';
@@ -113,7 +114,7 @@ export function generateId() {
113
114
  export function normalizeTodos(todos) {
114
115
  return todos.map((todo, index) => ({
115
116
  id: randomUUID(),
116
- text: todo.text,
117
+ text: normalizeTextEscapes(todo.text),
117
118
  completed: todo.completed ?? false,
118
119
  order: index,
119
120
  }));
@@ -139,14 +140,14 @@ function buildNodeFields(params, color, position) {
139
140
  }
140
141
  nodeFields.url = params.url;
141
142
  if (params.content) {
142
- nodeFields.content = params.content;
143
+ nodeFields.content = normalizeTextEscapes(params.content);
143
144
  }
144
145
  if (params.todos) {
145
146
  nodeFields.todos = normalizeTodos(params.todos);
146
147
  }
147
148
  }
148
149
  else {
149
- nodeFields.content = params.content || '';
150
+ nodeFields.content = normalizeTextEscapes(params.content || '');
150
151
  if (params.todos) {
151
152
  nodeFields.todos = normalizeTodos(params.todos);
152
153
  }
@@ -1,6 +1,7 @@
1
1
  import { z } from 'zod';
2
2
  import { NodeType } from '../client/types.js';
3
3
  import { formatError } from '../utils/errors.js';
4
+ import { normalizeTextEscapes } from '../utils/normalize.js';
4
5
  const replaceOp = z.object({
5
6
  type: z.literal('replace'),
6
7
  oldString: z.string().min(1),
@@ -68,12 +69,20 @@ export async function handlePatchContent(client, params) {
68
69
  nodeId: params.nodeId,
69
70
  });
70
71
  const { field, text: initialText } = getNodeTextField(node);
72
+ const isCode = field === 'code';
71
73
  let text = initialText;
72
74
  const appliedOps = [];
75
+ /** Normalize escape sequences for non-code content fields. */
76
+ const normalize = (s) => isCode ? s : normalizeTextEscapes(s);
73
77
  for (const op of params.operations) {
74
78
  switch (op.type) {
75
79
  case 'replace': {
76
- const { result, error } = applyReplace(text, op);
80
+ const normalizedOp = {
81
+ ...op,
82
+ oldString: normalize(op.oldString),
83
+ newString: normalize(op.newString),
84
+ };
85
+ const { result, error } = applyReplace(text, normalizedOp);
77
86
  if (error) {
78
87
  return {
79
88
  content: [
@@ -95,14 +104,18 @@ export async function handlePatchContent(client, params) {
95
104
  appliedOps.push(`replace: "${truncate(op.oldString, 30)}" → "${truncate(op.newString, 30)}"`);
96
105
  break;
97
106
  }
98
- case 'prepend':
99
- text = op.content + text;
100
- appliedOps.push(`prepend: ${op.content.length} chars`);
107
+ case 'prepend': {
108
+ const content = normalize(op.content);
109
+ text = content + text;
110
+ appliedOps.push(`prepend: ${content.length} chars`);
101
111
  break;
102
- case 'append':
103
- text = text + op.content;
104
- appliedOps.push(`append: ${op.content.length} chars`);
112
+ }
113
+ case 'append': {
114
+ const content = normalize(op.content);
115
+ text = text + content;
116
+ appliedOps.push(`append: ${content.length} chars`);
105
117
  break;
118
+ }
106
119
  }
107
120
  }
108
121
  await client.updateNode({
@@ -1,6 +1,7 @@
1
1
  import { randomUUID } from 'crypto';
2
2
  import { z } from 'zod';
3
3
  import { formatError } from '../utils/errors.js';
4
+ import { normalizeTextEscapes } from '../utils/normalize.js';
4
5
  // ─────────────────────────────────────────────────────────────────────────────
5
6
  // Schemas
6
7
  // ─────────────────────────────────────────────────────────────────────────────
@@ -36,7 +37,7 @@ export async function handleAddTodo(client, params) {
36
37
  const existingTodos = node.todos || [];
37
38
  const newTodo = {
38
39
  id: randomUUID(),
39
- text: params.text,
40
+ text: normalizeTextEscapes(params.text),
40
41
  completed: false,
41
42
  order: existingTodos.length,
42
43
  };
@@ -2,6 +2,7 @@ import { z } from 'zod';
2
2
  import { NodeType } from '../client/types.js';
3
3
  import { nodeColorSchema } from '../utils/colors.js';
4
4
  import { formatError } from '../utils/errors.js';
5
+ import { normalizeTextEscapes } from '../utils/normalize.js';
5
6
  import { pixelsToUnits, unitsToPixels } from '../utils/units.js';
6
7
  import { snapToGrid } from './grid-calc.js';
7
8
  import { calculateNodeSize } from './size-calc.js';
@@ -132,11 +133,11 @@ function addContentFields(data, params, nodeType) {
132
133
  data.url = params.url;
133
134
  }
134
135
  if (params.content !== undefined) {
135
- data.content = params.content;
136
+ data.content = normalizeTextEscapes(params.content);
136
137
  }
137
138
  }
138
139
  else if (params.content !== undefined) {
139
- data.content = params.content;
140
+ data.content = normalizeTextEscapes(params.content);
140
141
  }
141
142
  }
142
143
  /**
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Normalize literal escape sequences commonly produced by LLMs in MCP tool parameters.
3
+ *
4
+ * LLMs often write `\n` as two literal characters (backslash + n) instead of
5
+ * an actual newline character. This converts those sequences for text content fields.
6
+ *
7
+ * Should NOT be applied to code fields where escape sequences are intentional.
8
+ */
9
+ export function normalizeTextEscapes(text) {
10
+ return text.replaceAll('\\n', '\n').replaceAll('\\t', '\t');
11
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mentagen/mcp",
3
- "version": "0.8.5",
3
+ "version": "0.8.6",
4
4
  "description": "MCP server for Mentagen knowledge base integration with Cursor",
5
5
  "type": "module",
6
6
  "bin": "dist/index.js",