phone-a-friend-mcp-server 0.1.2__py3-none-any.whl → 0.2.0__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,17 @@ 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
+ "MANDATORY. General, non-code context for the friend AI. "
89
+ "Include known constraints (Python version, allowed deps, etc.), "
90
+ "failing test output, or tracebacks. DO NOT include file contents here."
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
+ "MANDATORY. 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."
102
99
  ),
103
100
  },
104
101
  "task": {
@@ -122,29 +119,26 @@ replacing <file="…"> blocks as needed. Commentary goes outside those tags."""
122
119
  ),
123
120
  },
124
121
  },
125
- "required": ["all_related_context", "task", "output_directory"],
122
+ "required": ["all_related_context", "file_list", "task", "output_directory"],
126
123
  }
127
124
 
128
125
  async def run(self, **kwargs) -> dict[str, Any]:
129
126
  all_related_context = kwargs.get("all_related_context", "")
130
- any_additional_context = kwargs.get("any_additional_context", "")
127
+ file_list = kwargs.get("file_list", [])
131
128
  task = kwargs.get("task", "")
132
129
  output_directory = kwargs.get("output_directory", "")
133
130
 
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)
131
+ code_context = build_code_context(file_list)
132
+ master_prompt = self._create_master_prompt(all_related_context, code_context, task)
136
133
 
137
134
  try:
138
- # Validate and prepare output directory
139
135
  output_dir = self._prepare_output_directory(output_directory)
140
136
 
141
- # Create full file path
142
137
  file_path = os.path.join(output_dir, "fax_a_friend.md")
143
138
 
144
139
  async with aiofiles.open(file_path, "w", encoding="utf-8") as f:
145
140
  await f.write(master_prompt)
146
141
 
147
- # Get absolute path for user reference
148
142
  abs_path = os.path.abspath(file_path)
149
143
 
150
144
  return {
@@ -153,15 +147,15 @@ replacing <file="…"> blocks as needed. Commentary goes outside those tags."""
153
147
  "file_name": "fax_a_friend.md",
154
148
  "output_directory": output_dir,
155
149
  "prompt_length": len(master_prompt),
156
- "context_length": len(all_related_context + any_additional_context),
150
+ "context_length": len(master_prompt),
157
151
  "task": task,
158
152
  "instructions": self._get_manual_workflow_instructions(abs_path),
159
153
  }
160
154
 
161
155
  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}
156
+ return {"status": "failed", "error": str(e), "output_directory": output_directory, "context_length": len(master_prompt), "task": task}
163
157
 
164
- def _create_master_prompt(self, all_related_context: str, any_additional_context: str, task: str) -> str:
158
+ def _create_master_prompt(self, all_related_context: str, code_context: str, task: str) -> str:
165
159
  """Create a comprehensive prompt identical to PhoneAFriendTool's version."""
166
160
 
167
161
  prompt_parts = [
@@ -171,23 +165,19 @@ replacing <file="…"> blocks as needed. Commentary goes outside those tags."""
171
165
  "=== TASK ===",
172
166
  task,
173
167
  "",
174
- "=== ALL RELATED CONTEXT ===",
168
+ "=== GENERAL CONTEXT ===",
175
169
  all_related_context,
170
+ "",
171
+ "=== CODE CONTEXT ===",
172
+ code_context,
176
173
  ]
177
174
 
178
- if any_additional_context.strip():
179
- prompt_parts.extend(
180
- [
181
- "",
182
- "=== ADDITIONAL CONTEXT ===",
183
- any_additional_context,
184
- ]
185
- )
186
-
187
175
  prompt_parts.extend(
188
176
  [
189
177
  "",
190
178
  "=== INSTRUCTIONS ===",
179
+ "- Provide exhaustive, step-by-step reasoning.",
180
+ "- Never include files matching .gitignore patterns.",
191
181
  "- Analyze the code and requirements step-by-step.",
192
182
  "- Show your reasoning and propose concrete changes.",
193
183
  '- Provide updated code using the XML format (<file_tree> plus <file="…"> blocks).',
@@ -204,17 +194,14 @@ replacing <file="…"> blocks as needed. Commentary goes outside those tags."""
204
194
  if not output_directory:
205
195
  raise ValueError("output_directory parameter is required")
206
196
 
207
- # Expand user path (~) and resolve relative paths
208
197
  expanded_path = os.path.expanduser(output_directory)
209
198
  resolved_path = os.path.abspath(expanded_path)
210
199
 
211
- # Create directory if it doesn't exist
212
200
  try:
213
201
  os.makedirs(resolved_path, exist_ok=True)
214
202
  except OSError as e:
215
203
  raise ValueError(f"Cannot create directory '{resolved_path}': {e}")
216
204
 
217
- # Check if directory is writable
218
205
  if not os.access(resolved_path, os.W_OK):
219
206
  raise ValueError(f"Directory '{resolved_path}' is not writable")
220
207
 
@@ -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,17 @@ 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
+ "MANDATORY. General, non-code context for the friend AI. "
91
+ "Include known constraints (Python version, allowed deps, etc.), "
92
+ "failing test output, or tracebacks. DO NOT include file contents here."
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
+ "MANDATORY. 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."
104
101
  ),
