PSI Machine Intelligence MCP Server

MCP server that gives AI agents (Claude Code, Claude Desktop, custom agents) tool access to all PSI machine data — BOM, PLC programs, electrical drawings, project provenance, supply chain, fault diagnostics.


Overview

The PSI Machine Intelligence MCP Server exposes 51 tools over the Model Context Protocol standard. It connects to the UniData REST API for ERP data, reads network file shares (LDS/CAD via UNC paths) for PLC programs, electrical drawings, and engineering files, and uses Azure OpenAI for AI-powered assessments.

The vision: An engineer asks “Machine 2399 is throwing a low air pressure fault — what sensor is that, where’s the wiring diagram, and have we seen this on other machines?” and gets a complete answer.

FeatureDescription
Production URLhttp://ps-proxy:3100/mcp
SourceProgressiveSurface/psi-machine-mcp
Dev LocationC:\git\PLC\mcp-server\
RuntimeNode.js (CommonJS) + ffmpeg (for video transcription)
SDK@modelcontextprotocol/sdk v1.26.0
Tools64 tools across 14 categories
TransportStdio (local) or Streamable HTTP (network)
HTTP Port3100 (configurable via MCP_PORT env var)
DeployedPS-PROXY as Windows Service via NSSM
Auto-deployPush to master triggers GitHub Actions deploy

Architecture

┌──────────────────────────────────────────────────────────────────┐
│                         MCP Clients                              │
├──────────────────────────────────────────────────────────────────┤
│                                                                  │
│  PSI Explorer "Ask the Fleet"     Claude Code        Claude      │
│  (explorer.progressivesurface    (developer CLI)    Desktop     │
│   .com — any PSI employee)                           (GUI)      │
│        │                               │                │        │
│        │ HTTP (MCP SDK)                │ stdio           │ HTTP   │
│        ▼                               ▼                ▼        │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │   PSI Machine Intelligence MCP Server                    │    │
│  │   (Node.js, PS-PROXY:3100, 51 tools)                    │    │
│  └────┬──────────┬──────────────┬───────────────┬──────────┘    │
│       │          │              │               │                │
│       ▼          ▼              ▼               ▼                │
│  REST API    File Shares    PLC Tools      Analysis             │
│  (93+ eps)   (LDS, CAD)    (ACD, L5X)     (reports)            │
└──────────────────────────────────────────────────────────────────┘

Clients

ClientTransportUsersSetup Required
Ask the Fleet (PSI Explorer)HTTPAny PSI employeeNone — open the website
Claude CodeStdioDevelopersAdd to .mcp.json
Claude DesktopHTTPAnyone with app installedAdd to claude_desktop_config.json
Custom agentsHTTPDevelopers@modelcontextprotocol/sdk client

Ask the Fleet is the first production web application consuming the MCP server. It embeds an AI chat panel inside PSI Explorer where users ask questions in plain English. The Express.js backend connects to PS-PROXY:3100 as an MCP client, passes the 51 tools to Azure OpenAI (GPT 5.2), and streams responses back to the browser via SSE. No AI expertise or developer tools required — anyone on VPN can use it at explorer.progressivesurface.com.

Two Transport Modes

ModeFlagUse Case
Stdionode mcp-server.js (default)Single-user, local Claude Code / Claude Desktop
HTTPnode mcp-server.js --httpMulti-user, network access, remote agents

Stdio mode — Claude Code spawns the server as a child process. Communication is JSON-RPC over stdin/stdout. This is the standard MCP pattern for local tools.

HTTP mode — Runs an Express server on port 3100 supporting both the newer Streamable HTTP protocol (POST/GET/DELETE /mcp) and legacy SSE (GET /sse, POST /messages). Any MCP client on the network can connect.


Tools Reference (51 Tools)

Category 1: Project & Machine Identity

ToolDescription
get_project_infoProject metadata with resolved machine type names, customer, team names, schedule, hours
find_similar_machinesSibling projects with same machine type — paginated, with resolved machine type names, customer names, status labels
get_project_lineageREF.PROJ.NO chain — follow project ancestry (uses /api/project/dev/{job}/lineage)
list_machine_typesAll 50 machine types with project counts (uses /api/project/dev/machine-types)

