@wrongstack/mcp 1.0.19 → 1.0.24
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/dist/client-capabilities.d.ts +15 -0
- package/dist/client-process.d.ts +6 -0
- package/dist/client-types.d.ts +62 -0
- package/dist/client.d.ts +4 -47
- package/dist/index.js +264 -184
- package/dist/manage.d.ts +1 -0
- package/dist/manifest-cache.d.ts +1 -0
- package/dist/server.d.ts +6 -3
- package/package.json +2 -2
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { MCPPageOptions, MCPRequestOptions } from './client-types.js';
|
|
2
|
+
import { type MCPGetPromptResult, type MCPListPromptsResult, type MCPListResourcesResult, type MCPListResourceTemplatesResult, type MCPReadResourceResult } from './protocol.js';
|
|
3
|
+
export declare class MCPCapabilityClient {
|
|
4
|
+
private readonly requestCapability;
|
|
5
|
+
private readonly requireResourceSubscriptions;
|
|
6
|
+
constructor(requestCapability: <T>(capability: 'resources' | 'prompts', method: string, params: unknown, parse: (value: unknown) => T, opts: MCPRequestOptions) => Promise<T>, requireResourceSubscriptions: (method: string) => void);
|
|
7
|
+
listResources(opts?: MCPPageOptions): Promise<MCPListResourcesResult>;
|
|
8
|
+
listResourceTemplates(opts?: MCPPageOptions): Promise<MCPListResourceTemplatesResult>;
|
|
9
|
+
readResource(uri: string, opts?: MCPRequestOptions): Promise<MCPReadResourceResult>;
|
|
10
|
+
subscribeResource(uri: string, opts?: MCPRequestOptions): Promise<void>;
|
|
11
|
+
unsubscribeResource(uri: string, opts?: MCPRequestOptions): Promise<void>;
|
|
12
|
+
listPrompts(opts?: MCPPageOptions): Promise<MCPListPromptsResult>;
|
|
13
|
+
getPrompt(name: string, args?: Record<string, string> | undefined, opts?: MCPRequestOptions): Promise<MCPGetPromptResult>;
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=client-capabilities.d.ts.map
|
package/dist/client-process.d.ts
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
import { type ChildProcess } from 'node:child_process';
|
|
2
|
+
/** Resolve an HTTP Bearer token without ever persisting the secret itself. */
|
|
3
|
+
export declare function resolveHttpBearerHeaders(options: {
|
|
4
|
+
headers?: Record<string, string> | undefined;
|
|
5
|
+
bearerTokenEnv?: string | undefined;
|
|
6
|
+
name: string;
|
|
7
|
+
}, env?: NodeJS.ProcessEnv): Record<string, string> | undefined;
|
|
2
8
|
/**
|
|
3
9
|
* Force-kill a child and its descendants. On Windows a stdio server is launched
|
|
4
10
|
* through a `.cmd` shim with `shell: true`, so `child` is the `cmd.exe` wrapper
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { MCPAuthorizationProvider } from './authorization.js';
|
|
2
|
+
import type { MCPTool } from './contracts.js';
|
|
3
|
+
export type Transport = 'stdio' | 'sse' | 'streamable-http';
|
|
4
|
+
export interface MCPClientOptions {
|
|
5
|
+
name: string;
|
|
6
|
+
transport: Transport;
|
|
7
|
+
command?: string | undefined;
|
|
8
|
+
args?: string[] | undefined;
|
|
9
|
+
env?: Record<string, string> | undefined;
|
|
10
|
+
url?: string | undefined;
|
|
11
|
+
headers?: Record<string, string> | undefined;
|
|
12
|
+
/** Resolve an HTTP Bearer token from this environment variable at connect time. */
|
|
13
|
+
bearerTokenEnv?: string | undefined;
|
|
14
|
+
startupTimeoutMs?: number | undefined;
|
|
15
|
+
requestTimeoutMs?: number | undefined;
|
|
16
|
+
/**
|
|
17
|
+
* Working directory for a stdio server process. Presets address the project
|
|
18
|
+
* as `--project-root .` / `server-filesystem .`, which resolve against the
|
|
19
|
+
* spawn cwd — without this the child inherited WrongStack's PROCESS cwd, so
|
|
20
|
+
* a host serving a project from elsewhere (WebUI, ACP sessions with their
|
|
21
|
+
* own cwd) pointed those servers at the wrong directory.
|
|
22
|
+
*/
|
|
23
|
+
cwd?: string | undefined;
|
|
24
|
+
/** Host-owned, vault-backed authorization for HTTP transports. */
|
|
25
|
+
authorizationProvider?: MCPAuthorizationProvider | undefined;
|
|
26
|
+
/**
|
|
27
|
+
* Allowlist of env var names to forward from the parent process (process.env)
|
|
28
|
+
* to the child. Values are resolved at spawn time and merged into `env`
|
|
29
|
+
* via the `extra` path of `buildChildEnv` (unfiltered). This is how built-in
|
|
30
|
+
* MCP server presets (GitHub, Slack, Brave Search, …) get their API tokens
|
|
31
|
+
* without storing them in config.json or being scrubbed by the secret filter.
|
|
32
|
+
*/
|
|
33
|
+
passthroughEnv?: string[] | undefined;
|
|
34
|
+
/**
|
|
35
|
+
* Resolution-bound private-network policy for HTTP transports. Default:
|
|
36
|
+
* private/LAN targets are blocked at dial time (DNS-rebinding safe); the
|
|
37
|
+
* flag opts this server in. See MCPServerConfig.allowPrivateNetworks.
|
|
38
|
+
*/
|
|
39
|
+
allowPrivateNetworks?: boolean | undefined;
|
|
40
|
+
}
|
|
41
|
+
export interface MCPRequestOptions {
|
|
42
|
+
signal?: AbortSignal | undefined;
|
|
43
|
+
}
|
|
44
|
+
export interface MCPPageOptions extends MCPRequestOptions {
|
|
45
|
+
cursor?: string | undefined;
|
|
46
|
+
}
|
|
47
|
+
export interface JsonRpcRequest {
|
|
48
|
+
jsonrpc: '2.0';
|
|
49
|
+
id: number;
|
|
50
|
+
method: string;
|
|
51
|
+
params?: unknown | undefined;
|
|
52
|
+
}
|
|
53
|
+
export type JsonRpcServerRequest = {
|
|
54
|
+
jsonrpc: '2.0';
|
|
55
|
+
id: number | string;
|
|
56
|
+
method: string;
|
|
57
|
+
params?: unknown | undefined;
|
|
58
|
+
};
|
|
59
|
+
export type ExitListener = (name: string, code: number | null, signal: string | null) => void;
|
|
60
|
+
export type ToolsChangedListener = (name: string, tools: MCPTool[]) => void;
|
|
61
|
+
export type MCPListChangedListener = (name: string) => void;
|
|
62
|
+
//# sourceMappingURL=client-types.d.ts.map
|
package/dist/client.d.ts
CHANGED
|
@@ -1,56 +1,12 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ExitListener, MCPClientOptions, MCPListChangedListener, MCPPageOptions, MCPRequestOptions, ToolsChangedListener } from './client-types.js';
|
|
2
|
+
import type { ConnectionState, JsonRpcResponse, MCPTool, ToolCallResult } from './contracts.js';
|
|
2
3
|
import { type MCPGetPromptResult, type MCPListPromptsResult, type MCPListResourcesResult, type MCPListResourceTemplatesResult, type MCPReadResourceResult, type MCPServerMetadata } from './protocol.js';
|
|
3
4
|
export { forceKillTree } from './client-process.js';
|
|
4
5
|
export { quoteWindowsArg } from './client-protocol-helpers.js';
|
|
5
|
-
export type Transport = 'stdio' | 'sse' | 'streamable-http';
|
|
6
|
-
export interface MCPClientOptions {
|
|
7
|
-
name: string;
|
|
8
|
-
transport: Transport;
|
|
9
|
-
command?: string | undefined;
|
|
10
|
-
args?: string[] | undefined;
|
|
11
|
-
env?: Record<string, string> | undefined;
|
|
12
|
-
url?: string | undefined;
|
|
13
|
-
headers?: Record<string, string> | undefined;
|
|
14
|
-
startupTimeoutMs?: number | undefined;
|
|
15
|
-
requestTimeoutMs?: number | undefined;
|
|
16
|
-
/**
|
|
17
|
-
* Working directory for a stdio server process. Presets address the project
|
|
18
|
-
* as `--project-root .` / `server-filesystem .`, which resolve against the
|
|
19
|
-
* spawn cwd — without this the child inherited WrongStack's PROCESS cwd, so
|
|
20
|
-
* a host serving a project from elsewhere (WebUI, ACP sessions with their
|
|
21
|
-
* own cwd) pointed those servers at the wrong directory.
|
|
22
|
-
*/
|
|
23
|
-
cwd?: string | undefined;
|
|
24
|
-
/** Host-owned, vault-backed authorization for HTTP transports. */
|
|
25
|
-
authorizationProvider?: MCPAuthorizationProvider | undefined;
|
|
26
|
-
/**
|
|
27
|
-
* Allowlist of env var names to forward from the parent process (process.env)
|
|
28
|
-
* to the child. Values are resolved at spawn time and merged into `env`
|
|
29
|
-
* via the `extra` path of `buildChildEnv` (unfiltered). This is how built-in
|
|
30
|
-
* MCP server presets (GitHub, Slack, Brave Search, …) get their API tokens
|
|
31
|
-
* without storing them in config.json or being scrubbed by the secret filter.
|
|
32
|
-
*/
|
|
33
|
-
passthroughEnv?: string[] | undefined;
|
|
34
|
-
/**
|
|
35
|
-
* Resolution-bound private-network policy for HTTP transports. Default:
|
|
36
|
-
* private/LAN targets are blocked at dial time (DNS-rebinding safe); the
|
|
37
|
-
* flag opts this server in. See MCPServerConfig.allowPrivateNetworks.
|
|
38
|
-
*/
|
|
39
|
-
allowPrivateNetworks?: boolean | undefined;
|
|
40
|
-
}
|
|
41
|
-
import type { ConnectionState, JsonRpcResponse, MCPTool, ToolCallResult } from './contracts.js';
|
|
42
6
|
export type { ConnectionState, JsonRpcResponse, MCPTool, ToolCallResult };
|
|
43
|
-
export interface MCPRequestOptions {
|
|
44
|
-
signal?: AbortSignal | undefined;
|
|
45
|
-
}
|
|
46
|
-
export interface MCPPageOptions extends MCPRequestOptions {
|
|
47
|
-
cursor?: string | undefined;
|
|
48
|
-
}
|
|
49
|
-
type ExitListener = (name: string, code: number | null, signal: string | null) => void;
|
|
50
|
-
type ToolsChangedListener = (name: string, tools: MCPTool[]) => void;
|
|
51
|
-
export type MCPListChangedListener = (name: string) => void;
|
|
52
7
|
export declare class MCPClient {
|
|
53
8
|
readonly opts: MCPClientOptions;
|
|
9
|
+
private readonly capabilityClient;
|
|
54
10
|
/**
|
|
55
11
|
* Maximum bytes the rx buffer may accumulate before the connection is
|
|
56
12
|
* forcefully closed. A well-behaved JSON-RPC server emits newline-delimited
|
|
@@ -161,4 +117,5 @@ export declare class MCPClient {
|
|
|
161
117
|
removePromptsChangedListener(listener: MCPListChangedListener): void;
|
|
162
118
|
private emitCapabilityChanged;
|
|
163
119
|
}
|
|
120
|
+
export type { MCPClientOptions, MCPListChangedListener, MCPPageOptions, MCPRequestOptions, Transport, } from './client-types.js';
|
|
164
121
|
//# sourceMappingURL=client.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -831,40 +831,6 @@ import { spawn as spawn2 } from "node:child_process";
|
|
|
831
831
|
import { StringDecoder } from "node:string_decoder";
|
|
832
832
|
import { buildChildEnv as buildChildEnv2, buildWin32CmdShimInvocation, toErrorMessage } from "@wrongstack/core/utils";
|
|
833
833
|
|
|
834
|
-
// src/client-process.ts
|
|
835
|
-
import { spawn } from "node:child_process";
|
|
836
|
-
import { buildChildEnv } from "@wrongstack/core/utils";
|
|
837
|
-
function forceKillTree(child) {
|
|
838
|
-
if (child.pid === void 0) {
|
|
839
|
-
try {
|
|
840
|
-
child.kill("SIGKILL");
|
|
841
|
-
} catch {
|
|
842
|
-
}
|
|
843
|
-
return;
|
|
844
|
-
}
|
|
845
|
-
if (process.platform === "win32") {
|
|
846
|
-
const killer = spawn("taskkill", ["/pid", String(child.pid), "/T", "/F"], {
|
|
847
|
-
stdio: "ignore",
|
|
848
|
-
// H-8 convention (spawn-convention test): taskkill needs PATH only —
|
|
849
|
-
// do not hand it the credential-bearing parent environment.
|
|
850
|
-
env: buildChildEnv(),
|
|
851
|
-
windowsHide: true
|
|
852
|
-
});
|
|
853
|
-
killer.once("error", () => {
|
|
854
|
-
try {
|
|
855
|
-
child.kill("SIGKILL");
|
|
856
|
-
} catch {
|
|
857
|
-
}
|
|
858
|
-
});
|
|
859
|
-
killer.unref();
|
|
860
|
-
return;
|
|
861
|
-
}
|
|
862
|
-
try {
|
|
863
|
-
child.kill("SIGKILL");
|
|
864
|
-
} catch {
|
|
865
|
-
}
|
|
866
|
-
}
|
|
867
|
-
|
|
868
834
|
// src/client-protocol-helpers.ts
|
|
869
835
|
var MAX_PROTOCOL_INPUT_CHARS = 8192;
|
|
870
836
|
function validateProtocolString(value, label, allowEmpty = false) {
|
|
@@ -886,57 +852,6 @@ function parseEmptyResult(value) {
|
|
|
886
852
|
}
|
|
887
853
|
}
|
|
888
854
|
|
|
889
|
-
// src/constants.ts
|
|
890
|
-
var SUPPORTED_PROTOCOL_VERSIONS = Object.freeze(["2024-11-05"]);
|
|
891
|
-
function negotiateProtocolVersion(requested) {
|
|
892
|
-
if (typeof requested === "string" && SUPPORTED_PROTOCOL_VERSIONS.includes(requested)) {
|
|
893
|
-
return requested;
|
|
894
|
-
}
|
|
895
|
-
return MCP_CONSTANTS.PROTOCOL_VERSION;
|
|
896
|
-
}
|
|
897
|
-
var MCP_CONSTANTS = Object.freeze({
|
|
898
|
-
/** MCP protocol version advertised during handshake: the newest we implement. */
|
|
899
|
-
PROTOCOL_VERSION: SUPPORTED_PROTOCOL_VERSIONS[0],
|
|
900
|
-
/** Identity announced to MCP servers during `initialize`. */
|
|
901
|
-
CLIENT_INFO: Object.freeze({
|
|
902
|
-
name: "wrongstack",
|
|
903
|
-
version: "0.1.10"
|
|
904
|
-
}),
|
|
905
|
-
/** Reconnection behaviour when a transport disconnects. */
|
|
906
|
-
RECONNECT: Object.freeze({
|
|
907
|
-
/** Max full reconnect cycles before the slot is marked `failed`. */
|
|
908
|
-
MAX_CYCLES: 5,
|
|
909
|
-
/** Base delay between cycles (exponential backoff applied on top). */
|
|
910
|
-
BASE_DELAY_MS: 1e3,
|
|
911
|
-
/** Jitter factor applied to the backoff (0 = no jitter, 1 = full). */
|
|
912
|
-
JITTER_FACTOR: 0.2,
|
|
913
|
-
/** Max connection attempts within a single cycle. */
|
|
914
|
-
MAX_ATTEMPTS: 3,
|
|
915
|
-
/** Base multiplier for the exponential backoff formula (`delay = BASE * multiplier^attempt`). */
|
|
916
|
-
BACKOFF_MULTIPLIER: 2
|
|
917
|
-
}),
|
|
918
|
-
/** Timing for graceful / forced disconnect. */
|
|
919
|
-
DISCONNECT: Object.freeze({
|
|
920
|
-
/** Ms to wait for in-flight requests to complete before force-closing. */
|
|
921
|
-
GRACEFUL_MS: 800,
|
|
922
|
-
/** Ms after which the force disconnect is triggered. */
|
|
923
|
-
FORCE_TIMEOUT_MS: 1200
|
|
924
|
-
}),
|
|
925
|
-
/** Lazy-connect idle lifecycle. */
|
|
926
|
-
IDLE: Object.freeze({
|
|
927
|
-
/** Default ms a lazy server stays connected with no tool calls before auto-sleep. */
|
|
928
|
-
DEFAULT_TIMEOUT_MS: 3e5,
|
|
929
|
-
/** How often the idle sweep runs (kept well below the timeout). */
|
|
930
|
-
SWEEP_INTERVAL_MS: 3e4
|
|
931
|
-
}),
|
|
932
|
-
/** JSON-RPC response timeout for outstanding requests. */
|
|
933
|
-
RESPONSE_TIMEOUT_MS: 500,
|
|
934
|
-
/** Max buffer size for the SSE reader. */
|
|
935
|
-
SSE_READER_MAX_BUFFER: 256 * 1024,
|
|
936
|
-
/** Max characters logged from a request body. */
|
|
937
|
-
REQUEST_LOG_CAP: 1024
|
|
938
|
-
});
|
|
939
|
-
|
|
940
855
|
// src/protocol.ts
|
|
941
856
|
function record2(value, label) {
|
|
942
857
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
@@ -1138,6 +1053,192 @@ function parseGetPromptResult(value) {
|
|
|
1138
1053
|
};
|
|
1139
1054
|
}
|
|
1140
1055
|
|
|
1056
|
+
// src/client-capabilities.ts
|
|
1057
|
+
var MCPCapabilityClient = class {
|
|
1058
|
+
constructor(requestCapability, requireResourceSubscriptions) {
|
|
1059
|
+
this.requestCapability = requestCapability;
|
|
1060
|
+
this.requireResourceSubscriptions = requireResourceSubscriptions;
|
|
1061
|
+
}
|
|
1062
|
+
requestCapability;
|
|
1063
|
+
requireResourceSubscriptions;
|
|
1064
|
+
async listResources(opts = {}) {
|
|
1065
|
+
const params = pageParams(opts.cursor, "resources/list cursor");
|
|
1066
|
+
return this.requestCapability(
|
|
1067
|
+
"resources",
|
|
1068
|
+
"resources/list",
|
|
1069
|
+
params,
|
|
1070
|
+
parseListResourcesResult,
|
|
1071
|
+
opts
|
|
1072
|
+
);
|
|
1073
|
+
}
|
|
1074
|
+
async listResourceTemplates(opts = {}) {
|
|
1075
|
+
const params = pageParams(opts.cursor, "resources/templates/list cursor");
|
|
1076
|
+
return this.requestCapability(
|
|
1077
|
+
"resources",
|
|
1078
|
+
"resources/templates/list",
|
|
1079
|
+
params,
|
|
1080
|
+
parseListResourceTemplatesResult,
|
|
1081
|
+
opts
|
|
1082
|
+
);
|
|
1083
|
+
}
|
|
1084
|
+
async readResource(uri, opts = {}) {
|
|
1085
|
+
validateProtocolString(uri, "resource URI");
|
|
1086
|
+
return this.requestCapability(
|
|
1087
|
+
"resources",
|
|
1088
|
+
"resources/read",
|
|
1089
|
+
{ uri },
|
|
1090
|
+
parseReadResourceResult,
|
|
1091
|
+
opts
|
|
1092
|
+
);
|
|
1093
|
+
}
|
|
1094
|
+
async subscribeResource(uri, opts = {}) {
|
|
1095
|
+
validateProtocolString(uri, "resource URI");
|
|
1096
|
+
this.requireResourceSubscriptions("resources/subscribe");
|
|
1097
|
+
await this.requestCapability(
|
|
1098
|
+
"resources",
|
|
1099
|
+
"resources/subscribe",
|
|
1100
|
+
{ uri },
|
|
1101
|
+
parseEmptyResult,
|
|
1102
|
+
opts
|
|
1103
|
+
);
|
|
1104
|
+
}
|
|
1105
|
+
async unsubscribeResource(uri, opts = {}) {
|
|
1106
|
+
validateProtocolString(uri, "resource URI");
|
|
1107
|
+
this.requireResourceSubscriptions("resources/unsubscribe");
|
|
1108
|
+
await this.requestCapability(
|
|
1109
|
+
"resources",
|
|
1110
|
+
"resources/unsubscribe",
|
|
1111
|
+
{ uri },
|
|
1112
|
+
parseEmptyResult,
|
|
1113
|
+
opts
|
|
1114
|
+
);
|
|
1115
|
+
}
|
|
1116
|
+
async listPrompts(opts = {}) {
|
|
1117
|
+
const params = pageParams(opts.cursor, "prompts/list cursor");
|
|
1118
|
+
return this.requestCapability("prompts", "prompts/list", params, parseListPromptsResult, opts);
|
|
1119
|
+
}
|
|
1120
|
+
async getPrompt(name, args, opts = {}) {
|
|
1121
|
+
validateProtocolString(name, "prompt name");
|
|
1122
|
+
if (args && Object.keys(args).length > 64) {
|
|
1123
|
+
throw new Error("MCP prompt arguments exceed the limit of 64");
|
|
1124
|
+
}
|
|
1125
|
+
for (const [key, value] of Object.entries(args ?? {})) {
|
|
1126
|
+
validateProtocolString(key, "prompt argument name");
|
|
1127
|
+
validateProtocolString(value, `prompt argument "${key}"`, true);
|
|
1128
|
+
}
|
|
1129
|
+
return this.requestCapability(
|
|
1130
|
+
"prompts",
|
|
1131
|
+
"prompts/get",
|
|
1132
|
+
args === void 0 ? { name } : { name, arguments: args },
|
|
1133
|
+
parseGetPromptResult,
|
|
1134
|
+
opts
|
|
1135
|
+
);
|
|
1136
|
+
}
|
|
1137
|
+
};
|
|
1138
|
+
|
|
1139
|
+
// src/client-process.ts
|
|
1140
|
+
import { spawn } from "node:child_process";
|
|
1141
|
+
import { buildChildEnv } from "@wrongstack/core/utils";
|
|
1142
|
+
function resolveHttpBearerHeaders(options, env = process.env) {
|
|
1143
|
+
const envName = options.bearerTokenEnv?.trim();
|
|
1144
|
+
if (!envName) return options.headers;
|
|
1145
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]{0,127}$/.test(envName)) {
|
|
1146
|
+
throw new Error(`MCP bearerTokenEnv "${envName}" is not a valid environment variable name`);
|
|
1147
|
+
}
|
|
1148
|
+
const token = env[envName];
|
|
1149
|
+
if (!token) throw new Error(`MCP "${options.name}" requires environment variable ${envName}`);
|
|
1150
|
+
if (token.length > 16384 || /[\r\n]/.test(token)) {
|
|
1151
|
+
throw new Error(`MCP "${options.name}" bearer token is oversized or contains newlines`);
|
|
1152
|
+
}
|
|
1153
|
+
const resolved = { ...options.headers };
|
|
1154
|
+
for (const key of Object.keys(resolved)) {
|
|
1155
|
+
if (key.toLowerCase() === "authorization") delete resolved[key];
|
|
1156
|
+
}
|
|
1157
|
+
resolved.Authorization = `Bearer ${token}`;
|
|
1158
|
+
return resolved;
|
|
1159
|
+
}
|
|
1160
|
+
function forceKillTree(child) {
|
|
1161
|
+
if (child.pid === void 0) {
|
|
1162
|
+
try {
|
|
1163
|
+
child.kill("SIGKILL");
|
|
1164
|
+
} catch {
|
|
1165
|
+
}
|
|
1166
|
+
return;
|
|
1167
|
+
}
|
|
1168
|
+
if (process.platform === "win32") {
|
|
1169
|
+
const killer = spawn("taskkill", ["/pid", String(child.pid), "/T", "/F"], {
|
|
1170
|
+
stdio: "ignore",
|
|
1171
|
+
// H-8 convention (spawn-convention test): taskkill needs PATH only —
|
|
1172
|
+
// do not hand it the credential-bearing parent environment.
|
|
1173
|
+
env: buildChildEnv(),
|
|
1174
|
+
windowsHide: true
|
|
1175
|
+
});
|
|
1176
|
+
killer.once("error", () => {
|
|
1177
|
+
try {
|
|
1178
|
+
child.kill("SIGKILL");
|
|
1179
|
+
} catch {
|
|
1180
|
+
}
|
|
1181
|
+
});
|
|
1182
|
+
killer.unref();
|
|
1183
|
+
return;
|
|
1184
|
+
}
|
|
1185
|
+
try {
|
|
1186
|
+
child.kill("SIGKILL");
|
|
1187
|
+
} catch {
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
// src/constants.ts
|
|
1192
|
+
var SUPPORTED_PROTOCOL_VERSIONS = Object.freeze(["2024-11-05"]);
|
|
1193
|
+
function negotiateProtocolVersion(requested) {
|
|
1194
|
+
if (typeof requested === "string" && SUPPORTED_PROTOCOL_VERSIONS.includes(requested)) {
|
|
1195
|
+
return requested;
|
|
1196
|
+
}
|
|
1197
|
+
return MCP_CONSTANTS.PROTOCOL_VERSION;
|
|
1198
|
+
}
|
|
1199
|
+
var MCP_CONSTANTS = Object.freeze({
|
|
1200
|
+
/** MCP protocol version advertised during handshake: the newest we implement. */
|
|
1201
|
+
PROTOCOL_VERSION: SUPPORTED_PROTOCOL_VERSIONS[0],
|
|
1202
|
+
/** Identity announced to MCP servers during `initialize`. */
|
|
1203
|
+
CLIENT_INFO: Object.freeze({
|
|
1204
|
+
name: "wrongstack",
|
|
1205
|
+
version: "0.1.10"
|
|
1206
|
+
}),
|
|
1207
|
+
/** Reconnection behaviour when a transport disconnects. */
|
|
1208
|
+
RECONNECT: Object.freeze({
|
|
1209
|
+
/** Max full reconnect cycles before the slot is marked `failed`. */
|
|
1210
|
+
MAX_CYCLES: 5,
|
|
1211
|
+
/** Base delay between cycles (exponential backoff applied on top). */
|
|
1212
|
+
BASE_DELAY_MS: 1e3,
|
|
1213
|
+
/** Jitter factor applied to the backoff (0 = no jitter, 1 = full). */
|
|
1214
|
+
JITTER_FACTOR: 0.2,
|
|
1215
|
+
/** Max connection attempts within a single cycle. */
|
|
1216
|
+
MAX_ATTEMPTS: 3,
|
|
1217
|
+
/** Base multiplier for the exponential backoff formula (`delay = BASE * multiplier^attempt`). */
|
|
1218
|
+
BACKOFF_MULTIPLIER: 2
|
|
1219
|
+
}),
|
|
1220
|
+
/** Timing for graceful / forced disconnect. */
|
|
1221
|
+
DISCONNECT: Object.freeze({
|
|
1222
|
+
/** Ms to wait for in-flight requests to complete before force-closing. */
|
|
1223
|
+
GRACEFUL_MS: 800,
|
|
1224
|
+
/** Ms after which the force disconnect is triggered. */
|
|
1225
|
+
FORCE_TIMEOUT_MS: 1200
|
|
1226
|
+
}),
|
|
1227
|
+
/** Lazy-connect idle lifecycle. */
|
|
1228
|
+
IDLE: Object.freeze({
|
|
1229
|
+
/** Default ms a lazy server stays connected with no tool calls before auto-sleep. */
|
|
1230
|
+
DEFAULT_TIMEOUT_MS: 3e5,
|
|
1231
|
+
/** How often the idle sweep runs (kept well below the timeout). */
|
|
1232
|
+
SWEEP_INTERVAL_MS: 3e4
|
|
1233
|
+
}),
|
|
1234
|
+
/** JSON-RPC response timeout for outstanding requests. */
|
|
1235
|
+
RESPONSE_TIMEOUT_MS: 500,
|
|
1236
|
+
/** Max buffer size for the SSE reader. */
|
|
1237
|
+
SSE_READER_MAX_BUFFER: 256 * 1024,
|
|
1238
|
+
/** Max characters logged from a request body. */
|
|
1239
|
+
REQUEST_LOG_CAP: 1024
|
|
1240
|
+
});
|
|
1241
|
+
|
|
1141
1242
|
// src/tool-schema.ts
|
|
1142
1243
|
var MAX_TOOL_PAGES = 100;
|
|
1143
1244
|
var MAX_TOOLS = 1e4;
|
|
@@ -1361,19 +1462,9 @@ function validateTransportUrl(rawUrl) {
|
|
|
1361
1462
|
const hostname = url.hostname;
|
|
1362
1463
|
const host = hostname.startsWith("[") && hostname.endsWith("]") ? hostname.slice(1, -1) : hostname;
|
|
1363
1464
|
const ipVersion = net2.isIP(host);
|
|
1364
|
-
if (ipVersion
|
|
1365
|
-
const
|
|
1366
|
-
if (
|
|
1367
|
-
throw new ConfigError({
|
|
1368
|
-
message: `MCP transport: blocked link-local/IMDS address "${hostname}" \u2014 likely not a valid MCP server`,
|
|
1369
|
-
code: "CONFIG_INVALID",
|
|
1370
|
-
context: { field: "url", rawUrl, hostname }
|
|
1371
|
-
});
|
|
1372
|
-
}
|
|
1373
|
-
} else if (ipVersion === 6) {
|
|
1374
|
-
const lower = host.toLowerCase();
|
|
1375
|
-
const linkLocal = /^fe[89ab]/.test(lower);
|
|
1376
|
-
if (linkLocal || lower === "fd00:ec2::254") {
|
|
1465
|
+
if (ipVersion !== 0) {
|
|
1466
|
+
const classification = classifyTransportAddress(host, ipVersion);
|
|
1467
|
+
if (classification === "blocked") {
|
|
1377
1468
|
throw new ConfigError({
|
|
1378
1469
|
message: `MCP transport: blocked link-local/IMDS address "${hostname}" \u2014 likely not a valid MCP server`,
|
|
1379
1470
|
code: "CONFIG_INVALID",
|
|
@@ -1382,7 +1473,7 @@ function validateTransportUrl(rawUrl) {
|
|
|
1382
1473
|
}
|
|
1383
1474
|
}
|
|
1384
1475
|
if (url.protocol === "http:") {
|
|
1385
|
-
const isLoopback =
|
|
1476
|
+
const isLoopback = host === "localhost" || ipVersion !== 0 && classifyTransportAddress(host, ipVersion) === "loopback";
|
|
1386
1477
|
if (!isLoopback) {
|
|
1387
1478
|
throw new ConfigError({
|
|
1388
1479
|
message: `MCP transport: http:// is only allowed for loopback addresses; use https:// for "${hostname}"`,
|
|
@@ -2535,6 +2626,10 @@ var MCPClient = class _MCPClient {
|
|
|
2535
2626
|
this.opts = opts;
|
|
2536
2627
|
}
|
|
2537
2628
|
opts;
|
|
2629
|
+
capabilityClient = new MCPCapabilityClient(
|
|
2630
|
+
this.requestCapability.bind(this),
|
|
2631
|
+
this.requireResourceSubscriptions.bind(this)
|
|
2632
|
+
);
|
|
2538
2633
|
/**
|
|
2539
2634
|
* Maximum bytes the rx buffer may accumulate before the connection is
|
|
2540
2635
|
* forcefully closed. A well-behaved JSON-RPC server emits newline-delimited
|
|
@@ -2760,7 +2855,7 @@ var MCPClient = class _MCPClient {
|
|
|
2760
2855
|
const httpOpts = {
|
|
2761
2856
|
name: this.opts.name,
|
|
2762
2857
|
url: this.opts.url,
|
|
2763
|
-
headers: this.opts
|
|
2858
|
+
headers: resolveHttpBearerHeaders(this.opts),
|
|
2764
2859
|
startupTimeoutMs: this.opts.startupTimeoutMs,
|
|
2765
2860
|
requestTimeoutMs: this.opts.requestTimeoutMs,
|
|
2766
2861
|
authorizationProvider: this.opts.authorizationProvider,
|
|
@@ -2811,7 +2906,7 @@ var MCPClient = class _MCPClient {
|
|
|
2811
2906
|
const httpOpts = {
|
|
2812
2907
|
name: this.opts.name,
|
|
2813
2908
|
url: this.opts.url,
|
|
2814
|
-
headers: this.opts
|
|
2909
|
+
headers: resolveHttpBearerHeaders(this.opts),
|
|
2815
2910
|
startupTimeoutMs: this.opts.startupTimeoutMs,
|
|
2816
2911
|
requestTimeoutMs: this.opts.requestTimeoutMs,
|
|
2817
2912
|
authorizationProvider: this.opts.authorizationProvider,
|
|
@@ -2875,77 +2970,25 @@ var MCPClient = class _MCPClient {
|
|
|
2875
2970
|
};
|
|
2876
2971
|
}
|
|
2877
2972
|
async listResources(opts = {}) {
|
|
2878
|
-
|
|
2879
|
-
return this.requestCapability(
|
|
2880
|
-
"resources",
|
|
2881
|
-
"resources/list",
|
|
2882
|
-
params,
|
|
2883
|
-
parseListResourcesResult,
|
|
2884
|
-
opts
|
|
2885
|
-
);
|
|
2973
|
+
return this.capabilityClient.listResources(opts);
|
|
2886
2974
|
}
|
|
2887
2975
|
async listResourceTemplates(opts = {}) {
|
|
2888
|
-
|
|
2889
|
-
return this.requestCapability(
|
|
2890
|
-
"resources",
|
|
2891
|
-
"resources/templates/list",
|
|
2892
|
-
params,
|
|
2893
|
-
parseListResourceTemplatesResult,
|
|
2894
|
-
opts
|
|
2895
|
-
);
|
|
2976
|
+
return this.capabilityClient.listResourceTemplates(opts);
|
|
2896
2977
|
}
|
|
2897
2978
|
async readResource(uri, opts = {}) {
|
|
2898
|
-
|
|
2899
|
-
return this.requestCapability(
|
|
2900
|
-
"resources",
|
|
2901
|
-
"resources/read",
|
|
2902
|
-
{ uri },
|
|
2903
|
-
parseReadResourceResult,
|
|
2904
|
-
opts
|
|
2905
|
-
);
|
|
2979
|
+
return this.capabilityClient.readResource(uri, opts);
|
|
2906
2980
|
}
|
|
2907
2981
|
async subscribeResource(uri, opts = {}) {
|
|
2908
|
-
|
|
2909
|
-
this.requireResourceSubscriptions("resources/subscribe");
|
|
2910
|
-
await this.requestCapability(
|
|
2911
|
-
"resources",
|
|
2912
|
-
"resources/subscribe",
|
|
2913
|
-
{ uri },
|
|
2914
|
-
parseEmptyResult,
|
|
2915
|
-
opts
|
|
2916
|
-
);
|
|
2982
|
+
return this.capabilityClient.subscribeResource(uri, opts);
|
|
2917
2983
|
}
|
|
2918
2984
|
async unsubscribeResource(uri, opts = {}) {
|
|
2919
|
-
|
|
2920
|
-
this.requireResourceSubscriptions("resources/unsubscribe");
|
|
2921
|
-
await this.requestCapability(
|
|
2922
|
-
"resources",
|
|
2923
|
-
"resources/unsubscribe",
|
|
2924
|
-
{ uri },
|
|
2925
|
-
parseEmptyResult,
|
|
2926
|
-
opts
|
|
2927
|
-
);
|
|
2985
|
+
return this.capabilityClient.unsubscribeResource(uri, opts);
|
|
2928
2986
|
}
|
|
2929
2987
|
async listPrompts(opts = {}) {
|
|
2930
|
-
|
|
2931
|
-
return this.requestCapability("prompts", "prompts/list", params, parseListPromptsResult, opts);
|
|
2988
|
+
return this.capabilityClient.listPrompts(opts);
|
|
2932
2989
|
}
|
|
2933
2990
|
async getPrompt(name, args, opts = {}) {
|
|
2934
|
-
|
|
2935
|
-
if (args && Object.keys(args).length > 64) {
|
|
2936
|
-
throw new Error("MCP prompt arguments exceed the limit of 64");
|
|
2937
|
-
}
|
|
2938
|
-
for (const [key, value] of Object.entries(args ?? {})) {
|
|
2939
|
-
validateProtocolString(key, "prompt argument name");
|
|
2940
|
-
validateProtocolString(value, `prompt argument "${key}"`, true);
|
|
2941
|
-
}
|
|
2942
|
-
return this.requestCapability(
|
|
2943
|
-
"prompts",
|
|
2944
|
-
"prompts/get",
|
|
2945
|
-
args === void 0 ? { name } : { name, arguments: args },
|
|
2946
|
-
parseGetPromptResult,
|
|
2947
|
-
opts
|
|
2948
|
-
);
|
|
2991
|
+
return this.capabilityClient.getPrompt(name, args, opts);
|
|
2949
2992
|
}
|
|
2950
2993
|
async close() {
|
|
2951
2994
|
if (this.closePromise) return this.closePromise;
|
|
@@ -3433,11 +3476,25 @@ function requireIdentity(value, label) {
|
|
|
3433
3476
|
import { randomBytes as randomBytes3 } from "node:crypto";
|
|
3434
3477
|
import * as fs from "node:fs/promises";
|
|
3435
3478
|
async function readConfig(path2) {
|
|
3479
|
+
let raw;
|
|
3436
3480
|
try {
|
|
3437
|
-
|
|
3481
|
+
raw = await fs.readFile(path2, "utf8");
|
|
3482
|
+
} catch (error) {
|
|
3483
|
+
if (error.code === "ENOENT") return {};
|
|
3484
|
+
throw error;
|
|
3485
|
+
}
|
|
3486
|
+
let parsed;
|
|
3487
|
+
try {
|
|
3488
|
+
parsed = JSON.parse(raw);
|
|
3438
3489
|
} catch {
|
|
3439
|
-
|
|
3490
|
+
throw new Error(
|
|
3491
|
+
`Profile config "${path2}" is not valid JSON \u2014 fix or remove it before changing MCP servers`
|
|
3492
|
+
);
|
|
3493
|
+
}
|
|
3494
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
3495
|
+
throw new Error(`Profile config "${path2}" must contain a JSON object`);
|
|
3440
3496
|
}
|
|
3497
|
+
return parsed;
|
|
3441
3498
|
}
|
|
3442
3499
|
async function writeConfig(path2, cfg) {
|
|
3443
3500
|
const raw = JSON.stringify(cfg, null, 2);
|
|
@@ -3509,6 +3566,8 @@ function buildConfig(input, base) {
|
|
|
3509
3566
|
if (url !== void 0) cfg.url = url;
|
|
3510
3567
|
const headers = input.headers ?? base?.headers;
|
|
3511
3568
|
if (headers !== void 0) cfg.headers = headers;
|
|
3569
|
+
const bearerTokenEnv = input.bearerTokenEnv ?? base?.bearerTokenEnv;
|
|
3570
|
+
if (bearerTokenEnv !== void 0) cfg.bearerTokenEnv = bearerTokenEnv;
|
|
3512
3571
|
const allowedTools = input.allowedTools ?? base?.allowedTools;
|
|
3513
3572
|
if (allowedTools !== void 0) cfg.allowedTools = allowedTools;
|
|
3514
3573
|
const permission = input.permission ?? base?.permission;
|
|
@@ -3753,6 +3812,7 @@ function manifestConfigHash(cfg) {
|
|
|
3753
3812
|
url: cfg.url ?? null,
|
|
3754
3813
|
env: sortedEntries(cfg.env),
|
|
3755
3814
|
headers: sortedEntries(cfg.headers),
|
|
3815
|
+
bearerTokenEnv: cfg.bearerTokenEnv ?? null,
|
|
3756
3816
|
passthroughEnv: passthroughEntries(cfg.passthroughEnv)
|
|
3757
3817
|
});
|
|
3758
3818
|
return createHash2("sha256").update(basis).digest("hex").slice(0, 16);
|
|
@@ -4070,7 +4130,7 @@ function renderContentBlock(item) {
|
|
|
4070
4130
|
if (type === "image" || type === "audio") {
|
|
4071
4131
|
const mime = typeof item["mimeType"] === "string" ? item["mimeType"] : "unknown type";
|
|
4072
4132
|
const data = typeof item["data"] === "string" ? item["data"] : "";
|
|
4073
|
-
const kb = Math.max(1, Math.round(data.length * 3 / 4 / 1024));
|
|
4133
|
+
const kb = data.length === 0 ? 0 : Math.max(1, Math.round(data.length * 3 / 4 / 1024));
|
|
4074
4134
|
return `[${type} content: ${mime}, ~${kb} KB \u2014 binary payload not inlined]`;
|
|
4075
4135
|
}
|
|
4076
4136
|
if (type === "resource" && item["resource"] && typeof item["resource"] === "object") {
|
|
@@ -4087,6 +4147,9 @@ ${resource["text"]}`;
|
|
|
4087
4147
|
if (type === "resource_link" && typeof item["uri"] === "string") {
|
|
4088
4148
|
return `[resource link: ${item["uri"]}]`;
|
|
4089
4149
|
}
|
|
4150
|
+
if ("text" in item) {
|
|
4151
|
+
return String(item["text"] ?? "");
|
|
4152
|
+
}
|
|
4090
4153
|
return JSON.stringify(item);
|
|
4091
4154
|
}
|
|
4092
4155
|
function stringify(c) {
|
|
@@ -4100,10 +4163,7 @@ function stringify(c) {
|
|
|
4100
4163
|
}).join("\n");
|
|
4101
4164
|
}
|
|
4102
4165
|
if (c && typeof c === "object") {
|
|
4103
|
-
|
|
4104
|
-
return String(c.text);
|
|
4105
|
-
}
|
|
4106
|
-
return JSON.stringify(c);
|
|
4166
|
+
return renderContentBlock(c);
|
|
4107
4167
|
}
|
|
4108
4168
|
return String(c ?? "");
|
|
4109
4169
|
}
|
|
@@ -4250,6 +4310,7 @@ async function attemptConnectSlot(ctx, slot) {
|
|
|
4250
4310
|
env: slot.cfg.env,
|
|
4251
4311
|
url: slot.cfg.url,
|
|
4252
4312
|
headers: slot.cfg.headers,
|
|
4313
|
+
bearerTokenEnv: slot.cfg.bearerTokenEnv,
|
|
4253
4314
|
startupTimeoutMs: slot.cfg.startupTimeoutMs,
|
|
4254
4315
|
requestTimeoutMs: slot.cfg.requestTimeoutMs,
|
|
4255
4316
|
cwd: ctx.cwd,
|
|
@@ -4461,8 +4522,10 @@ async function sleepIdleSlot(ctx, slot) {
|
|
|
4461
4522
|
clearTimeout(slot.reconnectTimer);
|
|
4462
4523
|
slot.reconnectTimer = void 0;
|
|
4463
4524
|
}
|
|
4464
|
-
|
|
4465
|
-
|
|
4525
|
+
slot.state = "dormant";
|
|
4526
|
+
const client = slot.client;
|
|
4527
|
+
slot.client = void 0;
|
|
4528
|
+
if (client) {
|
|
4466
4529
|
client.removeExitListener?.(ctx.onChildExit);
|
|
4467
4530
|
if (slot.onDisconnect) client.removeDisconnectListener?.(slot.onDisconnect);
|
|
4468
4531
|
client.removeToolsChangedListener?.(ctx.onToolsChanged);
|
|
@@ -4472,10 +4535,8 @@ async function sleepIdleSlot(ctx, slot) {
|
|
|
4472
4535
|
} catch (err) {
|
|
4473
4536
|
ctx.log.warn(`MCP server "${slot.cfg.name}" error during idle sleep close`, err);
|
|
4474
4537
|
}
|
|
4475
|
-
slot.client = void 0;
|
|
4476
4538
|
}
|
|
4477
4539
|
slot.onDisconnect = void 0;
|
|
4478
|
-
slot.state = "dormant";
|
|
4479
4540
|
slot.operations.sleepCount++;
|
|
4480
4541
|
ctx.recordOperation(slot, "sleep", "idle-timeout");
|
|
4481
4542
|
ctx.log.info(`MCP server "${slot.cfg.name}" idle \u2014 sleeping (tools stay registered)`);
|
|
@@ -5240,6 +5301,7 @@ var MCPServer = class {
|
|
|
5240
5301
|
logger;
|
|
5241
5302
|
resources;
|
|
5242
5303
|
prompts;
|
|
5304
|
+
inFlightRequests = /* @__PURE__ */ new Map();
|
|
5243
5305
|
constructor(opts) {
|
|
5244
5306
|
this.host = opts.host;
|
|
5245
5307
|
this.serverInfo = opts.serverInfo ?? {
|
|
@@ -5264,32 +5326,44 @@ var MCPServer = class {
|
|
|
5264
5326
|
} catch {
|
|
5265
5327
|
return this.encodeError(null, PARSE_ERROR, "Parse error");
|
|
5266
5328
|
}
|
|
5267
|
-
|
|
5329
|
+
const validId = msg?.id === void 0 || msg.id === null || typeof msg.id === "string" || typeof msg.id === "number";
|
|
5330
|
+
if (typeof msg !== "object" || msg === null || msg.jsonrpc !== "2.0" || typeof msg.method !== "string" || !validId) {
|
|
5268
5331
|
const id = msg && typeof msg === "object" ? msg.id ?? null : null;
|
|
5269
5332
|
return this.encodeError(id ?? null, INVALID_REQUEST, "Invalid Request");
|
|
5270
5333
|
}
|
|
5271
5334
|
const isNotification = msg.id === void 0 || msg.id === null;
|
|
5272
5335
|
if (isNotification) {
|
|
5336
|
+
if (msg.method === "notifications/cancelled") {
|
|
5337
|
+
const input = paramsRecord(msg.params);
|
|
5338
|
+
const requestId2 = input["requestId"];
|
|
5339
|
+
if (typeof requestId2 === "number" || typeof requestId2 === "string") {
|
|
5340
|
+
const reason = typeof input["reason"] === "string" && input["reason"].trim().length > 0 ? input["reason"].trim().slice(0, 500) : "MCP request cancelled by client";
|
|
5341
|
+
this.inFlightRequests.get(requestId2)?.abort(new Error(reason));
|
|
5342
|
+
}
|
|
5343
|
+
}
|
|
5273
5344
|
return null;
|
|
5274
5345
|
}
|
|
5346
|
+
const requestId = expectDefined3(msg.id);
|
|
5347
|
+
const controller = new AbortController();
|
|
5348
|
+
this.inFlightRequests.set(requestId, controller);
|
|
5275
5349
|
try {
|
|
5276
|
-
const result = await this.dispatch(msg.method, msg.params);
|
|
5350
|
+
const result = await this.dispatch(msg.method, msg.params, controller.signal);
|
|
5277
5351
|
if (result === METHOD_NOT_FOUND_SENTINEL) {
|
|
5278
|
-
return this.encodeError(
|
|
5279
|
-
expectDefined3(msg.id),
|
|
5280
|
-
METHOD_NOT_FOUND,
|
|
5281
|
-
`Method not found: ${msg.method}`
|
|
5282
|
-
);
|
|
5352
|
+
return this.encodeError(requestId, METHOD_NOT_FOUND, `Method not found: ${msg.method}`);
|
|
5283
5353
|
}
|
|
5284
5354
|
return JSON.stringify({ jsonrpc: "2.0", id: msg.id, result });
|
|
5285
5355
|
} catch (err) {
|
|
5286
5356
|
const message = toErrorMessage2(err);
|
|
5287
5357
|
this.logger?.warn?.(`MCP server: method "${msg.method}" threw: ${message}`);
|
|
5288
5358
|
const code = err instanceof InvalidToolArgumentsError ? INVALID_PARAMS : INTERNAL_ERROR;
|
|
5289
|
-
return this.encodeError(
|
|
5359
|
+
return this.encodeError(requestId, code, message);
|
|
5360
|
+
} finally {
|
|
5361
|
+
if (this.inFlightRequests.get(requestId) === controller) {
|
|
5362
|
+
this.inFlightRequests.delete(requestId);
|
|
5363
|
+
}
|
|
5290
5364
|
}
|
|
5291
5365
|
}
|
|
5292
|
-
async dispatch(method, params) {
|
|
5366
|
+
async dispatch(method, params, signal) {
|
|
5293
5367
|
switch (method) {
|
|
5294
5368
|
case "initialize":
|
|
5295
5369
|
return {
|
|
@@ -5316,7 +5390,7 @@ var MCPServer = class {
|
|
|
5316
5390
|
}
|
|
5317
5391
|
const args = p.arguments && typeof p.arguments === "object" && !Array.isArray(p.arguments) ? p.arguments : {};
|
|
5318
5392
|
await this.assertArgumentsMatchSchema(p.name, args);
|
|
5319
|
-
const res = await this.host.callTool(p.name, args);
|
|
5393
|
+
const res = await this.host.callTool(p.name, args, { signal });
|
|
5320
5394
|
return { content: toContentBlocks(res.content), isError: res.isError };
|
|
5321
5395
|
}
|
|
5322
5396
|
case "resources/list": {
|
|
@@ -5473,16 +5547,22 @@ function renderPromptTemplate(template, args) {
|
|
|
5473
5547
|
});
|
|
5474
5548
|
}
|
|
5475
5549
|
var METHOD_NOT_FOUND_SENTINEL = /* @__PURE__ */ Symbol("method-not-found");
|
|
5550
|
+
function isMCPContentBlock(c) {
|
|
5551
|
+
if (!c || typeof c !== "object") return false;
|
|
5552
|
+
const type = c["type"];
|
|
5553
|
+
return type === "text" || type === "image" || type === "audio" || type === "resource" || type === "resource_link";
|
|
5554
|
+
}
|
|
5476
5555
|
function toContentBlocks(content) {
|
|
5477
5556
|
if (typeof content === "string") return [{ type: "text", text: content }];
|
|
5478
5557
|
if (Array.isArray(content)) {
|
|
5479
|
-
const allBlocks = content.every(
|
|
5480
|
-
(c) => c && typeof c === "object" && c.type === "text"
|
|
5481
|
-
);
|
|
5558
|
+
const allBlocks = content.every(isMCPContentBlock);
|
|
5482
5559
|
if (allBlocks) return content;
|
|
5483
5560
|
return [{ type: "text", text: content.map((c) => stringifyItem(c)).join("\n") }];
|
|
5484
5561
|
}
|
|
5485
5562
|
if (content === void 0 || content === null) return [{ type: "text", text: "" }];
|
|
5563
|
+
if (isMCPContentBlock(content)) {
|
|
5564
|
+
return [content];
|
|
5565
|
+
}
|
|
5486
5566
|
return [{ type: "text", text: stringifyItem(content) }];
|
|
5487
5567
|
}
|
|
5488
5568
|
function stringifyItem(c) {
|
package/dist/manage.d.ts
CHANGED
|
@@ -14,6 +14,7 @@ export interface McpServerInput {
|
|
|
14
14
|
env?: Record<string, string> | undefined;
|
|
15
15
|
url?: string | undefined;
|
|
16
16
|
headers?: Record<string, string> | undefined;
|
|
17
|
+
bearerTokenEnv?: string | undefined;
|
|
17
18
|
allowedTools?: string[] | undefined;
|
|
18
19
|
permission?: Permission | undefined;
|
|
19
20
|
/** Lazy connect — spawn the process only on first tool call (see config). */
|
package/dist/manifest-cache.d.ts
CHANGED
|
@@ -15,6 +15,7 @@ export declare function manifestConfigHash(cfg: {
|
|
|
15
15
|
url?: string | undefined;
|
|
16
16
|
env?: Record<string, string> | undefined;
|
|
17
17
|
headers?: Record<string, string> | undefined;
|
|
18
|
+
bearerTokenEnv?: string | undefined;
|
|
18
19
|
passthroughEnv?: string[] | undefined;
|
|
19
20
|
}): string;
|
|
20
21
|
/**
|
package/dist/server.d.ts
CHANGED
|
@@ -29,7 +29,9 @@ export interface MCPServerCallResult {
|
|
|
29
29
|
*/
|
|
30
30
|
export interface MCPServerToolHost {
|
|
31
31
|
listTools(): MCPServerTool[] | Promise<MCPServerTool[]>;
|
|
32
|
-
callTool(name: string, args: Record<string, unknown
|
|
32
|
+
callTool(name: string, args: Record<string, unknown>, opts?: {
|
|
33
|
+
signal?: AbortSignal | undefined;
|
|
34
|
+
}): Promise<MCPServerCallResult>;
|
|
33
35
|
}
|
|
34
36
|
export interface MCPServerResource {
|
|
35
37
|
uri: string;
|
|
@@ -72,6 +74,7 @@ export declare class MCPServer {
|
|
|
72
74
|
private readonly logger?;
|
|
73
75
|
private readonly resources;
|
|
74
76
|
private readonly prompts;
|
|
77
|
+
private readonly inFlightRequests;
|
|
75
78
|
constructor(opts: MCPServerOptions);
|
|
76
79
|
/**
|
|
77
80
|
* Handle one raw JSON-RPC line. Returns the response JSON string for
|
|
@@ -121,8 +124,8 @@ export declare class InvalidToolArgumentsError extends Error {
|
|
|
121
124
|
}
|
|
122
125
|
/** Normalize a host result's content into MCP content blocks. */
|
|
123
126
|
export declare function toContentBlocks(content: unknown): Array<{
|
|
124
|
-
type:
|
|
125
|
-
|
|
127
|
+
type: string;
|
|
128
|
+
[key: string]: unknown;
|
|
126
129
|
}>;
|
|
127
130
|
export interface ServeStdioHandle {
|
|
128
131
|
/** Stop reading and detach listeners. Does not exit the process. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wrongstack/mcp",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.24",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "WrongStack Model Context Protocol client and registry: stdio, SSE, and streamable HTTP transports.",
|
|
6
6
|
"repository": {
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"!dist/**/*.map"
|
|
27
27
|
],
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@wrongstack/core": "1.0.
|
|
29
|
+
"@wrongstack/core": "1.0.24",
|
|
30
30
|
"undici": "^8.10.2"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|