105
102
  },
106
103
  "task": {
@@ -115,15 +112,16 @@ replacing <file="…"> blocks as needed. Commentary goes outside those tags."""
115
112
  ),
116
113
  },
117
114
  },
118
- "required": ["all_related_context", "task"],
115
+ "required": ["all_related_context", "file_list", "task"],
119
116
  }
120
117
 
121
118
  async def run(self, **kwargs) -> dict[str, Any]:
122
119
  all_related_context = kwargs.get("all_related_context", "")
123
- any_additional_context = kwargs.get("any_additional_context", "")
120
+ file_list = kwargs.get("file_list", [])
124
121
  task = kwargs.get("task", "")
125
122
 
126
- master_prompt = self._create_master_prompt(all_related_context, any_additional_context, task)
123
+ code_context = build_code_context(file_list)
124
+ master_prompt = self._create_master_prompt(all_related_context, code_context, task)
127
125
 
128
126
  try:
129
127
  agent = self._create_agent()
@@ -140,7 +138,7 @@ replacing <file="…"> blocks as needed. Commentary goes outside those tags."""
140
138
  "provider": self.config.provider,
141
139
  "model": self.config.model,
142
140
  "temperature": temperature,
143
- "context_length": len(all_related_context + any_additional_context),
141
+ "context_length": len(master_prompt),
144
142
  "task": task,
145
143
  }
146
144
 
@@ -152,7 +150,7 @@ replacing <file="…"> blocks as needed. Commentary goes outside those tags."""
152
150
  "provider": self.config.provider,
153
151
  "model": self.config.model,
154
152
  "temperature": temperature,
155
- "context_length": len(all_related_context + any_additional_context),
153
+ "context_length": len(master_prompt),
156
154
  "task": task,
157
155
  "master_prompt": master_prompt,
158
156
  }
@@ -182,7 +180,7 @@ replacing <file="…"> blocks as needed. Commentary goes outside those tags."""
182
180
 
183
181
  return Agent(model)
184
182
 
185
- def _create_master_prompt(self, all_related_context: str, any_additional_context: str, task: str) -> str:
183
+ def _create_master_prompt(self, all_related_context: str, code_context: str, task: str) -> str:
186
184
  """Create a comprehensive prompt for the external AI."""
187
185
 
