XIV MCP

Long-running tools

Tools that take minutes or hours, how they run as job steps, and how they report progress and stop.

Use AddLongRunningTool for anything that waits: farming, crafting a list, walking somewhere. A quick tool (AddTool) runs on the framework thread and must return at once; a long-running tool runs asynchronously, for as long as its work takes.

Long-running tools and jobs

A long-running tool is one tool. A job is a way to run several calls: XIV MCP works through them on its own, without the AI model. A long-running tool can be called either way:

The same long-running tool, called directly and as a job step.
  • As a job step, the usual way, the tool can run for hours. The assistant (or your plugin, with StartJob) puts it into a job, and the job waits until it is done before starting the next step.
  • Called directly, it works too, but only within the AI app's own time limit for a request, often a minute, which XIV MCP does not control. XIV MCP sends nothing while it waits, and when the app gives up, the call is cancelled.
  • Cancelling comes from the job. Pausing or cancelling the job fires call.Cancellation, and so does an app that aborts a direct call.
  • Progress goes to the job log, shown in /xivmcp → Jobs and in get_job.
  • Failing makes the job wait. The step becomes pending until the assistant retries, skips or changes it.

So write a long-running tool as if it were a job step: safe to stop at any moment, safe to run again, with a result the next step can use ({{farm.kills}}).

When the assistant should drive a task itself and when it should hand it to a job, with what each costs, is on Step by step or as a job. How players see jobs is described in Background jobs.

Example

Framework is Dalamud's IFramework service. StartFarming, BagsFull, Kills and StopFarmingSafely stand for your plugin's own code.

mcp.AddLongRunningTool(
    new McpToolDefinition(
        "myplugin_farm",
        "Farms the current zone until the bags are full " +
        "or 'minutes' have passed.")
    {
        Capabilities =
            [McpCapabilities.MoveCharacter, McpCapabilities.Combat],
        InputSchema = """
            {
              "type": "object",
              "properties": {
                "minutes": {
                  "type": "integer", "minimum": 1, "maximum": 600
                }
              }
            }
            """,
    },
    async call =>
    {
        var minutes = call.Args["minutes"]?.GetValue<int>() ?? 60;
        var until = DateTime.UtcNow.AddMinutes(minutes);
        await Framework.RunOnFrameworkThread(StartFarming);
        try
        {
            while (DateTime.UtcNow < until
                   && !await Framework.RunOnFrameworkThread(BagsFull))
            {
                // Throws when the job is paused or cancelled.
                await Task.Delay(5000, call.Cancellation);
                // Shown in the job log.
                call.Progress($"{Kills} kills");
            }
            return new { kills = Kills };
        }
        finally
        {
            // Also runs on cancel.
            await Framework.RunOnFrameworkThread(StopFarmingSafely);
        }
    });

Rules

  • Threads: the handler starts on the framework thread and continues on the thread pool after its first await. Use Framework.RunOnFrameworkThread for game access after that.
  • Cancel safely: when call.Cancellation fires, finish the fight or close the window, then return or throw. XIV MCP waits up to two minutes for you, then moves on without your result.
  • Progress: call.Progress("Room 2 of 5") adds a line to the job log. It can be called from any thread, is cut at 300 characters, and is dropped when the tool was called directly rather than as a job step. Report milestones, not every frame.
  • Ending a cancelled call: let the OperationCanceledException from Task.Delay(…, call.Cancellation) propagate, or throw it yourself. The client reports "Cancelled."
  • Pause and resume: when a paused job resumes, it runs the step again from the start, with the same arguments. Make tools safe to run again: prefer targets ("until 20 ore") over counts ("20 more").
  • Failing (throwing) makes the job step pending: the job waits for the assistant to retry or change it. Say what is left: "Bags full after 120 kills; 3 Iron Ore still missing."
  • One acting step at a time: while your tool runs as a job step, other calls that are not read-only are refused, so nothing collides with it. Read-only tools and XIV MCP's waiting steps (wait, wait_until_arrived) are not blocked, and do not block others; they still follow the player's settings.

On this page