@xiongtao.xt/tau-plugins 0.10.0 → 0.12.0

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.
package/MCP.md ADDED
@@ -0,0 +1,89 @@
1
+ # 插件 MCP
2
+
3
+ 通过 `context.mcp.register()` 向 Tau 的 AI 提供工具,一个插件可以注册多个 MCP。它们直接出现在已有 MCP 页面和聊天快捷面板,用户可以整项启停、逐工具启停,不需要插件自行开发管理界面。
4
+
5
+ 需要 SDK 0.11.0 和包含此能力的 Tau 0.59.0 桌面客户端构建。旧客户端没有 `context.mcp`,应检查并明确提示升级;SDK 类型版本不能替代客户端能力检查。
6
+
7
+ ## 本地工具和外部服务器
8
+
9
+ 每次注册填写局部 `id`、`name` 和可选 `description`,再提供 `tools` 或 `transport`,两者互斥。相同插件内 MCP 的 ID 不能重复;不同插件可以使用相同局部 ID。每个 MCP 中的工具名称唯一。
10
+
11
+ 下面是一个完整插件示例。第一项提供本地查询函数;第二项连接外部 MCP,示例地址须替换成真实服务。
12
+
13
+ ```ts
14
+ import { definePlugin } from "@tau/plugins";
15
+
16
+ declare const __PLUGIN_META__: { id: string; name: string; version: string };
17
+
18
+ export default definePlugin({
19
+ ...__PLUGIN_META__,
20
+ create: () => ({
21
+ activate(context) {
22
+ if (!context.mcp) throw new Error("请升级到支持插件 MCP 的 Tau 客户端");
23
+
24
+ context.mcp.register({
25
+ id: "notes",
26
+ name: "我的笔记",
27
+ description: "读取本插件保存的笔记",
28
+ tools: [
29
+ {
30
+ name: "read_note",
31
+ description: "按笔记键读取本插件中的一条笔记",
32
+ inputSchema: {
33
+ type: "object",
34
+ properties: { key: { type: "string", description: "笔记键" } },
35
+ required: ["key"],
36
+ additionalProperties: false,
37
+ },
38
+ async execute(args, { signal }) {
39
+ signal.throwIfAborted();
40
+ if (typeof args.key !== "string") throw new Error("请提供笔记键");
41
+ const note = await context.storage.get(args.key);
42
+ signal.throwIfAborted();
43
+ return { content: note === undefined ? "未找到笔记" : JSON.stringify(note) };
44
+ },
45
+ },
46
+ ],
47
+ });
48
+
49
+ context.mcp.register({
50
+ id: "remote",
51
+ name: "外部知识服务",
52
+ transport: { type: "streamable-http", url: "https://your-service.example/mcp" },
53
+ });
54
+ },
55
+ }),
56
+ });
57
+ ```
58
+
59
+ 本地工具填写 `name`、`description`、对象类型的 JSON Schema `inputSchema`,以及 `execute(args, { signal })`。执行结果为 `{ content: string }`,结构化数据可用 JSON.stringify 返回。工具名称使用 1–128 位字母、数字、点、横线或下划线;宿主会生成全局工具名称,作者不要自行拼接命名空间。所有插件工具沿用现有 execute 级审批。
60
+
61
+ 需要本地进程时,把 transport 改为下面的结构,命令、参数、工作目录与应用内手动添加 MCP 的含义相同:
62
+
63
+ ```ts
64
+ context.mcp.register({
65
+ id: "local-server",
66
+ name: "本地 MCP 服务",
67
+ transport: {
68
+ type: "stdio",
69
+ command: "node",
70
+ args: ["/path/to/your-server.mjs"],
71
+ },
72
+ });
73
+ ```
74
+
75
+ 该片段放在 activate(context) 内,路径须替换。不会自动安装 Node、Python、npm 包或其他依赖,启动前须具备对应运行环境。stdio 支持可选 args、env、cwd;streamable-http 支持可选 headers。不要把密钥、令牌或用户机器路径打进分发包,用户可在 MCP 页面配置本机连接参数。
76
+
77
+ ## 管理和生命周期
78
+
79
+ - 激活成功后才展示和提供工具;重复启用不会重复登记。激活失败、插件停用、切库或卸载自动回收。
80
+ - MCP 页面关闭一项只关闭该项 AI 能力,不停用插件的页面、命令或其他 MCP。用户选择会跨启用和重启保留,重新注册不会强行开启。
81
+ - 用户可管理每个工具的开关。插件提供的名称和说明由作者维护;外部服务器支持连接配置、日志和重连,插件函数没有连接配置。
82
+ - 返回的 Disposable 可提前注销单项 MCP,无需再交给 context.register。
83
+ - 工具调用 signal 在取消、插件停用、整项或对应工具关闭时中止,调用上限为 60 秒。异步代码应响应 signal;取消不能撤销已经完成的写入或外部请求。
84
+ - 旧实例、旧数据库和重连前的调用不能转交给新实例。不要缓存上下文到下一次启用。
85
+ - Skill 可描述这些工具的用途;ctx.ai.chat/stream 仍是文本生成接口,不会因为注册 MCP 而自动获得工具调用能力。
86
+
87
+ ## 验证
88
+
89
+ 检查类型并打包后,在包含此实现的客户端启用插件:确认两项 MCP 出现在现有页面和快捷面板;测试整项开关、逐工具开关、外部连接配置和重连,再测试停用、重新启用及重启后偏好保留。真实服务器连接与模型调用需使用自己的服务和已配置的模型。
package/UPLOAD.md ADDED
@@ -0,0 +1,33 @@
1
+ # 资源上传插件
2
+
3
+ 需要 SDK 0.12.0 和包含资源上传插件能力的 Tau 0.59.0 客户端。上传返回正文可直接引用的 HTTP(S) 地址;数据库和资源目录同步使用 SYNC.md 的另一套接口。
4
+
5
+ 在 activate 中注册提供方:
6
+
7
+ ```tsx
8
+ context.upload.register({
9
+ id: context.pluginId,
10
+ name: "我的资源存储",
11
+ async upload(file, config) {
12
+ // 用 context.fetch 请求服务商,认证与路径由本次 config 决定。
13
+ // file.name 已唯一命名,file.mimeType 是 MIME,file.content 为 Uint8Array。
14
+ return uploadToStorage(context.fetch, file, config);
15
+ },
16
+ });
17
+ context.settings.register({
18
+ id: "upload",
19
+ title: "我的资源存储",
20
+ target: "upload",
21
+ component: UploadSettings,
22
+ });
23
+ ```
24
+
25
+ UploadSettings 接收 PluginSettingsProps。读取 uploadConfig.value,在用户保存时调用 await uploadConfig.save(config),宿主保存并选中该提供方;不要将同一份上传配置再存到 context.storage。配置是 JsonValue,读取时自行校验字段并给出错误提示。组件在资源存储设置页显示;插件详情页提供跳转入口。
26
+
27
+ 每个插件只能注册一个上传提供方,id 必须等于 context.pluginId。宿主负责压缩、文件唯一命名、本地保存和跨窗口转发。插件只负责远端上传和返回资源地址,不读取用户文件路径,也不修改笔记内容。
28
+
29
+ 请求通过 context.fetch 发送,失败抛出错误,不返回空地址或吞掉失败。停用插件会取消上下文请求并拒绝迟到结果。插件在渲染进程执行,不使用 Node 模块或额外后台入口。
30
+
31
+ 返回地址必须长期可引用;不要返回短期签名 URL。桶或仓库的公开读取策略由用户配置,上传成功不等于匿名用户可以访问。不要自动修改用户桶权限。需要自定义公开域名时由插件设置提供,按对象路径返回完整 URL。
32
+
33
+ 完成后验证:本地模式不调用插件、远端上传成功及失败、保存配置、停用、独立窗口上传、重启恢复。README.md 应说明如何配置公开访问和常见服务商错误。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xiongtao.xt/tau-plugins",
3
- "version": "0.10.0",
3
+ "version": "0.12.0",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -8,8 +8,10 @@ import type { PluginEditorExtension } from "./editor";
8
8
  import type { MarkdownCodec } from "../../markdown/src/index";
