> ## Documentation Index
> Fetch the complete documentation index at: https://docs.dotbot.ch/llms.txt
> Use this file to discover all available pages before exploring further.

# Add custom PowerShell tools to the MCP server

> Create custom dotbot MCP tools by dropping a metadata.json and script.ps1 into a workflow or stack tools subdirectory. No registration step required.

The dotbot MCP server auto-discovers tools at startup by scanning tool directories for subdirectories that contain both a `metadata.json` and a `script.ps1`. There is no registration step - dropping the right files into the right folder is all that is needed. This same discovery mechanism applies to tools added by workflows and stacks installed in your project.

## How tool discovery works

When the MCP server starts, it walks three sets of directories. For each subdirectory that contains both `metadata.json` and `script.ps1`, the server:

1. Reads the tool name and input schema from `metadata.json`
2. Dot-sources `script.ps1` to load the `Invoke-*` function into memory
3. Registers the tool with the MCP protocol handler

The three discovery locations are:

| Location       | Path                                                             |
| -------------- | ---------------------------------------------------------------- |
| Core tools     | `src/mcp/tools/` inside the dotbot install (`$DOTBOT_HOME`)      |
| Workflow tools | `<workflow-path>/tools/` or `<workflow-path>/systems/mcp/tools/` |
| Stack tools    | `<stack-path>/tools/` or `<stack-path>/systems/mcp/tools/`       |

No changes to the server itself are needed. Restart the MCP server process (by restarting your AI tool) to pick up new or modified tools.

## Directory structure

Each tool lives in its own folder inside one of the discovery directories. Use kebab-case for the folder name:

```
<workflow-path>/tools/
└── your-tool-name/
    ├── metadata.json   # Tool name, description, and input schema
    ├── script.ps1      # PowerShell implementation function
    └── test.ps1        # Test file (optional but recommended)
```

For workflow-scoped tools, place the folder under either `<workflow>/tools/` or `<workflow>/systems/mcp/tools/`. For stack-scoped tools, use the same structure under `<stack>/tools/` or `<stack>/systems/mcp/tools/`. The server scans both layout variants for each installed workflow and stack.

## Naming conventions

The three files follow different case conventions that must be consistent with each other:

| Location                        | Convention             | Example                   |
| ------------------------------- | ---------------------- | ------------------------- |
| Folder name                     | kebab-case             | `send-notification`       |
| `name` field in `metadata.json` | snake\_case            | `send_notification`       |
| Function name in `script.ps1`   | `Invoke-` + PascalCase | `Invoke-SendNotification` |

The server derives the function name to call from the `name` field in `metadata.json` by capitalizing each underscore-separated segment and prepending `Invoke-`. If the naming is inconsistent, the tool will load but fail at call time with a function-not-found error.

## The required files

### metadata.json

Defines the tool's name, description, and JSON Schema for its input parameters. The following is based on the real `metadata.json` for the built-in `task_create` tool, which you can use as a reference for field structure:

```json theme={null}
{
  "name": "task_create",
  "description": "Create a new task in the task queue",
  "inputSchema": {
    "type": "object",
    "additionalProperties": true,
    "properties": {
      "name": {
        "type": "string",
        "description": "Brief, descriptive name for the task"
      },
      "description": {
        "type": "string",
        "description": "Detailed description of what the task does and why it is needed"
      },
      "priority": {
        "type": "integer",
        "description": "Priority from 1 (highest) to 100 (lowest)",
        "minimum": 1,
        "maximum": 100
      },
      "effort": {
        "type": "string",
        "enum": ["XS", "S", "M", "L", "XL"],
        "description": "Effort estimate (XS=1 day, S=2-3 days, M=1 week, L=2 weeks, XL=3+ weeks)"
      },
      "dependencies": {
        "type": "array",
        "description": "Task IDs that must be completed before this one",
        "items": { "type": "string" }
      },
      "acceptance_criteria": {
        "type": "array",
        "description": "Criteria that must be met for the task to be considered complete",
        "items": { "type": "string" }
      }
    },
    "required": ["name"]
  }
}
```

Mark all parameters the AI must supply in the `required` array. Parameters not in `required` are treated as optional.

