From d31a087a7196226c5e804bec90110ecb0c38c847 Mon Sep 17 00:00:00 2001 From: "Y." Date: Mon, 5 Jan 2026 06:02:37 +0530 Subject: [PATCH] Progress tracking feature --- README.md | 130 +++++++++++++++++++++++++++++++---- src/index.ts | 2 + src/storage/progressStore.ts | 104 ++++++++++++++++++++++++++++ src/tools/ProgressTool.ts | 77 +++++++++++++++++++++ 4 files changed, 299 insertions(+), 14 deletions(-) create mode 100644 src/storage/progressStore.ts create mode 100644 src/tools/ProgressTool.ts diff --git a/README.md b/README.md index ea2784f..4d55675 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,11 @@ # mcp-server + An MCP server for InterviewReady. -1. GET ACTIONS: Exposes APIs to fetch the most relevant content from InterviewReady including blogs, resources and course materials. -2. UPDATE ACTIONS: Allows adding notes to a user's notepad, and setting google reminders for future classes. +1. **GET ACTIONS**: Exposes APIs to fetch the most relevant content from InterviewReady including blogs, resources and course materials. +2. **UPDATE ACTIONS**: Allows adding notes to a user's notepad, setting google reminders for future classes, and tracking learning progress. -Please feel free to add / change capabilities of this repo. +Please feel free to add / change capabilities of this repo. If you have doubts, please post them in discussions. If there are any problems/issues, please create an issue. @@ -12,6 +13,7 @@ Please self-review any PR before making a contribution. The repo is open for change! +--- ## Setup and run the server @@ -19,6 +21,21 @@ The repo is open for change! 2. Run `pnpm install` 3. Run `pnpm run build` +--- + +## Environment Variables + +Create a `.env` file in the project root: + +``` +BASE_URL=https://interviewready.io +API_URL=https://api.interviewready.io/api +``` + +These values must also be provided in the MCP client configuration. + +--- + ## Setup with Claude Desktop 1. Edit the config file for claude desktop `claude_desktop_config.json` @@ -34,7 +51,7 @@ The repo is open for change! } } ``` - +--- ## Setup with Cursor @@ -44,19 +61,104 @@ The repo is open for change! ```json { - "interviewready-mcp-server": { - "command": "node", - "args": [ - "{path-to-repo}/mcp-server/build/index.js" - ] - } -} + "interviewready-mcp-server": { + "command": "node", + "args": [ + "{path-to-repo}/mcp-server/build/index.js" + ], + "env": { + "BASE_URL": "https://interviewready.io", + "API_URL": "https://api.interviewready.io/api" + } + } +} ``` + 4. Use in agent mode. For some reason, MCP is not working in Ask mode. +--- + ## Reminder Tools -- **create-reminder:** Set a reminder for an upcoming class or deadline. -- **get-reminders:** Fetch all reminders. +* **create-reminder**: Set a reminder for an upcoming class or deadline. +* **get-reminders**: Fetch all reminders. + +Use these tools from any MCP-compatible client (such as Cursor or Claude Desktop). + +--- + +## Progress Tracking Tools + +Track completion progress, study streaks and learning goals. + +--- + +### `set-learning-goals` + +Set weekly and monthly learning targets for a user. + +#### Parameters + +* `userId` — string +* `weekly` — number (optional) +* `monthly` — number (optional) + +#### Example Output + +```json +{ + "completed": [], + "goals": { "weekly": 5, "monthly": 20 }, + "streakDays": 0 +} +``` + +--- + +### `mark-content-completed` + +Mark a piece of content (blog/resource/course) as completed. + +#### Parameters + +* `userId` — string +* `contentId` — string +* `type` — "blog" | "resource" | "course" + +#### Example Output + +```json +{ + "completed": [ + { "contentId": "blog-1", "type": "blog", "timestamp": 1767572472955 } + ], + "goals": { "weekly": 5, "monthly": 20 }, + "streakDays": 1, + "lastActivityDate": "Mon Jan 05 2026" +} +``` + +--- + +### `get-progress` + +Fetch a user's current learning progress. + +#### Parameters + +* `userId` — string + +#### Example Output + +```json +{ + "completed": [ + { "contentId": "blog-1", "type": "blog", "timestamp": 1767572472955 } + ], + "goals": { "weekly": 5, "monthly": 20 }, + "streakDays": 1, + "lastActivityDate": "Mon Jan 05 2026" +} +``` -Use these tools from any MCP-compatible client (such as Cursor or Claude Desktop). \ No newline at end of file +--- \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 5938bb9..bc5742d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,6 +4,7 @@ import { CoursesTool } from "./tools/CourseTool.js"; import { BlogTool } from "./tools/BlogTool.js"; import { ResourceTool } from "./tools/ResourceTool.js"; import { ReminderTool } from "./tools/ReminderTool.js"; +import { ProgressTool } from "./tools/ProgressTool.js"; const server = new McpServer({ name: "interviewready", @@ -18,6 +19,7 @@ new CoursesTool(server) new BlogTool(server) new ResourceTool(server) new ReminderTool(server) +new ProgressTool(server) async function main() { const transport = new StdioServerTransport(); diff --git a/src/storage/progressStore.ts b/src/storage/progressStore.ts new file mode 100644 index 0000000..27af07a --- /dev/null +++ b/src/storage/progressStore.ts @@ -0,0 +1,104 @@ +import fs from "fs"; +import path from "path"; + +const DATA_FILE = path.join(process.cwd(), "data-progress.json"); + +type CompletionEntry = { + contentId: string; + type: string; + timestamp: number; +}; + +type Goals = { + weekly?: number; + monthly?: number; +}; + +type UserProgress = { + completed: CompletionEntry[]; + goals: Goals; + streakDays: number; + lastActivityDate?: string; +}; + +type Store = { + users: Record; +}; + +function loadStore(): Store { + try { + const raw = fs.readFileSync(DATA_FILE, "utf8"); + return JSON.parse(raw); + } catch { + return { users: {} }; + } +} + +function saveStore(store: Store) { + fs.writeFileSync(DATA_FILE, JSON.stringify(store, null, 2)); +} + +export function getUser(userId: string): UserProgress { + const store = loadStore(); + + if (!store.users[userId]) { + store.users[userId] = { + completed: [], + goals: {}, + streakDays: 0, + }; + saveStore(store); + } + + return store.users[userId]; +} + +export function markCompleted(userId: string, contentId: string, type: string) { + const store = loadStore(); + const user = getUser(userId); + + // avoid duplicates + if (!user.completed.find(c => c.contentId === contentId)) { + user.completed.push({ + contentId, + type, + timestamp: Date.now(), + }); + } + + // streak logic + const today = new Date().toDateString(); + + if (user.lastActivityDate === today) { + // no change + } else { + const yesterday = new Date(Date.now() - 86400000).toDateString(); + if (user.lastActivityDate === yesterday) { + user.streakDays += 1; + } else { + user.streakDays = 1; + } + user.lastActivityDate = today; + } + + store.users[userId] = user; + saveStore(store); + + return user; +} + +export function setGoals(userId: string, goals: Goals) { + const store = loadStore(); + const user = getUser(userId); + + user.goals = goals; + + store.users[userId] = user; + saveStore(store); + + return user; +} + +export function getProgress(userId: string) { + return getUser(userId); +} diff --git a/src/tools/ProgressTool.ts b/src/tools/ProgressTool.ts new file mode 100644 index 0000000..e2ed784 --- /dev/null +++ b/src/tools/ProgressTool.ts @@ -0,0 +1,77 @@ +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { z } from "zod"; +import { withError } from "../middleware/withError.js"; + +import { + markCompleted, + getProgress, + setGoals +} from "../storage/progressStore.js"; + +export class ProgressTool { + constructor(private readonly server: McpServer) { + this.server = server; + this.registerTools(); + } + + private registerTools() { + + // 1. mark completed + this.server.tool( + "mark-content-completed", + "Mark a blog/resource/course as completed", + { + userId: z.string(), + contentId: z.string(), + type: z.enum(["blog", "resource", "course"]) + }, + withError(async ({ userId, contentId, type }) => { + const data = markCompleted(userId, contentId, type); + + return { + content: [ + { type: "text", text: JSON.stringify(data) } + ] + }; + }) + ); + + // 2. get progress + this.server.tool( + "get-progress", + "Get learning progress & streak", + { + userId: z.string(), + }, + withError(async ({ userId }) => { + const data = getProgress(userId); + + return { + content: [ + { type: "text", text: JSON.stringify(data) } + ] + }; + }) + ); + + // 3. set goals + this.server.tool( + "set-learning-goals", + "Set weekly/monthly learning goals", + { + userId: z.string(), + weekly: z.number().optional(), + monthly: z.number().optional(), + }, + withError(async ({ userId, weekly, monthly }) => { + const data = setGoals(userId, { weekly, monthly }); + + return { + content: [ + { type: "text", text: JSON.stringify(data) } + ] + }; + }) + ); + } +}