80 lines
2.8 KiB
PHP
80 lines
2.8 KiB
PHP
<?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;
|
||
}
|
||
}
|