Skip to content

feat(oc_mentor): OpenClassrooms RPA + MCP server for Claude Code#8

Draft
tomgrv wants to merge 3 commits into
mainfrom
claude/alaudida-oc-mentor-rpa-POn0E
Draft

feat(oc_mentor): OpenClassrooms RPA + MCP server for Claude Code#8
tomgrv wants to merge 3 commits into
mainfrom
claude/alaudida-oc-mentor-rpa-POn0E

Conversation

@tomgrv

@tomgrv tomgrv commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Adds oc_mentor/ — a standalone TypeScript package containing a Playwright RPA connector to the OpenClassrooms mentor portal + an MCP server exposing it as tools Claude Code can call directly
  • 8 MCP tools: oc_login, oc_status, oc_list_sessions, oc_get_session, oc_update_session_notes, oc_list_projects, oc_get_project, oc_submit_evaluation
  • Auth: email/password login with 8-hour session persistence via browser storage state (~/.oc_mentor/auth-state.json)
  • Priority scope: sessions & notes, project evaluations

Intended use

This package is meant to live in its own repository (alaudida/oc_mentor). Once you create that repo on GitHub:

# Extract and push
cp -r oc_mentor/ /path/to/alaudida/oc_mentor
cd /path/to/alaudida/oc_mentor
git init && git remote add origin https://github.com/alaudida/oc_mentor.git
git add . && git commit -m "feat: initial commit"
git push -u origin main

Test plan

  • cd oc_mentor && npm install && npx playwright install chromium
  • cp .env.example .env and fill in OC_EMAIL / OC_PASSWORD
  • npm run build — TypeScript compiles with 0 errors
  • echo '{"jsonrpc":"2.0","method":"tools/list","id":1}' | node dist/index.js — returns all 8 tools
  • Add to ~/.claude/settings.json, restart Claude Code, call oc_status → returns auth state
  • Call oc_list_sessions → returns real sessions from the OC dashboard

Generated by Claude Code

tomgrv added 2 commits June 4, 2026 17:43
Adds a standalone oc_mentor/ package with 8 MCP tools backed by a
Playwright connector to the OpenClassrooms mentor portal:
- oc_login / oc_status (auth + health)
- oc_list_sessions / oc_get_session / oc_update_session_notes (sessions)
- oc_list_projects / oc_get_project / oc_submit_evaluation (evaluations)

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces oc_mentor, an OpenClassrooms mentor RPA connector and Model Context Protocol (MCP) server that automates interactions with the mentor portal using Playwright. The review feedback identifies several key improvements and bug fixes: handling <select> elements correctly to avoid Playwright errors when setting competency levels, using a dedicated profile directory for the persistent browser context to prevent workspace pollution, removing a redundant page navigation to optimize speed, and resolving relative deliverable links to absolute URLs.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +184 to +186
if (await levelInput.isVisible().catch(() => false)) {
await levelInput.fill(String(comp.level));
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

If the competency level input is a <select> element (which matches the selector [data-competency-id="..."] select), calling fill() on it will throw a Playwright error because fill() is only supported on input, textarea, or contenteditable elements. To support dropdowns, check the element's tag name and use selectOption() for <select> elements.

      if (await levelInput.isVisible().catch(() => false)) {
        const tagName = await levelInput.evaluate(el => el.tagName.toLowerCase()).catch(() => "");
        if (tagName === "select") {
          await levelInput.selectOption(String(comp.level));
        } else {
          await levelInput.fill(String(comp.level));
        }
      }

existsSync(AUTH_STATE_PATH) &&
Date.now() - statSync(AUTH_STATE_PATH).mtimeMs < SESSION_MAX_AGE_MS;

_context = await chromium.launchPersistentContext("", {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using an empty string "" as the user data directory for launchPersistentContext will cause Playwright to create the browser profile directories and cache files directly in the current working directory. This pollutes the project workspace. Instead, use a dedicated profile directory inside the existing stateDir (e.g., ~/.oc_mentor/profile).

Suggested change
_context = await chromium.launchPersistentContext("", {
const profileDir = join(stateDir, "profile");
_context = await chromium.launchPersistentContext(profileDir, {

Comment on lines +87 to +93
// OC project detail pages follow /mentor/students/{studentId}/projects/{projectId}
// We navigate via the project list and find the right one
await page.goto(`${OC_STUDENTS_URL}`, { waitUntil: "networkidle" });

// Try direct URL patterns OC might use
const directUrl = `https://openclassrooms.com/en/mentor/projects/${projectId}`;
await page.goto(directUrl, { waitUntil: "networkidle" });

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The navigation to OC_STUDENTS_URL on line 89 is redundant because the code immediately navigates to directUrl on line 93. Removing this redundant navigation will improve the tool's execution speed and reduce unnecessary network requests.

Suggested change
// OC project detail pages follow /mentor/students/{studentId}/projects/{projectId}
// We navigate via the project list and find the right one
await page.goto(`${OC_STUDENTS_URL}`, { waitUntil: "networkidle" });
// Try direct URL patterns OC might use
const directUrl = `https://openclassrooms.com/en/mentor/projects/${projectId}`;
await page.goto(directUrl, { waitUntil: "networkidle" });
// Try direct URL patterns OC might use
const directUrl = "https://openclassrooms.com/en/mentor/projects/" + projectId;
await page.goto(directUrl, { waitUntil: "networkidle" });

Comment on lines +117 to +121
const deliverableLinks: string[] = [];
for (const el of linkEls) {
const href = await el.getAttribute("href").catch(() => null);
if (href) deliverableLinks.push(href);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The collected deliverable links might be relative URLs (e.g., starting with /). Resolving them to absolute URLs using the current page's URL ensures that the MCP client/Claude can directly access them.

Suggested change
const deliverableLinks: string[] = [];
for (const el of linkEls) {
const href = await el.getAttribute("href").catch(() => null);
if (href) deliverableLinks.push(href);
}
const deliverableLinks: string[] = [];
for (const el of linkEls) {
const href = await el.getAttribute("href").catch(() => null);
if (href) {
try {
deliverableLinks.push(new URL(href, page.url()).href);
} catch {
deliverableLinks.push(href);
}
}
}

Adds src/http.ts — a Node built-in HTTP server using StreamableHTTPServerTransport
with per-session state management and optional Bearer token auth (MCP_AUTH_TOKEN).

Updates src/index.ts to switch between stdio (Claude Code) and HTTP (Claude.ai)
based on MCP_TRANSPORT=http env var or --http argv flag.

Adds start:http / dev:http npm scripts and updates README with Claude.ai
Integrations setup instructions.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant