smart-web-mcp 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 rich-jojo
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,191 @@
1
+ # smart-web
2
+
3
+ `smart-web` is a portable MCP server that exposes two tools:
4
+
5
+ - `smartsearch`: fallback web search with API-key tiers and no-key fallbacks
6
+ - `smartfetch`: browser-aware page retrieval with escalation to Playwright when needed
7
+
8
+ It is built for Claude Code, OpenCode, and any other MCP client that can run a local stdio server.
9
+
10
+ ## Why this exists
11
+
12
+ Most AI coding setups still treat search and fetch as separate, inconsistent integrations.
13
+
14
+ - Good search APIs are often paid
15
+ - no-key fallbacks are usually weak or privacy-unclear
16
+ - page fetchers often fail on JS-heavy or bot-protected sites
17
+ - tool setups are usually client-specific instead of portable
18
+
19
+ `smart-web` packages a practical default stack into one MCP server with two focused tools.
20
+
21
+ ## What you get
22
+
23
+ - One MCP install, two tools
24
+ - Official MCP SDK over stdio
25
+ - Structured outputs plus readable text/markdown rendering
26
+ - Search fallback chain: Exa -> Brave -> SearXNG -> DuckDuckGo HTML
27
+ - Fetch fallback chain: impit -> stealth Playwright
28
+ - Target-aware normalization for Reddit, X, and DCInside
29
+ - Default blocking for localhost, private IP ranges, and reserved address space
30
+ - Shared setup examples for Claude Code and OpenCode
31
+ - `server.json` metadata for MCP Registry alignment
32
+
33
+ ## Why MCP is the right abstraction
34
+
35
+ For your goal, yes: MCP is the best primary interface.
36
+
37
+ - Claude Code already treats MCP as the standard way to add real tools
38
+ - OpenCode can consume local MCP servers too, so one implementation works across both
39
+ - plugins/marketplaces are packaging layers; MCP is the actual interoperability layer
40
+ - if you later want Claude marketplace distribution, the plugin should wrap this MCP server, not replace it
41
+
42
+ Short version:
43
+
44
+ - `MCP` = the core product
45
+ - `Claude plugin` = a distribution wrapper for Claude users
46
+ - `Marketplace` = discovery/install channel
47
+
48
+ ## Install
49
+
50
+ Canonical packaging is npm-first because MCP Registry publication and the lowest-friction Claude/OpenCode install path both expect a normal npm package.
51
+
52
+ ```bash
53
+ npm install
54
+ npm run build
55
+ ```
56
+
57
+ Run locally:
58
+
59
+ ```bash
60
+ node dist/index.js
61
+ ```
62
+
63
+ Once the npm package is published, the intended install path is:
64
+
65
+ ```bash
66
+ npx -y smart-web-mcp
67
+ ```
68
+
69
+ ## Quick start
70
+
71
+ Claude Code:
72
+
73
+ ```bash
74
+ claude mcp add smart-web -- node /absolute/path/to/smart-web/dist/index.js
75
+ ```
76
+
77
+ OpenCode:
78
+
79
+ ```json
80
+ {
81
+ "$schema": "https://opencode.ai/config.json",
82
+ "mcp": {
83
+ "smart-web": {
84
+ "type": "local",
85
+ "command": ["node", "/absolute/path/to/smart-web/dist/index.js"],
86
+ "enabled": true
87
+ }
88
+ }
89
+ }
90
+ ```
91
+
92
+ More complete examples live in `examples/claude.mcp.json` and `examples/opencode.json`.
93
+
94
+ ## Tool behavior
95
+
96
+ ### `smartsearch`
97
+
98
+ - uses `EXA_API_KEY` when available
99
+ - falls back to `BRAVE_SEARCH_API_KEY` when available
100
+ - falls back to self-hosted `SEARXNG_BASE_URL` when available
101
+ - falls back to DuckDuckGo HTML when no better option succeeds
102
+
103
+ ### `smartfetch`
104
+
105
+ - starts with `impit` for lightweight fetch realism
106
+ - escalates to stealth Playwright when content looks blocked or JS-rendered
107
+ - returns normalized output for Reddit, X, and DCInside
108
+ - can render `json`, `text`, or `markdown`
109
+ - blocks localhost/private/reserved network targets unless explicitly overridden
110
+
111
+ ## Configuration
112
+
113
+ Environment variables:
114
+
115
+ - `EXA_API_KEY`: optional first-tier search provider
116
+ - `BRAVE_SEARCH_API_KEY`: optional second-tier search provider
117
+ - `SEARXNG_BASE_URL`: optional self-hosted private fallback before public no-key search
118
+ - `SMARTFETCH_CHROME_CHANNEL`: optional Playwright browser channel override
119
+ - `SMARTFETCH_CHROME_PATH`: optional Playwright executable path override
120
+ - `SMARTFETCH_ENABLE_FXTWITTER`: opt in to FxTwitter relay enrichment for X posts
121
+ - `SMARTFETCH_ENABLE_X_OEMBED`: enable or disable X oEmbed fallback
122
+ - `SMARTFETCH_ENABLE_REDDIT_JSON`: enable or disable Reddit JSON enrichment
123
+ - `SMART_WEB_ALLOW_PRIVATE_HOSTS`: opt in to fetching localhost/private network targets
124
+ - `SMART_WEB_LOCAL_ONLY`: disable public no-key or relay-style fallbacks and keep traffic limited to explicitly chosen providers and direct targets
125
+
126
+ Provider toggles:
127
+
128
+ - `SMARTSEARCH_ENABLE_EXA`
129
+ - `SMARTSEARCH_ENABLE_BRAVE`
130
+ - `SMARTSEARCH_ENABLE_SEARXNG`
131
+ - `SMARTSEARCH_ENABLE_DUCKDUCKGO`
132
+
133
+ For local development, copy `.env.example` and export the values in your shell before starting your MCP client.
134
+
135
+ ## Security and privacy
136
+
137
+ - For stdio MCP servers, secrets should be injected through environment variables, not committed config
138
+ - Claude Code supports env expansion in `.mcp.json` with `${VAR}` syntax
139
+ - OpenCode supports env expansion in `opencode.json` with `{env:VAR}` syntax
140
+ - `smartsearch` sends only the query to the active backend
141
+ - `smartfetch` sends the target URL to the destination site and, for some enrichments, may call additional public endpoints like Reddit JSON or X oEmbed
142
+ - FxTwitter enrichment is opt-in via `SMARTFETCH_ENABLE_FXTWITTER`
143
+ - provider enable/disable env vars let you turn individual backends on or off without code changes
144
+ - Playwright may use browser-managed temporary state during execution; this project does not intentionally persist its own fetch history or secrets
145
+ - localhost/private/reserved targets are blocked by default to reduce agent-side SSRF risk
146
+
147
+ If query privacy matters, prefer self-hosted `SearXNG` and avoid public no-key fallbacks for sensitive searches.
148
+
149
+ ## Claude Code plugin and marketplace
150
+
151
+ This repo now includes an optional Claude plugin scaffold in `plugins/claude-smart-web` and a marketplace manifest in `.claude-plugin/marketplace.json`.
152
+
153
+ Recommendation:
154
+
155
+ - use raw MCP config first for fastest adoption
156
+ - add the plugin/marketplace path once the npm package is published
157
+
158
+ That gives you the right layering:
159
+
160
+ - MCP for cross-client compatibility
161
+ - plugin for Claude-native install UX
162
+ - marketplace for discovery and team rollout
163
+
164
+ ## Publish path
165
+
166
+ This project is already structured for:
167
+
168
+ 1. npm publish of `smart-web-mcp`
169
+ 2. MCP Registry publish using `server.json`
170
+ 3. Claude marketplace/plugin distribution using the included plugin scaffold
171
+
172
+ ## Development
173
+
174
+ ```bash
175
+ npm run check
176
+ ```
177
+
178
+ CI runs the same checks on GitHub Actions, including metadata consistency and `npm pack --dry-run`.
179
+
180
+ ## Design notes
181
+
182
+ - small shared library first, client wrappers second
183
+ - one server with two tools is the best default UX
184
+ - split packages remain possible later if demand appears
185
+
186
+ ## Roadmap
187
+
188
+ - publish `smart-web-mcp` to npm
189
+ - publish `smart-web` to the MCP Registry
190
+ - improve site-specific normalizers and ranking heuristics
191
+ - add more client wrappers only where they improve installation, not core behavior
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,62 @@
1
+ #!/usr/bin/env node
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { z } from "zod";
5
+ import { runSmartfetch, renderSmartfetch } from "./smartfetch.js";
6
+ import { runSmartsearch, renderSmartsearch } from "./smartsearch.js";
7
+ const server = new McpServer({
8
+ name: "smart-web",
9
+ version: "0.1.0",
10
+ });
11
+ server.tool("smartsearch", "Search the web with fallback engines. Uses Exa if EXA_API_KEY is set, then Brave if BRAVE_SEARCH_API_KEY is set, then DuckDuckGo HTML as a no-key fallback.", {
12
+ query: z.string().min(1).describe("Web search query"),
13
+ numResults: z.number().int().min(1).max(20).optional().describe("Number of search results to return"),
14
+ type: z.enum(["auto", "fast", "deep"]).optional().describe("Search depth hint"),
15
+ contextMaxCharacters: z.number().int().min(1000).max(20000).optional().describe("Maximum snippet/context size hint"),
16
+ }, async ({ query, numResults, type, contextMaxCharacters }) => {
17
+ const output = await runSmartsearch({
18
+ query,
19
+ ...(numResults === undefined ? {} : { numResults }),
20
+ ...(type === undefined ? {} : { searchType: type }),
21
+ ...(contextMaxCharacters === undefined ? {} : { contextMaxCharacters }),
22
+ });
23
+ return {
24
+ structuredContent: output,
25
+ content: [{ type: "text", text: renderSmartsearch(output) }],
26
+ };
27
+ });
28
+ server.tool("smartfetch", "Fetch a URL with browser-aware fallbacks. Uses impit first, then stealth Playwright when the page looks JS-rendered or blocked.", {
29
+ url: z.string().min(1).describe("Target URL to fetch"),
30
+ target: z.enum(["auto", "generic", "reddit_post", "x_post", "dcinside_post"]).optional().describe("Content type hint"),
31
+ timeout_ms: z.number().int().min(3000).max(120000).optional().describe("Fetch timeout in milliseconds"),
32
+ force_dynamic: z.boolean().optional().describe("Skip light fetches and go straight to Playwright"),
33
+ strict_schema: z.boolean().optional().describe("Return schema-safe fallback output when normalization breaks"),
34
+ format: z.enum(["json", "text", "markdown"]).optional().describe("Text rendering format for the human-readable content"),
35
+ }, async ({ url, target, timeout_ms, force_dynamic, strict_schema, format }) => {
36
+ const output = await runSmartfetch({
37
+ url,
38
+ ...(target === undefined ? {} : { target }),
39
+ ...(timeout_ms === undefined ? {} : { timeoutMs: timeout_ms }),
40
+ ...(force_dynamic === undefined ? {} : { forceDynamic: force_dynamic }),
41
+ ...(strict_schema === undefined ? {} : { strictSchema: strict_schema }),
42
+ });
43
+ const requestedFormat = format || "json";
44
+ return {
45
+ structuredContent: output,
46
+ content: [
47
+ {
48
+ type: "text",
49
+ text: requestedFormat === "json" ? JSON.stringify(output, null, 2) : renderSmartfetch(output, requestedFormat),
50
+ },
51
+ ],
52
+ };
53
+ });
54
+ async function main() {
55
+ const transport = new StdioServerTransport();
56
+ await server.connect(transport);
57
+ }
58
+ main().catch((error) => {
59
+ console.error(error);
60
+ process.exit(1);
61
+ });
62
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/lib.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ export { runSmartfetch, renderSmartfetch } from "./smartfetch.js";
2
+ export { runSmartsearch, renderSmartsearch } from "./smartsearch.js";
3
+ export type { AdapterResult, ErrorCategory, SearchResponse, SearchResult, SmartfetchOutput, SmartfetchRenderFormat, Target, ToolError, } from "./shared.js";
package/dist/lib.js ADDED
@@ -0,0 +1,3 @@
1
+ export { runSmartfetch, renderSmartfetch } from "./smartfetch.js";
2
+ export { runSmartsearch, renderSmartsearch } from "./smartsearch.js";
3
+ //# sourceMappingURL=lib.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lib.js","sourceRoot":"","sources":["../src/lib.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAA;AACjE,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAA"}
@@ -0,0 +1,105 @@
1
+ export type Target = "auto" | "generic" | "reddit_post" | "x_post" | "dcinside_post";
2
+ export type ResolvedTarget = Exclude<Target, "auto">;
3
+ export type ErrorCategory = "timeout" | "block" | "parse_error" | "unavailable";
4
+ export type ToolError = {
5
+ category: ErrorCategory;
6
+ code: string;
7
+ message: string;
8
+ };
9
+ export type AdapterResult = {
10
+ ok: boolean;
11
+ method: string;
12
+ content: string;
13
+ links: string[];
14
+ error?: ToolError;
15
+ };
16
+ export type SearchResult = {
17
+ title: string;
18
+ url: string;
19
+ snippet: string;
20
+ engine: string;
21
+ };
22
+ export type SearchResponse = {
23
+ engine: string;
24
+ query: string;
25
+ results: SearchResult[];
26
+ fallbacks_tried: string[];
27
+ notes: string[];
28
+ };
29
+ export type SmartfetchOutput = {
30
+ source: "smartfetch";
31
+ url: string;
32
+ target: ResolvedTarget;
33
+ retrieval_method: string[];
34
+ risk_level: "low" | "medium" | "high";
35
+ partial: boolean;
36
+ errors: ToolError[];
37
+ post: Record<string, unknown> | null;
38
+ thread: Record<string, unknown>[];
39
+ comments: Record<string, unknown>[];
40
+ outbound_links: string[];
41
+ evidence: {
42
+ impit_attempted: boolean;
43
+ impit_success: boolean;
44
+ playwright_attempted: boolean;
45
+ playwright_success: boolean;
46
+ generated_at: string;
47
+ };
48
+ };
49
+ export type SmartfetchRenderFormat = "text" | "markdown";
50
+ export declare function asString(value: unknown): string;
51
+ export declare function asNumber(value: unknown): number;
52
+ export declare function decodeHtml(value: string): string;
53
+ export declare function stripTags(value: string): string;
54
+ export declare function trimUrlToken(value: string): string;
55
+ export declare function dedupeUrls(values: string[]): string[];
56
+ export declare function cleanLinks(values: string[]): string[];
57
+ export declare function extractUrls(value: string): string[];
58
+ export declare function extractAnchorHrefs(value: string): string[];
59
+ export declare function uniqueResults(results: SearchResult[]): SearchResult[];
60
+ export declare function resolveDuckDuckGoUrl(input: string): string;
61
+ export declare function capture(value: string, regex: RegExp): string;
62
+ export declare function extractTitleFromHtml(html: string): string;
63
+ export declare function extractMetaDescription(html: string): string;
64
+ export declare function inferTarget(url: string, target: Target): ResolvedTarget;
65
+ export declare function envFlag(name: string): boolean;
66
+ export declare function envEnabled(name: string, defaultValue: boolean): boolean;
67
+ export declare function validateOutboundUrl(url: string, options?: {
68
+ allowPrivateHosts?: boolean;
69
+ }): Promise<{
70
+ ok: false;
71
+ reason: string;
72
+ message: string;
73
+ } | {
74
+ ok: true;
75
+ reason?: never;
76
+ message?: never;
77
+ }>;
78
+ export declare function isXUrl(url: string): boolean;
79
+ export declare function isRedditUrl(url: string): boolean;
80
+ export declare function isDcinsideUrl(url: string): boolean;
81
+ export declare function needsDynamicCrawl(result: AdapterResult): boolean;
82
+ export declare function fetchText(url: string, timeoutMs: number, init?: RequestInit): Promise<{
83
+ ok: boolean;
84
+ status: number;
85
+ text: string;
86
+ error?: never;
87
+ } | {
88
+ ok: boolean;
89
+ status: number;
90
+ text: string;
91
+ error: string;
92
+ }>;
93
+ export declare function fetchJson(url: string, timeoutMs: number, init?: RequestInit): Promise<{
94
+ data: any;
95
+ ok: boolean;
96
+ status: number;
97
+ text: string;
98
+ error?: never;
99
+ } | {
100
+ data: any;
101
+ ok: boolean;
102
+ status: number;
103
+ text: string;
104
+ error: string;
105
+ }>;