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
8 changes: 8 additions & 0 deletions .mcp.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"mcpServers": {
"ncu-mcp": {
"command": "node",
"args": ["bin/ncu-mcp.js"]
}
}
}
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ CLI tools for Node.js Core collaborators.
**DEPRECATED**: use [`git node metadata`](./docs/git-node.md#git-node-metadata)
instead.
- [`ncu-ci`](./docs/ncu-ci.md): Parse the results of a Jenkins CI run and display a summary for all the failures.
- [`ncu-mcp`](./docs/ncu-mcp.md): MCP server for using node-core-utils tools in AI agents.

## Usage

Expand Down
3 changes: 3 additions & 0 deletions bin/ncu-mcp.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { run } from '../lib/mcp_server.js';

await run();
131 changes: 131 additions & 0 deletions docs/ncu-mcp.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
# ncu-mcp

A [Model Context Protocol](https://modelcontextprotocol.io/) (MCP) server that
exposes node-core-utils tools to AI agents.

## Setup

Add the server to your MCP client configuration. Replace
`/path/to/node-core-utils` with the actual path to this repository.

### Claude Code

This repository includes `.mcp.json`, so no manual configuration is needed
when working inside it. For other projects, add to `.mcp.json`:

```json
{
"mcpServers": {
"ncu-mcp": {
"command": "node",
"args": ["/path/to/node-core-utils/bin/ncu-mcp.js"]
}
}
}
```

### Cursor

Add to `.cursor/mcp.json`:

```json
{
"mcpServers": {
"ncu-mcp": {
"command": "node",
"args": ["/path/to/node-core-utils/bin/ncu-mcp.js"]
}
}
}
```

### VS Code

Add to `.vscode/mcp.json`:

```json
{
"mcpServers": {
"ncu-mcp": {
"command": "node",
"args": ["/path/to/node-core-utils/bin/ncu-mcp.js"]
}
}
}
```

### Codex CLI

Add to `.codex/config.toml` (project) or `~/.codex/config.toml` (global,
macOS/Linux) or `%USERPROFILE%\.codex\config.toml` (global, Windows):

```toml
[mcp_servers.ncu-mcp]
command = "node"
args = ["/path/to/node-core-utils/bin/ncu-mcp.js"]
```

### Claude Desktop

Add to the config file for your platform:

- macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
- Windows: `%APPDATA%\Claude\claude_desktop_config.json`
- Linux: `~/.config/Claude/claude_desktop_config.json`

```json
{
"mcpServers": {
"ncu-mcp": {
"command": "node",
"args": ["/path/to/node-core-utils/bin/ncu-mcp.js"]
}
}
}
```

## Prerequisites

GitHub and Jenkins credentials must be configured before using the tools.
See the [README](../README.md) for setup instructions.

## Tools

### `git_node_metadata`

Fetch metadata for a Node.js pull request, including collaborator approvals,
CI status, and review information. Equivalent to
[`git node metadata`](./git-node.md#git-node-metadata).

Input:

| Parameter | Type | Description |
|---|---|---|
| `pr` | string | Pull request URL or number, e.g. `https://github.com/nodejs/node/pull/12345` or `12345` |

### `git_node_land`

Land a Node.js pull request. Equivalent to
[`git node land`](./git-node.md#git-node-land).

Input:

| Parameter | Type | Description |
|---|---|---|
| `pr` | string | Pull request URL or number |
| `yes` | boolean | Skip confirmation prompts (optional) |

### `git_node_status`

Show the status of an in-progress pull request landing. Equivalent to
[`git node status`](./git-node.md).

### `ncu_ci`

Check CI status for a Node.js pull request.

Input:

| Parameter | Type | Description |
|---|---|---|
| `pr` | string | Pull request URL or number |
3 changes: 3 additions & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ export default [
'promise/always-return': ['error', { ignoreLastCallback: true }],
'n/no-process-exit': 'off',
'n/no-unsupported-features/node-builtins': 'off',
// eslint-import-resolver-node doesn't support wildcard package.json exports (./*),
// so sub-path imports from this package appear unresolved despite being valid.
'import/no-unresolved': ['error', { ignore: ['^@modelcontextprotocol/'] }],
},
settings: {
'import/resolver': {
Expand Down
138 changes: 138 additions & 0 deletions lib/mcp_server.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import { Server } from '@modelcontextprotocol/sdk/server';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ListToolsRequestSchema
} from '@modelcontextprotocol/sdk/types.js';

import { forceRunAsync } from './run.js';

const GIT_NODE = new URL('../bin/git-node.js', import.meta.url).pathname;

function capture(cmd, args) {
return forceRunAsync(cmd, args, {
captureStdout: true,
captureStderr: true,
ignoreFailure: false
});
}

const TOOLS = [
{
name: 'git_node_metadata',
description: 'Fetch metadata for a Node.js pull request ' +
'(collaborators, CI status, reviews, etc.)',
inputSchema: {
type: 'object',
properties: {
pr: {
type: 'string',
description: 'Pull request URL or number, e.g. ' +
'https://github.com/nodejs/node/pull/12345 or 12345'
}
},
required: ['pr']
}
},
{
name: 'git_node_land',
description: 'Land a Node.js pull request (interactive landing process)',
inputSchema: {
type: 'object',
properties: {
pr: {
type: 'string',
description: 'Pull request URL or number'
},
yes: {
type: 'boolean',
description: 'Skip confirmation prompts (default: false)'
}
},
required: ['pr']
}
},
{
name: 'git_node_status',
description: 'Show the status of a Node.js pull request landing',
inputSchema: {
type: 'object',
properties: {},
required: []
}
},
{
name: 'ncu_ci',
description: 'Check CI status for a Node.js pull request or commit',
inputSchema: {
type: 'object',
properties: {
pr: {
type: 'string',
description: 'Pull request URL or number'
}
},
required: ['pr']
}
}
];

export function createServer(captureImpl = capture) {
const server = new Server(
{ name: 'ncu-mcp', version: '1.0.0' },
{ capabilities: { tools: {} } }
);

server.setRequestHandler(ListToolsRequestSchema, async() => ({ tools: TOOLS }));

server.setRequestHandler(CallToolRequestSchema, async(request) => {
const { name, arguments: args } = request.params;

try {
let output;

switch (name) {
case 'git_node_metadata': {
output = await captureImpl('node', [GIT_NODE, 'metadata', String(args.pr)]);
break;
}
case 'git_node_land': {
const landArgs = ['land', String(args.pr)];
if (args.yes) landArgs.push('--yes');
output = await captureImpl('node', [GIT_NODE, ...landArgs]);
break;
}
case 'git_node_status': {
output = await captureImpl('node', [GIT_NODE, 'status']);
break;
}
case 'ncu_ci': {
output = await captureImpl('node', [GIT_NODE, 'metadata', '--ci', String(args.pr)]);
break;
}
default:
return {
content: [{ type: 'text', text: `Unknown tool: ${name}` }],
isError: true
};
}

return {
content: [{ type: 'text', text: String(output) }]
};
} catch (err) {
return {
content: [{ type: 'text', text: `Error: ${err.message}` }],
isError: true
};
}
});

return server;
}

export async function run() {
const server = createServer();
const transport = new StdioServerTransport();
await server.connect(transport);
}
Loading
Loading