grounder-mcp 0.1.0__tar.gz
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.
- grounder_mcp-0.1.0/PKG-INFO +36 -0
- grounder_mcp-0.1.0/README.md +25 -0
- grounder_mcp-0.1.0/grounder/__init__.py +2 -0
- grounder_mcp-0.1.0/grounder/mcp_server.py +84 -0
- grounder_mcp-0.1.0/grounder/tools.py +327 -0
- grounder_mcp-0.1.0/grounder_mcp.egg-info/PKG-INFO +36 -0
- grounder_mcp-0.1.0/grounder_mcp.egg-info/SOURCES.txt +11 -0
- grounder_mcp-0.1.0/grounder_mcp.egg-info/dependency_links.txt +1 -0
- grounder_mcp-0.1.0/grounder_mcp.egg-info/entry_points.txt +2 -0
- grounder_mcp-0.1.0/grounder_mcp.egg-info/requires.txt +2 -0
- grounder_mcp-0.1.0/grounder_mcp.egg-info/top_level.txt +1 -0
- grounder_mcp-0.1.0/pyproject.toml +24 -0
- grounder_mcp-0.1.0/setup.cfg +4 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: grounder-mcp
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Current Google answers (Search + AI Overview + AI Mode) as MCP tools for local LLMs and agents.
|
|
5
|
+
Project-URL: Homepage, https://grounder.dev
|
|
6
|
+
Keywords: mcp,google,ai-overview,ai-mode,search,llm,ollama,lm-studio
|
|
7
|
+
Requires-Python: >=3.10
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
Requires-Dist: mcp>=1.2
|
|
10
|
+
Requires-Dist: httpx>=0.27
|
|
11
|
+
|
|
12
|
+
# grounder-mcp
|
|
13
|
+
|
|
14
|
+
From training data to current knowledge. Gives a local LLM or agent Google's live
|
|
15
|
+
answers — the real **AI Overview**, **AI Mode**, and organic **Search** with citations —
|
|
16
|
+
as three MCP tools. Privacy-first: the hosted service stores no query content.
|
|
17
|
+
|
|
18
|
+
This package is a thin MCP client; the scraping and billing run on the hosted service,
|
|
19
|
+
so it installs with no Playwright and runs fine on a laptop.
|
|
20
|
+
|
|
21
|
+
## Use
|
|
22
|
+
|
|
23
|
+
```json
|
|
24
|
+
{
|
|
25
|
+
"mcpServers": {
|
|
26
|
+
"grounder": {
|
|
27
|
+
"command": "uvx",
|
|
28
|
+
"args": ["grounder-mcp"],
|
|
29
|
+
"env": { "GROUNDER_API_KEY": "your_key_here" }
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Get a key at https://grounder.dev. Tools: `google_search`, `google_ai_overview`,
|
|
36
|
+
`google_ai_mode` — one credit per call.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# grounder-mcp
|
|
2
|
+
|
|
3
|
+
From training data to current knowledge. Gives a local LLM or agent Google's live
|
|
4
|
+
answers — the real **AI Overview**, **AI Mode**, and organic **Search** with citations —
|
|
5
|
+
as three MCP tools. Privacy-first: the hosted service stores no query content.
|
|
6
|
+
|
|
7
|
+
This package is a thin MCP client; the scraping and billing run on the hosted service,
|
|
8
|
+
so it installs with no Playwright and runs fine on a laptop.
|
|
9
|
+
|
|
10
|
+
## Use
|
|
11
|
+
|
|
12
|
+
```json
|
|
13
|
+
{
|
|
14
|
+
"mcpServers": {
|
|
15
|
+
"grounder": {
|
|
16
|
+
"command": "uvx",
|
|
17
|
+
"args": ["grounder-mcp"],
|
|
18
|
+
"env": { "GROUNDER_API_KEY": "your_key_here" }
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Get a key at https://grounder.dev. Tools: `google_search`, `google_ai_overview`,
|
|
25
|
+
`google_ai_mode` — one credit per call.
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""grounder MCP server (stdio).
|
|
2
|
+
|
|
3
|
+
Exposes the v1 tool surface — `google_search`, `google_ai_overview`, `google_ai_mode` —
|
|
4
|
+
over the Model Context Protocol so any MCP client (LM Studio, Claude Desktop, Cursor,
|
|
5
|
+
Continue.dev, Aider, a local-agent runtime) can ground answers in live Google.
|
|
6
|
+
|
|
7
|
+
This is a **thin client**: each tool forwards to the hosted API at `grounder.dev` with
|
|
8
|
+
your API key. The actual scraping (clean IP, captcha-solving) and billing happen on the
|
|
9
|
+
server — this just relays the call, so it runs fine on a laptop.
|
|
10
|
+
|
|
11
|
+
Run (set your key):
|
|
12
|
+
GROUNDER_API_KEY=gnd_live_... grounder-mcp
|
|
13
|
+
GROUNDER_API_KEY=gnd_live_... python -m grounder.mcp_server
|
|
14
|
+
|
|
15
|
+
Install (lightweight — no Playwright): pip install -e ".[client]"
|
|
16
|
+
"""
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
import os
|
|
19
|
+
from typing import Any
|
|
20
|
+
|
|
21
|
+
import httpx
|
|
22
|
+
from mcp.server.fastmcp import FastMCP
|
|
23
|
+
|
|
24
|
+
from .tools import GOOGLE_SEARCH, GOOGLE_AI_OVERVIEW, GOOGLE_AI_MODE
|
|
25
|
+
|
|
26
|
+
API_URL = os.environ.get("GROUNDER_API_URL", "https://grounder.dev").rstrip("/")
|
|
27
|
+
|
|
28
|
+
mcp = FastMCP("grounder")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
async def _run(query: str, region: str, language: str, device: str, engines: list[str]) -> dict[str, Any]:
|
|
32
|
+
"""Forward a search to the hosted gateway with the API key. Returns the SearchResponse.
|
|
33
|
+
|
|
34
|
+
Raises on a missing key or an HTTP error (FastMCP surfaces it as a tool error). A
|
|
35
|
+
*fetch* failure on the server does NOT raise — it comes back as a valid response with
|
|
36
|
+
`ai_answer.present=false`.
|
|
37
|
+
"""
|
|
38
|
+
key = os.environ.get("GROUNDER_API_KEY")
|
|
39
|
+
if not key:
|
|
40
|
+
raise RuntimeError(
|
|
41
|
+
"GROUNDER_API_KEY is not set. Get a key from grounder.dev and put it in the "
|
|
42
|
+
"MCP server's env (e.g. in your LM Studio / Claude Desktop mcp.json)."
|
|
43
|
+
)
|
|
44
|
+
async with httpx.AsyncClient(timeout=90.0) as client:
|
|
45
|
+
r = await client.post(
|
|
46
|
+
f"{API_URL}/v1/search",
|
|
47
|
+
headers={"Authorization": f"Bearer {key}"},
|
|
48
|
+
json={"query": query, "region": region, "language": language,
|
|
49
|
+
"device": device, "engines": engines},
|
|
50
|
+
)
|
|
51
|
+
r.raise_for_status()
|
|
52
|
+
return r.json()
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@mcp.tool(name=GOOGLE_SEARCH["name"], description=GOOGLE_SEARCH["description"])
|
|
56
|
+
async def google_search(
|
|
57
|
+
query: str, region: str = "us", language: str = "en", device: str = "desktop"
|
|
58
|
+
) -> dict[str, Any]:
|
|
59
|
+
return await _run(query, region, language, device, engines=["organic"])
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@mcp.tool(name=GOOGLE_AI_OVERVIEW["name"], description=GOOGLE_AI_OVERVIEW["description"])
|
|
63
|
+
async def google_ai_overview(
|
|
64
|
+
query: str, region: str = "us", language: str = "en", device: str = "desktop"
|
|
65
|
+
) -> dict[str, Any]:
|
|
66
|
+
# AIO + organic come from one fetch; AIO is the headline, organic is the side-effect.
|
|
67
|
+
return await _run(query, region, language, device, engines=["aio", "organic"])
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@mcp.tool(name=GOOGLE_AI_MODE["name"], description=GOOGLE_AI_MODE["description"])
|
|
71
|
+
async def google_ai_mode(
|
|
72
|
+
query: str, region: str = "us", language: str = "en", device: str = "desktop"
|
|
73
|
+
) -> dict[str, Any]:
|
|
74
|
+
# AI Mode is a separate udm=50 fetch on the server; no classic organic block.
|
|
75
|
+
return await _run(query, region, language, device, engines=["ai_mode"])
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def main() -> None:
|
|
79
|
+
"""Console-script entry point. Serves over stdio."""
|
|
80
|
+
mcp.run()
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
if __name__ == "__main__":
|
|
84
|
+
main()
|
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
"""MCP tool definitions — the v1 surface exposed to local LLMs.
|
|
2
|
+
|
|
3
|
+
Three tools, each = 1 credit per call:
|
|
4
|
+
google_search — organic + extras
|
|
5
|
+
google_ai_overview — AI Overview answer + citations
|
|
6
|
+
google_ai_mode — AI Mode (udm=50) deeper answer
|
|
7
|
+
|
|
8
|
+
Tool descriptions follow MCP best practices (per the Tavily #141 D-grade lesson):
|
|
9
|
+
1. Start with a verb
|
|
10
|
+
2. State unique capability vs sibling tools
|
|
11
|
+
3. Note when NOT to use it
|
|
12
|
+
4. Document defaults explicitly
|
|
13
|
+
5. State cost
|
|
14
|
+
|
|
15
|
+
These descriptions are how LLMs choose between tools. Specificity is the moat.
|
|
16
|
+
"""
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
from typing import Any
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# ─── google_search ────────────────────────────────────────────────────────────
|
|
22
|
+
|
|
23
|
+
GOOGLE_SEARCH = {
|
|
24
|
+
"name": "google_search",
|
|
25
|
+
"description": (
|
|
26
|
+
"Search Google and return the organic results + extras (People Also Ask, related searches, "
|
|
27
|
+
"knowledge panel, top stories when present). Use this when you need raw blue-link results, "
|
|
28
|
+
"source diversity, or when you want to gather URLs to read further. "
|
|
29
|
+
"Does NOT include the AI Overview text — use google_ai_overview for that. "
|
|
30
|
+
"Returns up to 10 organic results. AI Overview's presence is detected and surfaced as "
|
|
31
|
+
"`extras.aio_present: true|false` but the full AIO text is only available via the dedicated tool. "
|
|
32
|
+
"1 credit per call."
|
|
33
|
+
),
|
|
34
|
+
"inputSchema": {
|
|
35
|
+
"type": "object",
|
|
36
|
+
"properties": {
|
|
37
|
+
"query": {
|
|
38
|
+
"type": "string",
|
|
39
|
+
"description": "The search query. Use natural language; quoted phrases work as in google.com.",
|
|
40
|
+
"minLength": 1,
|
|
41
|
+
"maxLength": 2048,
|
|
42
|
+
},
|
|
43
|
+
"region": {
|
|
44
|
+
"type": "string",
|
|
45
|
+
"description": (
|
|
46
|
+
"ISO-3166-1 alpha-2 lowercase country code. Affects egress + `gl` param. "
|
|
47
|
+
"Default 'us'. Note: 'de'/'fr' regions have DMA-driven citation pool restrictions."
|
|
48
|
+
),
|
|
49
|
+
"pattern": "^[a-z]{2}$",
|
|
50
|
+
"default": "us",
|
|
51
|
+
},
|
|
52
|
+
"language": {
|
|
53
|
+
"type": "string",
|
|
54
|
+
"description": "BCP 47 lowercase. Affects `hl` param. Default 'en'.",
|
|
55
|
+
"pattern": "^[a-z]{2,3}$",
|
|
56
|
+
"default": "en",
|
|
57
|
+
},
|
|
58
|
+
"device": {
|
|
59
|
+
"type": "string",
|
|
60
|
+
"description": "Device profile. Mobile rendering surfaces different components.",
|
|
61
|
+
"enum": ["desktop", "mobile"],
|
|
62
|
+
"default": "desktop",
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
"required": ["query"],
|
|
66
|
+
"additionalProperties": False,
|
|
67
|
+
},
|
|
68
|
+
"outputSchema": {
|
|
69
|
+
"description": "See SCHEMA.md §`results`, `extras`, `meta` for the full shape.",
|
|
70
|
+
},
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
# ─── google_ai_overview ───────────────────────────────────────────────────────
|
|
75
|
+
|
|
76
|
+
GOOGLE_AI_OVERVIEW = {
|
|
77
|
+
"name": "google_ai_overview",
|
|
78
|
+
"description": (
|
|
79
|
+
"Get Google's AI Overview answer for a query. The AI Overview is the inline AI-synthesized "
|
|
80
|
+
"answer block Google places above classic SERP results. Returns: the answer text (split into "
|
|
81
|
+
"sections with headings); inline citation badges with corroboration counts ('+N additional "
|
|
82
|
+
"sources back this claim'); the right-side citation sidebar (top-3 sources by default); the "
|
|
83
|
+
"total source pool size ('27 sites' indicator). "
|
|
84
|
+
"Use this when you need Google's authoritative synthesis with citations — particularly for "
|
|
85
|
+
"informational, comparison, or commercial queries where Google has invested in an AI answer. "
|
|
86
|
+
"AI Overview fires for ~82% of typical queries (98% for question-form like 'how does X work', "
|
|
87
|
+
"96% for commercial like 'best X 2026', 56% for dev/technical queries where Google often shows "
|
|
88
|
+
"raw docs instead). When AIO does NOT fire, returns `present: false` with an unavailable_reason "
|
|
89
|
+
"and the classic SERP organic results in `results[]`. "
|
|
90
|
+
"Do NOT use this if you only need raw blue-link results — use google_search instead. "
|
|
91
|
+
"Do NOT use this for queries with `site:` or `filetype:` operators — those typically suppress AIO. "
|
|
92
|
+
"1 credit per call. Returns include the classic SERP as a side-effect (same fetch)."
|
|
93
|
+
),
|
|
94
|
+
"inputSchema": {
|
|
95
|
+
"type": "object",
|
|
96
|
+
"properties": {
|
|
97
|
+
"query": {
|
|
98
|
+
"type": "string",
|
|
99
|
+
"description": "The search query. Question-form queries fire AIO most reliably.",
|
|
100
|
+
"minLength": 1,
|
|
101
|
+
"maxLength": 2048,
|
|
102
|
+
},
|
|
103
|
+
"region": {
|
|
104
|
+
"type": "string",
|
|
105
|
+
"description": (
|
|
106
|
+
"ISO-3166-1 alpha-2 lowercase. **Important**: EU regions (de/fr/etc) have heavily "
|
|
107
|
+
"trimmed citation pools (DMA effect). For full source coverage use region='us'."
|
|
108
|
+
),
|
|
109
|
+
"pattern": "^[a-z]{2}$",
|
|
110
|
+
"default": "us",
|
|
111
|
+
},
|
|
112
|
+
"language": {
|
|
113
|
+
"type": "string",
|
|
114
|
+
"pattern": "^[a-z]{2,3}$",
|
|
115
|
+
"default": "en",
|
|
116
|
+
},
|
|
117
|
+
"device": {
|
|
118
|
+
"type": "string",
|
|
119
|
+
"enum": ["desktop", "mobile"],
|
|
120
|
+
"default": "desktop",
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
"required": ["query"],
|
|
124
|
+
"additionalProperties": False,
|
|
125
|
+
},
|
|
126
|
+
"outputSchema": {
|
|
127
|
+
"description": "See SCHEMA.md §`ai_answer` (surface='aio') + `results[]` + `extras` + `meta`.",
|
|
128
|
+
},
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
# ─── google_ai_mode ───────────────────────────────────────────────────────────
|
|
133
|
+
|
|
134
|
+
GOOGLE_AI_MODE = {
|
|
135
|
+
"name": "google_ai_mode",
|
|
136
|
+
"description": (
|
|
137
|
+
"Get Google's AI Mode answer for a query. AI Mode is Google's dedicated AI-search tab "
|
|
138
|
+
"(URL parameter `udm=50`), distinct from AI Overview. Different surface, different ranker. "
|
|
139
|
+
"Returns: a deeper synthesized answer (numbered sections, typically 9 sub-sections of content); "
|
|
140
|
+
"more citations than AI Overview (commonly 20-40 cited sources vs 5-10 in AIO); a right-side "
|
|
141
|
+
"citation sidebar; total source pool indicator. The answer is conversational in tone. "
|
|
142
|
+
"Use this when you need maximum source breadth for a topic, deeper exploration than what AI "
|
|
143
|
+
"Overview provides, or specifically the 'AI Mode' surface for GEO/SEO research (e.g., 'is my "
|
|
144
|
+
"brand cited in AI Mode for X query?'). "
|
|
145
|
+
"Compared to google_ai_overview: AI Mode has roughly 4× more total sources, only ~14% URL "
|
|
146
|
+
"overlap with AIO for the same query, and deeper structured text. "
|
|
147
|
+
"AI Mode is a separate Google page (URL `?q=...&udm=50`), so this tool makes its own fetch and "
|
|
148
|
+
"costs 1 credit independently of google_search/google_ai_overview. "
|
|
149
|
+
"Do NOT use this if you only need a quick fact — google_ai_overview is faster and cheaper "
|
|
150
|
+
"context for short answers. AI Mode is heavier and slower."
|
|
151
|
+
),
|
|
152
|
+
"inputSchema": {
|
|
153
|
+
"type": "object",
|
|
154
|
+
"properties": {
|
|
155
|
+
"query": {
|
|
156
|
+
"type": "string",
|
|
157
|
+
"description": "The search query.",
|
|
158
|
+
"minLength": 1,
|
|
159
|
+
"maxLength": 2048,
|
|
160
|
+
},
|
|
161
|
+
"region": {
|
|
162
|
+
"type": "string",
|
|
163
|
+
"description": (
|
|
164
|
+
"ISO-3166-1 alpha-2 lowercase. AI Mode availability varies — EU regions may have "
|
|
165
|
+
"it suppressed by DMA. US is most reliable."
|
|
166
|
+
),
|
|
167
|
+
"pattern": "^[a-z]{2}$",
|
|
168
|
+
"default": "us",
|
|
169
|
+
},
|
|
170
|
+
"language": {
|
|
171
|
+
"type": "string",
|
|
172
|
+
"pattern": "^[a-z]{2,3}$",
|
|
173
|
+
"default": "en",
|
|
174
|
+
},
|
|
175
|
+
"device": {
|
|
176
|
+
"type": "string",
|
|
177
|
+
"enum": ["desktop", "mobile"],
|
|
178
|
+
"default": "desktop",
|
|
179
|
+
},
|
|
180
|
+
},
|
|
181
|
+
"required": ["query"],
|
|
182
|
+
"additionalProperties": False,
|
|
183
|
+
},
|
|
184
|
+
"outputSchema": {
|
|
185
|
+
"description": "See SCHEMA.md §`ai_answer` (surface='ai_mode'). `results[]` is empty for ai_mode.",
|
|
186
|
+
},
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
# ─── Tool registry ────────────────────────────────────────────────────────────
|
|
191
|
+
|
|
192
|
+
ALL_TOOLS: list[dict[str, Any]] = [
|
|
193
|
+
GOOGLE_SEARCH,
|
|
194
|
+
GOOGLE_AI_OVERVIEW,
|
|
195
|
+
GOOGLE_AI_MODE,
|
|
196
|
+
]
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def get_tool_by_name(name: str) -> dict[str, Any] | None:
|
|
200
|
+
"""Look up a tool definition by its MCP name."""
|
|
201
|
+
for tool in ALL_TOOLS:
|
|
202
|
+
if tool["name"] == name:
|
|
203
|
+
return tool
|
|
204
|
+
return None
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
# ─── Example responses (for docs + MCP server testing) ────────────────────────
|
|
208
|
+
|
|
209
|
+
# Verified against the 20 Phase 1 samples. Production response shape per SCHEMA.md.
|
|
210
|
+
EXAMPLE_RESPONSES = {
|
|
211
|
+
"google_search": {
|
|
212
|
+
"input": {"query": "best wireless headphones 2026", "region": "us"},
|
|
213
|
+
"output_sketch": {
|
|
214
|
+
"schema_version": "0.1.0",
|
|
215
|
+
"query": "best wireless headphones 2026",
|
|
216
|
+
"results": [
|
|
217
|
+
{
|
|
218
|
+
"position": 1,
|
|
219
|
+
"title": "The Best Wireless Headphones for 2026",
|
|
220
|
+
"url": "https://www.rtings.com/headphones/...",
|
|
221
|
+
"domain": "www.rtings.com",
|
|
222
|
+
"snippet": "Our top pick is the Sony WH-1000XM6...",
|
|
223
|
+
"sitelinks": [],
|
|
224
|
+
},
|
|
225
|
+
"...",
|
|
226
|
+
],
|
|
227
|
+
"extras": {
|
|
228
|
+
"people_also_ask": [
|
|
229
|
+
{"question": "Which is the best wireless headphone in 2026?"}
|
|
230
|
+
],
|
|
231
|
+
"related_searches": [
|
|
232
|
+
{"query": "best noise cancelling headphones"}
|
|
233
|
+
],
|
|
234
|
+
},
|
|
235
|
+
},
|
|
236
|
+
},
|
|
237
|
+
"google_ai_overview": {
|
|
238
|
+
"input": {"query": "how does photosynthesis work", "region": "us"},
|
|
239
|
+
"output_sketch": {
|
|
240
|
+
"schema_version": "0.1.0",
|
|
241
|
+
"query": "how does photosynthesis work",
|
|
242
|
+
"ai_answer": {
|
|
243
|
+
"present": True,
|
|
244
|
+
"surface": "aio",
|
|
245
|
+
"text": "Photosynthesis is the process by which plants...",
|
|
246
|
+
"format": {"has_heading": True, "has_list": True, "has_table": False, "has_image": True},
|
|
247
|
+
"sections": [
|
|
248
|
+
{"id": None, "heading": None, "text": "Photosynthesis is..."},
|
|
249
|
+
{"id": None, "heading": "1. The Light-Dependent Reactions", "text": "..."},
|
|
250
|
+
],
|
|
251
|
+
"inline_citations": [
|
|
252
|
+
{
|
|
253
|
+
"source_name": "National Geographic Society",
|
|
254
|
+
"corroboration_count": 3,
|
|
255
|
+
"url": None,
|
|
256
|
+
"href_raw": "/goto?url=CAES...",
|
|
257
|
+
},
|
|
258
|
+
],
|
|
259
|
+
"sidebar_citations": [
|
|
260
|
+
{
|
|
261
|
+
"src_id": "1",
|
|
262
|
+
"url": "https://education.nationalgeographic.org/...",
|
|
263
|
+
"host": "education.nationalgeographic.org",
|
|
264
|
+
"title": "Photosynthesis - Education | National Geographic Society",
|
|
265
|
+
},
|
|
266
|
+
],
|
|
267
|
+
"total_sources_available": 27,
|
|
268
|
+
"unavailable_reason": None,
|
|
269
|
+
},
|
|
270
|
+
"results": ["...10 organic..."],
|
|
271
|
+
},
|
|
272
|
+
},
|
|
273
|
+
"google_ai_mode": {
|
|
274
|
+
"input": {"query": "explain transformer architecture", "region": "us"},
|
|
275
|
+
"output_sketch": {
|
|
276
|
+
"schema_version": "0.1.0",
|
|
277
|
+
"query": "explain transformer architecture",
|
|
278
|
+
"ai_answer": {
|
|
279
|
+
"present": True,
|
|
280
|
+
"surface": "ai_mode",
|
|
281
|
+
"text": "The Transformer architecture is...",
|
|
282
|
+
"sections": [
|
|
283
|
+
{"id": "0", "heading": None, "text": "..."},
|
|
284
|
+
{"id": "1", "heading": "Self-Attention Mechanism", "text": "..."},
|
|
285
|
+
],
|
|
286
|
+
"sidebar_citations": ["...3 cards..."],
|
|
287
|
+
"total_sources_available": 41,
|
|
288
|
+
},
|
|
289
|
+
"results": [],
|
|
290
|
+
},
|
|
291
|
+
},
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
# ─── Errors per tool ──────────────────────────────────────────────────────────
|
|
296
|
+
|
|
297
|
+
# When a tool fails, the MCP server returns an error response that the LLM can branch on.
|
|
298
|
+
# These are the documented failure cases by tool.
|
|
299
|
+
|
|
300
|
+
ERROR_CASES = {
|
|
301
|
+
"google_search": [
|
|
302
|
+
("invalid_request", "query missing or > 2048 chars"),
|
|
303
|
+
("rate_limited", "Customer per-key rate limit exceeded"),
|
|
304
|
+
("quota_exceeded", "API key has no remaining credits"),
|
|
305
|
+
("google_rate_limited", "Google rate-limited our infrastructure; retry with backoff"),
|
|
306
|
+
("captcha_failed", "CapSolver could not bypass captcha; try again"),
|
|
307
|
+
("timeout", "Request exceeded internal 60s timeout"),
|
|
308
|
+
],
|
|
309
|
+
"google_ai_overview": [
|
|
310
|
+
("invalid_request", "query missing or > 2048 chars"),
|
|
311
|
+
("rate_limited", "Customer per-key rate limit exceeded"),
|
|
312
|
+
("quota_exceeded", "API key has no remaining credits"),
|
|
313
|
+
# Note: "AIO didn't fire" is NOT an error — returns ai_answer.present=False in 200 OK
|
|
314
|
+
("google_rate_limited", "Google rate-limited"),
|
|
315
|
+
("captcha_failed", "Captcha bypass failed"),
|
|
316
|
+
("timeout", "Request timeout"),
|
|
317
|
+
],
|
|
318
|
+
"google_ai_mode": [
|
|
319
|
+
("invalid_request", "query missing or > 2048 chars"),
|
|
320
|
+
("rate_limited", "Customer per-key rate limit exceeded"),
|
|
321
|
+
("quota_exceeded", "API key has no remaining credits"),
|
|
322
|
+
# AI Mode often suppressed in EU — returns ai_answer.present=False, not an error
|
|
323
|
+
("google_rate_limited", "Google rate-limited"),
|
|
324
|
+
("captcha_failed", "Captcha bypass failed"),
|
|
325
|
+
("timeout", "Request timeout"),
|
|
326
|
+
],
|
|
327
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: grounder-mcp
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Current Google answers (Search + AI Overview + AI Mode) as MCP tools for local LLMs and agents.
|
|
5
|
+
Project-URL: Homepage, https://grounder.dev
|
|
6
|
+
Keywords: mcp,google,ai-overview,ai-mode,search,llm,ollama,lm-studio
|
|
7
|
+
Requires-Python: >=3.10
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
Requires-Dist: mcp>=1.2
|
|
10
|
+
Requires-Dist: httpx>=0.27
|
|
11
|
+
|
|
12
|
+
# grounder-mcp
|
|
13
|
+
|
|
14
|
+
From training data to current knowledge. Gives a local LLM or agent Google's live
|
|
15
|
+
answers — the real **AI Overview**, **AI Mode**, and organic **Search** with citations —
|
|
16
|
+
as three MCP tools. Privacy-first: the hosted service stores no query content.
|
|
17
|
+
|
|
18
|
+
This package is a thin MCP client; the scraping and billing run on the hosted service,
|
|
19
|
+
so it installs with no Playwright and runs fine on a laptop.
|
|
20
|
+
|
|
21
|
+
## Use
|
|
22
|
+
|
|
23
|
+
```json
|
|
24
|
+
{
|
|
25
|
+
"mcpServers": {
|
|
26
|
+
"grounder": {
|
|
27
|
+
"command": "uvx",
|
|
28
|
+
"args": ["grounder-mcp"],
|
|
29
|
+
"env": { "GROUNDER_API_KEY": "your_key_here" }
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Get a key at https://grounder.dev. Tools: `google_search`, `google_ai_overview`,
|
|
36
|
+
`google_ai_mode` — one credit per call.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
grounder/__init__.py
|
|
4
|
+
grounder/mcp_server.py
|
|
5
|
+
grounder/tools.py
|
|
6
|
+
grounder_mcp.egg-info/PKG-INFO
|
|
7
|
+
grounder_mcp.egg-info/SOURCES.txt
|
|
8
|
+
grounder_mcp.egg-info/dependency_links.txt
|
|
9
|
+
grounder_mcp.egg-info/entry_points.txt
|
|
10
|
+
grounder_mcp.egg-info/requires.txt
|
|
11
|
+
grounder_mcp.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
grounder
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "grounder-mcp"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Current Google answers (Search + AI Overview + AI Mode) as MCP tools for local LLMs and agents."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
dependencies = [
|
|
12
|
+
"mcp>=1.2",
|
|
13
|
+
"httpx>=0.27",
|
|
14
|
+
]
|
|
15
|
+
keywords = ["mcp", "google", "ai-overview", "ai-mode", "search", "llm", "ollama", "lm-studio"]
|
|
16
|
+
|
|
17
|
+
[project.urls]
|
|
18
|
+
Homepage = "https://grounder.dev"
|
|
19
|
+
|
|
20
|
+
[project.scripts]
|
|
21
|
+
grounder-mcp = "grounder.mcp_server:main"
|
|
22
|
+
|
|
23
|
+
[tool.setuptools]
|
|
24
|
+
packages = ["grounder"]
|