Files
Leantime/app/Mcp/Servers/OneBotServer.php

80 lines
2.8 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace Leantime\Mcp\Servers;
use Laravel\Mcp\Server as McpServer;
use Laravel\Mcp\Server\Tool;
/**
* OneBot 的 MCP server暴露所有 Domain 下 Tools 目录里定义的 AI 工具。
*
* 采用 boot() 动态发现(扫描 app/Domain 各域的 Tools 目录),而非静态 tools 数组,
* 这样后续新增任何域的 Tool 类都会自动注册,无需改这里。
*/
class OneBotServer extends McpServer
{
public string $serverName = 'OneBot';
public string $serverVersion = '1.0.0';
public function __construct()
{
parent::__construct();
$this->instructions = implode("\n", [
'你是 OneBot 项目管理系统的 AI 助手,可通过工具直接操作系统完成用户指令。',
'',
'【身份】你有系统操作能力,但只是辅助;涉及删除、批量修改、覆盖导入等高风险写操作前,必须先列出将影响的数据并征得用户明确确认。',
'',
'【业务规则】',
'- BOM / 工艺文件 / 工具清单是「全局主数据」(跨项目共享),修改会影响所有引用它的项目。',
'- 项目对主数据是「单向引用」:项目级追加的列/值不影响全局主数据。',
'- 删除、批量修改、覆盖导入前,先列影响并确认。',
'',
'【边界】不臆造数据;工具返回错误时如实报告并给排查建议,不反复重试同一失败操作;回答用简体中文;金额用人民币。',
]);
}
/**
* 动态发现并注册所有 Domain Tools。
*/
public function boot(): void
{
$pattern = app()->basePath('app/Domain/*/Tools/*Tool.php');
$files = glob($pattern) ?: [];
foreach ($files as $file) {
$class = $this->classFromFile($file);
if ($class !== null && class_exists($class) && is_subclass_of($class, Tool::class)) {
try {
$this->addTool($class);
} catch (\Throwable $e) {
// 单个工具失败不阻断整个 server 启动
}
}
}
}
/**
* 从文件路径推导类名app/Domain/Projects/Tools/GetAllProjectsTool.php
* → Leantime\Domain\Projects\Tools\GetAllProjectsTool
*/
private function classFromFile(string $file): ?string
{
$appRoot = rtrim(app()->basePath('app'), '/\\');
if (! str_starts_with($file, $appRoot)) {
return null;
}
$relative = substr($file, strlen($appRoot));
$relative = ltrim(str_replace(['/', '\\'], '\\', $relative), '\\');
$relative = preg_replace('/\.php$/', '', $relative);
if ($relative === '' || $relative === null) {
return null;
}
return app()->getNamespace().$relative;
}
}