create-windy 0.3.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/README.md +26 -3
  2. package/dist/cli.js +8077 -3852
  3. package/package.json +3 -3
  4. package/template/.windy-template.json +2 -2
  5. package/template/README.md +25 -3
  6. package/template/_gitignore +4 -0
  7. package/template/apps/agent-server/tests/__init__.py +1 -0
  8. package/template/apps/agent-server/tests/openai_provider_test_support.py +131 -0
  9. package/template/apps/agent-server/tests/test_openai_provider.py +7 -123
  10. package/template/apps/server/src/auth/routes.test-support.ts +94 -0
  11. package/template/apps/server/src/auth/routes.test.ts +20 -88
  12. package/template/apps/server/src/index.ts +17 -34
  13. package/template/apps/server/src/mcp/adapter.test.ts +3 -2
  14. package/template/apps/server/src/mcp/adapter.ts +9 -9
  15. package/template/apps/server/src/mcp/routes.ts +4 -3
  16. package/template/apps/server/src/platform-http-runtime.ts +87 -0
  17. package/template/apps/server/src/system/drizzle-system-configs.ts +319 -0
  18. package/template/apps/server/src/system/drizzle-system.ts +35 -315
  19. package/template/apps/server/src/system/resource-routes.ts +270 -0
  20. package/template/apps/server/src/system/routes.ts +3 -269
  21. package/template/apps/web/src/pages/system/components/SystemResourceLoadError.vue +21 -0
  22. package/template/apps/web/src/pages/system/components/SystemResourceLoadError.webtest.ts +17 -0
  23. package/template/apps/web/src/pages/system/components/SystemResourcePage.vue +5 -10
  24. package/template/docs/architecture/mcp-business-capabilities.md +107 -0
  25. package/template/docs/development/custom-login-page.md +3 -3
  26. package/template/docs/platform/agent-runtime.md +5 -5
  27. package/template/docs/platform/agent-tools.md +4 -0
  28. package/template/packages/modules/package.json +1 -1
  29. package/template/packages/modules/src/composition-fixtures.test-support.ts +164 -0
  30. package/template/packages/modules/src/composition.test.ts +5 -158
  31. package/template/packages/modules/src/system-api-operational.ts +234 -0
  32. package/template/packages/modules/src/system-api-permissions.ts +8 -229
  33. package/template/packages/modules/src/system-module-tasks.ts +86 -0
  34. package/template/packages/modules/src/system-modules-access.test.ts +60 -0
  35. package/template/packages/modules/src/system-modules.test.ts +0 -53
  36. package/template/packages/modules/src/system-modules.ts +2 -84
  37. package/template/packages/shared/index.ts +1 -0
  38. package/template/packages/shared/package.json +1 -1
  39. package/template/packages/shared/src/mcp.test.ts +12 -0
  40. package/template/packages/shared/src/mcp.ts +7 -0
@@ -1,49 +1,25 @@
1
1
  import type { FeatureFlagDefinition } from "@southwind-ai/shared";
2
- import { recordMutationAudit } from "../audit/policy.js";
3
2
  import { registerFeatureFlagRoutes } from "../feature/routes.js";
4
3
  import { findSystemFeature } from "../route-guards.js";
5
4
  import type { createServerRuntime } from "../runtime.js";
6
- import type { ManagedEntity } from "./entities.js";
7
5
  import { registerMenuRoutes } from "./menu-routes.js";
8
6
  import {
9
7
  registerDepartmentQuickCreateRoute,
10
8
  registerDepartmentTransitionRoute,
11
9
  } from "./department-routes.js";
12
- import {
13
- asPartial,
14
- notFound,
15
- parseImportPayload,
16
- parseListQuery,
17
- requiredId,
18
- resourceEvent,
19
- routeContext,
20
- } from "./route-helpers.js";
21
- import type { ResourceRouteConfig, RouteApp } from "./route-types.js";
10
+ import type { RouteApp } from "./route-types.js";
22
11
  import type { SystemRepositories } from "./seed.js";
23
- import { resource, withPermission } from "./resource-route-support.js";
12
+ import { resource } from "./resource-route-support.js";
24
13
  import type { AuthRepository } from "../auth/repository.js";
25
14
  import { identityRouteConfigs } from "./identity-route-config.js";
26
15
  // @windy-module system.audit begin
27
16
  import { registerAuditRoutes } from "./audit-routes.js";