Category 2: Bill of Materials & Supply Chain

ToolDescription
get_bomFull BOM explosion for a job or part number
get_part_detailsPart info including description, MRP code, cost, drawing number
get_part_manufacturerOEM manufacturer details (code, name, address, design category)
get_part_vendorsPurchase vendors with open POs, lead times, costs
search_partsFind parts by number or description keyword

Category 3: PLC & Controls

ToolDescription
get_plc_analysisFull PLC analysis (modules, tags, routines, faults). Reads from fleet archive first, falls back to ACD analyzer
analyze_acdDirectly analyze an ACD file using the Python ACD analyzer
lookup_faultSearch fault catalog by tag name, severity, or keyword — returns classified faults with abort/hold/inhibit severity. On zero results, suggests available keywords and sample faults
get_io_mapI/O cross-reference enriched with tag descriptions from analysis (tag ↔ slot ↔ device ↔ drawing ↔ BOM part)
lookup_plc_tagLook up a specific PLC tag (data type, scope, usage)
lookup_pc_read_writeDecode PC_Read[n] or PC_Write[n] from IO spreadsheet
get_robot_interfaceFANUC UOP signal mapping and fault signals
get_plc_changesChange history from plc-fleet-archive (what changed and when)
get_plc_historyVersion timeline with attribution (who changed it)
search_fleet_tagsCross-fleet tag/module/program search (88 projects, 5,423 tags)
compare_plc_versionsSide-by-side structural diff between two projects
get_fleet_summaryAggregate fleet statistics (overview, modules, changes, processors)

Category 4: Engineering Files & Drawings

ToolDescription
list_project_filesK: drive catalog for a project (categorized: PLC, robot, safety, tags, etc.)
check_commissioning_readiness9-point readiness check from K: drive scanner
find_drawingLook up drawing/part on X: drive (SolidWorks, DWG, PDF)
get_electrical_drawing_indexParse .wdp file for drawing list, check DWG and PDF availability
read_drawing_pdfRead a mechanical or electrical drawing PDF — metadata, text extraction, or binary
read_bom_fileRead a BOM export file (.xls) from X:\BOMFiles — parsed component list
get_drawing_metadataCheck what engineering files exist for a part across all CAD directories

Category 5: Change Tracking & History

ToolDescription
get_rfcsRedbook RFCs for a project (with details, status, costs)
get_vendor_historyAP payment history for a vendor
get_work_ordersWork order list for a project

Category 6: Analysis & Diagnostics

ToolDescription
diagnose_faultGiven a fault condition, trace: fault → tag → I/O → device → drawing → BOM → vendor. Reports supply chain lookup errors when vendor/manufacturer APIs fail
compare_projectsBOM delta with GT code breakdown, shared parts, schedule comparison between two projects
assess_obsolescence_riskCheck INC/OBS flags + manufacturer lifecycle for a project’s purchased parts

Category 7: Digital Thread

ToolDescription
get_digital_threadFull digital thread report for a project (runs digital_thread_generator.js)

Category 8: ACD Discovery

ToolDescription
find_acd_filesFind ACD files for a project on K: drive (9,293 ACD files across 467 projects)

Category 9: Inventory & Material Flow

ToolDescription
get_inventory_statusCurrent on-hand, allocated, available quantities + bin/lot detail for a part
get_inventory_transactionsTransaction history from INVHIST linked list (PO receipts, WO issues, cycle count adj, etc.)
get_inventory_history24-month summary: monthly balance, receipts, issues, returns, adjustments, scrap, sales
analyze_inventory_discrepancyRoot cause analysis combining all sources — flags CHRONIC_SHRINKAGE, WO_OVERPICK_LIKELY, RTS_LIKELY, etc.

Category 10: Cost Analysis & Floor Stock

ToolDescription
analyze_part_cost_leakageClassify every material transaction by WO type (Fabrication, Assembly, FloorStock, GlDirect, etc.), compute extended cost per category, and quantify cost leakage — material costs not attributed to any job
get_job_cost_attributionGiven a job number, classify all its work orders by type and show cost attribution breakdown (how many Fabrication vs Assembly vs Misc vs Floor Stock WOs)

