# How to Implement Custom Business Logic in Palmier Pro: A Step-by-Step Guide

> Learn to implement custom business logic in Palmier Pro by declaring tools, extending ToolExecutor, and invoking them via Agent or MCP server. Follow our step-by-step guide.

- Repository: [Palmier/palmier-pro](https://github.com/palmier-io/palmier-pro)
- Tags: how-to-guide
- Published: 2026-06-21

---

**You implement custom business logic in Palmier Pro by declaring a new tool in [`ToolDefinitions.swift`](https://github.com/palmier-io/palmier-pro/blob/main/ToolDefinitions.swift), implementing the behavior in a `ToolExecutor` extension, and invoking it through the Agent service or MCP server.**

Palmier Pro is an extensible video editing platform built around an AI Agent that communicates via declarative JSON tools. This guide shows you how to implement custom business logic in Palmier Pro by adding new tools to the agent's toolkit, allowing you to automate domain-specific workflows—from batch clip styling to automated analytics—without modifying the core editor UI.

## Understanding the Tool Architecture

The Palmier Pro architecture separates tool declaration from execution, enabling safe extension through Swift extensions.

### Tool Catalog

The **Tool Catalog** in [`Sources/PalmierPro/Agent/Tools/ToolDefinitions.swift`](https://github.com/palmier-io/palmier-pro/blob/main/Sources/PalmierPro/Agent/Tools/ToolDefinitions.swift) (lines 4-38, 45-55) declares every available tool via the `ToolName` enum and `AgentTool` structs. Each entry defines the tool's name, description, and JSON schema for LLM validation.

### Tool Executor

The **Tool Executor** layer maps tool names to Swift implementations. The core dispatcher lives in [`Sources/PalmierPro/Agent/Tools/ToolExecutor.swift`](https://github.com/palmier-io/palmier-pro/blob/main/Sources/PalmierPro/Agent/Tools/ToolExecutor.swift), while specific tools are implemented in separate extension files like `ToolExecutor+Timeline.swift`. This executor receives a `[String: Any]` dictionary of arguments and returns a `ToolResult` (defined in [`ToolResult.swift`](https://github.com/palmier-io/palmier-pro/blob/main/ToolResult.swift)).

### Agent Service

The **Agent Service** ([`Sources/PalmierPro/Agent/AgentService.swift`](https://github.com/palmier-io/palmier-pro/blob/main/Sources/PalmierPro/Agent/AgentService.swift), lines 46-62, 84-100) manages chat sessions, streams LLM responses, and forwards tool uses to the executor. It operates on the **Project Model** ([`Sources/PalmierPro/Project/VideoProject.swift`](https://github.com/palmier-io/palmier-pro/blob/main/Sources/PalmierPro/Project/VideoProject.swift)), which holds the timeline, tracks, and clips that your business logic will manipulate.

## Declaring a Custom Tool

All custom logic must first be declared in the tool catalog so the LLM recognizes it as a valid operation.

### Extending ToolDefinitions.swift

Open [`Sources/PalmierPro/Agent/Tools/ToolDefinitions.swift`](https://github.com/palmier-io/palmier-pro/blob/main/Sources/PalmierPro/Agent/Tools/ToolDefinitions.swift) and add a new case to the `ToolName` enum:

```swift
case myCustomLogic = "my_custom_logic"

```

Next, append an `AgentTool` entry to the `all` array (around line 45-55) to describe the tool's interface:

```swift
AgentTool(
    name: .myCustomLogic,
    description: "Runs custom business logic supplied by the developer. The `payload` field receives arbitrary JSON that the executor can interpret.",
    inputSchema: objectSchema(
        properties: [
            "payload": ["type": "object", "description": "Arbitrary data for custom processing."]
        ],
        required: ["payload"]
    )
),

```

The `objectSchema` helper function constructs a JSON Schema dictionary that validates incoming tool arguments before execution.

## Implementing the Business Logic

Once declared, you must implement the actual behavior in a `ToolExecutor` extension.

### Creating the Executor Extension

Create a new file `Sources/PalmierPro/Agent/Tools/ToolExecutor+MyCustomLogic.swift` and implement the execution function:

```swift
import Foundation

extension ToolExecutor {
    /// Executes `my_custom_logic`.
    func myCustomLogic(args: [String: Any]) async -> ToolResult {
        // Extract payload – decode to a strongly-typed struct if preferred.
        guard let payload = args["payload"] as? [String: Any] else {
            return .error("Missing `payload`")
        }

        // Example business rule: flag clips longer than 5 seconds.
        if let minDuration = payload["minDurationFrames"] as? Int {
            await flagLongClips(minFrames: minDuration)
        }

        return .success(["text": "Business rule applied."])
    }

    private func flagLongClips(minFrames: Int) async {
        guard let editor = editor else { return }
        
        // Walk the timeline and manipulate clips via existing APIs.
        for track in editor.timeline.tracks {
            for clip in track.clips where clip.durationFrames > minFrames {
                await setClipProperties(
                    clipIds: [clip.id],
                    opacity: 0.5,
                    transform: ["borderWidth": 0.02]
                )
            }
        }
    }
}

```

This implementation follows the established pattern: validate arguments, access the `editor` reference to mutate the `VideoProject` model, and return a `ToolResult`.

### Wiring Into the Dispatch Loop

The [`ToolExecutor.swift`](https://github.com/palmier-io/palmier-pro/blob/main/ToolExecutor.swift) file contains a dispatch switch that routes tool names to their implementations. Add your case to the `execute(name:args:)` method:

```swift
case .myCustomLogic:
    return await myCustomLogic(args: args)

```

The agent's execution loop (`runLoop` in [`AgentService.swift`](https://github.com/palmier-io/palmier-pro/blob/main/AgentService.swift)) now automatically resolves calls to `my_custom_logic`, treating your custom code exactly like built-in operations.

## Invoking Your Custom Logic

With the tool declared and implemented, you can trigger it from within the app or via external scripts.

### From Swift Code

Invoke the tool through the `AgentService` from any Swift UI component:

```swift
Button("Run Business Rule") {
    Task {
        let args = ["payload": ["minDurationFrames": 150]]
        let json = try! JSONSerialization.data(withJSONObject: args)
        let text = "@my_custom_logic \(String(data: json, encoding: .utf8)!)"
        
        await agentService.send(text: text, mentions: [])
    }
}

```

The `@my_custom_logic` prefix signals the agent to generate a tool use. The JSON payload automatically passes through to your executor implementation.

### Via the MCP Server

Palmier Pro exposes an HTTP endpoint at `http://127.0.0.1:19789/mcp` that accepts external tool calls. Trigger your logic from a CI job or external script:

```bash
curl -X POST http://127.0.0.1:19789/mcp \
  -H "Content-Type: application/json" \
  -d '{
        "messages": [
          {"role":"user","content":[{"type":"text","text":"@my_custom_logic"}]}
        ],
        "tools": [
          {"name":"my_custom_logic","input_schema":{"properties":{"payload":{"type":"object"}},"required":["payload"]}}
        ]
      }'

```

The server streams the response, executes your tool through the executor, and returns the result block to the caller.

## Complete Working Example

Here is a full implementation that adds yellow highlight overlays to video clips when the payload contains `highlight: true`:

```swift
// ToolExecutor+Highlight.swift
import Foundation

extension ToolExecutor {
    func highlightVideos(args: [String: Any]) async -> ToolResult {
        guard let payload = args["payload"] as? [String: Any] else {
            return .error("Missing payload")
        }

        if let shouldHighlight = payload["highlight"] as? Bool, shouldHighlight {
            await addHighlightOverlayToAllVideoClips()
        }

        return .success(["text": "Highlight effect applied to video clips."])
    }

    private func addHighlightOverlayToAllVideoClips() async {
        guard let editor = editor else { return }
        
        for (trackIdx, track) in editor.timeline.tracks.enumerated() {
            for clip in track.clips where clip.mediaType == .video {
                await setClipProperties(
                    clipIds: [clip.id],
                    transform: ["opacity": 0.3, "color": "#FFFF0099"]
                )
            }
        }
    }
}

```

Add the corresponding case to the dispatch switch in [`ToolExecutor.swift`](https://github.com/palmier-io/palmier-pro/blob/main/ToolExecutor.swift):

```swift
case .highlightVideos:
    return await highlightVideos(args: args)

```

And declare it in [`ToolDefinitions.swift`](https://github.com/palmier-io/palmier-pro/blob/main/ToolDefinitions.swift):

```swift
case highlightVideos = "highlight_videos"

```

## Summary

- **Declare** your custom tool in [`ToolDefinitions.swift`](https://github.com/palmier-io/palmier-pro/blob/main/ToolDefinitions.swift) by adding a `ToolName` enum case and an `AgentTool` entry with a JSON schema.
- **Implement** the logic in a `ToolExecutor` extension file, accessing the `editor` reference to manipulate the `VideoProject` timeline and clips.
- **Wire** the tool into the dispatch loop in [`ToolExecutor.swift`](https://github.com/palmier-io/palmier-pro/blob/main/ToolExecutor.swift) to make it reachable by the agent.
- **Invoke** the tool via `AgentService.send()` from Swift UI or through the MCP server endpoint at `127.0.0.1:19789/mcp` for external automation.

This pattern ensures your custom business logic remains undoable, repeatable, and visible to the LLM for intelligent orchestration.

## Frequently Asked Questions

### Can I implement custom business logic without modifying the Palmier Pro source code?

Currently, you must add tool definitions and executor extensions within the Palmier Pro codebase in `Sources/PalmierPro/Agent/Tools/`. However, once implemented, you can trigger these tools from external scripts via the MCP server without recompiling the app. The architecture treats external MCP calls identically to internal agent requests.

### What data types can I pass to my custom tool's payload?

The `inputSchema` in [`ToolDefinitions.swift`](https://github.com/palmier-io/palmier-pro/blob/main/ToolDefinitions.swift) supports standard JSON Schema types including `string`, `integer`, `number`, `boolean`, `object`, and `array`. The executor receives these as `[String: Any]` dictionaries. For complex business logic, decode the payload into a strongly-typed Swift struct using `JSONDecoder` for type safety.

### How do I ensure my custom tool respects the project frame rate?

All timing values in Palmier Pro use **project-frame units** (the same FPS as the timeline). When processing durations or positions in your executor, reference `clip.durationFrames` and other timeline properties from [`VideoProject.swift`](https://github.com/palmier-io/palmier-pro/blob/main/VideoProject.swift) rather than calculating raw time values. This ensures your business logic remains synchronized with the editor's playback settings.

### Can my custom tool call other existing tools?

Yes. Your executor extension can invoke other tool methods directly, such as `setClipProperties` or timeline manipulation functions. This approach maintains consistency with the UI layer and ensures that all mutations flow through the same validation paths. The `ToolExecutor` class provides access to the full suite of existing editing operations.