@procurementexpress.com/mcp 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +376 -0
- package/dist/api-client.d.ts +31 -0
- package/dist/api-client.js +108 -0
- package/dist/auth.d.ts +29 -0
- package/dist/auth.js +59 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +119 -0
- package/dist/tool-helpers.d.ts +34 -0
- package/dist/tool-helpers.js +26 -0
- package/dist/tools/approval-flows.d.ts +3 -0
- package/dist/tools/approval-flows.js +121 -0
- package/dist/tools/budgets.d.ts +3 -0
- package/dist/tools/budgets.js +73 -0
- package/dist/tools/comments.d.ts +3 -0
- package/dist/tools/comments.js +24 -0
- package/dist/tools/companies.d.ts +3 -0
- package/dist/tools/companies.js +67 -0
- package/dist/tools/departments.d.ts +3 -0
- package/dist/tools/departments.js +69 -0
- package/dist/tools/invoices.d.ts +3 -0
- package/dist/tools/invoices.js +132 -0
- package/dist/tools/payments.d.ts +3 -0
- package/dist/tools/payments.js +65 -0
- package/dist/tools/products.d.ts +3 -0
- package/dist/tools/products.js +57 -0
- package/dist/tools/purchase-orders.d.ts +3 -0
- package/dist/tools/purchase-orders.js +152 -0
- package/dist/tools/supplementary.d.ts +3 -0
- package/dist/tools/supplementary.js +70 -0
- package/dist/tools/suppliers.d.ts +3 -0
- package/dist/tools/suppliers.js +80 -0
- package/dist/tools/tax-rates.d.ts +3 -0
- package/dist/tools/tax-rates.js +44 -0
- package/dist/tools/users.d.ts +3 -0
- package/dist/tools/users.js +37 -0
- package/dist/tools/webhooks.d.ts +3 -0
- package/dist/tools/webhooks.js +61 -0
- package/dist/types.d.ts +732 -0
- package/dist/types.js +3 -0
- package/package.json +50 -0
package/README.md
ADDED
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
# ProcurementExpress MCP Server
|
|
2
|
+
|
|
3
|
+
A [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) server that provides LLMs with access to the [ProcurementExpress](https://www.procurementexpress.com/) API. Manage purchase orders, invoices, budgets, suppliers, and procurement workflows through natural language.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **70+ tools** covering the full ProcurementExpress API surface
|
|
8
|
+
- **Dual API version support** — V1 (token-based) and V3 (OAuth2) authentication
|
|
9
|
+
- **Version-agnostic tool layer** — all tools work identically across API versions
|
|
10
|
+
- **Type-safe** — comprehensive TypeScript interfaces for all API entities
|
|
11
|
+
- **Zero external runtime dependencies** — only `@modelcontextprotocol/sdk` and `zod`
|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
### Prerequisites
|
|
16
|
+
|
|
17
|
+
- Node.js 18+
|
|
18
|
+
- A ProcurementExpress account with API access
|
|
19
|
+
|
|
20
|
+
### Installation
|
|
21
|
+
|
|
22
|
+
No installation required — run directly with `npx`:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npx -y @procurementexpress.com/mcp
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Usage with Claude Desktop
|
|
29
|
+
|
|
30
|
+
Add this to your Claude Desktop configuration (`~/Library/Application Support/Claude/claude_desktop_config.json`):
|
|
31
|
+
|
|
32
|
+
```json
|
|
33
|
+
{
|
|
34
|
+
"mcpServers": {
|
|
35
|
+
"procurementexpress": {
|
|
36
|
+
"command": "npx",
|
|
37
|
+
"args": ["-y", "@procurementexpress.com/mcp"],
|
|
38
|
+
"env": {
|
|
39
|
+
"PROCUREMENTEXPRESS_API_VERSION": "v1",
|
|
40
|
+
"PROCUREMENTEXPRESS_AUTH_TOKEN": "your_token",
|
|
41
|
+
"PROCUREMENTEXPRESS_COMPANY_ID": "your_company_id"
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Usage with Claude Code
|
|
49
|
+
|
|
50
|
+
Add to your project's `.mcp.json`:
|
|
51
|
+
|
|
52
|
+
```json
|
|
53
|
+
{
|
|
54
|
+
"mcpServers": {
|
|
55
|
+
"procurementexpress": {
|
|
56
|
+
"command": "npx",
|
|
57
|
+
"args": ["-y", "@procurementexpress.com/mcp"],
|
|
58
|
+
"env": {
|
|
59
|
+
"PROCUREMENTEXPRESS_API_VERSION": "v1",
|
|
60
|
+
"PROCUREMENTEXPRESS_AUTH_TOKEN": "your_token",
|
|
61
|
+
"PROCUREMENTEXPRESS_COMPANY_ID": "your_company_id"
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Configuration
|
|
69
|
+
|
|
70
|
+
The server is configured entirely via environment variables (set them in the `env` block above).
|
|
71
|
+
|
|
72
|
+
#### V1 Authentication (Recommended)
|
|
73
|
+
|
|
74
|
+
Static token authentication. The token never expires.
|
|
75
|
+
|
|
76
|
+
| Variable | Value |
|
|
77
|
+
|----------|-------|
|
|
78
|
+
| `PROCUREMENTEXPRESS_API_VERSION` | `v1` |
|
|
79
|
+
| `PROCUREMENTEXPRESS_AUTH_TOKEN` | Your authentication token |
|
|
80
|
+
| `PROCUREMENTEXPRESS_COMPANY_ID` | Your company ID |
|
|
81
|
+
|
|
82
|
+
#### V3 Authentication (OAuth2)
|
|
83
|
+
|
|
84
|
+
OAuth2 password grant. Tokens are time-limited and require `client_id`/`client_secret`.
|
|
85
|
+
|
|
86
|
+
| Variable | Value |
|
|
87
|
+
|----------|-------|
|
|
88
|
+
| `PROCUREMENTEXPRESS_API_VERSION` | `v3` |
|
|
89
|
+
| `PROCUREMENTEXPRESS_CLIENT_ID` | Your OAuth2 client ID |
|
|
90
|
+
| `PROCUREMENTEXPRESS_CLIENT_SECRET` | Your OAuth2 client secret |
|
|
91
|
+
|
|
92
|
+
## Authentication
|
|
93
|
+
|
|
94
|
+
The server supports two authentication modes, selected by the `PROCUREMENTEXPRESS_API_VERSION` environment variable.
|
|
95
|
+
|
|
96
|
+
### V1 — Token-Based (Default)
|
|
97
|
+
|
|
98
|
+
Set `PROCUREMENTEXPRESS_API_VERSION=v1`. Provide your static token and company ID via environment variables or pass them to the `authenticate` tool at runtime.
|
|
99
|
+
|
|
100
|
+
- Sends `authentication_token` and `app_company_id` headers on every request
|
|
101
|
+
- Token never expires — no refresh logic needed
|
|
102
|
+
- Environment variables: `PROCUREMENTEXPRESS_AUTH_TOKEN`, `PROCUREMENTEXPRESS_COMPANY_ID`
|
|
103
|
+
|
|
104
|
+
### V3 — OAuth2
|
|
105
|
+
|
|
106
|
+
Set `PROCUREMENTEXPRESS_API_VERSION=v3`. Requires `PROCUREMENTEXPRESS_CLIENT_ID` and `PROCUREMENTEXPRESS_CLIENT_SECRET` environment variables. Call the `authenticate` tool with email/password to obtain a Bearer token.
|
|
107
|
+
|
|
108
|
+
- Sends `Authorization: Bearer <token>` header on every request
|
|
109
|
+
- Tokens are time-limited with refresh support
|
|
110
|
+
- After authenticating, use `set_active_company` to select a company
|
|
111
|
+
|
|
112
|
+
## Available Tools
|
|
113
|
+
|
|
114
|
+
### Authentication (3 tools)
|
|
115
|
+
|
|
116
|
+
| Tool | Description |
|
|
117
|
+
|------|-------------|
|
|
118
|
+
| `authenticate` | V1: Set token + company ID. V3: OAuth2 login with email/password |
|
|
119
|
+
| `validate_token` | V1: Fetch current user to verify token. V3: Get token metadata |
|
|
120
|
+
| `revoke_token` | V1: Clear local token. V3: Revoke OAuth2 token |
|
|
121
|
+
|
|
122
|
+
### Users (4 tools)
|
|
123
|
+
|
|
124
|
+
| Tool | Description |
|
|
125
|
+
|------|-------------|
|
|
126
|
+
| `get_current_user` | Get authenticated user's profile and company memberships |
|
|
127
|
+
| `update_current_user` | Update profile (email, name, phone, password) |
|
|
128
|
+
| `list_currencies` | List enabled currencies for the current company |
|
|
129
|
+
| `list_all_currencies` | List all available currencies globally |
|
|
130
|
+
|
|
131
|
+
### Companies (7 tools)
|
|
132
|
+
|
|
133
|
+
| Tool | Description |
|
|
134
|
+
|------|-------------|
|
|
135
|
+
| `list_companies` | List all companies the current user belongs to |
|
|
136
|
+
| `get_company` | Get company details including settings and currencies |
|
|
137
|
+
| `set_active_company` | Set active company ID for subsequent API calls |
|
|
138
|
+
| `list_approvers` | List approvers filtered by department |
|
|
139
|
+
| `list_all_approvers` | List all approvers regardless of routing |
|
|
140
|
+
| `list_employees` | List all employees with roles |
|
|
141
|
+
| `invite_user` | Invite a user (roles: companyadmin, approver, finance, teammember) |
|
|
142
|
+
|
|
143
|
+
### Budgets (4 tools)
|
|
144
|
+
|
|
145
|
+
| Tool | Description |
|
|
146
|
+
|------|-------------|
|
|
147
|
+
| `list_budgets` | List budgets with pagination, filter by department/archived/active |
|
|
148
|
+
| `get_budget` | Get budget details including remaining amount |
|
|
149
|
+
| `create_budget` | Create a new budget |
|
|
150
|
+
| `update_budget` | Update an existing budget |
|
|
151
|
+
|
|
152
|
+
### Departments (4 tools)
|
|
153
|
+
|
|
154
|
+
| Tool | Description |
|
|
155
|
+
|------|-------------|
|
|
156
|
+
| `list_departments` | List departments with optional archived filter |
|
|
157
|
+
| `get_department` | Get a specific department |
|
|
158
|
+
| `create_department` | Create a new department |
|
|
159
|
+
| `update_department` | Update a department |
|
|
160
|
+
|
|
161
|
+
### Suppliers (4 tools)
|
|
162
|
+
|
|
163
|
+
| Tool | Description |
|
|
164
|
+
|------|-------------|
|
|
165
|
+
| `list_suppliers` | List suppliers with pagination and filters |
|
|
166
|
+
| `get_supplier` | Get a specific supplier |
|
|
167
|
+
| `create_supplier` | Create a supplier (name must be unique) |
|
|
168
|
+
| `update_supplier` | Update a supplier |
|
|
169
|
+
|
|
170
|
+
### Products (4 tools)
|
|
171
|
+
|
|
172
|
+
| Tool | Description |
|
|
173
|
+
|------|-------------|
|
|
174
|
+
| `list_products` | List products with supplier/archived filters |
|
|
175
|
+
| `get_product` | Get a specific product |
|
|
176
|
+
| `create_product` | Create a new product |
|
|
177
|
+
| `update_product` | Update a product |
|
|
178
|
+
|
|
179
|
+
### Purchase Orders (10 tools)
|
|
180
|
+
|
|
181
|
+
| Tool | Description |
|
|
182
|
+
|------|-------------|
|
|
183
|
+
| `list_purchase_orders` | List POs with pagination and search |
|
|
184
|
+
| `get_purchase_order` | Get PO details with line items, comments, approvals |
|
|
185
|
+
| `create_purchase_order` | Create a PO (commit='Send' to submit, 'Draft' to save) |
|
|
186
|
+
| `approve_purchase_order` | Approve using the accept token from approver request |
|
|
187
|
+
| `reject_purchase_order` | Reject using the reject token from approver request |
|
|
188
|
+
| `override_and_approve_purchase_order` | Finance override approval (no token required) |
|
|
189
|
+
| `cancel_purchase_order` | Cancel a purchase order |
|
|
190
|
+
| `archive_purchase_order` | Archive a purchase order |
|
|
191
|
+
| `get_pending_request_count` | Get count of pending approval requests |
|
|
192
|
+
| `receive_purchase_order_items` | Mark items as received |
|
|
193
|
+
|
|
194
|
+
### Invoices (10 tools)
|
|
195
|
+
|
|
196
|
+
| Tool | Description |
|
|
197
|
+
|------|-------------|
|
|
198
|
+
| `list_invoices` | List invoices with pagination and filters (100 per page) |
|
|
199
|
+
| `get_invoice` | Get invoice details |
|
|
200
|
+
| `create_invoice` | Create a new invoice |
|
|
201
|
+
| `update_invoice` | Update an existing invoice |
|
|
202
|
+
| `accept_invoice` | Accept an invoice awaiting review |
|
|
203
|
+
| `approve_invoice` | Approve an invoice |
|
|
204
|
+
| `reject_invoice` | Reject an invoice |
|
|
205
|
+
| `cancel_invoice` | Cancel an invoice |
|
|
206
|
+
| `archive_invoice` | Archive an invoice |
|
|
207
|
+
| `dearchive_invoice` | Restore an archived invoice |
|
|
208
|
+
|
|
209
|
+
### Approval Flows (6 tools)
|
|
210
|
+
|
|
211
|
+
| Tool | Description |
|
|
212
|
+
|------|-------------|
|
|
213
|
+
| `list_approval_flows` | List approval flows with search and pagination |
|
|
214
|
+
| `get_approval_flow` | Get flow details with steps, approvers, conditions |
|
|
215
|
+
| `create_approval_flow` | Create a flow (document_type: 0=PO, 1=invoice) |
|
|
216
|
+
| `delete_approval_flow` | Delete an approval flow |
|
|
217
|
+
| `archive_approval_flow` | Archive an approval flow |
|
|
218
|
+
| `list_approval_flow_runs` | List runs with status and date filters |
|
|
219
|
+
|
|
220
|
+
### Payments (2 tools)
|
|
221
|
+
|
|
222
|
+
| Tool | Description |
|
|
223
|
+
|------|-------------|
|
|
224
|
+
| `create_payment` | Create a payment (types: bank_transfer, card, check, cash, etc.) |
|
|
225
|
+
| `create_po_payment` | Create item-level payments for a purchase order |
|
|
226
|
+
|
|
227
|
+
### Tax Rates (4 tools)
|
|
228
|
+
|
|
229
|
+
| Tool | Description |
|
|
230
|
+
|------|-------------|
|
|
231
|
+
| `list_tax_rates` | List all tax rates (single and combined) |
|
|
232
|
+
| `get_tax_rate` | Get a specific tax rate |
|
|
233
|
+
| `create_tax_rate` | Create a new tax rate |
|
|
234
|
+
| `update_tax_rate` | Update a tax rate |
|
|
235
|
+
|
|
236
|
+
### Webhooks (4 tools)
|
|
237
|
+
|
|
238
|
+
| Tool | Description |
|
|
239
|
+
|------|-------------|
|
|
240
|
+
| `list_webhooks` | List webhooks with optional archived filter |
|
|
241
|
+
| `get_webhook` | Get a specific webhook |
|
|
242
|
+
| `create_webhook` | Create a webhook (events: new_po, po_approved, po_delivered, po_paid, po_cancelled, po_update) |
|
|
243
|
+
| `update_webhook` | Update a webhook |
|
|
244
|
+
|
|
245
|
+
### Comments (2 tools)
|
|
246
|
+
|
|
247
|
+
| Tool | Description |
|
|
248
|
+
|------|-------------|
|
|
249
|
+
| `add_purchase_order_comment` | Add a comment to a purchase order |
|
|
250
|
+
| `add_invoice_comment` | Add a comment to an invoice |
|
|
251
|
+
|
|
252
|
+
### Supplementary (5 tools)
|
|
253
|
+
|
|
254
|
+
| Tool | Description |
|
|
255
|
+
|------|-------------|
|
|
256
|
+
| `list_chart_of_accounts` | List chart of accounts with search |
|
|
257
|
+
| `list_qbo_customers` | List QuickBooks customers with search |
|
|
258
|
+
| `list_qbo_classes` | List QuickBooks classes with search |
|
|
259
|
+
| `list_send_to_supplier_templates` | List email templates for sending POs |
|
|
260
|
+
| `forward_purchase_order` | Email a PO to supplier(s) |
|
|
261
|
+
|
|
262
|
+
## Project Structure
|
|
263
|
+
|
|
264
|
+
```
|
|
265
|
+
src/
|
|
266
|
+
index.ts # Entry point — MCP server setup, auth tools, tool registration
|
|
267
|
+
api-client.ts # HTTP client with versioned path building and auth headers
|
|
268
|
+
auth.ts # Dual auth manager (V1 token / V3 OAuth2)
|
|
269
|
+
tool-helpers.ts # Shared response helpers and error handling wrapper
|
|
270
|
+
types.ts # TypeScript interfaces for all API entities
|
|
271
|
+
tools/
|
|
272
|
+
approval-flows.ts # Approval flow CRUD and run listing
|
|
273
|
+
budgets.ts # Budget CRUD
|
|
274
|
+
comments.ts # PO and invoice comments
|
|
275
|
+
companies.ts # Company details, employees, approvers, invitations
|
|
276
|
+
departments.ts # Department CRUD
|
|
277
|
+
invoices.ts # Invoice CRUD, approve/reject/cancel/archive
|
|
278
|
+
payments.ts # Payment creation (standalone and PO-linked)
|
|
279
|
+
products.ts # Product CRUD
|
|
280
|
+
purchase-orders.ts # PO CRUD, approve/reject/cancel/archive, receiving
|
|
281
|
+
supplementary.ts # Chart of accounts, QBO integration, email forwarding
|
|
282
|
+
suppliers.ts # Supplier CRUD
|
|
283
|
+
tax-rates.ts # Tax rate CRUD
|
|
284
|
+
users.ts # Current user profile and currency listing
|
|
285
|
+
webhooks.ts # Webhook CRUD
|
|
286
|
+
tests/
|
|
287
|
+
e2e/
|
|
288
|
+
setup.ts # MockApiServer with version-agnostic route registration
|
|
289
|
+
*.test.ts # E2E tests for each tool group (49 tests, 11 files)
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
## Development
|
|
293
|
+
|
|
294
|
+
For contributors who want to work on the server itself:
|
|
295
|
+
|
|
296
|
+
```bash
|
|
297
|
+
git clone https://github.com/procurementexpress/mcp.git
|
|
298
|
+
cd mcp
|
|
299
|
+
npm install
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
### Build
|
|
303
|
+
|
|
304
|
+
```bash
|
|
305
|
+
npm run build # Compile TypeScript to dist/
|
|
306
|
+
npm run dev # Watch mode for development
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
### Test
|
|
310
|
+
|
|
311
|
+
```bash
|
|
312
|
+
npm test # Run all tests
|
|
313
|
+
npm run test:e2e # Run E2E tests only
|
|
314
|
+
npx vitest run tests/e2e/auth.test.ts # Run a single test file
|
|
315
|
+
npm run test:watch # Watch mode
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
Tests use a `MockApiServer` — a lightweight HTTP server that simulates the ProcurementExpress API. Mock routes use version-agnostic regex patterns (`/api/v[13]/`) so tests work for both V1 and V3 configurations.
|
|
319
|
+
|
|
320
|
+
### Adding a New Tool
|
|
321
|
+
|
|
322
|
+
1. Add TypeScript interfaces to `src/types.ts` if needed
|
|
323
|
+
2. Create or edit a file in `src/tools/` following the existing pattern:
|
|
324
|
+
|
|
325
|
+
```typescript
|
|
326
|
+
import { z } from "zod";
|
|
327
|
+
import type { ApiClient } from "../api-client.js";
|
|
328
|
+
import type { Server } from "../tool-helpers.js";
|
|
329
|
+
import { jsonResponse, withErrorHandling } from "../tool-helpers.js";
|
|
330
|
+
|
|
331
|
+
export function registerMyTools(server: Server, apiClient: ApiClient): void {
|
|
332
|
+
server.registerTool(
|
|
333
|
+
"my_tool_name",
|
|
334
|
+
{
|
|
335
|
+
description: "What this tool does",
|
|
336
|
+
inputSchema: {
|
|
337
|
+
id: z.number().int().positive().describe("Resource ID"),
|
|
338
|
+
},
|
|
339
|
+
},
|
|
340
|
+
withErrorHandling(async (args) => {
|
|
341
|
+
const result = await apiClient.get(apiClient.buildPath(`/my_resource/${args.id}`));
|
|
342
|
+
return jsonResponse(result);
|
|
343
|
+
}),
|
|
344
|
+
);
|
|
345
|
+
}
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
3. Register the tool group in `src/index.ts`:
|
|
349
|
+
|
|
350
|
+
```typescript
|
|
351
|
+
import { registerMyTools } from "./tools/my-tools.js";
|
|
352
|
+
registerMyTools(server, apiClient);
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
4. Add mock routes and tests in `tests/e2e/`
|
|
356
|
+
|
|
357
|
+
**Key conventions:**
|
|
358
|
+
- Always use `apiClient.buildPath("/resource")` — never hardcode `/api/v1/` or `/api/v3/`
|
|
359
|
+
- Wrap every handler with `withErrorHandling()`
|
|
360
|
+
- Use `jsonResponse()` for data and `textResponse()` for messages
|
|
361
|
+
- All imports must use `.js` extension (ES modules)
|
|
362
|
+
|
|
363
|
+
## Environment Variables Reference
|
|
364
|
+
|
|
365
|
+
| Variable | Required | Default | Description |
|
|
366
|
+
|----------|----------|---------|-------------|
|
|
367
|
+
| `PROCUREMENTEXPRESS_API_BASE_URL` | No | `https://app.procurementexpress.com` | API base URL |
|
|
368
|
+
| `PROCUREMENTEXPRESS_API_VERSION` | No | `v1` | API version (`v1` or `v3`) |
|
|
369
|
+
| `PROCUREMENTEXPRESS_COMPANY_ID` | V1 | — | Company ID for V1 auth |
|
|
370
|
+
| `PROCUREMENTEXPRESS_AUTH_TOKEN` | V1 | — | Static authentication token for V1 |
|
|
371
|
+
| `PROCUREMENTEXPRESS_CLIENT_ID` | V3 | — | OAuth2 client ID for V3 |
|
|
372
|
+
| `PROCUREMENTEXPRESS_CLIENT_SECRET` | V3 | — | OAuth2 client secret for V3 |
|
|
373
|
+
|
|
374
|
+
## License
|
|
375
|
+
|
|
376
|
+
ISC
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export type ApiVersion = "v1" | "v3";
|
|
2
|
+
export declare class ApiClientError extends Error {
|
|
3
|
+
status: number;
|
|
4
|
+
constructor(status: number, message: string);
|
|
5
|
+
}
|
|
6
|
+
export declare class ApiClient {
|
|
7
|
+
private baseUrl;
|
|
8
|
+
private token;
|
|
9
|
+
private companyId;
|
|
10
|
+
private apiVersion;
|
|
11
|
+
private authMode;
|
|
12
|
+
constructor(baseUrl?: string, apiVersion?: ApiVersion);
|
|
13
|
+
getApiVersion(): ApiVersion;
|
|
14
|
+
setToken(token: string): void;
|
|
15
|
+
clearToken(): void;
|
|
16
|
+
getToken(): string | null;
|
|
17
|
+
setCompanyId(companyId: string): void;
|
|
18
|
+
getCompanyId(): string | null;
|
|
19
|
+
/**
|
|
20
|
+
* Build the full API path with the configured version prefix.
|
|
21
|
+
* Paths starting with /oauth or /api/ are used as-is.
|
|
22
|
+
* Paths like /budgets become /api/{version}/budgets.
|
|
23
|
+
*/
|
|
24
|
+
buildPath(path: string): string;
|
|
25
|
+
private buildHeaders;
|
|
26
|
+
request<T>(method: string, path: string, body?: Record<string, unknown>, extraHeaders?: Record<string, string>): Promise<T>;
|
|
27
|
+
get<T>(path: string, extraHeaders?: Record<string, string>): Promise<T>;
|
|
28
|
+
post<T>(path: string, body?: Record<string, unknown>, extraHeaders?: Record<string, string>): Promise<T>;
|
|
29
|
+
put<T>(path: string, body?: Record<string, unknown>, extraHeaders?: Record<string, string>): Promise<T>;
|
|
30
|
+
delete<T>(path: string, extraHeaders?: Record<string, string>): Promise<T>;
|
|
31
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
export class ApiClientError extends Error {
|
|
2
|
+
status;
|
|
3
|
+
constructor(status, message) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.status = status;
|
|
6
|
+
this.name = "ApiClientError";
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
export class ApiClient {
|
|
10
|
+
baseUrl;
|
|
11
|
+
token = null;
|
|
12
|
+
companyId = null;
|
|
13
|
+
apiVersion;
|
|
14
|
+
authMode;
|
|
15
|
+
constructor(baseUrl, apiVersion) {
|
|
16
|
+
this.baseUrl = (baseUrl ||
|
|
17
|
+
process.env.PROCUREMENTEXPRESS_API_BASE_URL ||
|
|
18
|
+
"https://app.procurementexpress.com").replace(/\/$/, "");
|
|
19
|
+
this.apiVersion = apiVersion || process.env.PROCUREMENTEXPRESS_API_VERSION || "v1";
|
|
20
|
+
this.authMode = this.apiVersion;
|
|
21
|
+
}
|
|
22
|
+
getApiVersion() {
|
|
23
|
+
return this.apiVersion;
|
|
24
|
+
}
|
|
25
|
+
setToken(token) {
|
|
26
|
+
this.token = token;
|
|
27
|
+
}
|
|
28
|
+
clearToken() {
|
|
29
|
+
this.token = null;
|
|
30
|
+
}
|
|
31
|
+
getToken() {
|
|
32
|
+
return this.token;
|
|
33
|
+
}
|
|
34
|
+
setCompanyId(companyId) {
|
|
35
|
+
this.companyId = companyId;
|
|
36
|
+
}
|
|
37
|
+
getCompanyId() {
|
|
38
|
+
return this.companyId;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Build the full API path with the configured version prefix.
|
|
42
|
+
* Paths starting with /oauth or /api/ are used as-is.
|
|
43
|
+
* Paths like /budgets become /api/{version}/budgets.
|
|
44
|
+
*/
|
|
45
|
+
buildPath(path) {
|
|
46
|
+
if (path.startsWith("/oauth") || path.startsWith("/api/")) {
|
|
47
|
+
return path;
|
|
48
|
+
}
|
|
49
|
+
return `/api/${this.apiVersion}${path}`;
|
|
50
|
+
}
|
|
51
|
+
buildHeaders(extraHeaders) {
|
|
52
|
+
const headers = {
|
|
53
|
+
"Content-Type": "application/json",
|
|
54
|
+
};
|
|
55
|
+
if (this.token) {
|
|
56
|
+
if (this.authMode === "v1") {
|
|
57
|
+
headers["authentication_token"] = this.token;
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
headers["Authorization"] = `Bearer ${this.token}`;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if (this.companyId) {
|
|
64
|
+
headers["app_company_id"] = this.companyId;
|
|
65
|
+
}
|
|
66
|
+
if (extraHeaders) {
|
|
67
|
+
Object.assign(headers, extraHeaders);
|
|
68
|
+
}
|
|
69
|
+
return headers;
|
|
70
|
+
}
|
|
71
|
+
async request(method, path, body, extraHeaders) {
|
|
72
|
+
const url = `${this.baseUrl}${path}`;
|
|
73
|
+
const headers = this.buildHeaders(extraHeaders);
|
|
74
|
+
const options = { method, headers };
|
|
75
|
+
if (body && (method === "POST" || method === "PUT" || method === "PATCH")) {
|
|
76
|
+
options.body = JSON.stringify(body);
|
|
77
|
+
}
|
|
78
|
+
const response = await fetch(url, options);
|
|
79
|
+
if (!response.ok) {
|
|
80
|
+
let message;
|
|
81
|
+
try {
|
|
82
|
+
const errorBody = (await response.json());
|
|
83
|
+
message = errorBody.message || response.statusText;
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
message = response.statusText;
|
|
87
|
+
}
|
|
88
|
+
throw new ApiClientError(response.status, `${response.status}: ${message}`);
|
|
89
|
+
}
|
|
90
|
+
// Handle 204 No Content
|
|
91
|
+
if (response.status === 204) {
|
|
92
|
+
return {};
|
|
93
|
+
}
|
|
94
|
+
return (await response.json());
|
|
95
|
+
}
|
|
96
|
+
async get(path, extraHeaders) {
|
|
97
|
+
return this.request("GET", path, undefined, extraHeaders);
|
|
98
|
+
}
|
|
99
|
+
async post(path, body, extraHeaders) {
|
|
100
|
+
return this.request("POST", path, body, extraHeaders);
|
|
101
|
+
}
|
|
102
|
+
async put(path, body, extraHeaders) {
|
|
103
|
+
return this.request("PUT", path, body, extraHeaders);
|
|
104
|
+
}
|
|
105
|
+
async delete(path, extraHeaders) {
|
|
106
|
+
return this.request("DELETE", path, undefined, extraHeaders);
|
|
107
|
+
}
|
|
108
|
+
}
|
package/dist/auth.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { ApiClient } from "./api-client.js";
|
|
2
|
+
import type { OAuthTokenResponse, TokenInfo, User } from "./types.js";
|
|
3
|
+
export declare class AuthManager {
|
|
4
|
+
private apiClient;
|
|
5
|
+
private clientId;
|
|
6
|
+
private clientSecret;
|
|
7
|
+
constructor(apiClient: ApiClient, clientId?: string, clientSecret?: string);
|
|
8
|
+
isV1(): boolean;
|
|
9
|
+
/**
|
|
10
|
+
* V1 authentication: Set the static auth token and company ID directly.
|
|
11
|
+
*/
|
|
12
|
+
authenticateV1(authToken: string, companyId: string): void;
|
|
13
|
+
/**
|
|
14
|
+
* V3 authentication: OAuth2 password grant.
|
|
15
|
+
*/
|
|
16
|
+
authenticateV3(email: string, password: string): Promise<OAuthTokenResponse>;
|
|
17
|
+
/**
|
|
18
|
+
* Validate the current token.
|
|
19
|
+
* V1: Calls /api/v1/currentuser to verify the token works.
|
|
20
|
+
* V3: Calls /oauth/token/info for token metadata.
|
|
21
|
+
*/
|
|
22
|
+
validateToken(): Promise<TokenInfo | User>;
|
|
23
|
+
/**
|
|
24
|
+
* Revoke the current token.
|
|
25
|
+
* V1: Just clears the local token (V1 tokens are static).
|
|
26
|
+
* V3: Calls /oauth/revoke then clears the local token.
|
|
27
|
+
*/
|
|
28
|
+
revokeToken(): Promise<void>;
|
|
29
|
+
}
|
package/dist/auth.js
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
export class AuthManager {
|
|
2
|
+
apiClient;
|
|
3
|
+
clientId;
|
|
4
|
+
clientSecret;
|
|
5
|
+
constructor(apiClient, clientId, clientSecret) {
|
|
6
|
+
this.apiClient = apiClient;
|
|
7
|
+
this.clientId = clientId || process.env.PROCUREMENTEXPRESS_CLIENT_ID || "";
|
|
8
|
+
this.clientSecret = clientSecret || process.env.PROCUREMENTEXPRESS_CLIENT_SECRET || "";
|
|
9
|
+
}
|
|
10
|
+
isV1() {
|
|
11
|
+
return this.apiClient.getApiVersion() === "v1";
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* V1 authentication: Set the static auth token and company ID directly.
|
|
15
|
+
*/
|
|
16
|
+
authenticateV1(authToken, companyId) {
|
|
17
|
+
this.apiClient.setToken(authToken);
|
|
18
|
+
this.apiClient.setCompanyId(companyId);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* V3 authentication: OAuth2 password grant.
|
|
22
|
+
*/
|
|
23
|
+
async authenticateV3(email, password) {
|
|
24
|
+
const response = await this.apiClient.post("/oauth/token", {
|
|
25
|
+
email,
|
|
26
|
+
password,
|
|
27
|
+
grant_type: "password",
|
|
28
|
+
client_id: this.clientId,
|
|
29
|
+
client_secret: this.clientSecret,
|
|
30
|
+
});
|
|
31
|
+
this.apiClient.setToken(response.access_token);
|
|
32
|
+
return response;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Validate the current token.
|
|
36
|
+
* V1: Calls /api/v1/currentuser to verify the token works.
|
|
37
|
+
* V3: Calls /oauth/token/info for token metadata.
|
|
38
|
+
*/
|
|
39
|
+
async validateToken() {
|
|
40
|
+
if (this.isV1()) {
|
|
41
|
+
return this.apiClient.get(this.apiClient.buildPath("/currentuser"));
|
|
42
|
+
}
|
|
43
|
+
return this.apiClient.get("/oauth/token/info");
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Revoke the current token.
|
|
47
|
+
* V1: Just clears the local token (V1 tokens are static).
|
|
48
|
+
* V3: Calls /oauth/revoke then clears the local token.
|
|
49
|
+
*/
|
|
50
|
+
async revokeToken() {
|
|
51
|
+
if (!this.isV1()) {
|
|
52
|
+
await this.apiClient.post("/oauth/revoke", {
|
|
53
|
+
client_id: this.clientId,
|
|
54
|
+
client_secret: this.clientSecret,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
this.apiClient.clearToken();
|
|
58
|
+
}
|
|
59
|
+
}
|
package/dist/index.d.ts
ADDED