@mrscraper/opencode 0.1.0 → 0.2.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/CHANGELOG.md +13 -0
- package/README.md +93 -41
- package/index.ts +73 -5
- package/package.json +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,19 @@
|
|
|
3
3
|
All notable changes to the MrScraper OpenCode plugin are documented here. The
|
|
4
4
|
project follows [Semantic Versioning](https://semver.org/).
|
|
5
5
|
|
|
6
|
+
## 0.2.0 - 2026-10-12
|
|
7
|
+
|
|
8
|
+
- Added OpenCode 2 support. OpenCode 2 does not run OpenCode 1 plugins; the
|
|
9
|
+
package now default-exports one object with an OpenCode 2 `setup()`, which
|
|
10
|
+
registers the `mrscraper` MCP server and the four skills through MCP and
|
|
11
|
+
skill transforms, and an OpenCode 1 `server()`, which keeps the existing
|
|
12
|
+
config hook.
|
|
13
|
+
- OpenCode 1 now needs 1.18.29 or later (the first release that loads the
|
|
14
|
+
object form). On older releases, pin `@mrscraper/opencode@0.1.0`.
|
|
15
|
+
- The `MrScraperPlugin` named export was removed; use the default export.
|
|
16
|
+
- Documented OpenCode 2 install, sign-in, API-key configuration
|
|
17
|
+
(`mcp.servers`), and update commands.
|
|
18
|
+
|
|
6
19
|
## 0.1.0 - 2026-10-05
|
|
7
20
|
|
|
8
21
|
- Added the initial `@mrscraper/opencode` plugin: registers the hosted
|
package/README.md
CHANGED
|
@@ -6,6 +6,8 @@ saved scraper reruns, stored results, and account usage. The plugin also
|
|
|
6
6
|
includes four focused skills that help OpenCode choose the right workflow and
|
|
7
7
|
preserve raw page content.
|
|
8
8
|
|
|
9
|
+
The same package works in OpenCode 2 and OpenCode 1.
|
|
10
|
+
|
|
9
11
|
## What this plugin adds
|
|
10
12
|
|
|
11
13
|
- An MCP server named `mrscraper` that points at the hosted Streamable HTTP
|
|
@@ -13,21 +15,42 @@ preserve raw page content.
|
|
|
13
15
|
- OAuth 2.1 sign-in handled by OpenCode; no credential is stored in this
|
|
14
16
|
package.
|
|
15
17
|
- The `mrscraper`, `mrscraper-fetch`, `mrscraper-scrape`, and `mrscraper-serp`
|
|
16
|
-
skills
|
|
18
|
+
skills.
|
|
17
19
|
- A fetch-first workflow that keeps raw page responses available for analysis,
|
|
18
20
|
verification, and follow-up transformations.
|
|
19
21
|
|
|
20
|
-
If your configuration already defines `
|
|
21
|
-
entry and only adds the skills.
|
|
22
|
+
If your configuration already defines a `mrscraper` MCP server, the plugin
|
|
23
|
+
keeps your entry and only adds the skills. It also leaves any skill with the
|
|
24
|
+
same name that you already have in place.
|
|
22
25
|
|
|
23
26
|
## Requirements
|
|
24
27
|
|
|
25
|
-
-
|
|
28
|
+
- OpenCode 2 (verified with 2.0.26), or OpenCode 1.18.29 or later (verified
|
|
29
|
+
with 1.18.29 and 1.19.0). On older OpenCode 1 releases, pin
|
|
30
|
+
`@mrscraper/opencode@0.1.0`.
|
|
26
31
|
- A [MrScraper](https://app.mrscraper.com) account.
|
|
27
32
|
- Permission to access and process the target content.
|
|
28
33
|
|
|
29
34
|
## Install
|
|
30
35
|
|
|
36
|
+
### OpenCode 2
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
opencode plugin add @mrscraper/opencode
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
This adds the package to `plugins` in your global configuration. To enable it
|
|
43
|
+
for one project instead, add it to that project's `opencode.jsonc`:
|
|
44
|
+
|
|
45
|
+
```jsonc
|
|
46
|
+
{
|
|
47
|
+
"$schema": "https://opencode.ai/config.json",
|
|
48
|
+
"plugins": ["@mrscraper/opencode"]
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### OpenCode 1
|
|
53
|
+
|
|
31
54
|
```bash
|
|
32
55
|
opencode plugin @mrscraper/opencode --global
|
|
33
56
|
```
|
|
@@ -42,8 +65,7 @@ Or add the package to the `plugin` list in `~/.config/opencode/opencode.json`
|
|
|
42
65
|
}
|
|
43
66
|
```
|
|
44
67
|
|
|
45
|
-
OpenCode installs npm plugins when it starts and caches them
|
|
46
|
-
`~/.cache/opencode/packages/`.
|
|
68
|
+
OpenCode installs npm plugins when it starts and caches them.
|
|
47
69
|
|
|
48
70
|
## Sign in
|
|
49
71
|
|
|
@@ -51,16 +73,10 @@ OpenCode installs npm plugins when it starts and caches them under
|
|
|
51
73
|
opencode mcp auth mrscraper
|
|
52
74
|
```
|
|
53
75
|
|
|
54
|
-
OpenCode
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
`~/.local/share/opencode/mcp-auth.json`. Never paste OAuth tokens or API keys
|
|
59
|
-
into chat.
|
|
60
|
-
|
|
61
|
-
On a remote machine, open the printed URL in a local browser after forwarding
|
|
62
|
-
the callback port, for example `ssh -L 19876:127.0.0.1:19876 <host>`, or use
|
|
63
|
-
an API key instead.
|
|
76
|
+
In OpenCode 2 you can also run `/mcps` in a session, select `mrscraper`, and
|
|
77
|
+
sign in there. OpenCode opens the MrScraper consent page in your browser,
|
|
78
|
+
registers its own OAuth client, and stores the tokens outside your project
|
|
79
|
+
configuration. Never paste OAuth tokens or API keys into chat.
|
|
64
80
|
|
|
65
81
|
Check the connection:
|
|
66
82
|
|
|
@@ -71,11 +87,37 @@ opencode mcp list
|
|
|
71
87
|
`mrscraper` shows `needs authentication` before sign-in and `connected`
|
|
72
88
|
afterwards.
|
|
73
89
|
|
|
90
|
+
On a machine without a local browser, use an API key instead (next section).
|
|
91
|
+
OpenCode 1 waits for the browser on `http://127.0.0.1:19876/mcp/oauth/callback`,
|
|
92
|
+
so you can also forward that port, for example
|
|
93
|
+
`ssh -L 19876:127.0.0.1:19876 <host>`.
|
|
94
|
+
|
|
74
95
|
## Use an API key instead of OAuth
|
|
75
96
|
|
|
76
97
|
Create a key at [app.mrscraper.com/api-tokens](https://app.mrscraper.com/api-tokens),
|
|
77
98
|
export it as `MRSCRAPER_API_KEY` in the environment that starts OpenCode, and
|
|
78
|
-
define the server yourself
|
|
99
|
+
define the server yourself.
|
|
100
|
+
|
|
101
|
+
OpenCode 2 (`opencode.jsonc`):
|
|
102
|
+
|
|
103
|
+
```jsonc
|
|
104
|
+
{
|
|
105
|
+
"$schema": "https://opencode.ai/config.json",
|
|
106
|
+
"plugins": ["@mrscraper/opencode"],
|
|
107
|
+
"mcp": {
|
|
108
|
+
"servers": {
|
|
109
|
+
"mrscraper": {
|
|
110
|
+
"type": "remote",
|
|
111
|
+
"url": "https://mcp.mrscraper.com/mcp",
|
|
112
|
+
"oauth": false,
|
|
113
|
+
"headers": { "Authorization": "Bearer {env:MRSCRAPER_API_KEY}" }
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
OpenCode 1 (`opencode.json`):
|
|
79
121
|
|
|
80
122
|
```json
|
|
81
123
|
{
|
|
@@ -95,8 +137,10 @@ define the server yourself:
|
|
|
95
137
|
```
|
|
96
138
|
|
|
97
139
|
OpenCode substitutes `{env:MRSCRAPER_API_KEY}` when it loads the configuration,
|
|
98
|
-
so the key stays out of the file.
|
|
99
|
-
|
|
140
|
+
so the key stays out of the file. In OpenCode 2 the substitution happens in the
|
|
141
|
+
background service, so the variable must be set where the service starts; after
|
|
142
|
+
setting it, run `opencode service restart` from that shell. An API key carries
|
|
143
|
+
your full account authority; OAuth scopes do not apply to it.
|
|
100
144
|
|
|
101
145
|
## Try it
|
|
102
146
|
|
|
@@ -104,21 +148,18 @@ authority; OAuth scopes do not apply to it.
|
|
|
104
148
|
Fetch https://www.scrapethissite.com/pages/simple/ and summarize the page.
|
|
105
149
|
```
|
|
106
150
|
|
|
107
|
-
|
|
151
|
+
The server provides these tools. OpenCode 1 shows them as `mrscraper_<tool>`;
|
|
152
|
+
OpenCode 2 reaches MCP tools through Code Mode by default.
|
|
108
153
|
|
|
109
154
|
| Tool | Purpose |
|
|
110
155
|
| --- | --- |
|
|
111
|
-
| `
|
|
112
|
-
| `
|
|
113
|
-
| `
|
|
114
|
-
| `
|
|
115
|
-
| `
|
|
116
|
-
| `
|
|
117
|
-
| `
|
|
118
|
-
|
|
119
|
-
To keep the tools out of every session, add `"tools": { "mrscraper_*": false }`
|
|
120
|
-
to your configuration and enable them per agent; see
|
|
121
|
-
[MCP servers](https://opencode.ai/docs/mcp-servers/#manage).
|
|
156
|
+
| `fetch` | Retrieve and preserve a known page's raw response. |
|
|
157
|
+
| `scrape` | Run managed structured extraction or bounded site mapping. |
|
|
158
|
+
| `serp` | Discover public pages through Google. |
|
|
159
|
+
| `rerun` | Reuse a saved AI or manual scraper configuration. |
|
|
160
|
+
| `results` | Browse and filter stored result records. |
|
|
161
|
+
| `result` | Retrieve one stored result or poll an asynchronous run. |
|
|
162
|
+
| `status` | Inspect subscription usage and request outcomes. |
|
|
122
163
|
|
|
123
164
|
## Fetch-first routing
|
|
124
165
|
|
|
@@ -150,17 +191,25 @@ Review MrScraper's [MCP documentation](https://docs.mrscraper.com/docs/getting-s
|
|
|
150
191
|
|
|
151
192
|
## Update or remove
|
|
152
193
|
|
|
153
|
-
|
|
154
|
-
|
|
194
|
+
OpenCode 2 checks unpinned npm plugins for updates without installing them.
|
|
195
|
+
Update or remove the plugin with:
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
opencode plugin update @mrscraper/opencode
|
|
199
|
+
opencode plugin remove @mrscraper/opencode
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
In OpenCode 1, an unpinned entry stays on the version OpenCode first cached.
|
|
203
|
+
To control updates, pin a version and change it when you upgrade:
|
|
155
204
|
|
|
156
205
|
```json
|
|
157
206
|
{
|
|
158
|
-
"plugin": ["@mrscraper/opencode@0.
|
|
207
|
+
"plugin": ["@mrscraper/opencode@0.2.0"]
|
|
159
208
|
}
|
|
160
209
|
```
|
|
161
210
|
|
|
162
|
-
To remove
|
|
163
|
-
OAuth tokens:
|
|
211
|
+
To remove it from OpenCode 1, delete the entry from `plugin`. In either
|
|
212
|
+
version, remove the stored OAuth tokens afterwards:
|
|
164
213
|
|
|
165
214
|
```bash
|
|
166
215
|
opencode mcp logout mrscraper
|
|
@@ -179,7 +228,8 @@ account.
|
|
|
179
228
|
## Development
|
|
180
229
|
|
|
181
230
|
```text
|
|
182
|
-
index.ts
|
|
231
|
+
index.ts default export with an OpenCode 2 `setup()` and an OpenCode 1
|
|
232
|
+
`server()`; both register the MCP server and the skills
|
|
183
233
|
skills/ MCP-oriented skills, copied verbatim from
|
|
184
234
|
mrscraper-com/mrscraper-claude-plugin (plugins/mrscraper/skills)
|
|
185
235
|
```
|
|
@@ -190,10 +240,12 @@ npm run check
|
|
|
190
240
|
npm pack --dry-run
|
|
191
241
|
```
|
|
192
242
|
|
|
193
|
-
Test a local checkout
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
243
|
+
Test a local checkout in both majors before a release. In OpenCode 2, add the
|
|
244
|
+
checkout's absolute path to `plugins`, then run `opencode mcp list` and
|
|
245
|
+
`opencode api skill.list`. In OpenCode 1, add it to `plugin`, then run
|
|
246
|
+
`opencode mcp list` and `opencode debug skill`. Record user-visible changes in
|
|
247
|
+
[CHANGELOG.md](CHANGELOG.md) and follow [PUBLISHING.md](PUBLISHING.md) for
|
|
248
|
+
releases.
|
|
197
249
|
|
|
198
250
|
## License
|
|
199
251
|
|
package/index.ts
CHANGED
|
@@ -1,19 +1,23 @@
|
|
|
1
|
-
import type { Plugin } from "@opencode-ai/plugin"
|
|
1
|
+
import type { Plugin as OpenCode1Plugin } from "@opencode-ai/plugin"
|
|
2
|
+
import type { Plugin as OpenCode2, Skill } from "@opencode/plugin"
|
|
3
|
+
import { readdir, readFile } from "node:fs/promises"
|
|
2
4
|
import { dirname, join } from "node:path"
|
|
3
5
|
import { fileURLToPath } from "node:url"
|
|
4
6
|
|
|
7
|
+
const SERVER_NAME = "mrscraper"
|
|
5
8
|
const MCP_URL = "https://mcp.mrscraper.com/mcp"
|
|
6
9
|
const SKILLS_DIR = join(dirname(fileURLToPath(import.meta.url)), "skills")
|
|
7
10
|
|
|
8
|
-
// OpenCode reads `skills.paths` (see https://opencode.ai/config.json), but the
|
|
11
|
+
// OpenCode 1 reads `skills.paths` (see https://opencode.ai/config.json), but the
|
|
9
12
|
// plugin SDK's Config type does not declare it yet.
|
|
10
13
|
type SkillsConfig = { skills?: { paths?: string[]; urls?: string[] } }
|
|
11
14
|
|
|
12
|
-
|
|
15
|
+
// OpenCode 1: add the server and the skills directory through the config hook.
|
|
16
|
+
const server: OpenCode1Plugin = async () => ({
|
|
13
17
|
async config(input) {
|
|
14
18
|
input.mcp ??= {}
|
|
15
19
|
// Keep a user-defined entry, such as the API-key fallback or `enabled: false`.
|
|
16
|
-
input.mcp
|
|
20
|
+
input.mcp[SERVER_NAME] ??= { type: "remote", url: MCP_URL }
|
|
17
21
|
|
|
18
22
|
const config = input as typeof input & SkillsConfig
|
|
19
23
|
config.skills ??= {}
|
|
@@ -22,4 +26,68 @@ export const MrScraperPlugin: Plugin = async () => ({
|
|
|
22
26
|
},
|
|
23
27
|
})
|
|
24
28
|
|
|
25
|
-
|
|
29
|
+
// OpenCode 2: register the same server and skills through domain transforms.
|
|
30
|
+
const definition = {
|
|
31
|
+
id: "mrscraper",
|
|
32
|
+
async setup(ctx) {
|
|
33
|
+
const skills = await readSkills()
|
|
34
|
+
await ctx.mcp.transform((editor) => {
|
|
35
|
+
// Keep a user-defined `mcp.servers.mrscraper`, such as the API-key fallback.
|
|
36
|
+
if (!editor.get(SERVER_NAME)) editor.set(SERVER_NAME, { type: "remote", url: MCP_URL })
|
|
37
|
+
})
|
|
38
|
+
await ctx.skill.transform((editor) => {
|
|
39
|
+
for (const skill of skills) if (!editor.get(skill.id)) editor.add(skill)
|
|
40
|
+
})
|
|
41
|
+
},
|
|
42
|
+
} satisfies OpenCode2.Plugin
|
|
43
|
+
|
|
44
|
+
// Mirrors OpenCode 2's own SKILL.md loading: the folder name is the ID, the
|
|
45
|
+
// frontmatter supplies `name` and `description`, and the body is the content.
|
|
46
|
+
async function readSkills(): Promise<Skill.Info[]> {
|
|
47
|
+
const skills: Skill.Info[] = []
|
|
48
|
+
const folders = await readdir(SKILLS_DIR, { withFileTypes: true }).catch(() => [])
|
|
49
|
+
for (const folder of folders) {
|
|
50
|
+
if (!folder.isDirectory()) continue
|
|
51
|
+
const path = join(SKILLS_DIR, folder.name, "SKILL.md")
|
|
52
|
+
const text = await readFile(path, "utf8").catch(() => undefined)
|
|
53
|
+
const markdown = text === undefined ? undefined : parseMarkdown(text)
|
|
54
|
+
if (!markdown) continue
|
|
55
|
+
const { name, description } = markdown.frontmatter
|
|
56
|
+
skills.push({
|
|
57
|
+
id: folder.name as Skill.Info["id"],
|
|
58
|
+
name: (name || folder.name) as Skill.Info["name"],
|
|
59
|
+
...(description ? { description } : {}),
|
|
60
|
+
path: path as Skill.Info["path"],
|
|
61
|
+
content: markdown.content,
|
|
62
|
+
})
|
|
63
|
+
}
|
|
64
|
+
return skills
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Reads the flat frontmatter these skills use: plain or quoted scalars and
|
|
68
|
+
// `|` or `>` block scalars. Nested keys are ignored.
|
|
69
|
+
function parseMarkdown(text: string) {
|
|
70
|
+
const match = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)([\s\S]*)$/.exec(text)
|
|
71
|
+
if (!match) return undefined
|
|
72
|
+
const frontmatter: Record<string, string> = {}
|
|
73
|
+
const lines = match[1].split(/\r?\n/)
|
|
74
|
+
for (let i = 0; i < lines.length; i++) {
|
|
75
|
+
const field = /^([A-Za-z0-9_-]+):\s*(.*)$/.exec(lines[i])
|
|
76
|
+
if (!field) continue
|
|
77
|
+
const [, key, value] = field
|
|
78
|
+
if (!/^[|>][-+]?$/.test(value)) {
|
|
79
|
+
frontmatter[key] = value.replace(/^(["'])(.*)\1$/, "$2").trim()
|
|
80
|
+
continue
|
|
81
|
+
}
|
|
82
|
+
const block: string[] = []
|
|
83
|
+
while (i + 1 < lines.length && (lines[i + 1].trim() === "" || /^\s/.test(lines[i + 1]))) block.push(lines[++i])
|
|
84
|
+
const indent = Math.min(...block.filter((line) => line.trim()).map((line) => line.length - line.trimStart().length))
|
|
85
|
+
const literal = block.map((line) => line.slice(indent)).join("\n")
|
|
86
|
+
frontmatter[key] = (value.startsWith(">") ? literal.replace(/([^\n])\n(?=[^\n])/g, "$1 ") : literal).trim()
|
|
87
|
+
}
|
|
88
|
+
return { frontmatter, content: match[2] }
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// One default export serves both majors: OpenCode 1 (1.18.29 or later) calls
|
|
92
|
+
// `server()`, and OpenCode 2 reads `id` and `setup()` and ignores `server()`.
|
|
93
|
+
export default { ...definition, server }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mrscraper/opencode",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "MrScraper for OpenCode: the hosted MrScraper MCP server plus fetch-first skills for web scraping, structured data extraction, and Google search.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.ts",
|
|
@@ -42,7 +42,8 @@
|
|
|
42
42
|
"check": "tsc --noEmit"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
|
-
"@opencode-ai/plugin": "1.
|
|
45
|
+
"@opencode-ai/plugin": "1.19.0",
|
|
46
|
+
"@opencode/plugin": "2.0.26",
|
|
46
47
|
"@types/node": "^22.20.5",
|
|
47
48
|
"typescript": "^5.9.3"
|
|
48
49
|
},
|