@kagan-sh/opensearch 0.2.0 → 0.3.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 +37 -1
- package/SKILL.md +2 -2
- package/dist/config.d.ts +1 -0
- package/dist/config.js +14 -0
- package/dist/mcp.d.ts +2 -0
- package/dist/mcp.js +99 -0
- package/dist/orchestrator.d.ts +1 -1
- package/dist/orchestrator.js +4 -0
- package/package.json +11 -1
package/README.md
CHANGED
|
@@ -22,10 +22,46 @@
|
|
|
22
22
|
|
|
23
23
|
---
|
|
24
24
|
|
|
25
|
-
`@kagan-sh/opensearch` is an
|
|
25
|
+
`@kagan-sh/opensearch` is an evidence-backed search tool for AI coding agents. It searches the live web and public code in parallel, then returns structured JSON your agent can act on.
|
|
26
|
+
|
|
27
|
+
Works as an **OpenCode plugin** and as a **Claude Code MCP server**.
|
|
26
28
|
|
|
27
29
|
## Install
|
|
28
30
|
|
|
31
|
+
### Claude Code (MCP)
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
claude mcp add opensearch -- npx -y @kagan-sh/opensearch
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
To enable web search via SearXNG:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
claude mcp add opensearch \
|
|
41
|
+
-e OPENSEARCH_WEB_URL=http://localhost:8080 \
|
|
42
|
+
-- npx -y @kagan-sh/opensearch
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Or add it to `.mcp.json` in your project root:
|
|
46
|
+
|
|
47
|
+
```json
|
|
48
|
+
{
|
|
49
|
+
"mcpServers": {
|
|
50
|
+
"opensearch": {
|
|
51
|
+
"command": "npx",
|
|
52
|
+
"args": ["-y", "@kagan-sh/opensearch"],
|
|
53
|
+
"env": {
|
|
54
|
+
"OPENSEARCH_WEB_URL": "http://localhost:8080"
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
See the [Claude Code install guide](https://kagan-sh.github.io/opensearch/guides/claude-code/) for full details.
|
|
62
|
+
|
|
63
|
+
### OpenCode (plugin)
|
|
64
|
+
|
|
29
65
|
Add the plugin to `opencode.json`:
|
|
30
66
|
|
|
31
67
|
```json
|
package/SKILL.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: opensearch
|
|
3
3
|
description: Use OpenSearch for broad, evidence-backed investigation across session history, the web, and public code when a user asks to search, research, compare sources, or gather official docs and examples.
|
|
4
4
|
license: MIT
|
|
5
|
-
compatibility:
|
|
5
|
+
compatibility: Works as an OpenCode plugin or a Claude Code MCP server. The web source requires a SearXNG instance and OPENSEARCH_WEB_URL. In Claude Code, only web and code sources are available.
|
|
6
6
|
metadata:
|
|
7
7
|
version: 0.0.1
|
|
8
8
|
---
|
|
@@ -42,7 +42,7 @@ Use this skill when the task is primarily about finding, comparing, and synthesi
|
|
|
42
42
|
|
|
43
43
|
## Source Selection Guide
|
|
44
44
|
|
|
45
|
-
- `session` for prior decisions, earlier runs, and local conversation history
|
|
45
|
+
- `session` for prior decisions, earlier runs, and local conversation history (OpenCode only)
|
|
46
46
|
- `web` for official docs, changelogs, and current vendor guidance
|
|
47
47
|
- `code` for public implementation examples and real usage patterns
|
|
48
48
|
|
package/dist/config.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Config, SourceId } from "./schema";
|
|
2
2
|
export declare function defaultConfig(): Config;
|
|
3
|
+
export declare function mcpDefaultConfig(): Config;
|
|
3
4
|
export declare function parsePluginConfig(input: unknown): Config | undefined;
|
|
4
5
|
export declare function isSourceAvailable(config: Config, source: SourceId): boolean;
|
|
5
6
|
export declare function resolveSources(config: Config, requested?: SourceId[]): {
|
package/dist/config.js
CHANGED
|
@@ -34,6 +34,20 @@ export function defaultConfig() {
|
|
|
34
34
|
synth: parseBoolean("OPENSEARCH_SYNTH", process.env.OPENSEARCH_SYNTH, true),
|
|
35
35
|
};
|
|
36
36
|
}
|
|
37
|
+
export function mcpDefaultConfig() {
|
|
38
|
+
return {
|
|
39
|
+
sources: {
|
|
40
|
+
session: false,
|
|
41
|
+
web: {
|
|
42
|
+
enabled: parseBoolean("OPENSEARCH_SOURCE_WEB", process.env.OPENSEARCH_SOURCE_WEB, true),
|
|
43
|
+
url: process.env.OPENSEARCH_WEB_URL,
|
|
44
|
+
},
|
|
45
|
+
code: parseBoolean("OPENSEARCH_SOURCE_CODE", process.env.OPENSEARCH_SOURCE_CODE, true),
|
|
46
|
+
},
|
|
47
|
+
depth: parseDepth(process.env.OPENSEARCH_DEPTH),
|
|
48
|
+
synth: false,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
37
51
|
export function parsePluginConfig(input) {
|
|
38
52
|
if (!input || typeof input !== "object" || !("opensearch" in input)) {
|
|
39
53
|
return undefined;
|
package/dist/mcp.d.ts
ADDED
package/dist/mcp.js
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
5
|
+
import { mcpDefaultConfig, resolveSources } from "./config";
|
|
6
|
+
import { noResultsResult, noSourcesResult, rawResultsResult, runSourceSearches, } from "./orchestrator";
|
|
7
|
+
import { SOURCE_IDS } from "./schema";
|
|
8
|
+
function serialize(value) {
|
|
9
|
+
return JSON.stringify(value, null, 2);
|
|
10
|
+
}
|
|
11
|
+
function parseSourceIds(raw) {
|
|
12
|
+
if (!Array.isArray(raw))
|
|
13
|
+
return undefined;
|
|
14
|
+
return raw.filter((s) => typeof s === "string" && SOURCE_IDS.includes(s));
|
|
15
|
+
}
|
|
16
|
+
const config = mcpDefaultConfig();
|
|
17
|
+
const server = new Server({ name: "opensearch", version: "0.0.1" }, { capabilities: { tools: {} } });
|
|
18
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
19
|
+
tools: [
|
|
20
|
+
{
|
|
21
|
+
name: "opensearch",
|
|
22
|
+
description: "Evidence-backed search across SearXNG web and public code. Returns structured JSON with sources, relevance scores, and follow-up suggestions.",
|
|
23
|
+
inputSchema: {
|
|
24
|
+
type: "object",
|
|
25
|
+
properties: {
|
|
26
|
+
query: {
|
|
27
|
+
type: "string",
|
|
28
|
+
description: "What to search for",
|
|
29
|
+
},
|
|
30
|
+
sources: {
|
|
31
|
+
type: "array",
|
|
32
|
+
items: { type: "string", enum: ["web", "code"] },
|
|
33
|
+
description: "Sources to query. Defaults to all enabled.",
|
|
34
|
+
},
|
|
35
|
+
depth: {
|
|
36
|
+
type: "string",
|
|
37
|
+
enum: ["quick", "thorough"],
|
|
38
|
+
description: "Search depth. Default: quick",
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
required: ["query"],
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
],
|
|
45
|
+
}));
|
|
46
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
47
|
+
if (request.params.name !== "opensearch") {
|
|
48
|
+
return {
|
|
49
|
+
isError: true,
|
|
50
|
+
content: [{ type: "text", text: `Unknown tool: ${request.params.name}` }],
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
const args = request.params.arguments ?? {};
|
|
54
|
+
const query = typeof args.query === "string" ? args.query : "";
|
|
55
|
+
const depth = args.depth === "thorough" ? "thorough" : config.depth;
|
|
56
|
+
const start = Date.now();
|
|
57
|
+
const requested = parseSourceIds(args.sources);
|
|
58
|
+
const resolved = resolveSources(config, requested);
|
|
59
|
+
if (resolved.sources.length === 0) {
|
|
60
|
+
const result = noSourcesResult({
|
|
61
|
+
query,
|
|
62
|
+
start,
|
|
63
|
+
requested: resolved.requested,
|
|
64
|
+
unavailable: resolved.unavailable,
|
|
65
|
+
});
|
|
66
|
+
return { content: [{ type: "text", text: serialize(result) }] };
|
|
67
|
+
}
|
|
68
|
+
const search = await runSourceSearches({
|
|
69
|
+
directory: process.cwd(),
|
|
70
|
+
config,
|
|
71
|
+
query,
|
|
72
|
+
depth,
|
|
73
|
+
sources: resolved.sources,
|
|
74
|
+
});
|
|
75
|
+
if (search.raw.length === 0) {
|
|
76
|
+
const result = noResultsResult({
|
|
77
|
+
query,
|
|
78
|
+
start,
|
|
79
|
+
requested: resolved.requested,
|
|
80
|
+
queried: resolved.sources,
|
|
81
|
+
unavailable: resolved.unavailable,
|
|
82
|
+
sourceErrors: search.sourceErrors,
|
|
83
|
+
});
|
|
84
|
+
return { content: [{ type: "text", text: serialize(result) }] };
|
|
85
|
+
}
|
|
86
|
+
const result = rawResultsResult({
|
|
87
|
+
query,
|
|
88
|
+
start,
|
|
89
|
+
requested: resolved.requested,
|
|
90
|
+
queried: resolved.sources,
|
|
91
|
+
unavailable: resolved.unavailable,
|
|
92
|
+
sourceErrors: search.sourceErrors,
|
|
93
|
+
raw: search.raw,
|
|
94
|
+
status: "raw",
|
|
95
|
+
});
|
|
96
|
+
return { content: [{ type: "text", text: serialize(result) }] };
|
|
97
|
+
});
|
|
98
|
+
const transport = new StdioServerTransport();
|
|
99
|
+
await server.connect(transport);
|
package/dist/orchestrator.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { createOpencodeClient } from "@opencode-ai/sdk";
|
|
|
2
2
|
import type { Config, RawResult, Result, Source, SourceError, SourceId, Synthesis } from "./schema";
|
|
3
3
|
export declare function normalize(raw: RawResult): Source;
|
|
4
4
|
export declare function runSourceSearches(input: {
|
|
5
|
-
client
|
|
5
|
+
client?: ReturnType<typeof createOpencodeClient>;
|
|
6
6
|
directory: string;
|
|
7
7
|
config: Config;
|
|
8
8
|
query: string;
|
package/dist/orchestrator.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { searchCode } from "./sources/code";
|
|
2
2
|
import { searchSessions } from "./sources/session";
|
|
3
|
+
import { failure } from "./sources/shared";
|
|
3
4
|
import { searchWeb } from "./sources/web";
|
|
4
5
|
function clampUnit(value) {
|
|
5
6
|
return Math.max(0, Math.min(1, value));
|
|
@@ -40,6 +41,9 @@ function createResult(input) {
|
|
|
40
41
|
export async function runSourceSearches(input) {
|
|
41
42
|
const outcomes = await Promise.all(input.sources.map((source) => {
|
|
42
43
|
if (source === "session") {
|
|
44
|
+
if (!input.client) {
|
|
45
|
+
return failure("session", "unavailable", "Session search requires the OpenCode runtime.");
|
|
46
|
+
}
|
|
43
47
|
return searchSessions(input.client, input.directory, input.query, input.depth);
|
|
44
48
|
}
|
|
45
49
|
if (source === "web") {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kagan-sh/opensearch",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "OpenCode plugin that combines session, web, and code search",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://kagan-sh.github.io/opensearch/",
|
|
@@ -14,11 +14,19 @@
|
|
|
14
14
|
"type": "module",
|
|
15
15
|
"main": "dist/index.js",
|
|
16
16
|
"types": "dist/index.d.ts",
|
|
17
|
+
"bin": {
|
|
18
|
+
"opensearch-mcp": "./dist/mcp.js"
|
|
19
|
+
},
|
|
17
20
|
"exports": {
|
|
18
21
|
".": {
|
|
19
22
|
"types": "./dist/index.d.ts",
|
|
20
23
|
"import": "./dist/index.js",
|
|
21
24
|
"default": "./dist/index.js"
|
|
25
|
+
},
|
|
26
|
+
"./mcp": {
|
|
27
|
+
"types": "./dist/mcp.d.ts",
|
|
28
|
+
"import": "./dist/mcp.js",
|
|
29
|
+
"default": "./dist/mcp.js"
|
|
22
30
|
}
|
|
23
31
|
},
|
|
24
32
|
"files": [
|
|
@@ -29,6 +37,7 @@
|
|
|
29
37
|
"LICENSE"
|
|
30
38
|
],
|
|
31
39
|
"scripts": {
|
|
40
|
+
"mcp": "bun run src/mcp.ts",
|
|
32
41
|
"build": "bun tsc",
|
|
33
42
|
"typecheck": "bun tsc --noEmit",
|
|
34
43
|
"test": "vitest run",
|
|
@@ -40,6 +49,7 @@
|
|
|
40
49
|
"release:dry-run": "semantic-release --dry-run"
|
|
41
50
|
},
|
|
42
51
|
"dependencies": {
|
|
52
|
+
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
43
53
|
"@opencode-ai/plugin": "1.2.27",
|
|
44
54
|
"@opencode-ai/sdk": "1.2.27",
|
|
45
55
|
"zod": "^3.24.0",
|