Category 11: Customer Lookup & Sales Orders

ToolDescription
search_customersFuzzy search 4,600+ PSI customers by name — paginated (offset/limit, max 200). Returns customer number, name, location, account type. Call this first to resolve a customer name to a number before using quote/sales tools
get_open_quotesList open spare parts quotes by customer number, contact number, or contact email. Returns quote date, value, description, status (open/ordered/expired)
get_quote_detailFull detail for a specific quote: header + all line items with part numbers, pricing, quantities, and linked sales orders
get_sales_ordersOpen sales orders for a customer with linked quote numbers. Use to check if a customer PO has been received for a quote
get_sales_historyInvoiced sales history (VB_SODET.REV4): what was sold to a customer, with quantities, prices, ship dates, and 24-field line detail

Category 12: Fleet Intelligence & Templates

ToolDescription
recommend_templateGiven specs (robot type, I/O range, safety required, machine family), scores fleet candidates and returns top matches with gap analysis per area (I/O, safety, quality, robot type)
audit_safety_complianceFleet-wide safety audit: finds GuardLogix without safety task, scores below threshold, no E-stop detection, no fault aggregation
get_fleet_overviewFleet-wide statistics and trends
fleet_parsing_summarySummary of parsed/unparsed projects across the fleet
fleet_reuse_summaryModule and tag reuse analysis across projects
fleet_scorecard_summaryScorecard distribution and trends across fleet
search_fleet_summariesSearch fleet summaries by keyword or criteria
get_machine_summarySummary for a specific machine type across fleet
compare_machine_summariesCompare two machine type summaries side-by-side
get_plc_scorecardQuality scorecard for a specific project

Category 13: AI-Powered Assessment

ToolDescription
get_ai_assessmentAzure OpenAI-powered deep analysis of a PLC program — generates fault catalog with test procedures, safety assessment narrative, design quality review, and commissioning checklist. Results cached as ai_assessment.json in fleet archive. Sections: faultCatalog, safetyAssessment, designQuality, commissioningChecklist

Authentication

The HTTP transport is an OAuth 2.1 Resource Server secured with Microsoft Entra ID bearer tokens (added 2026-06-23). Every route is protected except /health and the discovery doc.

  • App registration: PSI Machine MCP — client/app id 0dfb7d0a-b815-4611-93ba-ccdf3213187b, App ID URI api://0dfb7d0a-b815-4611-93ba-ccdf3213187b.
  • Required: delegated scope Mcp.Invoke (user tokens) or app role Mcp.Invoke.App (daemon / managed-identity tokens).
  • Validation: RS256 signature via tenant JWKS, plus iss (PSI tenant), aud (the app), and scope/role checks. Missing/invalid token → 401; valid token without the scope/role → 403. Fail-closed — if MCP_APP_CLIENT_ID is unset the server rejects all protected requests.
  • Discovery: RFC 9728 Protected Resource Metadata at /.well-known/oauth-protected-resource, plus a 401 WWW-Authenticate: Bearer resource_metadata=… header.

How clients get a token

ClientMechanism
Claude CodeheadersHelper runs az account get-access-token --resource api://0dfb7d0a-… per connection (see Option 1). The Azure CLI client is pre-authorized, so any PSI user with az login gets a delegated Mcp.Invoke token.
Daemon / web backend (e.g. PSI Explorer bom-explorer-web)App Service managed identity granted the Mcp.Invoke.App app role; DefaultAzureCredential.getToken('api://0dfb7d0a-…/.default').
VS Code / CopilotStatic Authorization header via a .vscode/mcp.json input (no headersHelper support).

Network posture

Private by default — bound to the PSI network with the firewall scoped to private ranges; no public exposure. stdio connections (Option 2) bypass HTTP auth entirely (local child process, launching user’s identity). Public access for cloud clients (e.g. M365 Copilot) requires a separate public front (gateway / App Proxy) — not configured.


Setup

The MCP server runs on PS-PROXY as a Windows service, reachable from the PSI network (LAN/VPN). The HTTP transport requires an Entra bearer token (see Authentication) — an unauthenticated request returns 401.

