@jorgeluismlima/teamwork-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 +79 -0
- package/dist/index.js +369 -0
- package/package.json +34 -0
package/README.md
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# Teamwork MCP Server
|
|
2
|
+
|
|
3
|
+
A Model Context Protocol (MCP) server that connects to the Teamwork API, allowing AI agents to interact with projects, time entries, people, and companies.
|
|
4
|
+
|
|
5
|
+
## Functionalities (Tools)
|
|
6
|
+
|
|
7
|
+
This server exposes the following tools:
|
|
8
|
+
|
|
9
|
+
### Projects & Time Tracking
|
|
10
|
+
* **`list_projects`**: Listings projects, filtering by search term.
|
|
11
|
+
* **`create_time_entry`**: Creates a time entry (timelog) for a specific project.
|
|
12
|
+
* **`get_time_entries`**: Retrieves time entries for a project, with optional date filtering.
|
|
13
|
+
* **`total_count_of_active_projects`**: Returns the total count of active projects.
|
|
14
|
+
* **`total_billable_time_per_project`**: Returns total billable minutes per project (optional date range).
|
|
15
|
+
* **`get_health_stats`**: Returns project health metrics (Good, Bad, OK, etc.), with various filters.
|
|
16
|
+
* **`get_project_time_totals`**: Returns total hours/minutes for a project (optionally filtered by user).
|
|
17
|
+
|
|
18
|
+
### People & Companies
|
|
19
|
+
* **`get_project_people`**: Lists all people associated with a project.
|
|
20
|
+
* **`get_person`**: Retrieves detailed information about a specific person (user).
|
|
21
|
+
* **`list_companies`**: Lists companies (clients).
|
|
22
|
+
* **`get_company`**: Retrieves details of a specific company.
|
|
23
|
+
|
|
24
|
+
## Configuration
|
|
25
|
+
|
|
26
|
+
To use this server, you need to configure the following environment variables:
|
|
27
|
+
|
|
28
|
+
* **`TEAMWORK_SITE_NAME`**: Your Teamwork site name/subdomain (e.g., `agencyname`).
|
|
29
|
+
* **`TEAMWORK_USERNAME`**: Your Teamwork username or email.
|
|
30
|
+
* **`TEAMWORK_PASSWORD`**: Your Teamwork password or API Key.
|
|
31
|
+
|
|
32
|
+
## Prerequisites
|
|
33
|
+
|
|
34
|
+
Before using this MCP server, ensure you have:
|
|
35
|
+
|
|
36
|
+
* **Node.js**: Version 18 or higher installed. [Download Node.js](https://nodejs.org/)
|
|
37
|
+
* **Teamwork Account**: You must have an active Teamwork account.
|
|
38
|
+
* **API Credentials**: You need your Teamwork Site Name and an API Key (or password) to authenticate.
|
|
39
|
+
|
|
40
|
+
## Installation & Running
|
|
41
|
+
|
|
42
|
+
1. **Install dependencies:**
|
|
43
|
+
```bash
|
|
44
|
+
npm install
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
2. **Build the project:**
|
|
48
|
+
```bash
|
|
49
|
+
npm run build
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
3. **Run the server:**
|
|
53
|
+
```bash
|
|
54
|
+
node dist/index.js
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Client Configuration (Example)
|
|
58
|
+
|
|
59
|
+
To add this server to an MCP client (like Claude Desktop or an IDE extension), add the following to your MCP configuration file:
|
|
60
|
+
|
|
61
|
+
```json
|
|
62
|
+
{
|
|
63
|
+
"mcpServers": {
|
|
64
|
+
"teamwork": {
|
|
65
|
+
"command": "node",
|
|
66
|
+
"args": [
|
|
67
|
+
"c:/path/to/teamwork-mcp/dist/index.js"
|
|
68
|
+
],
|
|
69
|
+
"env": {
|
|
70
|
+
"TEAMWORK_SITE_NAME": "your-site-name",
|
|
71
|
+
"TEAMWORK_USERNAME": "your-username",
|
|
72
|
+
"TEAMWORK_PASSWORD": "your-password-or-api-key"
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
> **Note:** Replace `c:/path/to/teamwork-mcp/dist/index.js` with the absolute path to the built `index.js` file on your system.
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import axios from "axios";
|
|
6
|
+
import dotenv from "dotenv";
|
|
7
|
+
const originalWrite = process.stdout.write.bind(process.stdout);
|
|
8
|
+
// @ts-ignore
|
|
9
|
+
process.stdout.write = () => true;
|
|
10
|
+
dotenv.config();
|
|
11
|
+
// @ts-ignore
|
|
12
|
+
process.stdout.write = originalWrite;
|
|
13
|
+
const TEAMWORK_SITE_NAME = process.env.TEAMWORK_SITE_NAME;
|
|
14
|
+
const TEAMWORK_USERNAME = process.env.TEAMWORK_USERNAME;
|
|
15
|
+
const TEAMWORK_PASSWORD = process.env.TEAMWORK_PASSWORD;
|
|
16
|
+
if (!TEAMWORK_SITE_NAME || !TEAMWORK_USERNAME || !TEAMWORK_PASSWORD) {
|
|
17
|
+
console.error("Missing TEAMWORK_SITE_NAME, TEAMWORK_USERNAME, or TEAMWORK_PASSWORD environment variables");
|
|
18
|
+
process.exit(1);
|
|
19
|
+
}
|
|
20
|
+
const BASE_URL = `https://${TEAMWORK_SITE_NAME}.teamwork.com/projects/api/v3`;
|
|
21
|
+
const server = new McpServer({
|
|
22
|
+
name: "teamwork-mcp",
|
|
23
|
+
version: "1.0.0",
|
|
24
|
+
});
|
|
25
|
+
const axiosInstance = axios.create({
|
|
26
|
+
baseURL: BASE_URL,
|
|
27
|
+
auth: {
|
|
28
|
+
username: TEAMWORK_USERNAME,
|
|
29
|
+
password: TEAMWORK_PASSWORD,
|
|
30
|
+
},
|
|
31
|
+
headers: {
|
|
32
|
+
"Content-Type": "application/json",
|
|
33
|
+
Accept: "application/json",
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
// Helper to handle API errors
|
|
37
|
+
const handleApiError = (error) => {
|
|
38
|
+
if (axios.isAxiosError(error)) {
|
|
39
|
+
return {
|
|
40
|
+
content: [
|
|
41
|
+
{
|
|
42
|
+
type: "text",
|
|
43
|
+
text: `Teamwork API Error: ${error.response?.status} - ${JSON.stringify(error.response?.data || error.message)}`,
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
isError: true,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
content: [
|
|
51
|
+
{
|
|
52
|
+
type: "text",
|
|
53
|
+
text: `Unexpected Error: ${error instanceof Error ? error.message : String(error)}`,
|
|
54
|
+
},
|
|
55
|
+
],
|
|
56
|
+
isError: true,
|
|
57
|
+
};
|
|
58
|
+
};
|
|
59
|
+
server.tool("list_projects", {
|
|
60
|
+
searchTerm: z.string().optional().describe("Filter by project name"),
|
|
61
|
+
page: z.number().optional().describe("Page number"),
|
|
62
|
+
}, async ({ searchTerm, page }) => {
|
|
63
|
+
try {
|
|
64
|
+
const params = {};
|
|
65
|
+
if (searchTerm)
|
|
66
|
+
params.searchTerm = searchTerm;
|
|
67
|
+
if (page)
|
|
68
|
+
params.page = page;
|
|
69
|
+
const response = await axiosInstance.get("/projects.json", { params });
|
|
70
|
+
const simplifiedProjects = response.data.projects.map((p) => ({
|
|
71
|
+
id: p.id,
|
|
72
|
+
name: p.name,
|
|
73
|
+
description: p.description,
|
|
74
|
+
status: p.status,
|
|
75
|
+
company: p.company,
|
|
76
|
+
createdOn: p.createdOn,
|
|
77
|
+
lastUpdated: p.lastUpdated
|
|
78
|
+
}));
|
|
79
|
+
return {
|
|
80
|
+
content: [
|
|
81
|
+
{
|
|
82
|
+
type: "text",
|
|
83
|
+
text: JSON.stringify({ projects: simplifiedProjects, meta: response.data.meta }, null, 2),
|
|
84
|
+
},
|
|
85
|
+
],
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
return handleApiError(error);
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
server.tool("create_time_entry", {
|
|
93
|
+
projectId: z.number().describe("The ID of the project"),
|
|
94
|
+
description: z.string().describe("Description of the time entry"),
|
|
95
|
+
date: z.string().regex(/^\d{4}-?\d{2}-?\d{2}$/).describe("Date in YYYYMMDD or YYYY-MM-DD format"),
|
|
96
|
+
time: z.string().regex(/^\d{2}:\d{2}(:\d{2})?$/).describe("Time in HH:MM or HH:MM:SS format"),
|
|
97
|
+
hours: z.number().min(0).describe("Number of hours"),
|
|
98
|
+
minutes: z.number().min(0).max(59).describe("Number of minutes"),
|
|
99
|
+
isbillable: z.boolean().describe("Whether the time is billable"),
|
|
100
|
+
personId: z.number().optional().describe("ID of the person (optional)"),
|
|
101
|
+
}, async ({ projectId, description, date, time, hours, minutes, isbillable, personId }) => {
|
|
102
|
+
try {
|
|
103
|
+
const payload = {
|
|
104
|
+
timelog: {
|
|
105
|
+
description,
|
|
106
|
+
date,
|
|
107
|
+
time,
|
|
108
|
+
hours: hours,
|
|
109
|
+
minutes: minutes,
|
|
110
|
+
isBillable: isbillable,
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
if (personId) {
|
|
114
|
+
payload.timelog.personId = personId;
|
|
115
|
+
}
|
|
116
|
+
const response = await axiosInstance.post(`/projects/${projectId}/time.json`, payload);
|
|
117
|
+
return {
|
|
118
|
+
content: [
|
|
119
|
+
{
|
|
120
|
+
type: "text",
|
|
121
|
+
text: JSON.stringify(response.data, null, 2),
|
|
122
|
+
},
|
|
123
|
+
],
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
catch (error) {
|
|
127
|
+
return handleApiError(error);
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
server.tool("get_time_entries", {
|
|
131
|
+
projectId: z.number().describe("The ID of the project"),
|
|
132
|
+
page: z.number().optional().describe("Page number"),
|
|
133
|
+
fromDate: z.string().optional().describe("Start date (YYYYMMDD)"),
|
|
134
|
+
toDate: z.string().optional().describe("End date (YYYYMMDD)"),
|
|
135
|
+
}, async ({ projectId, page, fromDate, toDate }) => {
|
|
136
|
+
try {
|
|
137
|
+
const params = {};
|
|
138
|
+
if (page)
|
|
139
|
+
params.page = page;
|
|
140
|
+
if (fromDate)
|
|
141
|
+
params.fromDate = fromDate;
|
|
142
|
+
if (toDate)
|
|
143
|
+
params.toDate = toDate;
|
|
144
|
+
const response = await axiosInstance.get(`/projects/${projectId}/time.json`, { params });
|
|
145
|
+
return {
|
|
146
|
+
content: [
|
|
147
|
+
{
|
|
148
|
+
type: "text",
|
|
149
|
+
text: JSON.stringify(response.data, null, 2),
|
|
150
|
+
},
|
|
151
|
+
],
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
catch (error) {
|
|
155
|
+
return handleApiError(error);
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
server.tool("get_project_people", {
|
|
159
|
+
projectId: z.number().describe("The ID of the project"),
|
|
160
|
+
}, async ({ projectId }) => {
|
|
161
|
+
try {
|
|
162
|
+
const response = await axiosInstance.get(`/projects/${projectId}/people.json`);
|
|
163
|
+
return {
|
|
164
|
+
content: [
|
|
165
|
+
{
|
|
166
|
+
type: "text",
|
|
167
|
+
text: JSON.stringify(response.data, null, 2),
|
|
168
|
+
},
|
|
169
|
+
],
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
catch (error) {
|
|
173
|
+
return handleApiError(error);
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
server.tool("get_person", {
|
|
177
|
+
personId: z.number().describe("The ID of the person"),
|
|
178
|
+
}, async ({ personId }) => {
|
|
179
|
+
try {
|
|
180
|
+
const response = await axiosInstance.get(`/people/${personId}.json`);
|
|
181
|
+
return {
|
|
182
|
+
content: [
|
|
183
|
+
{
|
|
184
|
+
type: "text",
|
|
185
|
+
text: JSON.stringify(response.data, null, 2),
|
|
186
|
+
},
|
|
187
|
+
],
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
catch (error) {
|
|
191
|
+
return handleApiError(error);
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
server.tool("get_project_time_totals", {
|
|
195
|
+
projectId: z.number().describe("The ID of the project"),
|
|
196
|
+
userId: z.number().optional().describe("Filter by user ID"),
|
|
197
|
+
fromDate: z.string().optional().describe("Start date (YYYYMMDD)"),
|
|
198
|
+
toDate: z.string().optional().describe("End date (YYYYMMDD)"),
|
|
199
|
+
}, async ({ projectId, userId, fromDate, toDate }) => {
|
|
200
|
+
try {
|
|
201
|
+
const params = {};
|
|
202
|
+
if (userId)
|
|
203
|
+
params.userId = userId;
|
|
204
|
+
if (fromDate)
|
|
205
|
+
params.fromDate = fromDate;
|
|
206
|
+
if (toDate)
|
|
207
|
+
params.toDate = toDate;
|
|
208
|
+
const response = await axiosInstance.get(`/projects/${projectId}/time/total.json`, { params });
|
|
209
|
+
return {
|
|
210
|
+
content: [
|
|
211
|
+
{
|
|
212
|
+
type: "text",
|
|
213
|
+
text: JSON.stringify(response.data, null, 2),
|
|
214
|
+
},
|
|
215
|
+
],
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
catch (error) {
|
|
219
|
+
return handleApiError(error);
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
server.tool("total_count_of_active_projects", {}, async () => {
|
|
223
|
+
try {
|
|
224
|
+
const response = await axiosInstance.get("/projects/metrics/active.json");
|
|
225
|
+
return {
|
|
226
|
+
content: [
|
|
227
|
+
{
|
|
228
|
+
type: "text",
|
|
229
|
+
text: JSON.stringify(response.data, null, 2),
|
|
230
|
+
},
|
|
231
|
+
],
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
catch (error) {
|
|
235
|
+
return handleApiError(error);
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
server.tool("total_billable_time_per_project", {
|
|
239
|
+
startDate: z.string().optional().describe("Start date (YYYYMMDD)"),
|
|
240
|
+
endDate: z.string().optional().describe("End date (YYYYMMDD)"),
|
|
241
|
+
orderMode: z.string().optional().describe("Order mode (asc, desc). Default: desc"),
|
|
242
|
+
}, async ({ startDate, endDate, orderMode }) => {
|
|
243
|
+
try {
|
|
244
|
+
const params = {};
|
|
245
|
+
if (startDate)
|
|
246
|
+
params.startDate = startDate;
|
|
247
|
+
if (endDate)
|
|
248
|
+
params.endDate = endDate;
|
|
249
|
+
if (orderMode)
|
|
250
|
+
params.orderMode = orderMode;
|
|
251
|
+
const response = await axiosInstance.get("/projects/metrics/billable.json", {
|
|
252
|
+
params,
|
|
253
|
+
});
|
|
254
|
+
return {
|
|
255
|
+
content: [
|
|
256
|
+
{
|
|
257
|
+
type: "text",
|
|
258
|
+
text: JSON.stringify(response.data, null, 2),
|
|
259
|
+
},
|
|
260
|
+
],
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
catch (error) {
|
|
264
|
+
return handleApiError(error);
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
server.tool("get_health_stats", {
|
|
268
|
+
projectStatus: z.string().optional().describe("Filter by project status (active, current, late, upcoming, completed, deleted)"),
|
|
269
|
+
onlyStarredProjects: z.boolean().optional().describe("Filter by starred projects only"),
|
|
270
|
+
matchAllProjectTags: z.boolean().optional().describe("Match all project tags"),
|
|
271
|
+
projectTagIds: z.array(z.number()).optional().describe("Filter by project tag ids"),
|
|
272
|
+
projectStatuses: z.array(z.string()).optional().describe("Filter by project statuses"),
|
|
273
|
+
projectOwnerIds: z.array(z.number()).optional().describe("Filter by project owner ids"),
|
|
274
|
+
projectIds: z.array(z.number()).optional().describe("Filter by project ids"),
|
|
275
|
+
projectHealths: z.array(z.number()).optional().describe("Project health (0: not set, 1: bad, 2: ok, 3: good)"),
|
|
276
|
+
projectCompanyIds: z.array(z.number()).optional().describe("Filter by company ids"),
|
|
277
|
+
projectCategoryIds: z.array(z.number()).optional().describe("Filter by project category ids"),
|
|
278
|
+
}, async (args) => {
|
|
279
|
+
try {
|
|
280
|
+
const params = {};
|
|
281
|
+
if (args.projectStatus)
|
|
282
|
+
params.projectStatus = args.projectStatus;
|
|
283
|
+
if (args.onlyStarredProjects !== undefined)
|
|
284
|
+
params.onlyStarredProjects = args.onlyStarredProjects;
|
|
285
|
+
if (args.matchAllProjectTags !== undefined)
|
|
286
|
+
params.matchAllProjectTags = args.matchAllProjectTags;
|
|
287
|
+
// Helper to join arrays for API
|
|
288
|
+
const joinParam = (arr) => arr ? arr.join(',') : undefined;
|
|
289
|
+
if (args.projectTagIds)
|
|
290
|
+
params.projectTagIds = joinParam(args.projectTagIds);
|
|
291
|
+
if (args.projectStatuses)
|
|
292
|
+
params.projectStatuses = joinParam(args.projectStatuses);
|
|
293
|
+
if (args.projectOwnerIds)
|
|
294
|
+
params.projectOwnerIds = joinParam(args.projectOwnerIds);
|
|
295
|
+
if (args.projectIds)
|
|
296
|
+
params.projectIds = joinParam(args.projectIds);
|
|
297
|
+
if (args.projectHealths)
|
|
298
|
+
params.projectHealths = joinParam(args.projectHealths);
|
|
299
|
+
if (args.projectCompanyIds)
|
|
300
|
+
params.projectCompanyIds = joinParam(args.projectCompanyIds);
|
|
301
|
+
if (args.projectCategoryIds)
|
|
302
|
+
params.projectCategoryIds = joinParam(args.projectCategoryIds);
|
|
303
|
+
const response = await axiosInstance.get("/projects/metrics/healths.json", {
|
|
304
|
+
params,
|
|
305
|
+
});
|
|
306
|
+
return {
|
|
307
|
+
content: [
|
|
308
|
+
{
|
|
309
|
+
type: "text",
|
|
310
|
+
text: JSON.stringify(response.data, null, 2),
|
|
311
|
+
},
|
|
312
|
+
],
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
catch (error) {
|
|
316
|
+
return handleApiError(error);
|
|
317
|
+
}
|
|
318
|
+
});
|
|
319
|
+
server.tool("list_companies", {
|
|
320
|
+
page: z.number().optional().describe("Page number"),
|
|
321
|
+
searchTerm: z.string().optional().describe("Filter by search term"),
|
|
322
|
+
}, async ({ page, searchTerm }) => {
|
|
323
|
+
try {
|
|
324
|
+
const params = {};
|
|
325
|
+
if (page)
|
|
326
|
+
params.page = page;
|
|
327
|
+
if (searchTerm)
|
|
328
|
+
params.searchTerm = searchTerm;
|
|
329
|
+
const response = await axiosInstance.get("/companies.json", { params });
|
|
330
|
+
return {
|
|
331
|
+
content: [
|
|
332
|
+
{
|
|
333
|
+
type: "text",
|
|
334
|
+
text: JSON.stringify(response.data, null, 2),
|
|
335
|
+
},
|
|
336
|
+
],
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
catch (error) {
|
|
340
|
+
return handleApiError(error);
|
|
341
|
+
}
|
|
342
|
+
});
|
|
343
|
+
server.tool("get_company", {
|
|
344
|
+
companyId: z.number().describe("The ID of the company"),
|
|
345
|
+
}, async ({ companyId }) => {
|
|
346
|
+
try {
|
|
347
|
+
const response = await axiosInstance.get(`/companies/${companyId}.json`);
|
|
348
|
+
return {
|
|
349
|
+
content: [
|
|
350
|
+
{
|
|
351
|
+
type: "text",
|
|
352
|
+
text: JSON.stringify(response.data, null, 2),
|
|
353
|
+
},
|
|
354
|
+
],
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
catch (error) {
|
|
358
|
+
return handleApiError(error);
|
|
359
|
+
}
|
|
360
|
+
});
|
|
361
|
+
async function main() {
|
|
362
|
+
const transport = new StdioServerTransport();
|
|
363
|
+
await server.connect(transport);
|
|
364
|
+
console.error("Teamwork MCP Server running on stdio");
|
|
365
|
+
}
|
|
366
|
+
main().catch((error) => {
|
|
367
|
+
console.error("Fatal error in main loop:", error);
|
|
368
|
+
process.exit(1);
|
|
369
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@jorgeluismlima/teamwork-mcp",
|
|
3
|
+
"publishConfig": {
|
|
4
|
+
"access": "public"
|
|
5
|
+
},
|
|
6
|
+
"version": "1.0.0",
|
|
7
|
+
"description": "MCP Server for Teamwork",
|
|
8
|
+
"main": "dist/index.js",
|
|
9
|
+
"bin": {
|
|
10
|
+
"teamwork-mcp": "./dist/index.js"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"dist"
|
|
14
|
+
],
|
|
15
|
+
"type": "module",
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "tsc",
|
|
18
|
+
"start": "node dist/index.js",
|
|
19
|
+
"dev": "tsc --watch"
|
|
20
|
+
},
|
|
21
|
+
"keywords": [],
|
|
22
|
+
"author": "",
|
|
23
|
+
"license": "ISC",
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@modelcontextprotocol/sdk": "^1.0.1",
|
|
26
|
+
"axios": "^1.6.0",
|
|
27
|
+
"dotenv": "^16.4.5",
|
|
28
|
+
"zod": "^3.23.0"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@types/node": "^20.0.0",
|
|
32
|
+
"typescript": "^5.0.0"
|
|
33
|
+
}
|
|
34
|
+
}
|