wolfpack-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 +369 -0
- package/dist/apiClient.js +118 -0
- package/dist/client.js +229 -0
- package/dist/config.js +26 -0
- package/dist/index.js +939 -0
- package/dist/types.js +2 -0
- package/package.json +51 -0
package/README.md
ADDED
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
# Wolfpack MCP Server
|
|
2
|
+
|
|
3
|
+
The Wolfpack MCP (Model Context Protocol) server enables AI assistants to interact with Wolfpack team projects, providing full CRUD operations for work items, issues, wiki pages, journal entries, and more.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g wolfpack-mcp
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Or use directly with npx (no installation required):
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npx wolfpack-mcp
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Setup
|
|
18
|
+
|
|
19
|
+
### 1. Generate an API Key
|
|
20
|
+
|
|
21
|
+
1. Log in to your Wolfpack application at [wolfpacks.work](https://wolfpacks.work)
|
|
22
|
+
2. Navigate to **Account Settings** → **API Keys**
|
|
23
|
+
3. Click **Create New Key**
|
|
24
|
+
4. Give your key a descriptive name (e.g., "Claude MCP Server")
|
|
25
|
+
5. Select the MCP permissions you want to grant (e.g., work items, issues, wiki)
|
|
26
|
+
6. Choose an expiration period (or "Never expires" for permanent keys)
|
|
27
|
+
7. Click **Create Key**
|
|
28
|
+
8. **Important**: Copy your API key immediately - you won't be able to see it again!
|
|
29
|
+
|
|
30
|
+
### 2. Configure Claude Desktop
|
|
31
|
+
|
|
32
|
+
Add the MCP server to your Claude Desktop configuration:
|
|
33
|
+
|
|
34
|
+
**macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
35
|
+
**Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
|
|
36
|
+
|
|
37
|
+
```json
|
|
38
|
+
{
|
|
39
|
+
"mcpServers": {
|
|
40
|
+
"wolfpack": {
|
|
41
|
+
"command": "npx",
|
|
42
|
+
"args": ["-y", "wolfpack-mcp"],
|
|
43
|
+
"env": {
|
|
44
|
+
"WOLFPACK_API_KEY": "wfp_sk_your_api_key_here"
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Replace `wfp_sk_your_api_key_here` with your actual API key.
|
|
52
|
+
|
|
53
|
+
**Team Selection**: If you belong to only one team, it will be selected automatically. If you have multiple teams, use the `list_teams` tool to see them, then specify `team_id` in your tool calls.
|
|
54
|
+
|
|
55
|
+
**Optional**: To pre-select a specific team, add `WOLFPACK_TEAM_SLUG` to the env:
|
|
56
|
+
|
|
57
|
+
```json
|
|
58
|
+
"env": {
|
|
59
|
+
"WOLFPACK_API_KEY": "wfp_sk_your_api_key_here",
|
|
60
|
+
"WOLFPACK_TEAM_SLUG": "your-team-slug"
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### 3. Restart Claude Desktop
|
|
65
|
+
|
|
66
|
+
After updating the configuration, restart Claude Desktop to load the MCP server.
|
|
67
|
+
|
|
68
|
+
## API Key Permissions
|
|
69
|
+
|
|
70
|
+
Each API key has granular permissions that control which MCP tools are available:
|
|
71
|
+
|
|
72
|
+
| Permission | Tools Enabled |
|
|
73
|
+
| ----------------------- | -------------------------------------------------- |
|
|
74
|
+
| `mcp:work_items:read` | `list_work_items`, `get_work_item` |
|
|
75
|
+
| `mcp:work_items:create` | `create_work_item` |
|
|
76
|
+
| `mcp:work_items:update` | `update_work_progress`, `update_work_item_status` |
|
|
77
|
+
| `mcp:issues:read` | `list_issues`, `get_issue` |
|
|
78
|
+
| `mcp:issues:create` | `create_issue` |
|
|
79
|
+
| `mcp:issues:update` | `update_issue` |
|
|
80
|
+
| `mcp:wiki:read` | `list_wiki_pages`, `get_wiki_page` |
|
|
81
|
+
| `mcp:wiki:create` | `create_wiki_page` |
|
|
82
|
+
| `mcp:wiki:update` | `update_wiki_page` |
|
|
83
|
+
| `mcp:journal:read` | `list_journal_entries`, `get_journal_entry` |
|
|
84
|
+
| `mcp:journal:create` | `create_journal_entry` |
|
|
85
|
+
| `mcp:journal:update` | `update_journal_entry` |
|
|
86
|
+
| `mcp:comments:create` | `create_work_item_comment`, `create_issue_comment` |
|
|
87
|
+
| `mcp:radar:read` | `list_radar_items`, `get_radar_item` |
|
|
88
|
+
|
|
89
|
+
## Available Tools
|
|
90
|
+
|
|
91
|
+
### Teams
|
|
92
|
+
|
|
93
|
+
#### `list_teams`
|
|
94
|
+
|
|
95
|
+
List all teams you have access to. Use this to discover available teams and get team IDs.
|
|
96
|
+
|
|
97
|
+
- **Parameters:** None
|
|
98
|
+
- **Returns:** Array of team objects with `id`, `slug`, and `name`
|
|
99
|
+
|
|
100
|
+
### Work Items
|
|
101
|
+
|
|
102
|
+
Work items are tasks or tickets assigned to team members.
|
|
103
|
+
|
|
104
|
+
#### `list_work_items`
|
|
105
|
+
|
|
106
|
+
Lists work items assigned to you.
|
|
107
|
+
|
|
108
|
+
- **Parameters:**
|
|
109
|
+
- `team_id` (number, optional): Team ID (use `list_teams` to get IDs)
|
|
110
|
+
- `status` (string, optional): Filter by status (new, doing, review, completed, blocked)
|
|
111
|
+
- `limit` (number, optional): Maximum number of items to return
|
|
112
|
+
- `offset` (number, optional): Number of items to skip for pagination
|
|
113
|
+
- **Returns:** Array of work item objects
|
|
114
|
+
|
|
115
|
+
#### `get_work_item`
|
|
116
|
+
|
|
117
|
+
Gets a specific work item by ID or reference number.
|
|
118
|
+
|
|
119
|
+
- **Parameters:**
|
|
120
|
+
- `work_item_id` (string, required): The work item ID (UUID) or refId (number)
|
|
121
|
+
- `team_id` (number, optional): Team ID (required when using refId)
|
|
122
|
+
- **Returns:** Work item object with full description
|
|
123
|
+
|
|
124
|
+
#### `create_work_item`
|
|
125
|
+
|
|
126
|
+
Creates a new work item. Requires `mcp:work_items:create` permission.
|
|
127
|
+
|
|
128
|
+
- **Parameters:**
|
|
129
|
+
- `title` (string, required): Title of the work item
|
|
130
|
+
- `description` (string, optional): Description/notes (markdown)
|
|
131
|
+
- `status` (string, optional): Initial status (new, doing, review, completed, blocked - defaults to 'new')
|
|
132
|
+
- `priority` (number, optional): Priority level (0-4, higher is more important)
|
|
133
|
+
- `leading_user_id` (string, optional): User ID to assign
|
|
134
|
+
- **Returns:** The created work item object
|
|
135
|
+
|
|
136
|
+
#### `update_work_progress`
|
|
137
|
+
|
|
138
|
+
Updates the description/notes on a work item. Requires `mcp:work_items:update` permission.
|
|
139
|
+
|
|
140
|
+
- **Parameters:**
|
|
141
|
+
- `work_item_id` (string, required): The work item ID
|
|
142
|
+
- `description` (string, required): Complete updated description (markdown)
|
|
143
|
+
- **Returns:** The updated work item object
|
|
144
|
+
|
|
145
|
+
#### `update_work_item_status`
|
|
146
|
+
|
|
147
|
+
Changes work item status. Requires `mcp:work_items:update` permission.
|
|
148
|
+
|
|
149
|
+
- **Parameters:**
|
|
150
|
+
- `work_item_id` (string, required): The work item ID
|
|
151
|
+
- `status` (string, required): New status (new, doing, review, completed, blocked)
|
|
152
|
+
- **Returns:** The updated work item object
|
|
153
|
+
|
|
154
|
+
### Issues
|
|
155
|
+
|
|
156
|
+
Issues track bugs, feature requests, and other items in the issue tracker.
|
|
157
|
+
|
|
158
|
+
#### `list_issues`
|
|
159
|
+
|
|
160
|
+
Lists issues from the team issue tracker.
|
|
161
|
+
|
|
162
|
+
- **Parameters:**
|
|
163
|
+
- `team_id` (number, optional): Team ID to filter issues
|
|
164
|
+
- `status` (string, optional): Filter by status (open, in-progress, resolved, closed)
|
|
165
|
+
- `severity` (string, optional): Filter by severity (low, medium, high, critical)
|
|
166
|
+
- `type` (string, optional): Filter by type (bug, feature-request, task, improvement, question)
|
|
167
|
+
- `assigned_to_id` (string, optional): Filter by assignee ('unassigned' or 'me' for special cases)
|
|
168
|
+
- `limit` (number, optional): Maximum number of items
|
|
169
|
+
- `offset` (number, optional): Items to skip
|
|
170
|
+
- **Returns:** Array of issue objects
|
|
171
|
+
|
|
172
|
+
#### `get_issue`
|
|
173
|
+
|
|
174
|
+
Gets a specific issue by ID or reference number.
|
|
175
|
+
|
|
176
|
+
- **Parameters:**
|
|
177
|
+
- `issue_id` (string, required): The issue ID (UUID) or refId (number)
|
|
178
|
+
- `team_id` (number, optional): Team ID (required when using refId)
|
|
179
|
+
- **Returns:** Issue object with comment and attachment counts
|
|
180
|
+
|
|
181
|
+
#### `create_issue`
|
|
182
|
+
|
|
183
|
+
Creates a new issue. Requires `mcp:issues:create` permission.
|
|
184
|
+
|
|
185
|
+
- **Parameters:**
|
|
186
|
+
- `title` (string, required): Issue title
|
|
187
|
+
- `description` (string, optional): Issue description
|
|
188
|
+
- `severity` (string, optional): Severity level (low, medium, high, critical)
|
|
189
|
+
- `type` (string, optional): Issue type (bug, feature-request, task, improvement, question)
|
|
190
|
+
- `assigned_to_id` (string, optional): User ID to assign
|
|
191
|
+
- `environment` (string, optional): Environment where issue occurred
|
|
192
|
+
- `affected_version` (string, optional): Version affected
|
|
193
|
+
- `reproducible` (boolean, optional): Whether reproducible
|
|
194
|
+
- `steps_to_reproduce` (string, optional): Steps to reproduce
|
|
195
|
+
- `expected_behavior` (string, optional): Expected behavior
|
|
196
|
+
- `actual_behavior` (string, optional): Actual behavior observed
|
|
197
|
+
- **Returns:** The created issue object
|
|
198
|
+
|
|
199
|
+
#### `update_issue`
|
|
200
|
+
|
|
201
|
+
Updates an existing issue. Requires `mcp:issues:update` permission.
|
|
202
|
+
|
|
203
|
+
- **Parameters:**
|
|
204
|
+
- `issue_id` (string, required): The issue UUID
|
|
205
|
+
- `title` (string, optional): Updated title
|
|
206
|
+
- `description` (string, optional): Updated description
|
|
207
|
+
- `status` (string, optional): Updated status (open, in-progress, resolved, closed)
|
|
208
|
+
- `severity` (string, optional): Updated severity
|
|
209
|
+
- `assigned_to_id` (string, optional): Updated assignee
|
|
210
|
+
- `closing_note` (string, optional): Note when closing
|
|
211
|
+
- **Returns:** The updated issue object
|
|
212
|
+
|
|
213
|
+
### Wiki Pages
|
|
214
|
+
|
|
215
|
+
Wiki pages store team documentation and knowledge base articles.
|
|
216
|
+
|
|
217
|
+
#### `list_wiki_pages`
|
|
218
|
+
|
|
219
|
+
Lists wiki pages from your accessible teams.
|
|
220
|
+
|
|
221
|
+
- **Parameters:**
|
|
222
|
+
- `limit` (number, optional): Maximum pages to return
|
|
223
|
+
- `offset` (number, optional): Pages to skip
|
|
224
|
+
- **Returns:** Array of wiki page objects
|
|
225
|
+
|
|
226
|
+
#### `get_wiki_page`
|
|
227
|
+
|
|
228
|
+
Gets a specific wiki page by ID or slug.
|
|
229
|
+
|
|
230
|
+
- **Parameters:**
|
|
231
|
+
- `page_id` (string, required): The page UUID or slug (URL path like "getting-started")
|
|
232
|
+
- **Returns:** Wiki page object with content
|
|
233
|
+
|
|
234
|
+
#### `create_wiki_page`
|
|
235
|
+
|
|
236
|
+
Creates a new wiki page. Requires `mcp:wiki:create` permission.
|
|
237
|
+
|
|
238
|
+
- **Parameters:**
|
|
239
|
+
- `slug` (string, required): URL slug for the page (without team prefix)
|
|
240
|
+
- `title` (string, required): Page title
|
|
241
|
+
- `content` (string, required): Page content (markdown)
|
|
242
|
+
- **Returns:** The created wiki page object
|
|
243
|
+
|
|
244
|
+
#### `update_wiki_page`
|
|
245
|
+
|
|
246
|
+
Updates an existing wiki page. Requires `mcp:wiki:update` permission.
|
|
247
|
+
|
|
248
|
+
- **Parameters:**
|
|
249
|
+
- `page_id` (string, required): The page UUID
|
|
250
|
+
- `title` (string, optional): Updated title
|
|
251
|
+
- `content` (string, optional): Updated content (markdown)
|
|
252
|
+
- **Returns:** The updated wiki page object
|
|
253
|
+
|
|
254
|
+
### Journal Entries
|
|
255
|
+
|
|
256
|
+
Journal entries are daily logs, standup notes, or progress updates.
|
|
257
|
+
|
|
258
|
+
#### `list_journal_entries`
|
|
259
|
+
|
|
260
|
+
Lists journal entries from your accessible teams.
|
|
261
|
+
|
|
262
|
+
- **Parameters:**
|
|
263
|
+
- `limit` (number, optional): Maximum entries to return
|
|
264
|
+
- `offset` (number, optional): Entries to skip
|
|
265
|
+
- **Returns:** Array of journal entry objects
|
|
266
|
+
|
|
267
|
+
#### `get_journal_entry`
|
|
268
|
+
|
|
269
|
+
Gets a specific journal entry by ID or reference number.
|
|
270
|
+
|
|
271
|
+
- **Parameters:**
|
|
272
|
+
- `entry_id` (string, required): The entry UUID or refId
|
|
273
|
+
- `team_id` (number, optional): Team ID (required when using refId)
|
|
274
|
+
- **Returns:** Journal entry object
|
|
275
|
+
|
|
276
|
+
#### `create_journal_entry`
|
|
277
|
+
|
|
278
|
+
Creates a new journal entry. Requires `mcp:journal:create` permission.
|
|
279
|
+
|
|
280
|
+
- **Parameters:**
|
|
281
|
+
- `title` (string, required): Entry title
|
|
282
|
+
- `content` (string, required): Entry content (markdown)
|
|
283
|
+
- `date` (string, optional): Entry date (ISO format, defaults to now)
|
|
284
|
+
- **Returns:** The created journal entry object
|
|
285
|
+
|
|
286
|
+
#### `update_journal_entry`
|
|
287
|
+
|
|
288
|
+
Updates an existing journal entry. Requires `mcp:journal:update` permission.
|
|
289
|
+
|
|
290
|
+
- **Parameters:**
|
|
291
|
+
- `entry_id` (string, required): The entry UUID
|
|
292
|
+
- `title` (string, optional): Updated title
|
|
293
|
+
- `content` (string, optional): Updated content (markdown)
|
|
294
|
+
- **Returns:** The updated journal entry object
|
|
295
|
+
|
|
296
|
+
### Comments
|
|
297
|
+
|
|
298
|
+
#### `create_work_item_comment`
|
|
299
|
+
|
|
300
|
+
Adds a comment to a work item. Requires `mcp:comments:create` permission.
|
|
301
|
+
|
|
302
|
+
- **Parameters:**
|
|
303
|
+
- `work_item_id` (string, required): The work item UUID
|
|
304
|
+
- `content` (string, required): Comment content (markdown)
|
|
305
|
+
- **Returns:** The created comment object
|
|
306
|
+
|
|
307
|
+
#### `create_issue_comment`
|
|
308
|
+
|
|
309
|
+
Adds a comment to an issue. Requires `mcp:comments:create` permission.
|
|
310
|
+
|
|
311
|
+
- **Parameters:**
|
|
312
|
+
- `issue_id` (string, required): The issue UUID
|
|
313
|
+
- `content` (string, required): Comment content (markdown)
|
|
314
|
+
- **Returns:** The created comment object
|
|
315
|
+
|
|
316
|
+
### Radar Items (Initiatives)
|
|
317
|
+
|
|
318
|
+
Radar items are strategic initiatives or roadmap items that group related work.
|
|
319
|
+
|
|
320
|
+
#### `list_radar_items`
|
|
321
|
+
|
|
322
|
+
Lists radar items from the team.
|
|
323
|
+
|
|
324
|
+
- **Parameters:**
|
|
325
|
+
- `team_id` (number, optional): Team ID to filter
|
|
326
|
+
- `stage` (string, optional): Filter by stage (pending, now, next, later, completed)
|
|
327
|
+
- `limit` (number, optional): Maximum items to return
|
|
328
|
+
- `offset` (number, optional): Items to skip
|
|
329
|
+
- **Returns:** Array of radar item objects
|
|
330
|
+
|
|
331
|
+
#### `get_radar_item`
|
|
332
|
+
|
|
333
|
+
Gets a specific radar item by ID or reference number.
|
|
334
|
+
|
|
335
|
+
- **Parameters:**
|
|
336
|
+
- `item_id` (string, required): The radar item ID (UUID) or refId (number)
|
|
337
|
+
- `team_id` (number, optional): Team ID (required when using refId)
|
|
338
|
+
- **Returns:** Radar item object with work items and participants
|
|
339
|
+
|
|
340
|
+
## Security
|
|
341
|
+
|
|
342
|
+
- API keys authenticate with your Wolfpack account
|
|
343
|
+
- Each key has configurable permissions for granular access control
|
|
344
|
+
- Two-layer permission system: API key permissions checked first, then team role permissions
|
|
345
|
+
- All operations are scoped to teams you are a member of
|
|
346
|
+
- API keys can be revoked at any time from the Wolfpack application
|
|
347
|
+
- All changes are logged with `source: 'mcp'` for audit trails
|
|
348
|
+
|
|
349
|
+
## Troubleshooting
|
|
350
|
+
|
|
351
|
+
### "WOLFPACK_API_KEY environment variable is required"
|
|
352
|
+
|
|
353
|
+
This error means the API key is not configured. Check your Claude Desktop configuration file and ensure the `WOLFPACK_API_KEY` is set correctly.
|
|
354
|
+
|
|
355
|
+
### "Invalid API key"
|
|
356
|
+
|
|
357
|
+
Your API key may be incorrect or has been revoked. Generate a new API key from the Wolfpack application.
|
|
358
|
+
|
|
359
|
+
### "API key does not have permission: mcp:..."
|
|
360
|
+
|
|
361
|
+
Your API key doesn't have the required MCP permission. Create a new key with the appropriate permissions enabled.
|
|
362
|
+
|
|
363
|
+
### Connection errors
|
|
364
|
+
|
|
365
|
+
Ensure:
|
|
366
|
+
|
|
367
|
+
1. The Wolfpack service is available at [wolfpacks.work](https://wolfpacks.work)
|
|
368
|
+
2. Your network allows connections to the backend
|
|
369
|
+
3. If self-hosting, verify `WOLFPACK_API_URL` points to the correct backend URL
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import fetch from 'node-fetch';
|
|
2
|
+
import { config } from './config.js';
|
|
3
|
+
export class ApiError extends Error {
|
|
4
|
+
status;
|
|
5
|
+
constructor(status, message) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.status = status;
|
|
8
|
+
this.name = 'ApiError';
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
export class ApiClient {
|
|
12
|
+
headers = {
|
|
13
|
+
Authorization: `Bearer ${config.apiKey}`,
|
|
14
|
+
'Content-Type': 'application/json',
|
|
15
|
+
};
|
|
16
|
+
async get(path) {
|
|
17
|
+
const url = `${config.apiUrl}${path}`;
|
|
18
|
+
try {
|
|
19
|
+
const response = await fetch(url, {
|
|
20
|
+
method: 'GET',
|
|
21
|
+
headers: this.headers,
|
|
22
|
+
});
|
|
23
|
+
if (!response.ok) {
|
|
24
|
+
const errorText = await response.text();
|
|
25
|
+
throw new ApiError(response.status, `API error: ${response.statusText} - ${errorText}`);
|
|
26
|
+
}
|
|
27
|
+
return (await response.json());
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
if (error instanceof ApiError) {
|
|
31
|
+
throw error;
|
|
32
|
+
}
|
|
33
|
+
throw new Error(`Failed to GET ${path}: ${error}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
async post(path, body) {
|
|
37
|
+
const url = `${config.apiUrl}${path}`;
|
|
38
|
+
try {
|
|
39
|
+
const response = await fetch(url, {
|
|
40
|
+
method: 'POST',
|
|
41
|
+
headers: this.headers,
|
|
42
|
+
body: JSON.stringify(body),
|
|
43
|
+
});
|
|
44
|
+
if (!response.ok) {
|
|
45
|
+
const errorText = await response.text();
|
|
46
|
+
throw new ApiError(response.status, `API error: ${response.statusText} - ${errorText}`);
|
|
47
|
+
}
|
|
48
|
+
return (await response.json());
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
if (error instanceof ApiError) {
|
|
52
|
+
throw error;
|
|
53
|
+
}
|
|
54
|
+
throw new Error(`Failed to POST ${path}: ${error}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
async put(path, body) {
|
|
58
|
+
const url = `${config.apiUrl}${path}`;
|
|
59
|
+
try {
|
|
60
|
+
const response = await fetch(url, {
|
|
61
|
+
method: 'PUT',
|
|
62
|
+
headers: this.headers,
|
|
63
|
+
body: JSON.stringify(body),
|
|
64
|
+
});
|
|
65
|
+
if (!response.ok) {
|
|
66
|
+
const errorText = await response.text();
|
|
67
|
+
throw new ApiError(response.status, `API error: ${response.statusText} - ${errorText}`);
|
|
68
|
+
}
|
|
69
|
+
return (await response.json());
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
if (error instanceof ApiError) {
|
|
73
|
+
throw error;
|
|
74
|
+
}
|
|
75
|
+
throw new Error(`Failed to PUT ${path}: ${error}`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
async patch(path, body) {
|
|
79
|
+
const url = `${config.apiUrl}${path}`;
|
|
80
|
+
try {
|
|
81
|
+
const response = await fetch(url, {
|
|
82
|
+
method: 'PATCH',
|
|
83
|
+
headers: this.headers,
|
|
84
|
+
body: JSON.stringify(body),
|
|
85
|
+
});
|
|
86
|
+
if (!response.ok) {
|
|
87
|
+
const errorText = await response.text();
|
|
88
|
+
throw new ApiError(response.status, `API error: ${response.statusText} - ${errorText}`);
|
|
89
|
+
}
|
|
90
|
+
return (await response.json());
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
if (error instanceof ApiError) {
|
|
94
|
+
throw error;
|
|
95
|
+
}
|
|
96
|
+
throw new Error(`Failed to PATCH ${path}: ${error}`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
async delete(path) {
|
|
100
|
+
const url = `${config.apiUrl}${path}`;
|
|
101
|
+
try {
|
|
102
|
+
const response = await fetch(url, {
|
|
103
|
+
method: 'DELETE',
|
|
104
|
+
headers: this.headers,
|
|
105
|
+
});
|
|
106
|
+
if (!response.ok) {
|
|
107
|
+
const errorText = await response.text();
|
|
108
|
+
throw new ApiError(response.status, `API error: ${response.statusText} - ${errorText}`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
if (error instanceof ApiError) {
|
|
113
|
+
throw error;
|
|
114
|
+
}
|
|
115
|
+
throw new Error(`Failed to DELETE ${path}: ${error}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|