Claude Code — use headersHelper to mint a token per connection via az (requires az login):

{
  "mcpServers": {
    "psi-machine": {
      "type": "http",
      "url": "http://ps-proxy:3100/mcp",
      "headersHelper": "az account get-access-token --resource api://0dfb7d0a-b815-4611-93ba-ccdf3213187b --query \"{Authorization:join(' ',['Bearer',accessToken])}\" -o json"
    }
  }
}

VS Code / GitHub Copilot — uses a different schema (.vscode/mcp.json, servers key, no headersHelper); supply a static Authorization header via an input (token from the same az command). See the MCP standard.

Option 2: Local Stdio (Dev/Offline)

For local development or when PS-PROXY is unavailable:

{
  "mcpServers": {
    "psi-machine": {
      "command": "node",
      "args": ["C:/git/PLC/mcp-server/mcp-server.js"]
    }
  }
}

Requirements: Node.js, network access to \\ad.ptihome.com\DFS\ shares, Python 3.11 (for ACD analysis).

Health Check

curl http://ps-proxy:3100/health
{
  "status": "ok",
  "server": "psi-machine-intelligence",
  "version": "1.0.0",
  "transport": "streamable-http",
  "activeSessions": 0,
  "uptime": 123.45
}

HTTP Endpoints

MethodPathDescription
POST/mcpInitialize session or send MCP messages (Streamable HTTP)
GET/mcpEstablish SSE stream for responses (Streamable HTTP)
DELETE/mcpTerminate session (Streamable HTTP)
GET/sseLegacy SSE connection (older MCP clients)
POST/messages?sessionId=<id>Legacy message endpoint (older MCP clients)
GET/healthServer health check (anonymous)
GET/.well-known/oauth-protected-resourceRFC 9728 OAuth discovery doc (anonymous)

All paths except /health and the discovery doc require a valid Entra bearer token (see Authentication).


Project Structure

C:\git\PLC\mcp-server\                     (source)
C:\Services\PSI.MCP.Server\                (deployed on PS-PROXY)
├── mcp-server.js           # Main server -- tool definitions + transport layer
├── package.json            # Dependencies: @modelcontextprotocol/sdk, express, xlsx, zod
├── lib/
│   ├── api-client.js       # REST API wrapper (apiGet, apiPost, apiExportAll) with 10s timeout
│   ├── file-tools.js       # File access via UNC paths (LDS, CAD shares)
│   ├── fleet-summary-tools.js  # Fleet intelligence (recommend_template, audit_safety_compliance)
│   ├── generate-summary.js     # Summary generation with fault severity classification
│   └── ai-assessment.js        # Azure OpenAI integration for AI-powered PLC assessment
├── acd_analyzer.py         # Python ACD parser (uses hutcheb/acd-tools) — see [[l5x-tools|L5X Tools]]
├── .mcp.json               # Claude Code integration config
├── deploy/
│   └── manual-deploy.ps1   # Manual deploy script for PS-PROXY
└── .github/workflows/
    ├── deploy-ps-proxy.yml # Auto-deploy on push to master
    └── diagnose.yml        # Diagnostic workflow for troubleshooting

Dependencies

PackagePurpose
@modelcontextprotocol/sdkMCP protocol implementation (stdio + HTTP transports)
expressHTTP server for network mode
xlsxRead IO spreadsheets and BOM export files
pdf-parseExtract text from mechanical/electrical drawing PDFs
zodTool input schema validation

External Dependencies

DependencyPath / URLPurpose
UniData APIhttps://api.progressivesurface.comERP data (93+ endpoints)
LDS Share\\ad.ptihome.com\DFS\LDS\PROJECT\{job}\Project folders (PLC, robot, safety, tags)
CAD Share\\ad.ptihome.com\DFS\CAD\Engineering files (187K SolidWorks, 230K DWG)
Python 3.11C:\Python311\python.exeACD file analysis (acd-tools library)
Azure OpenAIadeve-midqp8v8-eastus2.cognitiveservices.azure.comAI-powered PLC assessment (GPT 5.2)
L5X ToolsSee L5X Tools pageACD→L5X conversion, L5X parsing, fleet analysis

