Created
March 11, 2026 12:39
-
-
Save phosae/58736bb6ef388232ea19e4bd5e074fe8 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env bash | |
| # Configure Claude Code | |
| # Usage: PPIO_API_KEY=sk_xxx bash setup-claude-code.sh | |
| # After running, the key is stored in settings.json — just run `claude`, no env needed. | |
| set -euo pipefail | |
| : "${PPIO_API_KEY:?Usage: PPIO_API_KEY=sk_xxx bash setup-claude-code.sh}" | |
| CLAUDE_DIR="$HOME/.claude" | |
| mkdir -p "$CLAUDE_DIR/mcp-servers" | |
| # --- settings.json --- | |
| cat > "$CLAUDE_DIR/settings.json" <<EOF | |
| { | |
| "env": { | |
| "ANTHROPIC_API_KEY": "${PPIO_API_KEY}", | |
| "ANTHROPIC_BASE_URL": "https://api.ppinfra.com/anthropic", | |
| "ANTHROPIC_MODEL": "pa/claude-opus-4-6", | |
| "PPIO_API_KEY": "${PPIO_API_KEY}" | |
| }, | |
| "permissions": { | |
| "allow": [], | |
| "deny": [], | |
| "mode": "bypassPermissions" | |
| }, | |
| "autoUpdaterStatus": "disabled", | |
| "skipDangerousModePermissionPrompt": true | |
| } | |
| EOF | |
| # --- settings.local.json (optional tool permissions) --- | |
| cat > "$CLAUDE_DIR/settings.local.json" <<'EOF' | |
| { | |
| "permissions": { | |
| "allow": [ | |
| "mcp__google-search__google_search", | |
| "WebFetch(domain:linear.app)", | |
| "WebSearch" | |
| ] | |
| } | |
| } | |
| EOF | |
| # --- MCP Server: Google Search via Gemini on PPIO --- | |
| cat > "$CLAUDE_DIR/mcp-servers/google-search.sh" <<'MCPEOF' | |
| #!/usr/bin/env bash | |
| # MCP Server: Gemini Google Search via PPIO | |
| set -euo pipefail | |
| API_URL="https://api.ppinfra.com/gemini/v1/models/pa/gmn-2.5-fls-lt:generateContent" | |
| send_response() { printf '%s\n' "$1"; } | |
| send_error() { | |
| local id="$1" code="$2" msg="$3" | |
| send_response "{\"jsonrpc\":\"2.0\",\"id\":${id},\"error\":{\"code\":${code},\"message\":$(python3 -c "import json; print(json.dumps('$msg'))")}}" | |
| } | |
| handle_initialize() { | |
| local id="$1" | |
| send_response "{\"jsonrpc\":\"2.0\",\"id\":${id},\"result\":{\"protocolVersion\":\"2024-11-05\",\"capabilities\":{\"tools\":{}},\"serverInfo\":{\"name\":\"google-search\",\"version\":\"1.0.0\"}}}" | |
| } | |
| handle_tools_list() { | |
| local id="$1" | |
| send_response "{\"jsonrpc\":\"2.0\",\"id\":${id},\"result\":{\"tools\":[{\"name\":\"google_search\",\"description\":\"Search the web using Google Search via Gemini. Returns an answer with grounding sources.\",\"inputSchema\":{\"type\":\"object\",\"properties\":{\"query\":{\"type\":\"string\",\"description\":\"The search query\"}},\"required\":[\"query\"]}}]}}" | |
| } | |
| handle_tools_call() { | |
| local id="$1" raw_params="$2" | |
| local tool_name query | |
| tool_name=$(printf '%s' "$raw_params" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('name',''))") | |
| query=$(printf '%s' "$raw_params" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('arguments',{}).get('query',''))") | |
| if [ "$tool_name" != "google_search" ]; then | |
| send_error "$id" -32602 "Unknown tool: ${tool_name}"; return | |
| fi | |
| if [ -z "$query" ]; then | |
| send_error "$id" -32602 "Missing required parameter: query"; return | |
| fi | |
| local request_body | |
| request_body=$(python3 -c " | |
| import json, sys | |
| q = sys.argv[1] | |
| body = { | |
| 'contents': [{'role': 'user', 'parts': [{'text': q}]}], | |
| 'tools': [{'google_search': {}}] | |
| } | |
| print(json.dumps(body)) | |
| " "$query") | |
| local api_response http_code body | |
| api_response=$(curl -s -w '\n%{http_code}' -X POST "$API_URL" \ | |
| -H "Content-Type: application/json" \ | |
| -H "Authorization: Bearer ${PPIO_API_KEY}" \ | |
| -d "$request_body" 2>/dev/null) || true | |
| http_code=$(printf '%s' "$api_response" | tail -n1) | |
| body=$(printf '%s' "$api_response" | sed '$d') | |
| if [ "$http_code" != "200" ]; then | |
| local err_text | |
| err_text=$(printf '%s' "$body" | python3 -c " | |
| import sys, json | |
| try: | |
| d = json.load(sys.stdin) | |
| print(d.get('error', {}).get('message', 'API error')) | |
| except: print('API request failed with status $http_code') | |
| " 2>/dev/null) | |
| local result | |
| result=$(python3 -c " | |
| import json, sys | |
| text = sys.argv[1] | |
| print(json.dumps({'jsonrpc':'2.0','id':${id},'result':{'content':[{'type':'text','text':text}],'isError':True}})) | |
| " "$err_text") | |
| send_response "$result"; return | |
| fi | |
| local result | |
| result=$(printf '%s' "$body" | python3 -c ' | |
| import sys, json | |
| data = json.load(sys.stdin) | |
| parts = [] | |
| for candidate in data.get("candidates", []): | |
| for part in candidate.get("content", {}).get("parts", []): | |
| if "text" in part: | |
| parts.append(part["text"]) | |
| search_queries, sources, supports = [], [], [] | |
| for candidate in data.get("candidates", []): | |
| meta = candidate.get("groundingMetadata", {}) | |
| for q in meta.get("webSearchQueries", []): | |
| search_queries.append(q) | |
| chunks = meta.get("groundingChunks", []) | |
| for chunk in chunks: | |
| web = chunk.get("web", {}) | |
| uri = web.get("uri", "") | |
| title = web.get("title", "") | |
| domain = web.get("domain", "") | |
| if uri: | |
| sources.append("- [" + (title or domain or uri) + "](" + uri + ")") | |
| for sup in meta.get("groundingSupports", []): | |
| seg = sup.get("segment", {}) | |
| text = seg.get("text", "") | |
| indices = sup.get("groundingChunkIndices", []) | |
| if text and indices: | |
| refs = [chunks[i].get("web", {}).get("title", chunks[i].get("web", {}).get("domain", "[" + str(i) + "]")) for i in indices if i < len(chunks)] | |
| supports.append("- \"" + text + "\" -- " + ", ".join(refs)) | |
| output = "\n".join(parts) | |
| if search_queries: output += "\n\n## Search Queries\n" + "\n".join("- " + q for q in search_queries) | |
| if sources: output += "\n\n## Sources\n" + "\n".join(sources) | |
| if supports: output += "\n\n## Grounding Details\n" + "\n".join(supports) | |
| rpc_id = '"'"'${id}'"'"' | |
| try: rpc_id = json.loads(rpc_id) | |
| except: pass | |
| print(json.dumps({"jsonrpc": "2.0", "id": rpc_id, "result": {"content": [{"type": "text", "text": output}]}})) | |
| ') | |
| send_response "$result" | |
| } | |
| while IFS= read -r line; do | |
| [ -z "$line" ] && continue | |
| method=$(printf '%s' "$line" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('method',''))") | |
| id=$(printf '%s' "$line" | python3 -c "import sys,json; d=json.load(sys.stdin); print(json.dumps(d.get('id')))") | |
| case "$method" in | |
| initialize) handle_initialize "$id" ;; | |
| notifications/initialized) ;; | |
| tools/list) handle_tools_list "$id" ;; | |
| tools/call) | |
| params=$(printf '%s' "$line" | python3 -c "import sys,json; print(json.dumps(json.load(sys.stdin).get('params',{})))") | |
| handle_tools_call "$id" "$params" ;; | |
| *) [ "$id" != "null" ] && send_error "$id" -32601 "Method not found: ${method}" ;; | |
| esac | |
| done | |
| MCPEOF | |
| chmod +x "$CLAUDE_DIR/mcp-servers/google-search.sh" | |
| echo "Claude Code configured successfully." | |
| echo "Just run: claude" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment