write_resource
Edit files on the Backlog MCP Server by replacing, inserting, or appending content to manage task backlog updates.
Instructions
Edit existing files on the MCP server. All file creation goes through backlog_create.
The
appendcommand will add content to the end of an existing file, automatically adding a newline if the file doesn't end with one. Notes for using thestr_replacecommand:The
old_strparameter should match EXACTLY one or more consecutive lines from the original file. Be mindful of whitespaces!If the
old_strparameter is not unique in the file, the replacement will not be performed. Make sure to include enough context inold_strto make it uniqueThe
new_strparameter should contain the edited lines that should replace theold_str
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| uri | Yes | MCP resource URI, e.g. mcp://backlog/path/to/file.md | |
| operation | Yes | Operation to apply |
Implementation Reference
- The write_resource tool handler callback that executes the write operation via resourceManager.write() and returns the result as JSON
async ({ uri, operation }) => { const result = this.write(uri, operation); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }], }; } ); - Core write method that resolves URIs to file paths, applies operations, updates task timestamps, and handles file operations
write(uri: string, operation: Operation): WriteResourceResult { try { const filePath = this.resolve(uri); const isTask = this.isTaskUri(uri); if (!existsSync(filePath)) { return { success: false, message: 'File not found', error: `Resource not found: ${uri} (${operation.type} requires existing file). Use backlog_create to create new entities.`, }; } const fileContent = readFileSync(filePath, 'utf-8'); let newContent = applyOperation(fileContent, operation); // Update timestamp for task files if (isTask) { newContent = this.updateTaskTimestamp(newContent); } writeFileSync(filePath, newContent, 'utf-8'); return { success: true, message: `Successfully applied ${operation.type} to ${uri}`, }; } catch (error) { return { success: false, message: 'Operation failed', error: error instanceof Error ? error.message : String(error), }; } } - Zod schema definition for write_resource tool input parameters, validating URI and operation types (str_replace, insert, append)
inputSchema: z.object({ uri: z.string().describe('MCP resource URI, e.g. mcp://backlog/path/to/file.md'), operation: z.preprocess( // Workaround: MCP clients stringify object params with $ref/oneOf schemas // https://github.com/anthropics/claude-code/issues/18260 (val) => typeof val === 'string' ? JSON.parse(val) : val, z.discriminatedUnion('type', [ z.object({ type: z.literal('str_replace'), old_str: z.string().describe('String in file to replace (must match exactly)'), new_str: z.string().describe('New string to replace old_str with'), }), z.object({ type: z.literal('insert'), insert_line: z.number().describe('Line number after which new_str will be inserted'), new_str: z.string().describe('String to insert'), }), z.object({ type: z.literal('append'), new_str: z.string().describe('Content to append to the file'), }), ])).describe('Operation to apply'), }), - packages/server/src/resources/manager.ts:255-296 (registration)Method that registers the write_resource tool with the MCP server, including description, input schema, and handler callback
registerWriteTool(server: McpServer) { server.registerTool( 'write_resource', { description: `Edit existing files on the MCP server. All file creation goes through backlog_create. * The \`append\` command will add content to the end of an existing file, automatically adding a newline if the file doesn't end with one. Notes for using the \`str_replace\` command: * The \`old_str\` parameter should match EXACTLY one or more consecutive lines from the original file. Be mindful of whitespaces! * If the \`old_str\` parameter is not unique in the file, the replacement will not be performed. Make sure to include enough context in \`old_str\` to make it unique * The \`new_str\` parameter should contain the edited lines that should replace the \`old_str\``, inputSchema: z.object({ uri: z.string().describe('MCP resource URI, e.g. mcp://backlog/path/to/file.md'), operation: z.preprocess( // Workaround: MCP clients stringify object params with $ref/oneOf schemas // https://github.com/anthropics/claude-code/issues/18260 (val) => typeof val === 'string' ? JSON.parse(val) : val, z.discriminatedUnion('type', [ z.object({ type: z.literal('str_replace'), old_str: z.string().describe('String in file to replace (must match exactly)'), new_str: z.string().describe('New string to replace old_str with'), }), z.object({ type: z.literal('insert'), insert_line: z.number().describe('Line number after which new_str will be inserted'), new_str: z.string().describe('String to insert'), }), z.object({ type: z.literal('append'), new_str: z.string().describe('Content to append to the file'), }), ])).describe('Operation to apply'), }), }, async ({ uri, operation }) => { const result = this.write(uri, operation); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }], }; } ); } - Helper function that applies the actual text operations (str_replace, insert, append) to file content
export function applyOperation(content: string, operation: Operation): string { switch (operation.type) { case 'str_replace': { const { old_str, new_str } = operation; if (!content.includes(old_str)) { throw new Error(`str_replace failed: old_str not found in content`); } // Check uniqueness - fail if old_str appears more than once const firstIndex = content.indexOf(old_str); const secondIndex = content.indexOf(old_str, firstIndex + 1); if (secondIndex !== -1) { throw new Error(`str_replace failed: old_str is not unique in file. Include more context to make it unique.`); } return content.replace(old_str, new_str); } case 'insert': { // insert_line: insert AFTER this line (1-based, like fs_write) const lines = content.split('\n'); const lineNum = operation.insert_line; if (lineNum < 0 || lineNum > lines.length) { throw new Error(`insert failed: line ${lineNum} out of range (0-${lines.length})`); } lines.splice(lineNum, 0, operation.new_str); return lines.join('\n'); } case 'append': { // Add newline if file doesn't end with one (like fs_write) const needsNewline = content.length > 0 && !content.endsWith('\n'); return content + (needsNewline ? '\n' : '') + operation.new_str; } default: throw new Error(`Unknown operation type: ${(operation as any).type}`); } }