Note: File shares use UNC paths, not drive letters. This works on both workstations (with K:/X: mapped) and servers (PS-PROXY).


Data Flow

Example: “What’s the air pressure sensor on machine 2399?”

1. Agent calls get_project_info(2399)
   └→ GET /api/project/dev/2399/info
   └→ Returns: GE GREENVILLE, Large Auto Door, team names

2. Agent calls lookup_fault(2399, "air pressure")
   └→ Reads plc_analysis_2399.json (cached or auto-analyzed from ACD)
   └→ Returns: Fault #23, tag AirPressure_Low, Slot 4 Module 7

3. Agent calls get_io_map(2399)
   └→ Reads io_crossref_2399.json
   └→ Returns: Tag → Slot 4:7 → Drawing 359912 → BOM Part 039496

4. Agent calls get_part_details(039496)
   └→ GET /api/parts/dev/039496
   └→ Returns: SMC ISE30A Pressure Switch

5. Agent calls find_drawing(039496)
   └→ Checks X:\PDF\039496.pdf, X:\MasterDwgs\039496.slddrw
   └→ Returns: PDF available, SolidWorks drawing available

Deployment

Production (PS-PROXY)

The server runs on PS-PROXY (192.9.201.217) as a Windows service managed by NSSM.

ComponentDetail
Service namePSI.MCP.Server
Service managerNSSM 2.24 (C:\tools\nssm\nssm.exe)
Install pathC:\Services\PSI.MCP.Server\
Node.jsv22.14.0
Port3100 (TCP, firewall rule: “PSI MCP Server”)
LogsC:\Services\PSI.MCP.Server\logs\ (stdout.log, stderr.log)
Auto-startYes (SERVICE_AUTO_START)
Log rotation10 MB per file

Auto-Deploy (CI/CD)

Pushing to master on psi-machine-mcp automatically deploys to PS-PROXY via GitHub Actions:

  1. Checks out code
  2. Ensures Node.js is installed
  3. Stops existing service
  4. Copies files to C:\Services\PSI.MCP.Server\
  5. Runs npm install --production
  6. Installs NSSM if needed
  7. Registers/starts Windows service
  8. Runs health check

The workflow uses the ps-proxy self-hosted runner (org-level, labels: ps-proxy, dotnet-8, node, unidata-access).

Manual Deploy

For first-time setup or troubleshooting:

# Run on PS-PROXY as Administrator
Invoke-Command -ComputerName PS-PROXY -FilePath .\deploy\manual-deploy.ps1

Environment Variables

Set via NSSM AppEnvironmentExtra (configured by CI/CD from GitHub Actions secrets):

VariableDescription
MCP_PORTHTTP listen port (default: 3100)
MCP_AUTH_MODEentra (default) enforces Entra bearer auth; none disables it (local/dev only)
MCP_APP_CLIENT_IDClient id of the PSI Machine MCP app reg (0dfb7d0a-…). Unset → server fails closed. GitHub secret.
ENTRA_TENANT_IDTenant GUID (default = PSI tenant). Repo variable.
MCP_REQUIRED_SCOPERequired delegated scope (default Mcp.Invoke); app role Mcp.Invoke.App is also accepted. Repo variable.
MCP_ALLOWED_REMOTEComma-separated firewall source allowlist (default RFC1918 private ranges). Repo variable.
AZURE_OPENAI_ENDPOINTAzure OpenAI endpoint URL (for get_ai_assessment tool)
AZURE_OPENAI_API_KEYAzure OpenAI API key
AZURE_OPENAI_API_VERSIONAPI version (default: 2024-05-01-preview)
AZURE_OPENAI_MODELModel deployment name (default: gpt-5.2-chat)

Service Management

# On PS-PROXY:
Get-Service PSI.MCP.Server           # Check status
Start-Service PSI.MCP.Server         # Start
Stop-Service PSI.MCP.Server          # Stop
Restart-Service PSI.MCP.Server       # Restart
Get-Content C:\Services\PSI.MCP.Server\logs\stderr.log -Tail 50  # View logs

Verify

curl http://ps-proxy:3100/health