Skip to content

Commit d9a1404

Browse files
committed
udpate claude installer script
1 parent 5bc4e22 commit d9a1404

File tree

2 files changed

+223
-38
lines changed

2 files changed

+223
-38
lines changed

install_claude.py

Lines changed: 212 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
11
#!/usr/bin/env python3
22
"""
33
Auto-installer for Google Workspace MCP in Claude Desktop
4+
Enhanced version with OAuth configuration and installation options
45
"""
56

67
import json
78
import os
89
import platform
910
import sys
1011
from pathlib import Path
12+
from typing import Dict, Optional, Tuple
1113

1214

13-
def get_claude_config_path():
15+
def get_claude_config_path() -> Path:
1416
"""Get the Claude Desktop config file path for the current platform."""
1517
system = platform.system()
1618
if system == "Darwin": # macOS
@@ -24,44 +26,227 @@ def get_claude_config_path():
2426
raise RuntimeError(f"Unsupported platform: {system}")
2527

2628

29+
def prompt_yes_no(question: str, default: bool = True) -> bool:
30+
"""Prompt user for yes/no question."""
31+
default_str = "Y/n" if default else "y/N"
32+
while True:
33+
response = input(f"{question} [{default_str}]: ").strip().lower()
34+
if not response:
35+
return default
36+
if response in ['y', 'yes']:
37+
return True
38+
if response in ['n', 'no']:
39+
return False
40+
print("Please answer 'y' or 'n'")
41+
42+
43+
def get_oauth_credentials() -> Tuple[Optional[Dict[str, str]], Optional[str]]:
44+
"""Get OAuth credentials from user."""
45+
print("\n🔑 OAuth Credentials Setup")
46+
print("You need Google OAuth 2.0 credentials to use this server.")
47+
print("\nYou can provide credentials in two ways:")
48+
print("1. Environment variables (recommended for production)")
49+
print("2. Client secrets JSON file")
50+
51+
use_env = prompt_yes_no("\nDo you want to use environment variables?", default=True)
52+
53+
env_vars = {}
54+
client_secret_path = None
55+
56+
if use_env:
57+
print("\n📝 Enter your OAuth credentials:")
58+
client_id = input("Client ID (ends with .apps.googleusercontent.com): ").strip()
59+
client_secret = input("Client Secret: ").strip()
60+
61+
if not client_id or not client_secret:
62+
print("❌ Both Client ID and Client Secret are required!")
63+
return None, None
64+
65+
env_vars["GOOGLE_OAUTH_CLIENT_ID"] = client_id
66+
env_vars["GOOGLE_OAUTH_CLIENT_SECRET"] = client_secret
67+
68+
# Optional redirect URI
69+
custom_redirect = input("Redirect URI (press Enter for default http://localhost:8000/oauth2callback): ").strip()
70+
if custom_redirect:
71+
env_vars["GOOGLE_OAUTH_REDIRECT_URI"] = custom_redirect
72+
73+
else:
74+
print("\n📁 Client secrets file setup:")
75+
default_path = "client_secret.json"
76+
file_path = input(f"Path to client_secret.json file [{default_path}]: ").strip()
77+
78+
if not file_path:
79+
file_path = default_path
80+
81+
# Check if file exists
82+
if not Path(file_path).exists():
83+
print(f"❌ File not found: {file_path}")
84+
return None, None
85+
86+
client_secret_path = file_path
87+
88+
# Optional: Default user email
89+
print("\n📧 Optional: Default user email (for single-user setups)")
90+
user_email = input("Your Google email (press Enter to skip): ").strip()
91+
if user_email:
92+
env_vars["USER_GOOGLE_EMAIL"] = user_email
93+
94+
# Development mode
95+
if prompt_yes_no("\n🔧 Enable development mode (OAUTHLIB_INSECURE_TRANSPORT)?", default=False):
96+
env_vars["OAUTHLIB_INSECURE_TRANSPORT"] = "1"
97+
98+
return env_vars, client_secret_path
99+
100+
101+
def get_installation_options() -> Dict[str, any]:
102+
"""Get installation options from user."""
103+
options = {}
104+
105+
print("\n⚙️ Installation Options")
106+
107+
# Installation method
108+
print("\nChoose installation method:")
109+
print("1. uvx (recommended - auto-installs from PyPI)")
110+
print("2. Development mode (requires local repository)")
111+
112+
method = input("Select method [1]: ").strip()
113+
if method == "2":
114+
options["dev_mode"] = True
115+
cwd = input("Path to google_workspace_mcp repository [current directory]: ").strip()
116+
options["cwd"] = cwd if cwd else os.getcwd()
117+
else:
118+
options["dev_mode"] = False
119+
120+
# Single-user mode
121+
if prompt_yes_no("\n👤 Enable single-user mode (simplified authentication)?", default=False):
122+
options["single_user"] = True
123+
124+
# Tool selection
125+
print("\n🛠️ Tool Selection")
126+
print("Available tools: gmail, drive, calendar, docs, sheets, forms, chat")
127+
print("Leave empty to enable all tools")
128+
tools = input("Enter tools to enable (comma-separated): ").strip()
129+
if tools:
130+
options["tools"] = [t.strip() for t in tools.split(",")]
131+
132+
# Transport mode
133+
if prompt_yes_no("\n🌐 Use HTTP transport mode (for debugging)?", default=False):
134+
options["http_mode"] = True
135+
136+
return options
137+
138+
139+
def create_server_config(options: Dict, env_vars: Dict, client_secret_path: Optional[str]) -> Dict:
140+
"""Create the server configuration."""
141+
config = {}
142+
143+
if options.get("dev_mode"):
144+
config["command"] = "uv"
145+
config["args"] = ["run", "main.py"]
146+
config["cwd"] = options["cwd"]
147+
else:
148+
config["command"] = "uvx"
149+
config["args"] = ["workspace-mcp"]
150+
151+
# Add command line arguments
152+
if options.get("single_user"):
153+
config["args"].append("--single-user")
154+
155+
if options.get("tools"):
156+
config["args"].extend(["--tools"] + options["tools"])
157+
158+
if options.get("http_mode"):
159+
config["args"].extend(["--transport", "streamable-http"])
160+
161+
# Add environment variables
162+
if env_vars or client_secret_path:
163+
config["env"] = {}
164+
165+
if env_vars:
166+
config["env"].update(env_vars)
167+
168+
if client_secret_path:
169+
config["env"]["GOOGLE_CLIENT_SECRET_PATH"] = client_secret_path
170+
171+
return config
172+
173+
27174
def main():
175+
print("🚀 Google Workspace MCP Installer for Claude Desktop")
176+
print("=" * 50)
177+
28178
try:
29179
config_path = get_claude_config_path()
30-
31-
# Create directory if it doesn't exist
32-
config_path.parent.mkdir(parents=True, exist_ok=True)
33-
34-
# Load existing config or create new one
180+
181+
# Check if config already exists
182+
existing_config = {}
35183
if config_path.exists():
36184
with open(config_path, 'r') as f:
37-
config = json.load(f)
38-
else:
39-
config = {}
40-
41-
# Ensure mcpServers section exists
42-
if "mcpServers" not in config:
43-
config["mcpServers"] = {}
44-
45-
# Add Google Workspace MCP server
46-
config["mcpServers"]["google_workspace"] = {
47-
"command": "uvx",
48-
"args": ["workspace-mcp"]
49-
}
50-
51-
# Write updated config
185+
existing_config = json.load(f)
186+
187+
if "mcpServers" in existing_config and "google_workspace" in existing_config["mcpServers"]:
188+
print(f"\n⚠️ Google Workspace MCP is already configured in {config_path}")
189+
if not prompt_yes_no("Do you want to reconfigure it?", default=True):
190+
print("Installation cancelled.")
191+
return
192+
193+
# Get OAuth credentials
194+
env_vars, client_secret_path = get_oauth_credentials()
195+
if env_vars is None and client_secret_path is None:
196+
print("\n❌ OAuth credentials are required. Installation cancelled.")
197+
sys.exit(1)
198+
199+
# Get installation options
200+
options = get_installation_options()
201+
202+
# Create server configuration
203+
server_config = create_server_config(options, env_vars, client_secret_path)
204+
205+
# Prepare final config
206+
if "mcpServers" not in existing_config:
207+
existing_config["mcpServers"] = {}
208+
209+
existing_config["mcpServers"]["google_workspace"] = server_config
210+
211+
# Create directory if needed
212+
config_path.parent.mkdir(parents=True, exist_ok=True)
213+
214+
# Write configuration
52215
with open(config_path, 'w') as f:
53-
json.dump(config, f, indent=2)
54-
55-
print(f"✅ Successfully added Google Workspace MCP to Claude Desktop config!")
216+
json.dump(existing_config, f, indent=2)
217+
218+
print(f"\n✅ Successfully configured Google Workspace MCP!")
56219
print(f"📁 Config file: {config_path}")
220+
221+
print("\n📋 Configuration Summary:")
222+
print(f" • Installation method: {'Development' if options.get('dev_mode') else 'uvx (PyPI)'}")
223+
print(f" • Authentication: {'Environment variables' if env_vars else 'Client secrets file'}")
224+
if options.get("single_user"):
225+
print(" • Single-user mode: Enabled")
226+
if options.get("tools"):
227+
print(f" • Tools: {', '.join(options['tools'])}")
228+
else:
229+
print(" • Tools: All enabled")
230+
if options.get("http_mode"):
231+
print(" • Transport: HTTP mode")
232+
else:
233+
print(" • Transport: stdio (default)")
234+
57235
print("\n🚀 Next steps:")
58236
print("1. Restart Claude Desktop")
59237
print("2. The Google Workspace tools will be available in your chats!")
60238
print("\n💡 The server will start automatically when Claude Desktop needs it.")
61-
print(" No need to manually start the server - uvx handles everything!")
62-
239+
240+
if options.get("http_mode"):
241+
print("\n⚠️ Note: HTTP mode requires additional setup.")
242+
print(" You may need to install and configure mcp-remote.")
243+
print(" See the README for details.")
244+
245+
except KeyboardInterrupt:
246+
print("\n\nInstallation cancelled by user.")
247+
sys.exit(0)
63248
except Exception as e:
64-
print(f"❌ Error: {e}")
249+
print(f"\n❌ Error: {e}")
65250
print("\n📋 Manual installation:")
66251
print("1. Open Claude Desktop Settings → Developer → Edit Config")
67252
print("2. Add the server configuration shown in the README")

