opencode-raven 2.1.0 → 2.1.2
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 +47 -33
- package/index.ts +64 -32
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -2,27 +2,27 @@
|
|
|
2
2
|
|
|
3
3
|
<table>
|
|
4
4
|
<tr>
|
|
5
|
-
<td><img src="Raven.png" alt="Raven" width="
|
|
5
|
+
<td width="40%"><img src="Raven.png" alt="Raven" width="100%" /></td>
|
|
6
6
|
<td>
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
Raven gives OpenCode useful search tools by default and optional on-demand MCPs for any remote or local MCP you want to add. It keeps noisy tool work behind a focused Raven agent, so your main model gets compact answers instead of raw search results, docs pages, GitHub examples, MCP output, or large MCP schemas.
|
|
8
|
+
|
|
9
|
+
## Why Use Raven?
|
|
10
|
+
|
|
11
|
+
* Routed search, fetch, and bash discovery work through Raven.
|
|
12
|
+
* Context7, Exa, and Grep.app are bundled as on-demand MCP defaults.
|
|
13
|
+
* Any extra MCP can be added under `onDemandMcpServers` when you want it.
|
|
14
|
+
* Configured noisy tools and MCPs are blocked for non-Raven agents and forced through `raven_seek`.
|
|
9
15
|
</td>
|
|
10
16
|
</tr>
|
|
11
17
|
</table>
|
|
12
18
|
|
|
13
|
-
##
|
|
14
|
-
|
|
15
|
-
Tool-heavy work floods context. Search, docs, web, GitHub examples, and verbose MCP calls can dump raw results into your expensive main model. Raven is a hard blocker-rerouter: configured tools fail closed for non-Raven agents and must be delegated through `raven_seek`, where a focused Raven agent performs the work and returns a compact answer.
|
|
16
|
-
|
|
17
|
-
Raven fixes three problems:
|
|
18
|
-
|
|
19
|
-
1. **Context flooding** - Keep noisy tool results out of the main session. Raven summarizes tool/MCP output before returning it.
|
|
20
|
-
2. **Cost** - Use a cheaper model like `opencode/deepseek-v4-flash-free` for tool-heavy work while saving your main model's context for decisions and edits.
|
|
21
|
-
3. **Enforcement** - This is not soft nudging. Raven blocks configured tools/MCP prefixes for non-Raven agents and gives them the exact `raven_seek` retry path.
|
|
19
|
+
## Example
|
|
22
20
|
|
|
23
|
-
|
|
21
|
+
<p align="center">
|
|
22
|
+
<img src="example.gif" alt="Raven example: searching today's top 30 AI news items while saving 71K context" />
|
|
23
|
+
</p>
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
In this example, the main model asks for the top 30 AI news items from today. Raven handles the noisy search/web work in a focused agent and returns the compact result, saving about 71K context that would otherwise have landed in the main session.
|
|
26
26
|
|
|
27
27
|
## Install
|
|
28
28
|
|
|
@@ -36,6 +36,8 @@ Add Raven to your `opencode.jsonc` plugins:
|
|
|
36
36
|
|
|
37
37
|
Restart opencode. It will resolve Raven from npm and cache the plugin automatically.
|
|
38
38
|
|
|
39
|
+
That is enough to start using Raven. The default config already includes routed search/fetch tools and bundled on-demand MCPs for Context7, Exa, and Grep.app. Add any other MCP under `onDemandMcpServers` only if you want more capabilities.
|
|
40
|
+
|
|
39
41
|
Raven checks for package updates and notifies you when a newer version is available. Run `/raven update`, then restart opencode.
|
|
40
42
|
|
|
41
43
|
## Quick Use
|
|
@@ -78,17 +80,11 @@ The main agent does not see Raven's internal tool calls or raw tool output, just
|
|
|
78
80
|
|
|
79
81
|
Config persists across restarts in `~/.config/opencode/opencode-raven/raven-config.json`. It is global and shared across all projects.
|
|
80
82
|
|
|
81
|
-
## Example
|
|
82
|
-
|
|
83
|
-
<p align="center">
|
|
84
|
-
<img src="example.gif" alt="Raven example: searching today's top 30 AI news items while saving 71K context" />
|
|
85
|
-
</p>
|
|
86
|
-
|
|
87
|
-
In this example, the main model asks for the top 30 AI news items from today. Raven handles the noisy search/web work in a focused agent and returns the compact result, saving about 71K context that would otherwise have landed in the main session.
|
|
88
|
-
|
|
89
83
|
## Recommended MCP Setup
|
|
90
84
|
|
|
91
|
-
|
|
85
|
+
You do not need to configure MCPs just to try Raven. Raven gives you the search tools up front, supports any MCP as an on-demand MCP, and bundles Context7, Exa, and Grep.app as defaults. Add more MCPs only when you want extra capabilities.
|
|
86
|
+
|
|
87
|
+
On-demand MCPs live in `onDemandMcpServers`, so Raven connects to them internally through `raven_mcp` only when needed instead of registering their full tool schemas in OpenCode's global MCP config. Context7, Exa, and Grep.app are included there by default, but the same setup works for any remote or local MCP you want to add.
|
|
92
88
|
|
|
93
89
|
On-demand MCPs are always behind Raven and do not need routing entries.
|
|
94
90
|
|
|
@@ -96,6 +92,8 @@ Raven checks configured on-demand MCPs after startup. `/raven` and `/raven mcp`
|
|
|
96
92
|
|
|
97
93
|
Raven also warns if the same MCP server name is configured both globally in OpenCode and in Raven's `onDemandMcpServers`. Prefer one location: on-demand for schema hiding, or global only when direct main-agent access is required.
|
|
98
94
|
|
|
95
|
+
Important limitation: MCPs enabled directly in OpenCode config still load their tool schemas into the main session. Raven saves context from tool calls and raw results for those global MCPs, but schema hiding requires putting the MCP in Raven's `onDemandMcpServers`. **Strongly recommended:** configure noisy/search/docs/web MCPs as on-demand MCPs unless you need direct main-agent access.
|
|
96
|
+
|
|
99
97
|
All three bundled MCPs work without API keys. Add keys for higher rate limits:
|
|
100
98
|
|
|
101
99
|
| MCP | URL | API key |
|
|
@@ -119,23 +117,36 @@ To add an API key to an on-demand MCP, edit Raven config:
|
|
|
119
117
|
}
|
|
120
118
|
```
|
|
121
119
|
|
|
122
|
-
|
|
120
|
+
### Add Your Own On-Demand MCP
|
|
121
|
+
|
|
122
|
+
To add another MCP, put it under `onDemandMcpServers`. Raven will connect to it only when needed, discover its tools, and generate guidance automatically. Your main model does not need to load that MCP's full schema up front.
|
|
123
|
+
|
|
124
|
+
Use `remote` for hosted MCPs and `local` for command-based MCPs.
|
|
125
|
+
|
|
126
|
+
To add an OpenCode-style local MCP:
|
|
123
127
|
|
|
124
128
|
```jsonc
|
|
125
129
|
{
|
|
126
130
|
"onDemandMcpServers": {
|
|
127
|
-
"
|
|
128
|
-
"type": "
|
|
129
|
-
"command":
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
131
|
+
"unityMCP": {
|
|
132
|
+
"type": "local",
|
|
133
|
+
"command": [
|
|
134
|
+
"C:\\Users\\evila\\.local\\bin\\uvx.exe",
|
|
135
|
+
"--offline",
|
|
136
|
+
"--from",
|
|
137
|
+
"mcpforunityserver==9.7.1",
|
|
138
|
+
"mcp-for-unity",
|
|
139
|
+
"--transport",
|
|
140
|
+
"stdio"
|
|
141
|
+
],
|
|
142
|
+
"enabled": true
|
|
134
143
|
}
|
|
135
144
|
}
|
|
136
145
|
}
|
|
137
146
|
```
|
|
138
147
|
|
|
148
|
+
This matches OpenCode's local MCP shape: `type: "local"`, `command` as an array, optional `environment`, optional `timeout`, and optional `enabled`. Raven also supports `cwd` and `description`. `enabled` defaults to `true`; set it to `false` to keep a server in config without loading it.
|
|
149
|
+
|
|
139
150
|
`description` is user-authored and never overwritten. Raven fills missing generated metadata after startup or when you run `/raven mcp refresh [server]`, storing tool summaries, MCP health, and avoided schema-load estimates separately in `~/.config/opencode/opencode-raven/autogenerated-on-demand-mcp-metadata.json`.
|
|
140
151
|
|
|
141
152
|
Raven also writes generated main-model guidance to `~/.config/opencode/opencode-raven/autogenerated-on-demand-guidance.md` and injects that file into OpenCode instructions at startup.
|
|
@@ -157,16 +168,19 @@ Edit config manually or use `/raven` commands:
|
|
|
157
168
|
"onDemandMcpDescriptionDetail": "full",
|
|
158
169
|
"onDemandMcpServers": {
|
|
159
170
|
"context7": {
|
|
171
|
+
"enabled": true,
|
|
160
172
|
"type": "remote",
|
|
161
173
|
"url": "https://mcp.context7.com/mcp",
|
|
162
174
|
"description": "Library/framework documentation lookup."
|
|
163
175
|
},
|
|
164
176
|
"exa": {
|
|
177
|
+
"enabled": true,
|
|
165
178
|
"type": "remote",
|
|
166
179
|
"url": "https://mcp.exa.ai/mcp",
|
|
167
180
|
"description": "Live web search and webpage fetching."
|
|
168
181
|
},
|
|
169
182
|
"grep_app": {
|
|
183
|
+
"enabled": true,
|
|
170
184
|
"type": "remote",
|
|
171
185
|
"url": "https://mcp.grep.app",
|
|
172
186
|
"description": "Public GitHub code search and real-world examples."
|
|
@@ -187,7 +201,7 @@ Edit config manually or use `/raven` commands:
|
|
|
187
201
|
| `routeTools` | built-in search/fetch tools plus `bash` | Exact tool names hard-routed through Raven. `bash` routes only search-like bash commands, not every bash call. |
|
|
188
202
|
| `routeToolKeywords` | `[]` | Case-insensitive substrings hard-routed through Raven for globally registered tools with non-standard names. Catches suffix-style names like `web_search_exa`. |
|
|
189
203
|
| `onDemandMcpDescriptionDetail` | `"full"` | On-demand MCP guidance detail. `full` includes tool names/descriptions; `minimized` uses compact capability summaries. |
|
|
190
|
-
| `onDemandMcpServers` | Context7, Exa, Grep.app | MCPs Raven connects to internally on demand, without registering full schemas in OpenCode global MCP config. Supports `remote` and `
|
|
204
|
+
| `onDemandMcpServers` | Context7, Exa, Grep.app | MCPs Raven connects to internally on demand, without registering full schemas in OpenCode global MCP config. Supports OpenCode-style `remote` and `local` entries. Each server supports `enabled`, defaulting to `true`. |
|
|
191
205
|
| `excludeAgents` | `[]` | Agents that bypass Raven routing, case-insensitive. |
|
|
192
206
|
| `excludeTools` | `[]` | Exact tools that never get blocked, even if matched by auto-routed global MCP prefixes. |
|
|
193
207
|
| `timeout` | `180` | Max seconds for a `raven_seek` call. On timeout the session is kept for inspection. |
|
|
@@ -285,7 +299,7 @@ Raven itself has access to these tools, blocked for other agents when configured
|
|
|
285
299
|
| `read`, `glob`, `grep`, `list` | Local codebase inspection |
|
|
286
300
|
| `bash` | Full local shell access, including `rg`, `grep`, `dir`, `ls`, `Get-ChildItem`, and `find` |
|
|
287
301
|
| `external_directory` | Allowed, with no permission prompts when accessing paths outside the workspace |
|
|
288
|
-
| `raven_mcp` | Raven-only bridge to on-demand remote/
|
|
302
|
+
| `raven_mcp` | Raven-only bridge to on-demand remote/local MCP servers |
|
|
289
303
|
| Context7 | On-demand library/framework/SDK/API docs |
|
|
290
304
|
| Exa AI | On-demand web search, news, pages, products |
|
|
291
305
|
| Grep.app | On-demand public GitHub examples |
|
package/index.ts
CHANGED
|
@@ -31,16 +31,19 @@ const DEFAULT_ROUTE_TOOL_KEYWORDS: string[] = []
|
|
|
31
31
|
|
|
32
32
|
const DEFAULT_ON_DEMAND_MCP_SERVERS: Record<string, OnDemandMcpServer> = {
|
|
33
33
|
context7: {
|
|
34
|
+
enabled: true,
|
|
34
35
|
type: "remote",
|
|
35
36
|
url: "https://mcp.context7.com/mcp",
|
|
36
37
|
description: "Library/framework documentation lookup.",
|
|
37
38
|
},
|
|
38
39
|
exa: {
|
|
40
|
+
enabled: true,
|
|
39
41
|
type: "remote",
|
|
40
42
|
url: "https://mcp.exa.ai/mcp",
|
|
41
43
|
description: "Live web search and webpage fetching.",
|
|
42
44
|
},
|
|
43
45
|
grep_app: {
|
|
46
|
+
enabled: true,
|
|
44
47
|
type: "remote",
|
|
45
48
|
url: "https://mcp.grep.app",
|
|
46
49
|
description: "Public GitHub code search and real-world examples.",
|
|
@@ -138,6 +141,8 @@ interface OnDemandMcpToolInfo {
|
|
|
138
141
|
}
|
|
139
142
|
|
|
140
143
|
interface BaseOnDemandMcpServer {
|
|
144
|
+
enabled?: boolean
|
|
145
|
+
timeout?: number
|
|
141
146
|
description?: string
|
|
142
147
|
}
|
|
143
148
|
|
|
@@ -154,17 +159,17 @@ interface RemoteOnDemandMcpServer extends BaseOnDemandMcpServer {
|
|
|
154
159
|
type: "remote"
|
|
155
160
|
url: string
|
|
156
161
|
headers?: Record<string, string>
|
|
162
|
+
oauth?: Record<string, unknown> | false
|
|
157
163
|
}
|
|
158
164
|
|
|
159
|
-
interface
|
|
160
|
-
type: "
|
|
161
|
-
command: string
|
|
162
|
-
|
|
163
|
-
env?: Record<string, string>
|
|
165
|
+
interface LocalOnDemandMcpServer extends BaseOnDemandMcpServer {
|
|
166
|
+
type: "local"
|
|
167
|
+
command: string[]
|
|
168
|
+
environment?: Record<string, string>
|
|
164
169
|
cwd?: string
|
|
165
170
|
}
|
|
166
171
|
|
|
167
|
-
type OnDemandMcpServer = RemoteOnDemandMcpServer |
|
|
172
|
+
type OnDemandMcpServer = RemoteOnDemandMcpServer | LocalOnDemandMcpServer
|
|
168
173
|
type OnDemandMcpDescriptionDetail = "full" | "minimized"
|
|
169
174
|
type RavenMcpOperation = "list_tools" | "call_tool" | "list_resources" | "read_resource" | "list_prompts" | "get_prompt"
|
|
170
175
|
|
|
@@ -303,32 +308,45 @@ function normalizeToolInfo(value: unknown): OnDemandMcpToolInfo[] | undefined {
|
|
|
303
308
|
return tools.length ? tools : undefined
|
|
304
309
|
}
|
|
305
310
|
|
|
311
|
+
function normalizeStringRecord(value: unknown): Record<string, string> | undefined {
|
|
312
|
+
if (!value || typeof value !== "object") return undefined
|
|
313
|
+
const entries = Object.entries(value).filter((entry): entry is [string, string] => typeof entry[1] === "string")
|
|
314
|
+
return entries.length ? Object.fromEntries(entries) : undefined
|
|
315
|
+
}
|
|
316
|
+
|
|
306
317
|
function normalizeOnDemandMcpServer(value: unknown): OnDemandMcpServer | undefined {
|
|
307
318
|
if (!value || typeof value !== "object") return undefined
|
|
308
319
|
const source = value as Record<string, any>
|
|
309
320
|
const base = {
|
|
321
|
+
enabled: source.enabled !== false,
|
|
322
|
+
...(typeof source.timeout === "number" && source.timeout > 0 ? { timeout: Math.floor(source.timeout) } : {}),
|
|
310
323
|
...(typeof source.description === "string" && source.description.trim() ? { description: source.description.trim() } : {}),
|
|
311
324
|
}
|
|
312
325
|
|
|
313
|
-
if (source.type === "
|
|
314
|
-
|
|
326
|
+
if (source.type === "local") {
|
|
327
|
+
const commandParts = Array.isArray(source.command)
|
|
328
|
+
? source.command.filter((arg: unknown): arg is string => typeof arg === "string" && arg.trim())
|
|
329
|
+
: []
|
|
330
|
+
if (!commandParts.length) return undefined
|
|
331
|
+
const environment = normalizeStringRecord(source.environment)
|
|
315
332
|
return {
|
|
316
333
|
...base,
|
|
317
|
-
type: "
|
|
318
|
-
command:
|
|
319
|
-
...(
|
|
320
|
-
...(source.env && typeof source.env === "object" ? { env: Object.fromEntries(Object.entries(source.env).filter((entry): entry is [string, string] => typeof entry[1] === "string")) } : {}),
|
|
334
|
+
type: "local",
|
|
335
|
+
command: commandParts,
|
|
336
|
+
...(environment ? { environment } : {}),
|
|
321
337
|
...(typeof source.cwd === "string" && source.cwd.trim() ? { cwd: source.cwd.trim() } : {}),
|
|
322
338
|
}
|
|
323
339
|
}
|
|
324
340
|
|
|
325
341
|
if (source.type === "remote" || typeof source.url === "string") {
|
|
326
342
|
if (typeof source.url !== "string" || !source.url.trim()) return undefined
|
|
343
|
+
const headers = normalizeStringRecord(source.headers)
|
|
327
344
|
return {
|
|
328
345
|
...base,
|
|
329
346
|
type: "remote",
|
|
330
347
|
url: source.url.trim(),
|
|
331
|
-
...(
|
|
348
|
+
...(headers ? { headers } : {}),
|
|
349
|
+
...(source.oauth === false ? { oauth: false } : source.oauth && typeof source.oauth === "object" ? { oauth: source.oauth } : {}),
|
|
332
350
|
}
|
|
333
351
|
}
|
|
334
352
|
|
|
@@ -491,7 +509,7 @@ export default ((input: PluginInput) => {
|
|
|
491
509
|
|
|
492
510
|
function resetOnDemandMcpRuntimeStatuses() {
|
|
493
511
|
let changed = false
|
|
494
|
-
for (const name of
|
|
512
|
+
for (const [name] of activeOnDemandMcpEntries()) {
|
|
495
513
|
const metadata = mcpMetadata[name] ?? {}
|
|
496
514
|
if (metadata.status !== "pending" || metadata.lastError || metadata.lastCheckedAt) {
|
|
497
515
|
mcpMetadata[name] = { ...metadata, status: "pending" }
|
|
@@ -591,8 +609,11 @@ export default ((input: PluginInput) => {
|
|
|
591
609
|
|
|
592
610
|
function onDemandMcpGuidance(target: "delegate" | "raven" = "delegate"): string {
|
|
593
611
|
const servers = config.onDemandMcpServers ?? {}
|
|
594
|
-
const
|
|
595
|
-
|
|
612
|
+
const activeEntries = activeOnDemandMcpEntries()
|
|
613
|
+
const entries = activeEntries.filter(([name]) => onDemandMcpRuntimeStatus(name) === "ready")
|
|
614
|
+
if (!Object.keys(servers).length) return "No on-demand MCPs are configured."
|
|
615
|
+
if (!activeEntries.length) return "No enabled on-demand MCPs are configured."
|
|
616
|
+
if (!entries.length) return "No loaded on-demand MCPs are available."
|
|
596
617
|
|
|
597
618
|
const detail = config.onDemandMcpDescriptionDetail ?? "full"
|
|
598
619
|
const lines = entries.map(([name, server]) => {
|
|
@@ -693,22 +714,29 @@ export default ((input: PluginInput) => {
|
|
|
693
714
|
}
|
|
694
715
|
|
|
695
716
|
function getOnDemandMcpServer(serverName: string): [string, OnDemandMcpServer] | undefined {
|
|
696
|
-
const entries =
|
|
717
|
+
const entries = activeOnDemandMcpEntries()
|
|
697
718
|
return entries.find(([name]) => name.toLowerCase() === serverName.toLowerCase())
|
|
698
719
|
}
|
|
699
720
|
|
|
721
|
+
function activeOnDemandMcpEntries(): [string, OnDemandMcpServer][] {
|
|
722
|
+
return Object.entries(config.onDemandMcpServers ?? {}).filter(([, server]) => server.enabled !== false)
|
|
723
|
+
}
|
|
724
|
+
|
|
700
725
|
async function connectOnDemandMcp(serverName: string, server: OnDemandMcpServer): Promise<McpConnection> {
|
|
701
|
-
const timeout = (config.timeout ?? 180) * 1000
|
|
726
|
+
const timeout = server.timeout ?? (config.timeout ?? 180) * 1000
|
|
702
727
|
const client = new Client({ name: PACKAGE_NAME, version: PACKAGE_VERSION })
|
|
703
728
|
|
|
704
|
-
if (server.type === "
|
|
705
|
-
const env = { ...cleanProcessEnv(), ...resolveStringRecord(server.
|
|
729
|
+
if (server.type === "local") {
|
|
730
|
+
const env = { ...cleanProcessEnv(), ...resolveStringRecord(server.environment) }
|
|
706
731
|
const transport = new StdioClientTransport({
|
|
707
|
-
command: resolveEnvPlaceholders(server.command),
|
|
708
|
-
args: server.
|
|
732
|
+
command: resolveEnvPlaceholders(server.command[0]),
|
|
733
|
+
args: server.command.slice(1).map((arg) => resolveEnvPlaceholders(arg)),
|
|
709
734
|
env,
|
|
735
|
+
stderr: "pipe",
|
|
710
736
|
cwd: server.cwd ? resolveEnvPlaceholders(server.cwd) : undefined,
|
|
711
737
|
})
|
|
738
|
+
transport.stderr?.on("data", () => {})
|
|
739
|
+
transport.stderr?.on("error", () => {})
|
|
712
740
|
await withTimeout(client.connect(transport), timeout, `Connecting to ${serverName}`)
|
|
713
741
|
return { client, transport, serverName, lastUsed: Date.now() }
|
|
714
742
|
}
|
|
@@ -799,7 +827,7 @@ export default ((input: PluginInput) => {
|
|
|
799
827
|
}
|
|
800
828
|
|
|
801
829
|
async function refreshOnDemandMcpDescriptions(force = false, serverName?: string): Promise<{ refreshed: string[]; failed: string[] }> {
|
|
802
|
-
const entries =
|
|
830
|
+
const entries = activeOnDemandMcpEntries()
|
|
803
831
|
.filter(([name]) => !serverName || name.toLowerCase() === serverName.toLowerCase())
|
|
804
832
|
.filter(([name]) => {
|
|
805
833
|
const metadata = mcpMetadata[name]
|
|
@@ -869,7 +897,7 @@ export default ((input: PluginInput) => {
|
|
|
869
897
|
|
|
870
898
|
function onDemandMcpStatusBuckets(): { loaded: string[]; failed: string[]; pending: string[] } {
|
|
871
899
|
const buckets = { loaded: [] as string[], failed: [] as string[], pending: [] as string[] }
|
|
872
|
-
for (const name of
|
|
900
|
+
for (const [name] of activeOnDemandMcpEntries()) {
|
|
873
901
|
const status = onDemandMcpRuntimeStatus(name)
|
|
874
902
|
if (status === "ready") buckets.loaded.push(name)
|
|
875
903
|
else if (status === "failed") buckets.failed.push(name)
|
|
@@ -879,7 +907,7 @@ export default ((input: PluginInput) => {
|
|
|
879
907
|
}
|
|
880
908
|
|
|
881
909
|
function failedOnDemandMcpNames(names?: string[]): string[] {
|
|
882
|
-
const configured =
|
|
910
|
+
const configured = activeOnDemandMcpEntries().map(([name]) => name)
|
|
883
911
|
const wanted = names?.length ? names : configured
|
|
884
912
|
return wanted.filter((name) => configured.includes(name) && onDemandMcpRuntimeStatus(name) === "failed")
|
|
885
913
|
}
|
|
@@ -897,12 +925,14 @@ export default ((input: PluginInput) => {
|
|
|
897
925
|
else delete next.lastError
|
|
898
926
|
mcpMetadata[name] = next
|
|
899
927
|
saveMcpMetadata(mcpMetadata)
|
|
928
|
+
saveDynamicGuidance()
|
|
900
929
|
return name
|
|
901
930
|
}
|
|
902
931
|
|
|
903
932
|
function onDemandMcpStatus(): string {
|
|
904
|
-
const entries =
|
|
905
|
-
if (!
|
|
933
|
+
const entries = activeOnDemandMcpEntries()
|
|
934
|
+
if (!Object.keys(config.onDemandMcpServers ?? {}).length) return "Raven on-demand MCPs: none configured."
|
|
935
|
+
if (!entries.length) return "Raven on-demand MCPs: none enabled."
|
|
906
936
|
const buckets = onDemandMcpStatusBuckets()
|
|
907
937
|
const lines = entries.map(([name, server]) => {
|
|
908
938
|
const metadata = mcpMetadata[name]
|
|
@@ -918,7 +948,7 @@ export default ((input: PluginInput) => {
|
|
|
918
948
|
}
|
|
919
949
|
|
|
920
950
|
function avoidedMcpSchemaBytes(): number {
|
|
921
|
-
return
|
|
951
|
+
return activeOnDemandMcpEntries().reduce((sum, [name]) => {
|
|
922
952
|
const metadata = mcpMetadata[name]
|
|
923
953
|
if (onDemandMcpRuntimeStatus(name) !== "ready") return sum
|
|
924
954
|
return sum + Math.max(0, metadata?.avoidedSchemaBytes ?? 0)
|
|
@@ -994,7 +1024,9 @@ export default ((input: PluginInput) => {
|
|
|
994
1024
|
|
|
995
1025
|
function formatTokens(bytes: number): string {
|
|
996
1026
|
const tokens = Math.round(bytes / 4)
|
|
997
|
-
return tokens >=
|
|
1027
|
+
return tokens >= 1_000_000 ? `${(tokens / 1_000_000).toFixed(1)}M`
|
|
1028
|
+
: tokens >= 1000 ? `${(tokens / 1000).toFixed(1)}K`
|
|
1029
|
+
: `${tokens}`
|
|
998
1030
|
}
|
|
999
1031
|
|
|
1000
1032
|
function compareVersions(a: string, b: string): number {
|
|
@@ -1153,11 +1185,11 @@ export default ((input: PluginInput) => {
|
|
|
1153
1185
|
globalMcpServers = uniqueStrings(Object.entries(configInput.mcp ?? {})
|
|
1154
1186
|
.filter(([, value]) => !value || typeof value !== "object" || (value as any).enabled !== false)
|
|
1155
1187
|
.map(([name]) => name))
|
|
1156
|
-
const onDemandNames = new Set(
|
|
1188
|
+
const onDemandNames = new Set(activeOnDemandMcpEntries().map(([name]) => name.toLowerCase()))
|
|
1157
1189
|
duplicateMcpServers = globalMcpServers.filter((name) => onDemandNames.has(name.toLowerCase()))
|
|
1158
1190
|
|
|
1159
|
-
// Inject MCP guidance as a startup instruction file (absolute path for npm compat)
|
|
1160
|
-
|
|
1191
|
+
// Inject MCP guidance as a startup instruction file (absolute path for npm compat).
|
|
1192
|
+
// The file is regenerated after MCP health checks, not during the startup pending reset.
|
|
1161
1193
|
cleanupOldRootFiles()
|
|
1162
1194
|
configInput.instructions = configInput.instructions || []
|
|
1163
1195
|
if (!configInput.instructions.includes(MCP_GUIDANCE_MD)) {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-raven",
|
|
3
|
-
"version": "2.1.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "2.1.2",
|
|
4
|
+
"description": "Ready-to-use search tools and optional on-demand MCPs for OpenCode, routed through Raven to save your main model's context and cost.",
|
|
5
5
|
"main": "./index.ts",
|
|
6
6
|
"exports": {
|
|
7
7
|
".": "./index.ts"
|