Skip to content

Commit 391d3c2

Browse files
committed
feat(mcp): add websearch MCP server 🎉
- Add CI workflow to run deno check and tests - Add Deno tasks and strict compiler options - Add HTML-to-markdown converter with link resolution - Add MCP stdio JSON-RPC server with tools/list and tools/call - Add npm package metadata and unbuild config for CLI/dist output - Add README, LICENSE, and preview assets for usage docs - Add web_fetch tool with URL validation, content-type handling, and timeouts - Add web_search tool calling Ollama API with API key support - Add unit tests for converter, fetcher, and tool schemas
0 parents  commit 391d3c2

20 files changed

Lines changed: 1211 additions & 0 deletions

.github/workflows/ci.yaml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
9+
jobs:
10+
build-check:
11+
name: Build Check
12+
runs-on: ubuntu-latest
13+
steps:
14+
- name: Checkout code
15+
uses: actions/checkout@v4
16+
17+
- name: Setup Deno
18+
uses: denoland/setup-deno@v2
19+
with:
20+
deno-version: v2.x
21+
22+
- name: Check
23+
run: deno task check
24+
25+
- name: Test
26+
run: deno task test

.gitignore

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Deno
2+
.deno/
3+
coverage/
4+
deno.lock
5+
6+
# IDE
7+
.vscode/
8+
.idea/
9+
10+
# OS
11+
.DS_Store
12+
Thumbs.db
13+
14+
# Node
15+
dist/
16+
node_modules/
17+
package-lock.json

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2025 NeaByteLab
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.

