webify-mcp 0.0.1__py3-none-any.whl
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.
- mcp_server.py +154 -0
- webify.py +2213 -0
- webify_mcp-0.0.1.dist-info/METADATA +284 -0
- webify_mcp-0.0.1.dist-info/RECORD +7 -0
- webify_mcp-0.0.1.dist-info/WHEEL +4 -0
- webify_mcp-0.0.1.dist-info/entry_points.txt +2 -0
- webify_mcp-0.0.1.dist-info/licenses/LICENSE +21 -0
mcp_server.py
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Webify MCP Server — stdio transport.
|
|
3
|
+
|
|
4
|
+
Exposes Webify as MCP tools for Claude Code / any MCP client.
|
|
5
|
+
|
|
6
|
+
Setup:
|
|
7
|
+
claude mcp add webify -- python3 /path/to/mcp_server.py
|
|
8
|
+
|
|
9
|
+
Tools:
|
|
10
|
+
web_lookup(url, query) — Smart lookup with graph retrieval + confidence
|
|
11
|
+
web_find(query) — Search web + parallel multi-source retrieval
|
|
12
|
+
web_build(url) — Pre-build graph for a URL (cache it)
|
|
13
|
+
web_stats(url) — Show graph stats for a cached URL
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import sys
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
|
|
19
|
+
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
20
|
+
|
|
21
|
+
from mcp.server.fastmcp import FastMCP
|
|
22
|
+
import webify
|
|
23
|
+
|
|
24
|
+
mcp = FastMCP(
|
|
25
|
+
"webify",
|
|
26
|
+
instructions=(
|
|
27
|
+
"Webify provides efficient web lookup via semantic graph retrieval. "
|
|
28
|
+
"Use web_find(query) to search the web and get a synthesized, multi-source answer — "
|
|
29
|
+
"it searches DuckDuckGo, builds semantic graphs in parallel, extracts structurally "
|
|
30
|
+
"relevant subtrees from multiple anchors per page, then synthesizes with Haiku. "
|
|
31
|
+
"Result quality matches DeepSearch at 1-5% of the token cost. "
|
|
32
|
+
"Use web_lookup(url, query) when you already know the URL. "
|
|
33
|
+
"If web_lookup returns status='fallback_needed', use WebFetch instead."
|
|
34
|
+
),
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@mcp.tool()
|
|
39
|
+
def web_lookup(url: str, query: str, max_results: int = 3) -> dict:
|
|
40
|
+
"""Look up specific information from a web page using graph-based retrieval.
|
|
41
|
+
|
|
42
|
+
Fetches the page (cached after first hit), builds a semantic graph, then
|
|
43
|
+
retrieves only the relevant nodes via BFS traversal. Returns ~250-750 tokens
|
|
44
|
+
instead of the full page (5000-50000 tokens).
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
url: The web page URL to look up.
|
|
48
|
+
query: What you're looking for (e.g. "authentication setup", "API rate limits").
|
|
49
|
+
max_results: Max nodes to return (default 3, usually sufficient).
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
status: "success" | "low_confidence" | "fallback_needed"
|
|
53
|
+
content: The retrieved content (relevant sections only).
|
|
54
|
+
confidence: Graph quality assessment.
|
|
55
|
+
tokens_used: How many tokens the response costs.
|
|
56
|
+
fallback_reason: Why to use WebFetch instead (if fallback_needed).
|
|
57
|
+
"""
|
|
58
|
+
return webify.smart_lookup(url, query, max_results=max_results)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@mcp.tool()
|
|
62
|
+
def web_find(query: str, num_sources: int = 0, synthesize: bool = True) -> dict:
|
|
63
|
+
"""Search the web and return a synthesized, high-quality answer from multiple sources.
|
|
64
|
+
|
|
65
|
+
Pipeline: DuckDuckGo search → parallel semantic graph builds → multi-aspect
|
|
66
|
+
BM25 extraction → Haiku synthesis. Adaptively scales depth based on query
|
|
67
|
+
complexity (more sources + broader retrieval for multi-dimensional questions).
|
|
68
|
+
|
|
69
|
+
No hard cap on sources. For exhaustive research, call web_find multiple times
|
|
70
|
+
with different sub-queries — each call runs its own search and graph builds.
|
|
71
|
+
This is how you get deep-research-level coverage through Webify.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
query: What to search for (e.g. "gut microbiome mental health evidence").
|
|
75
|
+
num_sources: Sources to fetch per search. 0 = auto-scale by complexity (3-6).
|
|
76
|
+
Pass higher (8, 10, 12) for broader single-query coverage.
|
|
77
|
+
For exhaustive research, prefer multiple calls with focused queries.
|
|
78
|
+
synthesize: Set False to return raw fragments instead of synthesized answer.
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
status: "success" | "partial" | "no_results"
|
|
82
|
+
content: Synthesized answer with source-attributed fragments appended
|
|
83
|
+
for complex queries (gives the caller full context).
|
|
84
|
+
sources: [{url, title, confidence, tokens}]
|
|
85
|
+
tokens_used: Tokens in response.
|
|
86
|
+
search_results: Raw DDG results for transparency.
|
|
87
|
+
"""
|
|
88
|
+
result = webify.web_find(query, num_sources=num_sources, synthesize=synthesize)
|
|
89
|
+
|
|
90
|
+
# For complex queries: append raw fragments so the calling model has full
|
|
91
|
+
# material to synthesize from (Haiku synthesis is concise; the caller may
|
|
92
|
+
# want the underlying evidence for deeper answers)
|
|
93
|
+
complexity = webify._query_complexity(query)
|
|
94
|
+
if complexity >= 2 and synthesize and result.get("raw_fragments"):
|
|
95
|
+
result["content"] = (
|
|
96
|
+
result["content"] +
|
|
97
|
+
"\n\n---\n## Source Fragments (for additional detail)\n\n" +
|
|
98
|
+
result["raw_fragments"]
|
|
99
|
+
)
|
|
100
|
+
result["tokens_used"] = result.get("fragment_tokens", 0) + result.get("tokens_used", 0)
|
|
101
|
+
|
|
102
|
+
result.pop("raw_fragments", None)
|
|
103
|
+
return result
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
@mcp.tool()
|
|
107
|
+
def web_build(url: str, force_refresh: bool = False) -> dict:
|
|
108
|
+
"""Pre-build/refresh the graph for a URL. Useful for pages you'll query multiple times.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
url: The web page URL to index.
|
|
112
|
+
force_refresh: Re-fetch even if cached.
|
|
113
|
+
"""
|
|
114
|
+
try:
|
|
115
|
+
graph = webify.build_graph(url, force_refresh=force_refresh)
|
|
116
|
+
return {
|
|
117
|
+
"ok": True,
|
|
118
|
+
"url": url,
|
|
119
|
+
"title": graph.get("title", "")[:100],
|
|
120
|
+
"stats": graph.get("stats", {}),
|
|
121
|
+
"confidence": graph.get("confidence", {}),
|
|
122
|
+
"extraction_method": graph.get("extraction_method", "unknown"),
|
|
123
|
+
}
|
|
124
|
+
except Exception as e:
|
|
125
|
+
return {"ok": False, "error": str(e)}
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
@mcp.tool()
|
|
129
|
+
def web_stats(url: str) -> dict:
|
|
130
|
+
"""Show graph statistics for a previously fetched URL.
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
url: The URL to check stats for.
|
|
134
|
+
"""
|
|
135
|
+
try:
|
|
136
|
+
graph = webify.build_graph(url, force_refresh=False)
|
|
137
|
+
return {
|
|
138
|
+
"ok": True,
|
|
139
|
+
"url": url,
|
|
140
|
+
"title": graph.get("title", "")[:100],
|
|
141
|
+
"stats": graph.get("stats", {}),
|
|
142
|
+
"confidence": graph.get("confidence", {}),
|
|
143
|
+
"cached": True,
|
|
144
|
+
}
|
|
145
|
+
except Exception as e:
|
|
146
|
+
return {"ok": False, "error": str(e)}
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def main():
|
|
150
|
+
mcp.run(transport="stdio")
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
if __name__ == "__main__":
|
|
154
|
+
main()
|