cf-mcp-orange 1.0.1__tar.gz

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.
@@ -0,0 +1,49 @@
1
+ Metadata-Version: 2.4
2
+ Name: cf-mcp-orange
3
+ Version: 1.0.1
4
+ Summary: A complete Codeforces MCP toolset for AI agents
5
+ Author-email: supriya <supriya81205@gmail.com>
6
+ License: MIT
7
+ Requires-Python: >=3.10
8
+ Description-Content-Type: text/markdown
9
+ Requires-Dist: fastmcp>=0.1.2
10
+ Requires-Dist: httpx>=0.27.0
11
+ Requires-Dist: beautifulsoup4>=4.12.0
12
+
13
+ # cpmcp-oren 🚀
14
+
15
+ [![PyPI version](https://img.shields.io/pypi/v/cpmcp-oren)](https://pypi.org/project/cpmcp-oren/)
16
+
17
+ A **complete, all-in-one MCP (Model Context Protocol) server** for Codeforces.
18
+ Designed to supercharge AI coding assistants like **Cline** and **Claude Desktop**.
19
+
20
+ ---
21
+
22
+ ## ✨ Features (10 Powerful Tools)
23
+
24
+ 1. **`get_user`** – Fetch a detailed profile (rating, max rating, last 5 contests, problem stats by difficulty, top 10 categories).
25
+ 2. **`compare_user`** – Side-by-side comparison of two Codeforces users.
26
+ 3. **`get_problemlist`** – Search the entire problemset by rating range, topic tags (`AND`/`OR`), and sorting.
27
+ 4. **`get_problem`** – Retrieve the full problem statement, rating, and tags for any specific contest/problem ID.
28
+ 5. **`get_practiceproblems`** – Identify a user's weakest core topics and recommend 3 targeted problems within `+300` rating.
29
+ 6. **`get_random_practice`** – A completely random problem within `+/- 300` of a user's current rating.
30
+ 7. **`get_upsolve`** – Analyzes a user's last 10 contests and recommends unsolved problems within their rating range.
31
+ 8. **`get_status`** – Summarizes the last 1000 submissions (AC/WA/TLE counts) and the 20 most recent attempts.
32
+ 9. **`get_catalog`** – Searches and fetches the latest official educational articles from the Codeforces Catalog.
33
+ 10. **`submit_solution`** – (*Optional*) Submits code to a problem via the authenticated API.
34
+
35
+ ---
36
+
37
+ ## 🛠️ Installation & Configuration (No Keys Required!)
38
+
39
+ You don't need to clone or manually install anything! Just add this to your `cline_mcp_settings.json` or `mcp.json`:
40
+
41
+ ```json
42
+ {
43
+ "mcpServers": {
44
+ "cpmcp-oren": {
45
+ "command": "uvx",
46
+ "args": ["cpmcp-oren"]
47
+ }
48
+ }
49
+ }
@@ -0,0 +1,37 @@
1
+ # cpmcp-oren 🚀
2
+
3
+ [![PyPI version](https://img.shields.io/pypi/v/cpmcp-oren)](https://pypi.org/project/cpmcp-oren/)
4
+
5
+ A **complete, all-in-one MCP (Model Context Protocol) server** for Codeforces.
6
+ Designed to supercharge AI coding assistants like **Cline** and **Claude Desktop**.
7
+
8
+ ---
9
+
10
+ ## ✨ Features (10 Powerful Tools)
11
+
12
+ 1. **`get_user`** – Fetch a detailed profile (rating, max rating, last 5 contests, problem stats by difficulty, top 10 categories).
13
+ 2. **`compare_user`** – Side-by-side comparison of two Codeforces users.
14
+ 3. **`get_problemlist`** – Search the entire problemset by rating range, topic tags (`AND`/`OR`), and sorting.
15
+ 4. **`get_problem`** – Retrieve the full problem statement, rating, and tags for any specific contest/problem ID.
16
+ 5. **`get_practiceproblems`** – Identify a user's weakest core topics and recommend 3 targeted problems within `+300` rating.
17
+ 6. **`get_random_practice`** – A completely random problem within `+/- 300` of a user's current rating.
18
+ 7. **`get_upsolve`** – Analyzes a user's last 10 contests and recommends unsolved problems within their rating range.
19
+ 8. **`get_status`** – Summarizes the last 1000 submissions (AC/WA/TLE counts) and the 20 most recent attempts.
20
+ 9. **`get_catalog`** – Searches and fetches the latest official educational articles from the Codeforces Catalog.
21
+ 10. **`submit_solution`** – (*Optional*) Submits code to a problem via the authenticated API.
22
+
23
+ ---
24
+
25
+ ## 🛠️ Installation & Configuration (No Keys Required!)
26
+
27
+ You don't need to clone or manually install anything! Just add this to your `cline_mcp_settings.json` or `mcp.json`:
28
+
29
+ ```json
30
+ {
31
+ "mcpServers": {
32
+ "cpmcp-oren": {
33
+ "command": "uvx",
34
+ "args": ["cpmcp-oren"]
35
+ }
36
+ }
37
+ }
@@ -0,0 +1,49 @@
1
+ Metadata-Version: 2.4
2
+ Name: cf-mcp-orange
3
+ Version: 1.0.1
4
+ Summary: A complete Codeforces MCP toolset for AI agents
5
+ Author-email: supriya <supriya81205@gmail.com>
6
+ License: MIT
7
+ Requires-Python: >=3.10
8
+ Description-Content-Type: text/markdown
9
+ Requires-Dist: fastmcp>=0.1.2
10
+ Requires-Dist: httpx>=0.27.0
11
+ Requires-Dist: beautifulsoup4>=4.12.0
12
+
13
+ # cpmcp-oren 🚀
14
+
15
+ [![PyPI version](https://img.shields.io/pypi/v/cpmcp-oren)](https://pypi.org/project/cpmcp-oren/)
16
+
17
+ A **complete, all-in-one MCP (Model Context Protocol) server** for Codeforces.
18
+ Designed to supercharge AI coding assistants like **Cline** and **Claude Desktop**.
19
+
20
+ ---
21
+
22
+ ## ✨ Features (10 Powerful Tools)
23
+
24
+ 1. **`get_user`** – Fetch a detailed profile (rating, max rating, last 5 contests, problem stats by difficulty, top 10 categories).
25
+ 2. **`compare_user`** – Side-by-side comparison of two Codeforces users.
26
+ 3. **`get_problemlist`** – Search the entire problemset by rating range, topic tags (`AND`/`OR`), and sorting.
27
+ 4. **`get_problem`** – Retrieve the full problem statement, rating, and tags for any specific contest/problem ID.
28
+ 5. **`get_practiceproblems`** – Identify a user's weakest core topics and recommend 3 targeted problems within `+300` rating.
29
+ 6. **`get_random_practice`** – A completely random problem within `+/- 300` of a user's current rating.
30
+ 7. **`get_upsolve`** – Analyzes a user's last 10 contests and recommends unsolved problems within their rating range.
31
+ 8. **`get_status`** – Summarizes the last 1000 submissions (AC/WA/TLE counts) and the 20 most recent attempts.
32
+ 9. **`get_catalog`** – Searches and fetches the latest official educational articles from the Codeforces Catalog.
33
+ 10. **`submit_solution`** – (*Optional*) Submits code to a problem via the authenticated API.
34
+
35
+ ---
36
+
37
+ ## 🛠️ Installation & Configuration (No Keys Required!)
38
+
39
+ You don't need to clone or manually install anything! Just add this to your `cline_mcp_settings.json` or `mcp.json`:
40
+
41
+ ```json
42
+ {
43
+ "mcpServers": {
44
+ "cpmcp-oren": {
45
+ "command": "uvx",
46
+ "args": ["cpmcp-oren"]
47
+ }
48
+ }
49
+ }
@@ -0,0 +1,9 @@
1
+ README.md
2
+ pyproject.toml
3
+ cf_mcp_orange.egg-info/PKG-INFO
4
+ cf_mcp_orange.egg-info/SOURCES.txt
5
+ cf_mcp_orange.egg-info/dependency_links.txt
6
+ cf_mcp_orange.egg-info/requires.txt
7
+ cf_mcp_orange.egg-info/top_level.txt
8
+ src/cpmcp_oren/__init__.py
9
+ src/cpmcp_oren/__main__.py
@@ -0,0 +1,3 @@
1
+ fastmcp>=0.1.2
2
+ httpx>=0.27.0
3
+ beautifulsoup4>=4.12.0
@@ -0,0 +1 @@
1
+ cpmcp_oren
@@ -0,0 +1,21 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "cf-mcp-orange"
7
+ version = "1.0.1"
8
+ description = "A complete Codeforces MCP toolset for AI agents"
9
+ readme = "README.md"
10
+ authors = [{name = "supriya", email = "supriya81205@gmail.com"}]
11
+ license = {text = "MIT"}
12
+ requires-python = ">=3.10"
13
+ dependencies = [
14
+ "fastmcp>=0.1.2",
15
+ "httpx>=0.27.0",
16
+ "beautifulsoup4>=4.12.0"
17
+ ]
18
+
19
+ [tool.setuptools]
20
+ packages = ["cpmcp_oren"]
21
+ package-dir = {"cpmcp_oren" = "src/cpmcp_oren"}
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,303 @@
1
+ from fastmcp import FastMCP
2
+
3
+ # Import your tool logic from the 'tools' package (Relative imports)
4
+ from .tools.get_user import fetch_and_parse_user_data
5
+ from .tools.compare_user import compare_users_data
6
+ from .tools.get_problemlist import get_filtered_problems
7
+ from .tools.get_problem import get_problem_details
8
+ from .tools.get_practice import get_practice_plan
9
+ from .tools.get_random_problem import get_random_problem_suggestion
10
+ from .tools.get_upsolve import get_upsolve_suggestions
11
+ from .tools.get_status import get_user_submission_status
12
+
13
+ # Initialize the MCP Server
14
+ mcp = FastMCP("cpmcp-oren")
15
+
16
+ # ==========================================
17
+ # TOOL 1: Get Single User Profile
18
+ # ==========================================
19
+ @mcp.tool()
20
+ async def get_user(username: str) -> str:
21
+ """
22
+ Fetches a complete Codeforces profile in one call, returning:
23
+ - Current rating, max rating, title, and max title.
24
+ - Number of total contests given.
25
+ - Recent 5 contest rating changes (delta).
26
+ - Total distinct problems solved.
27
+ - Breakdown of solved problems by difficulty.
28
+ - Top 10 problem categories solved.
29
+ """
30
+ data = await fetch_and_parse_user_data(username)
31
+
32
+ if data["status"] == "error":
33
+ return f"❌ Error fetching user: {data['message']}"
34
+
35
+ output = f"**🧑‍💻 User Profile: {username}**\n\n"
36
+
37
+ output += f"**🏆 Ratings & Titles:**\n"
38
+ output += f"- Current Rating: **{data['rating']}**\n"
39
+ output += f"- Max Rating: **{data['max_rating']}**\n"
40
+ output += f"- Current Title: {data['title']}\n"
41
+ output += f"- Max Title: {data['max_title']}\n\n"
42
+
43
+ output += f"**🏁 Contest History:**\n"
44
+ output += f"- Total Rated Contests Given: **{data['total_contests']}**\n"
45
+
46
+ if data['recent_5']:
47
+ output += "- **Last 5 Rating Changes:**\n"
48
+ for c in data['recent_5']:
49
+ sign = "+" if c['newRating'] > c['oldRating'] else ""
50
+ output += f" - {c['contestName']}: Rank {c['rank']} ({sign}{c['newRating'] - c['oldRating']}, {c['oldRating']} ➜ {c['newRating']})\n"
51
+ else:
52
+ output += "- No rated contests found.\n"
53
+ output += "\n"
54
+
55
+ output += f"**✅ Problem Solving Stats:**\n"
56
+ output += f"- Total Distinct Problems Solved: **{data['total_solved']}**\n"
57
+
58
+ if data['difficulty_stats']:
59
+ output += "- **Solved by Difficulty:**\n"
60
+ sorted_diff = sorted(data['difficulty_stats'].items(), key=lambda x: int(x[0]) if x[0] != 'Unrated' else 0)
61
+ for diff, count in sorted_diff:
62
+ output += f" - Rating {diff}: {count} problems\n"
63
+ else:
64
+ output += "- No solved problems found by difficulty.\n"
65
+
66
+ if data['top_10_categories']:
67
+ output += "- **Top 10 Categories:**\n"
68
+ for tag, count in data['top_10_categories']:
69
+ output += f" - {tag}: {count} problems\n"
70
+ else:
71
+ output += "- No categories found.\n"
72
+
73
+ return output
74
+
75
+ # ==========================================
76
+ # TOOL 2: Compare Two Users
77
+ # ==========================================
78
+ @mcp.tool()
79
+ async def compare_user(user_a: str, user_b: str) -> str:
80
+ """
81
+ Compare two Codeforces users side-by-side.
82
+ Returns a detailed comparison of ratings, titles, contest count,
83
+ and problem solving breakdown (difficulty & category).
84
+ """
85
+ return await compare_users_data(user_a, user_b)
86
+
87
+ # ==========================================
88
+ # TOOL 3: Search Problem List
89
+ # ==========================================
90
+ @mcp.tool()
91
+ async def get_problemlist(
92
+ min_rating: int = None,
93
+ max_rating: int = None,
94
+ topics: list = None,
95
+ sort_by: str = "recent",
96
+ match_type: str = "OR"
97
+ ) -> str:
98
+ """
99
+ Search the entire Codeforces problemset and return up to 10 problems.
100
+
101
+ Parameters:
102
+ - min_rating: Minimum difficulty rating (e.g., 800)
103
+ - max_rating: Maximum difficulty rating (e.g., 2400)
104
+ - topics: List of tags to filter by (e.g., ["dp", "greedy"])
105
+ - sort_by: "recent" (newest first) or "oldest" (oldest first)
106
+ - match_type: "OR" (match any topic) or "AND" (must match ALL topics)
107
+
108
+ Returns 10 problems with names, ratings, tags, and direct links.
109
+ """
110
+ try:
111
+ problems = await get_filtered_problems(
112
+ min_rating=min_rating,
113
+ max_rating=max_rating,
114
+ topics=topics,
115
+ sort_by=sort_by,
116
+ match_type=match_type
117
+ )
118
+
119
+ if not problems:
120
+ return "No problems found matching your criteria. Try broadening your filters."
121
+
122
+ output = f"**💻 Found {len(problems)} Problems:**\n\n"
123
+
124
+ for p in problems:
125
+ name = p.get('name', 'Unknown Problem')
126
+ contest_id = p['contestId']
127
+ index = p['index']
128
+ rating = p.get('rating', 'Unrated')
129
+ tags = ", ".join(p.get('tags', [])[:5])
130
+ url = f"https://codeforces.com/problemset/problem/{contest_id}/{index}"
131
+
132
+ output += f"### [{name}]({url})\n"
133
+ output += f"- **Rating**: {rating}\n"
134
+ output += f"- **Tags**: {tags}\n\n"
135
+
136
+ return output
137
+
138
+ except Exception as e:
139
+ return f"❌ Error fetching problem list: {str(e)}"
140
+
141
+ # ==========================================
142
+ # TOOL 4: Get Full Problem Statement
143
+ # ==========================================
144
+ @mcp.tool()
145
+ async def get_problem(contest_id: int, index: str) -> str:
146
+ """
147
+ Fetch the full problem statement, rating, and topics for a specific Codeforces problem.
148
+ Provide the contest ID and the problem index (e.g., contest_id=4, index='A').
149
+ """
150
+ try:
151
+ data = await get_problem_details(contest_id, index)
152
+
153
+ if "error" in data:
154
+ return f"❌ {data['error']}"
155
+
156
+ output = f"# {data['title']}\n\n"
157
+ output += f"**Link:** [View on Codeforces]({data['url']})\n\n"
158
+
159
+ # The dropdown you requested!
160
+ output += f"📊 **Rating:** `{data['rating']}` | **Topics:** `{', '.join(data['topics'])}`\n\n"
161
+ output += f"---\n\n"
162
+
163
+ output += "**Problem Statement:**\n\n"
164
+ output += data['statement']
165
+
166
+ return output
167
+ except Exception as e:
168
+ return f"❌ Error fetching problem: {str(e)}"
169
+
170
+ # ==========================================
171
+ # TOOL 5: Get Practice Problems
172
+ # ==========================================
173
+ @mcp.tool()
174
+ async def get_practiceproblems(username: str) -> str:
175
+ """
176
+ Analyzes a user's solved problems to identify their weakest topics.
177
+ Recommends up to 3 practice problems within +300 rating of their current level.
178
+ """
179
+ result = await get_practice_plan(username)
180
+
181
+ if result["status"] == "error":
182
+ return f"❌ Error generating practice plan: {result['message']}"
183
+
184
+ output = f"**🎯 Practice Plan for `{username}`**\n\n"
185
+ output += f"Current Rating: **{result['rating']}**\n"
186
+ output += f"Identified Weak Topic(s): **{', '.join(result['weak_topics'])}**\n"
187
+ output += f"Target Rating Range: **{result['rating']} - {result['rating'] + 300}**\n\n"
188
+
189
+ if not result["problems"]:
190
+ output += "No suitable practice problems found in the target rating range. Try increasing your range or broadening topics."
191
+ return output
192
+
193
+ output += "**💻 Recommended Problems to Practice:**\n"
194
+ for idx, p in enumerate(result["problems"], 1):
195
+ name = p.get('name', 'Unknown Problem')
196
+ contest_id = p['contestId']
197
+ index = p['index']
198
+ rating = p.get('rating', 'Unrated')
199
+ tags = ", ".join(p.get('tags', [])[:5])
200
+ url = f"https://codeforces.com/problemset/problem/{contest_id}/{index}"
201
+
202
+ output += f"\n**{idx}. [{name}]({url})**\n"
203
+ output += f" - Rating: {rating}\n"
204
+ output += f" - Topics: {tags}\n"
205
+
206
+ return output
207
+
208
+ # ==========================================
209
+ # TOOL 7: Get Random Practice Problem
210
+ # ==========================================
211
+ @mcp.tool()
212
+ async def get_random_practice(username: str) -> str:
213
+ """
214
+ Ignore weaknesses and recommend a single, completely random Codeforces problem
215
+ within +/- 300 rating of the user's current rating.
216
+ Great for a fresh, challenging surprise!
217
+ """
218
+ result = await get_random_problem_suggestion(username)
219
+
220
+ if "error" in result:
221
+ return f"❌ Error generating random problem: {result['error']}"
222
+
223
+ p = result
224
+ name = p.get('name', 'Unknown Problem')
225
+ contest_id = p['contestId']
226
+ index = p['index']
227
+ rating = p.get('rating', 'Unrated')
228
+ tags = ", ".join(p.get('tags', [])[:5])
229
+ url = f"https://codeforces.com/problemset/problem/{contest_id}/{index}"
230
+
231
+ output = f"**🎲 Random Practice Problem for `{username}`**\n\n"
232
+ output += f"**[{name}]({url})**\n"
233
+ output += f"- **Rating**: {rating}\n"
234
+ output += f"- **Tags**: {tags}\n"
235
+ output += f"- *(Generated from range: Current Rating ± 300)*\n"
236
+
237
+ return output
238
+
239
+ # ==========================================
240
+ # TOOL 8: Get Upsolve Problems
241
+ # ==========================================
242
+ @mcp.tool()
243
+ async def get_upsolve(username: str) -> str:
244
+ """
245
+ Analyze the user's last 10 rated Codeforces contests.
246
+ Recommends up to 2 unsolved problems per contest if:
247
+ - The problem is unrated, OR
248
+ - The problem is rated within +/- 300 of the user's current rating.
249
+ This is perfect for targeted practice to improve rank!
250
+ """
251
+ result = await get_upsolve_suggestions(username)
252
+
253
+ if result["status"] == "error":
254
+ return f"❌ Error generating upsolve list: {result['message']}"
255
+
256
+ if not result["suggestions"]:
257
+ return f"🎉 No upsolve problems found for `{username}` within +/- 300 of {result['rating']} rating. Either you solved them all, or they are out of your range!"
258
+
259
+ output = f"**📝 Upsolve Plan for `{username}`**\n"
260
+ output += f"(Based on last 10 rated contests. Current Rating: **{result['rating']}**)\n\n"
261
+
262
+ for item in result["suggestions"]:
263
+ p = item["problem"]
264
+ name = p.get('name', 'Unknown')
265
+ contest_id = p['contestId']
266
+ index = p['index']
267
+ rating = p.get('rating', 'Unrated')
268
+ tags = ", ".join(p.get('tags', [])[:3]) # Show top 3 tags
269
+ url = f"https://codeforces.com/problemset/problem/{contest_id}/{index}"
270
+
271
+ output += f"### [{name}]({url})\n"
272
+ output += f"- **Contest:** {item['contest_name']}\n"
273
+ output += f"- **Rating:** {rating}\n"
274
+ output += f"- **Tags:** {tags}\n"
275
+ output += f"- **Why upsolve?** *{item['reason']}*\n\n"
276
+
277
+ return output
278
+
279
+ # ==========================================
280
+ # TOOL 10: Get User Submission Status
281
+ # ==========================================
282
+ @mcp.tool()
283
+ async def get_status(username: str) -> str:
284
+ """
285
+ Fetch the last 1000 submissions of a user. Provides a summary of their
286
+ AC/WA/TLE count, and details of their 20 most recent submissions.
287
+ Great for debugging how a user is performing on their current attempts.
288
+ """
289
+ result = await get_user_submission_status(username)
290
+
291
+ if result["status"] == "error":
292
+ return f"❌ Error fetching submissions: {result['message']}"
293
+
294
+ output = f"**📊 Submission Status for `{username}`**\n\n"
295
+ output += result["summary"] + "\n"
296
+
297
+ output += "**🕒 Most Recent 20 Submissions:**\n"
298
+ for sub in result["recent_submissions"]:
299
+ output += f"- **[`{sub['prob_name']}`]({sub['url']})**\n"
300
+ output += f" - Verdict: {sub['verdict']}\n"
301
+ output += f" - Time: {sub['time']} | Memory: {sub['memory']}\n"
302
+
303
+ return output
@@ -0,0 +1,4 @@
1
+ from . import mcp
2
+
3
+ if __name__ == "__main__":
4
+ mcp.run(transport="stdio")