stackprep-mcp 0.1.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.
File without changes
@@ -0,0 +1,389 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import os
5
+ import re
6
+ import secrets
7
+ from pathlib import Path
8
+ from typing import Any
9
+
10
+ from mcp.server.fastmcp import FastMCP
11
+
12
+ mcp = FastMCP("stackprep")
13
+
14
+ SKILLS_DIR = Path(__file__).parent / "skills"
15
+
16
+ _sessions: dict[str, dict[str, Any]] = {}
17
+
18
+
19
+ # ── Storage ────────────────────────────────────────────────────────────────────
20
+
21
+ def _data_dir() -> Path:
22
+ env = os.environ.get("STACKPREP_PACKS_DIR")
23
+ d = Path(env) if env else Path.home() / ".stackprep"
24
+ d.mkdir(parents=True, exist_ok=True)
25
+ return d
26
+
27
+
28
+ def _packs_dir() -> Path:
29
+ d = _data_dir() / "packs"
30
+ d.mkdir(parents=True, exist_ok=True)
31
+ return d
32
+
33
+
34
+ def _sessions_dir() -> Path:
35
+ d = _data_dir() / "sessions"
36
+ d.mkdir(parents=True, exist_ok=True)
37
+ return d
38
+
39
+
40
+ def _persist_session(session_id: str) -> None:
41
+ path = _sessions_dir() / f"{session_id}.json"
42
+ path.write_text(json.dumps(_sessions[session_id], indent=2, ensure_ascii=False), encoding="utf-8")
43
+
44
+
45
+ def _restore_session(session_id: str) -> dict | None:
46
+ path = _sessions_dir() / f"{session_id}.json"
47
+ if path.exists():
48
+ data = json.loads(path.read_text(encoding="utf-8"))
49
+ _sessions[session_id] = data
50
+ return data
51
+ return None
52
+
53
+
54
+ def _delete_session(session_id: str) -> None:
55
+ path = _sessions_dir() / f"{session_id}.json"
56
+ if path.exists():
57
+ path.unlink()
58
+ _sessions.pop(session_id, None)
59
+
60
+
61
+ # ── Skill loading ──────────────────────────────────────────────────────────────
62
+
63
+ def _load_skill(mode: str) -> str:
64
+ skill_file = SKILLS_DIR / f"{mode}.md"
65
+ if skill_file.exists():
66
+ return skill_file.read_text(encoding="utf-8")
67
+ return ""
68
+
69
+
70
+ # ── Tools ──────────────────────────────────────────────────────────────────────
71
+
72
+ @mcp.tool()
73
+ def start_session(
74
+ mode: str,
75
+ cert_name: str = "",
76
+ cv: str = "",
77
+ jd: str = "",
78
+ extra_topics: str = "",
79
+ ) -> str:
80
+ """Start a new stackprep session. Returns a session ID and the skill rules for the AI to follow.
81
+
82
+ Args:
83
+ mode: "interview" or "certification"
84
+ cert_name: For certification mode — the exam name (e.g. "AWS SAA-C03")
85
+ cv: For interview mode — the user's CV/resume text
86
+ jd: For interview mode — the job description text
87
+ extra_topics: Optional comma-separated extra topics to focus on
88
+ """
89
+ if mode not in ("interview", "certification"):
90
+ return "ERROR: mode must be 'interview' or 'certification'"
91
+ if mode == "certification" and not cert_name:
92
+ return "ERROR: cert_name is required for certification mode"
93
+ if mode == "interview" and not (cv and jd):
94
+ return "ERROR: cv and jd are required for interview mode"
95
+
96
+ session_id = secrets.token_hex(6)
97
+
98
+ _sessions[session_id] = {
99
+ "mode": mode,
100
+ "cert_name": cert_name,
101
+ "extra_topics": extra_topics,
102
+ "q_num": 0,
103
+ "score": {"correct": 0, "partial": 0, "incorrect": 0, "total": 0},
104
+ "auto_flagged": [],
105
+ "flagged": [],
106
+ "ended": False,
107
+ "all_flagged": [],
108
+ }
109
+ _persist_session(session_id)
110
+
111
+ skill = _load_skill(mode)
112
+ context_lines = [f"Session ID: {session_id}", f"Mode: {mode}"]
113
+ if cert_name:
114
+ context_lines.append(f"Certification: {cert_name}")
115
+ if extra_topics:
116
+ context_lines.append(f"Extra topics: {extra_topics}")
117
+ if cv:
118
+ context_lines.append(f"\n--- CV ---\n{cv}")
119
+ if jd:
120
+ context_lines.append(f"\n--- Job description ---\n{jd}")
121
+
122
+ return "\n".join([
123
+ "=== STACKPREP SESSION STARTED ===",
124
+ "\n".join(context_lines),
125
+ "\n=== SKILL RULES (follow these exactly) ===",
126
+ skill,
127
+ ])
128
+
129
+
130
+ @mcp.tool()
131
+ def submit_answer(session_id: str, result: str, question: str = "") -> str:
132
+ """Record the result of an answered question.
133
+
134
+ Args:
135
+ session_id: The session ID
136
+ result: "correct", "partial", or "incorrect"
137
+ question: The question text (used to build the study pack)
138
+ """
139
+ session = _sessions.get(session_id) or _restore_session(session_id)
140
+ if not session:
141
+ return f"ERROR: No session found with ID '{session_id}'."
142
+
143
+ result = result.lower().strip()
144
+ if result not in ("correct", "partial", "incorrect"):
145
+ return "ERROR: result must be 'correct', 'partial', or 'incorrect'"
146
+
147
+ session["q_num"] += 1
148
+ session["score"]["total"] += 1
149
+ q_num = session["q_num"]
150
+ snippet = question[:200] if question else f"Q{q_num}"
151
+
152
+ if result == "correct":
153
+ session["score"]["correct"] += 1
154
+ elif result == "partial":
155
+ session["score"]["partial"] += 1
156
+ session["score"]["correct"] += 1
157
+ session["auto_flagged"].append(f"Q{q_num}: {snippet}")
158
+ elif result == "incorrect":
159
+ session["score"]["incorrect"] += 1
160
+ session["auto_flagged"].append(f"Q{q_num}: {snippet}")
161
+
162
+ _persist_session(session_id)
163
+
164
+ score = session["score"]
165
+ return f"Recorded Q{q_num}: {result}. Score: {score['correct']}/{score['total']}"
166
+
167
+
168
+ @mcp.tool()
169
+ def flag_for_study(session_id: str, question: str = "") -> str:
170
+ """Manually flag the current question for the study pack.
171
+
172
+ Args:
173
+ session_id: The session ID
174
+ question: The question text to flag
175
+ """
176
+ session = _sessions.get(session_id) or _restore_session(session_id)
177
+ if not session:
178
+ return f"ERROR: No session found with ID '{session_id}'."
179
+
180
+ q_num = session["q_num"]
181
+ snippet = question[:200] if question else f"Q{q_num}"
182
+ entry = f"Q{q_num}: {snippet}"
183
+ if entry not in session["flagged"]:
184
+ session["flagged"].append(entry)
185
+ _persist_session(session_id)
186
+ return f"Flagged Q{q_num} for study. Total flagged: {len(session['flagged'])}."
187
+
188
+
189
+ @mcp.tool()
190
+ def end_session(session_id: str) -> str:
191
+ """End the session. Returns the score and flagged topics so the AI can generate a study plan and study pack.
192
+
193
+ Args:
194
+ session_id: The session ID
195
+ """
196
+ session = _sessions.get(session_id) or _restore_session(session_id)
197
+ if not session:
198
+ return f"ERROR: No session found with ID '{session_id}'."
199
+
200
+ all_flagged = list(dict.fromkeys(session["auto_flagged"] + session["flagged"]))
201
+ session["ended"] = True
202
+ session["all_flagged"] = all_flagged
203
+ _persist_session(session_id)
204
+
205
+ score = session["score"]
206
+ topics_list = "\n".join(f" • {t}" for t in all_flagged) if all_flagged else " (none — full score!)"
207
+
208
+ return "\n".join([
209
+ "=== SESSION ENDED ===",
210
+ f"Score: {score['correct']}/{score['total']} "
211
+ f"({score['incorrect']} incorrect, {score['partial']} partial)",
212
+ "",
213
+ "Auto-detected study topics:",
214
+ topics_list,
215
+ "",
216
+ "Generate a Study Plan now (see skill rules), then ask the user:",
217
+ ' "Want to add any extra topics to your study pack before I save it?"',
218
+ f"Then call save_study_pack(session_id='{session_id}', name=<chosen name>, content=<generated pack>)",
219
+ ])
220
+
221
+
222
+ @mcp.tool()
223
+ def save_study_pack(session_id: str, name: str, content: str) -> str:
224
+ """Save the study pack content to disk.
225
+
226
+ Args:
227
+ session_id: The session ID (must have called end_session first)
228
+ name: Slug name for this pack, e.g. "aws-saa-week1" or "python-interview-june"
229
+ content: The full study pack markdown generated by the AI
230
+ """
231
+ session = _sessions.get(session_id) or _restore_session(session_id)
232
+ if not session:
233
+ return f"ERROR: No session found with ID '{session_id}'."
234
+ if not session.get("ended"):
235
+ return "ERROR: Call end_session first before saving a study pack."
236
+
237
+ safe_name = re.sub(r"[^a-z0-9_-]", "-", name.lower().strip())
238
+ if not safe_name:
239
+ return "ERROR: Pack name must contain at least one letter or number."
240
+
241
+ packs = _packs_dir()
242
+ pack_json_path = packs / f"{safe_name}.json"
243
+ pack_md_path = packs / f"{safe_name}.md"
244
+
245
+ pack_data = {
246
+ "name": safe_name,
247
+ "mode": session["mode"],
248
+ "score": session["score"],
249
+ "topics": session.get("all_flagged", []),
250
+ "raw_markdown": content,
251
+ }
252
+ pack_json_path.write_text(json.dumps(pack_data, indent=2, ensure_ascii=False), encoding="utf-8")
253
+ pack_md_path.write_text(f"# Study Pack: {safe_name}\n\n{content}", encoding="utf-8")
254
+
255
+ _delete_session(session_id)
256
+
257
+ return (
258
+ f"Study pack '{safe_name}' saved.\n"
259
+ f" JSON → {pack_json_path}\n"
260
+ f" Markdown → {pack_md_path}"
261
+ )
262
+
263
+
264
+ @mcp.tool()
265
+ def list_sessions() -> str:
266
+ """List all saved sessions (pending and completed).
267
+
268
+ IMPORTANT: Call this at the start of every new conversation before doing anything else.
269
+ If there are pending (non-ended) sessions, ask the user whether they want to resume one
270
+ or start a new session. Use resume_session(session_id) to continue a pending session.
271
+ """
272
+ sessions_dir = _sessions_dir()
273
+ files = sorted(sessions_dir.glob("*.json"), key=lambda f: f.stat().st_mtime, reverse=True)
274
+ if not files:
275
+ return "No saved sessions found. Use start_session to begin."
276
+
277
+ lines = [f"Saved sessions ({len(files)}):\n"]
278
+ for f in files:
279
+ try:
280
+ data = json.loads(f.read_text(encoding="utf-8"))
281
+ session_id = f.stem
282
+ mode = data.get("mode", "?")
283
+ cert = data.get("cert_name", "")
284
+ score = data.get("score", {})
285
+ ended = data.get("ended", False)
286
+ status = "completed" if ended else "IN PROGRESS"
287
+ label = f"{mode}" + (f" — {cert}" if cert else "")
288
+ lines.append(
289
+ f" • {session_id} [{label}] "
290
+ f"score: {score.get('correct','?')}/{score.get('total','?')} [{status}]"
291
+ )
292
+ except Exception:
293
+ lines.append(f" • {f.stem} [unreadable]")
294
+
295
+ pending = sum(
296
+ 1 for f in files
297
+ if not json.loads(f.read_text(encoding="utf-8")).get("ended", False)
298
+ )
299
+ if pending:
300
+ lines.append(f"\n{pending} session(s) in progress — use resume_session(session_id) to continue one.")
301
+ return "\n".join(lines)
302
+
303
+
304
+ @mcp.tool()
305
+ def resume_session(session_id: str) -> str:
306
+ """Resume a previously saved session. Returns full session state and skill rules.
307
+
308
+ Args:
309
+ session_id: The session ID to resume (from list_sessions)
310
+ """
311
+ session = _restore_session(session_id)
312
+ if not session:
313
+ return f"ERROR: No session found with ID '{session_id}'. Use list_sessions to see available sessions."
314
+ if session.get("ended"):
315
+ return f"ERROR: Session '{session_id}' has already ended. Use start_session to begin a new one."
316
+
317
+ mode = session["mode"]
318
+ skill = _load_skill(mode)
319
+ score = session["score"]
320
+ cert = session.get("cert_name", "")
321
+ q_num = session.get("q_num", 0)
322
+ auto_flagged = session.get("auto_flagged", [])
323
+
324
+ context_lines = [
325
+ f"Session ID: {session_id}",
326
+ f"Mode: {mode}",
327
+ ]
328
+ if cert:
329
+ context_lines.append(f"Certification: {cert}")
330
+ context_lines += [
331
+ f"Questions answered so far: {q_num}",
332
+ f"Score so far: {score['correct']}/{score['total']} "
333
+ f"({score['incorrect']} incorrect, {score['partial']} partial)",
334
+ ]
335
+ if auto_flagged:
336
+ context_lines.append("Topics flagged for study so far:")
337
+ for t in auto_flagged:
338
+ context_lines.append(f" • {t}")
339
+
340
+ return "\n".join([
341
+ "=== STACKPREP SESSION RESUMED ===",
342
+ "\n".join(context_lines),
343
+ "\n=== SKILL RULES (follow these exactly) ===",
344
+ skill,
345
+ ])
346
+
347
+
348
+ @mcp.tool()
349
+ def list_study_packs() -> str:
350
+ """List all saved study packs."""
351
+ packs = _packs_dir()
352
+ files = sorted(packs.glob("*.json"))
353
+ if not files:
354
+ return f"No study packs saved yet. Packs are stored in: {packs}"
355
+
356
+ lines = [f"Saved study packs ({len(files)}) — {packs}\n"]
357
+ for f in files:
358
+ try:
359
+ data = json.loads(f.read_text(encoding="utf-8"))
360
+ mode = data.get("mode", "?")
361
+ score = data.get("score", {})
362
+ lines.append(f" • {f.stem} [{mode}] score: {score.get('correct','?')}/{score.get('total','?')}")
363
+ except Exception:
364
+ lines.append(f" • {f.stem}")
365
+ return "\n".join(lines)
366
+
367
+
368
+ @mcp.tool()
369
+ def load_study_pack(name: str) -> str:
370
+ """Load a previously saved study pack by name.
371
+
372
+ Args:
373
+ name: The pack name (e.g. "aws-saa-week1"). Use list_study_packs to see available packs.
374
+ """
375
+ safe_name = re.sub(r"[^a-z0-9_-]", "-", name.lower().strip())
376
+ pack_path = _packs_dir() / f"{safe_name}.json"
377
+ if not pack_path.exists():
378
+ return f"ERROR: No study pack named '{safe_name}' found. Use list_study_packs to see available packs."
379
+
380
+ data = json.loads(pack_path.read_text(encoding="utf-8"))
381
+ return data.get("raw_markdown", json.dumps(data, indent=2))
382
+
383
+
384
+ def main() -> None:
385
+ mcp.run()
386
+
387
+
388
+ if __name__ == "__main__":
389
+ main()
@@ -0,0 +1,110 @@
1
+ ---
2
+ name: stackprep-certification
3
+ description: Certification prep skill for the stackprep MCP server. Activated when mode is "certification". Drives question generation, scoring, adaptive difficulty, and study pack creation.
4
+ ---
5
+
6
+ # stackprep — Certification Mode
7
+
8
+ Adaptive certification exam prep — one question at a time, with instant feedback and doc links.
9
+
10
+ ## Session setup
11
+
12
+ Inputs arrive via MCP (certification name, extra topics). Give a short 2–3 line summary:
13
+ - Exam structure overview
14
+ - Key domains and weightings from the latest official exam guide
15
+
16
+ Then wait — questions are requested one at a time via `next_question`.
17
+
18
+ ## Question format
19
+
20
+ Generate ONE question per turn. Never generate multiple questions at once.
21
+
22
+ **Always multiple choice:**
23
+ ```
24
+ Q. [Domain: <domain name>] <question text>
25
+ a) …
26
+ b) …
27
+ c) …
28
+ d) …
29
+ ```
30
+
31
+ Do NOT include the answer in the question. Only reveal the correct answer in the EXPLANATION after the user has replied.
32
+
33
+ ## Scoring (after each answer)
34
+
35
+ Keep scoring SHORT — 2 sentences max.
36
+
37
+ - ⚠️ Partial = ✅ Correct. Note one improvement in 1 sentence.
38
+ - ❌ Only if clearly wrong.
39
+
40
+ Always use this exact structure:
41
+
42
+ ```
43
+ RESULT: ✅ Correct OR RESULT: ⚠️ Partial OR RESULT: ❌ Incorrect
44
+
45
+ EXPLANATION: <2 sentences max>
46
+
47
+ DOCS: <Title>: <url>
48
+ ```
49
+
50
+ Always include 1 real, publicly accessible URL relevant to the topic (official docs, RFC, vendor channel).
51
+
52
+ After scoring, ask:
53
+ **"Next question? [Y] — or type S to save a Study Pack, X to exit"**
54
+
55
+ ## Adaptive difficulty
56
+
57
+ - Track topics the user gets wrong — weight subsequent questions toward those topics
58
+ - Gradually increase difficulty on topics answered correctly
59
+ - Never repeat the same question in a session
60
+
61
+ ## Study Pack
62
+
63
+ A study pack is ALWAYS offered at the end of every session, regardless of score.
64
+
65
+ The MCP server automatically tracks all ❌ Incorrect and ⚠️ Partial answers as study topics — the user does not need to flag them manually. If the user scored 100%, still offer the pack to reinforce the topics covered.
66
+
67
+ When `end_session` is called, the server shows the auto-detected topics and asks:
68
+ > "Want to add any extra topics before I generate it?"
69
+
70
+ The user can say no or list extras, then call `save_study_pack` with a chosen name to persist it to disk.
71
+
72
+ **Pack format** — produce a JSON block first, then a markdown summary:
73
+
74
+ ```json
75
+ [
76
+ {
77
+ "topic": "",
78
+ "official_docs": [{"title": "", "url": ""}],
79
+ "videos": [{"title": "", "url": ""}],
80
+ "exam_prep": [{"title": "", "url": ""}],
81
+ "summary": ""
82
+ }
83
+ ]
84
+ ```
85
+
86
+ Use only real, publicly accessible URLs. Never fabricate documentation links.
87
+
88
+ ## Exit / Study Plan
89
+
90
+ When `end_session` is called, produce a **Study Plan**:
91
+ - Topics mastered (≥ 80% correct)
92
+ - Topics to review (50–79%)
93
+ - Topics to focus on (< 50%)
94
+ - 3–5 concrete study actions per weak area
95
+
96
+ ## Quality rules
97
+
98
+ - Questions must reflect the latest stable documentation for every technology
99
+ - Never repeat identical questions in the same session
100
+ - For cert prep, always cite the exam domain and use the latest official exam guide
101
+ - Only use real, publicly accessible URLs
102
+ - **CRITICAL — answer position**: The correct answer MUST be placed at a genuinely random position (a, b, c, or d). Spread correct answers across all four letters throughout a session. Never default to b or c. If the last two questions shared the same correct letter, force a different one now.
103
+ - Never use "all of the above" or "none of the above" unless it is the only honest way to test the concept.
104
+
105
+ ## Certifications with limited official documentation
106
+
107
+ For newer or niche certifications (e.g. NVIDIA Agentic AI, emerging vendor certs) where the official exam guide is sparse:
108
+ - Draw questions from course learning objectives and community exam reports (Reddit, Discord, LinkedIn posts from recent candidates).
109
+ - Cite community sources in DOCS when no official docs exist.
110
+ - Do NOT fabricate official documentation URLs.
@@ -0,0 +1,109 @@
1
+ ---
2
+ name: stackprep-interview
3
+ description: Interview prep skill for the stackprep MCP server. Activated when mode is "interview". Drives question generation, scoring, adaptive difficulty, and study pack creation.
4
+ ---
5
+
6
+ # stackprep — Interview Mode
7
+
8
+ Adaptive technical interview prep — one question at a time, with instant feedback and doc links.
9
+
10
+ ## Session setup
11
+
12
+ Inputs arrive via MCP (CV, job description, extra topics). Give a short 2–3 line summary:
13
+ - Seniority level inferred from CV
14
+ - Key domains
15
+ - Top skill gaps vs. the job description
16
+
17
+ Then wait — questions are requested one at a time via `next_question`.
18
+
19
+ ## Question format
20
+
21
+ Generate ONE question per turn. Never generate multiple questions at once.
22
+
23
+ **Open-ended** (only for simple one-sentence answers):
24
+ ```
25
+ Q. [Conceptual | Scenario | Code | Gotcha] <question text>
26
+ ```
27
+
28
+ **Multiple choice** (when the topic requires a long or detailed answer):
29
+ ```
30
+ Q. [<domain>] <question text>
31
+ a) …
32
+ b) …
33
+ c) …
34
+ d) …
35
+ ```
36
+
37
+ Do NOT include the answer in the question. Only reveal the correct answer in the EXPLANATION after the user has replied.
38
+
39
+ ## Scoring (after each answer)
40
+
41
+ Keep scoring SHORT — 2 sentences max.
42
+
43
+ - ⚠️ Partial = ✅ Correct. Note one improvement in 1 sentence.
44
+ - ❌ Only if clearly wrong.
45
+
46
+ Always use this exact structure:
47
+
48
+ ```
49
+ RESULT: ✅ Correct OR RESULT: ⚠️ Partial OR RESULT: ❌ Incorrect
50
+
51
+ EXPLANATION: <2 sentences max>
52
+
53
+ DOCS: <Title>: <url>
54
+ ```
55
+
56
+ Always include 1 real, publicly accessible URL relevant to the topic (official docs, RFC, vendor channel).
57
+
58
+ After scoring, ask:
59
+ **"Next question? [Y] — or type S to save a Study Pack, X to exit"**
60
+
61
+ ## Adaptive difficulty
62
+
63
+ - Track topics the user gets wrong — weight subsequent questions toward those topics
64
+ - Gradually increase difficulty on topics answered correctly
65
+ - Never repeat the same question in a session
66
+
67
+ ## Study Pack
68
+
69
+ A study pack is ALWAYS offered at the end of every session, regardless of score.
70
+
71
+ The MCP server automatically tracks all ❌ Incorrect and ⚠️ Partial answers as study topics — the user does not need to flag them manually. If the user scored 100%, still offer the pack to reinforce the topics covered.
72
+
73
+ When `end_session` is called, the server shows the auto-detected topics and asks:
74
+ > "Want to add any extra topics before I generate it?"
75
+
76
+ The user can say no or list extras, then call `save_study_pack` with a chosen name to persist it to disk.
77
+
78
+ **Pack format** — produce a JSON block first, then a markdown summary:
79
+
80
+ ```json
81
+ [
82
+ {
83
+ "topic": "",
84
+ "official_docs": [{"title": "", "url": ""}],
85
+ "videos": [{"title": "", "url": ""}],
86
+ "exam_prep": [{"title": "", "url": ""}],
87
+ "summary": ""
88
+ }
89
+ ]
90
+ ```
91
+
92
+ Use only real, publicly accessible URLs. Never fabricate documentation links.
93
+
94
+ ## Exit / Study Plan
95
+
96
+ When `end_session` is called, produce a **Study Plan**:
97
+ - Topics mastered (≥ 80% correct)
98
+ - Topics to review (50–79%)
99
+ - Topics to focus on (< 50%)
100
+ - 3–5 concrete study actions per weak area
101
+
102
+ ## Quality rules
103
+
104
+ - Questions must reflect the latest stable documentation for every technology
105
+ - Never repeat identical questions in the same session
106
+ - For SQL/code, specify dialect/runtime (e.g. PostgreSQL 16, Python 3.12, dbt 1.8)
107
+ - Only use real, publicly accessible URLs
108
+ - **CRITICAL — answer position**: The correct answer MUST be placed at a genuinely random position (a, b, c, or d). Spread correct answers across all four letters throughout a session. Never default to b or c. If the last two questions shared the same correct letter, force a different one now.
109
+ - Never use "all of the above" or "none of the above" unless it is the only honest way to test the concept.
@@ -0,0 +1,241 @@
1
+ Metadata-Version: 2.4
2
+ Name: stackprep-mcp
3
+ Version: 0.1.0
4
+ Summary: stackprep as an MCP server — interview & certification prep for any AI client
5
+ Project-URL: Homepage, https://github.com/youngpada1/stackprep-mcp
6
+ Project-URL: Repository, https://github.com/youngpada1/stackprep-mcp
7
+ Author-email: Flavia Fauconnet <flavsferr@gmail.com>
8
+ License: MIT
9
+ Keywords: ai,certification,interview,mcp,prep
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Requires-Python: >=3.11
18
+ Requires-Dist: mcp[cli]>=1.0.0
19
+ Description-Content-Type: text/markdown
20
+
21
+ # stackprep-mcp
22
+
23
+ > stackprep as an MCP server — interview & certification prep for any AI client
24
+
25
+ Works with any MCP-compatible client: **Claude Code**, **Cursor**, **Cline**, **Windsurf**, **Continue.dev**, **Codex CLI**, and more. No API key required — your existing AI subscription does the work.
26
+
27
+ ---
28
+
29
+ ## What it does
30
+
31
+ stackprep-mcp is a pure state-management MCP server. It tracks your session and study packs on disk; your AI client (Claude, Cursor, Codex, etc.) handles all the question generation and scoring logic using the skill rules returned at session start.
32
+
33
+ - One question at a time — interview or certification mode
34
+ - Instant scoring with doc links after every answer
35
+ - Auto-detects wrong/partial answers and builds a named study pack
36
+ - Sessions and study packs saved to disk — resume anytime, sync via iCloud
37
+ - Resume in-progress sessions across conversations or devices
38
+
39
+ ---
40
+
41
+ ## Install
42
+
43
+ Clone the repo and install dependencies with `uv`:
44
+
45
+ ```bash
46
+ git clone https://github.com/youngpada1/stackprep-mcp
47
+ cd stackprep-mcp
48
+ uv sync
49
+ ```
50
+
51
+ > Requires [uv](https://docs.astral.sh/uv/) and Python 3.11+.
52
+
53
+ ---
54
+
55
+ ## Configure your MCP client
56
+
57
+ ### Claude Code
58
+
59
+ Create `.mcp.json` in your project root:
60
+
61
+ ```json
62
+ {
63
+ "mcpServers": {
64
+ "stackprep": {
65
+ "command": "uv",
66
+ "args": ["run", "--directory", "/path/to/stackprep-mcp", "stackprep-mcp"]
67
+ }
68
+ }
69
+ }
70
+ ```
71
+
72
+ ### Cursor
73
+
74
+ Create `~/.cursor/mcp.json`:
75
+
76
+ ```json
77
+ {
78
+ "mcpServers": {
79
+ "stackprep": {
80
+ "command": "uv",
81
+ "args": ["run", "--directory", "/path/to/stackprep-mcp", "stackprep-mcp"]
82
+ }
83
+ }
84
+ }
85
+ ```
86
+
87
+ Then open Cursor → **Cmd+Shift+J** → MCP tab — stackprep should appear with a green dot.
88
+
89
+ ### Codex CLI
90
+
91
+ Add to `~/.codex/config.yaml`:
92
+
93
+ ```yaml
94
+ mcpServers:
95
+ stackprep:
96
+ command: uv
97
+ args:
98
+ - run
99
+ - --directory
100
+ - /path/to/stackprep-mcp
101
+ - stackprep-mcp
102
+ ```
103
+
104
+ Replace `/path/to/stackprep-mcp` with the absolute path to the cloned repo.
105
+
106
+ ---
107
+
108
+ ## Study pack storage
109
+
110
+ Study packs and sessions are saved to `~/.stackprep/` by default.
111
+
112
+ **Sync across devices with iCloud** (recommended on macOS):
113
+
114
+ ```bash
115
+ # Add to ~/.zshrc or ~/.zprofile
116
+ export STACKPREP_PACKS_DIR="$HOME/Documents/stackprep-packs"
117
+ ```
118
+
119
+ `~/Documents` is synced to iCloud by default on macOS (requires iCloud Drive > Desktop & Documents enabled). Your packs will be available on any Mac signed into your Apple ID — and readable via the Files app on iPhone.
120
+
121
+ **Custom path:**
122
+
123
+ ```bash
124
+ export STACKPREP_PACKS_DIR="/path/to/your/folder"
125
+ ```
126
+
127
+ Point this at any Dropbox, Google Drive, or OneDrive folder for cross-platform sync.
128
+
129
+ ---
130
+
131
+ ## Environment variables
132
+
133
+ | Variable | Default | Description |
134
+ |---|---|---|
135
+ | `STACKPREP_PACKS_DIR` | `~/.stackprep` | Root directory for packs and sessions. |
136
+
137
+ ---
138
+
139
+ ## Skills (modes)
140
+
141
+ | Mode | Description |
142
+ |---|---|
143
+ | `certification` | description: Certification prep skill for the stackprep MCP server. Activated when mode is "certification". Drives question generation, scoring, adaptive difficulty, and study pack creation. |
144
+ | `interview` | description: Interview prep skill for the stackprep MCP server. Activated when mode is "interview". Drives question generation, scoring, adaptive difficulty, and study pack creation. |
145
+
146
+ ---
147
+
148
+ ## Tools
149
+
150
+ | Tool | Description | Args |
151
+ |---|---|---|
152
+ | `start_session` | Start a new stackprep session. Returns a session ID and the skill rules for the AI to follow. | `mode`, `cert_name`, `cv`, `jd`, `extra_topics` |
153
+ | `submit_answer` | Record the result of an answered question. | `session_id`, `result`, `question` |
154
+ | `flag_for_study` | Manually flag the current question for the study pack. | `session_id`, `question` |
155
+ | `end_session` | End the session. Returns the score and flagged topics so the AI can generate a study plan and study pack. | `session_id` |
156
+ | `save_study_pack` | Save the study pack content to disk. | `session_id`, `name`, `content` |
157
+ | `list_sessions` | List all saved sessions (pending and completed). | |
158
+ | `resume_session` | Resume a previously saved session. Returns full session state and skill rules. | `session_id` |
159
+ | `list_study_packs` | List all saved study packs. | |
160
+ | `load_study_pack` | Load a previously saved study pack by name. | `name` |
161
+
162
+ ---
163
+
164
+ ## Session flow
165
+
166
+ **Certification:**
167
+ ```
168
+ list_sessions() ← always called first
169
+ → start_session(mode="certification", cert_name="AWS SAA-C03")
170
+ → [AI generates questions one at a time]
171
+ → submit_answer(session_id, result="correct"|"partial"|"incorrect", question="...")
172
+ → ... repeat ...
173
+ → end_session(session_id)
174
+ → save_study_pack(session_id, name="aws-saa-week1", content="...")
175
+ ```
176
+
177
+ **Interview:**
178
+ ```
179
+ list_sessions() ← always called first
180
+ → start_session(mode="interview", cv="...", jd="...")
181
+ → [AI generates questions one at a time]
182
+ → submit_answer(session_id, result="correct"|"partial"|"incorrect", question="...")
183
+ → ... repeat ...
184
+ → end_session(session_id)
185
+ → save_study_pack(session_id, name="python-interview-june", content="...")
186
+ ```
187
+
188
+ **Resuming a session:**
189
+ ```
190
+ list_sessions() → shows in-progress sessions
191
+ → resume_session(session_id) → loads state + skill rules, continues where you left off
192
+ ```
193
+
194
+ **Loading a saved study pack:**
195
+ ```
196
+ list_study_packs()
197
+ → load_study_pack(name="aws-saa-week1")
198
+ ```
199
+
200
+ ---
201
+
202
+ ## Session persistence
203
+
204
+ Every session is saved to disk on every update. At the start of each new conversation the AI automatically calls `list_sessions` and asks whether you want to resume an in-progress session or start a new one. Sessions are stored in `~/.stackprep/sessions/` (or your custom `STACKPREP_PACKS_DIR`).
205
+
206
+ ---
207
+
208
+ ## Also available as a Claude Code plugin
209
+
210
+ For Claude Projects or direct Claude.ai use, the behaviour rules are also available as a standalone skill file at [plugins/stackprep](https://github.com/youngpada1/stackprep/tree/main/plugins/stackprep) — no install needed.
211
+
212
+ ---
213
+
214
+ ## Contributing / Development
215
+
216
+ ```bash
217
+ git clone https://github.com/youngpada1/stackprep-mcp
218
+ cd stackprep-mcp
219
+
220
+ # Install dependencies
221
+ uv sync
222
+
223
+ # Activate the pre-commit hook (auto-regenerates README on every commit)
224
+ git config core.hooksPath .githooks
225
+
226
+ # Run the server locally
227
+ uv run stackprep-mcp
228
+ ```
229
+
230
+ The README is auto-generated from `server.py` tool definitions and the skills files in `src/stackprep_mcp/skills/`.
231
+ To regenerate manually:
232
+
233
+ ```bash
234
+ uv run python scripts/generate_readme.py
235
+ ```
236
+
237
+ ---
238
+
239
+ ## License
240
+
241
+ MIT — [Flavia Fauconnet](https://github.com/flavsferr)
@@ -0,0 +1,8 @@
1
+ stackprep_mcp/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ stackprep_mcp/server.py,sha256=Mt4c2iLFaS7M-hOd2JRY5Ua__5lRbTw0Vv-dGxLDA44,13353
3
+ stackprep_mcp/skills/certification.md,sha256=8YcxfUM9coY93gHQVLPTgmzzptg3621p3mF9EwNkeME,3982
4
+ stackprep_mcp/skills/interview.md,sha256=DmzfwO4xMDi7OfZFfIAFswQdGO--WQtuFvaeLcPT0DU,3698
5
+ stackprep_mcp-0.1.0.dist-info/METADATA,sha256=xf2k18DvHkzi69FX5prWBYVoDkMLmUmdlfQS917Bcqw,7397
6
+ stackprep_mcp-0.1.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
7
+ stackprep_mcp-0.1.0.dist-info/entry_points.txt,sha256=XYbSPfESAJ4JDMyc2UZd9Vt8HsDzr3X6XLPT1Wz-ER8,60
8
+ stackprep_mcp-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.30.1
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ stackprep-mcp = stackprep_mcp.server:main