dsh-agent-plugins-market 0.5.2 → 0.6.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/README.md +29 -3
- package/README.zh.md +29 -3
- package/client/client.js +1388 -204
- package/client/client.js.map +1 -1
- package/lib/application/catalog.js +537 -71
- package/lib/application/details.js +39 -12
- package/lib/catalog/archive.js +341 -0
- package/lib/catalog/git.js +85 -12
- package/lib/catalog/legacy-root-migration.js +51 -0
- package/lib/catalog/lsp-spec.js +87 -0
- package/lib/catalog/manifests.js +84 -32
- package/lib/catalog/paths.js +65 -7
- package/lib/catalog/scan-pipeline.js +50 -0
- package/lib/catalog/scan-resolvers.js +402 -0
- package/lib/catalog/source-catalog.js +22 -8
- package/lib/catalog/suite-scanner.js +5 -176
- package/lib/catalog/surfaces.js +80 -30
- package/lib/contracts/lsp-status.js +2 -0
- package/lib/contracts/market.js +8 -1
- package/lib/index.js +118 -13
- package/lib/model/state.js +14 -1
- package/lib/model/types.js +21 -2
- package/lib/routes.js +88 -6
- package/lib/runtime/commands-mounts.js +6 -2
- package/lib/runtime/hooks-mounts.js +9 -6
- package/lib/runtime/lsp-direct-config.js +44 -0
- package/lib/runtime/lsp-mounts.js +236 -0
- package/lib/runtime/lsp-status.js +96 -0
- package/lib/runtime/mcp-backend.js +60 -0
- package/lib/runtime/mcp-client/bridge.js +126 -0
- package/lib/runtime/mcp-client/config.js +42 -0
- package/lib/runtime/mcp-client/connection.js +380 -0
- package/lib/runtime/mcp-client/host-seams.js +88 -0
- package/lib/runtime/mcp-client/json-schema-subset.js +181 -0
- package/lib/runtime/mcp-client/oauth.js +379 -0
- package/lib/runtime/mcp-client/tools.js +432 -0
- package/lib/runtime/mcp-client/transport.js +103 -0
- package/lib/runtime/mcp-config.js +196 -54
- package/lib/runtime/mcp-credentials.js +20 -0
- package/lib/runtime/mcp-mounts.js +237 -27
- package/lib/runtime/mcp-overrides.js +64 -6
- package/lib/runtime/mcp-redaction.js +92 -0
- package/lib/runtime/mcp-status.js +76 -32
- package/lib/runtime/reconciler.js +35 -1
- package/lib/runtime/regions.js +56 -0
- package/lib/types/application/catalog.d.ts +191 -14
- package/lib/types/application/details.d.ts +1 -6
- package/lib/types/application/queries.d.ts +28 -1
- package/lib/types/catalog/archive.d.ts +48 -0
- package/lib/types/catalog/git.d.ts +27 -3
- package/lib/types/catalog/legacy-root-migration.d.ts +14 -0
- package/lib/types/catalog/lsp-spec.d.ts +14 -0
- package/lib/types/catalog/manifests.d.ts +16 -1
- package/lib/types/catalog/paths.d.ts +23 -2
- package/lib/types/catalog/scan-pipeline.d.ts +76 -0
- package/lib/types/catalog/scan-resolvers.d.ts +81 -0
- package/lib/types/catalog/source-catalog.d.ts +5 -0
- package/lib/types/catalog/suite-scanner.d.ts +14 -7
- package/lib/types/catalog/surfaces.d.ts +3 -3
- package/lib/types/contracts/lsp-status.d.ts +53 -0
- package/lib/types/contracts/market.d.ts +55 -1
- package/lib/types/contracts/mcp-status.d.ts +14 -2
- package/lib/types/index.d.ts +24 -8
- package/lib/types/model/types.d.ts +52 -3
- package/lib/types/runtime/lsp-direct-config.d.ts +12 -0
- package/lib/types/runtime/lsp-mounts.d.ts +89 -0
- package/lib/types/runtime/lsp-status.d.ts +30 -0
- package/lib/types/runtime/mcp-backend.d.ts +49 -0
- package/lib/types/runtime/mcp-client/bridge.d.ts +37 -0
- package/lib/types/runtime/mcp-client/config.d.ts +123 -0
- package/lib/types/runtime/mcp-client/connection.d.ts +82 -0
- package/lib/types/runtime/mcp-client/host-seams.d.ts +89 -0
- package/lib/types/runtime/mcp-client/json-schema-subset.d.ts +58 -0
- package/lib/types/runtime/mcp-client/oauth.d.ts +121 -0
- package/lib/types/runtime/mcp-client/tools.d.ts +136 -0
- package/lib/types/runtime/mcp-client/transport.d.ts +36 -0
- package/lib/types/runtime/mcp-config.d.ts +29 -12
- package/lib/types/runtime/mcp-credentials.d.ts +9 -0
- package/lib/types/runtime/mcp-mounts.d.ts +56 -6
- package/lib/types/runtime/mcp-overrides.d.ts +23 -4
- package/lib/types/runtime/mcp-redaction.d.ts +7 -0
- package/lib/types/runtime/mcp-status.d.ts +4 -2
- package/lib/types/runtime/reconciler.d.ts +22 -4
- package/lib/types/runtime/regions.d.ts +37 -0
- package/package.json +15 -11
- package/schemas/1.0.0/mcp.schema.json +22 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP backend selection: whether suite MCP servers mount through the
|
|
3
|
+
* market's self-built bridge (default — stdio, Streamable HTTP with OAuth,
|
|
4
|
+
* and legacy SSE) or through the host's `@deepseek-ai/dsh-mcp-client`
|
|
5
|
+
* (compatibility mode: no OAuth, no SSE, but the host-native implementation).
|
|
6
|
+
*
|
|
7
|
+
* The choice is a host settings namespace (`dsh-agent-plugins-market`,
|
|
8
|
+
* `mcpEnhanced: boolean`, default true) registered by the plugin's node half:
|
|
9
|
+
* the registration is what makes the host 插件配置 tab serve our card, the
|
|
10
|
+
* client card binds it for state, and the node half watches it to remount
|
|
11
|
+
* servers when the switch flips. `readMcpBackend` remains only as the
|
|
12
|
+
* one-time migration from the earlier data-root `settings.json` choice.
|
|
13
|
+
*
|
|
14
|
+
* @module runtime/mcp-backend
|
|
15
|
+
*/
|
|
16
|
+
import z from '@deepseek-ai/schemastery';
|
|
17
|
+
/** The MCP mount backend the market uses for suite servers. */
|
|
18
|
+
export type McpBackend = 'builtin' | 'host';
|
|
19
|
+
export declare const MCP_BACKENDS: readonly McpBackend[];
|
|
20
|
+
/** Settings namespace this plugin registers; the key the plugin-config tab pairs our card by. */
|
|
21
|
+
export declare const MCP_SETTINGS_NAMESPACE = "dsh-agent-plugins-market";
|
|
22
|
+
/** Schema of the market settings namespace: the MCP enhancement switch and the download region. */
|
|
23
|
+
export declare const MarketSettingsSchema: z<Schemastery.ObjectS<{
|
|
24
|
+
/** ON (default) = the built-in bridge with OAuth and SSE; OFF = host client compat mode. */
|
|
25
|
+
mcpEnhanced: z<boolean, boolean>;
|
|
26
|
+
/** Download region for GitHub acquisition; `auto` follows the interface language. */
|
|
27
|
+
downloadRegion: z<"auto" | "global" | "china", "auto" | "global" | "china">;
|
|
28
|
+
}>, Schemastery.ObjectT<{
|
|
29
|
+
/** ON (default) = the built-in bridge with OAuth and SSE; OFF = host client compat mode. */
|
|
30
|
+
mcpEnhanced: z<boolean, boolean>;
|
|
31
|
+
/** Download region for GitHub acquisition; `auto` follows the interface language. */
|
|
32
|
+
downloadRegion: z<"auto" | "global" | "china", "auto" | "global" | "china">;
|
|
33
|
+
}>>;
|
|
34
|
+
/** Path of the legacy persisted settings file under the plugin data root. */
|
|
35
|
+
export declare function marketSettingsPath(dataRoot: string): string;
|
|
36
|
+
/** Read the legacy persisted backend; absent or invalid values read as the default. */
|
|
37
|
+
export declare function readMcpBackend(dataRoot: string): Promise<McpBackend>;
|
|
38
|
+
/** What the host client probe reports: resolvability plus its version. */
|
|
39
|
+
export interface HostMcpClientProbe {
|
|
40
|
+
available: boolean;
|
|
41
|
+
version?: string;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Probe whether the host's `dsh-mcp-client` is resolvable from this plugin's
|
|
45
|
+
* module context, and at which version. Best effort: any failure reads as
|
|
46
|
+
* "unavailable", which the mount path surfaces as a per-server diagnostic
|
|
47
|
+
* when the host backend is selected.
|
|
48
|
+
*/
|
|
49
|
+
export declare function probeHostMcpClient(): Promise<HostMcpClientProbe>;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The market's self-built MCP client bridge plugin: connects to one external
|
|
3
|
+
* MCP server and registers its tools on the harness ToolRuntime under
|
|
4
|
+
* server-qualified public names (`mcp__<serverName>__<rawName>`). One plugin
|
|
5
|
+
* instance per server; the mount registry loads instances through
|
|
6
|
+
* `ctx.plugin` exactly like the host's own client, so reconcile, retry,
|
|
7
|
+
* rollback, and HMR semantics are unchanged.
|
|
8
|
+
*
|
|
9
|
+
* Namespace plugin (named exports, no default export). Lifecycle is
|
|
10
|
+
* effect-scoped: disposal disconnects from the server, unregisters all tools,
|
|
11
|
+
* and releases the `serverName` namespace reservation. HMR hot-swaps by
|
|
12
|
+
* disposing the old instance and creating a new one; identical `serverName`
|
|
13
|
+
* reproduces identical public tool names.
|
|
14
|
+
*
|
|
15
|
+
* This replaces the runtime dependency on the host's `dsh-mcp-client`
|
|
16
|
+
* package: stdio, Streamable HTTP (with OAuth 2.1), and legacy SSE all run
|
|
17
|
+
* inside this plugin from the market's own `@modelcontextprotocol/sdk`
|
|
18
|
+
* dependency.
|
|
19
|
+
*
|
|
20
|
+
* @module runtime/mcp-client/bridge
|
|
21
|
+
*/
|
|
22
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
23
|
+
import type { Config } from './config.js';
|
|
24
|
+
/** Cordis plugin name used by loader diagnostics. */
|
|
25
|
+
export declare const name = "market-mcp-client";
|
|
26
|
+
/** Services required by this plugin. */
|
|
27
|
+
export declare const inject: string[];
|
|
28
|
+
/**
|
|
29
|
+
* Connect one MCP server and publish its initial tool generation before
|
|
30
|
+
* activation. Remains explicitly `async`: Cordis treats a prototype-bearing
|
|
31
|
+
* ordinary function as a constructor, whose returned Promise is not startup
|
|
32
|
+
* work.
|
|
33
|
+
* @param ctx - plugin context carrying the tool registry.
|
|
34
|
+
* @param config - resolved transport and server namespace configuration.
|
|
35
|
+
* @returns startup readiness after connection and initial tool discovery settle.
|
|
36
|
+
*/
|
|
37
|
+
export declare function apply(ctx: Context, config: Config): Promise<void>;
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration types for the market's self-built MCP client bridge. One
|
|
3
|
+
* bridge instance connects to one MCP server; mounts are built
|
|
4
|
+
* programmatically by `mcp-config.ts`, so there is no Schemastery schema —
|
|
5
|
+
* every field the supervisor relies on is re-judged at load
|
|
6
|
+
* (`resolveReconnectPolicy`, serverName pattern) and misconfiguration fails
|
|
7
|
+
* that instance loudly instead of being normalized silently.
|
|
8
|
+
*
|
|
9
|
+
* @module runtime/mcp-client/config
|
|
10
|
+
*/
|
|
11
|
+
import type { ReconnectConfig } from './connection.js';
|
|
12
|
+
export type { ReconnectConfig, ResolvedReconnectPolicy } from './connection.js';
|
|
13
|
+
export { RECONNECT_DEFAULTS } from './connection.js';
|
|
14
|
+
/** Default timeout for individual MCP tool calls (ms). */
|
|
15
|
+
export declare const DEFAULT_TOOL_CALL_TIMEOUT_MS = 60000;
|
|
16
|
+
/** Valid `serverName`, kept below the public tool-name budget. */
|
|
17
|
+
export declare const SERVER_NAME_PATTERN: RegExp;
|
|
18
|
+
/**
|
|
19
|
+
* Loopback callback tuning for the OAuth browser leg. The redirect listener
|
|
20
|
+
* binds `127.0.0.1`; port `0` (the default) picks an ephemeral port, which
|
|
21
|
+
* RFC 8252 §7.3 loopback redirects permit.
|
|
22
|
+
*/
|
|
23
|
+
export interface OAuthStorageConfig {
|
|
24
|
+
/** Fixed loopback port for the authorization redirect; `0` picks one ephemally. */
|
|
25
|
+
callbackPort?: number;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* OAuth 2.1 authorization config for a Streamable HTTP (or legacy SSE)
|
|
29
|
+
* server. The MCP authorization flow (RFC 9728 discovery, dynamic client
|
|
30
|
+
* registration, PKCE, refresh) is performed by the SDK: on the server's `401`
|
|
31
|
+
* challenge the transport opens a loopback browser authorization, and granted
|
|
32
|
+
* tokens persist through the credentials service when one is mounted.
|
|
33
|
+
*
|
|
34
|
+
* Defaults to enabled — servers that never challenge are unaffected (the
|
|
35
|
+
* provider only activates on a `401`), so a suite needs no OAuth declaration
|
|
36
|
+
* for the flow to work.
|
|
37
|
+
*/
|
|
38
|
+
export interface OAuthConfig {
|
|
39
|
+
/** Enable the OAuth flow (default `true`; set `false` to opt out explicitly). */
|
|
40
|
+
enabled?: boolean;
|
|
41
|
+
/** Space-separated scope string requested during authorization, when the server needs one. */
|
|
42
|
+
scope?: string;
|
|
43
|
+
/** Loopback callback tuning; omission uses an ephemeral port and the defaults. */
|
|
44
|
+
storage?: OAuthStorageConfig;
|
|
45
|
+
}
|
|
46
|
+
/** Config for connecting to an MCP server via a spawned child process over stdio. */
|
|
47
|
+
export interface StdioConfig {
|
|
48
|
+
/** Selects child-process stdio transport. */
|
|
49
|
+
transport: 'stdio';
|
|
50
|
+
/**
|
|
51
|
+
* Stable local namespace for this server's model-facing tool names
|
|
52
|
+
* (`mcp__<serverName>__<rawName>`). Must match `[A-Za-z0-9_-]{1,32}` and be
|
|
53
|
+
* unique across live bridge instances.
|
|
54
|
+
*/
|
|
55
|
+
serverName: string;
|
|
56
|
+
/** Executable used to start the server. */
|
|
57
|
+
command: string;
|
|
58
|
+
/** Arguments passed directly, without shell interpolation. */
|
|
59
|
+
args: string[];
|
|
60
|
+
/** Extra env vars merged on top of scrubbed ambient env. */
|
|
61
|
+
env: Record<string, string>;
|
|
62
|
+
/** Working directory for the child process. */
|
|
63
|
+
cwd: string;
|
|
64
|
+
/** Per-tool-call timeout in milliseconds. */
|
|
65
|
+
toolCallTimeoutMs: number;
|
|
66
|
+
/** Fail plugin activation when the initial connection or tool synchronization fails. */
|
|
67
|
+
failOnStartupError: boolean;
|
|
68
|
+
/** Automatic reconnect policy after a lost connection; omission uses the defaults. */
|
|
69
|
+
reconnect?: ReconnectConfig;
|
|
70
|
+
}
|
|
71
|
+
/** Config for connecting to an MCP server over Streamable HTTP. */
|
|
72
|
+
export interface StreamableHttpConfig {
|
|
73
|
+
/** Selects Streamable HTTP transport. */
|
|
74
|
+
transport: 'streamable-http';
|
|
75
|
+
/** Stable local namespace — see {@link StdioConfig.serverName}. */
|
|
76
|
+
serverName: string;
|
|
77
|
+
/** MCP endpoint URL. */
|
|
78
|
+
url: string;
|
|
79
|
+
/** Additional headers attached to MCP requests. */
|
|
80
|
+
headers: Record<string, string>;
|
|
81
|
+
/** OAuth 2.1 authorization (default enabled — see {@link OAuthConfig}); set `enabled: false` to opt out. */
|
|
82
|
+
auth?: OAuthConfig;
|
|
83
|
+
/** Per-tool-call timeout in milliseconds. */
|
|
84
|
+
toolCallTimeoutMs: number;
|
|
85
|
+
/** Fail plugin activation when the initial connection or tool synchronization fails. */
|
|
86
|
+
failOnStartupError: boolean;
|
|
87
|
+
/** Automatic reconnect policy after a lost connection; omission uses the defaults. */
|
|
88
|
+
reconnect?: ReconnectConfig;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Config for connecting to an MCP server over the legacy HTTP+SSE transport
|
|
92
|
+
* (the pre-Streamable-HTTP protocol some older servers still speak). The SDK
|
|
93
|
+
* owns the protocol pairing; OAuth semantics mirror the Streamable HTTP case.
|
|
94
|
+
*/
|
|
95
|
+
export interface SseConfig {
|
|
96
|
+
/** Selects the legacy HTTP+SSE transport. */
|
|
97
|
+
transport: 'sse';
|
|
98
|
+
/** Stable local namespace — see {@link StdioConfig.serverName}. */
|
|
99
|
+
serverName: string;
|
|
100
|
+
/** SSE endpoint URL the server advertises. */
|
|
101
|
+
url: string;
|
|
102
|
+
/** Additional headers attached to both the SSE stream and endpoint requests. */
|
|
103
|
+
headers: Record<string, string>;
|
|
104
|
+
/** OAuth 2.1 authorization (default enabled — see {@link OAuthConfig}). */
|
|
105
|
+
auth?: OAuthConfig;
|
|
106
|
+
/** Per-tool-call timeout in milliseconds. */
|
|
107
|
+
toolCallTimeoutMs: number;
|
|
108
|
+
/** Fail plugin activation when the initial connection or tool synchronization fails. */
|
|
109
|
+
failOnStartupError: boolean;
|
|
110
|
+
/** Automatic reconnect policy after a lost connection; omission uses the defaults. */
|
|
111
|
+
reconnect?: ReconnectConfig;
|
|
112
|
+
}
|
|
113
|
+
/** Configuration for one stdio, Streamable HTTP, or legacy SSE MCP server. */
|
|
114
|
+
export type Config = StdioConfig | StreamableHttpConfig | SseConfig;
|
|
115
|
+
/**
|
|
116
|
+
* Validate one programmatically-built config. Programmatic construction
|
|
117
|
+
* bypasses any schema layer, so every invariant the bridge relies on is
|
|
118
|
+
* re-judged here — a misconfiguration rejects the mount with an actionable
|
|
119
|
+
* error before any connection is attempted.
|
|
120
|
+
* @param config - raw bridge config built by `mcp-config.ts`.
|
|
121
|
+
* @returns the same config, narrowed to the validated shape.
|
|
122
|
+
*/
|
|
123
|
+
export declare function validateConfig(config: Config): Config;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Connection supervisor: owns the MCP client/transport generations for one
|
|
3
|
+
* bridge instance, keeps the harness tool registry in sync with the live
|
|
4
|
+
* generation, and — when the connection drops — restarts the configured
|
|
5
|
+
* server with bounded exponential backoff.
|
|
6
|
+
*
|
|
7
|
+
* One outage shares one attempt budget (`maxAttempts` consecutive failed
|
|
8
|
+
* attempts, delays doubling from `initialDelayMs` up to `maxDelayMs`). A
|
|
9
|
+
* connection that stays up past the stability window closes the outage, so
|
|
10
|
+
* the next disconnect starts a fresh budget while a crash-looping server —
|
|
11
|
+
* even one whose connects briefly succeed — still exhausts the cap instead of
|
|
12
|
+
* restarting forever. Exhaustion unregisters the server's tools and stops;
|
|
13
|
+
* disposal (including HMR) is the only way back from that state.
|
|
14
|
+
*
|
|
15
|
+
* Ported from the harness `dsh-mcp-client` supervisor (including the OAuth
|
|
16
|
+
* browser-leg hold: a failed initialize during a pending browser
|
|
17
|
+
* authorization holds the generation open — up to the loopback callback
|
|
18
|
+
* budget — instead of tearing down the authorization the user is mid-way
|
|
19
|
+
* through approving).
|
|
20
|
+
*
|
|
21
|
+
* @module runtime/mcp-client/connection
|
|
22
|
+
*/
|
|
23
|
+
import type { ToolHost } from './tools.js';
|
|
24
|
+
import type { Config } from './config.js';
|
|
25
|
+
/** Automatic reconnect policy for one MCP server connection. */
|
|
26
|
+
export interface ReconnectConfig {
|
|
27
|
+
/** Reconnect automatically after a lost connection (default true). */
|
|
28
|
+
enabled?: boolean;
|
|
29
|
+
/** First reconnect delay in milliseconds; doubles per consecutive failed attempt (default 500). */
|
|
30
|
+
initialDelayMs?: number;
|
|
31
|
+
/** Backoff ceiling in milliseconds; also the uptime after which the attempt budget resets (default 30000). */
|
|
32
|
+
maxDelayMs?: number;
|
|
33
|
+
/** Consecutive failed attempts per outage before giving up for good (default 10). */
|
|
34
|
+
maxAttempts?: number;
|
|
35
|
+
}
|
|
36
|
+
/** Defaults shared by the config and {@link resolveReconnectPolicy}. */
|
|
37
|
+
export declare const RECONNECT_DEFAULTS: Required<ReconnectConfig>;
|
|
38
|
+
/** Fully resolved reconnect policy captured at plugin load. */
|
|
39
|
+
export type ResolvedReconnectPolicy = Readonly<Required<ReconnectConfig>>;
|
|
40
|
+
/**
|
|
41
|
+
* The one explicit resolve step from raw reconnect config to the policy the
|
|
42
|
+
* supervisor runs. Programmatic construction bypasses any schema layer, so
|
|
43
|
+
* every default and bound is re-judged here — misconfiguration fails the
|
|
44
|
+
* bridge instance at load.
|
|
45
|
+
*
|
|
46
|
+
* @param config - Raw `reconnect` config; omission uses the defaults.
|
|
47
|
+
* @param path - Diagnostic prefix naming the config location in thrown messages.
|
|
48
|
+
* @returns The frozen resolved policy.
|
|
49
|
+
*/
|
|
50
|
+
export declare function resolveReconnectPolicy(config: ReconnectConfig | undefined, path: string): ResolvedReconnectPolicy;
|
|
51
|
+
/** Result from the initial connection attempt, for startup-await semantics. */
|
|
52
|
+
export interface ConnectionOutcome {
|
|
53
|
+
/** If the initial connection or tool sync failed, the error; otherwise absent. */
|
|
54
|
+
error?: unknown;
|
|
55
|
+
}
|
|
56
|
+
/** Handle for one bridge instance's supervised connection. */
|
|
57
|
+
export interface ConnectionHandle {
|
|
58
|
+
/**
|
|
59
|
+
* Settles when the first connection attempt completes (success or failure).
|
|
60
|
+
* The supervisor enters its reconnect loop regardless; the caller decides
|
|
61
|
+
* whether a failed startup is fatal via `failOnStartupError`.
|
|
62
|
+
*/
|
|
63
|
+
ready: Promise<ConnectionOutcome>;
|
|
64
|
+
/**
|
|
65
|
+
* Stop reconnection, close the live client, wait for the in-flight attempt
|
|
66
|
+
* and queued tool syncs to quiesce, then unregister every tool this server
|
|
67
|
+
* still owns.
|
|
68
|
+
*/
|
|
69
|
+
dispose(): Promise<void>;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Start the supervised connection for one MCP server and keep it alive per
|
|
73
|
+
* the reconnect policy.
|
|
74
|
+
*
|
|
75
|
+
* @param host - The bridge host providing the tool registry and logger.
|
|
76
|
+
* @param config - Resolved bridge config selecting the transport and server identity.
|
|
77
|
+
* @param policy - Resolved reconnect policy from {@link resolveReconnectPolicy}.
|
|
78
|
+
* @param credentials - The composition's credential store, when mounted; OAuth
|
|
79
|
+
* state persists only when it is.
|
|
80
|
+
* @returns Handle with a `ready` promise for startup-await and a `dispose` for teardown.
|
|
81
|
+
*/
|
|
82
|
+
export declare function startConnection(host: ToolHost, config: Config, policy: ResolvedReconnectPolicy, credentials?: unknown): ConnectionHandle;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local ports of the tiny host seams the self-built MCP client needs.
|
|
3
|
+
*
|
|
4
|
+
* The market plugin must stay self-contained at runtime: it cannot import
|
|
5
|
+
* host-internal packages that are not published at compatible versions, and
|
|
6
|
+
* every one of these seams is a small pure function or constant. Each port
|
|
7
|
+
* below is copied verbatim (semantics, not prose) from the harness source at
|
|
8
|
+
* `0.1.1-rc.2` and carries the upstream reference it came from, so a future
|
|
9
|
+
* host change can be re-diffed against `~/workspace/deepseek-harness`.
|
|
10
|
+
*
|
|
11
|
+
* Anything larger than a handful of lines (the JSON Schema subset validator)
|
|
12
|
+
* lives in its own module.
|
|
13
|
+
*
|
|
14
|
+
* @module runtime/mcp-client/host-seams
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* The largest delay `setTimeout` accepts without overflowing to a 1ms clamp
|
|
18
|
+
* (2^31 - 1). Shared guard for reconnect and callback timers.
|
|
19
|
+
*/
|
|
20
|
+
export declare const MAX_TIMER_DELAY_MS = 2147483647;
|
|
21
|
+
/** `DSH_*` environment namespace owned by the harness. */
|
|
22
|
+
export declare const DSH_ENV_PREFIX: "DSH_";
|
|
23
|
+
/**
|
|
24
|
+
* Credential-shaped environment names are NOT forwarded to MCP server child
|
|
25
|
+
* processes (the harness's own `DEEPSEEK_API_KEY`/secrets must not leak into a
|
|
26
|
+
* spawned server implicitly). Deliberately supplied env entries survive
|
|
27
|
+
* because the config's explicit env merges after the scrub.
|
|
28
|
+
*/
|
|
29
|
+
export declare const SENSITIVE_ENV_PATTERN: RegExp;
|
|
30
|
+
/**
|
|
31
|
+
* The ambient parent environment minus credential-shaped names and minus all
|
|
32
|
+
* `DSH_*` names — the canonical base every harness child starts from. `PATH`,
|
|
33
|
+
* `HOME`, locale, and proxy variables survive, so child CLIs run normally;
|
|
34
|
+
* harness identity never leaks implicitly. Both scrubs match case-insensitively:
|
|
35
|
+
* Windows environment names are case-insensitive, so a parent `dsh_*` entry
|
|
36
|
+
* would otherwise survive and read back as `$env:DSH_*` in the child.
|
|
37
|
+
* @returns a fresh environment object safe to hand to a child spawn.
|
|
38
|
+
*/
|
|
39
|
+
export declare function scrubbedParentEnv(): Record<string, string>;
|
|
40
|
+
/** A credential record key: `<scope>/<id>` with both segments in the grammar below. */
|
|
41
|
+
declare const credentialKeyBrand: unique symbol;
|
|
42
|
+
export type CredentialKey = string & {
|
|
43
|
+
readonly [credentialKeyBrand]: true;
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* Brand a scope and an id as a {@link CredentialKey}.
|
|
47
|
+
* @param scope - the owning plugin's registered name (here: the `mcp-auth` record scope).
|
|
48
|
+
* @param id - the plugin's own addressing unit (here: the folded serverName).
|
|
49
|
+
* @returns the branded key.
|
|
50
|
+
* @throws TypeError when either segment is not a lowercase hyphenated identifier.
|
|
51
|
+
*/
|
|
52
|
+
export declare function credentialKey(scope: string, id: string): CredentialKey;
|
|
53
|
+
/**
|
|
54
|
+
* Structural mirror of the host `CredentialProvider` record surface the OAuth
|
|
55
|
+
* provider needs. Typed structurally because the store arrives via the
|
|
56
|
+
* optional `credentials` service; the OAuth provider narrows at runtime and
|
|
57
|
+
* falls back to memory-only state when the service is absent.
|
|
58
|
+
*/
|
|
59
|
+
export interface CredentialRecordStore {
|
|
60
|
+
describeRecord(key: CredentialKey): Promise<{
|
|
61
|
+
configured: boolean;
|
|
62
|
+
writable: boolean;
|
|
63
|
+
}>;
|
|
64
|
+
readRecord(key: CredentialKey): Promise<unknown>;
|
|
65
|
+
modifyRecord(key: CredentialKey, mutate: (current: unknown) => Promise<unknown>): Promise<unknown>;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Distinguish caller-correctable image admission failures from storage faults,
|
|
69
|
+
* so an admission refusal projects text diagnostics while a storage fault is
|
|
70
|
+
* reported as such.
|
|
71
|
+
*/
|
|
72
|
+
export declare function isImageAdmissionError(error: unknown): boolean;
|
|
73
|
+
/** Raster formats supported by the durable attachment vocabulary. */
|
|
74
|
+
export type ImageMediaType = 'image/png' | 'image/jpeg' | 'image/webp' | 'image/gif';
|
|
75
|
+
/** One decoded image handed to the host attachment store. */
|
|
76
|
+
export interface SaveImageAttachment {
|
|
77
|
+
data: Buffer;
|
|
78
|
+
mediaType: ImageMediaType;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Structural mirror of the host `AttachmentStore.saveImages` surface. The
|
|
82
|
+
* returned refs are opaque host values that ride inside `ContentBlock`
|
|
83
|
+
* image blocks; typed `unknown` here and narrowed by the model content
|
|
84
|
+
* contract at the projection site.
|
|
85
|
+
*/
|
|
86
|
+
export interface AttachmentStoreLike {
|
|
87
|
+
saveImages(images: readonly SaveImageAttachment[]): Promise<readonly unknown[]>;
|
|
88
|
+
}
|
|
89
|
+
export {};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local port of the harness's enforced JSON Schema subset validator
|
|
3
|
+
* (`@deepseek-ai/dsh-tools` `json-schema.ts`, rc.2) — narrowed to the one use
|
|
4
|
+
* the MCP bridge has: deciding whether an MCP server's advertised
|
|
5
|
+
* `outputSchema` may ride a tool registration as a structured-output schema.
|
|
6
|
+
*
|
|
7
|
+
* The upstream validator is 656 lines with realm-safe JSON intrinsics; this
|
|
8
|
+
* port keeps the *contract* — a closed keyword table, one scalar `type`,
|
|
9
|
+
* nested structure, and "unsupported keywords reject rather than pass
|
|
10
|
+
* unenforced" — with a conservative structural check. A schema this check
|
|
11
|
+
* wrongly rejects only loses structured typing (the bridge falls back to
|
|
12
|
+
* untyped JSON, exactly the upstream failure mode); one it wrongly accepts
|
|
13
|
+
* would flow into the host registry, so every branch here fails closed.
|
|
14
|
+
*
|
|
15
|
+
* @module runtime/mcp-client/json-schema-subset
|
|
16
|
+
*/
|
|
17
|
+
/** Scalar JSON values supported by `enum` and `const`. */
|
|
18
|
+
export type JsonSchemaScalar = string | number | boolean | null;
|
|
19
|
+
/** Single-type keywords accepted by the enforced subset. */
|
|
20
|
+
export type JsonSchemaType = 'object' | 'array' | 'string' | 'number' | 'integer' | 'boolean' | 'null';
|
|
21
|
+
/**
|
|
22
|
+
* One raw JSON Schema node in the enforced subset. The optional fields express
|
|
23
|
+
* the external wire schema; {@link assertSupportedJsonSchema} rejects invalid
|
|
24
|
+
* combinations before a caller treats the node as trusted.
|
|
25
|
+
*/
|
|
26
|
+
export interface JsonSchemaNode {
|
|
27
|
+
/** Omit with no constraints for any JSON value, or use `oneOf`. */
|
|
28
|
+
type?: JsonSchemaType;
|
|
29
|
+
/** Exactly one branch must validate; at least two branches are required. */
|
|
30
|
+
oneOf?: JsonSchemaNode[];
|
|
31
|
+
/** Nested property schemas (`type: 'object'` only). */
|
|
32
|
+
properties?: Record<string, JsonSchemaNode>;
|
|
33
|
+
/** Required property names; each must appear in `properties`. */
|
|
34
|
+
required?: string[];
|
|
35
|
+
/** `false` rejects undeclared keys; absent/`true` follows JSON Schema's open default. */
|
|
36
|
+
additionalProperties?: boolean;
|
|
37
|
+
/** Item schema (`type: 'array'` only); absent accepts any JSON item. */
|
|
38
|
+
items?: JsonSchemaNode;
|
|
39
|
+
/** Allowed values for a scalar node. */
|
|
40
|
+
enum?: JsonSchemaScalar[];
|
|
41
|
+
/** The single allowed value for a scalar node. */
|
|
42
|
+
const?: JsonSchemaScalar;
|
|
43
|
+
/** Annotation, ignored for validation. */
|
|
44
|
+
description?: string;
|
|
45
|
+
/** Annotation, ignored for validation. */
|
|
46
|
+
title?: string;
|
|
47
|
+
/** Annotation, ignored for validation but required to be lossless JSON. */
|
|
48
|
+
default?: JsonSchemaScalar | Record<string, unknown> | unknown[];
|
|
49
|
+
/** Annotation, ignored for validation but required to be lossless JSON. */
|
|
50
|
+
examples?: JsonSchemaScalar | Record<string, unknown> | unknown[];
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Assert that a raw schema uses only the enforced subset. Annotation-only
|
|
54
|
+
* schemas are accepted as the standard unconstrained-JSON form.
|
|
55
|
+
* @param schema - untrusted raw JSON Schema.
|
|
56
|
+
* @throws Error listing every violation when the schema leaves the subset.
|
|
57
|
+
*/
|
|
58
|
+
export declare function assertSupportedJsonSchema(schema: unknown): asserts schema is JsonSchemaNode;
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OAuth 2.1 authorization-code support for Streamable HTTP (and legacy SSE)
|
|
3
|
+
* MCP servers that answer `401` with an OAuth challenge (`WWW-Authenticate:
|
|
4
|
+
* Bearer resource_metadata="…"` — the MCP authorization spec's discovery
|
|
5
|
+
* anchor).
|
|
6
|
+
*
|
|
7
|
+
* The SDK owns the protocol: its `auth()` helper performs RFC 9728 protected-
|
|
8
|
+
* resource discovery, authorization-server metadata lookup, RFC 7591 dynamic
|
|
9
|
+
* client registration, PKCE, token exchange, and refresh, and the HTTP
|
|
10
|
+
* transports invoke it on the first `401` through the
|
|
11
|
+
* {@link OAuthClientProvider} this module implements. This module owns the two
|
|
12
|
+
* parts the SDK leaves to the application: durable storage of client and token
|
|
13
|
+
* state, and the human leg of the flow — a loopback redirect that captures
|
|
14
|
+
* the authorization code without caller-side wiring.
|
|
15
|
+
*
|
|
16
|
+
* Tokens and registered client information persist as a `grant` credential
|
|
17
|
+
* record under `mcp-auth/<serverName>` through the harness credentials seam
|
|
18
|
+
* when one is mounted, so they land in the managed `.credentials.yaml`
|
|
19
|
+
* document (mode `0600`, file-locked, refresh-safe across processes) and are
|
|
20
|
+
* visible to the same surface that manages every other credential. Without a
|
|
21
|
+
* credentials service the provider still completes flows in memory; tokens
|
|
22
|
+
* then last exactly as long as the process.
|
|
23
|
+
*
|
|
24
|
+
* Ported from the harness `dsh-mcp-client` OAuth implementation (upstream
|
|
25
|
+
* commits archived at `docs/upstream-proposal/patches/`); the record format is
|
|
26
|
+
* byte-compatible, so grants written by either implementation are interchangeable.
|
|
27
|
+
*
|
|
28
|
+
* @module runtime/mcp-client/oauth
|
|
29
|
+
*/
|
|
30
|
+
import { type OAuthClientProvider } from '@modelcontextprotocol/sdk/client/auth.js';
|
|
31
|
+
import type { OAuthClientInformationMixed, OAuthClientMetadata, OAuthTokens } from '@modelcontextprotocol/sdk/shared/auth.js';
|
|
32
|
+
import type { OAuthStorageConfig } from './config.js';
|
|
33
|
+
/** Credential-record scope for MCP OAuth state; per-server ids follow it. */
|
|
34
|
+
export declare const MCP_AUTH_RECORD_SCOPE = "mcp-auth";
|
|
35
|
+
/**
|
|
36
|
+
* Error thrown when the human leg fails or is abandoned: the browser never
|
|
37
|
+
* returned, the callback carried an OAuth error, or no browser could open.
|
|
38
|
+
* The transport surfaces it from `connect`; the supervisor's reconnect loop
|
|
39
|
+
* re-enters the flow — which reopens the browser — on a later attempt.
|
|
40
|
+
*/
|
|
41
|
+
export declare class AuthorizationAbortedError extends Error {
|
|
42
|
+
constructor(message: string, options?: ErrorOptions);
|
|
43
|
+
}
|
|
44
|
+
/** Opens the platform browser; injected in tests to record the URL instead. */
|
|
45
|
+
export type BrowserOpener = (authorizationUrl: URL, serverName: string, log: (message: string) => void) => Promise<void>;
|
|
46
|
+
/** The real opener: platform browser or default URL handler. */
|
|
47
|
+
export declare const platformBrowserOpener: BrowserOpener;
|
|
48
|
+
/**
|
|
49
|
+
* {@link OAuthClientProvider} over the harness credentials seam (or process
|
|
50
|
+
* memory when the seam is absent), with a loopback redirect that opens the
|
|
51
|
+
* system browser.
|
|
52
|
+
*/
|
|
53
|
+
export declare class LoopbackOAuthClientProvider implements OAuthClientProvider {
|
|
54
|
+
private readonly serverName;
|
|
55
|
+
private readonly storage;
|
|
56
|
+
private readonly scope;
|
|
57
|
+
private readonly log;
|
|
58
|
+
private readonly opener;
|
|
59
|
+
private readonly recordKey;
|
|
60
|
+
/** Credential store mounted in this composition; absence means memory-only. */
|
|
61
|
+
private readonly store;
|
|
62
|
+
/** In-memory fallback and cache over the store's durable state. */
|
|
63
|
+
private providerState;
|
|
64
|
+
/** The transport generation whose `finishAuth` completes the browser leg. */
|
|
65
|
+
private activeTransport;
|
|
66
|
+
/** Resolves `true` when the leg saved tokens, `false` when it failed; undefined while no leg is open. */
|
|
67
|
+
private browserLegSettled;
|
|
68
|
+
private callbackServer;
|
|
69
|
+
private callbackTimer;
|
|
70
|
+
private boundPort;
|
|
71
|
+
/** Serializes store writes so a rapid refresh cannot interleave read-modify-writes. */
|
|
72
|
+
private writeChain;
|
|
73
|
+
constructor(serverName: string, storage: OAuthStorageConfig, scope: string | undefined, log: (message: string) => void, store: unknown, opener?: BrowserOpener);
|
|
74
|
+
get redirectUrl(): string;
|
|
75
|
+
get clientMetadata(): OAuthClientMetadata;
|
|
76
|
+
clientInformation(): Promise<OAuthClientInformationMixed | undefined>;
|
|
77
|
+
saveClientInformation(clientInformation: OAuthClientInformationMixed): Promise<void>;
|
|
78
|
+
tokens(): Promise<OAuthTokens | undefined>;
|
|
79
|
+
saveTokens(tokens: OAuthTokens): Promise<void>;
|
|
80
|
+
/**
|
|
81
|
+
* Bind the loopback server, open the browser, and complete the code
|
|
82
|
+
* exchange into the bound transport generation. Resolves only after tokens
|
|
83
|
+
* are saved; an OAuth `error` redirect, a timeout, or a listener failure
|
|
84
|
+
* rejects with {@link AuthorizationAbortedError}.
|
|
85
|
+
*/
|
|
86
|
+
redirectToAuthorization(authorizationUrl: URL): Promise<void>;
|
|
87
|
+
/** Whether a browser leg is open and the code exchange is still pending. */
|
|
88
|
+
get awaitingBrowser(): boolean;
|
|
89
|
+
/**
|
|
90
|
+
* Resolves once the pending browser leg finishes (tokens saved or the leg
|
|
91
|
+
* failed); undefined while no leg is open. The supervisor holds the
|
|
92
|
+
* connection generation — instead of timing out and respawning, which would
|
|
93
|
+
* abandon the leg the user is mid-way through approving — until this
|
|
94
|
+
* settles.
|
|
95
|
+
*/
|
|
96
|
+
get browserLeg(): Promise<boolean> | undefined;
|
|
97
|
+
/**
|
|
98
|
+
* Register the transport generation whose `finishAuth` completes the flow.
|
|
99
|
+
* The transport calls `redirectToAuthorization` during `connect`; binding
|
|
100
|
+
* it here keeps the code exchange inside the generation that owns the
|
|
101
|
+
* connection, per the SDK's per-connection session model.
|
|
102
|
+
*/
|
|
103
|
+
bindTransport(transport: {
|
|
104
|
+
finishAuth(code: string): Promise<void>;
|
|
105
|
+
}): void;
|
|
106
|
+
/** Abandon any previously bound browser leg before a new one starts. */
|
|
107
|
+
resetCallback(): void;
|
|
108
|
+
saveCodeVerifier(codeVerifier: string): Promise<void>;
|
|
109
|
+
codeVerifier(): Promise<string>;
|
|
110
|
+
invalidateCredentials(scope: 'all' | 'client' | 'tokens' | 'verifier' | 'discovery'): Promise<void>;
|
|
111
|
+
private load;
|
|
112
|
+
private mutate;
|
|
113
|
+
/**
|
|
114
|
+
* Bind a loopback listener, open the browser at `authorizationUrl` (its
|
|
115
|
+
* `redirect_uri` is rewritten to the bound port), and resolve with the code
|
|
116
|
+
* the user agent lands back with.
|
|
117
|
+
*/
|
|
118
|
+
private awaitCallback;
|
|
119
|
+
private handleCallbackRequest;
|
|
120
|
+
private clearCallbackTimer;
|
|
121
|
+
}
|