|
|
@@ -0,0 +1,717 @@
|
|
|
+---
|
|
|
+title: 'Admin MCP 工具扩展 - 添加缺失的管理后台功能'
|
|
|
+slug: 'admin-mcp-tools-extension'
|
|
|
+created: '2026-03-17'
|
|
|
+status: 'ready-for-dev'
|
|
|
+stepsCompleted: [1, 2, 3, 4]
|
|
|
+tech_stack: ['TypeScript', 'Zod', 'Hono', 'Express', 'Node.js']
|
|
|
+files_to_modify: [
|
|
|
+ 'packages/admin-mcp-server/src/index.ts',
|
|
|
+ 'packages/admin-mcp-server/src/schemas/index.ts',
|
|
|
+ 'packages/admin-mcp-server/src/tools/index.ts',
|
|
|
+ 'packages/admin-mcp-server/src/types.ts',
|
|
|
+ 'packages/admin-mcp-server/src/tools/auth-tools.ts'
|
|
|
+]
|
|
|
+code_patterns: [
|
|
|
+ 'Zod Schema 验证模式',
|
|
|
+ 'createCrudRoutes 标准 CRUD 模式',
|
|
|
+ '自定义路由命名模式 (createXxx, updateXxx, deleteXxx, getXxx, getAllXxx)',
|
|
|
+ 'API 客户端单例模式',
|
|
|
+ '工具注册配置对象模式',
|
|
|
+ 'Markdown 格式化输出模式'
|
|
|
+]
|
|
|
+test_patterns: []
|
|
|
+---
|
|
|
+
|
|
|
+# Tech-Spec: Admin MCP 工具扩展 - 添加缺失的管理后台功能
|
|
|
+
|
|
|
+**Created:** 2026-03-17
|
|
|
+
|
|
|
+## Overview
|
|
|
+
|
|
|
+### Problem Statement
|
|
|
+
|
|
|
+当前 Admin MCP Server 只覆盖了部分管理后台功能。通过 Playwright MCP 查看管理后台和现有工具对比,发现以下模块缺少 MCP 工具支持:
|
|
|
+
|
|
|
+| 模块 | 缺少工具 | 后端 API |
|
|
|
+|-----|---------|---------|
|
|
|
+| 文件管理 | list, get, delete | `createCrudRoutes` + 自定义路由 |
|
|
|
+| 银行名称管理 | list, get, create, update, delete | `createCrudRoutes` |
|
|
|
+| 渠道管理 | list, get, create, update, delete | 自定义路由 (`getAllChannels`, `createChannel` 等) |
|
|
|
+| 残疾人管理 | list, get, create, update, delete | 自定义路由 (`getAllDisabledPersons`, `createDisabledPerson` 等) |
|
|
|
+| 残疾人企业查询 | query, export | 自定义查询路由 |
|
|
|
+| 平台管理 | list, get, create, update, delete, toggleStatus | 自定义路由 (`getAllPlatforms`, `togglePlatformStatus` 等) |
|
|
|
+| 薪资管理 | list, get, create, update, delete | 自定义路由 (`getAllSalaries`, `createSalary` 等) |
|
|
|
+| 订单管理 | create, update, delete (只有 list/get) | 自定义路由 (`createOrder`, `updateOrder`, `deleteOrder` 等) |
|
|
|
+| 公司管理 | create, update, delete (只有 list/get) | 自定义路由 (`createCompany`, `updateCompany`, `deleteCompany` 等) |
|
|
|
+
|
|
|
+### Solution
|
|
|
+
|
|
|
+为上述模块创建对应的 MCP 工具,包装现有的后端 API。所有后端 API 已存在,使用自定义路由或标准 CRUD 路由。
|
|
|
+
|
|
|
+### Scope
|
|
|
+
|
|
|
+**In Scope:**
|
|
|
+- 创建新的工具文件 (`packages/admin-mcp-server/src/tools/*.ts`)
|
|
|
+- 扩展 Schema 文件 (`packages/admin-mcp-server/src/schemas/index.ts`)
|
|
|
+- 扩展类型定义 (`packages/admin-mcp-server/src/types.ts`)
|
|
|
+- 清理不需要的登录工具
|
|
|
+- 在 `index.ts` 中注册新工具
|
|
|
+- 遵循现有工具的代码模式和结构
|
|
|
+- 支持分页、搜索、过滤等常用功能
|
|
|
+
|
|
|
+**Out of Scope:**
|
|
|
+- 不修改后端 API
|
|
|
+- 不修改现有工具的行为
|
|
|
+- 不添加新的业务逻辑
|
|
|
+- 不涉及数据库迁移
|
|
|
+- 文件二进制上传功能(MCP 限制)
|
|
|
+
|
|
|
+## Context for Development
|
|
|
+
|
|
|
+### Codebase Patterns
|
|
|
+
|
|
|
+**1. 工具文件结构** (`tools/xxx-tools.ts`):
|
|
|
+```typescript
|
|
|
+// 导入依赖
|
|
|
+import { getApiClient } from '../services/api-client.js';
|
|
|
+import { ... } from '../schemas/index.js';
|
|
|
+import { CHARACTER_LIMIT, ResponseFormat } from '../constants.js';
|
|
|
+import type { ... } from '../types.js';
|
|
|
+
|
|
|
+// 格式化函数
|
|
|
+function formatXxxMarkdown(data: Xxx): string { ... }
|
|
|
+function formatXxxListMarkdown(items: Xxx[], total: number, ...): string { ... }
|
|
|
+
|
|
|
+// 工具函数
|
|
|
+export const xxxListTool = async (args: XxxListInput) => { ... };
|
|
|
+export const xxxGetTool = async (args: XxxGetInput) => { ... };
|
|
|
+export const xxxCreateTool = async (args: XxxCreateInput) => { ... };
|
|
|
+export const xxxUpdateTool = async (args: XxxUpdateInput) => { ... };
|
|
|
+export const xxxDeleteTool = async (args: XxxDeleteInput) => { ... };
|
|
|
+
|
|
|
+// 导出工具注册配置
|
|
|
+export const xxxTools = {
|
|
|
+ xxxList: { name: 'admin_list_xxx', schema: XxxListInputSchema, handler: xxxListTool },
|
|
|
+ xxxGet: { name: 'admin_get_xxx', schema: XxxGetInputSchema, handler: xxxGetTool },
|
|
|
+ xxxCreate: { name: 'admin_create_xxx', schema: XxxCreateInputSchema, handler: xxxCreateTool },
|
|
|
+ xxxUpdate: { name: 'admin_update_xxx', schema: XxxUpdateInputSchema, handler: xxxUpdateTool },
|
|
|
+ xxxDelete: { name: 'admin_delete_xxx', schema: XxxDeleteInputSchema, handler: xxxDeleteTool },
|
|
|
+};
|
|
|
+```
|
|
|
+
|
|
|
+**2. Schema 定义模式** (`schemas/index.ts`):
|
|
|
+```typescript
|
|
|
+// 使用 ListQuerySchema 作为基础
|
|
|
+export const XxxListInputSchema = ListQuerySchema;
|
|
|
+
|
|
|
+// 资源 ID 查询
|
|
|
+export const XxxGetInputSchema = ResourceIdSchema;
|
|
|
+
|
|
|
+// 创建 Schema
|
|
|
+export const XxxCreateInputSchema = z.object({
|
|
|
+ field1: z.string().min(1).describe('描述'),
|
|
|
+ field2: z.string().optional().describe('描述'),
|
|
|
+}).strict();
|
|
|
+
|
|
|
+// 更新 Schema (包含 id)
|
|
|
+export const XxxUpdateInputSchema = z.object({
|
|
|
+ id: z.number().int().positive().describe('Xxx ID to update'),
|
|
|
+ field1: z.string().optional().describe('描述'),
|
|
|
+}).strict();
|
|
|
+
|
|
|
+// 删除 Schema
|
|
|
+export const XxxDeleteInputSchema = ResourceIdSchema;
|
|
|
+```
|
|
|
+
|
|
|
+**3. API 调用模式**:
|
|
|
+- **标准 CRUD** (`createCrudRoutes`):
|
|
|
+ - List: `apiClient.get('/api/v1/resources', { page, pageSize, ... })`
|
|
|
+ - Get: `apiClient.get('/api/v1/resources/{id}')`
|
|
|
+ - Create: `apiClient.post('/api/v1/resources', data)`
|
|
|
+ - Update: `apiClient.put('/api/v1/resources/{id}', data)`
|
|
|
+ - Delete: `apiClient.delete('/api/v1/resources/{id}')`
|
|
|
+
|
|
|
+- **自定义路由**:
|
|
|
+ - List: `apiClient.get('/api/v1/channels/getAllChannels', { skip, take })`
|
|
|
+ - Get: `apiClient.get('/api/v1/channels/getChannel', { id })`
|
|
|
+ - Create: `apiClient.post('/api/v1/channels/createChannel', data)`
|
|
|
+ - Update: `apiClient.post('/api/v1/channels/updateChannel', { id, ...data })`
|
|
|
+ - Delete: `apiClient.post('/api/v1/channels/deleteChannel', { id })`
|
|
|
+
|
|
|
+**4. 工具注册模式** (`index.ts`):
|
|
|
+```typescript
|
|
|
+import { xxxTools } from './tools/xxx-tools.js';
|
|
|
+
|
|
|
+function registerAllTools(targetServer: McpServer): void {
|
|
|
+ registerToolWithAnnotations(
|
|
|
+ xxxTools.xxxList,
|
|
|
+ { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
|
+ 'List Xxx',
|
|
|
+ `描述...`,
|
|
|
+ targetServer
|
|
|
+ );
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+**5. 命名约定**:
|
|
|
+- 工具名称: `admin_list_xxx`, `admin_get_xxx`, `admin_create_xxx`, `admin_update_xxx`, `admin_delete_xxx`
|
|
|
+- Schema 名称: `XxxListInputSchema`, `XxxGetInputSchema`, `XxxCreateInputSchema`, `XxxUpdateInputSchema`, `XxxDeleteInputSchema`
|
|
|
+
|
|
|
+### Files to Reference
|
|
|
+
|
|
|
+| File | Purpose |
|
|
|
+| ---- | ------- |
|
|
|
+| `packages/admin-mcp-server/src/index.ts` | 主入口,注册所有工具 |
|
|
|
+| `packages/admin-mcp-server/src/tools/user-tools.ts` | 工具实现参考(完整 CRUD) |
|
|
|
+| `packages/admin-mcp-server/src/tools/area-tools.ts` | 地理区域工具参考(有树形结构) |
|
|
|
+| `packages/admin-mcp-server/src/tools/company-tools.ts` | 公司工具参考 |
|
|
|
+| `packages/admin-mcp-server/src/schemas/index.ts` | Schema 定义参考 |
|
|
|
+| `packages/admin-mcp-server/src/types.ts` | 类型定义参考 |
|
|
|
+| `packages/admin-mcp-server/src/services/api-client.ts` | API 客户端 |
|
|
|
+
|
|
|
+**后端 API 参考**:
|
|
|
+| 模块 | 路由模式 | 位置 | Schema 文件 |
|
|
|
+|-----|---------|------|------------|
|
|
|
+| 文件管理 | `createCrudRoutes` + 自定义 | `packages/core-module/file-module/src/routes/index.ts` | `packages/core-module/file-module/src/schemas/file.schema.ts` |
|
|
|
+| 银行名称 | `createCrudRoutes` | `packages/bank-names-module/src/routes/bank-names.ts` | `packages/bank-names-module/src/schemas/bank-name.schema.ts` |
|
|
|
+| 渠道管理 | 自定义路由 | `allin-packages/channel-module/src/routes/channel-custom.routes.ts` | `allin-packages/channel-module/src/schemas/channel.schema.ts` |
|
|
|
+| 残疾人管理 | 自定义路由 | `allin-packages/disability-module/src/routes/disabled-person-custom.routes.ts` | `allin-packages/disability-module/src/schemas/disabled-person.schema.ts` |
|
|
|
+| 残疾人企业查询 | 自定义查询 | `allin-packages/disability-module/src/routes/person-company.routes.ts` | `allin-packages/disability-module/src/schemas/disabled-person.schema.ts` |
|
|
|
+| 平台管理 | 自定义路由 | `allin-packages/platform-module/src/routes/platform-custom.routes.ts` | `allin-packages/platform-module/src/schemas/platform.schema.ts` |
|
|
|
+| 薪资管理 | 自定义路由 | `allin-packages/salary-module/src/routes/salary-crud.routes.ts` | `allin-packages/salary-module/src/schemas/salary.schema.ts` |
|
|
|
+| 订单管理 | 自定义路由 | `allin-packages/order-module/src/routes/order-crud.routes.ts` | `allin-packages/order-module/src/schemas/order.schema.ts` |
|
|
|
+| 公司管理 | 自定义路由 | `allin-packages/company-module/src/routes/company-crud.routes.ts` | `allin-packages/company-module/src/schemas/company.schema.ts` |
|
|
|
+
|
|
|
+**后端 Schema 文件路径速查**:
|
|
|
+```bash
|
|
|
+# 残疾人管理 Schema
|
|
|
+allin-packages/disability-module/src/schemas/disabled-person.schema.ts
|
|
|
+
|
|
|
+# 平台管理 Schema (已有部分定义,需扩展)
|
|
|
+allin-packages/platform-module/src/schemas/platform.schema.ts
|
|
|
+
|
|
|
+# 薪资管理 Schema
|
|
|
+allin-packages/salary-module/src/schemas/salary.schema.ts
|
|
|
+
|
|
|
+# 残疾人企业查询 Schema
|
|
|
+allin-packages/disability-module/src/schemas/disabled-person.schema.ts
|
|
|
+# 注意: FindPersonsWithCompanyQuerySchema 定义在 disabled-person.schema.ts 中
|
|
|
+```
|
|
|
+
|
|
|
+### Technical Decisions
|
|
|
+
|
|
|
+**已确定的技术决策**:
|
|
|
+
|
|
|
+1. **Schema 组织**: 所有 Schema 在 `schemas/index.ts` 中,不拆分文件
|
|
|
+2. **API 调用**: 标准路由用 RESTful,自定义路由按实际端点
|
|
|
+3. **文件上传**: 暂不支持二进制上传,只提供元数据管理
|
|
|
+4. **导出功能**: 返回确认信息,实际文件通过后端 API 下载
|
|
|
+5. **分页参数**: 标准路由用 `page/pageSize`,自定义路由用 `skip/take`
|
|
|
+6. **特殊工具**: 平台状态切换、残疾人企业查询
|
|
|
+
|
|
|
+## Implementation Plan
|
|
|
+
|
|
|
+### Tasks
|
|
|
+
|
|
|
+#### Task 0: 清理登录相关工具
|
|
|
+- **File**: `packages/admin-mcp-server/src/index.ts`
|
|
|
+- **File**: `packages/admin-mcp-server/src/tools/auth-tools.ts`
|
|
|
+- **Action**:
|
|
|
+ - 删除 `admin_login` 和 `admin_logout` 工具的注册和实现
|
|
|
+ - 保留 `admin_getCurrentUser` 工具(用于验证 token)
|
|
|
+ - 更新 `index.ts` 中的 `registerAllTools` 函数,移除登录/登出工具的注册代码
|
|
|
+- **Notes**: Token 通过 Authorization header 预置,不需要登录工具
|
|
|
+
|
|
|
+#### Task 1: 扩展类型定义
|
|
|
+- **File**: `packages/admin-mcp-server/src/types.ts`
|
|
|
+- **Action**: 添加以下类型定义:
|
|
|
+ ```typescript
|
|
|
+ // File Types
|
|
|
+ export interface File {
|
|
|
+ id: number;
|
|
|
+ fileName: string;
|
|
|
+ fileSize: number;
|
|
|
+ mimeType: string;
|
|
|
+ filePath: string;
|
|
|
+ uploadUserId: number;
|
|
|
+ uploadUser?: User;
|
|
|
+ createdAt: Date;
|
|
|
+ }
|
|
|
+
|
|
|
+ // BankName Types
|
|
|
+ export interface BankName {
|
|
|
+ id: number;
|
|
|
+ name: string;
|
|
|
+ code: string;
|
|
|
+ status: number;
|
|
|
+ createdAt: Date;
|
|
|
+ updatedAt: Date;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Channel Types
|
|
|
+ export interface Channel {
|
|
|
+ id: number;
|
|
|
+ channelName: string;
|
|
|
+ channelType: string;
|
|
|
+ contactPerson?: string;
|
|
|
+ contactPhone?: string;
|
|
|
+ description?: string;
|
|
|
+ status: number;
|
|
|
+ createTime: Date;
|
|
|
+ updateTime: Date;
|
|
|
+ }
|
|
|
+
|
|
|
+ // DisabledPerson Types
|
|
|
+ export interface DisabledPerson {
|
|
|
+ id: number;
|
|
|
+ name: string;
|
|
|
+ idCard: string;
|
|
|
+ disabilityCertNo: string;
|
|
|
+ disabilityType?: string;
|
|
|
+ disabilityLevel?: string;
|
|
|
+ phone?: string;
|
|
|
+ // ... other fields
|
|
|
+ }
|
|
|
+
|
|
|
+ // Platform Types (已有,需扩展)
|
|
|
+ export interface Platform {
|
|
|
+ id: number;
|
|
|
+ platformName: string;
|
|
|
+ contactPerson?: string;
|
|
|
+ contactPhone?: string;
|
|
|
+ contactEmail?: string;
|
|
|
+ status: number;
|
|
|
+ createTime: Date;
|
|
|
+ updateTime: Date;
|
|
|
+ }
|
|
|
+
|
|
|
+ // SalaryLevel Types
|
|
|
+ export interface SalaryLevel {
|
|
|
+ id: number;
|
|
|
+ provinceId?: number;
|
|
|
+ cityId?: number;
|
|
|
+ districtId?: number;
|
|
|
+ baseSalary?: number;
|
|
|
+ allowance?: number;
|
|
|
+ insurance?: number;
|
|
|
+ providentFund?: number;
|
|
|
+ totalSalary?: number;
|
|
|
+ updateTime: Date;
|
|
|
+ }
|
|
|
+ ```
|
|
|
+
|
|
|
+#### Task 2: 扩展 Schema 定义
|
|
|
+- **File**: `packages/admin-mcp-server/src/schemas/index.ts`
|
|
|
+- **Action**: 添加以下 Schema:
|
|
|
+
|
|
|
+**文件管理 Schema**:
|
|
|
+```typescript
|
|
|
+export const FileListInputSchema = ListQuerySchema;
|
|
|
+export const FileGetInputSchema = ResourceIdSchema;
|
|
|
+export const FileDeleteInputSchema = ResourceIdSchema;
|
|
|
+
|
|
|
+export type FileListInput = z.infer<typeof FileListInputSchema>;
|
|
|
+export type FileGetInput = z.infer<typeof FileGetInputSchema>;
|
|
|
+export type FileDeleteInput = z.infer<typeof FileDeleteInputSchema>;
|
|
|
+```
|
|
|
+
|
|
|
+**银行名称 Schema**:
|
|
|
+```typescript
|
|
|
+export const BankNameListInputSchema = ListQuerySchema;
|
|
|
+export const BankNameGetInputSchema = ResourceIdSchema;
|
|
|
+export const BankNameCreateInputSchema = z.object({
|
|
|
+ name: z.string().min(1).max(100).describe('Bank name (required)'),
|
|
|
+ code: z.string().min(1).max(20).describe('Bank code (required)'),
|
|
|
+ status: z.number().int().min(0).max(1).optional().describe('Status: 0 = disabled, 1 = enabled')
|
|
|
+}).strict();
|
|
|
+export const BankNameUpdateInputSchema = z.object({
|
|
|
+ id: z.number().int().positive().describe('Bank name ID to update'),
|
|
|
+ name: z.string().min(1).max(100).optional().describe('Bank name'),
|
|
|
+ code: z.string().min(1).max(20).optional().describe('Bank code'),
|
|
|
+ status: z.number().int().min(0).max(1).optional().describe('Status')
|
|
|
+}).strict();
|
|
|
+export const BankNameDeleteInputSchema = ResourceIdSchema;
|
|
|
+```
|
|
|
+
|
|
|
+**渠道管理 Schema**:
|
|
|
+```typescript
|
|
|
+export const ChannelListInputSchema = z.object({
|
|
|
+ skip: z.number().int().min(0).default(0).optional().describe('Skip records'),
|
|
|
+ take: z.number().int().min(1).max(100).default(20).optional().describe('Take records'),
|
|
|
+ response_format: ResponseFormatSchema.default(ResponseFormat.MARKDOWN)
|
|
|
+}).strict();
|
|
|
+export const ChannelGetInputSchema = z.object({
|
|
|
+ id: z.number().int().positive().describe('Channel ID')
|
|
|
+}).strict();
|
|
|
+export const ChannelCreateInputSchema = z.object({
|
|
|
+ channelName: z.string().min(1).max(100).describe('Channel name (required)'),
|
|
|
+ channelType: z.string().max(50).optional().describe('Channel type'),
|
|
|
+ contactPerson: z.string().max(50).optional().describe('Contact person'),
|
|
|
+ contactPhone: z.string().max(20).optional().describe('Contact phone'),
|
|
|
+ description: z.string().optional().describe('Description')
|
|
|
+}).strict();
|
|
|
+export const ChannelUpdateInputSchema = z.object({
|
|
|
+ id: z.number().int().positive().describe('Channel ID to update'),
|
|
|
+ channelName: z.string().min(1).max(100).optional(),
|
|
|
+ channelType: z.string().max(50).optional(),
|
|
|
+ contactPerson: z.string().max(50).optional(),
|
|
|
+ contactPhone: z.string().max(20).optional(),
|
|
|
+ description: z.string().optional()
|
|
|
+}).strict();
|
|
|
+export const ChannelDeleteInputSchema = z.object({
|
|
|
+ id: z.number().int().positive().describe('Channel ID to delete')
|
|
|
+}).strict();
|
|
|
+```
|
|
|
+
|
|
|
+**残疾人管理 Schema**:
|
|
|
+```typescript
|
|
|
+// 参考后端: allin-packages/disability-module/src/schemas/disabled-person.schema.ts
|
|
|
+export const DisabledPersonListInputSchema = z.object({
|
|
|
+ skip: z.number().int().min(0).default(0).optional().describe('Skip records'),
|
|
|
+ take: z.number().int().min(1).max(100).default(20).optional().describe('Take records'),
|
|
|
+ response_format: ResponseFormatSchema.default(ResponseFormat.MARKDOWN)
|
|
|
+}).strict();
|
|
|
+export const DisabledPersonGetInputSchema = ResourceIdSchema;
|
|
|
+export const DisabledPersonCreateInputSchema = z.object({
|
|
|
+ name: z.string().min(1).max(50).describe('Name (required)'),
|
|
|
+ idCard: z.string().regex(/^[1-9]\d{5}(18|19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dXx]$/).describe('ID Card number'),
|
|
|
+ disabilityCertNo: z.string().min(1).describe('Disability certificate number (required)'),
|
|
|
+ disabilityType: z.string().optional().describe('Disability type'),
|
|
|
+ disabilityLevel: z.string().optional().describe('Disability level'),
|
|
|
+ phone: z.string().regex(/^1[3-9]\d{9}$/).optional().describe('Phone number')
|
|
|
+ // ... 其他字段参考后端 schema
|
|
|
+}).strict();
|
|
|
+export const DisabledPersonUpdateInputSchema = z.object({
|
|
|
+ id: z.number().int().positive().describe('Disabled person ID to update'),
|
|
|
+ name: z.string().min(1).max(50).optional(),
|
|
|
+ idCard: z.string().regex(/^[1-9]\d{5}(18|19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dXx]$/).optional(),
|
|
|
+ disabilityCertNo: z.string().min(1).optional(),
|
|
|
+ disabilityType: z.string().optional(),
|
|
|
+ disabilityLevel: z.string().optional(),
|
|
|
+ phone: z.string().regex(/^1[3-9]\d{9}$/).optional()
|
|
|
+ // ... 其他字段
|
|
|
+}).strict();
|
|
|
+export const DisabledPersonDeleteInputSchema = ResourceIdSchema;
|
|
|
+```
|
|
|
+
|
|
|
+**平台管理 Schema**:
|
|
|
+```typescript
|
|
|
+// 参考后端: allin-packages/platform-module/src/schemas/platform.schema.ts
|
|
|
+export const PlatformListInputSchema = z.object({
|
|
|
+ skip: z.number().int().min(0).default(0).optional().describe('Skip records'),
|
|
|
+ take: z.number().int().min(1).max(100).default(20).optional().describe('Take records'),
|
|
|
+ response_format: ResponseFormatSchema.default(ResponseFormat.MARKDOWN)
|
|
|
+}).strict();
|
|
|
+export const PlatformGetInputSchema = ResourceIdSchema;
|
|
|
+export const PlatformCreateInputSchema = z.object({
|
|
|
+ platformName: z.string().min(1).max(100).describe('Platform name (required)'),
|
|
|
+ contactPerson: z.string().max(50).optional().describe('Contact person'),
|
|
|
+ contactPhone: z.string().max(20).optional().describe('Contact phone'),
|
|
|
+ contactEmail: z.string().email().max(100).optional().describe('Contact email')
|
|
|
+}).strict();
|
|
|
+export const PlatformUpdateInputSchema = z.object({
|
|
|
+ id: z.number().int().positive().describe('Platform ID to update'),
|
|
|
+ platformName: z.string().min(1).max(100).optional(),
|
|
|
+ contactPerson: z.string().max(50).optional(),
|
|
|
+ contactPhone: z.string().max(20).optional(),
|
|
|
+ contactEmail: z.string().email().max(100).optional()
|
|
|
+}).strict();
|
|
|
+export const PlatformDeleteInputSchema = ResourceIdSchema;
|
|
|
+export const PlatformToggleStatusInputSchema = ResourceIdSchema;
|
|
|
+```
|
|
|
+
|
|
|
+**薪资管理 Schema**:
|
|
|
+```typescript
|
|
|
+// 参考后端: allin-packages/salary-module/src/schemas/salary.schema.ts
|
|
|
+export const SalaryListInputSchema = z.object({
|
|
|
+ skip: z.number().int().min(0).default(0).optional().describe('Skip records'),
|
|
|
+ take: z.number().int().min(1).max(100).default(20).optional().describe('Take records'),
|
|
|
+ response_format: ResponseFormatSchema.default(ResponseFormat.MARKDOWN)
|
|
|
+}).strict();
|
|
|
+export const SalaryGetInputSchema = ResourceIdSchema;
|
|
|
+export const SalaryCreateInputSchema = z.object({
|
|
|
+ provinceId: z.number().int().positive().optional().describe('Province ID'),
|
|
|
+ cityId: z.number().int().positive().optional().describe('City ID'),
|
|
|
+ districtId: z.number().int().positive().optional().describe('District ID'),
|
|
|
+ baseSalary: z.number().nonnegative().optional().describe('Base salary'),
|
|
|
+ allowance: z.number().nonnegative().optional().describe('Allowance'),
|
|
|
+ insurance: z.number().nonnegative().optional().describe('Insurance'),
|
|
|
+ providentFund: z.number().nonnegative().optional().describe('Provident fund'),
|
|
|
+ totalSalary: z.number().nonnegative().optional().describe('Total salary')
|
|
|
+}).strict();
|
|
|
+export const SalaryUpdateInputSchema = z.object({
|
|
|
+ id: z.number().int().positive().describe('Salary level ID to update'),
|
|
|
+ provinceId: z.number().int().positive().optional(),
|
|
|
+ cityId: z.number().int().positive().optional(),
|
|
|
+ districtId: z.number().int().positive().optional(),
|
|
|
+ baseSalary: z.number().nonnegative().optional(),
|
|
|
+ allowance: z.number().nonnegative().optional(),
|
|
|
+ insurance: z.number().nonnegative().optional(),
|
|
|
+ providentFund: z.number().nonnegative().optional(),
|
|
|
+ totalSalary: z.number().nonnegative().optional()
|
|
|
+}).strict();
|
|
|
+export const SalaryDeleteInputSchema = ResourceIdSchema;
|
|
|
+```
|
|
|
+
|
|
|
+**订单管理 Schema** (添加 Create/Update/Delete):
|
|
|
+```typescript
|
|
|
+export const OrderCreateInputSchema = z.object({
|
|
|
+ // ... 参考后端 order.schema.ts 定义
|
|
|
+ companyId: z.number().int().positive().describe('Company ID (required)'),
|
|
|
+ platformId: z.number().int().positive().optional().describe('Platform ID'),
|
|
|
+ orderAmount: z.number().nonnegative().describe('Order amount (required)')
|
|
|
+ // ... 其他字段
|
|
|
+}).strict();
|
|
|
+export const OrderUpdateInputSchema = z.object({
|
|
|
+ id: z.number().int().positive().describe('Order ID to update'),
|
|
|
+ // ... 可更新字段
|
|
|
+}).strict();
|
|
|
+export const OrderDeleteInputSchema = ResourceIdSchema;
|
|
|
+```
|
|
|
+
|
|
|
+**公司管理 Schema** (添加 Create/Update/Delete):
|
|
|
+```typescript
|
|
|
+export const CompanyCreateInputSchema = z.object({
|
|
|
+ companyName: z.string().min(1).max(200).describe('Company name (required)'),
|
|
|
+ contactPerson: z.string().max(50).optional().describe('Contact person'),
|
|
|
+ contactPhone: z.string().regex(/^1[3-9]\d{9}$/).optional().describe('Contact phone'),
|
|
|
+ contactEmail: z.string().email().optional().describe('Contact email'),
|
|
|
+ address: z.string().max(500).optional().describe('Company address'),
|
|
|
+ platformId: z.number().int().positive().optional().describe('Platform ID'),
|
|
|
+ status: z.number().int().min(0).max(1).optional().describe('Status: 0 = disabled, 1 = enabled')
|
|
|
+}).strict();
|
|
|
+export const CompanyUpdateInputSchema = z.object({
|
|
|
+ id: z.number().int().positive().describe('Company ID to update'),
|
|
|
+ companyName: z.string().min(1).max(200).optional(),
|
|
|
+ contactPerson: z.string().max(50).optional(),
|
|
|
+ contactPhone: z.string().regex(/^1[3-9]\d{9}$/).optional(),
|
|
|
+ contactEmail: z.string().email().optional(),
|
|
|
+ address: z.string().max(500).optional(),
|
|
|
+ platformId: z.number().int().positive().optional(),
|
|
|
+ status: z.number().int().min(0).max(1).optional()
|
|
|
+}).strict();
|
|
|
+export const CompanyDeleteInputSchema = ResourceIdSchema;
|
|
|
+```
|
|
|
+
|
|
|
+**残疾人企业查询 Schema**:
|
|
|
+```typescript
|
|
|
+// 参考后端: allin-packages/disability-module/src/schemas/disabled-person.schema.ts
|
|
|
+// 使用 FindPersonsWithCompanyQuerySchema
|
|
|
+export const DisabilityCompanyQueryInputSchema = z.object({
|
|
|
+ name: z.string().optional().describe('Person name for search'),
|
|
|
+ idCard: z.string().optional().describe('ID Card number'),
|
|
|
+ companyName: z.string().optional().describe('Company name'),
|
|
|
+ gender: z.string().optional().describe('Gender'),
|
|
|
+ disabilityType: z.string().optional().describe('Disability type'),
|
|
|
+ disabilityLevel: z.string().optional().describe('Disability level'),
|
|
|
+ minAge: z.number().int().min(0).optional().describe('Minimum age'),
|
|
|
+ maxAge: z.number().int().min(0).optional().describe('Maximum age'),
|
|
|
+ city: z.string().optional().describe('City'),
|
|
|
+ district: z.string().optional().describe('District'),
|
|
|
+ companyId: z.number().int().positive().optional().describe('Company ID'),
|
|
|
+ platformId: z.number().int().positive().optional().describe('Platform ID'),
|
|
|
+ skip: z.number().int().min(0).default(0).optional().describe('Skip records'),
|
|
|
+ take: z.number().int().min(1).max(100).default(20).optional().describe('Take records'),
|
|
|
+ response_format: ResponseFormatSchema.default(ResponseFormat.MARKDOWN)
|
|
|
+}).strict();
|
|
|
+```
|
|
|
+
|
|
|
+#### Task 3: 创建文件管理工具
|
|
|
+- **File**: `packages/admin-mcp-server/src/tools/file-tools.ts` (新建)
|
|
|
+- **Action**: 创建文件管理工具,包含以下功能:
|
|
|
+ - `admin_list_files` - 列出文件(支持分页、搜索)
|
|
|
+ - `admin_get_file` - 获取单个文件详情
|
|
|
+ - `admin_delete_file` - 删除文件
|
|
|
+- **API 端点**:
|
|
|
+ - List: `GET /api/v1/files`
|
|
|
+ - Get: `GET /api/v1/files/{id}`
|
|
|
+ - Delete: `DELETE /api/v1/files/{id}`
|
|
|
+
|
|
|
+#### Task 4: 创建银行名称管理工具
|
|
|
+- **File**: `packages/admin-mcp-server/src/tools/bank-name-tools.ts` (新建)
|
|
|
+- **Action**: 创建银行名称管理工具,包含完整的 CRUD 操作
|
|
|
+- **API 端点**: 标准 CRUD (`/api/v1/bank-names`)
|
|
|
+
|
|
|
+#### Task 5: 创建渠道管理工具
|
|
|
+- **File**: `packages/admin-mcp-server/src/tools/channel-tools.ts` (新建)
|
|
|
+- **Action**: 创建渠道管理工具,使用自定义路由 API
|
|
|
+- **API 端点**:
|
|
|
+ - List: `GET /api/v1/channels/getAllChannels?skip=0&take=20`
|
|
|
+ - Get: `GET /api/v1/channels/getChannel?id=1`
|
|
|
+ - Create: `POST /api/v1/channels/createChannel`
|
|
|
+ - Update: `POST /api/v1/channels/updateChannel`
|
|
|
+ - Delete: `POST /api/v1/channels/deleteChannel`
|
|
|
+
|
|
|
+#### Task 6: 创建残疾人管理工具
|
|
|
+- **File**: `packages/admin-mcp-server/src/tools/disability-tools.ts` (新建)
|
|
|
+- **Action**: 创建残疾人管理工具
|
|
|
+- **API 端点**: 参考后端 `disabled-person-custom.routes.ts`
|
|
|
+
|
|
|
+#### Task 7: 创建残疾人企业查询工具
|
|
|
+- **File**: `packages/admin-mcp-server/src/tools/disability-company-tools.ts` (新建)
|
|
|
+- **Action**: 创建残疾人企业查询工具,包装后端查询 API
|
|
|
+- **API 端点**:
|
|
|
+ - Query: `GET /api/v1/disability-company/findPersonsWithCompany` - 支持按人员姓名、身份证、公司名称等条件查询
|
|
|
+- **Notes**: 这是一个只读查询工具,不涉及数据修改。查询输入参数包括:
|
|
|
+ - `name`: 人员姓名(模糊搜索)
|
|
|
+ - `idCard`: 身份证号
|
|
|
+ - `companyName`: 公司名称(模糊搜索)
|
|
|
+ - `gender`: 性别
|
|
|
+ - `disabilityType`: 残疾类型
|
|
|
+ - `disabilityLevel`: 残疾等级
|
|
|
+ - `city`: 城市
|
|
|
+ - `district`: 区县
|
|
|
+ - `companyId`: 公司ID
|
|
|
+ - `platformId`: 平台ID
|
|
|
+ - 分页参数: `skip`, `take`
|
|
|
+
|
|
|
+#### Task 8: 创建平台管理工具
|
|
|
+- **File**: `packages/admin-mcp-server/src/tools/platform-tools.ts` (新建)
|
|
|
+- **Action**: 创建平台管理工具,包含状态切换功能
|
|
|
+- **API 端点**:
|
|
|
+ - List: `GET /api/v1/platforms/getAllPlatforms`
|
|
|
+ - Get: `GET /api/v1/platforms/getPlatform/{id}`
|
|
|
+ - Create: `POST /api/v1/platforms/createPlatform`
|
|
|
+ - Update: `POST /api/v1/platforms/updatePlatform`
|
|
|
+ - Delete: `POST /api/v1/platforms/deletePlatform`
|
|
|
+ - Toggle Status: `POST /api/v1/platforms/togglePlatformStatus`
|
|
|
+
|
|
|
+#### Task 9: 创建薪资管理工具
|
|
|
+- **File**: `packages/admin-mcp-server/src/tools/salary-tools.ts` (新建)
|
|
|
+- **Action**: 创建薪资管理工具
|
|
|
+- **API 端点**: 参考后端 `salary-crud.routes.ts`
|
|
|
+
|
|
|
+#### Task 10: 扩展订单管理工具
|
|
|
+- **File**: `packages/admin-mcp-server/src/tools/order-tools.ts` (修改现有文件)
|
|
|
+- **Action**: 添加 Create/Update/Delete 工具
|
|
|
+
|
|
|
+#### Task 11: 扩展公司管理工具
|
|
|
+- **File**: `packages/admin-mcp-server/src/tools/company-tools.ts` (修改现有文件)
|
|
|
+- **Action**: 添加 Create/Update/Delete 工具
|
|
|
+
|
|
|
+#### Task 12: 在主入口注册所有新工具
|
|
|
+- **File**: `packages/admin-mcp-server/src/index.ts`
|
|
|
+- **Action**:
|
|
|
+ - 导入所有新工具模块
|
|
|
+ - 在 `registerAllTools` 函数中注册所有新工具
|
|
|
+ - 为每个工具添加描述文档
|
|
|
+
|
|
|
+#### Task 13: 更新工具导出索引
|
|
|
+- **File**: `packages/admin-mcp-server/src/tools/index.ts`
|
|
|
+- **Action**: 添加新工具的导出
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+### 并行执行说明
|
|
|
+
|
|
|
+**Task 3-11 可以并行开发**,因为这些任务创建的是独立的工具文件,彼此之间没有依赖关系:
|
|
|
+
|
|
|
+```
|
|
|
+Task 0 → Task 1 → Task 2 → [Task 3-11 并行] → Task 12 → Task 13
|
|
|
+ ┌─────────────┐
|
|
|
+ │ Task 3 │ file-tools.ts
|
|
|
+ │ Task 4 │ bank-name-tools.ts
|
|
|
+ │ Task 5 │ channel-tools.ts
|
|
|
+ │ Task 6 │ disability-tools.ts
|
|
|
+ │ Task 7 │ disability-company-tools.ts
|
|
|
+ │ Task 8 │ platform-tools.ts
|
|
|
+ │ Task 9 │ salary-tools.ts
|
|
|
+ │ Task 10 │ order-tools.ts (修改)
|
|
|
+ │ Task 11 │ company-tools.ts (修改)
|
|
|
+ └─────────────┘
|
|
|
+```
|
|
|
+
|
|
|
+**推荐执行顺序**:
|
|
|
+1. 串行完成 Task 0-2(基础准备工作)
|
|
|
+2. 并行执行 Task 3-11(独立工具开发)
|
|
|
+3. 串行完成 Task 12-13(整合工作)
|
|
|
+
|
|
|
+这种方式可以最大化开发效率。
|
|
|
+
|
|
|
+### Acceptance Criteria
|
|
|
+
|
|
|
+**认证与授权**:
|
|
|
+- [ ] AC 1: Given Token 已预置,当调用任何 MCP 工具时,then API 应该正确使用预置的 token 进行认证
|
|
|
+- [ ] AC 2: Given 用户调用 `admin_getCurrentUser`,when token 有效,then 返回当前用户信息
|
|
|
+
|
|
|
+**文件管理**:
|
|
|
+- [ ] AC 3: Given 用户调用 `admin_list_files`,when 请求成功,then 返回文件列表(支持分页、搜索)
|
|
|
+- [ ] AC 4: Given 用户调用 `admin_delete_file`,when 文件存在,then 删除成功
|
|
|
+
|
|
|
+**银行名称管理**:
|
|
|
+- [ ] AC 5: Given 用户调用 `admin_create_bank_name`,when 参数有效,then 成功创建银行名称
|
|
|
+- [ ] AC 6: Given 用户调用 `admin_update_bank_name`,when 银行名称存在,then 更新成功
|
|
|
+- [ ] AC 7: Given 用户调用 `admin_delete_bank_name`,when 银行名称存在,then 删除成功
|
|
|
+
|
|
|
+**渠道管理**:
|
|
|
+- [ ] AC 8: Given 用户调用 `admin_list_channels`,when 请求成功,then 返回渠道列表(支持 skip/take 分页)
|
|
|
+- [ ] AC 9: Given 用户调用 `admin_create_channel`,when 参数有效,then 成功创建渠道并返回渠道信息
|
|
|
+- [ ] AC 10: Given 用户调用 `admin_update_channel`,when 渠道存在,then 更新成功
|
|
|
+- [ ] AC 11: Given 用户调用 `admin_delete_channel`,when 渠道存在,then 删除成功
|
|
|
+
|
|
|
+**残疾人管理**:
|
|
|
+- [ ] AC 12: Given 用户调用 `admin_list_disabled_persons`,when 请求成功,then 返回残疾人列表
|
|
|
+- [ ] AC 13: Given 用户调用 `admin_create_disabled_person`,when 参数有效(含身份证号、残疾证号),then 创建成功
|
|
|
+
|
|
|
+**残疾人企业查询**:
|
|
|
+- [ ] AC 14: Given 用户调用 `admin_query_disability_company`,when 提供查询参数(如姓名、公司名),then 返回匹配结果(支持多条件筛选)
|
|
|
+
|
|
|
+**平台管理**:
|
|
|
+- [ ] AC 15: Given 用户调用 `admin_list_platforms`,when 请求成功,then 返回平台列表
|
|
|
+- [ ] AC 16: Given 用户调用 `admin_toggle_platform_status`,when 平台存在,then 状态切换成功
|
|
|
+
|
|
|
+**薪资管理**:
|
|
|
+- [ ] AC 17: Given 用户调用 `admin_list_salaries`,when 请求成功,then 返回薪资级别列表
|
|
|
+- [ ] AC 18: Given 用户调用 `admin_create_salary`,when 参数有效,then 创建薪资级别
|
|
|
+
|
|
|
+**订单管理**:
|
|
|
+- [ ] AC 19: Given 用户调用 `admin_create_order`,when 参数有效(公司ID、金额),then 创建成功
|
|
|
+- [ ] AC 20: Given 用户调用 `admin_delete_order`,when 订单存在,then 删除成功
|
|
|
+
|
|
|
+**公司管理**:
|
|
|
+- [ ] AC 21: Given 用户调用 `admin_create_company`,when 参数有效(公司名),then 创建成功
|
|
|
+- [ ] AC 22: Given 用户调用 `admin_update_company`,when 公司存在,then 更新成功
|
|
|
+- [ ] AC 23: Given 用户调用 `admin_delete_company`,when 公司存在且无依赖数据,then 删除成功
|
|
|
+
|
|
|
+**通用功能**:
|
|
|
+- [ ] AC 24: Given 所有工具都支持 `response_format: "json"`,when 指定 JSON 格式,then 返回结构化的 JSON 数据
|
|
|
+- [ ] AC 25: Given 用户调用工具时参数无效,when Zod 验证失败,then 返回清晰的验证错误信息
|
|
|
+- [ ] AC 26: Given API 调用失败,when 发生错误,then 返回包含错误信息的响应
|
|
|
+- [ ] AC 27: Given 列表数据超过 CHARACTER_LIMIT,when 数据过多,then 自动截断并提示用户使用分页
|
|
|
+
|
|
|
+## Additional Context
|
|
|
+
|
|
|
+### Dependencies
|
|
|
+
|
|
|
+- **后端 API**: 所有目标 API 端点已存在并正常运行
|
|
|
+- **Admin MCP Server 基础架构**: `@modelcontextprotocol/sdk`, `express`, `zod`
|
|
|
+- **现有工具模式**: `user-tools.ts`, `company-tools.ts` 等作为参考
|
|
|
+
|
|
|
+### Testing Strategy
|
|
|
+
|
|
|
+**手动测试步骤**:
|
|
|
+1. 确保 Token 已通过 Authorization header 预置
|
|
|
+2. 逐个测试新工具的 list 操作,验证分页功能
|
|
|
+3. 测试 create 操作,验证数据创建成功
|
|
|
+4. 测试 get 操作,验证能获取单个资源
|
|
|
+5. 测试 update 操作,验证数据更新成功
|
|
|
+6. 测试 delete 操作,验证数据删除成功
|
|
|
+7. 测试特殊功能(如 `togglePlatformStatus`)
|
|
|
+8. 测试 JSON 输出格式
|
|
|
+9. 测试错误处理(无效参数、不存在的资源等)
|
|
|
+
|
|
|
+**验证点**:
|
|
|
+- 工具命名符合约定 (`admin_xxx_yyy`)
|
|
|
+- Schema 验证正常工作
|
|
|
+- Markdown 输出格式正确
|
|
|
+- JSON 输出格式正确
|
|
|
+- 错误信息清晰有用
|
|
|
+
|
|
|
+### Notes
|
|
|
+
|
|
|
+- **Token 预置**: MCP 使用时 token 通过 Authorization header 预置,不需要调用登录工具
|
|
|
+- **分页参数差异**: 标准路由用 `page/pageSize`,自定义路由用 `skip/take`(需要转换)
|
|
|
+- **自定义路由 CUD**: 使用 POST 方法(后端设计)
|
|
|
+- **文件上传**: 暂不支持二进制上传,只提供文件元数据管理
|
|
|
+- **character 限制**: 默认 25000 字符,超过时自动截断
|