188
186
  prompt_parts = [
@@ -192,23 +190,19 @@ replacing <file="…"> blocks as needed. Commentary goes outside those tags."""
192
190
  "=== TASK ===",
193
191
  task,
194
192
  "",
195
- "=== ALL RELATED CONTEXT ===",
193
+ "=== GENERAL CONTEXT ===",
196
194
  all_related_context,
195
+ "",
196
+ "=== CODE CONTEXT ===",
197
+ code_context,
197
198
  ]
198
199
 
199
- if any_additional_context.strip():
200
- prompt_parts.extend(
201
- [
202
- "",
203
- "=== ADDITIONAL CONTEXT ===",
204
- any_additional_context,
205
- ]
206
- )
207
-
208
200
  prompt_parts.extend(
209
201
  [
210
202
  "",
211
203
  "=== INSTRUCTIONS ===",
204
+ "- Provide exhaustive, step-by-step reasoning.",
205
+ "- Never include files matching .gitignore patterns.",
212
206
  "- Analyze the code and requirements step-by-step.",
213
207
  "- Show your reasoning and propose concrete changes.",
214
208
  '- 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,93 @@
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 filter_paths(paths: list[str], spec: pathspec.PathSpec, base_dir: str = ".") -> list[str]:
18
+ """Filters out paths that match the .gitignore spec and non-text files."""
19
+ filtered_paths = []
20
+ for path in paths:
21
+ normalized_path = os.path.normpath(os.path.join(base_dir, path))
22
+ if not spec.match_file(normalized_path) and os.path.isfile(normalized_path):
23
+ try:
24
+ with open(normalized_path, encoding="utf-8") as f:
25
+ f.read(1024)
26
+ filtered_paths.append(path)
27
+ except (OSError, UnicodeDecodeError):
28
+ pass
29
+ return filtered_paths
30
+
31
+
32
+ def build_file_tree(paths: list[str], base_dir: str = ".") -> str:
33
+ """Builds an ASCII file tree from a list of paths."""
34
+ tree = {}
35
+ for path in paths:
36
+ parts = path.split(os.sep)
37
+ current_level = tree
38
+ for part in parts:
39
+ if part not in current_level:
40
+ current_level[part] = {}
41
+ current_level = current_level[part]
42
+
43
+ def generate_tree_lines(d, prefix=""):
44
+ lines = []
45
+ entries = sorted(d.keys())
46
+ for i, entry in enumerate(entries):
47
+ connector = "├── " if i < len(entries) - 1 else "└── "
48
+ lines.append(prefix + connector + entry)
49
+ if d[entry]:
50
+ extension = "│ " if i < len(entries) - 1 else " "
51
+ lines.extend(generate_tree_lines(d[entry], prefix + extension))
52
+ return lines
53
+
54
+ tree_lines = [base_dir] + generate_tree_lines(tree)
55
+ return "\n".join(tree_lines)
56
+
57
+
58
+ def build_file_blocks(paths: list[str], base_dir: str = ".") -> str:
59
+ """Creates <file="..." /> blocks with the full source code for each file."""
60
+ blocks = []
61
+ for path in paths:
62
+ full_path = os.path.join(base_dir, path)
63
+ try:
64
+ with open(full_path, encoding="utf-8") as f:
65
+ content = f.read()
66
+ blocks.append(f'<file="{path}">\n{content}\n</file>')
67
+ except (OSError, UnicodeDecodeError) as e:
68
+ blocks.append(f'<file="{path}">\nError reading file: {e}\n</file>')
69
+ return "\n\n".join(blocks)
70
+
71
+
72
+ def build_code_context(file_list: list[str], base_dir: str = ".") -> str:
73
+ """
74
+ Builds the complete code context, including a file tree and file content blocks,
75
+ filtering out ignored and binary files.
76
+ """
77
+ spec = load_gitignore(base_dir)
78
+
79
+ all_files = []
80
+ for pattern in file_list:
81
+ all_files.extend(glob.glob(pattern, recursive=True))
82
+
83
+ unique_files = sorted(list(set(all_files)))
84
+
85
+ filtered_files = filter_paths(unique_files, spec, base_dir)
86
+
87
+ if not filtered_files:
88
+ return "No files to display. Check your `file_list` and `.gitignore`."
89
+
90
+ file_tree = build_file_tree(filtered_files, base_dir)
91
+ file_blocks = build_file_blocks(filtered_files, base_dir)
92
+
93
+ return f"<file_tree>\n{file_tree}\n</file_tree>\n\n{file_blocks}"
@@ -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.2.0
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
@@ -63,87 +64,106 @@ 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 ⚡
68
+
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.
71
+
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": "uv",
79
+ "args": [
80
+ "run",
81
+ "phone-a-friend-mcp-server",
82
+ "--provider", "openai",
83
+ "--api-key", "<YOUR_API_KEY>"
84
+ ]
85
+ }
86
+ }
87
+ }
88
+ ```
89
+ > That's it! You can now use the `phone_a_friend` tool in any compatible client. For more options, see the Advanced Configuration section.
67
90
 
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
- ```
91
+ ## Available Tools 🛠️
73
92
 
74
- 2. Install dependencies:
75
- ```bash
76
- uv pip install -e .
77
- ```
93
+ ### phone_a_friend
94
+ 📞 Consult external AI for critical thinking and complex reasoning. Makes API calls to get responses.
78
95
 
79
- 3. Configure API access (choose one method):
96
+ ### fax_a_friend
97
+ 📠 Generate master prompt file for manual AI consultation. Creates file for copy-paste workflow.
80
98
 
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
- ```
99
+ **Parameters**
91
100
 
92
- **Option B: CLI Arguments**
93
- ```bash
94
- phone-a-friend-mcp-server --api-key "your-api-key" --provider openai
95
- ```
101
+ *phone_a_friend*
96
102
 
97
- ## Usage 💡
103
+ - `all_related_context` (required): General, non-code context such as constraints, tracebacks, or high-level requirements.
104
+ - `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.
105
+ - `task` (required): A clear, specific description of what you want the external AI to do.
98
106
 
99
- ### Command Line Options
100
- ```bash
107
+ *fax_a_friend*
101
108
 
102
- # Custom base URL (if needed)
103
- phone-a-friend-mcp-server --base-url "https://custom-api.example.com"
109
+ - `all_related_context` (required): Same as above.
110
+ - `file_list` (required): Same as above.
111
+ - `task` (required): Same as above.
112
+ - `output_directory` (required): Directory where the generated `fax_a_friend.md` master prompt file will be saved.
104
113
 
105
- # Temperature control (0.0 = deterministic, 2.0 = very creative)
106
- phone-a-friend-mcp-server --temperature 0.4
114
+ ## Advanced Configuration 🔧
107
115
 
108
- # Combined example
109
- phone-a-friend-mcp-server --api-key "sk-..." --provider openai --model "o3" -v
110
- ```
116
+ This section covers all configuration options, including environment variables, CLI flags, and model selection.
111
117
 
112
- ### Environment Variables (Optional)
113
- ```bash
118
+ ### Providers and API Keys
119
+
120
+ The server can be configured via CLI flags or environment variables.
114
121
 
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"
122
+ | Provider | CLI Flag | Environment Variable |
123
+ | :--- | :--- | :--- |
124
+ | OpenAI | `--provider openai` | `OPENAI_API_KEY` |
125
+ | OpenRouter | `--provider openrouter` | `OPENROUTER_API_KEY` |
126
+ | Anthropic | `--provider anthropic` | `ANTHROPIC_API_KEY` |
127
+ | Google | `--provider google` | `GOOGLE_API_KEY` |
119
128
 
120
- # Temperature control (0.0-2.0, where 0.0 = deterministic, 2.0 = very creative)
121
- export PHONE_A_FRIEND_TEMPERATURE=0.4
129
+ **CLI Example:**
130
+ ```bash
131
+ phone-a-friend-mcp-server --provider openai --api-key "sk-..."
122
132
  ```
123
133
 
124
- ## Model Selection 🤖
134
+ **Environment Variable Example:**
135
+ ```bash
136
+ export OPENAI_API_KEY="sk-..."
137
+ phone-a-friend-mcp-server
138
+ ```
125
139
 
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
140
+ ### Model Selection
131
141
 
132
- You can override the auto-selection by setting `PHONE_A_FRIEND_MODEL` environment variable or using the `--model` CLI option.
142
+ You can override the default model for each provider.
133
143
 
134
- ## Available Tools 🛠️
144
+ | Provider | Default Model |
145
+ | :--- | :--- |
146
+ | **OpenAI** | `o3` |
147
+ | **Anthropic** | `Claude 4 Opus` |
148
+ | **Google** | `Gemini 2.5 Pro Preview 05-06` |
149
+ | **OpenRouter**| `anthropic/claude-4-opus` |
135
150
 
136
- ### phone_a_friend
137
- 📞 Consult external AI for critical thinking and complex reasoning. Makes API calls to get responses.
151
+ **Override with CLI:**
152
+ ```bash
153
+ phone-a-friend-mcp-server --model "gpt-4-turbo"
154
+ ```
138
155
 
139
- ### fax_a_friend
140
- 📠 Generate master prompt file for manual AI consultation. Creates file for copy-paste workflow.
156
+ **Override with Environment Variable:**
157
+ ```bash
158
+ export PHONE_A_FRIEND_MODEL="gpt-4-turbo"
159
+ ```
141
160
 
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
161
+ ### Additional Options
146
162
 
163
+ | Feature | CLI Flag | Environment Variable | Default |
164
+ | :--- | :--- | :--- | :--- |
165
+ | **Temperature** | `--temperature 0.5` | `PHONE_A_FRIEND_TEMPERATURE` | `0.4` |
166
+ | **Base URL** | `--base-url ...` | `PHONE_A_FRIEND_BASE_URL` | Provider default |
147
167
 
148
168
  ## Use Cases 🎯
149
169
 
@@ -151,55 +171,6 @@ You can override the auto-selection by setting `PHONE_A_FRIEND_MODEL` environmen
151
171
  2. For complex algorithms, data structures, or mathematical computations
152
172
  3. Frontend Development with React, Vue, CSS, or modern frontend frameworks
153
173
 
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
174
  ## License 📄
204
175
 
205
176
  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=esQwxt-j69CiBRQHaVVShRbwEHdcvAppwO1uBD6UCQs,8836
9
+ phone_a_friend_mcp_server/tools/phone_tool.py,sha256=4OLRGThNQO8aAFWL8Bz5J40RPKqDqSZuGXpJc0oRL94,8396
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=bw3FwnRg1WQsXjxyKPwLpqrTObPkmQnZ_MtBzaz6WnM,3398
13
+ phone_a_friend_mcp_server-0.2.0.dist-info/METADATA,sha256=wMrRoQMFcWN8DZoHDV1eqHKmsCziQxy3H7dsxIccepY,6126
14
+ phone_a_friend_mcp_server-0.2.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
15
+ phone_a_friend_mcp_server-0.2.0.dist-info/entry_points.txt,sha256=c_08XI-vG07VmUT3mtzyuCQjaus5l1NBl4q00Q3jLug,86
16
+ phone_a_friend_mcp_server-0.2.0.dist-info/licenses/LICENSE,sha256=-8bInetillKZC0qZDT8RWYIOrph3HIU5cr5N4Pg7bBE,1065
17
+ phone_a_friend_mcp_server-0.2.0.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,,