@cogitx-ai/mcp-server
v0.1.0
Published
MCP server for the CogitX platform — build, inspect and run workflows and agents from any MCP-capable coding agent.
Readme
CogitX Platform MCP Server
An MCP server that lets any MCP-capable coding agent — Claude Code, Cursor, Windsurf, Zed, a custom client — create projects and build, inspect, run and delete CogitX workflows and agents.
It speaks stdio to the coding agent and HTTPS to the platform's
/mcp/v1 surface.
Every call acts as a real platform user. The credential you configure
resolves to a user, and that user's RBAC decides what the agent can reach — the
same @ResourceAccess checks the web app goes through. A credential carries no
permissions of its own, so an agent can never do something you couldn't do
yourself in the UI.
1. Choose a credential
These are two different services. Personal access tokens and login live on identity (
$IDENTITY_URL); MCP client credentials and the/mcp/v1surface live on project ($PROJECT_URL).COGITX_BASE_URLpoints at the project service.
Three work. Pick by how long it needs to live and how much it should reach.
| | Reaches | Lives | Use for |
|---|---|---|---|
| Platform JWT | everything the user can | JWT_EXPIRATION (2h) | a quick test — no setup at all |
| Personal access token | everything the user can | until revoked or expired | a developer's own machine |
| Client credential | one project, narrowed scopes | until revoked | CI, shared runners, contractors |
A. Platform JWT — zero setup
Any token from your normal login works immediately:
COGITX_TOKEN=$(curl -s -X POST "$IDENTITY_URL/identity/login" \
-H 'content-type: application/json' \
-d '{"email":"...","password":"..."}' | jq -r .token)Good for confirming the connection works. It stops working in two hours, so don't put it in a config file you intend to keep.
B. Personal access token — the normal choice
Long-lived, revocable, and scoped to you. Mint one with any valid session:
curl -X POST "$IDENTITY_URL/personal-access-tokens" \
-H "Authorization: Bearer $JWT" \
-H 'content-type: application/json' \
-d '{ "name": "Claude Code — laptop", "expiresInDays": 90 }'{
"id": "...",
"token": "cxp_… — shown once, store it now",
"tokenPrefix": "cxp_a1b2c3d4",
"expiresAt": "2026-12-02T…",
"isRevoked": false
}expiresInDays is optional (max 365); omit it for a token that does not
expire. List yours with GET $IDENTITY_URL/personal-access-tokens — only the
non-secret prefix and lastUsedAt come back, so you can tell devices apart.
Revoke with DELETE $IDENTITY_URL/personal-access-tokens/:id.
Only the SHA-256 digest is stored, so the token cannot be recovered from the database — if you lose it, revoke and mint another.
C. Project-pinned client credential — least privilege
Bound to one project, cannot create projects, and can be narrowed with scopes.
Mint it as a user holding project:update on that project:
curl -X POST "$PROJECT_URL/projects/$PROJECT_ID/mcp-clients" \
-H "Authorization: Bearer $JWT" \
-H 'content-type: application/json' \
-d '{
"clientName": "CI — nightly workflow sync",
"scopes": ["catalog:read","workflow:read","workflow:write","execution:run","execution:read"]
}'Returns clientId and a clientSecret shown once. Rotate with
POST /projects/:projectId/mcp-clients/:clientId/rotate-secret, revoke with
DELETE /projects/:projectId/mcp-clients/:clientId.
It still acts as the user who minted it — so RBAC applies on top — but can never leave its project.
Scopes
Scopes only ever narrow what RBAC already allows; they never grant anything. A token with no scopes is unrestricted, meaning "whatever this user may do".
| Scope | Grants |
|---|---|
| catalog:read | node type catalog |
| project:read / project:write | list, read, create, update projects |
| workflow:read | list/get/validate/export workflows |
| workflow:write | create/update/import workflows |
| workflow:delete | soft-delete workflows |
| agent:read / agent:write / agent:delete | agent CRUD |
| execution:run / execution:read | run a workflow, read run status |
2. Install it
Published to npm, so there is nothing to build or check out:
npx -y @cogitx-ai/mcp-server # verifies it runs; exits without credentialsEvery client config below invokes it the same way — npx -y @cogitx-ai/mcp-server
— which always fetches the current release. Pin a version if you would rather
control upgrades: npx -y @cogitx-ai/[email protected].
cd mcp
pnpm install
pnpm build # -> dist/index.jsThen use "command": "node" with "args": ["/absolute/path/to/mcp/dist/index.js"]
in place of the npx invocation.
Environment variables
Set either COGITX_TOKEN or the client credential pair. Every client
below passes these the same way — only the file they live in differs.
| Variable | Required | Notes |
|---|---|---|
| COGITX_TOKEN | one of | A cxp_… personal access token, or a platform JWT |
| COGITX_CLIENT_ID + COGITX_CLIENT_SECRET | one of | The project-pinned pair |
| COGITX_BASE_URL | no | Default http://localhost:3000 |
| COGITX_PROJECT_ID | no | Default project, so tools can omit projectId |
| COGITX_REQUEST_TIMEOUT_MS | no | Default 60000 |
COGITX_PROJECT_ID is worth setting when you mostly work in one project: tools
take an optional projectId, and without either the agent has to call
list_projects first. A pinned client credential supplies it automatically.
3. Connect your coding agent
This is a stdio MCP server: a client launches it as a subprocess and talks over stdin/stdout. Almost every MCP client accepts the same JSON block —
{
"mcpServers": {
"cogitx": {
"command": "npx",
"args": ["-y", "@cogitx-ai/mcp-server"],
"env": {
"COGITX_BASE_URL": "https://api.your-platform.com",
"COGITX_TOKEN": "cxp_9f2c..."
}
}
}
}— so for most clients the only question is which file it goes in.
npxneeds Node on the client's PATH. GUI clients (Claude Desktop, Cursor) do not always inherit a shell PATH — if the server shows as "failed to start", use the absolute path to your Node binary (which node) ascommand, withargs: ["-y", "@cogitx-ai/mcp-server"]handled by an absolutenpxpath too.
Where the config lives
| Client | File | Top-level key |
|---|---|---|
| Claude Desktop | macOS ~/Library/Application Support/Claude/claude_desktop_config.jsonWindows %APPDATA%\Claude\claude_desktop_config.json | mcpServers |
| Cursor | ~/.cursor/mcp.json (global) or .cursor/mcp.json (per repo) | mcpServers |
| Windsurf | ~/.codeium/windsurf/mcp_config.json | mcpServers |
| Cline (VS Code) | cline_mcp_settings.json — open via MCP Servers → Configure | mcpServers |
| Gemini CLI | ~/.gemini/settings.json | mcpServers |
| LM Studio | mcp.json — Program → Install → Edit mcp.json | mcpServers |
| JetBrains AI Assistant | Settings → Tools → AI Assistant → MCP (accepts the same JSON) | mcpServers |
| Warp | Settings → AI → MCP Servers → Add (accepts the same JSON) | mcpServers |
Three clients differ from the shared shape — covered below.
Claude Code
Use the CLI rather than editing a file:
claude mcp add cogitx \
--env COGITX_BASE_URL=https://api.your-platform.com \
--env COGITX_TOKEN=cxp_9f2c... \
-- npx -y @cogitx-ai/mcp-serverScope controls who gets it:
| Flag | Stored in | Use for |
|---|---|---|
| -s local (default) | your user settings, this project only | personal credential |
| -s user | your user settings, all projects | personal credential |
| -s project | .mcp.json, committed to the repo | shared team config — see the secrets note below |
Verify and manage:
claude mcp list # health of every configured server
claude mcp get cogitx # resolved command, args and env
claude mcp remove cogitxInside a session, /mcp shows connection status and the tools each server exposes.
VS Code (GitHub Copilot agent mode)
VS Code uses servers, not mcpServers, and can prompt for the secret
instead of storing it in the file. Put this in .vscode/mcp.json:
{
"inputs": [
{
"type": "promptString",
"id": "cogitx-token",
"description": "CogitX personal access token",
"password": true
}
],
"servers": {
"cogitx": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@cogitx-ai/mcp-server"],
"env": {
"COGITX_BASE_URL": "https://api.your-platform.com",
"COGITX_TOKEN": "${input:cogitx-token}"
}
}
}
}VS Code prompts once and stores the value in its secret storage, so this file is safe to commit. Then open the Chat view, switch to Agent mode, and the tools appear under the tools picker.
OpenAI Codex CLI
TOML, in ~/.codex/config.toml:
[mcp_servers.cogitx]
command = "npx"
args = ["-y", "@cogitx-ai/mcp-server"]
[mcp_servers.cogitx.env]
COGITX_BASE_URL = "https://api.your-platform.com"
COGITX_TOKEN = "cxp_9f2c..."Zed
Zed calls MCP servers context servers, configured in settings.json
(cmd-,):
{
"context_servers": {
"cogitx": {
"source": "custom",
"command": "npx",
"args": ["-y", "@cogitx-ai/mcp-server"],
"env": {
"COGITX_BASE_URL": "https://api.your-platform.com",
"COGITX_TOKEN": "cxp_9f2c..."
}
}
}
}Zed's context-server schema has changed across releases — if this is rejected,
check Zed's current docs for the context_servers shape and keep the
command / args / env values as above.
Goose
~/.config/goose/config.yaml:
extensions:
cogitx:
enabled: true
type: stdio
cmd: npx
args:
- -y
- "@cogitx-ai/mcp-server"
envs:
COGITX_BASE_URL: https://api.your-platform.com
COGITX_TOKEN: "cxp_9f2c..."Any other MCP client
If it accepts a stdio server, it needs three things:
- command —
npx - args —
["-y", "@cogitx-ai/mcp-server"] - env —
COGITX_TOKEN(or the client pair) andCOGITX_BASE_URL
Nothing else is required: no port, no URL, no OAuth callback.
Remote / hosted clients
Clients that only accept a remote URL — ChatGPT connectors, Claude.ai
custom connectors, and similar — cannot use this server today. It speaks stdio
only. Supporting them means adding a Streamable HTTP transport in front of the
same tool definitions and terminating the client-credential headers there; the
/mcp/v1 backend surface already works over HTTPS, so only the transport layer
in src/index.ts would change.
4. Keeping the secret out of your repo
The credential is bearer-style: whoever holds it has its full scope on that project. Three options, best first:
Prompted secret storage — VS Code
inputs(above). The config file carries no secret and can be committed.Shell indirection — point the config at a wrapper that reads from your keychain, so the JSON holds no literal:
#!/usr/bin/env bash # ~/bin/cogitx-mcp-launch export COGITX_TOKEN="$(security find-generic-password -s cogitx-mcp -w)" export COGITX_BASE_URL="https://api.your-platform.com" exec npx -y @cogitx-ai/mcp-serverThen
"command": "/Users/you/bin/cogitx-mcp-launch"with noenvblock.Literal in a user-scoped file — acceptable for
~/.cursor/mcp.jsonorclaude mcp add -s user, never for a committed.mcp.jsonor.cursor/mcp.json.
Issue one token per person per machine, so revoking one never disrupts
anyone else — lastUsedAt and the cxp_ prefix in GET /personal-access-tokens
tell you which is which. For anything unattended (CI, a shared runner, a
contractor) prefer a project-pinned client credential with narrow scopes over a
personal token: it cannot leave its project and cannot create new ones.
5. Verify the connection
Ask the agent:
Use the cogitx tools to show my context, then list my projects.
A healthy setup answers with your user id and auth mode, then your projects. If not:
| Symptom | Cause | Fix |
|---|---|---|
| Server not listed / "failed to start" | npx/Node not on the client's PATH | use absolute paths for command (which npx) |
| No credentials configured | no env block, or a misspelled key | set COGITX_TOKEN, or both client vars |
| 401 Invalid or expired authentication token | JWT older than 2h | mint a cxp_ personal access token instead |
| 401 Invalid or expired access token | token revoked or past expiresAt | check GET /personal-access-tokens, mint a new one |
| 401 Credential is not authorized for MCP access | you used a workflow REST API export client id | mint an MCP credential (section 1C) |
| 403 ... missing required scope(s): workflow:write | scopes too narrow | PATCH /projects/:projectId/mcp-clients/:clientId |
| 403 naming a permission like workflow:create | RBAC — the user lacks it | grant the role in the platform; the token cannot bypass this |
| 403 This credential is restricted to project … | pinned credential used elsewhere | use a personal access token |
| 403 A project-pinned credential cannot create a project | by design | use a personal access token |
| No project specified | no projectId, no COGITX_PROJECT_ID, unpinned credential | pass projectId or set the env var |
| Requests time out | wrong COGITX_BASE_URL, or platform unreachable | curl $COGITX_BASE_URL/mcp/v1/context with the same auth header |
A 403 naming a permission is real authorization, not a misconfiguration —
the fix is granting that user the role, not changing the token.
The server logs to stderr, which every client surfaces in its MCP logs
(Claude Code: claude mcp list; VS Code and Cursor: the MCP output channel).
stdout carries the protocol and never contains diagnostics.
6. Tools
Catalog — get_context, list_node_types, get_node_type
Projects — list_projects, get_project, create_project,
update_project
Workflows — list_workflows, get_workflow, validate_workflow_draft,
create_workflow, update_workflow, delete_workflow, validate_workflow,
export_workflow_definition, import_workflow_definition
Executions — execute_workflow, get_run_status
Agents — list_agents, get_agent, create_agent, update_agent,
delete_agent
Every project-scoped tool takes an optional projectId, falling back to
COGITX_PROJECT_ID and then to a pinned credential's project.
7. The authoring loop
The server's instructions steer the model through this sequence, and it is what makes one-shot authoring of a complex graph reliable:
get_context -> confirm who you are acting as, and any limits
list_projects -> pick a project (skip if COGITX_PROJECT_ID is set)
list_node_types -> learn valid `type` strings and configSchemas
validate_workflow_draft -> check the graph, read errors, fix, repeat
create_workflow -> persist once it validates
execute_workflow -> run it
get_run_status -> per-node results, timings, errorsvalidate_workflow_draft is the important one: it runs the platform's real
WorkflowValidationService against a candidate graph without saving, so the
agent iterates against genuine engine errors rather than guessing.
Graph shape
A workflow is a directed acyclic graph.
- Nodes connect via
nextarrays, or explicitedges. - For routing nodes (
conditional,parallel_router,loop) the index withinnextis the branch index; the matching edge usessourceHandle: "branch-N". joinnodes take slotted inputs viatargetHandle: "input-N".- Cycles are rejected. Use a
loopnode for iteration. nodeTypeIdis resolved server-side fromtype— the agent never handles Mongo ObjectIds.- Omit
edgesand they are derived fromnext.
Minimal example the agent can emit:
{
"name": "User Intent Routing",
"nodes": [
{ "id": "in", "type": "json_input", "next": ["detect"] },
{ "id": "detect","type": "agent", "next": ["route"],
"config": { "provider": "openai", "model": "gpt-4o-mini",
"message": "{{ nodes.in.payload.user_text }}" } },
{ "id": "route","type": "conditional", "next": ["order", "faq"],
"config": { "conditions": [
{ "name": "Order", "expression": "nodes.detect.payload.intent_type === 'order_info'", "outputIndex": 0 },
{ "name": "FAQ", "expression": "nodes.detect.payload.intent_type === 'faq'", "outputIndex": 1 }
], "defaultOutput": 1 } },
{ "id": "order","type": "json_output" },
{ "id": "faq", "type": "json_output" }
]
}8. Security notes
- RBAC is the authorization boundary. Every credential resolves to a user,
and each MCP route carries the same
@ResourceAccesscheck as its human-facing equivalent. Nothing here can exceed what that user can do in the UI, and scopes only narrow further. - Writes are attributed to that user, so version history and audit logs show a real person, not an anonymous machine.
- Personal access tokens are stored only as a SHA-256 digest, compared in
constant time, and carry a non-secret
cxp_prefix so a leaked secret is greppable and identifiable in listings. - A project-pinned credential is checked against the request path before RBAC runs, and cannot create projects — the narrower limit is enforced first.
- A workflow REST API export credential cannot be reused here: the guard
requires
metadata.source = "mcp-client". Without that check, a credential minted only to invoke a workflow would gain authoring rights. - Client secrets are scrypt-hashed with a per-secret salt; rate limiting is per client id.
- Every authentication attempt is written to the security audit log as
mcp_authenticate, with the auth mode and the acting user.
