diff --git a/.gitignore b/.gitignore index 00428b401..610d1e32a 100644 --- a/.gitignore +++ b/.gitignore @@ -24,7 +24,6 @@ node_modules/ npm-debug.log* yarn-debug.log* yarn-error.log* -package-lock.json yarn.lock # Python (if adding scripts) @@ -57,6 +56,10 @@ dist/ build/ *.egg-info/ +# MCP Server +mcp-server/dist/ +mcp-server/node_modules/ + # Personal notes and scratch files scratch/ notes/ @@ -71,6 +74,12 @@ integrations/gemini-cli/skills/ integrations/gemini-cli/gemini-extension.json integrations/opencode/agents/ integrations/cursor/rules/ +integrations/trae/rules/ +integrations/mcp-server/trae-mcp-config.json +integrations/mcp-server/cursor-mcp-config.json +integrations/mcp-server/claude-mcp-config.json +.trae/* +.omx/ integrations/aider/CONVENTIONS.md integrations/windsurf/.windsurfrules integrations/openclaw/* diff --git a/README.md b/README.md index 2dfcf3615..c9aa0ead0 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ Each agent file contains: Browse the agents below and copy/adapt the ones you need! -### Option 3: Use with Other Tools (GitHub Copilot, Antigravity, Gemini CLI, OpenCode, OpenClaw, Cursor, Aider, Windsurf, Kimi Code) +### Option 3: Use with Other Tools (GitHub Copilot, Antigravity, Gemini CLI, OpenCode, OpenClaw, Cursor, Aider, Windsurf, Kimi Code, MCP Server) ```bash # Step 1 -- generate integration files for all supported tools @@ -66,6 +66,7 @@ Browse the agents below and copy/adapt the ones you need! ./scripts/install.sh --tool aider ./scripts/install.sh --tool windsurf ./scripts/install.sh --tool kimi +./scripts/install.sh --tool mcp-server ``` See the [Multi-Tool Integrations](#-multi-tool-integrations) section below for full details. @@ -555,6 +556,7 @@ The Agency works natively with Claude Code, and ships conversion + install scrip - **[OpenClaw](https://github.com/openclaw/openclaw)** — `SOUL.md` + `AGENTS.md` + `IDENTITY.md` per agent - **[Qwen Code](https://github.com/QwenLM/qwen-code)** — `.md` SubAgent files → `~/.qwen/agents/` - **[Kimi Code](https://github.com/MoonshotAI/kimi-cli)** — YAML agent specs → `~/.config/kimi/agents/` +- **[MCP Server](integrations/mcp-server/README.md)** — Model Context Protocol server → Trae / Cursor / Claude Desktop --- @@ -593,7 +595,7 @@ The installer scans your system for installed tools, shows a checkbox UI, and le [ ] 10) [ ] Qwen Code (~/.qwen/agents) [ ] 11) [ ] Kimi Code (~/.config/kimi/agents) - [1-11] toggle [a] all [n] none [d] detected + [1-N] toggle [a] all [n] none [d] detected [Enter] install [q] quit ``` @@ -603,6 +605,7 @@ The installer scans your system for installed tools, shows a checkbox UI, and le ./scripts/install.sh --tool opencode ./scripts/install.sh --tool openclaw ./scripts/install.sh --tool antigravity +./scripts/install.sh --tool mcp-server ``` **Non-interactive (CI/scripts):** diff --git a/docs/mcp-server-development-plan.md b/docs/mcp-server-development-plan.md new file mode 100644 index 000000000..143a752df --- /dev/null +++ b/docs/mcp-server-development-plan.md @@ -0,0 +1,369 @@ +# Agency MCP Server — 开发规划 + +> 版本: 1.0-draft +> 日期: 2026-05-09 +> 状态: 待评审 + +--- + +## 1. 项目背景与动机 + +### 1.1 现状 + +Agency Agents 项目包含仓库内的专业 AI Agent 定义(Markdown + YAML Frontmatter 格式),覆盖工程、设计、营销、产品、测试等 14 个专业领域。当前通过 `convert.sh` + `install.sh` 管道转换为各 IDE 的规则文件(如 Cursor `.mdc`、Trae `.md`),以静态文本注入方式使用。 + +### 1.2 问题 + +| 问题 | 影响 | +|------|------| +| Agent 不可发现 | 用户必须记住 Agent 名称才能通过 `#name` 引用 | +| 无编排能力 | NEXUS 7 阶段流水线策略无法在 IDE 中原生执行 | +| 语义错配 | Agent 人格被塞入"规则"容器,丢失动态决策能力 | +| 项目级绑定 | 每个项目需独立安装规则文件,无法跨项目复用 | +| 无状态管理 | Agent 无法跨会话记忆,每次激活都是冷启动 | + +### 1.3 目标 + +构建 Agency MCP Server,将 Agent 从"静态规则文本"升级为"可发现、可调用、可编排"的动态服务,同时与现有 Rules 方式并存互补。 + +--- + +## 2. 架构设计 + +### 2.1 系统架构 + +```text +┌─────────────────────────────────────────────────────────────┐ +│ 宿主 AI (Trae / Claude / Cursor) │ +│ │ +│ Rules 方式 (现有) MCP Server 方式 (新增) │ +│ ───────────────── ────────────────────── │ +│ #devops-automator invoke_agent │ +│ 手动记忆名称 list_agents 浏览发现 │ +│ 单 Agent 激活 orchestrate 多 Agent 编排 │ +│ 零依赖 需 Node.js 运行时 │ +└───────────────────────────┬─────────────────────────────────┘ + │ MCP Protocol (Stdio) + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ Agency MCP Server (Node.js) │ +│ │ +│ ┌──────────────┐ ┌───────────────┐ ┌─────────────────┐ │ +│ │ list_agents │ │ invoke_agent │ │ orchestrate │ │ +│ │ 浏览 + 搜索 │ │ 加载 Agent 人格│ │ NEXUS 编排 │ │ +│ └──────────────┘ └───────────────┘ └─────────────────┘ │ +│ │ +│ ┌─────────────────────────────────────────────────────┐ │ +│ │ Resources: agents://{name}/profile │ │ +│ │ Prompts: nexus-pipeline, agent-activation │ │ +│ └─────────────────────────────────────────────────────┘ │ +│ │ +│ 数据层: 运行时读取 */*.md Agent 定义 + strategy/*.md │ +└─────────────────────────────────────────────────────────────┘ +``` + +### 2.2 设计原则 + +| 原则 | 说明 | +|------|------| +| **只路由,不执行** | Server 返回 Agent 人格和编排计划,由宿主 AI 执行,Server 本身无 LLM 调用 | +| **零外部 API** | 不依赖任何云服务,纯本地文件读取 + 结构化输出 | +| **与 Rules 并存** | MCP 不替代 Rules,两者服务不同场景 | +| **增量复杂度** | 阶段 1 可独立交付,后续阶段按需叠加 | +| **源文件唯一真相** | Agent 定义仍由 `*/.md` 源文件维护,Server 运行时读取 | + +### 2.3 Tool 详细规格 + +#### `list_agents` — Agent 发现与浏览 + +``` +描述: 浏览和搜索 Agency 当前仓库中的专业 Agent 目录 + +参数: + - category (可选): 按分类过滤 + 枚举: engineering | design | marketing | product | project-management | + testing | support | spatial-computing | specialized | finance | + sales | academic | game-development | paid-media + - query (可选): 关键词搜索(匹配 name + description) + - format (可选): 输出格式 + 枚举: summary (默认,名称+描述) | full (完整人格概要) + +返回: Agent 列表,每项包含: + - name: Agent 名称 + - slug: URL 安全标识符 + - description: 一句话描述 + - category: 所属分类 + - emoji: 标识图标 +``` + +#### `invoke_agent` — Agent 人格加载 + +```text +描述: 加载指定 Agent 的完整人格定义,注入到当前对话上下文 + +参数: + - name (必需): Agent 名称或 slug + 示例: "devops-automator" | "DevOps Automator" + - task (可选): 要执行的具体任务描述 + 作用: 附加到 Agent 人格之后,作为任务指令 + - context (可选): 项目上下文信息 + 作用: 技术栈、约束条件等,帮助 Agent 更精准响应 + +返回: 结构化 Agent 激活指令: + - system_prompt: Agent 完整 System Prompt + - task_instruction: 任务指令(如有) + - suggested_globs: 建议关联的文件模式 + - quality_gates: 该 Agent 的质量验证标准 +``` + +#### `orchestrate` — NEXUS 多 Agent 编排 + +``` +描述: 根据 NEXUS 策略,为复杂任务生成多 Agent 编排计划 + +参数: + - task (必需): 任务描述 + 示例: "构建一个 SaaS 产品的 MVP" + - mode (可选): 编排规模 + 枚举: micro (5-10 核心 Agent, 1-5天) | + sprint (15-25 核心 Agent, 2-6周) | + full (完整 NEXUS 核心 roster, 12-24周) + 默认: sprint + - constraints (可选): 约束条件 + 示例: "只用 TypeScript" | "预算有限" + +返回: NEXUS 编排计划: + - phases: 各阶段详情 + - phase_name: 阶段名称 (Discovery → Strategy → ...) + - agents: 该阶段激活的 Agent 列表 + - parallel_tracks: 可并行的工作流 + - quality_gate: 阶段质量门禁标准 + - handoff: 下一阶段的交接内容 + - total_agents: 涉及的 Agent 总数 + - estimated_phases: 预计阶段数 +``` + +### 2.4 Resource 规格 + +```text +agents://{name}/profile + → 返回指定 Agent 的完整 Markdown 定义(原始源文件内容) + +agents://{category}/catalog + → 返回指定分类下所有 Agent 的摘要列表 +``` + +### 2.5 Prompt Template 规格 + +``` +nexus-pipeline + 参数: task, mode + → 生成完整的 NEXUS 7 阶段执行引导 Prompt + +agent-activation + 参数: agent_name, task + → 生成 Agent 激活指令(含人格 + 任务 + 质量标准) +``` + +--- + +## 3. 技术选型 + +| 组件 | 选型 | 理由 | +|------|------|------| +| **运行时** | Node.js 18+ | MCP SDK 原生支持,与项目 MCP Builder Agent 推荐一致 | +| **语言** | TypeScript 5 | 类型安全,MCP SDK 一等公民 | +| **MCP SDK** | `@modelcontextprotocol/sdk` | 官方 SDK,Stdio 传输开箱即用 | +| **参数校验** | `zod` | MCP SDK 内置依赖,Schema 定义即文档 | +| **YAML 解析** | `gray-matter` | 轻量 Frontmatter 解析,零配置 | +| **传输方式** | Stdio | 本地使用零配置,所有主流 IDE 支持 | +| **构建** | `tsc` | 无需打包器,直接编译 | + +### 依赖清单 + +```json +{ + "dependencies": { + "@modelcontextprotocol/sdk": "^1.12.0", + "gray-matter": "^4.0.3", + "zod": "^3.24.0" + }, + "devDependencies": { + "typescript": "^5.7.0", + "@types/node": "^22.0.0" + } +} +``` + +--- + +## 4. 目录结构 + +``` +mcp-server/ +├── package.json +├── tsconfig.json +├── src/ +│ ├── index.ts # 入口:创建 Server + 注册 Tool/Resource/Prompt +│ ├── loader.ts # Agent 加载器:扫描目录 + 解析 Frontmatter +│ ├── types.ts # TypeScript 类型定义 +│ ├── nexus.ts # NEXUS 策略解析 + 编排引擎 +│ ├── tools/ +│ │ ├── list-agents.ts # list_agents Tool 实现 +│ │ ├── invoke-agent.ts # invoke_agent Tool 实现 +│ │ └── orchestrate.ts # orchestrate Tool 实现 +│ ├── resources/ +│ │ └── agent-profile.ts # agents:// Resource 实现 +│ └── prompts/ +│ ├── nexus-pipeline.ts # NEXUS 流水线 Prompt 模板 +│ └── agent-activate.ts # Agent 激活 Prompt 模板 +├── dist/ # 编译输出 (.gitignore) +└── README.md +``` + +--- + +## 5. 分阶段交付计划 + +### 阶段 1: 项目骨架与 Agent 加载器 + +**交付物**: 可启动的 MCP Server,`list_agents` 可用 + +| 编号 | 任务 | 验收标准 | +|------|------|----------| +| 1.1 | 初始化 `mcp-server/` 目录结构 | `package.json` + `tsconfig.json` 就绪 | +| 1.2 | 安装依赖并验证编译 | `npm run build` 无错误 | +| 1.3 | 定义 `AgentProfile`、`AgentCategory` 等类型 | 类型完整覆盖 Frontmatter 字段 | +| 1.4 | 实现 Agent 加载器 `loader.ts` | 扫描 14 个目录,解析仓库内全部 Agent,构建索引 | +| 1.5 | 实现 `list_agents` Tool | 返回 Agent 目录,支持 category 过滤和 query 搜索 | +| 1.6 | 实现 Server 入口 `index.ts` | Stdio 传输,可被 IDE 连接 | +| 1.7 | 端到端验证 | 在 Trae IDE 中调用 `list_agents` 成功返回 | + +### 阶段 2: 核心 Tool 实现 + +**交付物**: `invoke_agent` + `orchestrate` + Resources + Prompts + +| 编号 | 任务 | 验收标准 | +|------|------|----------| +| 2.1 | 实现 `invoke_agent` Tool | 输入 Agent 名称,返回完整 System Prompt | +| 2.2 | 实现 `agents://{name}/profile` Resource | 返回 Agent 原始 Markdown | +| 2.3 | 实现 `agents://{category}/catalog` Resource | 返回分类摘要 | +| 2.4 | 实现 `nexus-pipeline` Prompt 模板 | 输入 task + mode,生成 7 阶段引导 | +| 2.5 | 实现 `agent-activation` Prompt 模板 | 输入 agent + task,生成激活指令 | +| 2.6 | 端到端验证 | 在 IDE 中调用 `invoke_agent` 成功加载人格 | + +### 阶段 3: NEXUS 编排引擎 + +**交付物**: `orchestrate` 返回完整的 NEXUS 执行计划 + +| 编号 | 任务 | 验收标准 | +|------|------|----------| +| 3.1 | 解析 `strategy/nexus-strategy.md` | 提取 Phase-Agent 映射表 | +| 3.2 | 解析 7 个 Phase playbook | 提取各阶段 Agent 角色、并行工作流、质量门禁 | +| 3.3 | 实现编排引擎 `nexus.ts` | 根据 task + mode 生成编排计划 | +| 3.4 | 实现 `orchestrate` Tool | 返回结构化编排计划(Agent 组合 + 执行顺序 + 质量门禁) | +| 3.5 | 支持 micro / sprint / full 三种模式 | 各模式返回合理规模的 Agent 集合 | +| 3.6 | 端到端验证 | 输入 "构建 SaaS MVP",返回合理的 NEXUS 计划 | + +### 阶段 4: 集成与发布 + +**交付物**: 一键安装,与项目工具链完整整合 + +| 编号 | 任务 | 验收标准 | +|------|------|----------| +| 4.1 | 在 `convert.sh` 注册 `mcp-server` | `--tool mcp-server` 生成配置片段 | +| 4.2 | 在 `install.sh` 注册 `mcp-server` | 自动写入 IDE 的 `mcpServers` 配置 | +| 4.3 | 创建 `integrations/mcp-server/README.md` | 安装和使用文档 | +| 4.4 | 添加构建与启动脚本 | `npm run build` + `npm start` 可用 | +| 4.5 | 更新 `.gitignore` | 忽略 `mcp-server/dist/` 和 `node_modules/` | +| 4.6 | 全流程端到端测试 | convert → install → IDE 连接 → Tool 调用 全链路通过 | + +--- + +## 6. 关键技术决策 + +### 6.1 为什么 Server 不调用 LLM? + +Server 只做**路由和结构化**,不做推理。原因: +- 避免引入 API Key 管理复杂度 +- 保持零外部依赖、纯本地运行 +- 宿主 AI 本身就是 LLM,不需要 Server 再调一次 +- 编排计划由规则驱动(NEXUS 策略文件),不需要 LLM 推理 + +### 6.2 为什么用 Stdio 而非 SSE? + +- 本地使用场景,Stdio 零配置 +- 所有主流 IDE(Trae、Claude Desktop、Cursor)原生支持 Stdio +- 无需端口管理、无 CORS 问题 +- 后续如需远程部署,可增量添加 SSE 传输 + +### 6.3 为什么 invoke_agent 返回 Prompt 而非直接执行? + +MCP Tool 的返回值是文本内容,宿主 AI 将其作为上下文使用。返回 System Prompt 让宿主 AI "扮演"该 Agent 是当前 MCP 协议下最合理的方式: +- 宿主 AI 拥有完整的推理能力 +- Agent 人格 + 任务指令的组合效果远超纯规则注入 +- 无需 Server 维护对话状态 + +### 6.4 编排引擎为什么是规则驱动而非 AI 驱动? + +NEXUS 策略已经明确定义了 Phase-Agent 映射关系,不需要 AI 推理来决定"哪个 Agent 在哪个阶段"。规则驱动的好处: +- 确定性:相同输入总是返回相同编排计划 +- 可审计:编排逻辑来自可读的策略文件 +- 可维护:修改策略文件即可调整编排行为 +- 零延迟:无 LLM 调用开销 + +--- + +## 7. 风险与缓解 + +| 风险 | 概率 | 影响 | 缓解措施 | +|------|------|------|----------| +| MCP SDK 版本不兼容 | 中 | 高 | 锁定 SDK 版本,跟进官方 Release | +| Agent 名称模糊匹配不准 | 中 | 低 | 优先精确匹配 slug,模糊匹配时返回候选列表 | +| NEXUS 策略文件格式变更 | 低 | 中 | 解析层做防御性编程,缺失字段用默认值 | +| 宿主 AI 误解编排计划 | 中 | 中 | Prompt 模板中强化指令清晰度,提供示例 | +| Node.js 环境未安装 | 中 | 低 | 文档中说明前置条件,install.sh 检测 Node | + +--- + +## 8. 与 Rules 方式的定位对比 + +```text +场景决策树: + +用户需求 + │ + ├── "帮我做一件具体的事" (单 Agent) + │ │ + │ ├── 我知道 Agent 名字 → Rules: #name (更快) + │ └── 我不知道有哪些 Agent → MCP: list_agents → invoke_agent + │ + └── "帮我完成一个复杂项目" (多 Agent) + │ + ├── 简单顺序 → MCP: invoke_agent × N (手动编排) + └── 需要 NEXUS 流水线 → MCP: orchestrate (自动编排) +``` + +| 维度 | Rules | MCP Server | +|------|-------|------------| +| 依赖 | 零 | Node.js | +| 发现 | 无 | list_agents | +| 编排 | 无 | orchestrate | +| 跨项目 | 每项目安装 | 一个 Server 服务所有项目 | +| 状态 | 无 | 无(Phase 1-4 均无状态) | +| 适合 | 快速单次任务 | 复杂多 Agent 协作 | + +--- + +## 9. 后续演进方向(Phase 5+) + +以下为未来可探索的方向,不在当前 4 阶段范围内: + +| 方向 | 说明 | +|------|------| +| **Agent 状态持久化** | 跨会话记忆,Agent 记住之前的工作上下文 | +| **SSE 传输** | 支持远程部署,团队共享一个 Agency Server | +| **动态 Tool 注册** | Agent 可注册自己的专属 MCP Tool(如 DevOps Agent 注册 `deploy` Tool) | +| **Agent 间通信** | 多个 Agent 通过 Server 共享上下文和中间产物 | +| **Web UI** | Agent 目录浏览 + 编排计划可视化的 Web 界面 | +| **CI/CD 集成** | Agency Server 作为 CI Pipeline 的 Agent 调度器 | diff --git a/integrations/mcp-server/README.md b/integrations/mcp-server/README.md new file mode 100644 index 000000000..f9a0715d6 --- /dev/null +++ b/integrations/mcp-server/README.md @@ -0,0 +1,105 @@ +# Agency MCP Server + +Agency MCP Server 将 Agency Agents 仓库中的专业 AI Agent 从静态规则文本升级为可发现、可调用、可编排的动态 MCP 服务。 + +## 安装 + +### 前置条件 + +- Node.js 18+ +- 已克隆 [agency-agents](https://github.com/msitarzewski/agency-agents) 仓库 + +### 方式 1: 使用 install.sh + +```bash +cd agency-agents +./scripts/install.sh --tool mcp-server +``` + +### 方式 2: 手动安装 + +```bash +cd agency-agents/mcp-server +npm ci +npm run build +npm test +``` + +然后在 IDE 的 mcpServers 配置中添加: + +```json +{ + "mcpServers": { + "agency": { + "command": "node", + "args": ["/mcp-server/dist/index.js"] + } + } +} +``` + +### IDE 配置文件位置 + +| IDE | 配置文件 | +|-----|---------| +| **Trae** | `.trae/mcp.json` | +| **Cursor** | `.cursor/mcp.json` | +| **Claude Desktop** | `claude_desktop_config.json` | + +## 功能 + +### Tools + +- **`list_agents`** — 浏览和搜索当前仓库中的 Agent 目录,支持按分类过滤和关键词搜索 +- **`invoke_agent`** — 加载指定 Agent 的完整人格定义到当前对话上下文 +- **`orchestrate`** — 基于 NEXUS 7 阶段策略生成多 Agent 编排计划,`micro` / `sprint` / `full` 分别对应小型、标准与完整核心 roster + +### Resource Templates + +- **`agents://{name}/profile`** — 返回 Agent 的原始 Markdown 定义 +- **`agents://{category}/catalog`** — 返回分类下所有 Agent 的摘要列表 + +### Prompts + +- **`nexus-pipeline`** — 生成 NEXUS 7 阶段执行引导 Prompt +- **`agent-activation`** — 生成 Agent 激活指令 + +## 使用示例 + +```text +# 浏览所有 Agent +list_agents + +# 搜索特定领域 +list_agents category="engineering" + +# 关键词搜索 +list_agents query="devops" + +# 激活 Agent +invoke_agent name="devops-automator" task="设置 CI/CD 流水线" + +# 编排多 Agent +orchestrate task="构建 SaaS MVP" mode="sprint" +``` + +## 与 Rules 方式的关系 + +MCP Server 与现有的 Rules 方式并存互补: +- **Rules**: 快速单次任务,提前知道 Agent 名称 +- **MCP Server**: 浏览发现 Agent + 复杂多 Agent 协作编排 + +## 开发 + +```bash +cd mcp-server +npm ci +npm run build # 编译 TypeScript +npm test # 运行 smoke test +npm start # 启动 Server +``` + +修改源文件后重新构建: +```bash +npm run build +``` diff --git a/mcp-server/package-lock.json b/mcp-server/package-lock.json new file mode 100644 index 000000000..67dedba9f --- /dev/null +++ b/mcp-server/package-lock.json @@ -0,0 +1,1290 @@ +{ + "name": "agency-mcp-server", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "agency-mcp-server", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "@modelcontextprotocol/sdk": "^1.12.0", + "gray-matter": "^4.0.3", + "zod": "^3.24.0" + }, + "bin": { + "agency-mcp-server": "dist/index.js" + }, + "devDependencies": { + "@types/node": "^22.0.0", + "typescript": "^5.7.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@hono/node-server": { + "version": "1.19.14", + "resolved": "https://registry.npmmirror.com/@hono/node-server/-/node-server-1.19.14.tgz", + "integrity": "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==", + "license": "MIT", + "engines": { + "node": ">=18.14.1" + }, + "peerDependencies": { + "hono": "^4" + } + }, + "node_modules/@modelcontextprotocol/sdk": { + "version": "1.29.0", + "resolved": "https://registry.npmmirror.com/@modelcontextprotocol/sdk/-/sdk-1.29.0.tgz", + "integrity": "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==", + "license": "MIT", + "dependencies": { + "@hono/node-server": "^1.19.9", + "ajv": "^8.17.1", + "ajv-formats": "^3.0.1", + "content-type": "^1.0.5", + "cors": "^2.8.5", + "cross-spawn": "^7.0.5", + "eventsource": "^3.0.2", + "eventsource-parser": "^3.0.0", + "express": "^5.2.1", + "express-rate-limit": "^8.2.1", + "hono": "^4.11.4", + "jose": "^6.1.3", + "json-schema-typed": "^8.0.2", + "pkce-challenge": "^5.0.0", + "raw-body": "^3.0.0", + "zod": "^3.25 || ^4.0", + "zod-to-json-schema": "^3.25.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@cfworker/json-schema": "^4.1.1", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "@cfworker/json-schema": { + "optional": true + }, + "zod": { + "optional": false + } + } + }, + "node_modules/@types/node": { + "version": "22.19.18", + "resolved": "https://registry.npmmirror.com/@types/node/-/node-22.19.18.tgz", + "integrity": "sha512-9v00a+dn2yWVsYDEunWC4g/TcRKVq3r8N5FuZp7u0SGrPvdN9c2yXI9bBuf5Fl0hNCb+QTIePTn5pJs2pwBOQQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmmirror.com/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmmirror.com/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/body-parser": { + "version": "2.2.2", + "resolved": "https://registry.npmmirror.com/body-parser/-/body-parser-2.2.2.tgz", + "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.3", + "http-errors": "^2.0.0", + "iconv-lite": "^0.7.0", + "on-finished": "^2.4.1", + "qs": "^6.14.1", + "raw-body": "^3.0.1", + "type-is": "^2.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/content-disposition": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/content-disposition/-/content-disposition-1.1.0.tgz", + "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmmirror.com/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmmirror.com/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmmirror.com/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/cors": { + "version": "2.8.6", + "resolved": "https://registry.npmmirror.com/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmmirror.com/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eventsource": { + "version": "3.0.7", + "resolved": "https://registry.npmmirror.com/eventsource/-/eventsource-3.0.7.tgz", + "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", + "license": "MIT", + "dependencies": { + "eventsource-parser": "^3.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/eventsource-parser": { + "version": "3.0.8", + "resolved": "https://registry.npmmirror.com/eventsource-parser/-/eventsource-parser-3.0.8.tgz", + "integrity": "sha512-70QWGkr4snxr0OXLRWsFLeRBIRPuQOvt4s8QYjmUlmlkyTZkRqS7EDVRZtzU3TiyDbXSzaOeF0XUKy8PchzukQ==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/express": { + "version": "5.2.1", + "resolved": "https://registry.npmmirror.com/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express-rate-limit": { + "version": "8.5.1", + "resolved": "https://registry.npmmirror.com/express-rate-limit/-/express-rate-limit-8.5.1.tgz", + "integrity": "sha512-5O6KYmyJEpuPJV5hNTXKbAHWRqrzyu+OI3vUnSd2kXFubIVpG7ezpgxQy76Zo5GQZtrQBg86hF+CM/NX+cioiQ==", + "license": "MIT", + "dependencies": { + "ip-address": "^10.2.0" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": ">= 4.11" + } + }, + "node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "license": "MIT", + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmmirror.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/fast-uri/-/fast-uri-3.1.2.tgz", + "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmmirror.com/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gray-matter": { + "version": "4.0.3", + "resolved": "https://registry.npmmirror.com/gray-matter/-/gray-matter-4.0.3.tgz", + "integrity": "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==", + "license": "MIT", + "dependencies": { + "js-yaml": "^3.13.1", + "kind-of": "^6.0.2", + "section-matter": "^1.0.0", + "strip-bom-string": "^1.0.0" + }, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/hasown/-/hasown-2.0.3.tgz", + "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hono": { + "version": "4.12.18", + "resolved": "https://registry.npmmirror.com/hono/-/hono-4.12.18.tgz", + "integrity": "sha512-RWzP96k/yv0PQfyXnWjs6zot20TqfpfsNXhOnev8d1InAxubW93L11/oNUc3tQqn2G0bSdAOBpX+2uDFHV7kdQ==", + "license": "MIT", + "engines": { + "node": ">=16.9.0" + } + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ip-address": { + "version": "10.2.0", + "resolved": "https://registry.npmmirror.com/ip-address/-/ip-address-10.2.0.tgz", + "integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmmirror.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmmirror.com/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/jose": { + "version": "6.2.3", + "resolved": "https://registry.npmmirror.com/jose/-/jose-6.2.3.tgz", + "integrity": "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/js-yaml": { + "version": "3.14.2", + "resolved": "https://registry.npmmirror.com/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/json-schema-typed": { + "version": "8.0.2", + "resolved": "https://registry.npmmirror.com/json-schema-typed/-/json-schema-typed-8.0.2.tgz", + "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", + "license": "BSD-2-Clause" + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmmirror.com/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmmirror.com/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmmirror.com/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmmirror.com/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmmirror.com/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmmirror.com/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-to-regexp": { + "version": "8.4.2", + "resolved": "https://registry.npmmirror.com/path-to-regexp/-/path-to-regexp-8.4.2.tgz", + "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/pkce-challenge": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/pkce-challenge/-/pkce-challenge-5.0.1.tgz", + "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==", + "license": "MIT", + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmmirror.com/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/qs": { + "version": "6.15.1", + "resolved": "https://registry.npmmirror.com/qs/-/qs-6.15.1.tgz", + "integrity": "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/section-matter": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/section-matter/-/section-matter-1.0.0.tgz", + "integrity": "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==", + "license": "MIT", + "dependencies": { + "extend-shallow": "^2.0.1", + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/send": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/serve-static": { + "version": "2.2.1", + "resolved": "https://registry.npmmirror.com/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "license": "BSD-3-Clause" + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/strip-bom-string": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/strip-bom-string/-/strip-bom-string-1.0.0.tgz", + "integrity": "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmmirror.com/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmmirror.com/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-to-json-schema": { + "version": "3.25.2", + "resolved": "https://registry.npmmirror.com/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz", + "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==", + "license": "ISC", + "peerDependencies": { + "zod": "^3.25.28 || ^4" + } + } + } +} diff --git a/mcp-server/package.json b/mcp-server/package.json new file mode 100644 index 000000000..b63d92672 --- /dev/null +++ b/mcp-server/package.json @@ -0,0 +1,32 @@ +{ + "name": "agency-mcp-server", + "version": "1.0.0", + "description": "MCP Server for Agency Agents - discover, invoke, and orchestrate specialized AI agents", + "type": "module", + "license": "MIT", + "bin": { + "agency-mcp-server": "./dist/index.js" + }, + "main": "dist/index.js", + "files": [ + "dist" + ], + "engines": { + "node": ">=18.0.0" + }, + "scripts": { + "build": "tsc", + "test": "npm run build && node ./scripts/smoke-test.mjs", + "start": "node dist/index.js", + "dev": "tsc && node dist/index.js" + }, + "dependencies": { + "@modelcontextprotocol/sdk": "^1.12.0", + "gray-matter": "^4.0.3", + "zod": "^3.24.0" + }, + "devDependencies": { + "typescript": "^5.7.0", + "@types/node": "^22.0.0" + } +} diff --git a/mcp-server/scripts/smoke-test.mjs b/mcp-server/scripts/smoke-test.mjs new file mode 100644 index 000000000..ab2e472af --- /dev/null +++ b/mcp-server/scripts/smoke-test.mjs @@ -0,0 +1,172 @@ +import assert from "node:assert/strict"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import { Client } from "@modelcontextprotocol/sdk/client/index.js"; +import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"; +import { loadAgents } from "../dist/loader.js"; +import { generateOrchestrationPlan } from "../dist/nexus.js"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +const mcpRoot = path.resolve(__dirname, ".."); +const repoRoot = path.resolve(mcpRoot, ".."); +const serverEntry = path.resolve(mcpRoot, "dist/index.js"); + +const TIMEOUT_MS = 15_000; + +function withTimeout(promise, label) { + let timer; + const timeout = new Promise((_, reject) => { + timer = setTimeout( + () => reject(new Error(`Timeout (${TIMEOUT_MS}ms) waiting for: ${label}`)), + TIMEOUT_MS + ); + }); + return Promise.race([promise, timeout]).finally(() => clearTimeout(timer)); +} + +process.chdir(repoRoot); + +function extractText(result) { + return result.content + .filter((item) => item.type === "text") + .map((item) => item.text) + .join("\n"); +} + +const store = await loadAgents(); +assert.ok(store.profiles.length > 0, "Agent loader should load at least one agent"); +assert.ok(store.bySlug.size > 0, "Agent loader should build slug index"); +assert.ok(store.byCategory.size > 0, "Agent loader should build category index"); +assert.ok( + store.byCategory.has("engineering"), + "Agent loader should include 'engineering' category" +); +assert.ok( + store.profiles.some((p) => p.slug === "frontend-developer"), + "Agent loader should include known agent 'frontend-developer'" +); + +const microPlan = generateOrchestrationPlan(store, "Fix a production bug", "micro", ""); +assert.ok( + microPlan.total_agents >= 5 && microPlan.total_agents <= 10, + `Micro mode should stay within 5-10 core agents, got ${microPlan.total_agents}` +); + +const sprintPlan = generateOrchestrationPlan( + store, + "Build a SaaS MVP", + "sprint", + "TypeScript only" +); +assert.ok( + sprintPlan.total_agents >= 15 && sprintPlan.total_agents <= 25, + `Sprint mode should stay within 15-25 core agents, got ${sprintPlan.total_agents}` +); + +const fullPlan = generateOrchestrationPlan(store, "Build a SaaS MVP", "full", ""); +assert.ok( + fullPlan.total_agents >= sprintPlan.total_agents, + "Full mode should include at least as many core agents as sprint mode" +); +assert.deepEqual( + sprintPlan.phases[1]?.quality_gate.gate_keepers, + ["Studio Producer", "Reality Checker"], + "Dual gate keepers must be preserved in orchestration output" +); + +for (const plan of [microPlan, sprintPlan, fullPlan]) { + const uniqueAgents = new Set( + plan.phases.flatMap((phase) => phase.agents.map((agent) => agent.slug)) + ); + assert.equal( + plan.total_agents, + uniqueAgents.size, + `Plan ${plan.mode} should count unique core agents` + ); +} + +const client = new Client({ + name: "agency-mcp-smoke-test", + version: "1.0.0", +}); + +const transport = new StdioClientTransport({ + command: "node", + args: [serverEntry], +}); + +try { + await withTimeout(client.connect(transport), "client.connect"); + + const tools = await withTimeout(client.listTools(), "client.listTools"); + const toolNames = tools.tools.map((tool) => tool.name).sort(); + assert.deepEqual( + toolNames, + ["invoke_agent", "list_agents", "orchestrate"], + "MCP server should expose the expected tools" + ); + + const resources = await withTimeout(client.listResources(), "client.listResources"); + assert.equal( + resources.resources.length, + 0, + "Dynamic agent resources should be exposed via resource templates rather than static resources" + ); + + const templates = await withTimeout(client.listResourceTemplates(), "client.listResourceTemplates"); + const templateUris = templates.resourceTemplates + .map((template) => template.uriTemplate) + .sort(); + assert.deepEqual( + templateUris, + ["agents://{category}/catalog", "agents://{name}/profile"], + "MCP server should expose both dynamic resource templates" + ); + + const catalog = await withTimeout( + client.readResource({ uri: "agents://engineering/catalog" }), + "client.readResource(catalog)" + ); + assert.ok( + catalog.contents[0]?.text?.includes("Frontend Developer"), + "Engineering catalog should include known engineering agents" + ); + + const profile = await withTimeout( + client.readResource({ uri: "agents://devops-automator/profile" }), + "client.readResource(profile)" + ); + assert.ok( + profile.contents[0]?.text?.includes("DevOps Automator"), + "Agent profile should return the original markdown content" + ); + + const listAgentsResult = await withTimeout( + client.callTool({ name: "list_agents", arguments: { category: "engineering" } }), + "client.callTool(list_agents)" + ); + assert.ok( + extractText(listAgentsResult).includes("engineering"), + "list_agents should respond with engineering content" + ); + + const orchestrateResult = await withTimeout( + client.callTool({ name: "orchestrate", arguments: { task: "Build a SaaS MVP", mode: "sprint" } }), + "client.callTool(orchestrate)" + ); + const orchestrationText = extractText(orchestrateResult); + assert.ok( + orchestrationText.includes(`**Total Agents**: ${sprintPlan.total_agents}`), + "orchestrate tool output should report the unique core agent count" + ); + assert.ok( + orchestrationText.includes("Gate Keepers"), + "orchestrate tool output should surface multiple gate keepers" + ); +} finally { + await transport.close(); + await client.close(); +} + +console.log("Agency MCP Server smoke tests passed."); diff --git a/mcp-server/src/index.ts b/mcp-server/src/index.ts new file mode 100644 index 000000000..7fdf36747 --- /dev/null +++ b/mcp-server/src/index.ts @@ -0,0 +1,475 @@ +#!/usr/bin/env node +import { Server } from "@modelcontextprotocol/sdk/server/index.js"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import { + CallToolRequestSchema, + ListToolsRequestSchema, + ListResourcesRequestSchema, + ListResourceTemplatesRequestSchema, + ReadResourceRequestSchema, + ListPromptsRequestSchema, + GetPromptRequestSchema, +} from "@modelcontextprotocol/sdk/types.js"; +import { AGENT_DIRS } from "./types.js"; +import type { AgentStore } from "./types.js"; +import { + ListAgentsParams, + InvokeAgentParams, + OrchestrateParams, +} from "./types.js"; +import { loadAgents } from "./loader.js"; +import { listAgents } from "./tools/list-agents.js"; +import { invokeAgent } from "./tools/invoke-agent.js"; +import { orchestrate } from "./tools/orchestrate.js"; +import { getAgentProfile, getAgentCatalog } from "./resources/agent-profile.js"; +import { nexusPipelinePrompt } from "./prompts/nexus-pipeline.js"; +import { getAgentActivationPrompt } from "./prompts/agent-activate.js"; + +function createServer(store: AgentStore) { + try { + const agentCount = store.profiles.length; + + const server = new Server( + { + name: "agency-mcp-server", + version: "1.0.0", + }, + { + capabilities: { + tools: {}, + resources: {}, + prompts: {}, + }, + } + ); + + server.setRequestHandler(ListToolsRequestSchema, async () => { + try { + return { + tools: [ + { + name: "list_agents", + description: + `Browse and search the Agency's directory of ${agentCount} specialized AI agents. Filter by category or search by keyword.`, + inputSchema: { + type: "object", + properties: { + category: { + type: "string", + enum: AGENT_DIRS, + description: "Filter by agent category", + }, + query: { + type: "string", + description: "Keyword search in agent name and description", + }, + format: { + type: "string", + enum: ["summary", "full"], + description: "Output format: summary (default) or full personality overview", + }, + }, + }, + }, + { + name: "invoke_agent", + description: + "Load a specific agent's complete personality definition and inject it into the current conversation context. Supports slug names or full display names.", + inputSchema: { + type: "object", + properties: { + name: { + type: "string", + description: "Agent name or slug, e.g. 'devops-automator' or 'DevOps Automator'", + }, + task: { + type: "string", + description: "Specific task description to append to the agent's instructions", + }, + context: { + type: "string", + description: "Project context information (tech stack, constraints, etc.)", + }, + }, + required: ["name"], + }, + }, + { + name: "orchestrate", + description: + "Generate a NEXUS multi-agent orchestration plan for complex tasks. Supports micro (5-10 core agents), sprint (15-25 core agents), and full (complete curated NEXUS core roster) modes.", + inputSchema: { + type: "object", + properties: { + task: { + type: "string", + description: "Task description, e.g. 'Build a SaaS MVP'", + }, + mode: { + type: "string", + enum: ["micro", "sprint", "full"], + description: "Orchestration scale: micro (5-10 core agents, 1-5 days), sprint (15-25 core agents, 2-6 weeks), full (complete curated NEXUS core roster, 12-24 weeks)", + }, + constraints: { + type: "string", + description: "Constraints, e.g. 'TypeScript only, limited budget'", + }, + }, + required: ["task"], + }, + }, + ], + }; + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + throw new Error(`Failed to list tools: ${message}`); + } + }); + + server.setRequestHandler(CallToolRequestSchema, async (request) => { + const { name, arguments: args } = request.params; + + try { + switch (name) { + case "list_agents": { + const parsed = ListAgentsParams.safeParse(args || {}); + if (!parsed.success) { + return { + content: [ + { + type: "text", + text: `Invalid parameters: ${parsed.error.message}`, + }, + ], + }; + } + const result = listAgents(store, parsed.data); + return { content: [{ type: "text", text: result }] }; + } + + case "invoke_agent": { + const parsed = InvokeAgentParams.safeParse(args || {}); + if (!parsed.success) { + return { + content: [ + { + type: "text", + text: `Invalid parameters: ${parsed.error.message}`, + }, + ], + }; + } + const result = invokeAgent(store, parsed.data); + return { content: [{ type: "text", text: result }] }; + } + + case "orchestrate": { + const parsed = OrchestrateParams.safeParse(args || {}); + if (!parsed.success) { + return { + content: [ + { + type: "text", + text: `Invalid parameters: ${parsed.error.message}`, + }, + ], + }; + } + const result = orchestrate(store, { + task: parsed.data.task, + mode: parsed.data.mode, + constraints: parsed.data.constraints, + }); + return { content: [{ type: "text", text: result }] }; + } + + default: + return { + content: [ + { + type: "text", + text: `Unknown tool: ${name}. Available tools: list_agents, invoke_agent, orchestrate.`, + }, + ], + isError: true, + }; + } + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + return { + content: [ + { + type: "text", + text: `Failed to call tool '${name}': ${message}`, + }, + ], + isError: true, + }; + } + }); + + server.setRequestHandler(ListResourcesRequestSchema, async () => { + try { + return { resources: [] }; + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + throw new Error(`Failed to list resources: ${message}`); + } + }); + + server.setRequestHandler(ListResourceTemplatesRequestSchema, async () => { + try { + return { + resourceTemplates: [ + { + uriTemplate: "agents://{name}/profile", + name: "Agent Profile", + description: "Full markdown definition of a specific agent", + mimeType: "text/markdown", + }, + { + uriTemplate: "agents://{category}/catalog", + name: "Category Catalog", + description: "Summary list of all agents in a category", + mimeType: "text/markdown", + }, + ], + }; + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + throw new Error(`Failed to list resource templates: ${message}`); + } + }); + + server.setRequestHandler(ReadResourceRequestSchema, async (request) => { + const uri = request.params.uri; + + try { + const agentsPrefix = "agents://"; + if (!uri.startsWith(agentsPrefix)) { + return { + contents: [ + { + uri, + mimeType: "text/plain", + text: `Unknown resource: ${uri}`, + }, + ], + }; + } + + const path = uri.slice(agentsPrefix.length); + const parts = path.split("/"); + + if (parts.length === 2 && parts[1] === "profile") { + const result = await getAgentProfile(store, parts[0]); + if (!result) { + return { + contents: [ + { + uri, + mimeType: "text/plain", + text: `Agent "${parts[0]}" not found. Use list_agents to browse available agents.`, + }, + ], + }; + } + return { + contents: [{ uri, mimeType: result.mimeType, text: result.content }], + }; + } + + if (parts.length === 2 && parts[1] === "catalog") { + const result = getAgentCatalog(store, parts[0]); + if (!result) { + return { + contents: [ + { + uri, + mimeType: "text/plain", + text: `Unknown category "${parts[0]}". Available categories: ${AGENT_DIRS.join(", ")}`, + }, + ], + }; + } + return { + contents: [{ uri, mimeType: result.mimeType, text: result.content }], + }; + } + + return { + contents: [ + { + uri, + mimeType: "text/plain", + text: `Unknown resource pattern: ${uri}. Use agents://{name}/profile or agents://{category}/catalog.`, + }, + ], + }; + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + return { + contents: [ + { + uri, + mimeType: "text/plain", + text: `Failed to read resource '${uri}': ${message}`, + }, + ], + }; + } + }); + + server.setRequestHandler(ListPromptsRequestSchema, async () => { + try { + return { + prompts: [ + { + name: "nexus-pipeline", + description: + "Generate a complete NEXUS 7-phase execution guide prompt for multi-agent orchestration", + arguments: [ + { + name: "task", + description: "The task or project to execute", + required: true, + }, + { + name: "mode", + description: "Orchestration mode: micro, sprint, or full", + required: false, + }, + ], + }, + { + name: "agent-activation", + description: + "Generate an agent activation instruction prompt with personality, task, and quality standards", + arguments: [ + { + name: "agent_name", + description: "Name or slug of the agent to activate", + required: true, + }, + { + name: "task", + description: "The specific task for the agent to perform", + required: true, + }, + ], + }, + ], + }; + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + throw new Error(`Failed to list prompts: ${message}`); + } + }); + + server.setRequestHandler(GetPromptRequestSchema, async (request) => { + const { name, arguments: args } = request.params; + + try { + switch (name) { + case "nexus-pipeline": { + const task = (args?.task as string) || ""; + const mode = (args?.mode as "micro" | "sprint" | "full") || "sprint"; + if (!task) { + return { + messages: [ + { + role: "user", + content: { + type: "text", + text: "task is required for nexus-pipeline.", + }, + }, + ], + }; + } + const prompt = nexusPipelinePrompt({ task, mode }); + return { + messages: [ + { + role: "user", + content: { type: "text", text: prompt }, + }, + ], + }; + } + + case "agent-activation": { + const agent_name = (args?.agent_name as string) || ""; + const task = (args?.task as string) || ""; + if (!agent_name || !task) { + return { + messages: [ + { + role: "user", + content: { + type: "text", + text: "Both agent_name and task are required.", + }, + }, + ], + }; + } + const prompt = getAgentActivationPrompt(store, { agent_name, task }); + return { + messages: [ + { + role: "user", + content: { type: "text", text: prompt }, + }, + ], + }; + } + + default: + return { + messages: [ + { + role: "user", + content: { + type: "text", + text: `Unknown prompt: ${name}`, + }, + }, + ], + }; + } + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + return { + messages: [ + { + role: "user", + content: { + type: "text", + text: `Failed to get prompt '${name}': ${message}`, + }, + }, + ], + }; + } + }); + + return server; + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + console.error("Failed to create MCP server:", error); + throw new Error(`MCP server initialization failed: ${message}`); + } +} + +async function main() { + const store = await loadAgents(); + const server = createServer(store); + const transport = new StdioServerTransport(); + await server.connect(transport); +} + +main().catch((err) => { + console.error("Failed to start Agency MCP Server:", err); + process.exit(1); +}); diff --git a/mcp-server/src/loader.ts b/mcp-server/src/loader.ts new file mode 100644 index 000000000..0877db427 --- /dev/null +++ b/mcp-server/src/loader.ts @@ -0,0 +1,188 @@ +import { lstat } from "node:fs/promises"; +import { readFile } from "node:fs/promises"; +import { existsSync } from "node:fs"; +import { readdir } from "node:fs/promises"; +import { join } from "node:path"; +import matter from "gray-matter"; +import type { AgentProfile, AgentCategory, AgentStore } from "./types.js"; +import { AGENT_DIRS } from "./types.js"; +import { resolveRepoRoot } from "./utils.js"; + +export function slugify(name: string): string { + return name + .toLowerCase() + .replace(/[^a-z0-9]+/g, "-") + .replace(/^-|-$/g, ""); +} + +const MAX_DEPTH = process.env.MAX_SCAN_DEPTH ? parseInt(process.env.MAX_SCAN_DEPTH, 10) : 10; + +async function scanDirectory( + dirPath: string, + category: AgentCategory +): Promise<{ profiles: AgentProfile[]; issues: string[] }> { + const profiles: AgentProfile[] = []; + const issues: string[] = []; + + async function walk(currentPath: string, depth: number) { + if (depth > MAX_DEPTH) { + issues.push( + `Max directory depth (${MAX_DEPTH}) exceeded at ${currentPath}, skipping` + ); + return; + } + if (!existsSync(currentPath)) return; + + const entries = await readdir(currentPath, { withFileTypes: true }); + for (const entry of entries) { + const fullPath = join(currentPath, entry.name); + if (entry.isDirectory()) { + try { + const stat = await lstat(fullPath); + if (stat.isSymbolicLink()) { + issues.push( + `Skipping symbolic link directory: ${fullPath}` + ); + continue; + } + } catch { + continue; + } + await walk(fullPath, depth + 1); + } else if (entry.isFile() && entry.name.endsWith(".md")) { + let content: string; + try { + content = await readFile(fullPath, "utf-8"); + } catch (err) { + issues.push( + `Failed to read ${fullPath}: ${ + err instanceof Error ? err.message : String(err) + }` + ); + continue; + } + + try { + const { data, content: body } = matter(content); + const name = data.name as string | undefined; + if (!name) continue; + + const profile: AgentProfile = { + name, + slug: slugify(name), + description: (data.description as string) || "", + category, + emoji: (data.emoji as string) || "", + color: (data.color as string) || "", + vibe: (data.vibe as string) || "", + body: body.trim(), + filePath: fullPath, + }; + profiles.push(profile); + } catch (err) { + issues.push( + `Failed to parse frontmatter in ${fullPath}: ${ + err instanceof Error ? err.message : String(err) + }` + ); + continue; + } + } + } + } + + await walk(dirPath, 0); + return { profiles, issues }; +} + +function throwIfLoadIssues(issues: string[]) { + if (issues.length === 0) return; + + const preview = issues + .slice(0, 10) + .map((issue) => `- ${issue}`) + .join("\n"); + const remaining = + issues.length > 10 ? `\n- ... ${issues.length - 10} more issue(s)` : ""; + + throw new Error( + `Failed to load agent definitions:\n${preview}${remaining}` + ); +} + +export async function loadAgents(): Promise { + const repoRoot = resolveRepoRoot(); + const allProfiles: AgentProfile[] = []; + const issues: string[] = []; + + for (const dir of AGENT_DIRS) { + const dirPath = join(repoRoot, dir); + if (!existsSync(dirPath)) continue; + + const result = await scanDirectory(dirPath, dir); + allProfiles.push(...result.profiles); + issues.push(...result.issues); + } + + throwIfLoadIssues(issues); + + const bySlug = new Map(); + const byCategory = new Map(); + const collisionIssues: string[] = []; + + for (const profile of allProfiles) { + const existing = bySlug.get(profile.slug); + if (existing) { + collisionIssues.push( + `Duplicate slug "${profile.slug}": "${existing.name}" (${existing.category}) and "${profile.name}" (${profile.category})` + ); + } else { + bySlug.set(profile.slug, profile); + } + + const catList = byCategory.get(profile.category) || []; + catList.push(profile); + byCategory.set(profile.category, catList); + } + + throwIfLoadIssues(collisionIssues); + + for (const profiles of byCategory.values()) { + profiles.sort((a, b) => a.name.localeCompare(b.name)); + } + + return { profiles: allProfiles, bySlug, byCategory }; +} + +export function findAgent( + store: AgentStore, + nameOrSlug: string +): AgentProfile | undefined { + const slug = slugify(nameOrSlug); + if (store.bySlug.has(slug)) { + return store.bySlug.get(slug); + } + for (const profile of store.profiles) { + if (profile.name.toLowerCase() === nameOrSlug.toLowerCase().trim()) { + return profile; + } + } + return undefined; +} + +export function searchAgents( + store: AgentStore, + query: string, + category?: AgentCategory +): AgentProfile[] { + const q = query.toLowerCase(); + let candidates = category + ? store.byCategory.get(category) || [] + : store.profiles; + + return candidates.filter( + (p) => + p.name.toLowerCase().includes(q) || + p.description.toLowerCase().includes(q) + ); +} diff --git a/mcp-server/src/nexus.ts b/mcp-server/src/nexus.ts new file mode 100644 index 000000000..b144a1dd3 --- /dev/null +++ b/mcp-server/src/nexus.ts @@ -0,0 +1,491 @@ +import type { + AgentCategory, + AgentProfile, + AgentStore, + NexusPhase, + NexusPhaseAgent, + OrchestrationMode, + OrchestrationPlan, +} from "./types.js"; + +export interface PhaseDefinition { + phase_id: number; + phase_name: string; + objective: string; + gateKeepers: string[]; +} + +export interface NexusConfig { + phaseDefinitions: PhaseDefinition[]; + phaseAgentMap: Record; + taskKeywordAgents: Record; +} + +const DEFAULT_PHASE_DEFINITIONS: PhaseDefinition[] = [ + { + phase_id: 0, + phase_name: "Discovery & Intelligence", + objective: "Validate the opportunity before committing resources.", + gateKeepers: ["Executive Summary Generator"], + }, + { + phase_id: 1, + phase_name: "Strategy & Architecture", + objective: "Define what to build, how it's structured, and what success looks like.", + gateKeepers: ["Studio Producer", "Reality Checker"], + }, + { + phase_id: 2, + phase_name: "Foundation & Scaffolding", + objective: "Build technical and operational foundation.", + gateKeepers: ["DevOps Automator", "Evidence Collector"], + }, + { + phase_id: 3, + phase_name: "Build & Iterate", + objective: "Implement features through Dev↔QA loops.", + gateKeepers: ["Agents Orchestrator"], + }, + { + phase_id: 4, + phase_name: "Quality & Hardening", + objective: "The final quality gauntlet before launch.", + gateKeepers: ["Reality Checker"], + }, + { + phase_id: 5, + phase_name: "Launch & Growth", + objective: "Coordinate go-to-market execution across all channels.", + gateKeepers: ["Studio Producer", "Analytics Reporter"], + }, + { + phase_id: 6, + phase_name: "Operate & Evolve", + objective: "Sustained operations with continuous improvement.", + gateKeepers: ["Studio Producer"], + }, +]; + +const DEFAULT_PHASE_AGENT_MAP: Record = { + 0: [ + "trend-researcher", + "feedback-synthesizer", + "ux-researcher", + "analytics-reporter", + "legal-compliance-checker", + "tool-evaluator", + ], + 1: [ + "studio-producer", + "senior-project-manager", + "sprint-prioritizer", + "ux-architect", + "brand-guardian", + "backend-architect", + "ai-engineer", + "finance-tracker", + ], + 2: [ + "devops-automator", + "frontend-developer", + "backend-architect", + "ux-architect", + "infrastructure-maintainer", + "studio-operations", + ], + 3: [ + "frontend-developer", + "backend-architect", + "ai-engineer", + "mobile-app-builder", + "rapid-prototyper", + "evidence-collector", + "api-tester", + "performance-benchmarker", + "senior-developer", + ], + 4: [ + "reality-checker", + "evidence-collector", + "performance-benchmarker", + "api-tester", + "test-results-analyzer", + "legal-compliance-checker", + "infrastructure-maintainer", + "workflow-optimizer", + ], + 5: [ + "growth-hacker", + "content-creator", + "social-media-strategist", + "twitter-engager", + "tiktok-strategist", + "instagram-curator", + "reddit-community-builder", + "app-store-optimizer", + "devops-automator", + "infrastructure-maintainer", + "support-responder", + "analytics-reporter", + ], + 6: [ + "infrastructure-maintainer", + "support-responder", + "analytics-reporter", + "feedback-synthesizer", + "finance-tracker", + "legal-compliance-checker", + "trend-researcher", + "sprint-prioritizer", + "experiment-tracker", + "workflow-optimizer", + "executive-summary-generator", + ], +}; + +const DEFAULT_TASK_KEYWORD_AGENTS: Record = { + mobile: ["mobile-app-builder", "app-store-optimizer"], + api: ["backend-architect", "api-tester"], + frontend: ["frontend-developer", "ui-designer"], + ui: ["ui-designer", "ux-architect", "frontend-developer"], + ux: ["ux-researcher", "ux-architect", "ui-designer"], + devops: ["devops-automator", "infrastructure-maintainer"], + deploy: ["devops-automator", "infrastructure-maintainer"], + ci: ["devops-automator"], + cd: ["devops-automator"], + database: ["backend-architect", "database-optimizer"], + security: ["security-engineer", "legal-compliance-checker"], + ai: ["ai-engineer"], + ml: ["ai-engineer"], + marketing: [ + "growth-hacker", + "content-creator", + "social-media-strategist", + ], + seo: ["seo-specialist", "app-store-optimizer"], + content: ["content-creator", "visual-storyteller"], + brand: ["brand-guardian", "ui-designer"], + game: [ + "game-designer", + "level-designer", + "technical-artist", + ], + spatial: ["xr-interface-architect", "xr-immersive-developer"], + "3d": ["technical-artist", "xr-immersive-developer"], + china: ["china-market-localization-strategist"], + wechat: ["wechat-mini-program-developer"], + sales: [ + "sales-engineer", + "sales-deal-strategist", + "sales-pipeline-analyst", + ], + finance: ["financial-analyst", "finance-tracker", "fpa-analyst"], + legal: ["legal-compliance-checker", "legal-document-review"], + blockchain: ["blockchain-security-auditor", "solidity-smart-contract-engineer"], +}; + +const MODE_PHASE_BUDGETS: Record = { + micro: [1, 1, 1, 2, 1, 1, 1], + sprint: [2, 3, 3, 4, 4, 4, 4], + full: [6, 8, 6, 9, 8, 12, 11], +}; + +const CATEGORY_PHASE_PREFERENCES: Record = { + academic: [0], + design: [1, 3], + engineering: [2, 3], + finance: [1, 6], + "game-development": [3], + marketing: [5], + "paid-media": [5], + product: [0, 1], + "project-management": [1, 6], + sales: [5], + "spatial-computing": [3], + specialized: [3, 6], + support: [4, 6], + testing: [4], +}; + +const SLUG_PHASE_OVERRIDES: Record = { + "agents-orchestrator": [3, 4], + "ai-engineer": [1, 3], + "analytics-reporter": [0, 5, 6], + "api-tester": [3, 4], + "app-store-optimizer": [5], + "backend-architect": [1, 2, 3], + "brand-guardian": [1], + "content-creator": [5], + "devops-automator": [2, 5], + "evidence-collector": [2, 3, 4], + "executive-summary-generator": [0, 5, 6], + "experiment-tracker": [6], + "feedback-synthesizer": [0, 6], + "finance-tracker": [1, 6], + "frontend-developer": [2, 3], + "growth-hacker": [5], + "infrastructure-maintainer": [2, 4, 5, 6], + "instagram-curator": [5], + "legal-compliance-checker": [0, 4, 6], + "mobile-app-builder": [3], + "performance-benchmarker": [3, 4], + "rapid-prototyper": [3], + "reality-checker": [1, 4], + "reddit-community-builder": [5], + "senior-project-manager": [1], + "social-media-strategist": [5], + "sprint-prioritizer": [1, 6], + "studio-operations": [2], + "studio-producer": [1, 5, 6], + "support-responder": [5, 6], + "test-results-analyzer": [4], + "tiktok-strategist": [5], + "tool-evaluator": [0, 6], + "trend-researcher": [0, 6], + "twitter-engager": [5], + "ux-architect": [1, 2], + "ux-researcher": [0], + "workflow-optimizer": [4, 6], +}; + +let activeConfig: NexusConfig = { + phaseDefinitions: DEFAULT_PHASE_DEFINITIONS, + phaseAgentMap: DEFAULT_PHASE_AGENT_MAP, + taskKeywordAgents: DEFAULT_TASK_KEYWORD_AGENTS, +}; + +function uniqueStrings(values: string[]): string[] { + return [...new Set(values)]; +} + +function scorePhaseCandidate( + slug: string, + baseIndex: Map, + relevantSet: Set +): number { + let score = 0; + if (relevantSet.has(slug)) score += 2; + if (baseIndex.has(slug)) score += 1; + return score; +} + +function getModeBudget(mode: OrchestrationMode, phaseId: number, candidateCount: number): number { + if (mode === "full") { + return candidateCount; + } + return MODE_PHASE_BUDGETS[mode][phaseId] ?? candidateCount; +} + +function getRelevantAgentSlugs(task: string, constraints: string): Set { + const relevantSlugs = new Set(); + const haystacks = [task.toLowerCase(), constraints.toLowerCase()].filter(Boolean); + + for (const haystack of haystacks) { + for (const [keyword, slugs] of Object.entries(activeConfig.taskKeywordAgents)) { + if (!new RegExp(`\\b${keyword}\\b`, "i").test(haystack)) continue; + for (const slug of slugs) { + relevantSlugs.add(slug); + } + } + } + + return relevantSlugs; +} + +function getPreferredPhasesForAgent(profile: AgentProfile): number[] { + const override = SLUG_PHASE_OVERRIDES[profile.slug]; + if (override) { + return override; + } + + const mappedPhases = Object.entries(activeConfig.phaseAgentMap) + .filter(([, slugs]) => slugs.includes(profile.slug)) + .map(([phaseId]) => Number(phaseId)); + + if (mappedPhases.length > 0) { + return mappedPhases; + } + + return CATEGORY_PHASE_PREFERENCES[profile.category] || [3]; +} + +function buildRelevantPhaseMap( + store: AgentStore, + relevantSlugs: Set +): Map> { + const relevantByPhase = new Map>(); + + for (const slug of relevantSlugs) { + const profile = store.bySlug.get(slug); + if (!profile) continue; + + for (const phaseId of getPreferredPhasesForAgent(profile)) { + const slugs = relevantByPhase.get(phaseId) || new Set(); + slugs.add(slug); + relevantByPhase.set(phaseId, slugs); + } + } + + return relevantByPhase; +} + +function compareAgentNames( + store: AgentStore, + left: string, + right: string +): number { + const leftName = store.bySlug.get(left)?.name || left; + const rightName = store.bySlug.get(right)?.name || right; + return leftName.localeCompare(rightName); +} + +function buildPhaseCandidates( + store: AgentStore, + phaseId: number, + relevantByPhase: Map> +): string[] { + const baseSlugs = activeConfig.phaseAgentMap[phaseId] || []; + const relevantSet = relevantByPhase.get(phaseId) || new Set(); + const baseIndex = new Map(baseSlugs.map((slug, index) => [slug, index])); + const candidateSlugs = uniqueStrings([...baseSlugs, ...relevantSet]); + + return candidateSlugs + .filter((slug) => store.bySlug.has(slug)) + .sort((left, right) => { + const scoreDiff = + scorePhaseCandidate(right, baseIndex, relevantSet) - + scorePhaseCandidate(left, baseIndex, relevantSet); + if (scoreDiff !== 0) { + return scoreDiff; + } + + const leftIndex = baseIndex.get(left) ?? Number.MAX_SAFE_INTEGER; + const rightIndex = baseIndex.get(right) ?? Number.MAX_SAFE_INTEGER; + if (leftIndex !== rightIndex) { + return leftIndex - rightIndex; + } + + return compareAgentNames(store, left, right); + }); +} + +function buildPhaseAgents( + store: AgentStore, + phaseId: number, + mode: OrchestrationMode, + relevantByPhase: Map> +): NexusPhaseAgent[] { + const candidateSlugs = buildPhaseCandidates(store, phaseId, relevantByPhase); + const budget = getModeBudget(mode, phaseId, candidateSlugs.length); + const selectedSlugs = candidateSlugs.slice(0, budget); + + return selectedSlugs + .map((slug) => store.bySlug.get(slug)) + .filter((profile): profile is AgentProfile => Boolean(profile)) + .map((profile) => ({ + name: profile.name, + slug: profile.slug, + role: `Phase ${phaseId} contributor`, + primaryOutput: profile.description, + })); +} + +function buildParallelTracks(agents: NexusPhaseAgent[]): string[][] { + if (agents.length === 0) { + return []; + } + + if (agents.length === 1) { + return [[agents[0].name]]; + } + + const midpoint = Math.ceil(agents.length / 2); + const tracks = [agents.slice(0, midpoint).map((agent) => agent.name)]; + const remainder = agents.slice(midpoint).map((agent) => agent.name); + + if (remainder.length > 0) { + tracks.push(remainder); + } + + return tracks; +} + +export function getNexusConfig(): NexusConfig { + return activeConfig; +} + +export function setNexusConfig(config: Partial): void { + activeConfig = { + phaseDefinitions: config.phaseDefinitions ?? activeConfig.phaseDefinitions, + phaseAgentMap: config.phaseAgentMap ?? activeConfig.phaseAgentMap, + taskKeywordAgents: config.taskKeywordAgents ?? activeConfig.taskKeywordAgents, + }; +} + +export function generateOrchestrationPlan( + store: AgentStore, + task: string, + mode: OrchestrationMode, + constraints: string +): OrchestrationPlan { + const relevantSlugs = getRelevantAgentSlugs(task, constraints); + const relevantByPhase = buildRelevantPhaseMap(store, relevantSlugs); + const phases: NexusPhase[] = []; + + for (const def of activeConfig.phaseDefinitions) { + const agents = buildPhaseAgents(store, def.phase_id, mode, relevantByPhase); + const gateKeepers = [...def.gateKeepers]; + const gateKeeperEvidence = + gateKeepers.length === 0 + ? "no gate-keeper assigned" + : gateKeepers.length > 1 + ? `${gateKeepers.join(" + ")} sign-off` + : `${gateKeepers[0]} sign-off`; + + const isLastPhase = + def.phase_id === + activeConfig.phaseDefinitions[activeConfig.phaseDefinitions.length - 1] + .phase_id; + + phases.push({ + phase_id: def.phase_id, + phase_name: def.phase_name, + objective: def.objective, + agents, + parallel_tracks: buildParallelTracks(agents), + quality_gate: { + gate_keepers: gateKeepers, + criteria: [ + { + criterion: "Phase deliverables complete", + threshold: "100%", + evidence: `${def.phase_name} deliverables as defined in the NEXUS plan`, + }, + { + criterion: "Quality standards met", + threshold: "All criteria satisfied", + evidence: gateKeeperEvidence, + }, + ], + }, + handoff: isLastPhase + ? `Completion of ${def.phase_name}. All phases complete. Consolidate final deliverables, document lessons learned, and close out the NEXUS plan.` + : `Handoff from ${def.phase_name} to Phase ${ + def.phase_id + 1 + }. Carry forward all deliverables, key decisions, constraints, and open issues.`, + }); + } + + const uniqueAgents = new Set( + phases.flatMap((phase) => phase.agents.map((agent) => agent.slug)) + ); + + return { + task, + mode, + phases, + total_agents: uniqueAgents.size, + estimated_phases: activeConfig.phaseDefinitions.length, + constraints, + }; +} diff --git a/mcp-server/src/prompts/agent-activate.ts b/mcp-server/src/prompts/agent-activate.ts new file mode 100644 index 000000000..b4fef2bf2 --- /dev/null +++ b/mcp-server/src/prompts/agent-activate.ts @@ -0,0 +1,25 @@ +import type { AgentStore } from "../types.js"; +import { findAgent } from "../loader.js"; +import { agentActivationPrompt } from "./nexus-pipeline.js"; + +export function getAgentActivationPrompt( + store: AgentStore, + options: { + agent_name: string; + task: string; + } +): string { + const { agent_name, task } = options; + const profile = findAgent(store, agent_name); + + let profileContent: string | undefined; + if (profile) { + profileContent = `# ${profile.emoji ? profile.emoji + " " : ""}${profile.name}\n*${profile.description}*\n\n${profile.body}`; + } + + return agentActivationPrompt({ + agent_name, + task, + agentProfile: profileContent, + }); +} \ No newline at end of file diff --git a/mcp-server/src/prompts/nexus-pipeline.ts b/mcp-server/src/prompts/nexus-pipeline.ts new file mode 100644 index 000000000..f6312c342 --- /dev/null +++ b/mcp-server/src/prompts/nexus-pipeline.ts @@ -0,0 +1,125 @@ +export function nexusPipelinePrompt(options: { + task: string; + mode: "micro" | "sprint" | "full"; +}): string { + const { task, mode } = options; + + const modeDescriptions: Record = { + micro: + "NEXUS-Micro: 5-10 core agents, 1-5 days — for bug fixes, content campaigns, or single deliverables", + sprint: + "NEXUS-Sprint: 15-25 core agents, 2-6 weeks — for feature development or MVP builds", + full: + "NEXUS-Full: complete curated NEXUS core roster, 12-24 weeks — for enterprise product launches", + }; + + const lines: string[] = [ + "# NEXUS Pipeline Activation", + "", + `## Task: ${task}`, + `## Mode: ${mode}`, + `> ${modeDescriptions[mode] || modeDescriptions.sprint}`, + "", + "---", + "", + "## NEXUS 7-Phase Execution Guide", + "", + "### Phase 0 — Discovery & Intelligence", + "Activate Trend Researcher, Feedback Synthesizer, UX Researcher, Analytics Reporter, Legal Compliance Checker, and Tool Evaluator.", + "Goal: Validate the market opportunity, understand user needs, and assess regulatory and technical landscapes before committing resources.", + "Gate Keeper: Executive Summary Generator", + "Output: GO / NO-GO / PIVOT decision with supporting evidence.", + "", + "### Phase 1 — Strategy & Architecture", + "Activate Studio Producer, Senior Project Manager, Sprint Prioritizer, UX Architect, Brand Guardian, Backend Architect, AI Engineer (if applicable), and Finance Tracker.", + "Goal: Define what to build, how it's structured, and what success looks like before writing code.", + "Gate Keeper: Studio Producer + Reality Checker (dual sign-off)", + "Output: Approved Architecture Package with prioritized sprint plan.", + "", + "### Phase 2 — Foundation & Scaffolding", + "Activate DevOps Automator, Frontend Developer, Backend Architect, UX Architect, Infrastructure Maintainer, and Studio Operations.", + "Goal: Build the technical and operational foundation — CI/CD, scaffolding, database schema, design system.", + "Gate Keeper: DevOps Automator + Evidence Collector", + "Output: Working skeleton application with full DevOps pipeline.", + "", + "### Phase 3 — Build & Iterate", + "Run the Dev↔QA Loop: Developer implements → Evidence Collector tests → Decision (PASS/FAIL). Maximum 3 retries per task before escalation.", + "Primary developers: Frontend Developer, Backend Architect, AI Engineer, Mobile App Builder, Rapid Prototyper.", + "QA agents: Evidence Collector, API Tester, Performance Benchmarker.", + "Gate Keeper: Agents Orchestrator", + "Output: Feature-complete application with all tasks passing QA.", + "", + "### Phase 4 — Quality & Hardening", + "The final quality gauntlet. Reality Checker defaults to NEEDS WORK — prove production readiness.", + "Activate Reality Checker, Evidence Collector, Performance Benchmarker, API Tester, Test Results Analyzer, Legal Compliance Checker, Infrastructure Maintainer, Workflow Optimizer.", + "Gate Keeper: Reality Checker (sole authority)", + "Output: READY verdict or specific fix list for Phase 3 rework.", + "", + "### Phase 5 — Launch & Growth", + "Coordinate go-to-market across all channels.", + "Activate Growth Hacker, Content Creator, Social Media Strategist, Twitter Engager, TikTok Strategist, Instagram Curator, Reddit Community Builder, App Store Optimizer, DevOps Automator.", + "Gate Keeper: Studio Producer + Analytics Reporter", + "Output: Stable launched product with active growth channels.", + "", + "### Phase 6 — Operate & Evolve", + "Sustained operations with continuous improvement.", + "Activate Infrastructure Maintainer, Support Responder, Analytics Reporter, Feedback Synthesizer, Finance Tracker, Legal Compliance Checker, Trend Researcher, Sprint Prioritizer, Experiment Tracker, Workflow Optimizer.", + "Output: Continuous improvement loop with monthly executive reporting.", + "", + "---", + "", + "## Handoff Protocol", + "Every agent-to-agent handoff must include: from/to agent, phase, task reference, priority, current state, deliverable request with acceptance criteria, quality expectations, and evidence requirements.", + "", + "## Quality Gate Rules", + "- No phase advances without passing its gate", + "- Every handoff carries full context — no agent starts cold", + "- Independent workstreams run concurrently", + "- All quality assessments require proof", + "- Maximum 3 retries per task before escalation", + "", + "---", + "", + "## Instructions for the Host AI", + `You are the Agents Orchestrator running the NEXUS pipeline in **${mode.toUpperCase()}** mode for: "${task}".`, + "", + "1. Start at Phase 0 and progress through all 7 phases.", + "2. For each phase, invoke the specified agents using `invoke_agent`.", + "3. Manage handoffs between phases using the handoff protocol.", + "4. Enforce quality gates before advancing — do not skip gates.", + "5. Run Dev↔QA loops for all implementation tasks in Phase 3.", + "6. Escalate tasks that fail 3 QA attempts.", + "7. Report status at each phase boundary.", + ]; + + return lines.join("\n"); +} + +export function agentActivationPrompt(options: { + agent_name: string; + task: string; + agentProfile?: string; +}): string { + const { agent_name, task, agentProfile } = options; + + const lines: string[] = [ + agentProfile + ? agentProfile + : `Activate the **${agent_name}** agent. Use \`invoke_agent\` to load the full personality if needed.`, + "", + "---", + "", + "## Task", + task, + "", + "## Instructions", + `You are now activated as **${agent_name}**.`, + "- Follow your defined personality, core mission, and critical rules.", + "- Use your standard workflow process and deliverable templates.", + "- Communicate in your defined communication style.", + "- Provide evidence-based outputs with concrete deliverables.", + "- Quality check your work against your defined success metrics.", + ]; + + return lines.join("\n"); +} diff --git a/mcp-server/src/resources/agent-profile.ts b/mcp-server/src/resources/agent-profile.ts new file mode 100644 index 000000000..326fd1767 --- /dev/null +++ b/mcp-server/src/resources/agent-profile.ts @@ -0,0 +1,57 @@ +import { readFile } from "node:fs/promises"; +import type { AgentStore, AgentCategory } from "../types.js"; +import { AGENT_DIRS } from "../types.js"; +import { findAgent } from "../loader.js"; + +export async function getAgentProfile( + store: AgentStore, + name: string +): Promise<{ content: string; mimeType: string } | null> { + const profile = findAgent(store, name); + if (!profile) { + return null; + } + + try { + const content = await readFile(profile.filePath, "utf-8"); + return { content, mimeType: "text/markdown" }; + } catch { + return { + content: `# ${profile.name}\n\n${profile.body}`, + mimeType: "text/markdown", + }; + } +} + +export function getAgentCatalog( + store: AgentStore, + category: string +): { content: string; mimeType: string } | null { + if (!AGENT_DIRS.includes(category as AgentCategory)) { + return null; + } + + const profiles = store.byCategory.get(category as AgentCategory) || []; + + if (profiles.length === 0) { + return { + content: `# ${category} Catalog\n\nNo agents found in this category.`, + mimeType: "text/markdown", + }; + } + + const lines: string[] = [ + `# ${category} Agent Catalog`, + "", + `Total agents: ${profiles.length}`, + "", + ]; + + for (const p of profiles) { + lines.push( + `- ${p.emoji ? p.emoji + " " : ""}**${p.name}** (\`${p.slug}\`) — ${p.description}` + ); + } + + return { content: lines.join("\n"), mimeType: "text/markdown" }; +} diff --git a/mcp-server/src/tools/invoke-agent.ts b/mcp-server/src/tools/invoke-agent.ts new file mode 100644 index 000000000..db257d1e5 --- /dev/null +++ b/mcp-server/src/tools/invoke-agent.ts @@ -0,0 +1,89 @@ +import type { AgentStore } from "../types.js"; +import { findAgent, slugify } from "../loader.js"; + +export function invokeAgent( + store: AgentStore, + options: { + name: string; + task?: string; + context?: string; + } +): string { + const { name, task, context } = options; + const profile = findAgent(store, name); + + if (!profile) { + const q = name.toLowerCase(); + const slugQ = slugify(name); + const suggestions = store.profiles + .filter( + (p) => + p.name.toLowerCase().includes(q) || + p.slug.includes(slugQ) + ) + .slice(0, 10) + .map((p) => `- **${p.name}** (\`${p.slug}\`) — ${p.description}`); + + const lines: string[] = [ + `Agent "${name}" not found.`, + "", + suggestions.length > 0 + ? "Did you mean one of these?" + : `Use \`list_agents\` to browse all ${store.profiles.length} available agents.`, + ]; + + if (suggestions.length > 0) { + lines.push(...suggestions); + lines.push( + "", + `Tip: Use \`list_agents\` to see all ${store.profiles.length} available agents.` + ); + } + + return lines.join("\n"); + } + + const lines: string[] = []; + + lines.push(`# ${profile.emoji ? profile.emoji + " " : ""}${profile.name}`); + lines.push(`*${profile.description}*`); + lines.push(""); + + if (context) { + lines.push("## Project Context"); + lines.push(context); + lines.push(""); + } + + lines.push("## Agent Personality & Instructions"); + lines.push(profile.body); + + if (task) { + lines.push(""); + lines.push("---"); + lines.push(""); + lines.push("## Task Instruction"); + lines.push(task); + lines.push(""); + lines.push( + "Please complete the above task following the agent personality and instructions. Provide concrete deliverables with code, analysis, or documentation as appropriate." + ); + } + + lines.push(""); + lines.push("---"); + lines.push(""); + lines.push("## Quality Standards"); + lines.push( + "- Follow the agent's workflow process and deliverable templates as defined above" + ); + lines.push("- Provide evidence-based outputs (code, screenshots, data, analysis)"); + lines.push( + "- Ensure all outputs align with the agent's core mission and critical rules" + ); + lines.push( + "- Communicate in the agent's defined communication style throughout" + ); + + return lines.join("\n"); +} \ No newline at end of file diff --git a/mcp-server/src/tools/list-agents.ts b/mcp-server/src/tools/list-agents.ts new file mode 100644 index 000000000..45fe14cbf --- /dev/null +++ b/mcp-server/src/tools/list-agents.ts @@ -0,0 +1,98 @@ +import type { AgentStore, AgentCategory } from "../types.js"; +import { AGENT_DIRS } from "../types.js"; + +export function listAgents( + store: AgentStore, + options: { + category?: AgentCategory; + query?: string; + format?: "summary" | "full"; + } +): string { + const { category, query, format = "summary" } = options; + + let profiles = store.profiles; + + if (category) { + profiles = store.byCategory.get(category) || []; + } + + if (query) { + const q = query.toLowerCase(); + profiles = profiles.filter( + (p) => + p.name.toLowerCase().includes(q) || + p.description.toLowerCase().includes(q) + ); + } + + if (profiles.length === 0) { + if (category && query) { + return `No agents found in category "${category}" matching "${query}".`; + } + if (category) { + return `No agents found in category "${category}". Available categories: ${AGENT_DIRS.join(", ")}`; + } + if (query) { + return `No agents found matching "${query}". Try a different keyword or use list_agents without query to browse all ${store.profiles.length} agents.`; + } + return "No agents loaded."; + } + + const lines: string[] = []; + + if (format === "full") { + lines.push(`# Agency Agents Directory (${profiles.length} agents)\n`); + for (const p of profiles) { + lines.push(`## ${p.emoji ? p.emoji + " " : ""}${p.name}`); + lines.push(`- **Slug**: \`${p.slug}\``); + lines.push(`- **Category**: ${p.category}`); + lines.push(`- **Description**: ${p.description}`); + if (p.vibe) { + lines.push(`- **Vibe**: ${p.vibe}`); + } + lines.push( + `- **First 200 chars**: ${p.body.substring(0, 200).replace(/\n/g, " ")}...` + ); + lines.push(""); + } + } else { + if (!category) { + const byCategory = new Map(); + for (const p of profiles) { + const list = byCategory.get(p.category) || []; + list.push(p); + byCategory.set(p.category, list); + } + + lines.push( + `# Agency Agents Directory — ${profiles.length} agents across ${byCategory.size} categories\n` + ); + + for (const [cat, agents] of byCategory) { + lines.push(`## ${cat} (${agents.length} agents)`); + for (const a of agents) { + lines.push( + `- ${a.emoji ? a.emoji + " " : ""}**${a.name}** (\`${a.slug}\`) — ${a.description}` + ); + } + lines.push(""); + } + } else { + lines.push( + `# Agency Agents — ${category} (${profiles.length} agents)\n` + ); + for (const p of profiles) { + lines.push( + `- ${p.emoji ? p.emoji + " " : ""}**${p.name}** (\`${p.slug}\`) — ${p.description}` + ); + } + } + } + + lines.push( + `\n---\nUse \`invoke_agent\` with the agent slug to load its full personality. Example: \`invoke_agent name="${profiles[0]?.slug || "devops-automator"}"\`` + ); + + return lines.join("\n"); +} \ No newline at end of file diff --git a/mcp-server/src/tools/orchestrate.ts b/mcp-server/src/tools/orchestrate.ts new file mode 100644 index 000000000..6695a5450 --- /dev/null +++ b/mcp-server/src/tools/orchestrate.ts @@ -0,0 +1,94 @@ +import type { AgentStore } from "../types.js"; +import { generateOrchestrationPlan } from "../nexus.js"; + +export function orchestrate( + store: AgentStore, + options: { + task: string; + mode: "micro" | "sprint" | "full"; + constraints?: string; + } +): string { + const { task, mode, constraints = "" } = options; + + const plan = generateOrchestrationPlan(store, task, mode, constraints); + + const lines: string[] = []; + + lines.push(`# NEXUS Orchestration Plan`); + lines.push(""); + lines.push(`**Task**: ${task}`); + lines.push(`**Mode**: ${mode.toUpperCase()}`); + lines.push(`**Total Agents**: ${plan.total_agents}`); + lines.push(`**Phases**: ${plan.estimated_phases}`); + if (constraints) { + lines.push(`**Constraints**: ${constraints}`); + } + lines.push(""); + lines.push("---"); + + for (const phase of plan.phases) { + lines.push(""); + lines.push( + `## Phase ${phase.phase_id}: ${phase.phase_name}` + ); + lines.push(`> ${phase.objective}`); + lines.push(""); + + if (phase.agents.length > 0) { + lines.push("### Active Agents"); + for (const agent of phase.agents) { + lines.push(`- **${agent.name}** (\`${agent.slug}\`) — ${agent.primaryOutput}`); + } + } else { + lines.push("### Active Agents"); + lines.push("- No agents activated for this phase in current mode."); + } + + if (phase.parallel_tracks.length > 0) { + lines.push(""); + lines.push("### Parallel Workstreams"); + let trackLabel = "A"; + for (const track of phase.parallel_tracks) { + lines.push(`**Track ${trackLabel}**: ${track.join(", ")}`); + trackLabel = String.fromCharCode(trackLabel.charCodeAt(0) + 1); + } + } + + lines.push(""); + lines.push( + `### Quality Gate — Gate Keepers: **${phase.quality_gate.gate_keepers.join( + "**, **" + )}**` + ); + for (const criterion of phase.quality_gate.criteria) { + lines.push(`- ${criterion.criterion}: ${criterion.threshold}`); + } + + lines.push(""); + lines.push(`### Handoff`); + lines.push(phase.handoff); + lines.push(""); + lines.push("---"); + } + + lines.push(""); + lines.push("## Activation Instructions"); + lines.push("1. Start at Phase 0 and progress sequentially through all phases."); + lines.push( + `2. For each agent listed, use \`invoke_agent\` with the agent slug and a description of their task for the current phase.` + ); + lines.push( + "3. Do NOT advance to the next phase until the current phase's quality gate criteria are met." + ); + lines.push("4. Use parallel workstreams within a phase to compress timelines."); + lines.push( + "5. If a task fails QA more than 3 times, escalate to the phase's Gate Keeper for resolution." + ); + lines.push(""); + lines.push( + `> Generated by Agency MCP Server NEXUS Engine | Mode: ${mode.toUpperCase()} | Agents: ${plan.total_agents}` + ); + + return lines.join("\n"); +} diff --git a/mcp-server/src/types.ts b/mcp-server/src/types.ts new file mode 100644 index 000000000..8045e6feb --- /dev/null +++ b/mcp-server/src/types.ts @@ -0,0 +1,113 @@ +import { z } from "zod"; + +export const AGENT_DIRS = [ + "academic", + "design", + "engineering", + "finance", + "game-development", + "marketing", + "paid-media", + "product", + "project-management", + "sales", + "spatial-computing", + "specialized", + "support", + "testing", +] as const; + +export type AgentCategory = (typeof AGENT_DIRS)[number]; + +export interface AgentProfile { + name: string; + slug: string; + description: string; + category: AgentCategory; + emoji: string; + color: string; + vibe: string; + body: string; + filePath: string; +} + +export interface AgentSummary { + name: string; + slug: string; + description: string; + category: AgentCategory; + emoji: string; +} + +export interface AgentInvocation { + system_prompt: string; + task_instruction: string; + suggested_globs: string[]; + quality_gates: string[]; +} + +export interface NexusPhaseAgent { + name: string; + slug: string; + role: string; + primaryOutput: string; +} + +export interface NexusPhase { + phase_id: number; + phase_name: string; + objective: string; + agents: NexusPhaseAgent[]; + parallel_tracks: string[][]; + quality_gate: { + gate_keepers: string[]; + criteria: { criterion: string; threshold: string; evidence: string }[]; + }; + handoff: string; +} + +export type OrchestrationMode = "micro" | "sprint" | "full"; + +export interface OrchestrationPlan { + task: string; + mode: OrchestrationMode; + phases: NexusPhase[]; + total_agents: number; + estimated_phases: number; + constraints: string; +} + +export interface AgentStore { + profiles: AgentProfile[]; + bySlug: Map; + byCategory: Map; +} + +export const ListAgentsParams = z.object({ + category: z + .enum(AGENT_DIRS) + .optional() + .describe("Filter by agent category"), + query: z.string().optional().describe("Keyword search in name and description"), + format: z + .enum(["summary", "full"]) + .optional() + .default("summary") + .describe("Output format: summary (default) or full"), +}); + +export const InvokeAgentParams = z.object({ + name: z.string().describe("Agent name or slug, e.g. 'devops-automator' or 'DevOps Automator'"), + task: z.string().optional().describe("Specific task description to append"), + context: z.string().optional().describe("Project context to help agent respond precisely"), +}); + +export const OrchestrateParams = z.object({ + task: z.string().describe("Task description, e.g. 'Build a SaaS MVP'"), + mode: z + .enum(["micro", "sprint", "full"]) + .optional() + .default("sprint") + .describe("Orchestration scale: micro (5-10 core agents, 1-5 days), sprint (15-25 core agents, 2-6 weeks), full (complete curated NEXUS core roster, 12-24 weeks)"), + constraints: z.string().optional().describe("Constraints, e.g. 'TypeScript only, limited budget'"), +}); diff --git a/mcp-server/src/utils.ts b/mcp-server/src/utils.ts new file mode 100644 index 000000000..e4e01f1ec --- /dev/null +++ b/mcp-server/src/utils.ts @@ -0,0 +1,24 @@ +import { existsSync } from "node:fs"; +import { join, dirname } from "node:path"; +import { fileURLToPath } from "node:url"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +export function resolveRepoRoot(): string { + const candidates = [ + join(__dirname, "..", ".."), + join(__dirname, ".."), + process.cwd(), + ]; + for (const candidate of candidates) { + if (existsSync(join(candidate, "engineering")) && existsSync(join(candidate, "strategy"))) { + return candidate; + } + } + console.error( + "resolveRepoRoot: no candidate contained both 'engineering' and 'strategy' directories. Checked:", + candidates, + ); + return process.cwd(); +} diff --git a/mcp-server/tsconfig.json b/mcp-server/tsconfig.json new file mode 100644 index 000000000..cb31f3e9f --- /dev/null +++ b/mcp-server/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "outDir": "dist", + "rootDir": "src", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} \ No newline at end of file diff --git a/scripts/common.sh b/scripts/common.sh new file mode 100644 index 000000000..37aedd48e --- /dev/null +++ b/scripts/common.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash +# +# common.sh -- Shared helper functions for Agency shell scripts. +# +# Source this file from other scripts: +# SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# source "$SCRIPT_DIR/common.sh" +# + +normalize_node_entry_path() { + local path="$1" + if command -v cygpath >/dev/null 2>&1; then + local windows_path + windows_path="$(cygpath -w "$path")" + printf '%s\n' "${windows_path//\\//}" + return + fi + printf '%s\n' "$path" +} diff --git a/scripts/convert.sh b/scripts/convert.sh index 323937de5..b981bcffd 100755 --- a/scripts/convert.sh +++ b/scripts/convert.sh @@ -14,11 +14,13 @@ # gemini-cli — Gemini CLI extension (skills/ + gemini-extension.json) # opencode — OpenCode agent files (.opencode/agents/*.md) # cursor — Cursor rule files (.cursor/rules/*.mdc) +# trae — Single project_rules.md for Trae # aider — Single CONVENTIONS.md for Aider # windsurf — Single .windsurfrules for Windsurf # openclaw — OpenClaw workspaces (integrations/openclaw//SOUL.md) # qwen — Qwen Code SubAgent files (~/.qwen/agents/*.md) # kimi — Kimi Code CLI agent files (~/.config/kimi/agents/) +# mcp-server — MCP server config files (Trae/Cursor/Claude IDE configs) # all — All tools (default) # # Output is written to integrations// relative to the repo root. @@ -61,6 +63,8 @@ REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" OUT_DIR="$REPO_ROOT/integrations" TODAY="$(date +%Y-%m-%d)" +source "$SCRIPT_DIR/common.sh" + AGENT_DIRS=( academic design engineering finance game-development marketing paid-media product project-management sales spatial-computing specialized strategy support testing @@ -408,11 +412,12 @@ ${body} HEREDOC } -# Aider and Windsurf are single-file formats — accumulate into temp files +# Aider, Windsurf and Trae are single-file formats — accumulate into temp files # then write at the end. AIDER_TMP="$(mktemp)" WINDSURF_TMP="$(mktemp)" -trap 'rm -f "$AIDER_TMP" "$WINDSURF_TMP"' EXIT +TRAE_TMP="$(mktemp)" +trap 'rm -f "$AIDER_TMP" "$WINDSURF_TMP" "$TRAE_TMP"' EXIT # Write Aider/Windsurf headers once cat > "$AIDER_TMP" <<'HEREDOC' @@ -438,6 +443,16 @@ cat > "$WINDSURF_TMP" <<'HEREDOC' HEREDOC +cat > "$TRAE_TMP" <<'HEREDOC' +# The Agency — AI Agent Rules for Trae +# +# Full roster of specialized AI agents from The Agency. +# To activate an agent, reference it by name in your Trae conversation. +# +# Generated by scripts/convert.sh — do not edit manually. + +HEREDOC + accumulate_aider() { local file="$1" local name description body @@ -478,12 +493,67 @@ ${body} HEREDOC } +accumulate_trae() { + local file="$1" + local name description body + + name="$(get_field "name" "$file")" + description="$(get_field "description" "$file")" + body="$(get_body "$file")" + + cat >> "$TRAE_TMP" < ${description} + +${body} + +HEREDOC +} + # --- Main loop --- +convert_mcp_server() { + local repo_root="$REPO_ROOT" + local outdir="$OUT_DIR/mcp-server" + mkdir -p "$outdir" + + local entry_point + entry_point="$(normalize_node_entry_path "${repo_root}/mcp-server/dist/index.js")" + + _write_mcp_config() { + local target="$1" + cat > "$target" </dev/null 2>&1 || [[ -d "${HOME}/.o detect_windsurf() { command -v windsurf >/dev/null 2>&1 || [[ -d "${HOME}/.codeium" ]]; } detect_qwen() { command -v qwen >/dev/null 2>&1 || [[ -d "${HOME}/.qwen" ]]; } detect_kimi() { command -v kimi >/dev/null 2>&1; } +detect_mcp_server() { + command -v node >/dev/null 2>&1 || return 1 + local ver + ver="$(node -v 2>/dev/null)" + ver="${ver#v}" + local major="${ver%%.*}" + [[ "$major" -ge 18 ]] +} +detect_trae() { [[ -d "${HOME}/.trae" ]] || [[ -d "${PWD}/.trae" ]]; } is_detected() { case "$1" in @@ -163,6 +175,8 @@ is_detected() { windsurf) detect_windsurf ;; qwen) detect_qwen ;; kimi) detect_kimi ;; + mcp-server) detect_mcp_server ;; + trae) detect_trae ;; *) return 1 ;; esac } @@ -181,6 +195,8 @@ tool_label() { windsurf) printf "%-14s %s" "Windsurf" "(.windsurfrules)" ;; qwen) printf "%-14s %s" "Qwen Code" "(~/.qwen/agents)" ;; kimi) printf "%-14s %s" "Kimi Code" "(~/.config/kimi/agents)" ;; + mcp-server) printf "%-14s %s" "MCP Server" "(Node.js MCP Server)" ;; + trae) printf "%-14s %s" "Trae" "(.trae/rules/project_rules.md)" ;; esac } @@ -518,6 +534,130 @@ install_kimi() { ok "Usage: kimi --agent-file ~/.config/kimi/agents//agent.yaml" } +install_mcp_server() { + local mcp_dir="$REPO_ROOT/mcp-server" + local dist="$mcp_dir/dist/index.js" + + if ! detect_mcp_server; then + err "MCP Server requires Node.js 18+." + return 1 + fi + + if [[ ! -d "$mcp_dir/node_modules" ]]; then + warn "MCP Server dependencies missing. Installing with npm ci..." + (cd "$mcp_dir" && npm ci) || { + err "Failed to install mcp-server dependencies with npm ci." + return 1 + } + ok "MCP Server dependencies installed." + fi + + if [[ ! -f "$dist" ]]; then + warn "MCP Server not built. Building now..." + (cd "$mcp_dir" && npm run build) || { + err "Failed to build mcp-server after installing dependencies." + return 1 + } + ok "MCP Server built successfully." + fi + + local cmd="node" + local args + args="$(normalize_node_entry_path "$dist")" + + # Trae IDE + local trae_mcp="$REPO_ROOT/.trae/mcp.json" + if [[ -f "$trae_mcp" ]]; then + warn "Trae: .trae/mcp.json already exists. Skipping to avoid overwrite." + dim " Add this entry manually:" + dim " {\"mcpServers\":{\"agency\":{\"command\":\"$cmd\",\"args\":[\"$args\"]}}}" + else + mkdir -p "$(dirname "$trae_mcp")" + cat > "$trae_mcp" < $trae_mcp" + fi + + # Cursor IDE + local cursor_mcp="$REPO_ROOT/.cursor/mcp.json" + if [[ -f "$cursor_mcp" ]]; then + warn "Cursor: .cursor/mcp.json already exists. Skipping to avoid overwrite." + else + mkdir -p "$(dirname "$cursor_mcp")" + cat > "$cursor_mcp" < $cursor_mcp" + fi + + # Claude Desktop + local claude_config="" + if [[ "$(uname -s)" == "Darwin" ]]; then + claude_config="${HOME}/Library/Application Support/Claude/claude_desktop_config.json" + elif [[ -n "$APPDATA" ]]; then + claude_config="$(cygpath -u "$APPDATA" 2>/dev/null || echo "$APPDATA")/Claude/claude_desktop_config.json" + else + claude_config="${HOME}/.config/Claude/claude_desktop_config.json" + fi + if [[ -n "$claude_config" ]]; then + local claude_dir + claude_dir="$(dirname "$claude_config")" + if [[ -d "$claude_dir" ]]; then + if [[ -f "$claude_config" ]]; then + warn "Claude Desktop: config already exists. Skipping to avoid overwrite." + dim " Add this to your claude_desktop_config.json mcpServers section:" + dim " \"agency\": {\"command\": \"$cmd\", \"args\": [\"$args\"]}" + else + cat > "$claude_config" < $claude_config" + fi + else + warn "Claude Desktop: config directory not found at $claude_dir" + dim " Is Claude Desktop installed? Create the directory and re-run," + dim " or add manually to your claude_desktop_config.json:" + dim " \"agency\": {\"command\": \"$cmd\", \"args\": [\"$args\"]}" + fi + fi +} + +install_trae() { + local src="$INTEGRATIONS/trae/project_rules.md" + local dest="${PWD}/.trae/rules/project_rules.md" + [[ -f "$src" ]] || { err "integrations/trae/project_rules.md missing. Run convert.sh first."; return 1; } + mkdir -p "$(dirname "$dest")" + if [[ -f "$dest" ]]; then + warn "Trae: .trae/rules/project_rules.md already exists at $dest (remove to reinstall)." + return 0 + fi + cp "$src" "$dest" + ok "Trae: installed -> $dest" + warn "Trae: project-scoped. Run from your project root to install there." +} + install_tool() { case "$1" in claude-code) install_claude_code ;; @@ -531,6 +671,8 @@ install_tool() { windsurf) install_windsurf ;; qwen) install_qwen ;; kimi) install_kimi ;; + mcp-server) install_mcp_server ;; + trae) install_trae ;; esac } @@ -556,7 +698,7 @@ main() { esac done - check_integrations + [[ "$tool" != "mcp-server" ]] && check_integrations # Validate explicit tool if [[ "$tool" != "all" ]]; then diff --git a/specialized/zk-steward.md b/specialized/zk-steward.md index 6a56bb63d..c3bc8972d 100644 --- a/specialized/zk-steward.md +++ b/specialized/zk-steward.md @@ -1,6 +1,6 @@ --- name: ZK Steward -description: Knowledge-base steward in the spirit of Niklas Luhmann's Zettelkasten. Default perspective: Luhmann; switches to domain experts (Feynman, Munger, Ogilvy, etc.) by task. Enforces atomic notes, connectivity, and validation loops. Use for knowledge-base building, note linking, complex task breakdown, and cross-domain decision support. +description: "Knowledge-base steward in the spirit of Niklas Luhmann's Zettelkasten. Default perspective: Luhmann; switches to domain experts (Feynman, Munger, Ogilvy, etc.) by task. Enforces atomic notes, connectivity, and validation loops. Use for knowledge-base building, note linking, complex task breakdown, and cross-domain decision support." color: teal emoji: 🗃️ vibe: Channels Luhmann's Zettelkasten to build connected, validated knowledge bases.