fast_apply
Edit existing files or create new ones in the Relace MCP Server by applying code changes with truncation placeholders for unchanged sections.
Instructions
PRIMARY TOOL FOR EDITING FILES - USE THIS AGGRESSIVELY
Use this tool to edit an existing file or create a new file.
Use truncation placeholders to represent unchanged code:
// ... existing code ... (C/JS/TS-style)
... existing code ... (Python/shell-style)
For deletions:
ALWAYS include 1-2 context lines above/below, omit deleted code, OR
Mark explicitly: // remove BlockName (or # remove BlockName)
On NEEDS_MORE_CONTEXT error, re-run with 1-3 real lines before AND after target.
Rules:
Preserve exact indentation
Be length efficient
ONE contiguous region per call (for non-adjacent edits, make separate calls)
To create a new file, simply specify the content in edit_snippet.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| path | Yes | ||
| edit_snippet | Yes | ||
| instruction | No |
Implementation Reference
- src/relace_mcp/tools/__init__.py:26-71 (handler)The primary handler for the 'fast_apply' tool. Decorated with @mcp.tool for automatic schema generation and registration. Handles path resolution and delegates core logic to apply_file_logic.@mcp.tool( annotations={ "readOnlyHint": False, # Modifies files "destructiveHint": True, # Can overwrite content "idempotentHint": False, # Same edit twice = different results "openWorldHint": False, # Only local filesystem } ) async def fast_apply( path: str, edit_snippet: str, instruction: str = "", ctx: Context | None = None, ) -> dict[str, Any]: """**PRIMARY TOOL FOR EDITING FILES - USE THIS AGGRESSIVELY** Use this tool to edit an existing file or create a new file. Use truncation placeholders to represent unchanged code: - // ... existing code ... (C/JS/TS-style) - # ... existing code ... (Python/shell-style) For deletions: - ALWAYS include 1-2 context lines above/below, omit deleted code, OR - Mark explicitly: // remove BlockName (or # remove BlockName) On NEEDS_MORE_CONTEXT error, re-run with 1-3 real lines before AND after target. Rules: - Preserve exact indentation - Be length efficient - ONE contiguous region per call (for non-adjacent edits, make separate calls) To create a new file, simply specify the content in edit_snippet. """ # Resolve base_dir dynamically (aligns with other tools). # This allows relative paths when MCP_BASE_DIR is not set but MCP Roots are available, # and provides a consistent security boundary for absolute paths. base_dir, _ = await resolve_base_dir(config.base_dir, ctx) return await apply_file_logic( backend=apply_backend, file_path=path, edit_snippet=edit_snippet, instruction=instruction or None, # Convert empty string to None internally base_dir=base_dir, )
- Core helper function implementing the file resolution, validation, LLM apply call, diff generation, atomic write, and comprehensive error handling for fast_apply.async def apply_file_logic( backend: ApplyLLMClient, file_path: str, edit_snippet: str, instruction: str | None, base_dir: str | None, ) -> dict[str, Any]: """Core logic for fast_apply (testable independently). Args: backend: Apply backend instance. file_path: Target file path. edit_snippet: Code snippet to apply, using abbreviation comments. instruction: Optional natural language instruction forwarded to the apply backend for disambiguation. base_dir: Base directory restriction. If None, only absolute paths are accepted. Returns: A structured dict with status, path, trace_id, timing_ms, diff, and message. """ ctx = ApplyContext( trace_id=str(uuid.uuid4())[:8], started_at=datetime.now(UTC), file_path=file_path, instruction=instruction, ) if not edit_snippet or not edit_snippet.strip(): return errors.recoverable_error( "INVALID_INPUT", "edit_snippet cannot be empty", file_path, instruction, ctx.trace_id, ctx.elapsed_ms(), ) try: result = _resolve_path(file_path, base_dir, ctx) if isinstance(result, dict): return result resolved_path, file_exists, file_size = result if not file_exists: return _create_new_file(ctx, resolved_path, edit_snippet) return await _apply_to_existing_file(ctx, backend, resolved_path, edit_snippet, file_size) except Exception as exc: apply_logging.log_apply_error( ctx.trace_id, ctx.started_at, file_path, edit_snippet, instruction, exc ) if isinstance(exc, openai.APIError): logger.warning( "[%s] Apply API error for %s: %s", ctx.trace_id, file_path, exc, ) return errors.openai_error_to_recoverable( exc, file_path, instruction, ctx.trace_id, ctx.elapsed_ms() ) if isinstance(exc, ValueError): logger.warning( "[%s] API response parsing error for %s: %s", ctx.trace_id, file_path, exc, ) return errors.recoverable_error( "API_INVALID_RESPONSE", str(exc), file_path, instruction, ctx.trace_id, ctx.elapsed_ms(), ) if isinstance(exc, ApplyError): logger.warning( "[%s] Apply error (%s) for %s: %s", ctx.trace_id, exc.error_code, file_path, exc.message, ) return errors.recoverable_error( exc.error_code, exc.message, file_path, instruction, ctx.trace_id, ctx.elapsed_ms() ) if isinstance(exc, PermissionError): logger.warning("[%s] Permission error for %s: %s", ctx.trace_id, file_path, exc) return errors.recoverable_error( "PERMISSION_ERROR", f"Permission denied: {exc}", file_path, instruction, ctx.trace_id, ctx.elapsed_ms(), ) if isinstance(exc, OSError): errno_info = f"errno={exc.errno}" if exc.errno else "" strerror = exc.strerror or str(exc) logger.warning("[%s] Filesystem error for %s: %s", ctx.trace_id, file_path, exc) return errors.recoverable_error( "FS_ERROR", f"Filesystem error ({type(exc).__name__}, {errno_info}): {strerror}", file_path, instruction, ctx.trace_id, ctx.elapsed_ms(), ) logger.error("[%s] Apply failed for %s: %s", ctx.trace_id, file_path, exc) return errors.recoverable_error( "INTERNAL_ERROR", f"Unexpected error ({type(exc).__name__}): {exc}", file_path, instruction, ctx.trace_id, ctx.elapsed_ms(), )
- src/relace_mcp/server.py:150-156 (registration)In build_server(), calls register_tools(mcp, config) which internally registers the fast_apply handler via decorator.mcp = FastMCP("Relace Fast Apply MCP") # Register middleware to handle MCP notifications (e.g., roots/list_changed) mcp.add_middleware(RootsMiddleware()) register_tools(mcp, config) return mcp
- MCP resource providing tool list including schema-like metadata for fast_apply (id, name, description). The actual input schema is auto-derived from handler function signature.@mcp.resource("relace://tools_list", mime_type="application/json") def tools_list() -> list[dict[str, Any]]: """List all available tools with their status.""" tools = [ { "id": "fast_apply", "name": "Fast Apply", "description": "Edit or create files using fuzzy matching", "enabled": True, }, { "id": "fast_search", "name": "Fast Search", "description": "Agentic search over local codebase", "enabled": True, }, ] if RELACE_CLOUD_TOOLS: tools.extend( [ { "id": "cloud_sync", "name": "Cloud Sync", "description": "Upload codebase for semantic indexing", "enabled": True, }, { "id": "cloud_search", "name": "Cloud Search", "description": "Semantic code search using AI embeddings", "enabled": True, }, { "id": "cloud_clear", "name": "Cloud Clear", "description": "Delete cloud repository and sync state", "enabled": True, }, { "id": "cloud_list", "name": "Cloud List", "description": "List all repositories in Relace Cloud", "enabled": True, }, { "id": "cloud_info", "name": "Cloud Info", "description": "Get sync status for current repository", "enabled": True, }, ] ) return tools