使用 Laravel Workflow 作为 MCP 工具提供给 AI 客户端

使用 Laravel Workflow 作为 MCP 工具提供给 AI 客户端

Model Context Protocol (MCP) 正在迅速成为 AI 助手(如 Claude、ChatGPT 和 GitHub Copilot)与外部工具和服务交互的标准方式。通过 Laravel MCP,你可以将 Laravel Workflow 流程暴露为可调用的工具,任何兼容 MCP 的 AI 客户端都可以发现、调用和监控这些工具。

在本文中,我们将展示如何构建一个 MCP 服务器,使 AI 客户端能够:

  • 发现可用的工作流
  • 异步启动工作流
  • 轮询状态并检索结果

这创建了一个强大的模式,AI 代理可以编排长时间运行的持久化工作流,非常适合无法在单个请求中完成的复杂任务。

原文链接 使用 Laravel Workflow 作为 MCP 工具提供给 AI 客户端

为什么选择 MCP + Laravel Workflow?

Laravel Workflow 擅长持久化、有状态的执行。MCP 擅长为 AI 客户端提供对外部能力的结构化访问。两者结合可以实现:

  • 异步 AI 操作:启动工作流,继续对话,稍后检查结果
  • 可靠执行:工作流可以在崩溃、重试和长时间等待中存活
  • 可观察性:通过 Waterline 的仪表板跟踪每个工作流
  • 无状态服务器:MCP 服务器不保存状态。客户端跟踪工作流 ID

这类似于人类委派任务的方式:"开始这个报告,我稍后再检查。"

我们要构建什么

我们将创建一个包含三个工具的 MCP 服务器:

工具 用途
list_workflows 发现可用的工作流并查看最近的运行记录
start_workflow 启动工作流并获取跟踪 ID
get_workflow_result 检查状态并在完成时检索输出

分步实现

安装 Laravel MCP

bash 复制代码
composer require laravel/mcp
php artisan vendor:publish --tag=ai-routes

这会为你提供 routes/ai.php,你将在其中注册 MCP 服务器。

创建 MCP 服务器

bash 复制代码
php artisan make:mcp-server WorkflowServer

为 AI 配置说明:

php 复制代码
namespace App\Mcp\Servers;

use App\Mcp\Tools\GetWorkflowResultTool;
use App\Mcp\Tools\ListWorkflowsTool;
use App\Mcp\Tools\StartWorkflowTool;
use Laravel\Mcp\Server;

class WorkflowServer extends Server
{
    protected string $name = 'Laravel Workflow Server';
    protected string $version = '1.0.0';

    protected string $instructions = <<<'MARKDOWN'
        This server allows you to start and monitor Laravel Workflows.

        ## Typical Usage Pattern

        1. Call `list_workflows` to see what workflows are available.
        2. Call `start_workflow` with the workflow name and arguments.
        3. Store the returned `workflow_id`.
        4. Call `get_workflow_result` until status is `WorkflowCompletedStatus`.
        5. Read the `output` field for the result.

        ## Status Values

        - `WorkflowCreatedStatus` - Workflow has been created
        - `WorkflowPendingStatus` - Queued for execution
        - `WorkflowRunningStatus` - Currently executing
        - `WorkflowWaitingStatus` - Waiting (timer, signal, etc.)
        - `WorkflowCompletedStatus` - Finished successfully
        - `WorkflowFailedStatus` - Encountered an error
    MARKDOWN;

    protected array $tools = [
        ListWorkflowsTool::class,
        StartWorkflowTool::class,
        GetWorkflowResultTool::class,
    ];
}

创建启动工作流工具

bash 复制代码
php artisan make:mcp-tool StartWorkflowTool
php 复制代码
namespace App\Mcp\Tools;

use Illuminate\Contracts\JsonSchema\JsonSchema;
use Illuminate\Support\Arr;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\Server\Tool;
use Workflow\Workflow;
use Workflow\WorkflowStub;

class StartWorkflowTool extends Tool
{
    protected string $description = <<<'MARKDOWN'
        Start a Laravel Workflow asynchronously and return its workflow ID.
        
        The workflow will execute in the background on the queue. Use the
        `get_workflow_result` tool to poll for status and retrieve results
        once the workflow completes.
    MARKDOWN;

    public function handle(Request $request): Response
    {
        $data = $request->validate([
            'workflow' => ['required', 'string'],
            'args' => ['nullable', 'array'],
            'external_id' => ['nullable', 'string', 'max:255'],
        ]);

        $workflowKey = $data['workflow'];
        $args = Arr::get($data, 'args', []);
        $externalId = $data['external_id'] ?? null;

        $workflowClass = $this->resolveWorkflowClass($workflowKey);

        if ($workflowClass === null) {
            return Response::error("Unknown workflow: {$workflowKey}");
        }

        if (! class_exists($workflowClass) || ! is_subclass_of($workflowClass, Workflow::class)) {
            return Response::error("Invalid workflow class: {$workflowClass}");
        }

        $stub = WorkflowStub::make($workflowClass);
        $stub->start(...array_values($args));

        $status = $stub->status();
        $statusName = is_object($status) ? class_basename($status) : class_basename((string) $status);

        return Response::json([
            'workflow_id' => $stub->id(),
            'workflow' => $workflowKey,
            'status' => $statusName,
            'external_id' => $externalId,
            'message' => 'Workflow started. Use get_workflow_result to poll status.',
        ]);
    }

