鸿蒙应用开发从入门到实战(十三):ArkUI组件Slider&Progress

夜科贡渍为什么选择 MCP + Laravel Workflow?

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

异步 AI 操作:启动工作流,继续对话,稍后检查结果

可靠执行:工作流可以在崩溃、重试和长时间等待中存活

可观察性:通过 Waterline 的仪表板跟踪每个工作流

无状态服务器:MCP 服务器不保存状态。客户端跟踪工作流 ID

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

我们要构建什么

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

工具 用途

list_workflows 发现可用的工作流并查看最近的运行记录

start_workflow 启动工作流并获取跟踪 ID

get_workflow_result 检查状态并在完成时检索输出

分步实现

安装 Laravel MCP

composer require laravel/mcp

php artisan vendor:publish --tag=ai-routes

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

创建 MCP 服务器

php artisan make:mcp-server WorkflowServer

为 AI 配置说明:

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,

];

}

创建启动工作流工具

php artisan make:mcp-tool StartWorkflowTool

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.'),

];

}

}

创建获取结果工具

php artisan make:mcp-tool GetWorkflowResultTool

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.'),

];

}

}

创建列表工作流工具

php artisan make:mcp-tool ListWorkflowsTool

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 客户端可以启动的工作流加入白名单:

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:

use App\Mcp\Servers\WorkflowServer;

use Laravel\Mcp\Facades\Mcp;

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

连接 AI 客户端

VS Code / GitHub Copilot

在项目中创建 .vscode/mcp.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 中。

要试用:

在 GitHub 上打开 sample-app 仓库

点击 Code → Codespaces → Create codespace on main

等待环境构建

设置应用并启动队列工作器:

php artisan app:init

php artisan queue:work

启用 Laravel Workflow Server MCP 工具

让你的 AI 列出并运行工作流!

接下来可以做什么

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

参数化工作流:将用户输入传递给工作流参数

Webhook 通知:推送完成事件而不是轮询

工作流信号:让 AI 客户端向等待的工作流发送信号

进度流式传输:使用 SSE 实时流式传输工作流进度

多步骤代理:在对话中将多个工作流链接在一起