Connects Claude or other MCP clients to Perforce's P4 Plan project management system through its GraphQL API. You get 28 tools covering tasks, sprints, bugs, comments, and custom fields. The server runs stateless over stdio, forwarding your JWT token with each request. Authentication happens via environment variable, so you'll need a token from the P4 Plan API before connecting. Ships as an npm package you can run with npx, or pull the Docker image if you'd rather skip Node.js locally. Requires P4 Plan API version 2026.1.002 or later and Node 20+. Built by Perforce with MIT licensing and published to both npm and the MCP registry as io.github.perforce/p4plan-mcp.
MCP (Model Context Protocol) server for P4 Plan, enabling AI assistants like Claude and VS Code Copilot to interact with P4 Plan project management data.
Architecture · Prerequisites · Quick Start · Install · Client Configurations · Tools
Skills · Logging · Troubleshoot · Development · License
This service acts as a stateless protocol adapter between MCP clients (AI assistants) and the P4 Plan GraphQL API, using stdio transport (stdin/stdout).
┌─────────────────┐ stdio (stdin/stdout) ┌─────────────────┐ GraphQL ┌─────────────────┐
│ AI Client │ ────────────────────────▶ │ P4 Plan MCP │ ──────────────▶ │ P4 Plan GraphQL │
│(Claude, Copilot)│ Spawns as child process │ Server │ Port 4000 │ API │
│ │ P4PLAN_API_AUTH_TOKEN │ (stateless) │ Bearer token │ │
└─────────────────┘ env var └─────────────────┘ forwarded └─────────────────┘
The client spawns the MCP server as a child process. Authentication is provided via the P4PLAN_API_AUTH_TOKEN environment variable, which the server validates at startup and forwards to the GraphQL API on every tool call.
| Requirement | Version | Notes |
|---|---|---|
| Node.js | >= 20 (24+ recommended) | The server targets ES2023. Check with node -v. |
| npm | >= 9 | Comes with Node.js. Check with npm -v. |
| P4 Plan API | >= 2026.1.002 | Required for all tool operations. Earlier versions are not supported. |
Tip: Use nvm to manage Node.js versions, or skip the Node.js requirement entirely by using Docker.
The fastest way to get started — no installation required. Just configure your MCP client to use npx:
npx -y @perforce/p4plan-mcp
npx automatically downloads and runs the latest version of the server. Your MCP client (VS Code, Claude Desktop, etc.) handles this for you — just add the config below and start chatting.
VS Code — add to .vscode/mcp.json:
{
"servers": {
"p4-plan": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@perforce/p4plan-mcp"],
"env": {
"P4PLAN_API_AUTH_TOKEN": "YOUR_JWT_TOKEN",
"P4PLAN_API_URL": "http://localhost:4000"
}
}
}
}
Claude Desktop — add to your config:
{
"mcpServers": {
"p4-plan": {
"command": "npx",
"args": ["-y", "@perforce/p4plan-mcp"],
"env": {
"P4PLAN_API_AUTH_TOKEN": "YOUR_JWT_TOKEN",
"P4PLAN_API_URL": "http://localhost:4000"
}
}
}
}
Note: The
-yflag auto-confirms the npm install prompt so the server starts without user interaction.
See Client Configuration for more options including Docker, secure token prompts, and local builds.
This server is published to the official MCP Registry as io.github.perforce/p4plan-mcp. Discover it via the registry API:
curl 'https://registry.modelcontextprotocol.io/v0/servers?search=io.github.perforce/p4plan-mcp'
MCP clients that support registry discovery can install the server by name without needing to know the npm package identifier.
For development or when you want to run from a local clone:
npm ci
npm run build
# For using npx locally
npm link
Run the MCP server via Docker instead of installing Node.js locally. The MCP client (VS Code, Claude Desktop) spawns the container as a child process — same as npx, just using docker as the command.
The image is published to Docker Hub at perforce/p4plan-mcp, built multi-arch (linux/amd64 + linux/arm64) on every release, with SLSA provenance and SBOM attestations.
VS Code (.vscode/mcp.json):
{
"servers": {
"p4-plan": {
"type": "stdio",
"command": "docker",
"args": [
"run", "-i", "--rm",
"-e", "P4PLAN_API_AUTH_TOKEN=YOUR_JWT_TOKEN",
"-e", "P4PLAN_API_URL=http://host.docker.internal:4000",
"perforce/p4plan-mcp:latest"
]
}
}
}
Claude Desktop:
{
"mcpServers": {
"p4-plan": {
"command": "docker",
"args": [
"run", "-i", "--rm",
"-e", "P4PLAN_API_AUTH_TOKEN=YOUR_JWT_TOKEN",
"-e", "P4PLAN_API_URL=http://host.docker.internal:4000",
"perforce/p4plan-mcp:latest"
]
}
}
}
Note: Use
host.docker.internal(macOS/Windows) or172.17.0.1(Linux) to reach the P4 Plan GraphQL API running on the host machine.
Pin a specific version by replacing
:latestwith:2026.2.0(or whichever tag) for reproducible deployments.
Build locally (for development against unreleased changes):
docker build -t p4plan-mcp:dev .
# then swap "perforce/p4plan-mcp:latest" for "p4plan-mcp:dev" in the configs above
Copy the example config and configure:
cp config-example.env .env
Edit .env with your settings:
# JWT token for authenticating with P4 Plan GraphQL API
P4PLAN_API_AUTH_TOKEN=your-jwt-token
# P4 Plan GraphQL API URL
P4PLAN_API_URL=http://localhost:4000
# Logging level
LOG_LEVEL=debug
# Search results limit (default: 400)
# SEARCH_LIMIT=400
# Allow self-signed TLS certificates (for HTTPS APIs with untrusted certs)
# P4PLAN_ALLOW_SELF_SIGNED_CERTS=true
The server communicates via stdin/stdout. It is not meant to be run interactively — MCP clients (VS Code, Claude Desktop) spawn it as a child process automatically.
The MCP server requires a JWT token provided via the P4PLAN_API_AUTH_TOKEN environment variable. The token is validated at startup and forwarded to the P4 Plan GraphQL API on every tool call. No sessions or state are maintained.
Get a JWT token from the P4 Plan GraphQL API using curl. You can authenticate with either your password or a Personal Access Token (PAT):
curl -s -X POST http://localhost:4000/graphql \
-H "Content-Type: application/json" \
-d '{
"query": "mutation Login($loginUserInput: LoginUserInput!) { login(loginUserInput: $loginUserInput) { access_token } }",
"variables": { "loginUserInput": { "username": "YOUR_USERNAME", "password": "YOUR_PASSWORD" } }
}'
A PAT can be used in place of your password in the same login mutation. This avoids exposing your actual password:
curl -s -X POST http://localhost:4000/graphql \
-H "Content-Type: application/json" \
-d '{
"query": "mutation Login($loginUserInput: LoginUserInput!) { login(loginUserInput: $loginUserInput) { access_token } }",
"variables": { "loginUserInput": { "username": "YOUR_USERNAME", "password": "YOUR_PERSONAL_ACCESS_TOKEN" } }
}'
Both methods return the same response:
{
"data": {
"login": {
"access_token": "eyJhbGciOiJIUzI1NiIs..."
}
}
}
Copy the access_token value and use it in your MCP client configuration.
Note: JWT tokens expire. When your token expires, the server will fail to start with an authentication error. Generate a new JWT using the same
curlcommand above.
list_projects - List all active projects the user is a member ofget_project - Get project configuration including archivedStatus, backlog ID, and QA IDprojectIdget_my_tasks - Get tasks assigned to current user (todoList)showCompleted, showOnlyNextFourWeeks, showHidden, showPipelineTasksThatCannotStartget_tasks - Get detailed information for one or more items by ID (max 20)taskIds (array of strings, max 20)search_tasks - Search for items in a project section using P4 Plan Find queriesfindQuery, projectIdread_skill with skillName="search-queries" first to get exact column names, operators, and value formats. For simple name search use Itemname:Text("text"). Supports filtering by status, assignee, severity, item type, dates, boolean conditions, and combinations with AND/OR/NOT.create_item - Create any item typebacklog_task, bug, scheduled_task, sprint, release, sprint_tasktype, name, projectId, parentItemId, previousItemId, and type-specific fieldsupdate_item - Update any item (auto-detects type)itemId, plus any updatable fields (name, status, assignedTo, points, etc.)commit_to_sprint - Commit a backlog task or bug to a sprinttaskId, sprintIduncommit_from_sprint - Remove a task from a sprint (return to backlog)taskIdget_custom_columns - Get custom column definitions available in a projectprojectIdget_custom_fields - Get custom field values set on a tasktaskId, onlySetset_custom_field - Set a custom field value on a tasktaskId, columnId, valueget_workflows - Get workflow definitions and status IDs for a projectprojectIdcomplete_task - Mark a task as completedtaskIdstart_task - Mark a task as in progresstaskIdget_comments - Get all comments on a tasktaskIdpost_comment - Post a new comment on a tasktaskId, textupdate_comment - Edit an existing commenttaskId, commentId, textdelete_comment - Delete a comment from a tasktaskId, commentIdget_attachments - Get all attachments on a tasktaskIddownload_attachment - Download and return attachment file contenttaskId, pathdelete_attachment - Delete an attachment from a tasktaskId, pathset_cover_image - Set or unset the cover image for a tasktaskId, imagePathlink_items - Create internal or external linksfromItemId, toItemId or url, relation (blocks, duplicates, relatedTo)unlink_items - Remove an internal or external linkfromItemId, toItemId or urlget_current_user - Get current user informationlist_project_users - List users in a projectprojectIdread_skill - Read a P4 Plan skill document at runtimeskillNameskillName="search-queries" before composing any findQuery for search_tasks.project-navigation, search-queries, task-management, planning, backlog-refinement, bug-tracking, custom-fields, gantt-scheduling, workflowsCreate .vscode/mcp.json in your workspace:
{
"servers": {
"p4-plan": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@perforce/p4plan-mcp"],
"env": {
"P4PLAN_API_AUTH_TOKEN": "YOUR_JWT_TOKEN",
"P4PLAN_API_URL": "http://localhost:4000"
}
}
}
}
Note: The
-yflag auto-confirms the npm install prompt so the server starts without user interaction.
If running from a local clone instead of npm:
{
"servers": {
"p4-plan": {
"type": "stdio",
"command": "node",
"args": ["/path/to/MCP/dist/main.js"],
"env": {
"P4PLAN_API_AUTH_TOKEN": "YOUR_JWT_TOKEN",
"P4PLAN_API_URL": "http://localhost:4000"
}
}
}
}
For added security, you can use VS Code input prompts to avoid storing tokens in files:
{
"inputs": [
{
"type": "promptString",
"id": "p4-plan-jwt",
"description": "P4 Plan JWT Token",
"password": true
}
],
"servers": {
"p4-plan": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@perforce/p4plan-mcp"],
"env": {
"P4PLAN_API_AUTH_TOKEN": "${input:p4-plan-jwt}",
"P4PLAN_API_URL": "http://localhost:4000"
}
}
}
}
Add to ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or %APPDATA%\Claude\claude_desktop_config.json (Windows):
{
"mcpServers": {
"p4-plan": {
"command": "npx",
"args": ["-y", "@perforce/p4plan-mcp"],
"env": {
"P4PLAN_API_AUTH_TOKEN": "YOUR_JWT_TOKEN",
"P4PLAN_API_URL": "http://localhost:4000"
}
}
}
}
Replace YOUR_JWT_TOKEN with a token obtained from the login mutation (see Obtaining a JWT Token).
Create .mcp.json in your project root:
{
"mcpServers": {
"p4-plan": {
"command": "npx",
"args": ["-y", "@perforce/p4plan-mcp"],
"env": {
"P4PLAN_API_AUTH_TOKEN": "YOUR_JWT_TOKEN",
"P4PLAN_API_URL": "http://localhost:4000"
}
}
}
}
For a local build, replace "command" and "args" with:
{
"mcpServers": {
"p4-plan": {
"command": "node",
"args": ["/path/to/MCP/dist/main.js"],
"env": {
"P4PLAN_API_AUTH_TOKEN": "YOUR_JWT_TOKEN",
"P4PLAN_API_URL": "http://localhost:4000"
}
}
}
}
Or add it via the CLI:
claude mcp add p4-plan \
-e P4PLAN_API_AUTH_TOKEN=YOUR_JWT_TOKEN \
-e P4PLAN_API_URL=http://localhost:4000 \
-- npx -y @perforce/p4plan-mcp
Note: The server name must come before the
-eflags, otherwise the variadic-eparser consumes the name as an env value.
Verify it's running: type /mcp inside Claude Code to check server status.
Tip: Place
.mcp.jsonin your project root to share the config with your team (tokens excluded). For personal config, add the server to~/.claude.jsoninstead.
.vscode/mcp.jsonP4PLAN_API_AUTH_TOKEN - JWT token for authenticating with the P4 Plan GraphQL APIP4PLAN_API_URL - P4 Plan GraphQL API URL (default: http://localhost:4000)P4PLAN_ALLOW_SELF_SIGNED_CERTS - Set to true to accept self-signed or untrusted TLS certificates when connecting to the API over HTTPS (default: false)LOG_LEVEL - Logging level: debug, info, warn, error (default: debug)SEARCH_LIMIT - Maximum number of results returned by search_tasks (default: 400)The server includes skill files — domain-specific guides that help AI agents construct correct tool calls. Skills are accessible in two ways:
read_skill tool — any MCP client can call read_skill with a skillName to fetch skill content at runtime. This is the primary access method and works with all clients.skill://p4-plan/search-queries) for clients that support native resource reading.| Skill | Purpose |
|---|---|
| project-navigation | Finding projects, items, and getting started |
| search-queries | P4 Plan Find query syntax (column names, values, operators) |
| task-management | Task CRUD, status, assignments, comments, attachments |
| planning | Sprints, releases, commitment, allocations |
| backlog-refinement | Backlog items, estimation, prioritization |
| bug-tracking | Bugs, severity, QA section |
| custom-fields | Custom columns, project-specific metadata |
| gantt-scheduling | Scheduled tasks, timeline, dependencies |
| workflows | Workflows, pipelines, status state machines |
See skills/README.md for details on using skills with different AI clients.
To test the npx experience locally without publishing to npm:
# Build and create a global symlink
npm run build
npm link
# Now test exactly as an end user would
P4PLAN_API_AUTH_TOKEN=your-jwt-token npx @perforce/p4plan-mcp
# Clean up when done
npm unlink -g @perforce/p4plan-mcp
The symlink persists across rebuilds — just run npm run build after code changes.
Create or edit a tools file in src/tools/
Define the tool with:
name: Unique tool identifierdescription: What the tool does (shown to AI)inputSchema: JSON Schema for parametershandler: Function that executes the toolRegister in ToolsModule
# Unit tests
npm run test
# E2E tests (MCP protocol compliance via @modelcontextprotocol/sdk)
npm run test:e2e
The server uses Winston with two transports:
warn and error level messages are shown to keep the output clean.logs/P4PlanMCP_<timestamp>.log. Use these for detailed troubleshooting.Note: All console output goes to stderr (never stdout) because stdout is the MCP protocol channel. VS Code labels all stderr output as
[warning]— this is expected behavior and does not indicate a problem.
This server uses the MCP stdio transport — communication happens over stdin/stdout using JSON-RPC 2.0 messages. The client spawns the server as a child process.
P4PLAN_API_AUTH_TOKEN environment variable (validated at startup)@modelcontextprotocol/sdk with StdioServerTransportCheck P4PLAN_API_AUTH_TOKEN is set:
The server requires a valid JWT token in the P4PLAN_API_AUTH_TOKEN environment variable. If missing, it exits immediately with an error.
Check token is valid: If the token is expired or invalid, the server exits with "Authentication failed". Generate a new JWT using the login mutation.
Check GraphQL API is reachable:
The P4 Plan GraphQL API must be accessible at the configured P4PLAN_API_URL (default: http://localhost:4000).
Verify mcp.json syntax:
Ensure your .vscode/mcp.json is valid JSON. Check for trailing commas.
Check the command is available:
If using npx, ensure Node.js is in VS Code's PATH. If npx is not found, use the absolute path to node instead (see local build config).
Reload VS Code window:
Press Cmd+Shift+P → "Developer: Reload Window"
Start a new chat: MCP servers are connected when a new chat session starts.
Check GraphQL server is running: The P4 Plan GraphQL API must be accessible at the configured URL.
Check server logs:
In VS Code, check the Output panel → select "p4-plan" from the dropdown to see warnings/errors. For full debug logs, check the logs/ directory.
Verify the JWT hasn't expired: Generate a new JWT if needed and update your mcp.json config.
This project is licensed under the MIT License. See LICENSE for details.
P4PLAN_API_AUTH_TOKEN*secretJWT bearer token for the P4 Plan GraphQL API. Forwarded as Authorization on every tool call.
P4PLAN_API_URLURL of the P4 Plan GraphQL API (default: http://localhost:4000).
P4PLAN_ALLOW_SELF_SIGNED_CERTSSet to 'true' to accept self-signed TLS certificates from the GraphQL API. Default: false.