@rezti/dsh-rez-suite 0.1.26 → 0.1.27

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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @rezti/dsh-rez-suite
2
2
 
3
- 雷泽插件的**唯一安装入口**。Odoo / 企微 / HA / TAPD / GSC / 工作区(老板三室 + 全员助理)都由这一包拉齐,不要再逐个 `plugin add`,**不要**再装 `@rezti/dsh-rez-boss-desk`。
3
+ 雷泽插件的**唯一安装入口**。Odoo / 企微 / HA / TAPD / GSC / 天眼查(仅老板) / 工作区(老板三室 + 全员助理)都由这一包拉齐,不要再逐个 `plugin add`,**不要**再装 `@rezti/dsh-rez-boss-desk`。
4
4
 
5
5
  设置里选身份后,左侧自己出现对应房间(全员三助理;老板另加搞钱/产品/运营),不必手动添加工作区。
6
6
 
package/lib/client.d.ts CHANGED
@@ -7300,6 +7300,9 @@ declare const zh: {
7300
7300
  readonly 'config.gsc': "Google Search Console";
7301
7301
  readonly 'config.gsc.siteUrl': "站点 URL";
7302
7302
  readonly 'config.gsc.key': "服务账号 JSON 或密钥文件路径";
7303
+ readonly 'config.tianyancha': "天眼查(法务助理 · 仅老板)";
7304
+ readonly 'config.tianyancha.url': "天眼查 MCP URL";
7305
+ readonly 'config.tianyancha.token': "OpenAPI Token";
7303
7306
  readonly 'config.fs': "本地文件 (fs-mcp-server)";
7304
7307
  readonly 'config.fs.root': "根目录";
7305
7308
  readonly 'config.sqlite': "SQLite (sqlite-mcp-server)";
package/lib/client.js CHANGED
@@ -61,6 +61,9 @@ const zh = {
61
61
  "config.gsc": "Google Search Console",
62
62
  "config.gsc.siteUrl": "站点 URL",
63
63
  "config.gsc.key": "服务账号 JSON 或密钥文件路径",
64
+ "config.tianyancha": "天眼查(法务助理 · 仅老板)",
65
+ "config.tianyancha.url": "天眼查 MCP URL",
66
+ "config.tianyancha.token": "OpenAPI Token",
64
67
  "config.fs": "本地文件 (fs-mcp-server)",
65
68
  "config.fs.root": "根目录",
66
69
  "config.sqlite": "SQLite (sqlite-mcp-server)",
@@ -174,6 +177,9 @@ const en = {
174
177
  "config.gsc": "Google Search Console",
175
178
  "config.gsc.siteUrl": "Site URL",
176
179
  "config.gsc.key": "Service-account JSON or key file path",
180
+ "config.tianyancha": "Tianyancha (Legal assistant, boss only)",
181
+ "config.tianyancha.url": "Tianyancha MCP URL",
182
+ "config.tianyancha.token": "OpenAPI token",
177
183
  "config.fs": "Local files (fs-mcp-server)",
178
184
  "config.fs.root": "Root directory",
179
185
  "config.sqlite": "SQLite (sqlite-mcp-server)",
@@ -581,7 +587,8 @@ const EMPTY_SECRETS = {
581
587
  wechat: "",
582
588
  homeassistant: "",
583
589
  tapd: "",
584
- gsc: ""
590
+ gsc: "",
591
+ tianyancha: ""
585
592
  };
586
593
  function Field({ label, children }) {
587
594
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -745,6 +752,10 @@ function ConfigTab({ api }) {
745
752
  gsc: {
746
753
  ...config.connections.gsc,
747
754
  secret: secrets.gsc || void 0
755
+ },
756
+ tianyancha: {
757
+ ...config.connections.tianyancha,
758
+ secret: secrets.tianyancha || void 0
748
759
  }
749
760
  }
750
761
  };
@@ -950,6 +961,25 @@ function ConfigTab({ api }) {
950
961
  },
951
962
  secretLabel: tt("config.gsc.key")
952
963
  }),
964
+ config.role === "boss" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(McpSection, {
965
+ title: tt("config.tianyancha"),
966
+ row: config.connections.tianyancha,
967
+ secret: secrets.tianyancha,
968
+ onEnabled: (enabled) => {
969
+ patchConnection("tianyancha", { enabled });
970
+ },
971
+ onUrl: (url) => {
972
+ patchConnection("tianyancha", { url });
973
+ },
974
+ onSecret: (value) => {
975
+ setSecrets((prev) => ({
976
+ ...prev,
977
+ tianyancha: value
978
+ }));
979
+ },
980
+ urlLabel: tt("config.tianyancha.url"),
981
+ secretLabel: tt("config.tianyancha.token")
982
+ }),
953
983
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Section, {
954
984
  title: tt("config.billing"),
955
985
  children: [
package/lib/index.d.ts CHANGED
@@ -1730,6 +1730,10 @@ interface RezConnections {
1730
1730
  enabled: boolean;
1731
1731
  siteUrl: string;
1732
1732
  };
1733
+ tianyancha: {
1734
+ enabled: boolean;
1735
+ url: string;
1736
+ };
1733
1737
  }
1734
1738
  /** Full plugin configuration persisted under ~/.dsh/dsh-rez-suite.json. */
1735
1739
  interface RezConfig {
package/lib/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { createRequire } from "node:module";
2
2
  import { homedir } from "node:os";
3
3
  import { dirname, join, relative } from "node:path";
4
- import { REZ_CREDENTIAL_REFS, REZ_DEFAULT_CONNECTIONS, REZ_SYSTEMS, lastMcpStartError, mergeConnections, secretConfigured, writeManagedCredential } from "@rezti/dsh-rez-sso";
4
+ import { REZ_CREDENTIAL_REFS, REZ_DEFAULT_CONNECTIONS, REZ_SYSTEMS, lastMcpStartError, loadRezConnections, mergeConnections, secretConfigured, startMcpWhenSecret, writeManagedCredential } from "@rezti/dsh-rez-sso";
5
5
  import { existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, statSync, writeFileSync } from "node:fs";
6
6
  import { DatabaseSync } from "node:sqlite";
7
7
  import { fileURLToPath } from "node:url";
@@ -2858,7 +2858,8 @@ const COMPOSED_SERVERS = [
2858
2858
  "wechat",
2859
2859
  "homeassistant",
2860
2860
  "tapd",
2861
- "gsc"
2861
+ "gsc",
2862
+ "tianyancha"
2862
2863
  ];
2863
2864
  function mcpToolPrefix(server) {
2864
2865
  return "mcp__" + server + "__";
@@ -2920,7 +2921,7 @@ function observeServer(server, input) {
2920
2921
  };
2921
2922
  }
2922
2923
  function observeComposedServers(input) {
2923
- return COMPOSED_SERVERS.map((server) => observeServer(server, input));
2924
+ return (input.config.role === "boss" ? COMPOSED_SERVERS : COMPOSED_SERVERS.filter((server) => server !== "tianyancha")).map((server) => observeServer(server, input));
2924
2925
  }
2925
2926
  function testComposedServers(input) {
2926
2927
  return observeComposedServers(input).map((row) => {
@@ -3049,6 +3050,12 @@ function publicConfig(config) {
3049
3050
  siteUrl: connections.gsc.siteUrl,
3050
3051
  secretConfigured: secretConfigured("gsc"),
3051
3052
  secretRef: REZ_CREDENTIAL_REFS.gsc
3053
+ },
3054
+ tianyancha: {
3055
+ enabled: connections.tianyancha.enabled,
3056
+ url: connections.tianyancha.url,
3057
+ secretConfigured: secretConfigured("tianyancha"),
3058
+ secretRef: REZ_CREDENTIAL_REFS.tianyancha
3052
3059
  }
3053
3060
  },
3054
3061
  billing: { ...config.billing }
@@ -8080,6 +8087,65 @@ function liveSelfUpdate() {
8080
8087
  });
8081
8088
  }
8082
8089
  //#endregion
8090
+ //#region src/tianyancha.ts
8091
+ const TIANYANCHA_MCP_URL = "https://mcp.tianyancha.com/mcp";
8092
+ function shouldStartTianyancha(role) {
8093
+ return role === "boss";
8094
+ }
8095
+ function bearerAuthorization(secret) {
8096
+ const trimmed = secret.trim();
8097
+ if (/^bearer\s+/i.test(trimmed)) return "Bearer " + trimmed.replace(/^bearer\s+/i, "");
8098
+ return "Bearer " + trimmed;
8099
+ }
8100
+ function tianyanchaMcpConfig(secret) {
8101
+ const { url } = loadRezConnections().tianyancha;
8102
+ return {
8103
+ serverName: "tianyancha",
8104
+ transport: "streamable-http",
8105
+ url: url.length > 0 ? url : TIANYANCHA_MCP_URL,
8106
+ headers: { Authorization: bearerAuthorization(secret) },
8107
+ failOnStartupError: false
8108
+ };
8109
+ }
8110
+ /** Start when role is boss; dispose the mcp-client fiber when the identity leaves. */
8111
+ function mountTianyanchaMcp(ctx, getRole, start = startMcpWhenSecret) {
8112
+ let session;
8113
+ let pending = false;
8114
+ const ensure = () => {
8115
+ (async () => {
8116
+ if (!shouldStartTianyancha(getRole())) {
8117
+ session?.();
8118
+ session = void 0;
8119
+ return;
8120
+ }
8121
+ if (session !== void 0 || pending) return;
8122
+ pending = true;
8123
+ let raced = false;
8124
+ try {
8125
+ const dispose = await start(ctx, {
8126
+ system: "tianyancha",
8127
+ label: "天眼查",
8128
+ config: tianyanchaMcpConfig
8129
+ });
8130
+ if (!shouldStartTianyancha(getRole())) {
8131
+ raced = true;
8132
+ dispose();
8133
+ return;
8134
+ }
8135
+ session = dispose;
8136
+ } catch (error) {
8137
+ const why = error instanceof Error ? error.message : String(error);
8138
+ console.error("[dsh-rez-suite] tianyancha MCP start failed:", why);
8139
+ } finally {
8140
+ pending = false;
8141
+ if (raced && shouldStartTianyancha(getRole()) && session === void 0) ensure();
8142
+ }
8143
+ })();
8144
+ };
8145
+ ensure();
8146
+ return ensure;
8147
+ }
8148
+ //#endregion
8083
8149
  //#region src/index.ts
8084
8150
  const name = "rez-suite";
8085
8151
  const inject = [
@@ -8133,7 +8199,11 @@ const Config = Schema.object({
8133
8199
  gsc: Schema.object({
8134
8200
  enabled: Schema.boolean().default(true),
8135
8201
  siteUrl: Schema.string().default(REZ_DEFAULT_CONNECTIONS.gsc.siteUrl)
8136
- }).default(REZ_DEFAULT_CONNECTIONS.gsc)
8202
+ }).default(REZ_DEFAULT_CONNECTIONS.gsc),
8203
+ tianyancha: Schema.object({
8204
+ enabled: Schema.boolean().default(true),
8205
+ url: Schema.string().default(REZ_DEFAULT_CONNECTIONS.tianyancha.url)
8206
+ }).default(REZ_DEFAULT_CONNECTIONS.tianyancha)
8137
8207
  }).default(REZ_DEFAULT_CONNECTIONS),
8138
8208
  billing: Schema.object({
8139
8209
  inputCostPer1k: Schema.number().default(.001),
@@ -8180,6 +8250,7 @@ function delegatedHost(ctx, getConfig) {
8180
8250
  function apply(ctx, config) {
8181
8251
  let current = () => normalize(config);
8182
8252
  const syncWorkspaces = mountRezWorkspaces(ctx, () => current().role);
8253
+ const syncTianyancha = mountTianyanchaMcp(ctx, () => current().role);
8183
8254
  const tokens = new TokenManager(joinTokenDb());
8184
8255
  const host = delegatedHost(ctx, () => current());
8185
8256
  ctx.effect(() => () => {
@@ -8193,6 +8264,7 @@ function apply(ctx, config) {
8193
8264
  saveConfig(value);
8194
8265
  current = () => value;
8195
8266
  syncWorkspaces();
8267
+ syncTianyancha();
8196
8268
  try {
8197
8269
  const settings = ctx.get("settings");
8198
8270
  if (settings?.update !== void 0) await settings.update(REZ_SETTINGS_NAMESPACE, value);
@@ -8250,10 +8322,12 @@ function apply(ctx, config) {
8250
8322
  current = source;
8251
8323
  sync();
8252
8324
  syncWorkspaces();
8325
+ syncTianyancha();
8253
8326
  },
8254
8327
  onChange: () => {
8255
8328
  sync();
8256
8329
  syncWorkspaces();
8330
+ syncTianyancha();
8257
8331
  }
8258
8332
  });
8259
8333
  sync();
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@rezti/dsh-rez-suite",
3
3
  "description": "ReZ-TI one-package install for DeepSeek Harness. Update with: dsh plugin --profile web update @rezti/dsh-rez-suite",
4
- "version": "0.1.26",
4
+ "version": "0.1.27",
5
5
  "type": "module",
6
6
  "engines": {
7
7
  "node": "^22.19.0 || >=24.0.0"
@@ -39,15 +39,15 @@
39
39
  "dependencies": {
40
40
  "@modelcontextprotocol/sdk": "^1.30.0",
41
41
  "zod": "^4.4.3",
42
- "@rezti/dsh-rez-intent": "0.1.5",
43
42
  "@rezti/dsh-rez-nextcloud": "0.1.9",
44
- "@rezti/dsh-rez-odoo": "0.1.9",
45
- "@rezti/dsh-rez-sso": "0.1.9",
43
+ "@rezti/dsh-rez-gsc": "0.1.0",
44
+ "@rezti/dsh-rez-intent": "0.1.5",
45
+ "@rezti/dsh-rez-ha-bridge": "0.1.9",
46
46
  "@rezti/dsh-rez-tapd": "0.1.0",
47
+ "@rezti/dsh-rez-odoo": "0.1.9",
47
48
  "@rezti/dsh-rez-token-manager": "0.1.4",
48
- "@rezti/dsh-rez-ha-bridge": "0.1.9",
49
49
  "@rezti/dsh-rez-wechat": "0.1.14",
50
- "@rezti/dsh-rez-gsc": "0.1.0"
50
+ "@rezti/dsh-rez-sso": "0.1.10"
51
51
  },
52
52
  "peerDependencies": {
53
53
  "react": "^18.2.0",
@@ -52,6 +52,9 @@ export const zh = {
52
52
  'config.gsc': 'Google Search Console',
53
53
  'config.gsc.siteUrl': '站点 URL',
54
54
  'config.gsc.key': '服务账号 JSON 或密钥文件路径',
55
+ 'config.tianyancha': '天眼查(法务助理 · 仅老板)',
56
+ 'config.tianyancha.url': '天眼查 MCP URL',
57
+ 'config.tianyancha.token': 'OpenAPI Token',
55
58
  'config.fs': '本地文件 (fs-mcp-server)',
56
59
  'config.fs.root': '根目录',
57
60
  'config.sqlite': 'SQLite (sqlite-mcp-server)',
@@ -171,6 +174,9 @@ export const en: Record<RezKey, string> = {
171
174
  'config.gsc': 'Google Search Console',
172
175
  'config.gsc.siteUrl': 'Site URL',
173
176
  'config.gsc.key': 'Service-account JSON or key file path',
177
+ 'config.tianyancha': 'Tianyancha (Legal assistant, boss only)',
178
+ 'config.tianyancha.url': 'Tianyancha MCP URL',
179
+ 'config.tianyancha.token': 'OpenAPI token',
174
180
  'config.fs': 'Local files (fs-mcp-server)',
175
181
  'config.fs.root': 'Root directory',
176
182
  'config.sqlite': 'SQLite (sqlite-mcp-server)',
@@ -24,9 +24,10 @@ type SecretDrafts = {
24
24
  homeassistant: string
25
25
  tapd: string
26
26
  gsc: string
27
+ tianyancha: string
27
28
  }
28
29
 
29
- const EMPTY_SECRETS: SecretDrafts = { odoo: '', nextcloud: '', wechat: '', homeassistant: '', tapd: '', gsc: '' }
30
+ const EMPTY_SECRETS: SecretDrafts = { odoo: '', nextcloud: '', wechat: '', homeassistant: '', tapd: '', gsc: '', tianyancha: '' }
30
31
 
31
32
  function Field({ label, children }: { label: string; children: ReactNode }) {
32
33
  return (
@@ -170,6 +171,7 @@ export function ConfigTab({ api }: { api: RezApi }) {
170
171
  homeassistant: { ...config.connections.homeassistant, secret: secrets.homeassistant || undefined },
171
172
  tapd: { ...config.connections.tapd, secret: secrets.tapd || undefined },
172
173
  gsc: { ...config.connections.gsc, secret: secrets.gsc || undefined },
174
+ tianyancha: { ...config.connections.tianyancha, secret: secrets.tianyancha || undefined },
173
175
  },
174
176
  }
175
177
  const saved = await api.saveConfig(payload)
@@ -290,6 +292,18 @@ export function ConfigTab({ api }: { api: RezApi }) {
290
292
  onSecret={value => { setSecrets(prev => ({ ...prev, gsc: value })) }}
291
293
  secretLabel={tt('config.gsc.key')}
292
294
  />
295
+ {config.role === 'boss' && (
296
+ <McpSection
297
+ title={tt('config.tianyancha')}
298
+ row={config.connections.tianyancha}
299
+ secret={secrets.tianyancha}
300
+ onEnabled={enabled => { patchConnection('tianyancha', { enabled }) }}
301
+ onUrl={url => { patchConnection('tianyancha', { url }) }}
302
+ onSecret={value => { setSecrets(prev => ({ ...prev, tianyancha: value })) }}
303
+ urlLabel={tt('config.tianyancha.url')}
304
+ secretLabel={tt('config.tianyancha.token')}
305
+ />
306
+ )}
293
307
 
294
308
  <Section title={tt('config.billing')}>
295
309
  <Field label={tt('config.billing.input')}>
package/src/index.ts CHANGED
@@ -29,6 +29,7 @@ import { rezConfigTool, rezStatusTool } from './tools.ts'
29
29
  import type { RezConfig } from './protocol.ts'
30
30
  import { workspaceGuidanceFor, mountRezWorkspaces } from './boss/mount.ts'
31
31
  import { liveSelfUpdate } from './self-update.ts'
32
+ import { mountTianyanchaMcp } from './tianyancha.ts'
32
33
 
33
34
  export const name = 'rez-suite'
34
35
 
@@ -79,6 +80,10 @@ export const Config: z<RezConfig> = z.object({
79
80
  enabled: z.boolean().default(true),
80
81
  siteUrl: z.string().default(REZ_DEFAULT_CONNECTIONS.gsc.siteUrl),
81
82
  }).default(REZ_DEFAULT_CONNECTIONS.gsc),
83
+ tianyancha: z.object({
84
+ enabled: z.boolean().default(true),
85
+ url: z.string().default(REZ_DEFAULT_CONNECTIONS.tianyancha.url),
86
+ }).default(REZ_DEFAULT_CONNECTIONS.tianyancha),
82
87
  }).default(REZ_DEFAULT_CONNECTIONS),
83
88
  billing: z.object({
84
89
  inputCostPer1k: z.number().default(0.001),
@@ -128,6 +133,7 @@ function delegatedHost(ctx: Context, getConfig: () => RezConfig): McpHost {
128
133
  export function apply(ctx: Context, config?: RezConfig): void {
129
134
  let current: () => RezConfig = () => normalize(config)
130
135
  const syncWorkspaces = mountRezWorkspaces(ctx, () => current().role)
136
+ const syncTianyancha = mountTianyanchaMcp(ctx, () => current().role)
131
137
 
132
138
  const tokens = new TokenManager(joinTokenDb())
133
139
  const host = delegatedHost(ctx, () => current())
@@ -145,6 +151,7 @@ export function apply(ctx: Context, config?: RezConfig): void {
145
151
  saveConfig(value)
146
152
  current = () => value
147
153
  syncWorkspaces()
154
+ syncTianyancha()
148
155
  try {
149
156
  const settings = ctx.get('settings') as { update?: (namespace: unknown, value: unknown) => Promise<void> } | undefined
150
157
  if (settings?.update !== undefined) await settings.update(REZ_SETTINGS_NAMESPACE, value)
@@ -198,10 +205,12 @@ export function apply(ctx: Context, config?: RezConfig): void {
198
205
  current = source
199
206
  sync()
200
207
  syncWorkspaces()
208
+ syncTianyancha()
201
209
  },
202
210
  onChange: () => {
203
211
  sync()
204
212
  syncWorkspaces()
213
+ syncTianyancha()
205
214
  },
206
215
  })
207
216
 
@@ -9,7 +9,7 @@
9
9
  import { lastMcpStartError, REZ_CREDENTIAL_REFS, secretConfigured, type RezSystem } from '@rezti/dsh-rez-sso'
10
10
  import type { RezConfig, RezServerStatus, RezTestResult } from './protocol.ts'
11
11
 
12
- export const COMPOSED_SERVERS = ['odoo', 'nextcloud', 'wechat', 'homeassistant', 'tapd', 'gsc'] as const
12
+ export const COMPOSED_SERVERS = ['odoo', 'nextcloud', 'wechat', 'homeassistant', 'tapd', 'gsc', 'tianyancha'] as const
13
13
 
14
14
  export type ComposedServer = (typeof COMPOSED_SERVERS)[number]
15
15
 
@@ -102,7 +102,10 @@ export function observeServer(
102
102
  }
103
103
 
104
104
  export function observeComposedServers(input: ObserveInput): RezServerStatus[] {
105
- return COMPOSED_SERVERS.map(server => observeServer(server, input))
105
+ const servers = input.config.role === 'boss'
106
+ ? COMPOSED_SERVERS
107
+ : COMPOSED_SERVERS.filter((server) => server !== 'tianyancha')
108
+ return servers.map(server => observeServer(server, input))
106
109
  }
107
110
 
108
111
  export function testComposedServers(input: ObserveInput): RezTestResult[] {
package/src/presets.ts CHANGED
@@ -127,6 +127,7 @@ const COMPILED: Record<Exclude<RezRoleId, 'all' | 'boss'>, { server: RezServerId
127
127
 
128
128
  /** Whether one MCP tool may be registered for the selected role. */
129
129
  export function isToolAllowed(role: RezRoleId, server: string, rawName: string): boolean {
130
+ if (server === 'tianyancha') return role === 'boss'
130
131
  if (role === 'all' || role === 'boss') return true
131
132
  const rules = COMPILED[role]
132
133
  return rules.some(rule => rule.server === server && rule.test.test(rawName))
package/src/protocol.ts CHANGED
@@ -48,6 +48,7 @@ export interface RezConnections {
48
48
  homeassistant: { enabled: boolean; url: string }
49
49
  tapd: { enabled: boolean; workspaceId: string; nickName: string }
50
50
  gsc: { enabled: boolean; siteUrl: string }
51
+ tianyancha: { enabled: boolean; url: string }
51
52
  }
52
53
 
53
54
  export interface RezPublicConnection {
@@ -90,6 +91,7 @@ export interface RezPublicConfig {
90
91
  homeassistant: RezPublicConnection
91
92
  tapd: RezPublicConnection
92
93
  gsc: RezPublicConnection
94
+ tianyancha: RezPublicConnection
93
95
  }
94
96
  billing: { inputCostPer1k: number; outputCostPer1k: number; monthlyBudget: number }
95
97
  }
package/src/store.ts CHANGED
@@ -125,6 +125,12 @@ export function publicConfig(config: RezConfig): RezPublicConfig {
125
125
  secretConfigured: secretConfigured('gsc'),
126
126
  secretRef: REZ_CREDENTIAL_REFS.gsc,
127
127
  },
128
+ tianyancha: {
129
+ enabled: connections.tianyancha.enabled,
130
+ url: connections.tianyancha.url,
131
+ secretConfigured: secretConfigured('tianyancha'),
132
+ secretRef: REZ_CREDENTIAL_REFS.tianyancha,
133
+ },
128
134
  },
129
135
  billing: { ...config.billing },
130
136
  }
@@ -0,0 +1,79 @@
1
+ /**
2
+ * TianyanCha official MCP for the 法务助理 workspace.
3
+ * Reuse https://mcp.tianyancha.com/mcp via dsh-mcp-client. Do not spawn a
4
+ * local 162-tool catalog — tools/list only exposes a few agent entrypoints.
5
+ * Start the client only when suite role is boss so other identities never
6
+ * see mcp__tianyancha__* in the prompt.
7
+ */
8
+ import type { Context } from '@deepseek-ai/cordis'
9
+ import { loadRezConnections, startMcpWhenSecret } from '@rezti/dsh-rez-sso'
10
+ import type { RezRoleId } from './protocol.ts'
11
+
12
+ export const TIANYANCHA_MCP_URL = 'https://mcp.tianyancha.com/mcp'
13
+
14
+ export function shouldStartTianyancha(role: RezRoleId): boolean {
15
+ return role === 'boss'
16
+ }
17
+
18
+ export function bearerAuthorization(secret: string): string {
19
+ const trimmed = secret.trim()
20
+ if (/^bearer\s+/i.test(trimmed)) return 'Bearer ' + trimmed.replace(/^bearer\s+/i, '')
21
+ return 'Bearer ' + trimmed
22
+ }
23
+
24
+ export function tianyanchaMcpConfig(secret: string): Record<string, unknown> {
25
+ const { url } = loadRezConnections().tianyancha
26
+ return {
27
+ serverName: 'tianyancha',
28
+ transport: 'streamable-http',
29
+ url: url.length > 0 ? url : TIANYANCHA_MCP_URL,
30
+ headers: { Authorization: bearerAuthorization(secret) },
31
+ failOnStartupError: false,
32
+ }
33
+ }
34
+
35
+ /** Start when role is boss; dispose the mcp-client fiber when the identity leaves. */
36
+ export function mountTianyanchaMcp(
37
+ ctx: Context,
38
+ getRole: () => RezRoleId,
39
+ start: typeof startMcpWhenSecret = startMcpWhenSecret,
40
+ ): () => void {
41
+ let session: (() => void) | undefined
42
+ let pending = false
43
+
44
+ const ensure = (): void => {
45
+ void (async () => {
46
+ const want = shouldStartTianyancha(getRole())
47
+ if (!want) {
48
+ session?.()
49
+ session = undefined
50
+ return
51
+ }
52
+ if (session !== undefined || pending) return
53
+ pending = true
54
+ let raced = false
55
+ try {
56
+ const dispose = await start(ctx, {
57
+ system: 'tianyancha',
58
+ label: '天眼查',
59
+ config: tianyanchaMcpConfig,
60
+ })
61
+ if (!shouldStartTianyancha(getRole())) {
62
+ raced = true
63
+ dispose()
64
+ return
65
+ }
66
+ session = dispose
67
+ } catch (error: unknown) {
68
+ const why = error instanceof Error ? error.message : String(error)
69
+ console.error('[dsh-rez-suite] tianyancha MCP start failed:', why)
70
+ } finally {
71
+ pending = false
72
+ if (raced && shouldStartTianyancha(getRole()) && session === undefined) ensure()
73
+ }
74
+ })()
75
+ }
76
+
77
+ ensure()
78
+ return ensure
79
+ }
@@ -3,4 +3,4 @@ name: staff-legal
3
3
  description: 法务助理。全员可问制度、整理风险;能看哪些合同跟当前 Odoo/Nextcloud 帐号。不下具约束力的法律结论。
4
4
  ---
5
5
 
6
- 完整规矩在工作区 `AGENTS.md`。先 `mcp__odoo__get_current_context`。不是律师。
6
+ 完整规矩在工作区 `AGENTS.md`。先 `mcp__odoo__get_current_context`。不是律师。有 `mcp__tianyancha__*` 才查工商(仅老板身份会挂上)。
@@ -19,6 +19,10 @@
19
19
 
20
20
  没有独立的法务 App 时,不要假装有「案件模块」。用订单、伙伴、Nextcloud 文档凑齐材料即可。
21
21
 
22
+ ## 天眼查(仅老板身份)
23
+
24
+ 出现 `mcp__tianyancha__*` 时,才用来查对方公司工商/司法。官方 `tools/list` 只有搜索、公司画像、`call_tool` 等入口,不要枚举 162 个工具。没有这些工具就当没开通,不要提天眼查。
25
+
22
26
  ## 每次回答
23
27
 
24
28
  1. **结论**(风险高/中/低或「缺材料」)