9
9
  import type { PluginSettings } from "./settings";
10
10
  import type { PluginSync } from "./sync";
11
+ import type { PluginUpload } from "./upload";
11
12
  import type { PluginSidebar } from "./sidebar";
12
13
  import type { PluginExcerpts, PluginMaterials } from "./excerpts";
14
+ import type { PluginMCP } from "./mcp";
13
15
  import type { JsonValue } from "./json";
14
16
  export type { JsonValue } from "./json";
15
17
  export interface Disposable {
@@ -38,12 +40,14 @@ export interface PluginContext {
38
40
  readonly editor: PluginEditor;
39
41
  readonly settings: PluginSettings;
40
42
  readonly sync: PluginSync;
43
+ readonly upload: PluginUpload;
41
44
  readonly sidebar: PluginSidebar;
42
45
  readonly excerpts: PluginExcerpts;
43
46
  readonly materials: PluginMaterials;
44
47
  readonly fetch: typeof globalThis.fetch;
45
48
  readonly services: PluginServices;
46
49
  readonly ai: PluginAI;
50
+ readonly mcp: PluginMCP;
47
51
  /** 将订阅等资源交给宿主,在停用或激活失败时逆序回收。 */
48
52
  register(resource: Disposable): Disposable;
49
53
  }
@@ -1,4 +1,5 @@
1
1
  import type { PluginDefinition } from "./contract";
2
+ export type * from "./upload";
2
3
  export type { JsonValue, Disposable, PluginStorage, PluginCommand, PluginContext, PluginInstance, PluginDefinition, } from "./contract";
3
4
  export type { PluginSettings, PluginSettingsDefinition, PluginSettingsProps, PluginSettingsHandle, } from "./settings";
4
5
  export * from "./editor";
@@ -8,6 +9,7 @@ export * from "./sidebar";
8
9
  export type * from "./excerpts";
9
10
  export type * from "./services";
10
11
  export type { PluginSkill } from "./skills";
12
+ export type { PluginMCP, PluginMCPDefinition, PluginMCPTool, PluginMCPTransport } from "./mcp";
11
13
  export type { PluginPageDefinition, PluginPageHandle, PluginPageParams, PluginPageProps, PluginPages, } from "./pages";
12
14
  export declare function definePlugin(definition: Omit<PluginDefinition, "apiVersion" | "create"> & Partial<Pick<PluginDefinition, "create">>): PluginDefinition;
13
15
  export type { PluginAI, PluginAIFeature, PluginAIRequest, PluginAIResult, PluginAIEvent, } from "./ai";
@@ -0,0 +1,45 @@
1
+ import type { Disposable } from "./contract";
2
+ import type { JsonValue } from "./json";
3
+ export type PluginMCPTransport = {
4
+ type: "stdio";
5
+ command: string;
6
+ args?: string[];
7
+ env?: Record<string, string>;
8
+ cwd?: string;
9
+ } | {
10
+ type: "streamable-http";
11
+ url: string;
12
+ headers?: Record<string, string>;
13
+ };
14
+ export interface PluginMCPTool {
15
+ name: string;
16
+ description: string;
17
+ inputSchema: {
18
+ type: "object";
19
+ properties?: Record<string, JsonValue>;
20
+ [key: string]: JsonValue | undefined;
21
+ };
22
+ execute(args: Record<string, JsonValue>, context: {
23
+ signal: AbortSignal;
24
+ }): Promise<{
25
+ content: string;
26
+ }> | {
27
+ content: string;
28
+ };
29
+ }
30
+ export type PluginMCPDefinition = {
31
+ id: string;
32
+ name: string;
33
+ description?: string;
34
+ } & ({
35
+ tools: readonly PluginMCPTool[];
36
+ transport?: never;
37
+ } | {
38
+ transport: PluginMCPTransport;
39
+ tools?: never;
40
+ });
41
+ export interface PluginMCP {
42
+ /** 同一插件可注册多个 MCP;停用时由宿主自动回收。 */
43
+ register(definition: PluginMCPDefinition): Disposable;
44
+ }
45
+ export declare function snapshotMCPDefinition(definition: PluginMCPDefinition): PluginMCPDefinition;
@@ -3,6 +3,10 @@ import type { Disposable, PluginContext } from "./index";
3
3
  import type { JsonValue } from "./json";
4
4
  export interface PluginSettingsProps {
5
5
  readonly context: PluginContext;
6
+ readonly uploadConfig?: {
7
+ readonly value: JsonValue;
8
+ save(value: JsonValue): Promise<void>;
9
+ };
6
10
  readonly syncConfig?: {
7
11
  readonly value: JsonValue;
8
12
  save(value: JsonValue): Promise<void>;
@@ -13,7 +17,7 @@ export interface PluginSettingsDefinition {
13
17
  title: string;
14
18
  description?: string;
15
19
  /** 同步提供方的配置表单挂载到宿主同步设置。 */
16
- target?: "sync";
20
+ target?: "sync" | "upload";
17
21
  component: ComponentType<PluginSettingsProps>;
18
22
  }
19
23
  export interface PluginSettingsHandle extends Disposable {
@@ -0,0 +1,16 @@
1
+ import type { Disposable, JsonValue } from "./contract";
2
+ export interface UploadFile {
3
+ /** 宿主已完成压缩和唯一命名,插件只负责追加远端路径前缀。 */
4
+ name: string;
5
+ mimeType: string;
6
+ content: Uint8Array;
7
+ }
8
+ export interface UploadProviderDefinition {
9
+ id: string;
10
+ name: string;
11
+ /** 返回可供正文直接引用的HTTP(S)地址,不返回临时签名URL。 */
12
+ upload(file: UploadFile, config: JsonValue): Promise<string>;
13
+ }
14
+ export interface PluginUpload {
15
+ register(definition: UploadProviderDefinition): Disposable;
16
+ }