phone-a-friend-mcp-server 0.1.2__py3-none-any.whl → 0.3.0rc1__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.
@@ -74,6 +74,7 @@ class PhoneAFriendConfig:
74
74
  """Get default temperature for specific models that benefit from it."""
75
75
  default_temperatures = {
76
76
  "gemini-2.5-pro-preview-06-05": 0.0,
77
+ "gemini-2.5-pro": 0.0,
77
78
  }
78
79
 
79
80
  return default_temperatures.get(self.model)
@@ -4,6 +4,7 @@ from typing import Any
4
4
  import aiofiles
5
5
 
6
6
  from phone_a_friend_mcp_server.tools.base_tools import BaseTool
7
+ from phone_a_friend_mcp_server.utils.context_builder import build_code_context
7
8
 
8
9
 
9
10
  class FaxAFriendTool(BaseTool):
@@ -35,9 +36,10 @@ refactors, design, migrations.
35
36
 
36
37
  This tool creates a file for manual AI consultation. After file creation,
37
38
  wait for the user to return with the external AI's response.
39
+ Replies must be exhaustively detailed. Do **NOT** include files ignored by .gitignore (e.g., *.pyc).
38
40
 
39
41
  Hard restrictions:
40
- • Generated prompt includes *only* the two context blocks you send.
42
+ • Generated prompt includes *only* the context you provide.
41
43
  • No memory, no internet, no tools.
42
44
  • You must spell out every fact it should rely on.
43
45
 
@@ -83,22 +85,18 @@ replacing <file="…"> blocks as needed. Commentary goes outside those tags."""
83
85
  "all_related_context": {
84
86
  "type": "string",
85
87
  "description": (
86
- "MANDATORY. Everything the friend AI needs to see:\n"
87
- "- The full <file_tree> block (ASCII tree).\n"
88
- '- One or more <file="…"> blocks with the current code.\n'
89
- "- Known constraints (Python version, allowed deps, runtime limits, etc.).\n"
90
- "- Any failing test output or traceback.\n"
91
- "If it's not here, the friend AI can't use it."
88
+ "General context for the friend AI. Include known constraints "
89
+ "(Python version, allowed deps, etc.), failing test output, tracebacks, "
90
+ "or code snippets for reference. For complete files, use file_list instead."
92
91
  ),
93
92
  },
94
- "any_additional_context": {
95
- "type": "string",
93
+ "file_list": {
94
+ "type": "array",
95
+ "items": {"type": "string"},
96
96
  "description": (
97
- "Optional extras that help but aren't core code:\n"
98
- "- Style guides, architecture docs, API specs.\n"
99
- "- Performance targets, security rules, deployment notes.\n"
100
- "- Similar past solutions or reference snippets.\n"
101
- "Skip it if there's nothing useful."
97
+ "Optional but recommended. A list of file paths or glob patterns to be included in the code context. "
98
+ "The tool will automatically read these files, filter them against .gitignore, and build the context. "
99
+ "Better and faster than including complete files in all_related_context."
102
100
  ),
103
101
  },
104
102
  "task": {
@@ -127,24 +125,21 @@ replacing <file="…"> blocks as needed. Commentary goes outside those tags."""
127
125
 
128
126
  async def run(self, **kwargs) -> dict[str, Any]:
129
127
  all_related_context = kwargs.get("all_related_context", "")
130
- any_additional_context = kwargs.get("any_additional_context", "")
128
+ file_list = kwargs.get("file_list", [])
131
129
  task = kwargs.get("task", "")
132
130
  output_directory = kwargs.get("output_directory", "")
133
131
 
134
- # Create master prompt using the same logic as phone_a_friend
135
- master_prompt = self._create_master_prompt(all_related_context, any_additional_context, task)
132
+ code_context = build_code_context(file_list)
133
+ master_prompt = self._create_master_prompt(all_related_context, code_context, task)
136
134
 
137
135
  try:
138
- # Validate and prepare output directory
139
136
  output_dir = self._prepare_output_directory(output_directory)
140
137
 
141
- # Create full file path
142
138
  file_path = os.path.join(output_dir, "fax_a_friend.md")
143
139
 
144
140
  async with aiofiles.open(file_path, "w", encoding="utf-8") as f:
145
141
  await f.write(master_prompt)
146
142
 
147
- # Get absolute path for user reference
148
143
  abs_path = os.path.abspath(file_path)
149
144
 