### script.ps1

Contains a single PowerShell function named `Invoke-YourToolName`. The function receives a `[hashtable]$Arguments` parameter and returns a hashtable with the result data. The MCP server serializes the returned hashtable to JSON before sending it back to the AI tool.

```powershell theme={null}
function Invoke-YourToolName {
    param(
        [hashtable]$Arguments
    )

    # Extract arguments from the hashtable
    $input1 = $Arguments['input1']
    $input2 = $Arguments['input2']

    # Access the project root (set by the MCP server at startup)
    $tasksDir = Join-Path $global:DotbotProjectRoot ".bot\workspace\tasks"

    # Return a hashtable - the server serializes it to JSON
    return @{
        result = "processed $input1"
        count  = $input2
    }
}
```

<Note>
  The MCP server sets `$global:DotbotProjectRoot` before loading tool scripts. Use this variable to build paths to `.bot/workspace/`, `.bot/.control/`, or any other project-relative location - do not hardcode paths.
</Note>

### test.ps1

Tests the tool by sending a JSON-RPC request to a running MCP server process. Including a test file is optional but recommended, especially for tools that modify project state.

```powershell theme={null}
#!/usr/bin/env pwsh
param(
    [Parameter(Mandatory)]
    [System.Diagnostics.Process]$Process
)

. "$PSScriptRoot\..\..\dotbot-mcp-helpers.ps1"

Write-Host "Test: your tool name" -ForegroundColor Yellow

$response = Send-McpRequest -Process $Process -Request @{
    jsonrpc = '2.0'
    id      = 1
    method  = 'tools/call'
    params  = @{
        name      = 'your_tool_name'
        arguments = @{
            input1 = 'test value'
            input2 = 42
        }
    }
}

$result = $response.result.content[0].text | ConvertFrom-Json
Write-Host "Result: $($result.result)" -ForegroundColor Green
```

## Creating a custom tool

The following example creates a tool called `project_summary` that returns a count of tasks in each lifecycle state.

<Steps>
  <Step title="Create the tool folder">
    Inside your workflow's `tools/` directory, create a new directory using kebab-case:

    ```powershell theme={null}
    New-Item -ItemType Directory <workflow-path>/tools/project-summary
    ```
  </Step>

  <Step title="Write metadata.json">
    Create `<workflow-path>/tools/project-summary/metadata.json` with the snake\_case tool name, a description, and the input schema. For a tool with no required inputs, use an empty `properties` object:

    ```json theme={null}
    {
      "name": "project_summary",
      "description": "Return a count of tasks in each lifecycle state",
      "inputSchema": {
        "type": "object",
        "additionalProperties": false,
        "properties": {},
        "required": []
      }
    }
    ```
  </Step>

  <Step title="Write script.ps1">
    Create `<workflow-path>/tools/project-summary/script.ps1` with the `Invoke-ProjectSummary` function:

    ```powershell theme={null}
    function Invoke-ProjectSummary {
        param(
            [hashtable]$Arguments
        )

        $tasksDir = Join-Path $global:DotbotProjectRoot ".bot\workspace\tasks"
        $counts = @{ todo = 0; analysing = 0; analysed = 0; 'in-progress' = 0; done = 0 }

        Get-ChildItem -Path $tasksDir -Filter "*.json" | ForEach-Object {
            $task = Get-Content $_.FullName | ConvertFrom-Json
            if ($counts.ContainsKey($task.status)) {
                $counts[$task.status]++
            }
        }

        return $counts
    }
    ```
  </Step>

  <Step title="Restart the MCP server">
    The server loads tools at startup. Restart your AI tool (or the MCP server process) to pick up the new tool. Ask your AI tool to list available dotbot tools - `project_summary` should appear in the list.
  </Step>
</Steps>

## Tool name collision warning

<Warning>
  Tool name collisions are possible if two installed workflows or stacks define a tool with the same `name` field in their `metadata.json`. When a collision occurs, the last tool loaded wins, which may produce unexpected behavior. Use unique, workflow-prefixed names (for example, `jira_repo_clone` rather than `repo_clone`) to avoid conflicts.
</Warning>
