@memory-bank/mcp
Version:
Memory-enabled Co-Pilot (MCP) server for managing project documentation and context
118 lines • 4.43 kB
JavaScript
/**
* Utilities and safety measures for JSON Patch (RFC 6902) operations
*/
/**
* Generate JSON Patch property definitions, including clear warnings and validation rules
*/
export function createEnhancedPatchProperties() {
return {
patches: {
type: 'array',
description: 'JSON Patch operations (RFC 6902) - IMPORTANT: See warnings below',
items: {
type: 'object',
properties: {
op: {
type: 'string',
enum: ['add', 'remove', 'replace', 'move', 'copy', 'test'],
description: 'Operation type - must be one of these 6 values'
},
path: {
type: 'string',
description: 'JSON Pointer path - MUST start with "/" (e.g. "/metadata/title")'
},
value: {
description: 'Value for add, replace, test operations - required for these operations'
},
from: {
type: 'string',
description: 'Source path for move, copy operations - MUST start with "/" (e.g. "/oldPath")'
}
},
required: ['op', 'path']
},
warnings: [
"WARNING: Paths MUST start with '/'",
"WARNING: '/' within paths MUST be escaped as '~1'",
"WARNING: '~' within paths MUST be escaped as '~0'",
"WARNING: 'move'/'copy' operations require a 'from' property",
"WARNING: 'add'/'replace'/'test' operations require a 'value' property",
"WARNING: 'move' operation cannot target a descendant of its source path"
]
},
content: {
type: 'string',
description: 'Full document content (JSON string or plain text, cannot be used together with patches)'
}
};
}
/**
* Function to perform strict validation of JSON Patch
* Use this within tool handlers to validate before applying patches
*/
export function validateJsonPatch(operations) {
const errors = [];
for (let i = 0; i < operations.length; i++) {
const op = operations[i];
// Basic validation
if (!op.path.startsWith('/')) {
errors.push(`Operation #${i + 1}: Path must start with '/': ${op.path}`);
}
// Operation-specific validation
switch (op.op) {
case 'add':
case 'replace':
case 'test':
if (op.value === undefined) {
errors.push(`Operation #${i + 1}: '${op.op}' operation requires a 'value' property`);
}
break;
case 'move':
case 'copy':
if (!op.from) {
errors.push(`Operation #${i + 1}: '${op.op}' operation requires a 'from' property`);
}
else if (!op.from.startsWith('/')) {
errors.push(`Operation #${i + 1}: 'from' path must start with '/': ${op.from}`);
}
if (op.op === 'move' && op.path.startsWith(`${op.from}/`)) {
errors.push(`Operation #${i + 1}: Cannot move to a path that is a child of the source path`);
}
break;
}
}
return {
valid: errors.length === 0,
errors
};
}
/**
* Helper function to safely apply JSON Patch
* - Handles path escaping
* - Optimizes operation order
* - Checks for potential issues
*/
export function sanitizeJsonPatch(operations) {
// Ensure path escaping
const sanitized = operations.map(op => ({
...op,
path: ensureLeadingSlash(op.path),
...(op.from ? { from: ensureLeadingSlash(op.from) } : {})
}));
// Optimize operation order (remove operations last)
return sortPatchOperations(sanitized);
}
// Helper function
function ensureLeadingSlash(path) {
return path.startsWith('/') ? path : `/${path}`;
}
function sortPatchOperations(patches) {
return [...patches].sort((a, b) => {
if (a.op === 'remove' && b.op !== 'remove')
return 1;
if (a.op !== 'remove' && b.op === 'remove')
return -1;
return 0;
});
}
//# sourceMappingURL=patch-utils.js.map