Compare commits
8
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
40ac398acc | ||
|
|
ffb705ba43 | ||
|
|
4b5bd9e898 | ||
|
|
48e4f61ff4 | ||
|
|
3087570803 | ||
|
|
9b64798d6b | ||
|
|
abfaa8c2d1 | ||
|
|
c381249fa8 |
@@ -0,0 +1 @@
|
||||
registry=https://registry.npmmirror.com
|
||||
@@ -16,6 +16,7 @@ const session = reactive({ name: '', csrfToken: '' })
|
||||
const loginForm = reactive({ account: '', password: '' })
|
||||
const users = ref([])
|
||||
const userKeyword = ref('')
|
||||
const userPage = reactive({ page: 1, pageSize: 20, total: 0 })
|
||||
const selectedUser = ref(null)
|
||||
const permission = ref(null)
|
||||
const tokens = ref([])
|
||||
@@ -80,14 +81,22 @@ async function switchMenu(id) {
|
||||
if (id === 'audit') await Promise.all([loadAudit(), api('/tools').then(data => { tools.value = data })])
|
||||
}
|
||||
|
||||
async function loadUsers() {
|
||||
users.value = await api(`/users${qs({ keyword: userKeyword.value })}`)
|
||||
async function loadUsers(resetPage = false) {
|
||||
if (resetPage) userPage.page = 1
|
||||
const data = await api(`/users${qs({ keyword: userKeyword.value, page: userPage.page, pageSize: userPage.pageSize })}`)
|
||||
users.value = data.records
|
||||
userPage.total = data.total
|
||||
const existing = users.value.find(user => user.id === selectedUser.value?.id)
|
||||
if (existing) selectedUser.value = existing
|
||||
else if (users.value.length) await selectUser(users.value[0])
|
||||
else { selectedUser.value = null; permission.value = null; tokens.value = [] }
|
||||
}
|
||||
|
||||
async function changeUserPage(page) {
|
||||
userPage.page = page
|
||||
await run(loadUsers)
|
||||
}
|
||||
|
||||
async function selectUser(user) {
|
||||
selectedUser.value = user
|
||||
;[permission.value, tokens.value, scopes.value] = await Promise.all([
|
||||
@@ -245,11 +254,11 @@ onMounted(async () => {
|
||||
<div class="token-layout">
|
||||
<div>
|
||||
<article class="card">
|
||||
<div class="card-title"><span>人员</span><small>来自现有 user 表</small></div>
|
||||
<div class="card-title"><span>人员</span><small>来自现有 user 表 · 仅企业账号(不含物业)</small></div>
|
||||
<div class="toolbar compact">
|
||||
<el-input v-model="userKeyword" clearable placeholder="姓名 / 手机号" aria-label="搜索人员"
|
||||
data-testid="admin-input-user-search" @keyup.enter="run(loadUsers)" />
|
||||
<el-button data-testid="admin-user-search-button" @click="run(loadUsers)">搜索</el-button>
|
||||
data-testid="admin-input-user-search" @keyup.enter="run(() => loadUsers(true))" />
|
||||
<el-button data-testid="admin-user-search-button" @click="run(() => loadUsers(true))">搜索</el-button>
|
||||
</div>
|
||||
<div class="user-list">
|
||||
<button v-for="user in users" :key="user.id" :class="['user-row', { selected: selectedUser?.id === user.id }]"
|
||||
@@ -259,6 +268,9 @@ onMounted(async () => {
|
||||
</button>
|
||||
<el-empty v-if="!users.length" :image-size="60" description="未找到人员" />
|
||||
</div>
|
||||
<el-pagination class="user-pager" layout="prev, pager, next, total" :total="userPage.total"
|
||||
:page-size="userPage.pageSize" :current-page="userPage.page" small background
|
||||
data-testid="admin-user-pager" @current-change="changeUserPage" />
|
||||
</article>
|
||||
<article class="card permission-card">
|
||||
<div class="card-title"><span>数据权限归属</span><small>只读 · 在现有系统维护</small></div>
|
||||
|
||||
@@ -40,6 +40,7 @@ button, input { font: inherit; }
|
||||
.user-row.selected { background: #ecf5ff; }
|
||||
.user-row span:first-child { display: flex; flex-direction: column; gap: 4px; }
|
||||
.user-row small { color: #909399; }
|
||||
.user-pager { margin-top: 10px; justify-content: flex-end; }
|
||||
.permission-card dl { margin: 0; border-top: 1px solid #ebeef5; }
|
||||
.permission-card dl > div { display: grid; grid-template-columns: 92px 1fr; border-bottom: 1px solid #ebeef5; }
|
||||
.permission-card dt, .permission-card dd { margin: 0; padding: 10px 6px; }
|
||||
|
||||
@@ -136,7 +136,10 @@
|
||||
<goals><goal>install-node-and-npm</goal></goals>
|
||||
<configuration>
|
||||
<nodeVersion>v20.20.2</nodeVersion>
|
||||
<npmVersion>10.8.2</npmVersion>
|
||||
<!-- provided = 使用 node 自带的 npm, 无需单独下载 -->
|
||||
<npmVersion>provided</npmVersion>
|
||||
<!-- 国内镜像, 避免 nodejs.org 直连超时 -->
|
||||
<downloadRoot>https://npmmirror.com/mirrors/node/</downloadRoot>
|
||||
</configuration>
|
||||
</execution>
|
||||
<execution>
|
||||
|
||||
@@ -82,19 +82,28 @@ public class AdminConsoleController {
|
||||
}
|
||||
|
||||
@GetMapping("/users")
|
||||
public List<UserView> users(@RequestParam(defaultValue = "") String keyword) {
|
||||
public UserPageView users(@RequestParam(defaultValue = "") String keyword,
|
||||
@RequestParam(defaultValue = "1") int page,
|
||||
@RequestParam(defaultValue = "20") int pageSize) {
|
||||
if (keyword.length() > 100) bad("搜索关键词最多 100 字");
|
||||
LambdaQueryWrapper<User> query = new LambdaQueryWrapper<User>().eq(User::getSoftDelete, 0);
|
||||
int pageNo = Math.max(page, 1);
|
||||
int size = pageSize < 1 ? 20 : Math.min(pageSize, 100);
|
||||
LambdaQueryWrapper<User> query = new LambdaQueryWrapper<User>().eq(User::getSoftDelete, 0)
|
||||
// 只查企业账号(0=企业内部 1=代理),物业账号(2)不进列表;历史数据可能为 NULL 一并保留
|
||||
.and(w -> w.ne(User::getEnterpriseType, 2).or().isNull(User::getEnterpriseType));
|
||||
if (!keyword.isBlank()) query.and(w -> w.like(User::getName, keyword.trim())
|
||||
.or().like(User::getMobile, keyword.trim()));
|
||||
List<User> users = userMapper.selectList(query.orderByDesc(User::getId).last("LIMIT 100"));
|
||||
Long total = userMapper.selectCount(query);
|
||||
List<User> users = userMapper.selectList(query.orderByDesc(User::getId)
|
||||
.last("LIMIT " + (long) (pageNo - 1) * size + "," + size));
|
||||
Set<Long> ids = users.stream().map(User::getId).collect(Collectors.toSet());
|
||||
Map<Long, Long> counts = ids.isEmpty() ? Map.of() : tokenMapper.selectList(
|
||||
new LambdaQueryWrapper<AgentApiKey>().in(AgentApiKey::getUserId, ids)
|
||||
.eq(AgentApiKey::getSoftDelete, 0))
|
||||
.stream().collect(Collectors.groupingBy(AgentApiKey::getUserId, Collectors.counting()));
|
||||
return users.stream().map(user -> new UserView(user.getId(), user.getName(), maskMobile(user.getMobile()),
|
||||
counts.getOrDefault(user.getId(), 0L))).toList();
|
||||
List<UserView> records = users.stream().map(user -> new UserView(user.getId(), user.getName(),
|
||||
maskMobile(user.getMobile()), counts.getOrDefault(user.getId(), 0L))).toList();
|
||||
return new UserPageView(total, pageNo, size, records);
|
||||
}
|
||||
|
||||
@GetMapping("/users/{id}/permission")
|
||||
@@ -375,6 +384,7 @@ public class AdminConsoleController {
|
||||
}
|
||||
|
||||
public record UserView(Long id, String name, String mobile, long tokenCount) {}
|
||||
public record UserPageView(long total, int page, int pageSize, List<UserView> records) {}
|
||||
public record PermissionView(String authorityName, Integer isAgent, String agentName,
|
||||
Integer enterpriseType, String visibleRange) {}
|
||||
public record TokenRequest(String memo, List<Long> scopeIds, Date expireTime) {}
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
package me.iiv.agent.admin;
|
||||
|
||||
import me.iiv.agent.mapper.m0.AgentMapper;
|
||||
import me.iiv.agent.mapper.m0.AuthorityMapper;
|
||||
import me.iiv.agent.mapper.m0.UserMapper;
|
||||
import me.iiv.agent.mapper.svc.AgentApiKeyMapper;
|
||||
import me.iiv.agent.mapper.svc.AgentScopeMapper;
|
||||
import me.iiv.agent.mapper.svc.AuditLogMapper;
|
||||
import me.iiv.agent.mapper.svc.ScopeToolMapper;
|
||||
import me.iiv.agent.mapper.svc.TokenScopeMapper;
|
||||
import me.iiv.agent.mapper.svc.ToolMapper;
|
||||
import me.iiv.agent.permission.PermissionService;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class AdminConsoleControllerTest {
|
||||
|
||||
@Mock UserMapper userMapper;
|
||||
@Mock AuthorityMapper authorityMapper;
|
||||
@Mock AgentMapper agentMapper;
|
||||
@Mock AgentApiKeyMapper tokenMapper;
|
||||
@Mock AgentScopeMapper scopeMapper;
|
||||
@Mock ToolMapper toolMapper;
|
||||
@Mock ScopeToolMapper scopeToolMapper;
|
||||
@Mock AuditLogMapper auditMapper;
|
||||
@Mock TokenScopeMapper tokenScopeMapper;
|
||||
@Mock PermissionService permissionService;
|
||||
@Mock AdminTokenService tokenService;
|
||||
|
||||
AdminConsoleController controller;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
com.baomidou.mybatisplus.core.metadata.TableInfoHelper.initTableInfo(
|
||||
new org.apache.ibatis.builder.MapperBuilderAssistant(
|
||||
new com.baomidou.mybatisplus.core.MybatisConfiguration(), ""),
|
||||
me.iiv.agent.entity.m0.User.class);
|
||||
controller = new AdminConsoleController(userMapper, authorityMapper, agentMapper, tokenMapper,
|
||||
scopeMapper, toolMapper, scopeToolMapper, auditMapper, tokenScopeMapper,
|
||||
permissionService, tokenService);
|
||||
when(userMapper.selectCount(any())).thenReturn(0L);
|
||||
when(userMapper.selectList(any())).thenReturn(List.of());
|
||||
}
|
||||
|
||||
@Test
|
||||
void usersClampsPageSizeToHundred() {
|
||||
AdminConsoleController.UserPageView view = controller.users("", 1, 500);
|
||||
assertEquals(100, view.pageSize());
|
||||
assertEquals(1, view.page());
|
||||
assertEquals(0, view.total());
|
||||
}
|
||||
|
||||
@Test
|
||||
void usersDefaultsInvalidPageAndSize() {
|
||||
AdminConsoleController.UserPageView view = controller.users("", 0, 0);
|
||||
assertEquals(1, view.page());
|
||||
assertEquals(20, view.pageSize());
|
||||
}
|
||||
|
||||
@Test
|
||||
void usersExcludesPropertyAccounts() {
|
||||
controller.users("", 1, 20);
|
||||
org.mockito.ArgumentCaptor<com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<me.iiv.agent.entity.m0.User>>
|
||||
captor = org.mockito.ArgumentCaptor.forClass(com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper.class);
|
||||
org.mockito.Mockito.verify(userMapper, org.mockito.Mockito.atLeastOnce()).selectCount(captor.capture());
|
||||
String sql = captor.getValue().getSqlSegment();
|
||||
assertTrue(sql.contains("enterprise_type"), "用户列表必须过滤物业账号: " + sql);
|
||||
assertTrue(sql.contains("IS NULL"), "历史 NULL 数据需保留: " + sql);
|
||||
}
|
||||
}
|
||||
@@ -123,7 +123,7 @@ xq CLI ─────┘ (Go 单二进制, 同为 MCP 客户端) ┌──
|
||||
|
||||
### 5.2 Web 管理后台(四个页面)
|
||||
|
||||
- **用户与 Token 管理**:人员选自现有 `user` 表;每人名下签发/禁用 token,展示 key 前缀、作用域、最后使用时间;选中人员展示其**数据权限归属**(权限组/isagent、所属代理、企业类型、可见范围说明,只读,便于排查"查不到数据")。
|
||||
- **用户与 Token 管理**:人员选自现有 `user` 表,**只列企业账号**(`enterprise_type` 0=企业内部 / 1=代理,历史 NULL 保留),物业账号(=2)不进列表——Token 是给能查代理商/客户数据的人用的;每人名下签发/禁用 token,展示 key 前缀、作用域、最后使用时间;选中人员展示其**数据权限归属**(权限组/isagent、所属代理、企业类型、可见范围说明,只读,便于排查"查不到数据")。
|
||||
- **工具管理**:工具列表来自服务端注册自动同步(工具名/分类/读写类型页面只读);展示每个工具被哪些作用域引用;「停用」为全局紧急开关。分类(category)在代码注册时由开发者声明(工具的业务域归属是客观属性,不允许页面上修改,避免与代码漂移)。
|
||||
- **作用域管理**:作用域 CRUD + 勾选工具(工具按 category 分组展示)。
|
||||
- **审计日志**:按人/token/工具/时间筛选。
|
||||
@@ -223,6 +223,7 @@ xiaoqu-agent-service/
|
||||
- CLI 复用 `/mcp` 端点做 MCP 客户端,不开第二套服务 → 权限逻辑只有一份;Go 单二进制分发,与服务端 Java 栈解耦
|
||||
- 不依赖老 jar,Entity 自行生成 → 与老系统零代码依赖
|
||||
- 前后端一体打包单 jar → 可逆决定,将来可拆
|
||||
- 列表类接口一律服务端分页,pageSize/rows 上限 100(超出截断为 100)→ 防止一次性拉全表,MCP 工具与后台 API 同一尺度
|
||||
|
||||
## 11. 开发环境(dev)
|
||||
|
||||
@@ -239,3 +240,11 @@ xiaoqu-agent-service/
|
||||
- 密码等敏感值不入库本文档,以 `pom.xml` dev profile 为准;新系统 `application-dev.yml` 引用同名环境变量。
|
||||
- 自有库表(§7)建在同实例新 schema:dev 用 `xiaoqu_agent_svc_d`(沿用 `_d` 后缀约定),首次部署前手工建库。
|
||||
- 新系统与老的 dev 后端(Jetty 8091)可同时运行,互不干扰——注意两边连的是**同一套** dev 数据库和 Redis,老系统dev库的数据变动会同时影响两边的查询结果。
|
||||
|
||||
## 12. 开发流程约定
|
||||
|
||||
- **一律用 git worktree 开发**:每个需求从 main 拉分支到 `../oldproject-worktrees/<feature>/`,在 worktree 里改代码、跑测试;验证通过后合并回 main 再推送远程。主检出(本目录)保持可随时部署的干净状态。
|
||||
- 合并回 main 后用 `xiaoqu-agent-service/deploy/deploy.sh restart` 完成构建+重启,接口实测通过再 `git push`。
|
||||
- 后台列表页统一服务端分页(page 从 1 开始,pageSize 默认 20、上限 100);MCP 工具 `rows` 同样上限 100,超出截断并在 `notice` 字段提示。
|
||||
- **新增/修改 MCP 工具必须同步更新 `doc/agent-mcp-tools-reference.md`**(入参、出参、权限语义、示例),工具对外契约以该文档为准。
|
||||
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
# xiaoqu-agent-service MCP 工具接口文档
|
||||
|
||||
> 本文档是 MCP 服务**对外暴露工具**的接口契约。新增/修改工具时必须同步更新本文档(开发约定见 `agent-mcp-service-design.md` §12)。
|
||||
> 文档中的入参与代码 `@ToolParam`、出参与工具实际返回保持一致;在线验证方式见文末附录。
|
||||
|
||||
## 0. 调用约定(所有工具共用)
|
||||
|
||||
| 项 | 说明 |
|
||||
|---|---|
|
||||
| 端点 | `POST http://<host>:8088/mcp`(MCP Streamable HTTP) |
|
||||
| 认证 | Header `Authorization: Bearer xqak_xxx`,ak 在管理后台「用户与 Token」签发,服务端只存 SHA-256 |
|
||||
| 工具可见性 | 按 ak 绑定的作用域过滤,`tools/list` 只返回该 ak 可用的工具 |
|
||||
| 行级数据权限 | 跟 ak 所属人走,照搬老系统 `getByIsAgent` 语义(见设计文档 §3.3),与作用域无关 |
|
||||
| 分页 | 列表类工具统一 `page`(从 1 开始,默认 1)、`rows`(默认 20,**上限 100**,超出截断为 100 并在返回 `notice` 字段提示) |
|
||||
| 分页出参 | 统一含 `total`(总数)、`page`、`rows`、`records`(本页记录)、`notice`(仅截断时出现) |
|
||||
|
||||
---
|
||||
|
||||
## 1. agent.count
|
||||
|
||||
统计代理商总数(demo 工具)。
|
||||
|
||||
- 分类:system | 类型:读
|
||||
- 数据来源:m0 库 `agent` 表,固定条件 `soft_delete = 0`
|
||||
|
||||
**入参**:无
|
||||
|
||||
**出参**:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| count | number | 代理商总数 |
|
||||
|
||||
```json
|
||||
{ "count": 62 }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. agent.list.query
|
||||
|
||||
查询代理商列表(分页),对应老系统 `/agent/listByPage`。
|
||||
|
||||
- 分类:customer | 类型:读
|
||||
- 数据来源:m0 库 `agent` 表
|
||||
- 行级权限:权限组 `isagent=1`(代理商身份)只查自己名下直属下级(`supervisor_id = 自己agentId`);`isagent=0`(总部)看全部。传 `id` 精确查询时:代理商仅限自己或直属下级,总部可传任意合法 id,且其余过滤条件失效。
|
||||
|
||||
**入参**:
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
| id | number | 否 | 代理商 id,精确查询;传入后忽略其他过滤条件 |
|
||||
| name | string | 否 | 代理商名称,模糊匹配 |
|
||||
| level | number | 否 | 级别:0=总部 1=一级 2=二级 |
|
||||
| channelNo | string | 否 | 渠道编号:Z=总部 F=分公司 D=代理 |
|
||||
| province | string | 否 | 省份,精确匹配,如 `北京市` |
|
||||
| city | string | 否 | 城市,精确匹配,如 `北京市` |
|
||||
| serialnumber | string | 否 | 代理商编号,精确匹配 |
|
||||
| page | number | 否 | 页码,从 1 开始,默认 1 |
|
||||
| rows | number | 否 | 每页条数,默认 20,上限 100 |
|
||||
|
||||
**出参 records 字段**:
|
||||
|
||||
| 字段 | 说明 |
|
||||
|---|---|
|
||||
| id / name | 代理商 id / 名称 |
|
||||
| level / channelNo / serialnumber | 级别 / 渠道编号 / 代理商编号 |
|
||||
| supervisorName | 上级代理名称(无上级为 `-`,批量富化) |
|
||||
| province / city / countyseat | 省 / 市 / 区县 |
|
||||
| contact / phone | 联系人 / 电话 |
|
||||
| agencyarea / memo | 代理区域 / 备注 |
|
||||
| createTime | 创建时间,`yyyy-MM-dd HH:mm:ss` |
|
||||
|
||||
```json
|
||||
{
|
||||
"total": 62, "page": 1, "rows": 20,
|
||||
"records": [ { "id": 19, "name": "北京D类代理", "level": 2, "channelNo": "D", "...": "..." } ]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. property.list.query
|
||||
|
||||
查询物业(客户)列表(分页),对应老系统 `/property/listByPage`。
|
||||
|
||||
- 分类:customer | 类型:读
|
||||
- 数据来源:m0 库 `properyt` 表
|
||||
- 行级权限(完整版 `getByIsAgent` 语义):
|
||||
- 总部(isagent=0):全部;
|
||||
- 代理商(isagent=1):自己 + 直属下级代理 + `warehouse_agent_auth` 代管代理 名下物业,及 `agent_manage_district_auth` 授权代管的物业;
|
||||
- 销售部门非负责人:自己创建的物业 ∪ 代管授权代理名下物业(`id IN (...)` 结构,与其余规则互斥,1:1 复刻老代码 saleFlag 分支)。
|
||||
- **不含** enterprise_type=2 物业用户限自己物业的规则(物业用户不查此列表)。
|
||||
|
||||
**入参**:
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
| name | string | 否 | 物业名称,模糊匹配 |
|
||||
| agentId | number | 否 | 所属代理商 id,精确匹配 |
|
||||
| serialnumber | string | 否 | 物业编号,精确匹配 |
|
||||
| province | string | 否 | 省份,精确匹配 |
|
||||
| city | string | 否 | 城市,精确匹配 |
|
||||
| contact | string | 否 | 联系人,精确匹配 |
|
||||
| phone | string | 否 | 联系电话,精确匹配 |
|
||||
| page | number | 否 | 页码,从 1 开始,默认 1 |
|
||||
| rows | number | 否 | 每页条数,默认 20,上限 100 |
|
||||
|
||||
**出参 records 字段**:
|
||||
|
||||
| 字段 | 说明 |
|
||||
|---|---|
|
||||
| id / name / serialnumber | 物业 id / 名称 / 编号 |
|
||||
| agentName | 所属代理商名称(查不到为 `未查询到`) |
|
||||
| createName | 创建人/销售姓名(未分配为 `未分配`) |
|
||||
| province / city / countyseat / address | 省 / 市 / 区县 / 详细地址 |
|
||||
| contact / phone | 联系人 / 电话 |
|
||||
| createTime | 创建时间,`yyyy-MM-dd HH:mm:ss` |
|
||||
|
||||
---
|
||||
|
||||
## 4. sys.ping
|
||||
|
||||
服务自检(demo 工具,证明链路连通)。
|
||||
|
||||
- 分类:system | 类型:读
|
||||
|
||||
**入参**:无
|
||||
|
||||
**出参**:
|
||||
|
||||
| 字段 | 说明 |
|
||||
|---|---|
|
||||
| service | 服务名(spring.application.name) |
|
||||
| version | 服务版本 |
|
||||
| time | 服务器当前时间 |
|
||||
| redis | `PONG` 表示 Redis 连通;否则为 `ERROR: ...` |
|
||||
|
||||
---
|
||||
|
||||
## 附录:在线查看工具清单
|
||||
|
||||
```bash
|
||||
AK=xqak_xxx; H=http://localhost:8088/mcp
|
||||
# 1) initialize,从响应头取 mcp-session-id
|
||||
# 2) 带 mcp-session-id 发 notifications/initialized
|
||||
# 3) 带 mcp-session-id 调用 tools/list
|
||||
curl -X POST $H -H "Authorization: Bearer $AK" -H "mcp-session-id: $SID" \
|
||||
-H 'Content-Type: application/json' -H 'Accept: application/json, text/event-stream' \
|
||||
-d '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}'
|
||||
```
|
||||
|
||||
也可在管理后台「工具管理」页查看全量注册工具(不受作用域过滤)。
|
||||
Reference in New Issue
Block a user