MCP Integration
Nenjo as an MCP server, platform tools by resource group, external MCP servers, and scope filtering.
Nenjo integrates with the Model Context Protocol (MCP) in two ways: as an MCP server that exposes platform tools, and as an MCP client that connects to external MCP servers.
Nenjo as MCP Server
The backend exposes an MCP endpoint at POST /mcp using the Streamable HTTP transport. This endpoint provides tools for reading and writing Nenjo platform resources (tickets, projects, pipelines, etc.).
Authentication
MCP requests are authenticated via API key:
curl -X POST https://your-instance.com/mcp \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/list",
"params": {}
}'API keys with empty scopes have full access. Keys with specific scopes only see tools matching those scopes. See API Keys for details on scope configuration.
Scope Filtering
Each tool is assigned a scope. The tools/list response only includes tools the authenticated key has access to. The has_scope logic works as follows:
- Empty scopes = full access to all tools
- Exact match:
tickets:readgrants access to tools with scopetickets:read - Write implies read:
tickets:writealso grants access totickets:readtools
How the Worker Uses MCP
The worker connects to the backend MCP endpoint using a full-access API key, then filters tools on the client side based on the role's platform_scopes. This means:
- Worker calls
tools/listand gets all available tools - Each tool definition includes its
scopefield - The worker filters by the role's
platform_scopes(plus anyadditional_scopesfrom active modes) - Only matching tools are registered in the agent's tool set
Platform Tools by Resource Group
Tickets
| Tool | Scope | Description |
|---|---|---|
tickets_list | tickets:read | List tickets with optional filters (project_id, status, priority, type, tags, limit, offset) |
tickets_get | tickets:read | Get a ticket by ID with full details |
tickets_create | tickets:write | Create a new ticket |
tickets_update | tickets:write | Update a ticket (partial update) |
tickets_delete | tickets:write | Delete a ticket by ID |
Projects
| Tool | Scope | Description |
|---|---|---|
projects_list | projects:read | List all accessible projects |
projects_get | projects:read | Get a project by ID |
projects_create | projects:write | Create a new project |
projects_update | projects:write | Update a project (partial update) |
Documents
| Tool | Scope | Description |
|---|---|---|
documents_list | documents:read | List documents in a project |
documents_read | documents:read | Read document content by ID |
documents_delete | documents:write | Delete a document from a project |
Pipelines
| Tool | Scope | Description |
|---|---|---|
pipelines_list | pipelines:read | List all pipelines |
pipelines_get | pipelines:read | Get a pipeline by ID with all steps and edges |
pipelines_create | pipelines:write | Create a new pipeline |
pipelines_update | pipelines:write | Update a pipeline (partial update) |
pipelines_delete | pipelines:write | Delete a pipeline by ID |
pipeline_steps_create | pipelines:write | Add a step to a pipeline |
pipeline_edges_create | pipelines:write | Add an edge between two pipeline steps |
Executions
| Tool | Scope | Description |
|---|---|---|
executions_list | executions:read | List execution runs with optional filters |
executions_get | executions:read | Get an execution run by ID |
Agents
| Tool | Scope | Description |
|---|---|---|
agents_list | agents:read | List agents, optionally filtered by project |
agents_get | agents:read | Get an agent by ID |
agents_create | agents:write | Create a new agent |
agents_update | agents:write | Update an agent (partial update) |
agents_delete | agents:write | Delete an agent by ID |
roles_list | agents:read | List all agent roles |
roles_get | agents:read | Get an agent role by ID |
Councils
| Tool | Scope | Description |
|---|---|---|
councils_list | councils:read | List all councils |
councils_get | councils:read | Get a council by ID with leader and member details |
councils_create | councils:write | Create a new council |
councils_update | councils:write | Update a council (partial update) |
councils_delete | councils:write | Delete a council by ID |
Chat
| Tool | Scope | Description |
|---|---|---|
chat_sessions_list | chat:read | List chat sessions for a project and role |
chat_messages_list | chat:read | List chat messages with optional pagination |
Graph
| Tool | Scope | Description |
|---|---|---|
graph_dependencies | graph:read | Get the dependency graph for a project |
graph_execution_order | graph:read | Get the execution order based on the dependency graph |
External MCP Servers
Nenjo can connect to external MCP servers to give agents access to third-party tools (GitHub, Linear, Slack, etc.). External servers are configured in the platform and assigned to roles.
Transport Types
| Transport | Description |
|---|---|
stdio | Child process communicating via stdin/stdout JSON-RPC |
http | Streamable HTTP transport via JSON-RPC over HTTP POST |
Server Configuration
External MCP servers are configured via the API:
# Create an external MCP server
curl -X POST -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "github",
"display_name": "GitHub MCP",
"transport": "stdio",
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"env_schema": [
{"key": "GITHUB_PERSONAL_ACCESS_TOKEN", "label": "GitHub Token", "type": "secret"}
]
}' \
https://your-instance.com/api/v1/mcp-serversFor HTTP transport servers:
{
"name": "custom-api",
"display_name": "Custom API Server",
"transport": "http",
"url": "https://mcp.example.com/rpc",
"env_schema": [
{"key": "api_key", "label": "API Key", "type": "secret"}
]
}Credential Resolution for External MCP Servers
External MCP server credentials are resolved from two sources, checked in order:
1. Environment Variables
Format: NENJO_MCP_{SERVER_NAME}_{FIELD_KEY} (uppercased, non-alphanumeric replaced with _)
export NENJO_MCP_GITHUB_GITHUB_PERSONAL_ACCESS_TOKEN="ghp_xxxxxxxxxxxx"2. Local Credentials File
~/.nenjo/credentials.toml under [mcp.{server_name}]:
[mcp.github]
GITHUB_PERSONAL_ACCESS_TOKEN = "ghp_xxxxxxxxxxxx"
[mcp.linear]
api_key = "lin_xxxxxxxxxxxx"For HTTP transport, resolved credentials named authorization, api_key, token, or bearer_token are automatically sent as Authorization: Bearer ... headers. Credentials with keys starting with header_ are injected as custom headers.
For stdio transport, resolved credentials are injected as environment variables into the child process.
Role Assignments
External MCP servers are assigned to roles to make their tools available:
# Assign an MCP server to a role
curl -X POST -H "Authorization: Bearer $TOKEN" \
https://your-instance.com/api/v1/agent-roles/:id/mcp/:server_id
# List MCP servers assigned to a role
curl -H "Authorization: Bearer $TOKEN" \
https://your-instance.com/api/v1/agent-roles/:id/mcp
# Remove an MCP server from a role
curl -X DELETE -H "Authorization: Bearer $TOKEN" \
https://your-instance.com/api/v1/agent-roles/:id/mcp/:server_idMode-Based Activation
In addition to permanent role assignments, modes can temporarily activate external MCP servers using the activate_mcp field:
{
"tools": {
"activate_mcp": ["github", "linear"]
}
}This enables workflows where an agent only has access to certain external services when operating in a specific mode.
Connection Lifecycle
The worker maintains a pool of connected external MCP servers:
- Bootstrap -- On startup, the worker connects to all assigned MCP servers
- Initialize -- Each server receives an
initializerequest with protocol version2025-03-26 - Tool discovery --
tools/listis called to discover available tools - Reconcile -- When bootstrap data changes (via NATS notification), the pool is reconciled: new servers are connected, removed servers are shut down
- Tool calls -- During execution, tool calls are proxied to the appropriate server via JSON-RPC
External MCP tools are namespaced with the server name prefix (e.g., github_create_issue) to avoid name collisions between servers.
All external MCP tools are classified as Write category by default, which means they require a mode to be active (or the understanding filter to be bypassed) for the agent to use them.