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.
- phone_a_friend_mcp_server/config.py +1 -0
- phone_a_friend_mcp_server/tools/fax_tool.py +24 -37
- phone_a_friend_mcp_server/tools/phone_tool.py +24 -30
- phone_a_friend_mcp_server/utils/__init__.py +1 -0
- phone_a_friend_mcp_server/utils/context_builder.py +93 -0
- {phone_a_friend_mcp_server-0.1.2.dist-info → phone_a_friend_mcp_server-0.2.0.dist-info}/METADATA +80 -109
- phone_a_friend_mcp_server-0.2.0.dist-info/RECORD +17 -0
- phone_a_friend_mcp_server-0.1.2.dist-info/RECORD +0 -15
- {phone_a_friend_mcp_server-0.1.2.dist-info → phone_a_friend_mcp_server-0.2.0.dist-info}/WHEEL +0 -0
- {phone_a_friend_mcp_server-0.1.2.dist-info → phone_a_friend_mcp_server-0.2.0.dist-info}/entry_points.txt +0 -0
- {phone_a_friend_mcp_server-0.1.2.dist-info → phone_a_friend_mcp_server-0.2.0.dist-info}/licenses/LICENSE +0 -0
@@ -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
|
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.
|
87
|
-
"
|
88
|
-
|
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
|
-
"
|
95
|
-
"type": "
|
93
|
+
"file_list": {
|
94
|
+
"type": "array",
|
95
|
+
"items": {"type": "string"},
|
96
96
|
"description": (
|
97
|
-
"
|
98
|
-
"
|
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
|
-
|
127
|
+
file_list = kwargs.get("file_list", [])
|
131
128
|
task = kwargs.get("task", "")
|
132
129
|
output_directory = kwargs.get("output_directory", "")
|
133
130
|
|
134
|
-
|
135
|
-
master_prompt = self._create_master_prompt(all_related_context,
|
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(
|
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(
|
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,
|
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
|
-
"===
|
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
|
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.
|
89
|
-
"
|
90
|
-
|
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
|
-
"
|
97
|
-
"type": "
|
95
|
+
"file_list": {
|
96
|
+
"type": "array",
|
97
|
+
"items": {"type": "string"},
|
98
98
|
"description": (
|
99
|
-
"
|
100
|
-
"
|
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
|
-
|
120
|
+
file_list = kwargs.get("file_list", [])
|
124
121
|
task = kwargs.get("task", "")
|
125
122
|
|
126
|
-
|
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(
|
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(
|
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,
|
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
|
-
"===
|
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}"
|
{phone_a_friend_mcp_server-0.1.2.dist-info → phone_a_friend_mcp_server-0.2.0.dist-info}/METADATA
RENAMED
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: phone-a-friend-mcp-server
|
3
|
-
Version: 0.
|
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
|
-
##
|
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
|
-
|
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
|
-
|
75
|
-
|
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
|
-
|
96
|
+
### fax_a_friend
|
97
|
+
📠 Generate master prompt file for manual AI consultation. Creates file for copy-paste workflow.
|
80
98
|
|
81
|
-
**
|
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
|
-
|
93
|
-
```bash
|
94
|
-
phone-a-friend-mcp-server --api-key "your-api-key" --provider openai
|
95
|
-
```
|
101
|
+
*phone_a_friend*
|
96
102
|
|
97
|
-
|
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
|
-
|
100
|
-
```bash
|
107
|
+
*fax_a_friend*
|
101
108
|
|
102
|
-
|
103
|
-
|
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
|
-
|
106
|
-
phone-a-friend-mcp-server --temperature 0.4
|
114
|
+
## Advanced Configuration 🔧
|
107
115
|
|
108
|
-
|
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
|
-
###
|
113
|
-
|
118
|
+
### Providers and API Keys
|
119
|
+
|
120
|
+
The server can be configured via CLI flags or environment variables.
|
114
121
|
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
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
|
-
|
121
|
-
|
129
|
+
**CLI Example:**
|
130
|
+
```bash
|
131
|
+
phone-a-friend-mcp-server --provider openai --api-key "sk-..."
|
122
132
|
```
|
123
133
|
|
124
|
-
|
134
|
+
**Environment Variable Example:**
|
135
|
+
```bash
|
136
|
+
export OPENAI_API_KEY="sk-..."
|
137
|
+
phone-a-friend-mcp-server
|
138
|
+
```
|
125
139
|
|
126
|
-
|
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
|
142
|
+
You can override the default model for each provider.
|
133
143
|
|
134
|
-
|
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
|
-
|
137
|
-
|
151
|
+
**Override with CLI:**
|
152
|
+
```bash
|
153
|
+
phone-a-friend-mcp-server --model "gpt-4-turbo"
|
154
|
+
```
|
138
155
|
|
139
|
-
|
140
|
-
|
156
|
+
**Override with Environment Variable:**
|
157
|
+
```bash
|
158
|
+
export PHONE_A_FRIEND_MODEL="gpt-4-turbo"
|
159
|
+
```
|
141
160
|
|
142
|
-
|
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,,
|
{phone_a_friend_mcp_server-0.1.2.dist-info → phone_a_friend_mcp_server-0.2.0.dist-info}/WHEEL
RENAMED
File without changes
|
File without changes
|
File without changes
|