Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
130 changes: 116 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,24 +1,41 @@
# 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.

Please self-review any PR before making a contribution.

The repo is open for change!

---

## Setup and run the server

1. Clone the repo
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`
Expand All @@ -34,7 +51,7 @@ The repo is open for change!
}
}
```

---

## Setup with Cursor

Expand All @@ -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).
---
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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();
Expand Down
104 changes: 104 additions & 0 deletions src/storage/progressStore.ts
Original file line number Diff line number Diff line change
@@ -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<string, UserProgress>;
};

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);
}
77 changes: 77 additions & 0 deletions src/tools/ProgressTool.ts
Original file line number Diff line number Diff line change
@@ -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) }
]
};
})
);
}
}