@dsh-libing/mcp-manager 0.0.0-stage → 0.1.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/lib/shared.js ADDED
@@ -0,0 +1,151 @@
1
+ /** Authenticated protocol. Paths and raw config are never browser identities. */
2
+ import { z } from 'zod';
3
+ export const API = '/api/libing-mcp-manager';
4
+ export const MCP = '@deepseek-ai/dsh-mcp-client';
5
+ const id = z.string().min(1).max(512);
6
+ const revision = z.string().regex(/^[a-f0-9]{64}$/u);
7
+ const short = z
8
+ .string()
9
+ .max(4096)
10
+ .refine((value) => !value.includes('\0'));
11
+ export const selectionSchema = z
12
+ .object({ workspaceId: id.optional(), sessionId: id.optional() })
13
+ .strict();
14
+ export const secretSchema = z.discriminatedUnion('action', [
15
+ z
16
+ .object({
17
+ key: z.string().regex(/^[A-Za-z_][A-Za-z0-9_-]{0,127}$/u),
18
+ action: z.literal('keep')
19
+ })
20
+ .strict(),
21
+ z
22
+ .object({
23
+ key: z.string().regex(/^[A-Za-z_][A-Za-z0-9_-]{0,127}$/u),
24
+ action: z.literal('remove')
25
+ })
26
+ .strict(),
27
+ z
28
+ .object({
29
+ key: z.string().regex(/^[A-Za-z_][A-Za-z0-9_-]{0,127}$/u),
30
+ action: z.literal('value'),
31
+ value: short
32
+ })
33
+ .strict(),
34
+ z
35
+ .object({
36
+ key: z.string().regex(/^[A-Za-z_][A-Za-z0-9_-]{0,127}$/u),
37
+ action: z.literal('env'),
38
+ value: z.string().regex(/^[A-Za-z_][A-Za-z0-9_]{0,127}$/u)
39
+ })
40
+ .strict()
41
+ ]);
42
+ export const configSchema = z
43
+ .object({
44
+ serverName: z.string().regex(/^[A-Za-z0-9_-]{1,32}$/u),
45
+ transport: z.enum(['stdio', 'streamable-http']),
46
+ command: short.default(''),
47
+ args: z.array(short).max(128).default([]),
48
+ cwd: short.default(''),
49
+ url: short.default(''),
50
+ secrets: z.array(secretSchema).max(64).default([]),
51
+ toolCallTimeoutMs: z.number().int().min(1000).max(300000).default(60000),
52
+ reconnect: z.boolean().default(true)
53
+ })
54
+ .strict()
55
+ .superRefine((value, context) => {
56
+ if (value.transport === 'stdio' && !value.command.trim())
57
+ context.addIssue({ code: 'custom', message: 'Executable required', path: ['command'] });
58
+ if (value.transport === 'streamable-http') {
59
+ try {
60
+ const url = new URL(value.url);
61
+ if (!['https:', 'http:'].includes(url.protocol) ||
62
+ url.username ||
63
+ url.password ||
64
+ url.search ||
65
+ url.hash)
66
+ throw new Error();
67
+ }
68
+ catch {
69
+ context.addIssue({
70
+ code: 'custom',
71
+ message: 'HTTP URL required; put credentials in headers',
72
+ path: ['url']
73
+ });
74
+ }
75
+ }
76
+ const keys = value.secrets.map((item) => item.key);
77
+ if (new Set(keys).size !== keys.length ||
78
+ keys.some((key) => ['__proto__', 'constructor', 'prototype'].includes(key)))
79
+ context.addIssue({
80
+ code: 'custom',
81
+ message: 'Duplicate or reserved credential key',
82
+ path: ['secrets']
83
+ });
84
+ });
85
+ export const mutationSchema = z.discriminatedUnion('kind', [
86
+ z.object({ kind: z.literal('create'), revision, config: configSchema }).strict(),
87
+ z.object({ kind: z.literal('update'), id, revision, config: configSchema }).strict(),
88
+ z.object({ kind: z.literal('delete'), id, revision, confirm: z.literal(true) }).strict(),
89
+ z.object({ kind: z.literal('enable'), id, revision, enabled: z.boolean() }).strict(),
90
+ z
91
+ .object({
92
+ kind: z.literal('policy'),
93
+ name: id,
94
+ revision,
95
+ enabled: z.boolean(),
96
+ selection: selectionSchema
97
+ })
98
+ .strict(),
99
+ z.object({ kind: z.literal('probe'), id, revision, confirm: z.literal(true) }).strict()
100
+ ]);
101
+ export const toolSchema = z.object({
102
+ name: id,
103
+ description: z.string(),
104
+ parameters: z.record(z.string(), z.unknown()),
105
+ output: z.unknown().nullable(),
106
+ source: z.enum(['mcp', 'plugin', 'core', 'unknown']),
107
+ owner: z.string().nullable(),
108
+ visible: z.boolean().nullable(),
109
+ allowed: z.boolean(),
110
+ protected: z.boolean()
111
+ });
112
+ export const serverSchema = z.object({
113
+ id,
114
+ serverName: z.string(),
115
+ scope: z.string(),
116
+ origin: z.enum(['managed', 'profile', 'preset']),
117
+ transport: z.string(),
118
+ enabled: z.boolean(),
119
+ phase: z.string().nullable(),
120
+ toolCount: z.number().nullable(),
121
+ editable: z.boolean(),
122
+ toggleable: z.boolean(),
123
+ config: configSchema.nullable(),
124
+ credentialKeys: z.array(z.string()),
125
+ tested: z
126
+ .object({
127
+ ok: z.boolean(),
128
+ at: z.string(),
129
+ durationMs: z.number(),
130
+ toolCount: z.number().optional()
131
+ })
132
+ .nullable()
133
+ });
134
+ export const stateSchema = z.object({
135
+ profile: z.string(),
136
+ patchRevision: revision,
137
+ policyRevision: revision,
138
+ workspaceId: id,
139
+ workspaces: z.array(z.object({ id, title: z.string(), path: z.string() })),
140
+ sessions: z.array(z.object({ id, title: z.string(), cwd: z.string() })),
141
+ selectedSessionId: id.nullable(),
142
+ scopeKind: z.enum(['live', 'preset', 'global']),
143
+ presetId: z.string().nullable(),
144
+ tools: z.array(toolSchema),
145
+ servers: z.array(serverSchema),
146
+ notices: z.array(z.string())
147
+ });
148
+ export const outcomeSchema = z.object({
149
+ application: z.enum(['applied', 'restart-required', 'overridden', 'failed', 'tested']),
150
+ notice: z.string().optional()
151
+ });
@@ -0,0 +1,23 @@
1
+ import type { Document } from 'yaml';
2
+ export declare const hash: (text: string) => string;
3
+ export declare class ManagerError extends Error {
4
+ status: number;
5
+ constructor(message: string, status?: number);
6
+ }
7
+ export declare const message: (error: unknown) => string;
8
+ export declare function readOwnedFile(file: string): Promise<string>;
9
+ export declare function commit(file: string, before: string, after: string): Promise<void>;
10
+ export declare const BEGIN = "# BEGIN dsh-libing MCP managed entries\n", END = "# END dsh-libing MCP managed entries\n";
11
+ export declare function document(text: string): Document;
12
+ /** Official YAML writes may move comments, so IDs determine ownership, not marker position. */
13
+ export declare function patchBlock(text: string): {
14
+ before: string;
15
+ body: string;
16
+ after: string;
17
+ exists: boolean;
18
+ };
19
+ export declare function rowsIn(text: string): {
20
+ doc: Document<import("yaml").Node, true>;
21
+ rows: (import("yaml").Scalar<unknown> & import("yaml").YAMLSeq<unknown>) | undefined;
22
+ };
23
+ export declare function replaceBlock(text: string, body: string): string;
package/lib/storage.js ADDED
@@ -0,0 +1,106 @@
1
+ /** Owned patch block preserves surrounding bytes, including foreign !!js expressions. */
2
+ import { createHash } from 'node:crypto';
3
+ import { lstat, readFile } from 'node:fs/promises';
4
+ import { isMap, isSeq, parseDocument } from 'yaml';
5
+ import { writeFileAtomic } from '@deepseek-ai/dsh-atomic-write';
6
+ export const hash = (text) => createHash('sha256').update(text).digest('hex');
7
+ export class ManagerError extends Error {
8
+ status;
9
+ constructor(message, status = 400) {
10
+ super(message);
11
+ this.status = status;
12
+ }
13
+ }
14
+ export const message = (error) => error instanceof ManagerError ? error.message : 'Operation failed; check local DSH diagnostics';
15
+ export async function readOwnedFile(file) {
16
+ try {
17
+ const stat = await lstat(file);
18
+ if (!stat.isFile() || stat.isSymbolicLink() || stat.size > 1024 * 1024)
19
+ throw new ManagerError('Unsupported or oversized configuration file', 409);
20
+ return await readFile(file, 'utf8');
21
+ }
22
+ catch (error) {
23
+ if (error instanceof Error && 'code' in error && error.code === 'ENOENT')
24
+ return '';
25
+ throw error;
26
+ }
27
+ }
28
+ export async function commit(file, before, after) {
29
+ if ((await readOwnedFile(file)) !== before)
30
+ throw new ManagerError('Configuration changed; refresh and retry', 409);
31
+ if (before)
32
+ await writeFileAtomic(`${file}.libing-backup`, before, { mode: 0o600, dirMode: 0o700 });
33
+ if ((await readOwnedFile(file)) !== before)
34
+ throw new ManagerError('Configuration changed; refresh and retry', 409);
35
+ await writeFileAtomic(file, after, { mode: 0o600, dirMode: 0o700 });
36
+ }
37
+ export const BEGIN = '# BEGIN dsh-libing MCP managed entries\n', END = '# END dsh-libing MCP managed entries\n';
38
+ export function document(text) {
39
+ const doc = parseDocument(text.trim() ? text : '[]\n', {
40
+ customTags: [{ tag: 'tag:yaml.org,2002:js', resolve: (value) => value }]
41
+ });
42
+ if (doc.errors.length || !isSeq(doc.contents))
43
+ throw new ManagerError('Profile patch must be a valid YAML sequence', 409);
44
+ return doc;
45
+ }
46
+ /** Official YAML writes may move comments, so IDs determine ownership, not marker position. */
47
+ export function patchBlock(text) {
48
+ const full = document(text);
49
+ if (!isSeq(full.contents))
50
+ throw new ManagerError('Invalid profile patch', 409);
51
+ const groups = full.contents.items.filter((node) => {
52
+ const rows = isMap(node) ? node.get('insert', true) : undefined;
53
+ return (isSeq(rows) &&
54
+ rows.items.some((row) => isMap(row) &&
55
+ typeof row.get('id') === 'string' &&
56
+ String(row.get('id')).startsWith('libing-mcp-')));
57
+ });
58
+ if (groups.length > 1)
59
+ throw new ManagerError('Multiple managed groups; edit at their source', 409);
60
+ const group = groups[0];
61
+ if (!group)
62
+ return { before: text, body: '[]\n', after: '', exists: false };
63
+ if (!isMap(group) || group.items.length !== 1 || !group.range)
64
+ throw new ManagerError('Unsupported managed group', 409);
65
+ const rows = group.get('insert', true);
66
+ if (!isSeq(rows) ||
67
+ rows.items.some((row) => !isMap(row) || !/^libing-mcp-[a-f0-9-]+$/u.test(String(row.get('id')))))
68
+ throw new ManagerError('Mixed ownership in managed group', 409);
69
+ const start = group.range[0] - 2, end = group.range[2];
70
+ if (text.slice(start, start + 2) !== '- ')
71
+ throw new ManagerError('Managed entries require a block sequence', 409);
72
+ return {
73
+ before: text.slice(0, start),
74
+ body: text.slice(start, end),
75
+ after: text.slice(end),
76
+ exists: true
77
+ };
78
+ }
79
+ export function rowsIn(text) {
80
+ const { body } = patchBlock(text), doc = document(body);
81
+ if (!isSeq(doc.contents))
82
+ throw new ManagerError('Invalid managed entries', 409);
83
+ const insert = doc.contents.items[0];
84
+ if (doc.contents.items.length > 1 ||
85
+ (insert && (!isMap(insert) || !isSeq(insert.get('insert', true)))))
86
+ throw new ManagerError('Unsupported managed block; edit manually', 409);
87
+ const rows = isMap(insert) ? insert.get('insert', true) : undefined;
88
+ return { doc, rows: isSeq(rows) ? rows : undefined };
89
+ }
90
+ export function replaceBlock(text, body) {
91
+ const block = patchBlock(text), parsed = document(body);
92
+ const first = isSeq(parsed.contents) ? parsed.contents.items[0] : undefined;
93
+ const rows = isMap(first) ? first.get('insert', true) : undefined;
94
+ if (block.exists) {
95
+ const next = `${block.before}${isSeq(rows) && rows.items.length ? body : ''}${block.after}`;
96
+ document(next);
97
+ return next;
98
+ }
99
+ // The empty flow sequence must give way to the first block-sequence row.
100
+ const before = block.before
101
+ .replace(/^\s*\[\]\s*$/gmu, '')
102
+ .replace(/^# (?:BEGIN|END) dsh-libing MCP managed entries(?:\n|$)/gmu, '');
103
+ const next = `${before}${before && !before.endsWith('\n') ? '\n' : ''}${BEGIN}${body}${END}${block.after}`;
104
+ document(next);
105
+ return next;
106
+ }
package/lib/view.d.ts ADDED
@@ -0,0 +1,31 @@
1
+ /** Real service/scope observation. Cold reads lease a preset, never create an Agent. */
2
+ import { type Context } from '@deepseek-ai/cordis';
3
+ import type ToolRuntime from '@deepseek-ai/dsh-tools';
4
+ import { SessionId } from '@deepseek-ai/dsh-session';
5
+ import type { ScopeKey } from '@deepseek-ai/dsh-scope';
6
+ import type { Selection, State, ToolItem } from './shared.ts';
7
+ import type { ToolPolicy } from './policy.ts';
8
+ import type { ServerRecord } from './servers.ts';
9
+ export declare function selectionView(ctx: Context, selection: Selection, signal: AbortSignal): Promise<{
10
+ workspaceId: string;
11
+ workspaces: {
12
+ id: string;
13
+ title: string;
14
+ path: string;
15
+ }[];
16
+ sessions: {
17
+ id: SessionId;
18
+ cwd: string;
19
+ title: string;
20
+ }[];
21
+ selectedSessionId: string | null;
22
+ }>;
23
+ export interface ToolView {
24
+ tools: ToolRuntime;
25
+ scope?: ScopeKey;
26
+ kind: State['scopeKind'];
27
+ presetId: string | null;
28
+ }
29
+ export declare function withToolView<T>(ctx: Context, sessionId: string | null, signal: AbortSignal, read: (view: ToolView) => Promise<T>): Promise<T>;
30
+ /** Source annotations are conservative; undocumented ownership remains unknown. */
31
+ export declare function directory(ctx: Context, view: ToolView, servers: ServerRecord[], policy: ToolPolicy): ToolItem[];
package/lib/view.js ADDED
@@ -0,0 +1,176 @@
1
+ /** Real service/scope observation. Cold reads lease a preset, never create an Agent. */
2
+ import { symbols } from '@deepseek-ai/cordis';
3
+ import { SessionId } from '@deepseek-ai/dsh-session';
4
+ import { livePresetMounts } from '@deepseek-ai/dsh-agent-preset-registry';
5
+ import { scopeChainOf, scopeOf } from '@deepseek-ai/dsh-scope';
6
+ import { basename, resolve } from 'node:path';
7
+ import { realpath } from 'node:fs/promises';
8
+ import { hash, ManagerError } from "./storage.js";
9
+ import { protectedTool } from "./policy.js";
10
+ import { declaresTool } from "./providers.js";
11
+ const canonical = async (path) => await realpath(path).catch(() => resolve(path));
12
+ export async function selectionView(ctx, selection, signal) {
13
+ const records = await ctx.sessionQuery.listSessions(signal), workspaces = (ctx.get('workspaceRegistry')?.list() ?? []).map((w) => ({
14
+ id: w.id,
15
+ path: w.path,
16
+ title: w.title
17
+ }));
18
+ const paths = new Map();
19
+ for (const session of records) {
20
+ if (!session.header.cwd)
21
+ continue;
22
+ const path = await canonical(session.header.cwd);
23
+ paths.set(session.header.id, path);
24
+ if (!workspaces.some((w) => w.path === path))
25
+ workspaces.push({
26
+ id: `cwd-${hash(path)}`,
27
+ path,
28
+ title: basename(path) || path
29
+ });
30
+ }
31
+ if (!workspaces.length) {
32
+ const path = await canonical(ctx.profileContext.cwd);
33
+ workspaces.push({
34
+ id: `cwd-${hash(path)}`,
35
+ path,
36
+ title: basename(path) || path
37
+ });
38
+ }
39
+ const selected = selection.workspaceId
40
+ ? workspaces.find((w) => w.id === selection.workspaceId)
41
+ : (workspaces.find((w) => w.path === paths.get(selection.sessionId ?? '')) ?? workspaces[0]);
42
+ if (!selected)
43
+ throw new ManagerError('Workspace no longer exists', 404);
44
+ const matching = records.filter((r) => paths.get(r.header.id) === selected.path);
45
+ if (selection.sessionId && !matching.some((r) => r.header.id === selection.sessionId))
46
+ throw new ManagerError('Session does not belong to this workspace', 409);
47
+ const titles = await ctx.sessionQuery.readTitleSnapshots(matching.map((r) => r.header.id), signal);
48
+ const sessions = matching.map((r) => {
49
+ const title = titles.find((t) => t.sessionId === r.header.id);
50
+ return {
51
+ id: r.header.id,
52
+ cwd: r.header.cwd ?? selected.path,
53
+ title: title?.status === 'fulfilled' ? (title.value.title?.title ?? '') : ''
54
+ };
55
+ });
56
+ return {
57
+ workspaceId: selected.id,
58
+ workspaces,
59
+ sessions,
60
+ selectedSessionId: selection.sessionId ?? sessions[0]?.id ?? null
61
+ };
62
+ }
63
+ export async function withToolView(ctx, sessionId, signal, read) {
64
+ signal.throwIfAborted();
65
+ const agent = sessionId ? ctx.get('agents')?.get(SessionId(sessionId)) : undefined, presets = ctx.get('agentPresets');
66
+ if (agent)
67
+ return await read({
68
+ tools: presets?.serviceFor(agent, 'tools') ?? agent.ctx.get('tools') ?? ctx.tools,
69
+ scope: agent,
70
+ kind: 'live',
71
+ presetId: presets?.composedPreset(agent.ctx) ?? null
72
+ });
73
+ if (!sessionId || !presets)
74
+ return await read({ tools: ctx.tools, kind: 'global', presetId: null });
75
+ const observation = await ctx.sessionQuery.observeSession(SessionId(sessionId), { signal });
76
+ try {
77
+ const preset = observation.projections?.values.agentPreset;
78
+ if (preset === undefined)
79
+ return await read({ tools: ctx.tools, kind: 'global', presetId: null });
80
+ const lease = await presets.acquireScope(preset ?? presets.defaultId).catch(() => {
81
+ signal.throwIfAborted();
82
+ return undefined;
83
+ });
84
+ if (!lease)
85
+ return await read({
86
+ tools: ctx.tools,
87
+ kind: 'global',
88
+ presetId: preset ?? presets.defaultId
89
+ });
90
+ try {
91
+ signal.throwIfAborted();
92
+ const mount = livePresetMounts(ctx.root.fiber).find((m) => m.key === lease.key);
93
+ const supplied = mount
94
+ ? [...mount.tree.entries()]
95
+ .find((e) => e.options.name === '@deepseek-ai/dsh-tools')
96
+ ?.fiber?.ctx.get('tools')
97
+ : undefined;
98
+ return await read({
99
+ tools: supplied ?? ctx.tools,
100
+ scope: lease.key,
101
+ kind: 'preset',
102
+ presetId: preset ?? presets.defaultId
103
+ });
104
+ }
105
+ finally {
106
+ await lease[Symbol.asyncDispose]();
107
+ }
108
+ }
109
+ finally {
110
+ observation[Symbol.dispose]();
111
+ }
112
+ }
113
+ /** Source annotations are conservative; undocumented ownership remains unknown. */
114
+ export function directory(ctx, view, servers, policy) {
115
+ const visible = view.tools.schemas(view.scope), global = ctx.tools.schemas(), schemas = new Map([...global, ...visible].map((tool) => [tool.name, tool]));
116
+ const entries = [
117
+ ...new Set([
118
+ ...ctx.loader.entries(),
119
+ ...livePresetMounts(ctx.root.fiber).flatMap((mount) => [...mount.tree.entries()])
120
+ ])
121
+ ].filter((entry) => entry.fiber?.state === 2);
122
+ const chain = scopeChainOf(view.scope);
123
+ return [...schemas.values()]
124
+ .map((tool) => {
125
+ const resolved = view.tools.get(tool.name, view.scope) ?? ctx.tools.get(tool.name);
126
+ const candidates = servers.filter((s) => tool.name.startsWith(`mcp__${s.item.serverName}__`) &&
127
+ (s.item.scope === 'profile' ||
128
+ s.scopeKey === view.scope ||
129
+ (s.scopeKey !== undefined && chain.includes(s.scopeKey))));
130
+ const mcp = candidates.length === 1 ? candidates[0] : undefined;
131
+ // rc.2 definitions carry no generic owner metadata. Match the versioned
132
+ // declared contract to an active entry resolving the same service and
133
+ // exact definition from its own scope (reject unrelated presets/shadows).
134
+ const providers = new Set(entries
135
+ .filter((entry) => {
136
+ if (!declaresTool(entry.options.name, entry.fiber?.config ?? entry.options.config, tool.name) ||
137
+ !entry.fiber ||
138
+ !resolved)
139
+ return false;
140
+ const ownerScope = scopeOf(entry.fiber.ctx);
141
+ if (ownerScope !== undefined && !chain.includes(ownerScope))
142
+ return false;
143
+ const runtime = entry.fiber.ctx.get('tools');
144
+ const selectedRuntime = view.tools.get(tool.name, view.scope) ? view.tools : ctx.tools;
145
+ return (runtime !== undefined &&
146
+ (Reflect.get(runtime, symbols.original) ?? runtime) ===
147
+ (Reflect.get(selectedRuntime, symbols.original) ?? selectedRuntime) &&
148
+ runtime.get(tool.name, ownerScope) === resolved);
149
+ })
150
+ .map((entry) => entry.options.name));
151
+ const provider = providers.size === 1 ? [...providers][0] : undefined;
152
+ const builtin = provider?.startsWith('@deepseek-ai/dsh-') ?? false;
153
+ return {
154
+ name: tool.name,
155
+ description: tool.description ?? '',
156
+ parameters: tool.parameters ?? {},
157
+ output: resolved?.output?.schema ?? null,
158
+ source: mcp
159
+ ? 'mcp'
160
+ : builtin
161
+ ? 'core'
162
+ : provider
163
+ ? 'plugin'
164
+ : protectedTool(tool.name)
165
+ ? 'core'
166
+ : 'unknown',
167
+ owner: mcp
168
+ ? `MCP · ${mcp.item.serverName}`
169
+ : (provider ?? (tool.name === 'run_code' ? '@deepseek-ai/dsh-tools' : null)),
170
+ visible: view.kind === 'global' ? null : visible.some((v) => v.name === tool.name),
171
+ allowed: policy.allowed(tool.name),
172
+ protected: protectedTool(tool.name)
173
+ };
174
+ })
175
+ .sort((a, b) => a.name.localeCompare(b.name));
176
+ }
package/package.json CHANGED
@@ -1,6 +1,127 @@
1
1
  {
2
2
  "name": "@dsh-libing/mcp-manager",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.1.0",
4
+ "description": "MCP 管理中心:连接配置、统一工具目录与 profile 调用规则",
5
+ "type": "module",
6
+ "icon": "icon.svg",
7
+ "main": "lib/index.js",
8
+ "types": "lib/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./lib/index.d.ts",
12
+ "default": "./lib/index.js"
13
+ },
14
+ "./client": {
15
+ "types": "./lib/client-types/client/index.d.ts",
16
+ "default": "./lib/client.js"
17
+ },
18
+ "./package.json": "./package.json"
19
+ },
20
+ "files": [
21
+ "icon.svg",
22
+ "lib",
23
+ "cordis.patch.yml",
24
+ "README.md",
25
+ "CHANGELOG.md"
26
+ ],
27
+ "license": "UNLICENSED",
28
+ "engines": {
29
+ "node": "^22.19.0 || >=24.0.0"
30
+ },
31
+ "repository": {
32
+ "type": "git",
33
+ "url": "git+https://gitcode.com/LiBing-DSH/dsh-libing.git",
34
+ "directory": "plugins/mcp-manager"
35
+ },
36
+ "publishConfig": {
37
+ "access": "public",
38
+ "registry": "https://registry.npmjs.org/"
39
+ },
40
+ "dependencies": {
41
+ "@deepseek-ai/schemastery": "3.18.4",
42
+ "@modelcontextprotocol/client": "2.0.0",
43
+ "yaml": "2.9.1",
44
+ "zod": "4.4.3"
45
+ },
46
+ "peerDependencies": {
47
+ "@deepseek-ai/cordis": "4.0.4",
48
+ "@deepseek-ai/dsh-client-connection": "0.2.0-rc.2",
49
+ "@deepseek-ai/dsh-client-locale": "0.2.0-rc.2",
50
+ "@deepseek-ai/dsh-client-ui-layout": "0.2.0-rc.2",
51
+ "@deepseek-ai/dsh-client-ui-sidebar": "0.2.0-rc.2",
52
+ "@deepseek-ai/dsh-client-ui-slots": "0.2.0-rc.2",
53
+ "@deepseek-ai/dsh-client-ui-renderer": "0.2.0-rc.2",
54
+ "@deepseek-ai/dsh-client-ui-primitives": "0.2.0-rc.2",
55
+ "@deepseek-ai/dsh-session": "0.2.0-rc.2",
56
+ "@deepseek-ai/dsh-session-query": "0.2.0-rc.2",
57
+ "@deepseek-ai/dsh-agent": "0.2.0-rc.2",
58
+ "@deepseek-ai/dsh-agent-preset-registry": "0.2.0-rc.2",
59
+ "@deepseek-ai/dsh-workspace": "0.2.0-rc.2",
60
+ "@deepseek-ai/dsh-client-ui-session": "0.2.0-rc.2",
61
+ "@deepseek-ai/dsh-tools": "0.2.0-rc.2",
62
+ "@deepseek-ai/dsh-app-boot": "0.2.0-rc.2",
63
+ "@deepseek-ai/dsh-atomic-write": "0.2.0-rc.2",
64
+ "@deepseek-ai/dsh-subprocess": "0.2.0-rc.2",
65
+ "@deepseek-ai/dsh-hmr": "0.2.0-rc.2",
66
+ "@deepseek-ai/dsh-scope": "0.2.0-rc.2",
67
+ "@deepseek-ai/cordis-plugin-loader": "1.0.5",
68
+ "@deepseek-ai/dsh-plugin-manager": "0.2.0-rc.2"
69
+ },
70
+ "devDependencies": {
71
+ "@deepseek-ai/cordis": "4.0.4",
72
+ "@deepseek-ai/dsh-client-connection": "0.2.0-rc.2",
73
+ "@deepseek-ai/dsh-client-locale": "0.2.0-rc.2",
74
+ "@deepseek-ai/dsh-client-ui-layout": "0.2.0-rc.2",
75
+ "@deepseek-ai/dsh-client-ui-sidebar": "0.2.0-rc.2",
76
+ "@deepseek-ai/dsh-client-ui-slots": "0.2.0-rc.2",
77
+ "@deepseek-ai/dsh-client-ui-renderer": "0.2.0-rc.2",
78
+ "@deepseek-ai/dsh-client-ui-primitives": "0.2.0-rc.2",
79
+ "@deepseek-ai/dsh-session": "0.2.0-rc.2",
80
+ "@deepseek-ai/dsh-session-query": "0.2.0-rc.2",
81
+ "@deepseek-ai/dsh-agent": "0.2.0-rc.2",
82
+ "@deepseek-ai/dsh-agent-preset-registry": "0.2.0-rc.2",
83
+ "@deepseek-ai/dsh-workspace": "0.2.0-rc.2",
84
+ "@deepseek-ai/dsh-client-ui-session": "0.2.0-rc.2",
85
+ "@deepseek-ai/dsh-tools": "0.2.0-rc.2",
86
+ "@deepseek-ai/dsh-app-boot": "0.2.0-rc.2",
87
+ "@deepseek-ai/dsh-atomic-write": "0.2.0-rc.2",
88
+ "@deepseek-ai/dsh-subprocess": "0.2.0-rc.2",
89
+ "@deepseek-ai/dsh-hmr": "0.2.0-rc.2",
90
+ "@deepseek-ai/dsh-scope": "0.2.0-rc.2",
91
+ "@deepseek-ai/cordis-plugin-loader": "1.0.5",
92
+ "@deepseek-ai/dsh-plugin-manager": "0.2.0-rc.2"
93
+ },
94
+ "dsh": {
95
+ "bundle": {
96
+ "patch": "./cordis.patch.yml"
97
+ },
98
+ "client": {
99
+ "platform": "web",
100
+ "inject": [
101
+ "@deepseek-ai/dsh-client-locale",
102
+ "@deepseek-ai/dsh-client-ui-layout",
103
+ "@deepseek-ai/dsh-client-ui-sidebar",
104
+ "@deepseek-ai/dsh-client-ui-session"
105
+ ]
106
+ },
107
+ "market": {
108
+ "displayName": {
109
+ "zh": "MCP 管理中心",
110
+ "en": "MCP Center"
111
+ },
112
+ "category": "utilities",
113
+ "icon": "icon.svg",
114
+ "maintainers": [
115
+ "@lujinxing"
116
+ ],
117
+ "compatibility": {
118
+ "dsh": "0.2.0-rc.2"
119
+ },
120
+ "allowedBuilds": []
121
+ }
122
+ },
123
+ "scripts": {
124
+ "build": "tsc -p tsconfig.build.json && tsc -p tsconfig.client.build.json && node --import tsx/esm ../../scripts/build-client.ts mcp-manager",
125
+ "typecheck:client": "tsc -p tsconfig.client.json --noEmit"
126
+ }
6
127
  }