    protected function resolveWorkflowClass(string $key): ?string
    {
        $mapped = config("workflow_mcp.workflows.{$key}");
        if ($mapped !== null) {
            return $mapped;
        }

        if (config('workflow_mcp.allow_fqcn', false) && str_contains($key, '\\')) {
            return $key;
        }

        return null;
    }

    public function schema(JsonSchema $schema): array
    {
        $workflows = implode(', ', array_keys(config('workflow_mcp.workflows', [])));

        return [
            'workflow' => $schema->string()
                ->description("The workflow to start. Available: {$workflows}"),
            'args' => $schema->object()
                ->description('Arguments for the workflow execute() method.'),
            'external_id' => $schema->string()
                ->description('Optional idempotency key for tracking.'),
        ];
    }
}

创建获取结果工具

bash 复制代码
php artisan make:mcp-tool GetWorkflowResultTool
php 复制代码
namespace App\Mcp\Tools;

use App\Models\StoredWorkflow;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\Server\Tool;
use Workflow\States\WorkflowCompletedStatus;
use Workflow\States\WorkflowFailedStatus;
use Workflow\WorkflowStub;

class GetWorkflowResultTool extends Tool
{
    protected string $description = <<<'MARKDOWN'
        Fetch the status and, if completed, the output of a Laravel Workflow.
        
        Use the workflow_id returned by `start_workflow` to check progress.
        Once status is `WorkflowCompletedStatus`, the output field contains the result.
    MARKDOWN;

    public function handle(Request $request): Response
    {
        $data = $request->validate([
            'workflow_id' => ['required'],
        ]);

        $workflowId = $data['workflow_id'];
        $stored = StoredWorkflow::find($workflowId);

        if (! $stored) {
            return Response::json([
                'found' => false,
                'message' => "Workflow {$workflowId} not found.",
            ]);
        }

        $workflow = WorkflowStub::load($workflowId);
        $status = $workflow->status();
        $statusName = is_object($status) ? class_basename($status) : class_basename((string) $status);
        $running = $workflow->running();

        $result = null;
        $error = null;

        if (! $running && str_contains($statusName, 'Completed')) {
            $result = $workflow->output();
        }

        if (! $running && str_contains($statusName, 'Failed')) {
            $exception = $stored->exceptions()->latest()->first();
            $error = $exception?->exception ?? 'Unknown error';
        }

        return Response::json([
            'found' => true,
            'workflow_id' => $workflowId,
            'status' => $statusName,
            'running' => $running,
            'output' => $result,
            'error' => $error,
            'created_at' => $stored->created_at?->toIso8601String(),
            'updated_at' => $stored->updated_at?->toIso8601String(),
        ]);
    }

    public function schema(JsonSchema $schema): array
    {
        return [
            'workflow_id' => $schema->string()
                ->description('The workflow ID returned by start_workflow.'),
        ];
    }
}

创建列表工作流工具

bash 复制代码
php artisan make:mcp-tool ListWorkflowsTool
php 复制代码
namespace App\Mcp\Tools;

use App\Models\StoredWorkflow;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\Server\Tool;

class ListWorkflowsTool extends Tool
{
    protected string $description = <<<'MARKDOWN'
        List available workflow types and optionally show recent workflow runs.
        
        Use this to discover what workflows can be started, or to see
        the status of recent executions.
    MARKDOWN;

    public function handle(Request $request): Response
    {
        $data = $request->validate([
            'show_recent' => ['nullable', 'boolean'],
            'limit' => ['nullable', 'integer', 'min:1', 'max:50'],
            'status' => ['nullable', 'string'],
        ]);

        $showRecent = $data['show_recent'] ?? false;
        $limit = $data['limit'] ?? 10;
        $statusFilter = $data['status'] ?? null;

        $availableWorkflows = [];
        foreach (config('workflow_mcp.workflows', []) as $key => $class) {
            $availableWorkflows[] = ['key' => $key, 'class' => $class];
        }

        $response = [
            'available_workflows' => $availableWorkflows,
        ];

        if ($showRecent) {
            $query = StoredWorkflow::query()
                ->orderBy('created_at', 'desc')
                ->limit($limit);

            if ($statusFilter) {
                $query->where('status', 'like', "%{$statusFilter}%");
            }

            $response['recent_workflows'] = $query->get()->map(function ($w) {
                $status = $w->status;
                $statusName = is_object($status) ? class_basename($status) : class_basename((string) $status);

                return [
                    'id' => $w->id,
                    'class' => $w->class,
                    'status' => $statusName,
                    'created_at' => $w->created_at?->toIso8601String(),
                ];
            });
        }

        return Response::json($response);
    }

