Content
# invgate-mcp
A curated [Model Context Protocol](https://modelcontextprotocol.io/) (MCP) server
that exposes **12 read-only tools** over stdio for [InvGate Asset Management](https://www.invgate.com/asset-management).
Any MCP-compatible client (Claude Desktop, etc.) can query assets, people,
computers, servers, software, and API health through natural language.
- **Transport**: stdio only (the universal default for local agents).
- **Auth**: OAuth2 client-credentials flow (`scope=read`), in-memory token
cache with a 60-second expiry buffer and one-shot 401 retry.
- **Validation**: every tool input is validated with [zod](https://zod.dev);
invalid input returns a structured `validation_error` without calling the API.
- **Resilience**: HTTP, auth, and network failures are converted to MCP text
content with `isError: true` — the server never crashes the host.
- **Distribution**: published to npm, runnable via `npx invgate-mcp`.
## Install
```bash
# Run without installing
npx invgate-mcp
# Or install globally
npm install -g invgate-mcp
invgate-mcp
```
## Configuration
The server reads three required environment variables at startup:
| Env var | Description | Example |
|---|---|---|
| `INVGATE_BASE_URL` | InvGate public API base URL (ends with `/public-api/v2`) | `https://acme.invgate.net/public-api/v2` |
| `INVGATE_CLIENT_ID` | OAuth2 client ID | `my-client-id` |
| `INVGATE_CLIENT_SECRET` | OAuth2 client secret | `my-client-secret` |
If any are missing the server prints a descriptive error to stderr and exits
with code 1.
The OAuth2 token endpoint is derived from `INVGATE_BASE_URL` by stripping the
`/public-api/v2` segment and appending `/oauth2/token/`, so
`https://acme.invgate.net/public-api/v2` → `https://acme.invgate.net/oauth2/token/`.
## Claude Desktop config
Add `invgate-mcp` to your `mcpServers` block
(`~/Library/Application Support/Claude/claude_desktop_config.json` on macOS):
```json
{
"mcpServers": {
"invgate": {
"command": "npx",
"args": ["-y", "invgate-mcp"],
"env": {
"INVGATE_BASE_URL": "https://acme.invgate.net/public-api/v2",
"INVGATE_CLIENT_ID": "your-client-id",
"INVGATE_CLIENT_SECRET": "your-client-secret"
}
}
}
}
```
## Tools
| Tool | Input | Behavior |
|---|---|---|
| `invgate_list_asset_types` | _none_ | Returns all asset types |
| `invgate_list_assets` | `page`, `per_page`, `asset_type_id`, `criteria` | Paginated asset list with optional filters |
| `invgate_get_asset` | `asset_id` (required) | Single asset by ID |
| `invgate_list_people` | `page`, `per_page`, `criteria` | Paginated people list |
| `invgate_get_person` | `person_id` (required) | Single person by ID |
| `invgate_get_person_assets` | `person_id` (required), `page`, `per_page` | Assets assigned to a person |
| `invgate_list_computers` | `page`, `per_page`, `criteria` | Assets of type "computer" |
| `invgate_get_computer` | `asset_id` (required) | Single computer asset |
| `invgate_list_servers` | `page`, `per_page`, `criteria` | Assets of type "server" |
| `invgate_get_server` | `asset_id` (required) | Single server asset |
| `invgate_get_health` | _none_ | API connectivity status |
| `invgate_list_software` | `page`, `per_page`, `criteria` | Paginated installed-software catalog |
### Parameter mapping
Tool inputs use `snake_case`; the server maps them to the InvGate API query
parameters. `per_page` → `page_size`, `asset_type_id` → `asset_types`.
The `criteria` object maps to InvGate's nested syntax:
`{ "name": "cont:MacBook" }` becomes `criteria[name]=cont:MacBook` on the
query string (`op` examples: `cont`, `eq`, `startswith`, …).
### Results & errors
- **Success**: the raw JSON response body, pretty-printed, as MCP `text` content.
- **Errors**: structured JSON `{ "error": "...", "message": "...", "status": ... }`
as text content with `isError: true`. Types:
`validation_error`, `auth_error`, `api_error`, `network_error`.
## Development
```bash
npm install
npm run build # tsup → dist/index.js (single ESM bundle)
npm run typecheck # tsc --noEmit
npm test # vitest run (59 tests, MSW for HTTP interception)
npm run test:coverage # vitest run --coverage (80% gate on src/**)
npm run dev # tsx src/index.ts
```
Requires Node.js 20+ (uses native `fetch`).
## License
MIT