@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 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, loaded on demand through OpenCode's `skill` tool.
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 `mcp.mrscraper`, the plugin keeps your
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
- - [OpenCode](https://opencode.ai/docs/) (verified with 1.18.34).
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 under
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 opens the MrScraper consent page and waits up to five minutes for the
55
- browser to return to `http://127.0.0.1:19876/mcp/oauth/callback`. It requests
56
- the `scrape:read`, `scrape:write`, and `account:read` scopes advertised by the
57
- server, plus `offline_access` for token refresh, and stores the tokens in
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. An API key carries your full account
99
- authority; OAuth scopes do not apply to it.
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
- OpenCode prefixes MCP tools with the server name:
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
- | `mrscraper_fetch` | Retrieve and preserve a known page's raw response. |
112
- | `mrscraper_scrape` | Run managed structured extraction or bounded site mapping. |
113
- | `mrscraper_serp` | Discover public pages through Google. |
114
- | `mrscraper_rerun` | Reuse a saved AI or manual scraper configuration. |
115
- | `mrscraper_results` | Browse and filter stored result records. |
116
- | `mrscraper_result` | Retrieve one stored result or poll an asynchronous run. |
117
- | `mrscraper_status` | Inspect subscription usage and request outcomes. |
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
- An unpinned entry stays on the version OpenCode first cached. To control
154
- updates, pin a version and change it when you upgrade:
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.1.0"]
207
+ "plugin": ["@mrscraper/opencode@0.2.0"]
159
208
  }
160
209
  ```
161
210
 
162
- To remove MrScraper, delete the entry from `plugin`, then remove the stored
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 registers the MCP server and the skills directory
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 by adding its absolute path to `plugin` in an OpenCode
194
- configuration, then run `opencode mcp list` and `opencode debug skill`. Record
195
- user-visible changes in [CHANGELOG.md](CHANGELOG.md) and follow
196
- [PUBLISHING.md](PUBLISHING.md) for releases.
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
- export const MrScraperPlugin: Plugin = async () => ({
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.mrscraper ??= { type: "remote", url: MCP_URL }
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
- export default MrScraperPlugin
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.1.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.18.34",
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
  },