    public function schema(JsonSchema $schema): array
    {
        return [
            'show_recent' => $schema->boolean()
                ->description('Include recent workflow runs in response.'),
            'limit' => $schema->integer()
                ->description('Max recent workflows to return (default: 10).'),
            'status' => $schema->string()
                ->description('Filter by status (e.g., "Completed", "Failed").'),
        ];
    }
}

配置可用的工作流

创建 config/workflow_mcp.php 来将 AI 客户端可以启动的工作流加入白名单:

php 复制代码
return [
    'allow_fqcn' => env('WORKFLOW_MCP_ALLOW_FQCN', false),

    'workflows' => [
        'simple' => App\Workflows\Simple\SimpleWorkflow::class,
        'prism' => App\Workflows\Prism\PrismWorkflow::class,
    ],
];

这可以防止任意类执行。只有映射的工作流可以访问。

注册 MCP 服务器

更新 routes/ai.php:

php 复制代码
use App\Mcp\Servers\WorkflowServer;
use Laravel\Mcp\Facades\Mcp;

Mcp::web('/mcp/workflows', WorkflowServer::class);

连接 AI 客户端

VS Code / GitHub Copilot

在项目中创建 .vscode/mcp.json:

json 复制代码
{
  "servers": {
    "laravel-workflow": {
      "type": "http",
      "url": "http://localhost/mcp/workflows"
    }
  }
}

此配置适用于本地开发和 GitHub Codespaces。在 Codespaces 中,VS Code 服务器在容器内运行,因此 localhost 可以正确访问 Laravel 服务器,无需公共端口或 *.app.github.dev URL。

重新加载 VS Code(Cmd/Ctrl+Shift+P → "Developer: Reload Window")后,Copilot 可以直接在聊天中使用工作流工具。

实际使用

连接后,你可以与 AI 助手进行自然对话:

:"有哪些工作流可用?"

AI :调用 list_workflows "我找到了 2 个工作流:simple 和 prism。"

:"启动 prism 工作流"

AI :调用 start_workflow "已启动工作流 ID 42。我会检查它的状态。"

AI :调用 get_workflow_result "工作流已完成!这是生成的用户配置文件:{ name: 'Elena', hobbies: [...] }"

这创造了一种无缝体验,AI 助手可以编排复杂的长时间运行的操作,同时让用户了解情况。

这种模式的强大之处

  • 持久化:工作流可以在服务器重启和网络故障中存活
  • 异步设计:AI 客户端不会阻塞等待完成
  • 可观察:每个工作流都在 Waterline 的仪表板中跟踪
  • 安全:基于白名单的工作流访问防止任意执行
  • 无状态 MCP:服务器不保存状态。客户端跟踪工作流 ID

在浏览器中立即试用

此 MCP 集成已包含并预配置在 Laravel Workflow Sample App 中。

要试用:

  1. 在 GitHub 上打开 sample-app 仓库
  2. 点击 Code → Codespaces → Create codespace on main
  3. 等待环境构建
  4. 设置应用并启动队列工作器:
bash 复制代码
php artisan app:init
php artisan queue:work
  1. 启用 Laravel Workflow Server MCP 工具
  2. 让你的 AI 列出并运行工作流!

接下来可以做什么

你可以扩展此模式以实现:

  • 参数化工作流:将用户输入传递给工作流参数
  • Webhook 通知:推送完成事件而不是轮询
  • 工作流信号:让 AI 客户端向等待的工作流发送信号
  • 进度流式传输:使用 SSE 实时流式传输工作流进度
  • 多步骤代理:在对话中将多个工作流链接在一起

Laravel Workflow 的持久化执行与 MCP 的工具协议相结合,为真正有能力的 AI 代理创建了一个基础,这些代理可以处理现实世界的复杂性。

相关推荐
大学生资源网2 小时前
基于springboot的农村综合风貌展示平台设计与实现(源码+文档)
java·数据库·spring boot·后端·毕业设计·源码·springboot
BingoGo2 小时前
使用 Laravel Workflow 作为 MCP 工具提供给 AI 客户端
后端·php·laravel
czlczl200209252 小时前
Spring Boot Filter 机制与 FilterRegistrationBean
java·spring boot·后端
木子欢儿2 小时前
在 Debian 13 上搭建一个 NTP (Network Time Protocol) 服务器
运维·服务器·开发语言·debian·php
bybitq2 小时前
Go-Package-Module-functions
开发语言·后端·golang
QH_ShareHub2 小时前
SSH 隧道:如何让本机借用服务器网络
运维·ssh·php
Miss_Chenzr2 小时前
Springboot文化艺术发展有限公司4rl42(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。
数据库·spring boot·后端
m0_738120722 小时前
渗透测试——靶机DC-5详细渗透getshell过程
网络·安全·web安全·ssh·php
Alex_81D2 小时前
Spring Data JPA以及JPQL等特性详细使用教程
java·数据库·后端