README.md

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
<div align="center">
2+
3+
# WebSearch MCP
4+
5+
MCP server for web search and web fetch tools.
6+
7+
[![Node](https://img.shields.io/badge/node-%3E%3D20-339933?logo=node.js&logoColor=white)](https://nodejs.org)
8+
[![Deno](https://img.shields.io/badge/deno-compatible-ffcb00?logo=deno&logoColor=000000)](https://deno.com)
9+
[![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
10+
11+
</div>
12+
13+
## Features
14+
15+
- **Two tools**
16+
- **`web_search`**: find relevant URLs for a query
17+
- **`web_fetch`**: fetch latest page content (markdown/text)
18+
- **Cost-aware workflow**: recall → search → selective fetch (save quota)
19+
- **Docs-first answers**: answers can cite freshly fetched official pages
20+
21+
Official reference: [Ollama Web Search](https://docs.ollama.com/capabilities/web-search)
22+
23+
## Installation
24+
25+
> [!NOTE]
26+
> **Prerequisites:** Node.js for npm install. `OLLAMA_API_KEY` is required for `web_search`.
27+
28+
**npm:**
29+
30+
```bash
31+
npm install -g @neabyte/websearch-mcp
32+
```
33+
34+
## Quick Start
35+
36+
### Run MCP Server (Stdio)
37+
38+
Run the server (example for MCP clients using stdio):
39+
40+
```bash
41+
websearch-mcp
42+
```
43+
44+
If you want to set the API key for Ollama web search:
45+
46+
```bash
47+
export OLLAMA_API_KEY="..."
48+
websearch-mcp
49+
```
50+
51+
## Editor Integration (Cursor / VSCode)
52+
53+
You can integrate this MCP server directly into editors (Cursor/VSCode) via **stdio** MCP
54+
configuration. Two common options:
55+
56+
- **Global install**: `npm install -g @neabyte/websearch-mcp` then run `websearch-mcp`
57+
- **No global install**: use `npx -y @neabyte/websearch-mcp` (recommended for editors)
58+
59+
### Example (Recommended): `npx` Stdio + Env
60+
61+
Paste this MCP server config in your editor (example JSON used by many MCP clients):
62+
63+
```json
64+
{
65+
"mcpServers": {
66+
"websearch-mcp": {
67+
"type": "stdio",
68+
"command": "npx",
69+
"args": ["-y", "@neabyte/websearch-mcp"],
70+
"env": {
71+
"OLLAMA_API_KEY": "YOUR_OLLAMA_API_KEY"
72+
}
73+
}
74+
}
75+
}
76+
```
77+
78+
> `OLLAMA_API_KEY` is required for `web_search`. `web_fetch` can run without a key.
79+
80+
### Smoke Test Prompt
81+
82+
After the server is registered in your editor, try a prompt like this:
83+
84+
```text
85+
Use installed MCP Servers: `websearch-mcp` - `web_fetch` to fetch https://reactnative.dev/docs/environment-setup. Summarize the macOS install steps.
86+
```
87+
88+
### Example Prompts
89+
90+
**Direct fetch (when the URL is known):**
91+
92+
```text
93+
Use installed MCP Servers: `websearch-mcp` - `web_fetch` to fetch the official React Native documentation pages. Question: How to install in macOS?
94+
```
95+
96+
**Search → fetch (when the URL is unknown):**
97+
98+
```text
99+
Use installed MCP Servers: `websearch-mcp` - `web_search` to find official React Native docs URLs for "environment setup macOS". Then use `web_fetch` on the most relevant official doc page URLs. Question: How to install React Native in macOS?
100+
```
101+
102+
## Concept: LLM Knowledge → Latest Docs
103+
104+
This approach uses two steps:
105+
106+
- **Recall**: the LLM proposes likely official URLs from its knowledge
107+
- **Fetch**: the LLM calls `web_fetch` to retrieve the latest content
108+
109+
This is useful for fast-changing documentation, because the final answer is grounded in pages fetched
110+
at tool-call time.
111+
112+
## Strategy: Save Quota
113+
114+
Ollama can get expensive if you `web_fetch` many pages. A cost-aware pattern is:
115+
116+
- **Recall first**: ask for a shortlist of likely official URLs
117+
- **Search if needed**: use `web_search` to find fresh candidates
118+
- **Fetch selectively**: fetch only 1–3 most relevant pages
119+
120+
With this pattern, you only spend fetch calls on pages you actually cite.
121+
122+
## Examples (Screenshots)
123+
124+
### Web Fetch (Single Tool)
125+
126+
| Prompt | Output |
127+
| ------------------------------------------ | ------------------------------------------ |
128+
| <img src="./preview/1.png" height="360" /> | <img src="./preview/2.png" height="360" /> |
129+
130+
- **Prompt**: ask the model to use `websearch-mcp` + `web_fetch` to fetch official React Native docs
131+
- **Output**: the model fetches `reactnative.dev` and summarizes install steps
132+
133+
### Web Search → Web Fetch (Double Tool)
134+
135+
Even if `web_search` has its own quota/limits, the “double tool” pattern can reduce total cost by
136+
shortlisting URLs first and fetching only what you need.
137+
138+
```text
139+
Use websearch-mcp web_search with query: "latest crypto payment gateway 2026".
140+
Then use websearch-mcp web_fetch to fetch the official pages from the top results.
141+
Answer with a short list of the latest crypto payment gateways and include the source links.
142+
```
143+
144+
<img src="./preview/3.png" height="520" />
145+
146+
## Build & Test
147+
148+
From the repo root (requires [Deno](https://deno.com/)).
149+
150+
**Check** — format, lint, and typecheck source:
151+
152+
```bash
153+
deno task check
154+
```
155+
156+
**Unit Tests**:
157+
158+
```bash
159+
deno task test
160+
```
161+
162+
- Tests live under `tests/`
163+
164+
## License
165+
166+
This project is licensed under the MIT license. See the [LICENSE](LICENSE) file for details.

build.config.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { defineBuildConfig } from 'unbuild'
2+
import { resolve } from 'node:path'
3+
4+
/**
5+
* Unbuild config for package bundle.
6+
* @description Entry, alias, CJS/ESM, declarations, no sourcemaps.
7+
*/
8+
export default defineBuildConfig({
9+
/** Entry module path(s) for build. */
10+
entries: ['src/index', 'src/CLI'],
11+
/** Emit TypeScript declaration files. */
12+
declaration: true,
13+
/** Remove output directory before build. */
14+
clean: true,
15+
/** Path alias for imports (e.g. @app → src). */
16+
alias: {
17+
'@app': resolve(__dirname, 'src')
18+
},
19+
/** Rollup options: emit CJS and inline runtime deps. */
20+
rollup: {
21+
emitCJS: true,
22+
inlineDependencies: true,
23+
output: {
24+
banner: '#!/usr/bin/env node\n'
25+
}
26+
},
27+
/** Disable source map generation. */
28+
sourcemap: false,
29+
/** Do not fail build on Rollup warnings. */
30+
failOnWarn: false
31+
})

deno.json

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
{
2+
"name": "@neabyte/websearch-mcp",
3+
"description": "MCP server for web search and web fetch tools",
4+
"version": "0.1.0",
5+
"type": "module",
6+
"license": "MIT",
7+
"exports": "./src/index.ts",
8+
9+
"compilerOptions": {
10+
"allowUnreachableCode": false,
11+
"allowUnusedLabels": false,
12+
"checkJs": false,
13+
"exactOptionalPropertyTypes": true,
14+
"jsx": "react",
15+
"jsxFactory": "React.createElement",
16+
"jsxFragmentFactory": "React.Fragment",
17+
"lib": ["deno.ns", "deno.worker", "dom", "dom.asynciterable"],
18+
"module": "nodenext",
19+
"moduleResolution": "nodenext",
20+
"noErrorTruncation": true,
21+
"noFallthroughCasesInSwitch": true,
22+
"noImplicitAny": true,
23+
"noImplicitOverride": true,
24+
"noImplicitReturns": true,
25+
"noImplicitThis": true,
26+
"noPropertyAccessFromIndexSignature": true,
27+
"noUncheckedIndexedAccess": true,
28+
"noUnusedLocals": true,
29+
"noUnusedParameters": true,
30+
"strict": true,
31+
"strictBindCallApply": true,
32+
"strictFunctionTypes": true,
33+
"strictNullChecks": true,
34+
"strictPropertyInitialization": true,
35+
"useUnknownInCatchVariables": true
36+
},
37+
"fmt": {
38+
"bracePosition": "sameLine",
39+
"indentWidth": 2,
40+
"lineWidth": 100,
41+
"proseWrap": "preserve",
42+
"semiColons": false,
43+
"singleBodyPosition": "nextLine",
44+
"singleQuote": true,
45+
"spaceAround": false,
46+
"spaceSurroundingProperties": true,
47+
"trailingCommas": "never",
48+
"useBraces": "always",
49+
"useTabs": false
50+
},
51+
"lint": {
52+
"include": ["src/"],
53+
"rules": {
54+
"tags": ["fresh", "jsr", "jsx", "react", "recommended", "workspace"],
55+
"exclude": ["no-console", "no-external-import", "prefer-ascii", "prefer-primordials"]
56+
}
57+
},
58+
"lock": true,
59+
"nodeModulesDir": "auto",
60+
"tasks": {
61+
"check": "deno fmt src/ && deno lint src/ && deno check src/",
62+
"start": "deno run -A src/index.ts",
63+
"test": "deno test tests/ --allow-read"
64+
},
65+
"imports": {
66+
"@app/": "./src/"
67+
},
68+
"publish": {
69+
"include": ["src/", "README.md", "LICENSE", "deno.json"]
70+
}
71+
}

0 commit comments

Comments
 (0)