28
17
  // @windy-module system.audit end
29
- import {
30
- accessCall,
31
- importNotSupported,
32
- readAccessible,
33
- resourceConflict,
34
- } from "./resource-access-response.js";
35
- import { runResourceMutation } from "./mutation-runner.js";
36
- import { registerResourceExportRoute } from "./resource-export-route.js";
37
- import {
38
- projectEntity,
39
- projectEntities,
40
- projectImportResult,
41
- } from "./response-projection.js";
42
18
  // @windy-module system.data-governance begin
43
19
  import { registerUserPlaintextRoutes } from "../data-governance/user-plaintext-routes.js";
44
20
  // @windy-module system.data-governance end
45
- import { searchAuditEvent } from "../audit/search-summary.js";
46
21
  import { DepartmentOrganizationPolicy } from "./department-organization.js";
22
+ import { registerResourceRoutes } from "./resource-routes.js";
47
23
 
48
24
  export type { RouteApp } from "./route-types.js";
49
25
 
@@ -144,245 +120,3 @@ export function registerSystemRoutes(
144
120
 
145
121
  return app;
146
122
  }
147
-
148
- function registerResourceRoutes<T extends ManagedEntity>(
149
- app: RouteApp,
150
- config: ResourceRouteConfig<T>,
151
- runtime: Parameters<typeof registerSystemRoutes>[2],
152
- ) {
153
- app.get(`/system/${config.path}`, (rawContext) => {
154
- const { guardContext, query } = routeContext(rawContext);
155
- return withPermission(
156
- runtime,
157
- guardContext,
158
- "GET",
159
- config.permissionResource,
160
- "read",
161
- config.feature,
162
- async () => {
163
- const listQuery = parseListQuery(query);
164
- if (listQuery instanceof Response) return listQuery;
165
- const result = await accessCall(() =>
166
- config.dataAccess.mode === "request"
167
- ? config.dataAccess.access.list(
168
- listQuery,
169
- config.searchFields,
170
- guardContext.actor,
171
- )
172
- : config.repository.list(listQuery, config.searchFields),
173
- );
174
- if (result instanceof Response) return result;
175
- const search = listQuery.q?.trim();
176
- if (search) {
177
- runtime.auditSink.recordEvent(
178
- searchAuditEvent(guardContext, config.path, search, result.total),
179
- );
180
- }
181
- return { ...result, items: projectEntities(config, result.items) };
182
- },
183
- );
184
- });
185
-
186
- if (config.backupExport !== false) {
187
- registerResourceExportRoute(app, config, runtime);
188
- }
189
-
190
- app.get(`/system/${config.path}/:id`, (rawContext) => {
191
- const { guardContext, params } = routeContext(rawContext);
192
- return withPermission(
193
- runtime,
194
- guardContext,
195
- "GET",
196
- config.permissionResource,
197
- "read",
198
- config.feature,
199
- async () => {
200
- const item = await readAccessible(
201
- config,
202
- requiredId(params),
203
- guardContext.actor,
204
- );
205
- if (item instanceof Response) return item;
206
- return item
207
- ? projectEntity(config, item)
208
- : notFound(config.path, requiredId(params));
209
- },
210
- );
211
- });
212
-
213
- if (config.readonly) {
214
- return;
215
- }
216
-
217
- app.post(`/system/${config.path}/import`, (rawContext) => {
218
- const { guardContext, body } = routeContext(rawContext);
219
- return withPermission(
220
- runtime,
221
- guardContext,
222
- "POST",
223
- config.permissionResource,
224
- "manage",
225
- config.feature,
226
- async () => {
227
- if (config.importable === false) {
228
- return importNotSupported();
229
- }
230
- const payload = parseImportPayload<T>(body);
231
- const result = await accessCall(() =>
232
- config.dataAccess.mode === "request"
233
- ? config.dataAccess.access.importMany(
234
- payload.items,
235
- guardContext.actor,
236
- payload.mode,
237
- )
238
- : config.repository.importMany(
239
- payload.items,
240
- guardContext.actor,
241
- payload.mode,
242
- ),
243
- );
244
- if (result instanceof Response) return result;
245
- await recordMutationAudit(
246
- runtime,
247
- config.path,
248
- resourceEvent(guardContext, "data.import", config.path, undefined, {
249
- created: result.created,
250
- updated: result.updated,
251
- skipped: result.skipped,
252
- total: payload.items.length,
253
- }),
254
- );
255
- return {
256
- ...projectImportResult(config, result),
257
- total: payload.items.length,
258
- };
259
- },
260
- );
261
- });
262
-
263
- app.post(`/system/${config.path}`, (rawContext) => {
264
- const { guardContext, body } = routeContext(rawContext);
265
- return withPermission(
266
- runtime,
267
- guardContext,
268
- "POST",
269
- config.permissionResource,
270
- "manage",
271
- config.feature,
272
- async () =>
273
- runResourceMutation(config, async () => {
274
- const input = asPartial<T>(body);
275
- const validationError = await config.validateInput?.(input);
276
- if (validationError) return invalidResource(validationError);
277
- const policyError = await config.beforeCreate?.(input);
278
- if (policyError) return resourceConflict(policyError);
279
- const item = await accessCall(() =>
280
- config.dataAccess.mode === "request"
281
- ? config.dataAccess.access.create(input, guardContext.actor)
282
- : config.repository.create(input, guardContext.actor),
283
- );
284
- if (item instanceof Response) return item;
285
- await recordMutationAudit(
286
- runtime,
287
- config.path,
288
- resourceEvent(guardContext, "crud.create", config.path, item.id),
289
- );
290
- return projectEntity(config, item);
291
- }),
292
- );
293
- });
294
-
295
- app.put(`/system/${config.path}/:id`, (rawContext) => {
296
- const { guardContext, params, body } = routeContext(rawContext);
297
- return withPermission(
298
- runtime,
299
- guardContext,
300
- "PUT",
301
- config.permissionResource,
302
- "manage",
303
- config.feature,
304
- async () =>
305
- runResourceMutation(config, async () => {
306
- const id = requiredId(params);
307
- const input = asPartial<T>(body);
308
- const current = await readAccessible(config, id, guardContext.actor);
309
- if (current instanceof Response) return current;
310
- if (!current) return notFound(config.path, id);
311
- const validationError = await config.validateInput?.(input, current);
312
- if (validationError) return invalidResource(validationError);
313
- const policyError = await config.beforeUpdate?.(current, input);
314
- if (policyError) return resourceConflict(policyError);
315
- const item = await accessCall(() =>
316
- config.dataAccess.mode === "request"
317
- ? config.dataAccess.access.update(id, input, guardContext.actor)
318
- : config.repository.update(id, input, guardContext.actor),
319
- );
320
- if (item instanceof Response) return item;
321
- if (!item) {
322
- return notFound(config.path, id);
323
- }
324
- await config.afterUpdate?.(current, item, guardContext.actor);
325
- await recordMutationAudit(
326
- runtime,
327
- config.path,
328
- resourceEvent(guardContext, "crud.update", config.path, id),
329
- );
330
- return projectEntity(config, item);
331
- }),
332
- );
333
- });
334
-
335
- app.delete(`/system/${config.path}/:id`, (rawContext) => {
336
- const { guardContext, params } = routeContext(rawContext);
337
- return withPermission(
338
- runtime,
339
- guardContext,
340
- "DELETE",
341
- config.permissionResource,
342
- "manage",
343
- config.feature,
344
- async () =>
345
- runResourceMutation(config, async () => {
346
- const id = requiredId(params);
347
- const current = await readAccessible(config, id, guardContext.actor);
348
- if (current instanceof Response) return current;
349
- if (!current) return notFound(config.path, id);
350
- const policyError = await config.beforeDelete?.(current);
351
- if (policyError) return resourceConflict(policyError);
352
- const item = await accessCall(() =>
353
- config.dataAccess.mode === "request"
354
- ? config.dataAccess.access.softDelete(id, guardContext.actor)
355
- : config.repository.softDelete(id, guardContext.actor),
356
- );
357
- if (item instanceof Response) return item;
358
- if (!item) {
359
- return notFound(config.path, id);
360
- }
361
- await config.afterDelete?.(item, guardContext.actor);
362
- await recordMutationAudit(
363
- runtime,
364
- config.path,
365
- resourceEvent(guardContext, "crud.delete", config.path, id),
366
- );
367
- return projectEntity(config, item);
368
- }),
369
- );
370
- });
371
- }
372
-
373
- function invalidResource(
374
- error: NonNullable<
375
- Awaited<
376
- ReturnType<
377
- NonNullable<ResourceRouteConfig<ManagedEntity>["validateInput"]>
378
- >
379
- >
380
- >,
381
- ): Response {
382
- return Response.json(
383
- typeof error === "string"
384
- ? { error: "INVALID_RESOURCE", message: error }
385
- : error,
386
- { status: 400 },
387
- );
388
- }
@@ -0,0 +1,21 @@
1
+ <script setup lang="ts">
2
+ import { Button } from "@/components/ui/button";
3
+
4
+ defineProps<{
5
+ error: string;
6
+ }>();
7
+
8
+ const emit = defineEmits<{
9
+ retry: [];
10
+ }>();
11
+ </script>
12
+
13
+ <template>
14
+ <div
15
+ class="flex flex-col gap-3 rounded-md border border-destructive/30 bg-destructive/5 p-4 text-sm"
16
+ >
17
+ <p class="font-medium text-destructive">加载失败</p>
18
+ <p class="text-muted-foreground">{{ error }}</p>
19
+ <Button variant="outline" class="w-fit" @click="emit('retry')">重试</Button>
20
+ </div>
21
+ </template>
@@ -0,0 +1,17 @@
1
+ import { describe, expect, test } from "vitest";
2
+ import { mount } from "@vue/test-utils";
3
+ import SystemResourceLoadError from "./SystemResourceLoadError.vue";
4
+
5
+ describe("SystemResourceLoadError", () => {
6
+ test("展示加载错误并通过显式事件请求重试", async () => {
7
+ const wrapper = mount(SystemResourceLoadError, {
8
+ props: { error: "服务暂不可用" },
9
+ });
10
+
11
+ expect(wrapper.text()).toContain("加载失败");
12
+ expect(wrapper.text()).toContain("服务暂不可用");
13
+ await wrapper.get("button").trigger("click");
14
+
15
+ expect(wrapper.emitted("retry")).toHaveLength(1);
16
+ });
17
+ });
@@ -3,7 +3,6 @@ import { computed, onMounted, shallowRef } from "vue";
3
3
  import type { PermissionDefinition } from "@southwind-ai/shared";