main.py

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,9 @@
3535

3636
def safe_print(text):
3737
try:
38-
print(text)
38+
print(text, file=sys.stderr)
3939
except UnicodeEncodeError:
40-
print(text.encode('ascii', errors='replace').decode())
40+
print(text.encode('ascii', errors='replace').decode(), file=sys.stderr)
4141

4242
def main():
4343
"""
@@ -73,7 +73,7 @@ def main():
7373
safe_print(f" 🔐 OAuth Callback: {base_uri}:{port}/oauth2callback")
7474
safe_print(f" 👤 Mode: {'Single-user' if args.single_user else 'Multi-user'}")
7575
safe_print(f" 🐍 Python: {sys.version.split()[0]}")
76-
print()
76+
print(file=sys.stderr)
7777

7878
# Import tool modules to register them with the MCP server via decorators
7979
tool_imports = {
@@ -104,29 +104,29 @@ def main():
104104
for tool in tools_to_import:
105105
tool_imports[tool]()
106106
safe_print(f" {tool_icons[tool]} {tool.title()} - Google {tool.title()} API integration")
107-
print()
107+
print(file=sys.stderr)
108108

109109
safe_print(f"📊 Configuration Summary:")
110110
safe_print(f" 🔧 Tools Enabled: {len(tools_to_import)}/{len(tool_imports)}")
111111
safe_print(f" 🔑 Auth Method: OAuth 2.0 with PKCE")
112112
safe_print(f" 📝 Log Level: {logging.getLogger().getEffectiveLevel()}")
113-
print()
113+
print(file=sys.stderr)
114114

115115
# Set global single-user mode flag
116116
if args.single_user:
117117
os.environ['MCP_SINGLE_USER_MODE'] = '1'
118118
safe_print("🔐 Single-user mode enabled")
119-
print()
119+
print(file=sys.stderr)
120120

121121
# Check credentials directory permissions before starting
122122
try:
123123
safe_print("🔍 Checking credentials directory permissions...")
124124
check_credentials_directory_permissions()
125125
safe_print("✅ Credentials directory permissions verified")
126-
print()
126+
print(file=sys.stderr)
127127
except (PermissionError, OSError) as e:
128128
safe_print(f"❌ Credentials directory permission check failed: {e}")
129-
print(" Please ensure the service has write permissions to create/access the .credentials directory")
129+
print(" Please ensure the service has write permissions to create/access the .credentials directory", file=sys.stderr)
130130
logger.error(f"Failed credentials directory permission check: {e}")
131131
sys.exit(1)
132132

@@ -141,12 +141,12 @@ def main():
141141
# Start minimal OAuth callback server for stdio mode
142142
from auth.oauth_callback_server import ensure_oauth_callback_available
143143
if ensure_oauth_callback_available('stdio', port, base_uri):
144-
print(f" OAuth callback server started on {base_uri}:{port}/oauth2callback")
144+
print(f" OAuth callback server started on {base_uri}:{port}/oauth2callback", file=sys.stderr)
145145
else:
146146
safe_print(" ⚠️ Warning: Failed to start OAuth callback server")
147147

148-
print(" Ready for MCP connections!")
149-
print()
148+
print(" Ready for MCP connections!", file=sys.stderr)
149+
print(file=sys.stderr)
150150

151151
if args.transport == 'streamable-http':
152152
# The server is already configured with port and server_url in core/server.py

0 commit comments

Comments
 (0)