150
145
  return {
@@ -153,15 +148,15 @@ replacing <file="…"> blocks as needed. Commentary goes outside those tags."""
153
148
  "file_name": "fax_a_friend.md",
154
149
  "output_directory": output_dir,
155
150
  "prompt_length": len(master_prompt),
156
- "context_length": len(all_related_context + any_additional_context),
151
+ "context_length": len(master_prompt),
157
152
  "task": task,
158
153
  "instructions": self._get_manual_workflow_instructions(abs_path),
159
154
  }
160
155
 
161
156
  except Exception as e:
162
- return {"status": "failed", "error": str(e), "output_directory": output_directory, "context_length": len(all_related_context + any_additional_context), "task": task}
157
+ return {"status": "failed", "error": str(e), "output_directory": output_directory, "context_length": len(master_prompt), "task": task}
163
158
 
164
- def _create_master_prompt(self, all_related_context: str, any_additional_context: str, task: str) -> str:
159
+ def _create_master_prompt(self, all_related_context: str, code_context: str, task: str) -> str:
165
160
  """Create a comprehensive prompt identical to PhoneAFriendTool's version."""
166
161
 
167
162
  prompt_parts = [
@@ -171,23 +166,19 @@ replacing <file="…"> blocks as needed. Commentary goes outside those tags."""
171
166
  "=== TASK ===",
172
167
  task,
173
168
  "",
174
- "=== ALL RELATED CONTEXT ===",
169
+ "=== GENERAL CONTEXT ===",
175
170
  all_related_context,
171
+ "",
172
+ "=== CODE CONTEXT ===",
173
+ code_context,
176
174
  ]
177
175
 
178
- if any_additional_context.strip():
179
- prompt_parts.extend(
180
- [
181
- "",
182
- "=== ADDITIONAL CONTEXT ===",
183
- any_additional_context,
184
- ]
185
- )
186
-
187
176
  prompt_parts.extend(
188
177
  [
189
178
  "",
190
179
  "=== INSTRUCTIONS ===",
180
+ "- Provide exhaustive, step-by-step reasoning.",
181
+ "- Never include files matching .gitignore patterns.",
191
182
  "- Analyze the code and requirements step-by-step.",
192
183
  "- Show your reasoning and propose concrete changes.",
193
184
  '- Provide updated code using the XML format (<file_tree> plus <file="…"> blocks).',
@@ -204,17 +195,14 @@ replacing <file="…"> blocks as needed. Commentary goes outside those tags."""
204
195
  if not output_directory:
205
196
  raise ValueError("output_directory parameter is required")
206
197
 
207
- # Expand user path (~) and resolve relative paths
208
198
  expanded_path = os.path.expanduser(output_directory)
209
199
  resolved_path = os.path.abspath(expanded_path)
210
200
 
211
- # Create directory if it doesn't exist
212
201
  try:
213
202
  os.makedirs(resolved_path, exist_ok=True)
214
203
  except OSError as e:
215
204
  raise ValueError(f"Cannot create directory '{resolved_path}': {e}")
216
205
 
217
- # Check if directory is writable
218
206
  if not os.access(resolved_path, os.W_OK):
219
207
  raise ValueError(f"Directory '{resolved_path}' is not writable")
220
208
 
@@ -10,6 +10,7 @@ from pydantic_ai.providers.openai import OpenAIProvider
10
10
  from pydantic_ai.providers.openrouter import OpenRouterProvider
11
11
 
12
12
  from phone_a_friend_mcp_server.tools.base_tools import BaseTool
13
+ from phone_a_friend_mcp_server.utils.context_builder import build_code_context
13
14
 
14
15
 
15
16
  class PhoneAFriendTool(BaseTool):
@@ -39,9 +40,10 @@ Purpose: pair-programming caliber *coding help* — reviews, debugging,
39
40
  refactors, design, migrations.
40
41
 
41
42
  Hard restrictions:
42
- • Friend AI sees *only* the two context blocks you send.
43
+ • Friend AI sees *only* the context you provide.
43
44
  • No memory, no internet, no tools.
44
45
  • You must spell out every fact it should rely on.
46
+ Replies must be exhaustively detailed. Do **NOT** include files ignored by .gitignore (e.g., *.pyc).
45
47
 
46
48
  Required I/O format:
47
49
  ```
@@ -85,22 +87,18 @@ replacing <file="…"> blocks as needed. Commentary goes outside those tags."""
85
87
  "all_related_context": {
86
88
  "type": "string",
87
89
  "description": (
88
- "MANDATORY. Everything the friend AI needs to see:\n"
89
- "- The full <file_tree> block (ASCII tree).\n"
90
- '- One or more <file="…"> blocks with the current code.\n'
91
- "- Known constraints (Python version, allowed deps, runtime limits, etc.).\n"
92
- "- Any failing test output or traceback.\n"
93
- "If it's not here, the friend AI can't use it."
90
+ "General context for the friend AI. Include known constraints "
91
+ "(Python version, allowed deps, etc.), failing test output, tracebacks, "
92
+ "or code snippets for reference. For complete files, use file_list instead."
94
93
  ),
95
94
  },
96
- "any_additional_context": {
97
- "type": "string",
95
+ "file_list": {
96
+ "type": "array",
97
+ "items": {"type": "string"},
98
98
  "description": (
99
- "Optional extras that help but aren't core code:\n"
100
- "- Style guides, architecture docs, API specs.\n"
101
- "- Performance targets, security rules, deployment notes.\n"
102
- "- Similar past solutions or reference snippets.\n"
103
- "Skip it if there's nothing useful."
99
+ "Optional but recommended. A list of file paths or glob patterns to be included in the code context. "
100
+ "The tool will automatically read these files, filter them against .gitignore, and build the context. "
101
+ "Better and faster than including complete files in all_related_context."
104
102
  ),
105
103
  },
106
104
  "task": {
@@ -120,10 +118,11 @@ replacing <file="…"> blocks as needed. Commentary goes outside those tags."""
120
118
 
121
119
  async def run(self, **kwargs) -> dict[str, Any]:
122
120
  all_related_context = kwargs.get("all_related_context", "")
123
- any_additional_context = kwargs.get("any_additional_context", "")
121
+ file_list = kwargs.get("file_list", [])
124
122
  task = kwargs.get("task", "")
125
123
 
126
- master_prompt = self._create_master_prompt(all_related_context, any_additional_context, task)
124
+ code_context = build_code_context(file_list)
125
+ master_prompt = self._create_master_prompt(all_related_context, code_context, task)
127
126
 
128
127
  try:
129
128
  agent = self._create_agent()
@@ -140,7 +139,7 @@ replacing <file="…"> blocks as needed. Commentary goes outside those tags."""
140
139
  "provider": self.config.provider,
141
140
  "model": self.config.model,
142
141
  "temperature": temperature,
143
- "context_length": len(all_related_context + any_additional_context),
142
+ "context_length": len(master_prompt),
144
143
  "task": task,
145
144
  }
146
145
 
@@ -152,7 +151,7 @@ replacing <file="…"> blocks as needed. Commentary goes outside those tags."""
152
151
  "provider": self.config.provider,
153
152
  "model": self.config.model,
154
153
  "temperature": temperature,
155
- "context_length": len(all_related_context + any_additional_context),
154
+ "context_length": len(master_prompt),
156
155
  "task": task,
157
156
  "master_prompt": master_prompt,
158
157
  }
@@ -182,7 +181,7 @@ replacing <file="…"> blocks as needed. Commentary goes outside those tags."""
182
181
 
183
182
  return Agent(model)
184
183
 
185
- def _create_master_prompt(self, all_related_context: str, any_additional_context: str, task: str) -> str:
184
+ def _create_master_prompt(self, all_related_context: str, code_context: str, task: str) -> str:
186
185
  """Create a comprehensive prompt for the external AI."""
187
186
 
188
187
  prompt_parts = [
@@ -192,23 +191,19 @@ replacing <file="…"> blocks as needed. Commentary goes outside those tags."""
192
191
  "=== TASK ===",
193
192
  task,
194
193
  "",
195
- "=== ALL RELATED CONTEXT ===",
194
+ "=== GENERAL CONTEXT ===",
196
195
  all_related_context,
196
+ "",
197
+ "=== CODE CONTEXT ===",
198
+ code_context,
197
199
  ]
198
200
 
199
- if any_additional_context.strip():
200
- prompt_parts.extend(
201
- [
202
- "",
203
- "=== ADDITIONAL CONTEXT ===",
204
- any_additional_context,
205
- ]
206
- )
207
-
208
201
  prompt_parts.extend(
209
202
  [
210
203
  "",
211
204
  "=== INSTRUCTIONS ===",
205
+ "- Provide exhaustive, step-by-step reasoning.",
206
+ "- Never include files matching .gitignore patterns.",
212
207
  "- Analyze the code and requirements step-by-step.",
213
208
  "- Show your reasoning and propose concrete changes.",
214
209
  '- Provide updated code using the XML format (<file_tree> plus <file="…"> blocks).',
@@ -0,0 +1 @@
1
+ # This file makes the `utils` directory a Python package.
@@ -0,0 +1,114 @@
1
+ import glob
2
+ import os
3
+
4
+ import pathspec
5
+
6
+
7
+ def load_gitignore(base_dir: str) -> pathspec.PathSpec:
8
+ """Loads .gitignore patterns from the specified base directory."""
9
+ gitignore_path = os.path.join(base_dir, ".gitignore")
10
+ patterns = []
11
+ if os.path.exists(gitignore_path):
12
+ with open(gitignore_path, encoding="utf-8") as f:
13
+ patterns = f.read().splitlines()
14
+ return pathspec.PathSpec.from_lines("gitwildmatch", patterns)
15
+
16
+
17
+ def get_all_project_files(base_dir: str = ".") -> list[str]:
18
+ """Get all files in the project directory recursively."""
19
+ all_files = []
20
+ for root, dirs, files in os.walk(base_dir):
21
+ # Skip hidden directories like .git, .venv, etc.
22
+ dirs[:] = [d for d in dirs if not d.startswith('.')]
23
+
24
+ for file in files:
25
+ if not file.startswith('.'): # Skip hidden files
26
+ rel_path = os.path.relpath(os.path.join(root, file), base_dir)
27
+ all_files.append(rel_path)
28
+ return sorted(all_files)
29
+
30
+
31
+ def filter_paths(paths: list[str], spec: pathspec.PathSpec, base_dir: str = ".") -> list[str]:
32
+ """Filters out paths that match the .gitignore spec and non-text files."""
33
+ filtered_paths = []
34
+ for path in paths:
35
+ normalized_path = os.path.normpath(os.path.join(base_dir, path))
36
+ if not spec.match_file(normalized_path) and os.path.isfile(normalized_path):
37
+ try:
38
+ with open(normalized_path, encoding="utf-8") as f:
39
+ f.read(1024)
40
+ filtered_paths.append(path)
41
+ except (OSError, UnicodeDecodeError):
42
+ pass
43
+ return filtered_paths
44
+
45
+
46
+ def build_file_tree(paths: list[str], base_dir: str = ".") -> str:
47
+ """Builds an ASCII file tree from a list of paths."""
48
+ tree = {}
49
+ for path in paths:
50
+ parts = path.split(os.sep)
51
+ current_level = tree
52
+ for part in parts:
53
+ if part not in current_level:
54
+ current_level[part] = {}
55
+ current_level = current_level[part]
56
+
57
+ def generate_tree_lines(d, prefix=""):
58
+ lines = []
59
+ entries = sorted(d.keys())
60
+ for i, entry in enumerate(entries):
61
+ connector = "├── " if i < len(entries) - 1 else "└── "
62
+ lines.append(prefix + connector + entry)
63
+ if d[entry]:
64
+ extension = "│ " if i < len(entries) - 1 else " "
65
+ lines.extend(generate_tree_lines(d[entry], prefix + extension))
66
+ return lines
67
+
68
+ tree_lines = [base_dir] + generate_tree_lines(tree)
69
+ return "\n".join(tree_lines)
70
+
71
+
72
+ def build_file_blocks(paths: list[str], base_dir: str = ".") -> str:
73
+ """Creates <file="..." /> blocks with the full source code for each file."""
74
+ blocks = []
75
+ for path in paths:
76
+ full_path = os.path.join(base_dir, path)
77
+ try:
78
+ with open(full_path, encoding="utf-8") as f:
79
+ content = f.read()
80
+ blocks.append(f'<file="{path}">\n{content}\n</file>')
81
+ except (OSError, UnicodeDecodeError) as e:
82
+ blocks.append(f'<file="{path}">\nError reading file: {e}\n</file>')
83
+ return "\n\n".join(blocks)
84
+
85
+
86
+ def build_code_context(file_list: list[str], base_dir: str = ".") -> str:
87
+ """
88
+ Builds the complete code context, including a file tree and file content blocks,
89
+ filtering out ignored and binary files.
90
+ """
91
+ spec = load_gitignore(base_dir)
92
+
93
+ # Get complete project tree (like tree --gitignore)
94
+ all_project_files = get_all_project_files(base_dir)
95
+ filtered_all_files = filter_paths(all_project_files, spec, base_dir)
96
+ complete_tree = build_file_tree(filtered_all_files, base_dir)
97
+
98
+ # Handle selected files from file_list
99
+ if file_list:
100
+ selected_files = []
101
+ for pattern in file_list:
102
+ selected_files.extend(glob.glob(pattern, recursive=True))
103
+
104
+ unique_selected_files = sorted(list(set(selected_files)))
105
+ filtered_selected_files = filter_paths(unique_selected_files, spec, base_dir)
106
+
107
+ if filtered_selected_files:
108
+ file_blocks = build_file_blocks(filtered_selected_files, base_dir)
109
+ return f"<file_tree>\n{complete_tree}\n</file_tree>\n\n{file_blocks}"
110
+ else:
111
+ return f"<file_tree>\n{complete_tree}\n</file_tree>\n\nNo files to display from file_list. Check your patterns and .gitignore."
112
+ else:
113
+ # No file_list provided, just show the tree
114
+ return f"<file_tree>\n{complete_tree}\n</file_tree>\n\nNo specific files selected. Use file_list parameter to include file contents."
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: phone-a-friend-mcp-server
3
- Version: 0.1.2
3
+ Version: 0.3.0rc1
4
4
  Summary: MCP Server for Phone-a-Friend assistance
5
5
  Project-URL: GitHub, https://github.com/abhishekbhakat/phone-a-friend-mcp-server
6
6
  Project-URL: Issues, https://github.com/abhishekbhakat/phone-a-friend-mcp-server/issues
@@ -17,6 +17,7 @@ Requires-Dist: aiofiles>=24.1.0
17
17
  Requires-Dist: aiohttp>=3.12.7
18
18
  Requires-Dist: click>=8.2.1
19
19
  Requires-Dist: mcp>=1.9.2
20
+ Requires-Dist: pathspec>=0.12.1
20
21
  Requires-Dist: pydantic-ai-slim[anthropic,google,openai]>=0.2.14
21
22
  Requires-Dist: pydantic>=2.11.5
22
23
  Requires-Dist: pyyaml>=6.0.0
@@ -46,7 +47,7 @@ This enables AI systems to leverage other AI models as "consultants" for complex
46
47
  ## Architecture 🏗️
47
48
 
48
49
  ```
49
- Primary AI → Phone-a-Friend MCP → OpenRouter → External AI (GPT-4, Claude, etc.) → Processed Response → Primary AI
50
+ Primary AI → Phone-a-Friend MCP → OpenRouter → External AI (O3, Claude, etc.) → Processed Response → Primary AI
50
51
  ```
51
52
 
52
53
  **Sequential Workflow:**
@@ -63,87 +64,105 @@ Primary AI → Phone-a-Friend MCP → OpenRouter → External AI (GPT-4, Claude,
63
64
  - Critical decision-making with high stakes
64
65
  - Problems requiring multiple perspectives
65
66
 
66
- ## Installation 🚀
67
+ ## Quick Start ⚡
67
68
 
68
- 1. Clone the repository:
69
- ```bash
70
- git clone https://github.com/abhishekbhakat/phone-a-friend-mcp-server.git
71
- cd phone-a-friend-mcp-server
72
- ```
69
+ Configure your MCP client (e.g., Claude Desktop) using the JSON block below—no cloning or manual installation required.
70
+ The `uv` runner will automatically download and execute the server package if it isn't present.
73
71
 
74
- 2. Install dependencies:
75
- ```bash
76
- uv pip install -e .
72
+ Add the following JSON configuration to your MCP client and replace `<YOUR_API_KEY>` with your key:
73
+
74
+ ```json
75
+ {
76
+ "mcpServers": {
77
+ "phone-a-friend": {
78
+ "command": "uvx",
79
+ "args": [
80
+ "phone-a-friend-mcp-server",
81
+ "--provider", "openai",
82
+ "--api-key", "<YOUR_API_KEY>"
83
+ ]
84
+ }
85
+ }
86
+ }
77
87
  ```
88
+ > That's it! You can now use the `phone_a_friend` tool in any compatible client. For more options, see the Advanced Configuration section.
78
89
 
79
- 3. Configure API access (choose one method):
90
+ ## Available Tools 🛠️
80
91
 
81
- **Option A: Environment Variables**
82
- ```bash
83
- export OPENROUTER_API_KEY="your-openrouter-key"
84
- # OR
85
- export OPENAI_API_KEY="your-openai-key"
86
- # OR
87
- export ANTHROPIC_API_KEY="your-anthropic-key"
88
- # OR
89
- export GOOGLE_API_KEY="your-google-key"
90
- ```
92
+ ### phone_a_friend
93
+ 📞 Consult external AI for critical thinking and complex reasoning. Makes API calls to get responses.
91
94
 
92
- **Option B: CLI Arguments**
93
- ```bash
94
- phone-a-friend-mcp-server --api-key "your-api-key" --provider openai
95
- ```
95
+ ### fax_a_friend
96
+ 📠 Generate master prompt file for manual AI consultation. Creates file for copy-paste workflow.
96
97
 
97
- ## Usage 💡
98
+ **Parameters**
98
99
 
99
- ### Command Line Options
100
- ```bash
100
+ *phone_a_friend*
101
101
 
102
- # Custom base URL (if needed)
103
- phone-a-friend-mcp-server --base-url "https://custom-api.example.com"
102
+ - `all_related_context` (required): General, non-code context such as constraints, tracebacks, or high-level requirements.
103
+ - `file_list` (required): Array of file paths or glob patterns. **Just pass the paths** – the server automatically reads those files (skips anything in `.gitignore` or non-text/binary) and builds the full code context for the external AI.
104
+ - `task` (required): A clear, specific description of what you want the external AI to do.
104
105
 
105
- # Temperature control (0.0 = deterministic, 2.0 = very creative)
106
- phone-a-friend-mcp-server --temperature 0.4
106
+ *fax_a_friend*
107
107
 
108
- # Combined example
109
- phone-a-friend-mcp-server --api-key "sk-..." --provider openai --model "o3" -v
110
- ```
108
+ - `all_related_context` (required): Same as above.
109
+ - `file_list` (required): Same as above.
110
+ - `task` (required): Same as above.
111
+ - `output_directory` (required): Directory where the generated `fax_a_friend.md` master prompt file will be saved.
111
112
 
112
- ### Environment Variables (Optional)
113
- ```bash
113
+ ## Advanced Configuration 🔧
114
114
 
115
- # Optional model overrides
116
- export PHONE_A_FRIEND_MODEL="your-preferred-model"
117
- export PHONE_A_FRIEND_PROVIDER="your-preferred-provider"
118
- export PHONE_A_FRIEND_BASE_URL="https://custom-api.example.com"
115
+ This section covers all configuration options, including environment variables, CLI flags, and model selection.
119
116
 
120
- # Temperature control (0.0-2.0, where 0.0 = deterministic, 2.0 = very creative)
121
- export PHONE_A_FRIEND_TEMPERATURE=0.4
117
+ ### Providers and API Keys
118
+
119
+ The server can be configured via CLI flags or environment variables.
120
+
121
+ | Provider | CLI Flag | Environment Variable |
122
+ | :--- | :--- | :--- |
123
+ | OpenAI | `--provider openai` | `OPENAI_API_KEY` |
124
+ | OpenRouter | `--provider openrouter` | `OPENROUTER_API_KEY` |
125
+ | Anthropic | `--provider anthropic` | `ANTHROPIC_API_KEY` |
126
+ | Google | `--provider google` | `GOOGLE_API_KEY` |
127
+
128
+ **CLI Example:**
129
+ ```bash
130
+ phone-a-friend-mcp-server --provider openai --api-key "sk-..."
122
131
  ```
123
132
 
124
- ## Model Selection 🤖
133
+ **Environment Variable Example:**
134
+ ```bash
135
+ export OPENAI_API_KEY="sk-..."
136
+ phone-a-friend-mcp-server
137
+ ```
125
138
 
126
- Default reasoning models to be selected:
127
- - **OpenAI**: o3
128
- - **Anthropic**: Claude 4 Opus
129
- - **Google**: Gemini 2.5 Pro Preview 05-06 (automatically set temperature to 0.0)
130
- - **OpenRouter**: For other models like Deepseek or Qwen
139
+ ### Model Selection
131
140
 
132
- You can override the auto-selection by setting `PHONE_A_FRIEND_MODEL` environment variable or using the `--model` CLI option.
141
+ You can override the default model for each provider.
133
142
 
134
- ## Available Tools 🛠️
143
+ | Provider | Default Model |
144
+ | :--- | :--- |
145
+ | **OpenAI** | `o3` |
146
+ | **Anthropic** | `Claude 4 Opus` |
147
+ | **Google** | `Gemini 2.5 Pro Preview 05-06` |
148
+ | **OpenRouter**| `anthropic/claude-4-opus` |
135
149
 
136
- ### phone_a_friend
137
- 📞 Consult external AI for critical thinking and complex reasoning. Makes API calls to get responses.
150
+ **Override with CLI:**
151
+ ```bash
152
+ phone-a-friend-mcp-server --model "o3"
153
+ ```
138
154
 
139
- ### fax_a_friend
140
- 📠 Generate master prompt file for manual AI consultation. Creates file for copy-paste workflow.
155
+ **Override with Environment Variable:**
156
+ ```bash
157
+ export PHONE_A_FRIEND_MODEL="o3"
158
+ ```
141
159
 
142
- **Parameters (both tools):**
143
- - `all_related_context` (required): All context related to the problem
144
- - `any_additional_context` (optional): Additional helpful context
145
- - `task` (required): Specific task or question for the AI
160
+ ### Additional Options
146
161
 
162
+ | Feature | CLI Flag | Environment Variable | Default |
163
+ | :--- | :--- | :--- | :--- |
164
+ | **Temperature** | `--temperature 0.5` | `PHONE_A_FRIEND_TEMPERATURE` | `0.4` |
165
+ | **Base URL** | `--base-url ...` | `PHONE_A_FRIEND_BASE_URL` | Provider default |
147
166
 
148
167
  ## Use Cases 🎯
149
168
 
@@ -151,55 +170,6 @@ You can override the auto-selection by setting `PHONE_A_FRIEND_MODEL` environmen
151
170
  2. For complex algorithms, data structures, or mathematical computations
152
171
  3. Frontend Development with React, Vue, CSS, or modern frontend frameworks
153
172
 
154
- ## Claude Desktop Configuration 🖥️
155
-
156
- To use Phone-a-Friend MCP server with Claude Desktop, add this configuration to your `claude_desktop_config.json` file:
157
-
158
- ### Configuration File Location
159
- - **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
160
- - **Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
161
-
162
- ### Configuration
163
-
164
- **Option 1: Using uv (Recommended)**
165
- ```json
166
- {
167
- "mcpServers": {
168
- "phone-a-friend": {
169
- "command": "uvx",
170
- "args": [
171
- "--refresh",
172
- "phone-a-friend-mcp-server",
173
- ],
174
- "env": {
175
- "OPENROUTER_API_KEY": "your-openrouter-api-key",
176
- "PHONE_A_FRIEND_MODEL": "anthropic/claude-4-opus",
177
- "PHONE_A_FRIEND_TEMPERATURE": "0.4"
178
- }
179
- }
180
- }
181
- }
182
- ```
183
-
184
- ### Environment Variables in Configuration
185
-
186
- You can configure different AI providers directly in the Claude Desktop config:
187
-
188
- ```json
189
- {
190
- "mcpServers": {
191
- "phone-a-friend": {
192
- "command": "phone-a-friend-mcp-server",
193
- "env": {
194
- "OPENROUTER_API_KEY": "your-openrouter-api-key",
195
- "PHONE_A_FRIEND_MODEL": "anthropic/claude-4-opus",
196
- "PHONE_A_FRIEND_TEMPERATURE": "0.4"
197
- }
198
- }
199
- }
200
- }
201
- ```
202
-
203
173
  ## License 📄
204
174
 
205
175
  MIT License - see LICENSE file for details.
@@ -0,0 +1,17 @@
1
+ phone_a_friend_mcp_server/__init__.py,sha256=9sn_dPrIzLz4W7_Ww--o8aUxIhUI3YGNrxPa26pKShw,2025
2
+ phone_a_friend_mcp_server/__main__.py,sha256=A-8-jkY2FK2foabew5I-Wk2A54IwzWZcydlQKfiR-p4,51
3
+ phone_a_friend_mcp_server/config.py,sha256=AP8ZU5VEJjr73QUNAoPKidAhO2O_WOGO9mmj7-rTAz0,3988
4
+ phone_a_friend_mcp_server/server.py,sha256=z-O20j-j2oHFfFK8o0u9kn-MR8Q-Te0lRZOQfLkYUbM,3448
5
+ phone_a_friend_mcp_server/client/__init__.py,sha256=fsa8DXjz4rzYXmOUAdLdTpTwPSlZ3zobmBGXqnCEaWs,47
6
+ phone_a_friend_mcp_server/tools/__init__.py,sha256=jtuvmcStXzbaM8wuhOKC8M8mBqDjHr-ypZ2ct1Rgi7Q,46
7
+ phone_a_friend_mcp_server/tools/base_tools.py,sha256=DMjFq0E3TO9a9I7QY4wQ_B4-SntdXzSZzrYymFzSmVE,765
8
+ phone_a_friend_mcp_server/tools/fax_tool.py,sha256=muihVHXqLrP-H5TU8Ns2Civ_EOay_wuZO7y2Pcge5n8,8953
9
+ phone_a_friend_mcp_server/tools/phone_tool.py,sha256=X8MGkTxqAbo7RlaEYBdqGvfVVxhrbZ75RNnRwN_QK-s,8513
10
+ phone_a_friend_mcp_server/tools/tool_manager.py,sha256=VVtENC-n3D4GV6Cy3l9--30SJi06mJdyEiG7F_mfP7I,1474
11
+ phone_a_friend_mcp_server/utils/__init__.py,sha256=1IwFDtwJ76i1O7_iM4LLGqwgtt11y0PIV0DWubh8nLU,58
12
+ phone_a_friend_mcp_server/utils/context_builder.py,sha256=Fc1ej3disKnCrHbyYUInkHygOUr93izOyhFz3reP5gE,4580
13
+ phone_a_friend_mcp_server-0.3.0rc1.dist-info/METADATA,sha256=9i3VyjgZbDWTFOAhDocfSMIiAZCQG2Ec_uB_YCsTxts,6030
14
+ phone_a_friend_mcp_server-0.3.0rc1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
15
+ phone_a_friend_mcp_server-0.3.0rc1.dist-info/entry_points.txt,sha256=c_08XI-vG07VmUT3mtzyuCQjaus5l1NBl4q00Q3jLug,86
16
+ phone_a_friend_mcp_server-0.3.0rc1.dist-info/licenses/LICENSE,sha256=-8bInetillKZC0qZDT8RWYIOrph3HIU5cr5N4Pg7bBE,1065
17
+ phone_a_friend_mcp_server-0.3.0rc1.dist-info/RECORD,,
@@ -1,15 +0,0 @@
1
- phone_a_friend_mcp_server/__init__.py,sha256=9sn_dPrIzLz4W7_Ww--o8aUxIhUI3YGNrxPa26pKShw,2025
2
- phone_a_friend_mcp_server/__main__.py,sha256=A-8-jkY2FK2foabew5I-Wk2A54IwzWZcydlQKfiR-p4,51
3
- phone_a_friend_mcp_server/config.py,sha256=McHqEzIVhSXpfmNLrCRlrFckRLzjxN5LESkqAXX6c4o,3953
4
- phone_a_friend_mcp_server/server.py,sha256=z-O20j-j2oHFfFK8o0u9kn-MR8Q-Te0lRZOQfLkYUbM,3448
5
- phone_a_friend_mcp_server/client/__init__.py,sha256=fsa8DXjz4rzYXmOUAdLdTpTwPSlZ3zobmBGXqnCEaWs,47
6
- phone_a_friend_mcp_server/tools/__init__.py,sha256=jtuvmcStXzbaM8wuhOKC8M8mBqDjHr-ypZ2ct1Rgi7Q,46
7
- phone_a_friend_mcp_server/tools/base_tools.py,sha256=DMjFq0E3TO9a9I7QY4wQ_B4-SntdXzSZzrYymFzSmVE,765
8
- phone_a_friend_mcp_server/tools/fax_tool.py,sha256=qPKTDG1voXo_6MjgkMSDLN8AKYfal6sagK1l2BRPgCw,9399
9
- phone_a_friend_mcp_server/tools/phone_tool.py,sha256=fPgYE8iRc9xFCQc07J5Zh4RPIbgYaiE6WygTEo6oUbs,8604
10
- phone_a_friend_mcp_server/tools/tool_manager.py,sha256=VVtENC-n3D4GV6Cy3l9--30SJi06mJdyEiG7F_mfP7I,1474
11
- phone_a_friend_mcp_server-0.1.2.dist-info/METADATA,sha256=sMh-qLXluzJMAR0YZcA-OaSajsxq4tAKODDNyG8Y_tc,6222
12
- phone_a_friend_mcp_server-0.1.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
13
- phone_a_friend_mcp_server-0.1.2.dist-info/entry_points.txt,sha256=c_08XI-vG07VmUT3mtzyuCQjaus5l1NBl4q00Q3jLug,86
14
- phone_a_friend_mcp_server-0.1.2.dist-info/licenses/LICENSE,sha256=-8bInetillKZC0qZDT8RWYIOrph3HIU5cr5N4Pg7bBE,1065
15
- phone_a_friend_mcp_server-0.1.2.dist-info/RECORD,,