4
4
  import ConfirmDialog from "@/components/common/ConfirmDialog.vue";
5
5
  import DataPagination from "@/components/common/DataPagination.vue";
6
- import { Button } from "@/components/ui/button";
7
6
  import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
8
7
  import type { SystemRecord } from "@/services/system-api";
9
8
  import { useSystemResource } from "../composables/useSystemResource";
@@ -21,6 +20,7 @@ import DepartmentTransitionDialog from "./department/DepartmentTransitionDialog.
21
20
  import { useDepartmentTransitions } from "../composables/useDepartmentTransitions";
22
21
  import SystemResourceActions from "./SystemResourceActions.vue";
23
22
  import SystemResourceEditor from "./SystemResourceEditor.vue";
23
+ import SystemResourceLoadError from "./SystemResourceLoadError.vue";
24
24
  // @windy-module system.audit begin
25
25
  import AuditLogDetailDialog from "./AuditLogDetailDialog.vue";
26
26
  // @windy-module system.audit end
@@ -248,16 +248,11 @@ async function importData(file: File) {
248
248
  />
249
249
  </CardHeader>
250
250
  <CardContent class="space-y-4">
251
- <div
251
+ <SystemResourceLoadError
252
252
  v-if="resource.error.value"
253
- class="flex flex-col gap-3 rounded-md border border-destructive/30 bg-destructive/5 p-4 text-sm"
254
- >
255
- <p class="font-medium text-destructive">加载失败</p>
256
- <p class="text-muted-foreground">{{ resource.error.value }}</p>
257
- <Button variant="outline" class="w-fit" @click="resource.reload"
258
- >重试</Button
259
- >
260
- </div>
253
+ :error="resource.error.value"
254
+ @retry="resource.reload"
255
+ />
261
256
  <SystemResourceTable
262
257
  v-else
263
258
  :columns="config.columns"
@@ -0,0 +1,107 @@
1
+ # MCP 业务能力暴露
2
+
3
+ ## 决策
4
+
5
+ Windy 以 Module Manifest、Server SDK Handler 和 `GovernedToolRegistry` 组成业务能力深
6
+ 模块。MCP、原生 Agent Tool Route 和 Agent Runtime Tool Host 都是该 Interface 的
7
+ Adapter,不拥有业务能力、权限或审计定义。
8
+
9
+ ```text
10
+ 业务 Module
11
+ ├── Manifest:Schema / Tool / Feature / Permission / Audit
12
+ └── Handler:类型化 JSON 实现
13
+ │
14
+ ▼
15
+ GovernedToolRegistry
16
+ ├── Schema 与大小限制
17
+ ├── License / Feature / RBAC / Data Scope
18
+ ├── Timeout / Audit
19
+ └── 结构化结果
20
+ │
21
+ ┌───┴───────────┐
22
+ ▼ ▼
23
+ Agent Tool Route MCP Adapter
24
+ ```
25
+
26
+ 删除 Registry 后,输入输出校验、Guard、Scope、超时和审计复杂度会重新散落到多个调用面;
27
+ 因此该模块承担了真实的平台复杂度,而不是协议转发层。
28
+
29
+ ## 为什么不直接包装 OpenAPI
30
+
31
+ HTTP Route 面向确定性客户端,参数经常包含分页、传输和页面交互细节。MCP Tool 面向模型
32
+ 选择和结构化调用,需要更小、语义明确、风险可治理的 Interface。自动包装所有 OpenAPI
33
+ 会导致:
34
+
35
+ - 模型看到大量浅层 CRUD;
36
+ - 页面传输参数成为模型契约;
37
+ - 写操作缺少幂等和审批语义;
38
+ - Route、Tool、权限和审计产生多套漂移定义。
39
+
40
+ 因此业务模块显式选择哪些能力成为 Tool;OpenAPI 和 MCP 可以调用同一业务 Service,
41
+ 但不会相互自动推导。
42
+
43
+ ## MCP Transport
44
+
45
+ 远程接入已采用官方 MCP TypeScript SDK `1.29.0` 和无状态 Streamable HTTP,Endpoint
46
+ 为 `/api/mcp`。v2 在当前实施时仍为预发布,不进入生产依赖。无状态模式适合当前只读
47
+ 请求,也避免把授权快照保存为长期 Session 状态。
48
+
49
+ MCP Endpoint 必须位于现有 Server 身份解析之后:
50
+
51
+ - Bearer/Cookie 只用于解析当前 Server Actor,入口另需通过 `system.agent` Feature 与
52
+ `system.agent.read` Permission;
53
+ - `tools/list` 只返回当前 Actor 获准的只读 Tool;
54
+ - `tools/call` 每次重新经过 Registry,不复用列表阶段的授权结论;
55
+ - 客户端参数不能提供 Permission、Feature、License、Actor 或 Scope;
56
+ - MCP 协议错误与业务 Guard 错误使用稳定、脱敏的错误信息。
57
+
58
+ ## Tool、Resource 与 Skill
59
+
60
+ 当前只提供 Tool:
61
+
62
+ - Tool 对应可执行的类型化业务查询;
63
+ - Resource 等出现明确的只读文档或静态能力目录消费场景后再增加;
64
+ - Prompt 不作为业务规则来源。
65
+
66
+ Skill 解决开发流程,不属于运行时授权 Interface。仓库内开发 Skill 指导 Agent:
67
+
68
+ 1. 创建业务模块和 Manifest;
69
+ 2. 注册 Feature、Permission、Audit 与 JSON Schema;
70
+ 3. 实现 Repository、Service 和 Tool Handler;
71
+ 4. 增加 Composition、Guard、Data Scope 与 Schema 测试;
72
+ 5. 使用脚手架和真实生成回归验证。
73
+
74
+ Skill 内容可以从稳定契约和模板派生,但不能独立声明 Tool Key 或授权事实。
75
+ `.agents/skills/windy-business-module/SKILL.md` 随 Starter 一起分发,并指向 Manifest、
76
+ Module Host 和 Tool Handler 的代码事实源。生成组合未包含 `system.agent` 时,Skill
77
+ 要求明确物理边界,不在业务层另建 MCP 或授权实现。
78
+
79
+ ## 写操作门槛
80
+
81
+ MCP Adapter 只暴露 `operation=query`。开放 `command/background` 前必须完成:
82
+
83
+ - 强制幂等键及结果重放语义;
84
+ - 风险分级和人工审批;
85
+ - Durable Job/结果状态引用;
86
+ - Actor、请求、审批和业务副作用的完整关联;
87
+ - 超时后真实副作用是否仍继续的明确语义;
88
+ - 重试、取消和补偿回归。
89
+
90
+ 在这些条件满足前,Manifest 可以预先描述写能力,但 MCP Adapter 必须过滤它们。
91
+
92
+ ## 运维与验证
93
+
94
+ - Server 启动前校验声明、Schema 和 Handler 一一对应。
95
+ - MCP 不可用不能影响普通业务 Route 和非 AI 能力启动。
96
+ - 审计只记录 Tool Key、Actor、Request ID、输入输出字节数和结果,不保存原始内容。
97
+ - 回归至少覆盖未授权不可发现、直接调用重新授权、Schema 拒绝、数据范围不可扩大和
98
+ Handler 输出漂移。
99
+ - MCP API Path、Server 身份、Route 与 Manifest Binding 使用
100
+ `packages/shared/src/mcp.ts` 的共享 Contract;生成 README 通过跨层测试校对该路径。
101
+ - Tool 目录从当前 Module Manifest 组合,不在 README 或 Skill 固化。新模板通过发布时
102
+ `template:sync` 更新;既有客户项目只通过显式 `windy update` 合并,不远程静默改写。
103
+
104
+ 官方协议与 SDK 资料:
105
+
106
+ - [MCP TypeScript SDK v1](https://ts.sdk.modelcontextprotocol.io/)
107
+ - [MCP Server 与 Streamable HTTP](https://ts.sdk.modelcontextprotocol.io/server)
@@ -78,9 +78,9 @@ composable。
78
78
  先提交当前业务改动并保持 Git 工作区干净,然后执行:
79
79
 
80
80
  ```bash
81
- bunx --bun create-windy@0.3.0 update --check --cwd .
82
- bunx --bun create-windy@0.3.0 update --dry-run --cwd .
83
- bunx --bun create-windy@0.3.0 update --cwd .
81
+ bunx --bun create-windy@0.3.1 update --check --cwd .
82
+ bunx --bun create-windy@0.3.1 update --dry-run --cwd .
83
+ bunx --bun create-windy@0.3.1 update --cwd .
84
84
  bun install
85
85
  ```
86
86
 
@@ -212,14 +212,14 @@ SSE 事件包括 `output.delta`、`tool.requested`、`tool.completed`、usage,
212
212
  `run.completed`、`run.failed` 或 `run.cancelled` 终态。断线后使用已确认 sequence 作为
213
213
  cursor 恢复;不要重新创建 Run 或重复执行 Tool。
214
214
 
215
- ## 从 create-windy 0.2.34 升级到 0.3.0
215
+ ## 从 create-windy 0.2.34 升级到 0.3.1
216
216
 
217
- `0.2.34 → 0.3.0` 使用连续 recipe,不需要逐个安装中间 CLI。确认 npm registry 已能
218
- 解析精确的 `0.3.0` artifact 后,先检查计划,再应用:
217
+ 目标 CLI 会一次解析 `0.2.34 → 0.3.0 → 0.3.1` 连续 recipe,不需要逐个安装中间
218
+ CLI。确认 npm registry 已能解析精确的 `0.3.1` artifact 后,先检查计划,再应用:
219
219
 
220
220
  ```bash
221
- bunx --bun create-windy@0.3.0 update --check
222
- bunx --bun create-windy@0.3.0 update
221
+ bunx --bun create-windy@0.3.1 update --check
222
+ bunx --bun create-windy@0.3.1 update
223
223
  bun install
224
224
  ```
225
225
 
@@ -94,6 +94,10 @@ Transport 返回协议允许的状态。
94
94
  http://localhost:9747/api/mcp
95
95
  ```
96
96
 
97
+ 普通 Server 已提供该 Endpoint,不需要启动 `dev:agent`。非浏览器 MCP 客户端先通过
98
+ `POST /api/auth/api/login` 获取 Bearer Token,再调用 `tools/list` 获取当前账号实时可见
99
+ 的 Tool 与 JSON Schema;客户端和文档都不应固化 Tool 清单。
100
+
97
101
  MCP 使用无状态模式,不保存授权快照或长期 Session。Feature、角色或数据范围变化会在下一次
98
102
  调用立即生效。
99
103
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@southwind-ai/modules",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "license": "UNLICENSED",
5
5
  "type": "module",
6
6
  "engines": {