Use portal data in your AI tools
Read courses, units, questions and tags from the beta and prod portals inside Claude (web, desktop or Code), Codex or Cursor, or from any script. One account, one token per app, and your own access level everywhere.
https://3-6-75-122.sslip.io/mcp/https://3-6-75-122.sslip.io/api1 · Get access
Request an account on Request access with your
@nxtwave.co.inemail.Send your signup code to an Admin from your own work chat. They approve you and set your role and portal access.
Create a token on Tokens, one per app. It starts with
pmcp_, is shown once, and lasts 90 days.
L1 Associate · L2 Manager · L3 Admin. Managers and Admins approve and manage access.
READ · EDIT · WRITE, set separately for beta and prod. Reading needs READ on that portal.
A token acts as you. Keep it in an environment variable, never in a sheet, a chat or a committed file.
2 · Connect your app
Replace pmcp_YOUR_TOKEN with your token. Tip: the Tokens page shows these same steps with your new token already filled in.
export PORTAL_MCP_TOKEN="pmcp_YOUR_TOKEN"
Windows PowerShell: setx PORTAL_MCP_TOKEN "pmcp_YOUR_TOKEN", then open a new terminal. Apps below read the token from this variable, so it stays out of config files.
Add the server for all your projects:
terminalclaude mcp add --transport http --scope user portal-data \ https://3-6-75-122.sslip.io/mcp/ \ --header "Authorization: Bearer $PORTAL_MCP_TOKEN"
Or as JSON. Put this in a project's
.mcp.jsonto share it with that project; each person's own token is filled in from their environment:.mcp.json{ "mcpServers": { "portal-data": { "type": "http", "url": "https://3-6-75-122.sslip.io/mcp/", "headers": { "Authorization": "Bearer ${PORTAL_MCP_TOKEN}" } } } }Check: run
claude mcp list, or/mcpinside Claude Code. Then ask “run whoami on portal-data”.
Claude Desktop's “Custom connector” screen can't send a token, so it connects through the mcp-remote bridge. Needs Node.js 18+.
Open the config: Settings → Developer → Edit Config (
claude_desktop_config.json).Add portal-data under
mcpServers, keeping any servers already there:claude_desktop_config.json{ "mcpServers": { "portal-data": { "command": "npx", "args": ["-y", "mcp-remote", "https://3-6-75-122.sslip.io/mcp/", "--header", "Authorization:${AUTH_HEADER}"], "env": { "AUTH_HEADER": "Bearer pmcp_YOUR_TOKEN" } } } }Quit and reopen Claude Desktop. portal-data appears under the tools icon in the chat box.
Works for the Codex CLI and the Codex IDE extension (they share one config). Set PORTAL_MCP_TOKEN first.
Add the server:
terminalcodex mcp add portal-data \ --url https://3-6-75-122.sslip.io/mcp/ \ --bearer-token-env-var PORTAL_MCP_TOKEN
Or edit the config (Codex uses TOML, not JSON):
~/.codex/config.toml[mcp_servers.portal-data] url = "https://3-6-75-122.sslip.io/mcp/" bearer_token_env_var = "PORTAL_MCP_TOKEN"
Check:
codex mcp list, or/mcpinside Codex. Then ask “use portal-data to run whoami”.
Add the server in Cursor Settings → Tools & MCP → New MCP Server, or edit the file directly:
~/.cursor/mcp.json{ "mcpServers": { "portal-data": { "url": "https://3-6-75-122.sslip.io/mcp/", "headers": { "Authorization": "Bearer ${env:PORTAL_MCP_TOKEN}" } } } }If it shows as unauthorized, update Cursor; if it still fails, paste the token in place of
${env:PORTAL_MCP_TOKEN}. Never put a token in a project.cursor/mcp.jsonthat's committed to git.Restart Cursor. portal-data shows a green dot under Tools & MCP. In Agent chat, ask “run whoami with portal-data”.
For claude.ai in the browser. The connector sends your token as a request header.
Open Customize → Connectors → Add custom connector.
Name and URL: give it a name (e.g. Portal MCP) and this URL:
URLhttps://3-6-75-122.sslip.io/mcp/
Authentication: choose No sign-in (not “Sign in now”, even if it shows as detected).
Request headers: add one header:
header nameX-API-Key
header valuepmcp_YOUR_TOKEN
Save, turn the connector on in a chat (the “+” / tools menu), and ask “run whoami”.
Any script or tool can call the REST API with the same token. Every data route needs env=beta or env=prod.
curl -H "Authorization: Bearer $PORTAL_MCP_TOKEN" \ "https://3-6-75-122.sslip.io/api/courses?env=beta&q=html.*css"
import os, requests
r = requests.get("https://3-6-75-122.sslip.io/api/courses",
params={"env": "beta", "q": "html.*css"},
headers={"Authorization": f"Bearer {os.environ['PORTAL_MCP_TOKEN']}"},
timeout=60)
print(r.json())const token = PropertiesService.getScriptProperties().getProperty("PORTAL_MCP_TOKEN");
const res = UrlFetchApp.fetch("https://3-6-75-122.sslip.io/api/courses?env=beta&q=" + encodeURIComponent("html.*css"),
{ headers: { Authorization: "Bearer " + token } });
Logger.log(JSON.parse(res.getContentText()));Interactive reference: https://3-6-75-122.sslip.io/api/docs (Authorize → paste token). Spec as JSON: https://3-6-75-122.sslip.io/api/openapi.json.
3 · Check it works
Ask your app to run whoami. You should see your role and your permissions on each portal:
{
"email": "name@nxtwave.co.in",
"role": "L1",
"role_name": "Associate",
"permissions": { "beta": ["READ", "EDIT", "WRITE"], "prod": ["READ", "EDIT"] },
"unread_notifications": 0
}Then ask real questions. Always name the portal; the server never guesses between beta and prod.
Search courses for "html.*css" on beta. List the units of course <course_id> on prod. Show question <question_id> on beta with its options and tags. Which units use question <question_id> on prod?
MCP tools
27 tools · read-onlyEvery data tool takes env ("beta" or "prod") and needs READ on that portal. Full input schemas: mcp-tools.json.
| Tool | What it does |
|---|---|
| You and the server | |
whoami | Who you are on this server: role, BETA/PROD permissions, and what's waiting. |
my_notifications | Your in-app updates: account decisions, access changes, approvals waiting on you. |
list_environments | List the portal environments available, so you can ask the user which to use. |
list_admin_models | Browse the surveyed admin schema: models, row counts and columns. |
get_admin_model | Get one admin model's columns, fields, relations and inlines. |
| Courses | |
search_courses | Find courses by id or by a regex over the title. |
get_course_details | Get a course's details (name, description, duration, category, link). |
list_course_versions | List a course's versions, flagging the latest. |
get_course_tree | Get a course's tree: details → ordered topics → each topic's units. |
list_course_units | List a course's units as flat rows (course, topic and unit fields together). |
diff_course_versions | Show what changed between two course versions: units added, removed or moved. |
get_course_tags | List every concept tag used across a course, with per-unit attribution. |
| Links between resources | |
get_resource_children | List what a resource links to: a course's topics, or a topic's units. |
get_resource_parents | List what links to a resource — the topics a unit sits in, and so on. |
get_unit_ancestry | Show every (topic, course) path a unit is reachable through. |
| Units | |
get_unit | Get a unit with the details specific to its type. |
get_units | Get several units at once (concurrently), in the order requested. |
get_unit_questions | List a unit's questions in order, optionally enriched. |
get_unit_slides | Get slides and reading content for a LEARNING_SET unit. |
get_unit_tags | Get a unit's own tags plus a histogram of the concept tags its questions carry. |
get_unit_tag_coverage | Check how many of each unit's questions carry each tag (unit × tag matrix). |
| Questions and tags | |
search_questions | Find questions by id, or by a regex over their short text. |
get_question | Get a question in full: metadata, body, tags, and its type-specific payload. |
get_questions | Get several questions concurrently, in the order requested. |
get_question_tags | List the concept tags attached to one question. |
get_question_usage | List the units that include a question (exams and question sets). |
get_tag_questions | List every question id carrying a tag, across all courses. |
REST API
27 routesSend Authorization: Bearer pmcp_… on every request. Try any route in the browser at https://3-6-75-122.sslip.io/api/docs: click Authorize and paste your token.
| Method | Path | Parameters * required | Returns |
|---|---|---|---|
| Meta | |||
| GET | /api/health |
none | Health |
| GET | /api/environments |
none | Environments |
| POST | /api/environments/{env}/login |
env*
|
Login |
| Schema | |||
| GET | /api/schema/models |
app, search, env
|
Schema Models |
| GET | /api/schema/models/{app_name}/{model} |
app_name*, model*, env
|
Schema Model |
| Courses | |||
| GET | /api/courses |
q*, limit, env
|
Search Courses |
| GET | /api/courses/{course_id} |
course_id*, version_id, env
|
Get Course |
| GET | /api/courses/{course_id}/versions |
course_id*, env
|
Course Versions |
| GET | /api/courses/{course_id}/tree |
course_id*, version_id, include_unit_names, unit_details, env
|
Course Tree |
| GET | /api/courses/{course_id}/units |
course_id*, version_id, unit_type, include_unit_names, env
|
Course Units |
| GET | /api/courses/{course_id}/version-diff |
course_id*, from_version, to_version, include_unit_names, env
|
Course Version Diff |
| GET | /api/courses/{course_id}/tags |
course_id*, version_id, unit_type, env
|
Course Tags |
| Links | |||
| GET | /api/resources/{resource_id}/children |
resource_id*, describe, env
|
Resource Children |
| GET | /api/resources/{resource_id}/parents |
resource_id*, describe, env
|
Resource Parents |
| GET | /api/units/{unit_id}/ancestry |
unit_id*, env
|
Unit Ancestry |
| Units | |||
| GET | /api/units/{unit_id} |
unit_id*, include_questions, env
|
Get Unit |
| POST | /api/units/batch |
env
body {"unit_ids", "detail"}
|
Get Units |
| GET | /api/units/{unit_id}/questions |
unit_id*, include_details, include_tags, include_content, full_detail, env
|
Unit Questions |
| GET | /api/units/{unit_id}/slides |
unit_id*, token, include_content, env
|
Unit Slides |
| GET | /api/units/{unit_id}/tags |
unit_id*, include_question_tags, env
|
Unit Tags |
| Questions | |||
| GET | /api/questions |
q*, limit, env
|
Search Questions |
| GET | /api/questions/{question_id} |
question_id*, include_content, include_tags, full_detail, env
|
Get Question |
| POST | /api/questions/batch |
env
body {"question_ids", "include_content", "include_tags", "full_detail"}
|
Get Questions |
| GET | /api/questions/{question_id}/tags |
question_id*, env
|
Question Tags |
| GET | /api/questions/{question_id}/usage |
question_id*, env
|
Question Usage |
| Tags | |||
| GET | /api/tags/{tag_name}/questions |
tag_name*, env
|
Tag Questions |
| POST | /api/tags/coverage |
env
body {"unit_ids", "tag_names"}
|
Tag Coverage |
JSON
Ready-to-paste JSON config for each app, and the API and tools as JSON files.
{
"mcpServers": {
"portal-data": {
"type": "http",
"url": "https://3-6-75-122.sslip.io/mcp/",
"headers": { "Authorization": "Bearer ${PORTAL_MCP_TOKEN}" }
}
}
}{
"mcpServers": {
"portal-data": {
"url": "https://3-6-75-122.sslip.io/mcp/",
"headers": { "Authorization": "Bearer ${env:PORTAL_MCP_TOKEN}" }
}
}
}{
"mcpServers": {
"portal-data": {
"command": "npx",
"args": ["-y", "mcp-remote", "https://3-6-75-122.sslip.io/mcp/", "--header", "Authorization:${AUTH_HEADER}"],
"env": { "AUTH_HEADER": "Bearer pmcp_YOUR_TOKEN" }
}
}
}Errors & fixes
| You see | Meaning | Fix |
|---|---|---|
401 · “Not signed in” · server disconnected | Token missing, mistyped, expired or revoked, or account suspended | Create a new token on Tokens; check the env variable is set where the app starts |
403 · “you don't have PROD READ” | No access to that portal | Ask a Manager or Admin to switch it on |
environment_required | Beta or prod wasn't named | Say which portal, e.g. “on beta” |
version_required | The course has several versions | Pick a version id from the list, or “all” |
422 | A required parameter is missing | Check the route in /api/docs |
| Can't sign in | Wrong password; 5 wrong tries lock the account for 15 minutes | Wait, or ask a Manager or Admin for a reset link |