opencode-toolbox 0.7.0 → 0.9.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 +25 -6
- package/dist/index.js +514 -40
- package/package.json +3 -2
- package/toolbox.schema.json +125 -0
package/README.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# opencode-toolbox (Tool Search Tool)
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/opencode-toolbox)
|
|
4
|
+
[](https://www.npmjs.com/package/opencode-toolbox)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
[](https://www.typescriptlang.org/)
|
|
7
|
+
[](https://github.com/assagman/opencode-toolbox/actions/workflows/ci.yml)
|
|
8
|
+
[](https://codecov.io/gh/assagman/opencode-toolbox)
|
|
9
|
+
|
|
3
10
|
An OpenCode plugin that implements a **tool search tool** pattern, allowing users to keep only a tiny set of tools in LLM context while accessing a larger MCP catalog on-demand.
|
|
4
11
|
|
|
5
12
|
## Motivation
|
|
@@ -20,16 +27,26 @@ Add `opencode-toolbox` to your `~/.config/opencode/opencode.jsonc`:
|
|
|
20
27
|
|
|
21
28
|
```jsonc
|
|
22
29
|
{
|
|
23
|
-
"plugin": ["opencode-toolbox"]
|
|
30
|
+
"plugin": ["opencode-toolbox@x.y.z"]
|
|
24
31
|
}
|
|
25
32
|
```
|
|
26
33
|
|
|
34
|
+
> **⚡ Performance Tip:** Always pin to a specific version for instant startup.
|
|
35
|
+
>
|
|
36
|
+
> | Configuration | Startup Time | Why |
|
|
37
|
+
> |---------------|--------------|-----|
|
|
38
|
+
> | ✅ `opencode-toolbox@x.y.z` | ~0ms | Cached version matches, skips npm check |
|
|
39
|
+
> | ❌ `opencode-toolbox` | ~1000ms | Resolves "latest" via npm on every startup |
|
|
40
|
+
>
|
|
41
|
+
> OpenCode caches plugins by exact version. When you omit the version, it defaults to `"latest"` which doesn't match the cached resolved version, triggering a fresh `bun add` on every startup. Check [npm](https://www.npmjs.com/package/opencode-toolbox) for the latest version.
|
|
42
|
+
|
|
27
43
|
### 2. Configure Toolbox
|
|
28
44
|
|
|
29
45
|
Create `~/.config/opencode/toolbox.jsonc`:
|
|
30
46
|
|
|
31
47
|
```jsonc
|
|
32
48
|
{
|
|
49
|
+
"$schema": "https://unpkg.com/opencode-toolbox@latest/toolbox.schema.json",
|
|
33
50
|
"mcp": {
|
|
34
51
|
"time": {
|
|
35
52
|
"type": "local",
|
|
@@ -56,6 +73,8 @@ Create `~/.config/opencode/toolbox.jsonc`:
|
|
|
56
73
|
}
|
|
57
74
|
```
|
|
58
75
|
|
|
76
|
+
> **Note:** The config file is auto-created with default settings if it doesn't exist.
|
|
77
|
+
|
|
59
78
|
### Environment Variables
|
|
60
79
|
|
|
61
80
|
- `OPENCODE_TOOLBOX_CONFIG`: Path to config file (default: `~/.config/opencode/toolbox.jsonc`)
|
|
@@ -105,16 +124,16 @@ Both search tools return tool schemas so the LLM knows exact parameters:
|
|
|
105
124
|
}
|
|
106
125
|
}
|
|
107
126
|
],
|
|
108
|
-
"usage": "Use toolbox_execute({
|
|
127
|
+
"usage": "Use toolbox_execute({ toolId: '<toolId>', arguments: '<json>' }) to run a discovered tool"
|
|
109
128
|
}
|
|
110
129
|
```
|
|
111
130
|
|
|
112
131
|
### toolbox_execute
|
|
113
132
|
|
|
114
|
-
Execute a discovered tool with JSON-encoded arguments
|
|
133
|
+
Execute a discovered tool with JSON-encoded arguments. The `toolId` format is `{serverName}_{toolName}`:
|
|
115
134
|
|
|
116
135
|
```
|
|
117
|
-
toolbox_execute({
|
|
136
|
+
toolbox_execute({ toolId: "time_get_current_time", arguments: '{"timezone": "Asia/Tokyo"}' })
|
|
118
137
|
```
|
|
119
138
|
|
|
120
139
|
## Example Flow
|
|
@@ -128,7 +147,7 @@ LLM: I need to find a time-related tool.
|
|
|
128
147
|
Toolbox: Returns time_get_current_time with its schema
|
|
129
148
|
|
|
130
149
|
LLM: Now I know the parameters. Let me call it.
|
|
131
|
-
toolbox_execute({
|
|
150
|
+
toolbox_execute({ toolId: "time_get_current_time", arguments: '{"timezone":"Asia/Tokyo"}' })
|
|
132
151
|
|
|
133
152
|
Toolbox: { "datetime": "2026-01-07T02:15:00+09:00", "timezone": "Asia/Tokyo" }
|
|
134
153
|
|
|
@@ -368,7 +387,7 @@ This shows:
|
|
|
368
387
|
### Execute fails
|
|
369
388
|
|
|
370
389
|
1. Run `toolbox_status({})` to check server health
|
|
371
|
-
2. Verify
|
|
390
|
+
2. Verify `toolId` format: `{serverName}_{toolName}`
|
|
372
391
|
3. Check `arguments` is valid JSON
|
|
373
392
|
4. Ensure underlying MCP server is running and connected
|
|
374
393
|
5. Check logs for detailed error messages
|
package/dist/index.js
CHANGED
|
@@ -32809,7 +32809,7 @@ var LocalServerConfigSchema = exports_external2.object({
|
|
|
32809
32809
|
});
|
|
32810
32810
|
var RemoteServerConfigSchema = exports_external2.object({
|
|
32811
32811
|
type: exports_external2.literal("remote"),
|
|
32812
|
-
url: exports_external2.string().url().describe("
|
|
32812
|
+
url: exports_external2.string().url().describe("Remote MCP endpoint URL"),
|
|
32813
32813
|
headers: exports_external2.record(exports_external2.string(), exports_external2.string()).optional().describe("HTTP headers for authentication")
|
|
32814
32814
|
});
|
|
32815
32815
|
var ServerConfigSchema = exports_external2.discriminatedUnion("type", [
|
|
@@ -33953,6 +33953,7 @@ var InitializedNotificationSchema = NotificationSchema.extend({
|
|
|
33953
33953
|
method: literal2("notifications/initialized"),
|
|
33954
33954
|
params: NotificationsParamsSchema.optional()
|
|
33955
33955
|
});
|
|
33956
|
+
var isInitializedNotification = (value) => InitializedNotificationSchema.safeParse(value).success;
|
|
33956
33957
|
var PingRequestSchema = RequestSchema.extend({
|
|
33957
33958
|
method: literal2("ping"),
|
|
33958
33959
|
params: BaseRequestParamsSchema.optional()
|
|
@@ -37584,6 +37585,395 @@ class SSEClientTransport {
|
|
|
37584
37585
|
}
|
|
37585
37586
|
}
|
|
37586
37587
|
|
|
37588
|
+
// node_modules/eventsource-parser/dist/stream.js
|
|
37589
|
+
class EventSourceParserStream extends TransformStream {
|
|
37590
|
+
constructor({ onError, onRetry, onComment } = {}) {
|
|
37591
|
+
let parser;
|
|
37592
|
+
super({
|
|
37593
|
+
start(controller) {
|
|
37594
|
+
parser = createParser({
|
|
37595
|
+
onEvent: (event) => {
|
|
37596
|
+
controller.enqueue(event);
|
|
37597
|
+
},
|
|
37598
|
+
onError(error92) {
|
|
37599
|
+
onError === "terminate" ? controller.error(error92) : typeof onError == "function" && onError(error92);
|
|
37600
|
+
},
|
|
37601
|
+
onRetry,
|
|
37602
|
+
onComment
|
|
37603
|
+
});
|
|
37604
|
+
},
|
|
37605
|
+
transform(chunk) {
|
|
37606
|
+
parser.feed(chunk);
|
|
37607
|
+
}
|
|
37608
|
+
});
|
|
37609
|
+
}
|
|
37610
|
+
}
|
|
37611
|
+
|
|
37612
|
+
// node_modules/@modelcontextprotocol/sdk/dist/esm/client/streamableHttp.js
|
|
37613
|
+
var DEFAULT_STREAMABLE_HTTP_RECONNECTION_OPTIONS = {
|
|
37614
|
+
initialReconnectionDelay: 1000,
|
|
37615
|
+
maxReconnectionDelay: 30000,
|
|
37616
|
+
reconnectionDelayGrowFactor: 1.5,
|
|
37617
|
+
maxRetries: 2
|
|
37618
|
+
};
|
|
37619
|
+
|
|
37620
|
+
class StreamableHTTPError extends Error {
|
|
37621
|
+
constructor(code, message) {
|
|
37622
|
+
super(`Streamable HTTP error: ${message}`);
|
|
37623
|
+
this.code = code;
|
|
37624
|
+
}
|
|
37625
|
+
}
|
|
37626
|
+
|
|
37627
|
+
class StreamableHTTPClientTransport {
|
|
37628
|
+
constructor(url3, opts) {
|
|
37629
|
+
this._hasCompletedAuthFlow = false;
|
|
37630
|
+
this._url = url3;
|
|
37631
|
+
this._resourceMetadataUrl = undefined;
|
|
37632
|
+
this._scope = undefined;
|
|
37633
|
+
this._requestInit = opts?.requestInit;
|
|
37634
|
+
this._authProvider = opts?.authProvider;
|
|
37635
|
+
this._fetch = opts?.fetch;
|
|
37636
|
+
this._fetchWithInit = createFetchWithInit(opts?.fetch, opts?.requestInit);
|
|
37637
|
+
this._sessionId = opts?.sessionId;
|
|
37638
|
+
this._reconnectionOptions = opts?.reconnectionOptions ?? DEFAULT_STREAMABLE_HTTP_RECONNECTION_OPTIONS;
|
|
37639
|
+
}
|
|
37640
|
+
async _authThenStart() {
|
|
37641
|
+
if (!this._authProvider) {
|
|
37642
|
+
throw new UnauthorizedError("No auth provider");
|
|
37643
|
+
}
|
|
37644
|
+
let result;
|
|
37645
|
+
try {
|
|
37646
|
+
result = await auth(this._authProvider, {
|
|
37647
|
+
serverUrl: this._url,
|
|
37648
|
+
resourceMetadataUrl: this._resourceMetadataUrl,
|
|
37649
|
+
scope: this._scope,
|
|
37650
|
+
fetchFn: this._fetchWithInit
|
|
37651
|
+
});
|
|
37652
|
+
} catch (error92) {
|
|
37653
|
+
this.onerror?.(error92);
|
|
37654
|
+
throw error92;
|
|
37655
|
+
}
|
|
37656
|
+
if (result !== "AUTHORIZED") {
|
|
37657
|
+
throw new UnauthorizedError;
|
|
37658
|
+
}
|
|
37659
|
+
return await this._startOrAuthSse({ resumptionToken: undefined });
|
|
37660
|
+
}
|
|
37661
|
+
async _commonHeaders() {
|
|
37662
|
+
const headers = {};
|
|
37663
|
+
if (this._authProvider) {
|
|
37664
|
+
const tokens = await this._authProvider.tokens();
|
|
37665
|
+
if (tokens) {
|
|
37666
|
+
headers["Authorization"] = `Bearer ${tokens.access_token}`;
|
|
37667
|
+
}
|
|
37668
|
+
}
|
|
37669
|
+
if (this._sessionId) {
|
|
37670
|
+
headers["mcp-session-id"] = this._sessionId;
|
|
37671
|
+
}
|
|
37672
|
+
if (this._protocolVersion) {
|
|
37673
|
+
headers["mcp-protocol-version"] = this._protocolVersion;
|
|
37674
|
+
}
|
|
37675
|
+
const extraHeaders = normalizeHeaders(this._requestInit?.headers);
|
|
37676
|
+
return new Headers({
|
|
37677
|
+
...headers,
|
|
37678
|
+
...extraHeaders
|
|
37679
|
+
});
|
|
37680
|
+
}
|
|
37681
|
+
async _startOrAuthSse(options) {
|
|
37682
|
+
const { resumptionToken } = options;
|
|
37683
|
+
try {
|
|
37684
|
+
const headers = await this._commonHeaders();
|
|
37685
|
+
headers.set("Accept", "text/event-stream");
|
|
37686
|
+
if (resumptionToken) {
|
|
37687
|
+
headers.set("last-event-id", resumptionToken);
|
|
37688
|
+
}
|
|
37689
|
+
const response = await (this._fetch ?? fetch)(this._url, {
|
|
37690
|
+
method: "GET",
|
|
37691
|
+
headers,
|
|
37692
|
+
signal: this._abortController?.signal
|
|
37693
|
+
});
|
|
37694
|
+
if (!response.ok) {
|
|
37695
|
+
await response.body?.cancel();
|
|
37696
|
+
if (response.status === 401 && this._authProvider) {
|
|
37697
|
+
return await this._authThenStart();
|
|
37698
|
+
}
|
|
37699
|
+
if (response.status === 405) {
|
|
37700
|
+
return;
|
|
37701
|
+
}
|
|
37702
|
+
throw new StreamableHTTPError(response.status, `Failed to open SSE stream: ${response.statusText}`);
|
|
37703
|
+
}
|
|
37704
|
+
this._handleSseStream(response.body, options, true);
|
|
37705
|
+
} catch (error92) {
|
|
37706
|
+
this.onerror?.(error92);
|
|
37707
|
+
throw error92;
|
|
37708
|
+
}
|
|
37709
|
+
}
|
|
37710
|
+
_getNextReconnectionDelay(attempt) {
|
|
37711
|
+
if (this._serverRetryMs !== undefined) {
|
|
37712
|
+
return this._serverRetryMs;
|
|
37713
|
+
}
|
|
37714
|
+
const initialDelay = this._reconnectionOptions.initialReconnectionDelay;
|
|
37715
|
+
const growFactor = this._reconnectionOptions.reconnectionDelayGrowFactor;
|
|
37716
|
+
const maxDelay = this._reconnectionOptions.maxReconnectionDelay;
|
|
37717
|
+
return Math.min(initialDelay * Math.pow(growFactor, attempt), maxDelay);
|
|
37718
|
+
}
|
|
37719
|
+
_scheduleReconnection(options, attemptCount = 0) {
|
|
37720
|
+
const maxRetries = this._reconnectionOptions.maxRetries;
|
|
37721
|
+
if (attemptCount >= maxRetries) {
|
|
37722
|
+
this.onerror?.(new Error(`Maximum reconnection attempts (${maxRetries}) exceeded.`));
|
|
37723
|
+
return;
|
|
37724
|
+
}
|
|
37725
|
+
const delay = this._getNextReconnectionDelay(attemptCount);
|
|
37726
|
+
this._reconnectionTimeout = setTimeout(() => {
|
|
37727
|
+
this._startOrAuthSse(options).catch((error92) => {
|
|
37728
|
+
this.onerror?.(new Error(`Failed to reconnect SSE stream: ${error92 instanceof Error ? error92.message : String(error92)}`));
|
|
37729
|
+
this._scheduleReconnection(options, attemptCount + 1);
|
|
37730
|
+
});
|
|
37731
|
+
}, delay);
|
|
37732
|
+
}
|
|
37733
|
+
_handleSseStream(stream, options, isReconnectable) {
|
|
37734
|
+
if (!stream) {
|
|
37735
|
+
return;
|
|
37736
|
+
}
|
|
37737
|
+
const { onresumptiontoken, replayMessageId } = options;
|
|
37738
|
+
let lastEventId;
|
|
37739
|
+
let hasPrimingEvent = false;
|
|
37740
|
+
let receivedResponse = false;
|
|
37741
|
+
const processStream = async () => {
|
|
37742
|
+
try {
|
|
37743
|
+
const reader = stream.pipeThrough(new TextDecoderStream).pipeThrough(new EventSourceParserStream({
|
|
37744
|
+
onRetry: (retryMs) => {
|
|
37745
|
+
this._serverRetryMs = retryMs;
|
|
37746
|
+
}
|
|
37747
|
+
})).getReader();
|
|
37748
|
+
while (true) {
|
|
37749
|
+
const { value: event, done } = await reader.read();
|
|
37750
|
+
if (done) {
|
|
37751
|
+
break;
|
|
37752
|
+
}
|
|
37753
|
+
if (event.id) {
|
|
37754
|
+
lastEventId = event.id;
|
|
37755
|
+
hasPrimingEvent = true;
|
|
37756
|
+
onresumptiontoken?.(event.id);
|
|
37757
|
+
}
|
|
37758
|
+
if (!event.data) {
|
|
37759
|
+
continue;
|
|
37760
|
+
}
|
|
37761
|
+
if (!event.event || event.event === "message") {
|
|
37762
|
+
try {
|
|
37763
|
+
const message = JSONRPCMessageSchema.parse(JSON.parse(event.data));
|
|
37764
|
+
if (isJSONRPCResultResponse(message)) {
|
|
37765
|
+
receivedResponse = true;
|
|
37766
|
+
if (replayMessageId !== undefined) {
|
|
37767
|
+
message.id = replayMessageId;
|
|
37768
|
+
}
|
|
37769
|
+
}
|
|
37770
|
+
this.onmessage?.(message);
|
|
37771
|
+
} catch (error92) {
|
|
37772
|
+
this.onerror?.(error92);
|
|
37773
|
+
}
|
|
37774
|
+
}
|
|
37775
|
+
}
|
|
37776
|
+
const canResume = isReconnectable || hasPrimingEvent;
|
|
37777
|
+
const needsReconnect = canResume && !receivedResponse;
|
|
37778
|
+
if (needsReconnect && this._abortController && !this._abortController.signal.aborted) {
|
|
37779
|
+
this._scheduleReconnection({
|
|
37780
|
+
resumptionToken: lastEventId,
|
|
37781
|
+
onresumptiontoken,
|
|
37782
|
+
replayMessageId
|
|
37783
|
+
}, 0);
|
|
37784
|
+
}
|
|
37785
|
+
} catch (error92) {
|
|
37786
|
+
this.onerror?.(new Error(`SSE stream disconnected: ${error92}`));
|
|
37787
|
+
const canResume = isReconnectable || hasPrimingEvent;
|
|
37788
|
+
const needsReconnect = canResume && !receivedResponse;
|
|
37789
|
+
if (needsReconnect && this._abortController && !this._abortController.signal.aborted) {
|
|
37790
|
+
try {
|
|
37791
|
+
this._scheduleReconnection({
|
|
37792
|
+
resumptionToken: lastEventId,
|
|
37793
|
+
onresumptiontoken,
|
|
37794
|
+
replayMessageId
|
|
37795
|
+
}, 0);
|
|
37796
|
+
} catch (error93) {
|
|
37797
|
+
this.onerror?.(new Error(`Failed to reconnect: ${error93 instanceof Error ? error93.message : String(error93)}`));
|
|
37798
|
+
}
|
|
37799
|
+
}
|
|
37800
|
+
}
|
|
37801
|
+
};
|
|
37802
|
+
processStream();
|
|
37803
|
+
}
|
|
37804
|
+
async start() {
|
|
37805
|
+
if (this._abortController) {
|
|
37806
|
+
throw new Error("StreamableHTTPClientTransport already started! If using Client class, note that connect() calls start() automatically.");
|
|
37807
|
+
}
|
|
37808
|
+
this._abortController = new AbortController;
|
|
37809
|
+
}
|
|
37810
|
+
async finishAuth(authorizationCode) {
|
|
37811
|
+
if (!this._authProvider) {
|
|
37812
|
+
throw new UnauthorizedError("No auth provider");
|
|
37813
|
+
}
|
|
37814
|
+
const result = await auth(this._authProvider, {
|
|
37815
|
+
serverUrl: this._url,
|
|
37816
|
+
authorizationCode,
|
|
37817
|
+
resourceMetadataUrl: this._resourceMetadataUrl,
|
|
37818
|
+
scope: this._scope,
|
|
37819
|
+
fetchFn: this._fetchWithInit
|
|
37820
|
+
});
|
|
37821
|
+
if (result !== "AUTHORIZED") {
|
|
37822
|
+
throw new UnauthorizedError("Failed to authorize");
|
|
37823
|
+
}
|
|
37824
|
+
}
|
|
37825
|
+
async close() {
|
|
37826
|
+
if (this._reconnectionTimeout) {
|
|
37827
|
+
clearTimeout(this._reconnectionTimeout);
|
|
37828
|
+
this._reconnectionTimeout = undefined;
|
|
37829
|
+
}
|
|
37830
|
+
this._abortController?.abort();
|
|
37831
|
+
this.onclose?.();
|
|
37832
|
+
}
|
|
37833
|
+
async send(message, options) {
|
|
37834
|
+
try {
|
|
37835
|
+
const { resumptionToken, onresumptiontoken } = options || {};
|
|
37836
|
+
if (resumptionToken) {
|
|
37837
|
+
this._startOrAuthSse({ resumptionToken, replayMessageId: isJSONRPCRequest(message) ? message.id : undefined }).catch((err) => this.onerror?.(err));
|
|
37838
|
+
return;
|
|
37839
|
+
}
|
|
37840
|
+
const headers = await this._commonHeaders();
|
|
37841
|
+
headers.set("content-type", "application/json");
|
|
37842
|
+
headers.set("accept", "application/json, text/event-stream");
|
|
37843
|
+
const init = {
|
|
37844
|
+
...this._requestInit,
|
|
37845
|
+
method: "POST",
|
|
37846
|
+
headers,
|
|
37847
|
+
body: JSON.stringify(message),
|
|
37848
|
+
signal: this._abortController?.signal
|
|
37849
|
+
};
|
|
37850
|
+
const response = await (this._fetch ?? fetch)(this._url, init);
|
|
37851
|
+
const sessionId = response.headers.get("mcp-session-id");
|
|
37852
|
+
if (sessionId) {
|
|
37853
|
+
this._sessionId = sessionId;
|
|
37854
|
+
}
|
|
37855
|
+
if (!response.ok) {
|
|
37856
|
+
const text = await response.text().catch(() => null);
|
|
37857
|
+
if (response.status === 401 && this._authProvider) {
|
|
37858
|
+
if (this._hasCompletedAuthFlow) {
|
|
37859
|
+
throw new StreamableHTTPError(401, "Server returned 401 after successful authentication");
|
|
37860
|
+
}
|
|
37861
|
+
const { resourceMetadataUrl, scope } = extractWWWAuthenticateParams(response);
|
|
37862
|
+
this._resourceMetadataUrl = resourceMetadataUrl;
|
|
37863
|
+
this._scope = scope;
|
|
37864
|
+
const result = await auth(this._authProvider, {
|
|
37865
|
+
serverUrl: this._url,
|
|
37866
|
+
resourceMetadataUrl: this._resourceMetadataUrl,
|
|
37867
|
+
scope: this._scope,
|
|
37868
|
+
fetchFn: this._fetchWithInit
|
|
37869
|
+
});
|
|
37870
|
+
if (result !== "AUTHORIZED") {
|
|
37871
|
+
throw new UnauthorizedError;
|
|
37872
|
+
}
|
|
37873
|
+
this._hasCompletedAuthFlow = true;
|
|
37874
|
+
return this.send(message);
|
|
37875
|
+
}
|
|
37876
|
+
if (response.status === 403 && this._authProvider) {
|
|
37877
|
+
const { resourceMetadataUrl, scope, error: error92 } = extractWWWAuthenticateParams(response);
|
|
37878
|
+
if (error92 === "insufficient_scope") {
|
|
37879
|
+
const wwwAuthHeader = response.headers.get("WWW-Authenticate");
|
|
37880
|
+
if (this._lastUpscopingHeader === wwwAuthHeader) {
|
|
37881
|
+
throw new StreamableHTTPError(403, "Server returned 403 after trying upscoping");
|
|
37882
|
+
}
|
|
37883
|
+
if (scope) {
|
|
37884
|
+
this._scope = scope;
|
|
37885
|
+
}
|
|
37886
|
+
if (resourceMetadataUrl) {
|
|
37887
|
+
this._resourceMetadataUrl = resourceMetadataUrl;
|
|
37888
|
+
}
|
|
37889
|
+
this._lastUpscopingHeader = wwwAuthHeader ?? undefined;
|
|
37890
|
+
const result = await auth(this._authProvider, {
|
|
37891
|
+
serverUrl: this._url,
|
|
37892
|
+
resourceMetadataUrl: this._resourceMetadataUrl,
|
|
37893
|
+
scope: this._scope,
|
|
37894
|
+
fetchFn: this._fetch
|
|
37895
|
+
});
|
|
37896
|
+
if (result !== "AUTHORIZED") {
|
|
37897
|
+
throw new UnauthorizedError;
|
|
37898
|
+
}
|
|
37899
|
+
return this.send(message);
|
|
37900
|
+
}
|
|
37901
|
+
}
|
|
37902
|
+
throw new StreamableHTTPError(response.status, `Error POSTing to endpoint: ${text}`);
|
|
37903
|
+
}
|
|
37904
|
+
this._hasCompletedAuthFlow = false;
|
|
37905
|
+
this._lastUpscopingHeader = undefined;
|
|
37906
|
+
if (response.status === 202) {
|
|
37907
|
+
await response.body?.cancel();
|
|
37908
|
+
if (isInitializedNotification(message)) {
|
|
37909
|
+
this._startOrAuthSse({ resumptionToken: undefined }).catch((err) => this.onerror?.(err));
|
|
37910
|
+
}
|
|
37911
|
+
return;
|
|
37912
|
+
}
|
|
37913
|
+
const messages = Array.isArray(message) ? message : [message];
|
|
37914
|
+
const hasRequests = messages.filter((msg) => ("method" in msg) && ("id" in msg) && msg.id !== undefined).length > 0;
|
|
37915
|
+
const contentType = response.headers.get("content-type");
|
|
37916
|
+
if (hasRequests) {
|
|
37917
|
+
if (contentType?.includes("text/event-stream")) {
|
|
37918
|
+
this._handleSseStream(response.body, { onresumptiontoken }, false);
|
|
37919
|
+
} else if (contentType?.includes("application/json")) {
|
|
37920
|
+
const data = await response.json();
|
|
37921
|
+
const responseMessages = Array.isArray(data) ? data.map((msg) => JSONRPCMessageSchema.parse(msg)) : [JSONRPCMessageSchema.parse(data)];
|
|
37922
|
+
for (const msg of responseMessages) {
|
|
37923
|
+
this.onmessage?.(msg);
|
|
37924
|
+
}
|
|
37925
|
+
} else {
|
|
37926
|
+
await response.body?.cancel();
|
|
37927
|
+
throw new StreamableHTTPError(-1, `Unexpected content type: ${contentType}`);
|
|
37928
|
+
}
|
|
37929
|
+
} else {
|
|
37930
|
+
await response.body?.cancel();
|
|
37931
|
+
}
|
|
37932
|
+
} catch (error92) {
|
|
37933
|
+
this.onerror?.(error92);
|
|
37934
|
+
throw error92;
|
|
37935
|
+
}
|
|
37936
|
+
}
|
|
37937
|
+
get sessionId() {
|
|
37938
|
+
return this._sessionId;
|
|
37939
|
+
}
|
|
37940
|
+
async terminateSession() {
|
|
37941
|
+
if (!this._sessionId) {
|
|
37942
|
+
return;
|
|
37943
|
+
}
|
|
37944
|
+
try {
|
|
37945
|
+
const headers = await this._commonHeaders();
|
|
37946
|
+
const init = {
|
|
37947
|
+
...this._requestInit,
|
|
37948
|
+
method: "DELETE",
|
|
37949
|
+
headers,
|
|
37950
|
+
signal: this._abortController?.signal
|
|
37951
|
+
};
|
|
37952
|
+
const response = await (this._fetch ?? fetch)(this._url, init);
|
|
37953
|
+
await response.body?.cancel();
|
|
37954
|
+
if (!response.ok && response.status !== 405) {
|
|
37955
|
+
throw new StreamableHTTPError(response.status, `Failed to terminate session: ${response.statusText}`);
|
|
37956
|
+
}
|
|
37957
|
+
this._sessionId = undefined;
|
|
37958
|
+
} catch (error92) {
|
|
37959
|
+
this.onerror?.(error92);
|
|
37960
|
+
throw error92;
|
|
37961
|
+
}
|
|
37962
|
+
}
|
|
37963
|
+
setProtocolVersion(version3) {
|
|
37964
|
+
this._protocolVersion = version3;
|
|
37965
|
+
}
|
|
37966
|
+
get protocolVersion() {
|
|
37967
|
+
return this._protocolVersion;
|
|
37968
|
+
}
|
|
37969
|
+
async resumeStream(lastEventId, options) {
|
|
37970
|
+
await this._startOrAuthSse({
|
|
37971
|
+
resumptionToken: lastEventId,
|
|
37972
|
+
onresumptiontoken: options?.onresumptiontoken
|
|
37973
|
+
});
|
|
37974
|
+
}
|
|
37975
|
+
}
|
|
37976
|
+
|
|
37587
37977
|
// src/mcp-client/remote.ts
|
|
37588
37978
|
class RemoteMCPClient {
|
|
37589
37979
|
client;
|
|
@@ -37591,12 +37981,17 @@ class RemoteMCPClient {
|
|
|
37591
37981
|
toolsCache;
|
|
37592
37982
|
name;
|
|
37593
37983
|
config;
|
|
37984
|
+
transportType;
|
|
37594
37985
|
constructor(config3) {
|
|
37595
37986
|
this.transport = null;
|
|
37596
37987
|
this.toolsCache = null;
|
|
37597
37988
|
this.name = config3.name;
|
|
37598
37989
|
this.config = config3;
|
|
37599
|
-
this.
|
|
37990
|
+
this.transportType = null;
|
|
37991
|
+
this.client = this.createClient();
|
|
37992
|
+
}
|
|
37993
|
+
createClient() {
|
|
37994
|
+
return new Client({
|
|
37600
37995
|
name: `opencode-toolbox-client-${this.name}`,
|
|
37601
37996
|
version: "0.1.0"
|
|
37602
37997
|
}, {});
|
|
@@ -37606,12 +38001,46 @@ class RemoteMCPClient {
|
|
|
37606
38001
|
throw new Error(`Remote MCP server ${this.name} has no URL`);
|
|
37607
38002
|
}
|
|
37608
38003
|
const url3 = new URL(this.config.url);
|
|
37609
|
-
this.
|
|
37610
|
-
|
|
37611
|
-
|
|
38004
|
+
this.transportType = null;
|
|
38005
|
+
let streamableTransport = null;
|
|
38006
|
+
try {
|
|
38007
|
+
streamableTransport = new StreamableHTTPClientTransport(url3, {
|
|
38008
|
+
requestInit: {
|
|
38009
|
+
headers: this.config.headers
|
|
38010
|
+
}
|
|
38011
|
+
});
|
|
38012
|
+
await this.client.connect(streamableTransport);
|
|
38013
|
+
this.transport = streamableTransport;
|
|
38014
|
+
this.transportType = "streamable-http";
|
|
38015
|
+
return;
|
|
38016
|
+
} catch (error92) {
|
|
38017
|
+
if (streamableTransport) {
|
|
38018
|
+
await streamableTransport.close().catch(() => {});
|
|
37612
38019
|
}
|
|
37613
|
-
|
|
37614
|
-
|
|
38020
|
+
this.client = this.createClient();
|
|
38021
|
+
}
|
|
38022
|
+
let sseTransport = null;
|
|
38023
|
+
try {
|
|
38024
|
+
const sseHeaders = {
|
|
38025
|
+
Accept: "text/event-stream",
|
|
38026
|
+
...this.config.headers
|
|
38027
|
+
};
|
|
38028
|
+
sseTransport = new SSEClientTransport(url3, {
|
|
38029
|
+
requestInit: {
|
|
38030
|
+
headers: sseHeaders
|
|
38031
|
+
}
|
|
38032
|
+
});
|
|
38033
|
+
await this.client.connect(sseTransport);
|
|
38034
|
+
this.transport = sseTransport;
|
|
38035
|
+
this.transportType = "sse";
|
|
38036
|
+
} catch (error92) {
|
|
38037
|
+
if (sseTransport) {
|
|
38038
|
+
await sseTransport.close().catch(() => {});
|
|
38039
|
+
}
|
|
38040
|
+
this.transport = null;
|
|
38041
|
+
this.transportType = null;
|
|
38042
|
+
throw error92;
|
|
38043
|
+
}
|
|
37615
38044
|
}
|
|
37616
38045
|
async listTools() {
|
|
37617
38046
|
const result = await this.client.listTools();
|
|
@@ -37630,10 +38059,14 @@ class RemoteMCPClient {
|
|
|
37630
38059
|
this.transport = null;
|
|
37631
38060
|
}
|
|
37632
38061
|
this.toolsCache = null;
|
|
38062
|
+
this.transportType = null;
|
|
37633
38063
|
}
|
|
37634
38064
|
getCachedTools() {
|
|
37635
38065
|
return this.toolsCache;
|
|
37636
38066
|
}
|
|
38067
|
+
getTransportType() {
|
|
38068
|
+
return this.transportType;
|
|
38069
|
+
}
|
|
37637
38070
|
}
|
|
37638
38071
|
// src/mcp-client/manager.ts
|
|
37639
38072
|
import { EventEmitter } from "events";
|
|
@@ -38294,7 +38727,7 @@ function formatSearchResults(results, allTools) {
|
|
|
38294
38727
|
schema: catalogTool?.inputSchema || null
|
|
38295
38728
|
};
|
|
38296
38729
|
}),
|
|
38297
|
-
usage: "Use toolbox_execute({
|
|
38730
|
+
usage: "Use toolbox_execute({ toolId: '<toolId>', arguments: '<json>' }) to run a discovered tool"
|
|
38298
38731
|
};
|
|
38299
38732
|
return JSON.stringify(output, null, 2);
|
|
38300
38733
|
}
|
|
@@ -38308,7 +38741,8 @@ Use when you know part of a tool name or server prefix (e.g., "time_.*", "exa_.*
|
|
|
38308
38741
|
Returns tools with schemas. Use toolbox_execute() to run them.`;
|
|
38309
38742
|
var EXECUTE_DESC = `Execute a tool discovered via toolbox_search_bm25 or toolbox_search_regex.
|
|
38310
38743
|
|
|
38311
|
-
Pass arguments as JSON string matching the tool's schema
|
|
38744
|
+
Pass arguments as JSON string matching the tool's schema.
|
|
38745
|
+
toolId format: {serverName}_{toolName}`;
|
|
38312
38746
|
var STATUS_DESC = `Get toolbox status including plugin initialization, MCP server connections, and tool counts.
|
|
38313
38747
|
|
|
38314
38748
|
Shows success/total metrics to highlight failures. Use to check if toolbox is working correctly.`;
|
|
@@ -38320,7 +38754,11 @@ var TEST_DESC = `Test all toolbox tools with minimal predefined prompts.
|
|
|
38320
38754
|
Executes every registered tool with super simple inputs to verify they work. Returns pass/fail for each tool.`;
|
|
38321
38755
|
var TEST_PROMPTS = {
|
|
38322
38756
|
time_get_current_time: {},
|
|
38323
|
-
time_convert_time: {
|
|
38757
|
+
time_convert_time: {
|
|
38758
|
+
source_timezone: "UTC",
|
|
38759
|
+
time: "12:00",
|
|
38760
|
+
target_timezone: "America/New_York"
|
|
38761
|
+
},
|
|
38324
38762
|
brave_brave_web_search: { query: "test", count: 1 },
|
|
38325
38763
|
brave_brave_local_search: { query: "coffee", count: 1 },
|
|
38326
38764
|
brave_brave_video_search: { query: "test", count: 1 },
|
|
@@ -38328,14 +38766,20 @@ var TEST_PROMPTS = {
|
|
|
38328
38766
|
brave_brave_news_search: { query: "test", count: 1 },
|
|
38329
38767
|
brave_brave_summarizer: { key: "test" },
|
|
38330
38768
|
brightdata_search_engine: { query: "hello", engine: "google", count: 1 },
|
|
38331
|
-
brightdata_search_engine_batch: {
|
|
38769
|
+
brightdata_search_engine_batch: {
|
|
38770
|
+
queries: [{ query: "test", engine: "google", count: 1 }]
|
|
38771
|
+
},
|
|
38332
38772
|
brightdata_scrape_as_markdown: { url: "https://example.com" },
|
|
38333
38773
|
brightdata_scrape_as_html: { url: "https://example.com" },
|
|
38334
38774
|
brightdata_scrape_batch: { urls: ["https://example.com"] },
|
|
38335
38775
|
brightdata_extract: { url: "https://example.com" },
|
|
38336
38776
|
brightdata_session_stats: {},
|
|
38337
|
-
brightdata_web_data_reuter_news: {
|
|
38338
|
-
|
|
38777
|
+
brightdata_web_data_reuter_news: {
|
|
38778
|
+
url: "https://www.reuters.com/technology/"
|
|
38779
|
+
},
|
|
38780
|
+
brightdata_web_data_github_repository_file: {
|
|
38781
|
+
url: "https://github.com/octocat/Hello-World/blob/master/README"
|
|
38782
|
+
},
|
|
38339
38783
|
"tavily_tavily-search": { query: "test", maxResults: 1 },
|
|
38340
38784
|
"tavily_tavily-extract": { urls: ["https://example.com"] },
|
|
38341
38785
|
"tavily_tavily-map": { url: "https://example.com" },
|
|
@@ -38398,26 +38842,56 @@ function ensureCommandFile() {
|
|
|
38398
38842
|
mkdir(COMMAND_DIR, { recursive: true }).then(() => writeFile(COMMAND_FILE_PATH, COMMAND_CONTENT)).then(() => log("info", "Created /toolbox-status command file")).catch(() => {});
|
|
38399
38843
|
});
|
|
38400
38844
|
}
|
|
38401
|
-
var SYSTEM_PROMPT_BASE = `# Extended Toolbox
|
|
38402
|
-
|
|
38403
|
-
You have access to an extended toolbox with additional capabilities (web search, time utilities, code search, etc.).
|
|
38404
|
-
|
|
38405
|
-
## Rules
|
|
38406
|
-
1. ALWAYS toolbox_search_* before saying "I cannot do that" or "I don't have access to."
|
|
38407
|
-
2. ALWAYS toolbox_search_* if you think that user wants you to use some tools
|
|
38408
|
-
3. ALWAYS toolbox_search_* if you think that user may refer specific tool name which is not exist in the context
|
|
38409
|
-
|
|
38410
|
-
## Workflow
|
|
38411
|
-
1. Search: toolbox_search_bm25({ text: "what you need" }) or toolbox_search_regex({ pattern: "prefix_.*" })
|
|
38412
|
-
2. Execute: toolbox_execute({ name: "tool_name", arguments: '{"key": "value"}' })`;
|
|
38413
38845
|
function generateSystemPrompt(configuredServers) {
|
|
38414
|
-
|
|
38415
|
-
|
|
38416
|
-
|
|
38417
|
-
|
|
38418
|
-
|
|
38419
|
-
|
|
38420
|
-
|
|
38846
|
+
const registry3 = configuredServers.length > 0 ? configuredServers.map((s) => `${s}_*`).join(`
|
|
38847
|
+
`) : "(no servers configured)";
|
|
38848
|
+
return `
|
|
38849
|
+
<MCPTools>
|
|
38850
|
+
<Rules>
|
|
38851
|
+
ALWAYS toolbox_search_* before saying "I cannot do that" or "I don't have access to"
|
|
38852
|
+
ALWAYS toolbox_search_* if user wants to use tools or refers to unknown tool names
|
|
38853
|
+
</Rules>
|
|
38854
|
+
<MCPServers>
|
|
38855
|
+
<Registry>
|
|
38856
|
+
${registry3}
|
|
38857
|
+
</Registry>
|
|
38858
|
+
<NamingConvention>
|
|
38859
|
+
serverName: MCP server name
|
|
38860
|
+
toolName: tool name provided by MCP server
|
|
38861
|
+
toolId: {serverName}_{toolName}
|
|
38862
|
+
</NamingConvention>
|
|
38863
|
+
<Patterns>
|
|
38864
|
+
ALL: ".*"
|
|
38865
|
+
SERVER: "{serverName}_.*"
|
|
38866
|
+
TOOL: "{serverName}_{toolName}"
|
|
38867
|
+
</Patterns>
|
|
38868
|
+
<Discovery>
|
|
38869
|
+
<ListAllTools>
|
|
38870
|
+
toolbox_search_regex({ pattern: ".*" })
|
|
38871
|
+
</ListAllTools>
|
|
38872
|
+
<ServerTools>
|
|
38873
|
+
toolbox_search_regex({ pattern: "serverName_.*" })
|
|
38874
|
+
</ServerTools>
|
|
38875
|
+
<FreeSearch>
|
|
38876
|
+
toolbox_search_bm25({ text: "description keywords" })
|
|
38877
|
+
</FreeSearch>
|
|
38878
|
+
</Discovery>
|
|
38879
|
+
<Execute>
|
|
38880
|
+
toolbox_execute({ toolId: "toolId", arguments: '{}' })
|
|
38881
|
+
</Execute>
|
|
38882
|
+
<When>
|
|
38883
|
+
regex: know server name or partial tool name
|
|
38884
|
+
bm25: know what you want to do, not tool name
|
|
38885
|
+
</When>
|
|
38886
|
+
<Fallback>
|
|
38887
|
+
toolbox_search_regex \u2192 toolbox_search_bm25 \u2192 toolbox_status \u2192 ask user
|
|
38888
|
+
</Fallback>
|
|
38889
|
+
<Troubleshoot>
|
|
38890
|
+
If tool not found: check server prefix, try bm25 with descriptive text
|
|
38891
|
+
Check server health: toolbox_status()
|
|
38892
|
+
</Troubleshoot>
|
|
38893
|
+
</MCPServers>
|
|
38894
|
+
</MCPTools>`;
|
|
38421
38895
|
}
|
|
38422
38896
|
var ToolboxPlugin = async (ctx) => {
|
|
38423
38897
|
const pluginLoadStart = performance.now();
|
|
@@ -38565,7 +39039,7 @@ var ToolboxPlugin = async (ctx) => {
|
|
|
38565
39039
|
toolbox_execute: tool({
|
|
38566
39040
|
description: EXECUTE_DESC,
|
|
38567
39041
|
args: {
|
|
38568
|
-
|
|
39042
|
+
toolId: tool.schema.string().describe("Tool ID from search results. Format: {serverName}_{toolName} (e.g., 'time_get_current_time', 'brave_web_search')"),
|
|
38569
39043
|
arguments: tool.schema.string().optional().describe("JSON-encoded arguments for the tool, matching its schema. Use '{}' or omit for tools with no required arguments.")
|
|
38570
39044
|
},
|
|
38571
39045
|
async execute(args) {
|
|
@@ -38579,15 +39053,15 @@ var ToolboxPlugin = async (ctx) => {
|
|
|
38579
39053
|
error: `Failed to initialize: ${error92 instanceof Error ? error92.message : String(error92)}`
|
|
38580
39054
|
});
|
|
38581
39055
|
}
|
|
38582
|
-
const parsed = parseToolName(args.
|
|
39056
|
+
const parsed = parseToolName(args.toolId);
|
|
38583
39057
|
if (!parsed) {
|
|
38584
39058
|
timer();
|
|
38585
|
-
log("warn", `Invalid
|
|
38586
|
-
|
|
39059
|
+
log("warn", `Invalid toolId format: ${args.toolId}`, {
|
|
39060
|
+
toolId: args.toolId
|
|
38587
39061
|
});
|
|
38588
39062
|
return JSON.stringify({
|
|
38589
39063
|
success: false,
|
|
38590
|
-
error: `Invalid
|
|
39064
|
+
error: `Invalid toolId format: ${args.toolId}. Expected format: {serverName}_{toolName} (e.g., 'time_get_current_time')`
|
|
38591
39065
|
});
|
|
38592
39066
|
}
|
|
38593
39067
|
let toolArgs = {};
|
|
@@ -38596,8 +39070,8 @@ var ToolboxPlugin = async (ctx) => {
|
|
|
38596
39070
|
toolArgs = JSON.parse(args.arguments);
|
|
38597
39071
|
} catch (error92) {
|
|
38598
39072
|
timer();
|
|
38599
|
-
log("warn", `Failed to parse arguments as JSON for ${args.
|
|
38600
|
-
|
|
39073
|
+
log("warn", `Failed to parse arguments as JSON for ${args.toolId}`, {
|
|
39074
|
+
toolId: args.toolId,
|
|
38601
39075
|
arguments: args.arguments
|
|
38602
39076
|
});
|
|
38603
39077
|
return JSON.stringify({
|
|
@@ -38611,7 +39085,7 @@ var ToolboxPlugin = async (ctx) => {
|
|
|
38611
39085
|
const result = await mcpManager.callTool(parsed.serverName, parsed.toolName, toolArgs);
|
|
38612
39086
|
const duration5 = timer();
|
|
38613
39087
|
executionSuccessCount++;
|
|
38614
|
-
log("info", `Tool executed successfully: ${args.
|
|
39088
|
+
log("info", `Tool executed successfully: ${args.toolId} in ${duration5.toFixed(2)}ms`, {
|
|
38615
39089
|
server: parsed.serverName,
|
|
38616
39090
|
tool: parsed.toolName,
|
|
38617
39091
|
durationMs: duration5
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-toolbox",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "Tool Search Tool Plugin for OpenCode - search and execute tools from MCP servers on-demand",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.js",
|
|
@@ -24,7 +24,8 @@
|
|
|
24
24
|
},
|
|
25
25
|
"files": [
|
|
26
26
|
"dist",
|
|
27
|
-
"README.md"
|
|
27
|
+
"README.md",
|
|
28
|
+
"toolbox.schema.json"
|
|
28
29
|
],
|
|
29
30
|
"keywords": [
|
|
30
31
|
"opencode",
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://unpkg.com/opencode-toolbox@latest/toolbox.schema.json",
|
|
4
|
+
"title": "OpenCode Toolbox Configuration",
|
|
5
|
+
"description": "Configuration schema for opencode-toolbox plugin",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"properties": {
|
|
8
|
+
"$schema": {
|
|
9
|
+
"type": "string",
|
|
10
|
+
"description": "JSON Schema reference for editor support"
|
|
11
|
+
},
|
|
12
|
+
"mcp": {
|
|
13
|
+
"type": "object",
|
|
14
|
+
"description": "MCP servers to connect to",
|
|
15
|
+
"additionalProperties": {
|
|
16
|
+
"oneOf": [
|
|
17
|
+
{
|
|
18
|
+
"type": "object",
|
|
19
|
+
"description": "Local MCP server (stdio)",
|
|
20
|
+
"properties": {
|
|
21
|
+
"type": {
|
|
22
|
+
"type": "string",
|
|
23
|
+
"const": "local",
|
|
24
|
+
"description": "Server type: local process via stdio"
|
|
25
|
+
},
|
|
26
|
+
"command": {
|
|
27
|
+
"type": "array",
|
|
28
|
+
"items": { "type": "string" },
|
|
29
|
+
"minItems": 1,
|
|
30
|
+
"description": "Command and arguments to spawn the MCP server"
|
|
31
|
+
},
|
|
32
|
+
"environment": {
|
|
33
|
+
"type": "object",
|
|
34
|
+
"additionalProperties": { "type": "string" },
|
|
35
|
+
"description": "Environment variables for the process. Use {env:VAR_NAME} for interpolation."
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"required": ["type", "command"],
|
|
39
|
+
"additionalProperties": false
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"type": "object",
|
|
43
|
+
"description": "Remote MCP server (HTTP/SSE)",
|
|
44
|
+
"properties": {
|
|
45
|
+
"type": {
|
|
46
|
+
"type": "string",
|
|
47
|
+
"const": "remote",
|
|
48
|
+
"description": "Server type: remote HTTP/SSE endpoint"
|
|
49
|
+
},
|
|
50
|
+
"url": {
|
|
51
|
+
"type": "string",
|
|
52
|
+
"format": "uri",
|
|
53
|
+
"description": "Remote MCP endpoint URL"
|
|
54
|
+
},
|
|
55
|
+
"headers": {
|
|
56
|
+
"type": "object",
|
|
57
|
+
"additionalProperties": { "type": "string" },
|
|
58
|
+
"description": "HTTP headers for authentication. Use {env:VAR_NAME} for interpolation."
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
"required": ["type", "url"],
|
|
62
|
+
"additionalProperties": false
|
|
63
|
+
}
|
|
64
|
+
]
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
"settings": {
|
|
68
|
+
"type": "object",
|
|
69
|
+
"description": "Plugin settings",
|
|
70
|
+
"properties": {
|
|
71
|
+
"defaultLimit": {
|
|
72
|
+
"type": "integer",
|
|
73
|
+
"minimum": 1,
|
|
74
|
+
"maximum": 20,
|
|
75
|
+
"default": 5,
|
|
76
|
+
"description": "Default number of search results to return"
|
|
77
|
+
},
|
|
78
|
+
"initMode": {
|
|
79
|
+
"type": "string",
|
|
80
|
+
"enum": ["eager", "lazy"],
|
|
81
|
+
"default": "eager",
|
|
82
|
+
"description": "Initialization mode: 'eager' connects on plugin load, 'lazy' connects on first use"
|
|
83
|
+
},
|
|
84
|
+
"connection": {
|
|
85
|
+
"type": "object",
|
|
86
|
+
"description": "Connection settings for MCP servers",
|
|
87
|
+
"properties": {
|
|
88
|
+
"connectTimeout": {
|
|
89
|
+
"type": "integer",
|
|
90
|
+
"minimum": 100,
|
|
91
|
+
"maximum": 60000,
|
|
92
|
+
"default": 5000,
|
|
93
|
+
"description": "Connection timeout in milliseconds"
|
|
94
|
+
},
|
|
95
|
+
"requestTimeout": {
|
|
96
|
+
"type": "integer",
|
|
97
|
+
"minimum": 100,
|
|
98
|
+
"maximum": 300000,
|
|
99
|
+
"default": 30000,
|
|
100
|
+
"description": "Request timeout in milliseconds"
|
|
101
|
+
},
|
|
102
|
+
"retryAttempts": {
|
|
103
|
+
"type": "integer",
|
|
104
|
+
"minimum": 0,
|
|
105
|
+
"maximum": 10,
|
|
106
|
+
"default": 2,
|
|
107
|
+
"description": "Number of retry attempts on connection failure"
|
|
108
|
+
},
|
|
109
|
+
"retryDelay": {
|
|
110
|
+
"type": "integer",
|
|
111
|
+
"minimum": 0,
|
|
112
|
+
"maximum": 30000,
|
|
113
|
+
"default": 1000,
|
|
114
|
+
"description": "Delay between retries in milliseconds"
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
"additionalProperties": false
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
"additionalProperties": false
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
"required": ["mcp"],
|
|
124
|
+
"additionalProperties": false
|
|
125
|
+
}
|