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.
Files changed (86) hide show
  1. package/README.md +29 -3
  2. package/README.zh.md +29 -3
  3. package/client/client.js +1388 -204
  4. package/client/client.js.map +1 -1
  5. package/lib/application/catalog.js +537 -71
  6. package/lib/application/details.js +39 -12
  7. package/lib/catalog/archive.js +341 -0
  8. package/lib/catalog/git.js +85 -12
  9. package/lib/catalog/legacy-root-migration.js +51 -0
  10. package/lib/catalog/lsp-spec.js +87 -0
  11. package/lib/catalog/manifests.js +84 -32
  12. package/lib/catalog/paths.js +65 -7
  13. package/lib/catalog/scan-pipeline.js +50 -0
  14. package/lib/catalog/scan-resolvers.js +402 -0
  15. package/lib/catalog/source-catalog.js +22 -8
  16. package/lib/catalog/suite-scanner.js +5 -176
  17. package/lib/catalog/surfaces.js +80 -30
  18. package/lib/contracts/lsp-status.js +2 -0
  19. package/lib/contracts/market.js +8 -1
  20. package/lib/index.js +118 -13
  21. package/lib/model/state.js +14 -1
  22. package/lib/model/types.js +21 -2
  23. package/lib/routes.js +88 -6
  24. package/lib/runtime/commands-mounts.js +6 -2
  25. package/lib/runtime/hooks-mounts.js +9 -6
  26. package/lib/runtime/lsp-direct-config.js +44 -0
  27. package/lib/runtime/lsp-mounts.js +236 -0
  28. package/lib/runtime/lsp-status.js +96 -0
  29. package/lib/runtime/mcp-backend.js +60 -0
  30. package/lib/runtime/mcp-client/bridge.js +126 -0
  31. package/lib/runtime/mcp-client/config.js +42 -0
  32. package/lib/runtime/mcp-client/connection.js +380 -0
  33. package/lib/runtime/mcp-client/host-seams.js +88 -0
  34. package/lib/runtime/mcp-client/json-schema-subset.js +181 -0
  35. package/lib/runtime/mcp-client/oauth.js +379 -0
  36. package/lib/runtime/mcp-client/tools.js +432 -0
  37. package/lib/runtime/mcp-client/transport.js +103 -0
  38. package/lib/runtime/mcp-config.js +196 -54
  39. package/lib/runtime/mcp-credentials.js +20 -0
  40. package/lib/runtime/mcp-mounts.js +237 -27
  41. package/lib/runtime/mcp-overrides.js +64 -6
  42. package/lib/runtime/mcp-redaction.js +92 -0
  43. package/lib/runtime/mcp-status.js +76 -32
  44. package/lib/runtime/reconciler.js +35 -1
  45. package/lib/runtime/regions.js +56 -0
  46. package/lib/types/application/catalog.d.ts +191 -14
  47. package/lib/types/application/details.d.ts +1 -6
  48. package/lib/types/application/queries.d.ts +28 -1
  49. package/lib/types/catalog/archive.d.ts +48 -0
  50. package/lib/types/catalog/git.d.ts +27 -3
  51. package/lib/types/catalog/legacy-root-migration.d.ts +14 -0
  52. package/lib/types/catalog/lsp-spec.d.ts +14 -0
  53. package/lib/types/catalog/manifests.d.ts +16 -1
  54. package/lib/types/catalog/paths.d.ts +23 -2
  55. package/lib/types/catalog/scan-pipeline.d.ts +76 -0
  56. package/lib/types/catalog/scan-resolvers.d.ts +81 -0
  57. package/lib/types/catalog/source-catalog.d.ts +5 -0
  58. package/lib/types/catalog/suite-scanner.d.ts +14 -7
  59. package/lib/types/catalog/surfaces.d.ts +3 -3
  60. package/lib/types/contracts/lsp-status.d.ts +53 -0
  61. package/lib/types/contracts/market.d.ts +55 -1
  62. package/lib/types/contracts/mcp-status.d.ts +14 -2
  63. package/lib/types/index.d.ts +24 -8
  64. package/lib/types/model/types.d.ts +52 -3
  65. package/lib/types/runtime/lsp-direct-config.d.ts +12 -0
  66. package/lib/types/runtime/lsp-mounts.d.ts +89 -0
  67. package/lib/types/runtime/lsp-status.d.ts +30 -0
  68. package/lib/types/runtime/mcp-backend.d.ts +49 -0
  69. package/lib/types/runtime/mcp-client/bridge.d.ts +37 -0
  70. package/lib/types/runtime/mcp-client/config.d.ts +123 -0
  71. package/lib/types/runtime/mcp-client/connection.d.ts +82 -0
  72. package/lib/types/runtime/mcp-client/host-seams.d.ts +89 -0
  73. package/lib/types/runtime/mcp-client/json-schema-subset.d.ts +58 -0
  74. package/lib/types/runtime/mcp-client/oauth.d.ts +121 -0
  75. package/lib/types/runtime/mcp-client/tools.d.ts +136 -0
  76. package/lib/types/runtime/mcp-client/transport.d.ts +36 -0
  77. package/lib/types/runtime/mcp-config.d.ts +29 -12
  78. package/lib/types/runtime/mcp-credentials.d.ts +9 -0
  79. package/lib/types/runtime/mcp-mounts.d.ts +56 -6
  80. package/lib/types/runtime/mcp-overrides.d.ts +23 -4
  81. package/lib/types/runtime/mcp-redaction.d.ts +7 -0
  82. package/lib/types/runtime/mcp-status.d.ts +4 -2
  83. package/lib/types/runtime/reconciler.d.ts +22 -4
  84. package/lib/types/runtime/regions.d.ts +37 -0
  85. package/package.json +15 -11
  86. 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
+ }