@esaio/esa-mcp-server 0.2.0 → 0.2.2
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.en.md +10 -6
- package/README.md +16 -12
- package/bin/{index.js → index.mjs} +1 -1
- package/package.json +22 -3
- package/.dockerignore +0 -36
- package/.github/dependabot.yml +0 -23
- package/.github/workflows/docker-publish.yml +0 -120
- package/.github/workflows/main.yml +0 -41
- package/CLAUDE.md +0 -94
- package/Dockerfile +0 -34
- package/biome.json +0 -57
- package/src/__tests__/fixtures/mock-comment.ts +0 -90
- package/src/__tests__/fixtures/mock-post.ts +0 -79
- package/src/__tests__/index.test.ts +0 -216
- package/src/api_client/__tests__/index.test.ts +0 -149
- package/src/api_client/__tests__/middleware.test.ts +0 -120
- package/src/api_client/__tests__/with-context.test.ts +0 -98
- package/src/api_client/index.ts +0 -29
- package/src/api_client/middleware.ts +0 -21
- package/src/api_client/with-context.ts +0 -26
- package/src/config/__tests__/index.test.ts +0 -65
- package/src/config/index.ts +0 -20
- package/src/context/mcp-context.ts +0 -1
- package/src/context/stdio-context.ts +0 -6
- package/src/errors/missing-team-name-error.ts +0 -8
- package/src/formatters/__tests__/mcp-response.test.ts +0 -106
- package/src/formatters/mcp-response.ts +0 -95
- package/src/generated/api-types.ts +0 -2968
- package/src/i18n/__tests__/index.test.ts +0 -53
- package/src/i18n/index.ts +0 -39
- package/src/index.ts +0 -47
- package/src/locales/en.json +0 -13
- package/src/locales/ja.json +0 -13
- package/src/prompts/__tests__/index.test.ts +0 -48
- package/src/prompts/__tests__/summarize-post.test.ts +0 -291
- package/src/prompts/index.ts +0 -21
- package/src/prompts/summarize-post.ts +0 -94
- package/src/resources/__tests__/index.test.ts +0 -50
- package/src/resources/__tests__/recent-posts-list.test.ts +0 -92
- package/src/resources/__tests__/recent-posts.test.ts +0 -270
- package/src/resources/index.ts +0 -33
- package/src/resources/recent-posts-list.ts +0 -22
- package/src/resources/recent-posts.ts +0 -45
- package/src/schemas/team-name-schema.ts +0 -19
- package/src/tools/__tests__/attachments.test.ts +0 -460
- package/src/tools/__tests__/categories.test.ts +0 -402
- package/src/tools/__tests__/comments.test.ts +0 -970
- package/src/tools/__tests__/helps.test.ts +0 -222
- package/src/tools/__tests__/index.test.ts +0 -48
- package/src/tools/__tests__/post-actions.test.ts +0 -445
- package/src/tools/__tests__/posts.test.ts +0 -917
- package/src/tools/__tests__/search.test.ts +0 -339
- package/src/tools/__tests__/teams.test.ts +0 -615
- package/src/tools/attachments.ts +0 -167
- package/src/tools/categories.ts +0 -153
- package/src/tools/comments.ts +0 -258
- package/src/tools/helps.ts +0 -50
- package/src/tools/index.ts +0 -351
- package/src/tools/post-actions.ts +0 -132
- package/src/tools/posts.ts +0 -179
- package/src/tools/search.ts +0 -98
- package/src/tools/teams.ts +0 -157
- package/src/transformers/__tests__/category-transformer.test.ts +0 -161
- package/src/transformers/__tests__/comment-transformer.test.ts +0 -129
- package/src/transformers/__tests__/post-name-normalizer.test.ts +0 -53
- package/src/transformers/__tests__/post-transformer.test.ts +0 -70
- package/src/transformers/__tests__/query-normalizer.test.ts +0 -98
- package/src/transformers/__tests__/team-name-normalizer.test.ts +0 -21
- package/src/transformers/category-transformer.ts +0 -36
- package/src/transformers/comment-transformer.ts +0 -34
- package/src/transformers/post-name-normalizer.ts +0 -30
- package/src/transformers/post-transformer.ts +0 -38
- package/src/transformers/query-normalizer.ts +0 -36
- package/src/transformers/team-name-normalizer.ts +0 -7
- package/tsconfig.build.json +0 -4
- package/tsconfig.json +0 -30
- package/tsdown.config.ts +0 -13
- package/vitest.config.ts +0 -24
|
@@ -1,402 +0,0 @@
|
|
|
1
|
-
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
-
import type { createEsaClient } from "../../api_client/index.js";
|
|
3
|
-
import {
|
|
4
|
-
getAllCategoryPaths,
|
|
5
|
-
getCategories,
|
|
6
|
-
getTopCategories,
|
|
7
|
-
} from "../categories.js";
|
|
8
|
-
|
|
9
|
-
describe("getCategories", () => {
|
|
10
|
-
const mockClient = {
|
|
11
|
-
GET: vi.fn(),
|
|
12
|
-
} as unknown as ReturnType<typeof createEsaClient> & {
|
|
13
|
-
GET: ReturnType<typeof vi.fn>;
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
beforeEach(() => {
|
|
17
|
-
vi.clearAllMocks();
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
it("should fetch categories for a specific path", async () => {
|
|
21
|
-
const mockResponse = {
|
|
22
|
-
current_category: "dev/docs",
|
|
23
|
-
categories: [
|
|
24
|
-
{
|
|
25
|
-
name: "api",
|
|
26
|
-
full_name: "dev/docs/api",
|
|
27
|
-
count: 5,
|
|
28
|
-
has_child: true,
|
|
29
|
-
},
|
|
30
|
-
],
|
|
31
|
-
total_count: 1,
|
|
32
|
-
per_page: 20,
|
|
33
|
-
page: 1,
|
|
34
|
-
prev_page: null,
|
|
35
|
-
next_page: null,
|
|
36
|
-
max_per_page: 100,
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
mockClient.GET.mockResolvedValue({
|
|
40
|
-
data: mockResponse,
|
|
41
|
-
error: undefined,
|
|
42
|
-
response: { ok: true, status: 200 } as Response,
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
const result = await getCategories(mockClient, {
|
|
46
|
-
teamName: "test-team",
|
|
47
|
-
select: "dev/docs",
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
expect(mockClient.GET).toHaveBeenCalledWith(
|
|
51
|
-
"/v1/teams/{team_name}/categories",
|
|
52
|
-
{
|
|
53
|
-
params: {
|
|
54
|
-
path: { team_name: "test-team" },
|
|
55
|
-
query: {
|
|
56
|
-
select: "dev/docs",
|
|
57
|
-
include: undefined,
|
|
58
|
-
descendant_posts: undefined,
|
|
59
|
-
page: undefined,
|
|
60
|
-
per_page: undefined,
|
|
61
|
-
},
|
|
62
|
-
},
|
|
63
|
-
},
|
|
64
|
-
);
|
|
65
|
-
|
|
66
|
-
expect(result.content[0].text).toContain("dev/docs");
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
it("should handle API errors", async () => {
|
|
70
|
-
const mockError = { error: "not_found", message: "Category not found" };
|
|
71
|
-
|
|
72
|
-
mockClient.GET.mockResolvedValue({
|
|
73
|
-
data: undefined,
|
|
74
|
-
error: mockError,
|
|
75
|
-
response: { ok: false, status: 404 } as Response,
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
const result = await getCategories(mockClient, {
|
|
79
|
-
teamName: "test-team",
|
|
80
|
-
select: "nonexistent",
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
expect(result.content[0].text).toContain("not_found");
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
it("should handle network errors", async () => {
|
|
87
|
-
const networkError = new Error("Network connection failed");
|
|
88
|
-
|
|
89
|
-
mockClient.GET.mockRejectedValue(networkError);
|
|
90
|
-
|
|
91
|
-
const result = await getCategories(mockClient, {
|
|
92
|
-
teamName: "test-team",
|
|
93
|
-
select: "dev/docs",
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
expect(result.content[0].text).toContain("Network connection failed");
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
it("should handle non-Error exceptions", async () => {
|
|
100
|
-
mockClient.GET.mockRejectedValue("Unexpected error");
|
|
101
|
-
|
|
102
|
-
const result = await getCategories(mockClient, {
|
|
103
|
-
teamName: "test-team",
|
|
104
|
-
select: "dev/docs",
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
expect(result.content[0].text).toContain("Unexpected error");
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
it("should throw MissingTeamNameError when teamName is empty", async () => {
|
|
111
|
-
const result = await getCategories(mockClient, {
|
|
112
|
-
teamName: "",
|
|
113
|
-
select: "dev",
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
expect(result).toEqual({
|
|
117
|
-
content: [
|
|
118
|
-
{
|
|
119
|
-
type: "text",
|
|
120
|
-
text: "Error: Missing required parameter 'teamName'. Use esa_get_teams to list available teams, then retry with teamName specified.",
|
|
121
|
-
},
|
|
122
|
-
],
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
expect(mockClient.GET).not.toHaveBeenCalled();
|
|
126
|
-
});
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
describe("getTopCategories", () => {
|
|
130
|
-
const mockClient = {
|
|
131
|
-
GET: vi.fn(),
|
|
132
|
-
} as unknown as ReturnType<typeof createEsaClient> & {
|
|
133
|
-
GET: ReturnType<typeof vi.fn>;
|
|
134
|
-
};
|
|
135
|
-
|
|
136
|
-
beforeEach(() => {
|
|
137
|
-
vi.clearAllMocks();
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
it("should fetch top-level categories", async () => {
|
|
141
|
-
const mockResponse = {
|
|
142
|
-
current_category: "",
|
|
143
|
-
categories: [
|
|
144
|
-
{ name: "dev", full_name: "dev", count: 15, has_child: true },
|
|
145
|
-
{ name: "design", full_name: "design", count: 8, has_child: false },
|
|
146
|
-
],
|
|
147
|
-
total_count: 2,
|
|
148
|
-
per_page: 20,
|
|
149
|
-
page: 1,
|
|
150
|
-
prev_page: null,
|
|
151
|
-
next_page: null,
|
|
152
|
-
max_per_page: 100,
|
|
153
|
-
};
|
|
154
|
-
|
|
155
|
-
mockClient.GET.mockResolvedValue({
|
|
156
|
-
data: mockResponse,
|
|
157
|
-
error: undefined,
|
|
158
|
-
response: { ok: true, status: 200 } as Response,
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
const result = await getTopCategories(mockClient, {
|
|
162
|
-
teamName: "test-team",
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
expect(mockClient.GET).toHaveBeenCalledWith(
|
|
166
|
-
"/v1/teams/{team_name}/categories/top",
|
|
167
|
-
{
|
|
168
|
-
params: { path: { team_name: "test-team" } },
|
|
169
|
-
},
|
|
170
|
-
);
|
|
171
|
-
|
|
172
|
-
expect(result.content[0].text).toContain("dev");
|
|
173
|
-
expect(result.content[0].text).toContain("design");
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
it("should handle API errors", async () => {
|
|
177
|
-
const mockError = { error: "forbidden", message: "Access denied" };
|
|
178
|
-
|
|
179
|
-
mockClient.GET.mockResolvedValue({
|
|
180
|
-
data: undefined,
|
|
181
|
-
error: mockError,
|
|
182
|
-
response: { ok: false, status: 403 } as Response,
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
const result = await getTopCategories(mockClient, {
|
|
186
|
-
teamName: "test-team",
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
expect(result.content[0].text).toContain("forbidden");
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
it("should handle network errors", async () => {
|
|
193
|
-
const networkError = new Error("Network connection failed");
|
|
194
|
-
|
|
195
|
-
mockClient.GET.mockRejectedValue(networkError);
|
|
196
|
-
|
|
197
|
-
const result = await getTopCategories(mockClient, {
|
|
198
|
-
teamName: "test-team",
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
expect(result.content[0].text).toContain("Network connection failed");
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
it("should handle non-Error exceptions", async () => {
|
|
205
|
-
mockClient.GET.mockRejectedValue("Unexpected error");
|
|
206
|
-
|
|
207
|
-
const result = await getTopCategories(mockClient, {
|
|
208
|
-
teamName: "test-team",
|
|
209
|
-
});
|
|
210
|
-
|
|
211
|
-
expect(result.content[0].text).toContain("Unexpected error");
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
it("should throw MissingTeamNameError when teamName is empty", async () => {
|
|
215
|
-
const result = await getTopCategories(mockClient, {
|
|
216
|
-
teamName: "",
|
|
217
|
-
});
|
|
218
|
-
|
|
219
|
-
expect(result).toEqual({
|
|
220
|
-
content: [
|
|
221
|
-
{
|
|
222
|
-
type: "text",
|
|
223
|
-
text: "Error: Missing required parameter 'teamName'. Use esa_get_teams to list available teams, then retry with teamName specified.",
|
|
224
|
-
},
|
|
225
|
-
],
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
expect(mockClient.GET).not.toHaveBeenCalled();
|
|
229
|
-
});
|
|
230
|
-
});
|
|
231
|
-
|
|
232
|
-
describe("getAllCategoryPaths", () => {
|
|
233
|
-
const mockClient = {
|
|
234
|
-
GET: vi.fn(),
|
|
235
|
-
} as unknown as ReturnType<typeof createEsaClient> & {
|
|
236
|
-
GET: ReturnType<typeof vi.fn>;
|
|
237
|
-
};
|
|
238
|
-
|
|
239
|
-
beforeEach(() => {
|
|
240
|
-
vi.clearAllMocks();
|
|
241
|
-
});
|
|
242
|
-
|
|
243
|
-
it("should fetch all category paths", async () => {
|
|
244
|
-
const mockResponse = [
|
|
245
|
-
{ path: null, posts: 5 },
|
|
246
|
-
{ path: "dev", posts: 10 },
|
|
247
|
-
{ path: "dev/docs", posts: 3 },
|
|
248
|
-
{ path: "design", posts: 7 },
|
|
249
|
-
];
|
|
250
|
-
|
|
251
|
-
mockClient.GET.mockResolvedValue({
|
|
252
|
-
data: mockResponse,
|
|
253
|
-
error: undefined,
|
|
254
|
-
response: { ok: true, status: 200 } as Response,
|
|
255
|
-
});
|
|
256
|
-
|
|
257
|
-
const result = await getAllCategoryPaths(mockClient, {
|
|
258
|
-
teamName: "test-team",
|
|
259
|
-
});
|
|
260
|
-
|
|
261
|
-
expect(mockClient.GET).toHaveBeenCalledWith(
|
|
262
|
-
"/v1/teams/{team_name}/categories/paths",
|
|
263
|
-
{
|
|
264
|
-
params: {
|
|
265
|
-
path: { team_name: "test-team" },
|
|
266
|
-
query: {
|
|
267
|
-
prefix: undefined,
|
|
268
|
-
suffix: undefined,
|
|
269
|
-
match: undefined,
|
|
270
|
-
exact_match: undefined,
|
|
271
|
-
},
|
|
272
|
-
},
|
|
273
|
-
},
|
|
274
|
-
);
|
|
275
|
-
|
|
276
|
-
expect(result.content[0].text).toContain("dev");
|
|
277
|
-
expect(result.content[0].text).toContain("design");
|
|
278
|
-
});
|
|
279
|
-
|
|
280
|
-
it("should support prefix filter", async () => {
|
|
281
|
-
const mockResponse = [
|
|
282
|
-
{ path: "dev", posts: 10 },
|
|
283
|
-
{ path: "dev/docs", posts: 3 },
|
|
284
|
-
];
|
|
285
|
-
|
|
286
|
-
mockClient.GET.mockResolvedValue({
|
|
287
|
-
data: mockResponse,
|
|
288
|
-
error: undefined,
|
|
289
|
-
response: { ok: true, status: 200 } as Response,
|
|
290
|
-
});
|
|
291
|
-
|
|
292
|
-
const result = await getAllCategoryPaths(mockClient, {
|
|
293
|
-
teamName: "test-team",
|
|
294
|
-
prefix: "dev",
|
|
295
|
-
});
|
|
296
|
-
|
|
297
|
-
expect(mockClient.GET).toHaveBeenCalledWith(
|
|
298
|
-
"/v1/teams/{team_name}/categories/paths",
|
|
299
|
-
{
|
|
300
|
-
params: {
|
|
301
|
-
path: { team_name: "test-team" },
|
|
302
|
-
query: {
|
|
303
|
-
prefix: "dev",
|
|
304
|
-
suffix: undefined,
|
|
305
|
-
match: undefined,
|
|
306
|
-
exact_match: undefined,
|
|
307
|
-
},
|
|
308
|
-
},
|
|
309
|
-
},
|
|
310
|
-
);
|
|
311
|
-
|
|
312
|
-
expect(result.content[0].text).toContain("dev");
|
|
313
|
-
});
|
|
314
|
-
|
|
315
|
-
it("should support multiple filters", async () => {
|
|
316
|
-
const mockResponse = [{ path: "dev/docs", posts: 3 }];
|
|
317
|
-
|
|
318
|
-
mockClient.GET.mockResolvedValue({
|
|
319
|
-
data: mockResponse,
|
|
320
|
-
error: undefined,
|
|
321
|
-
response: { ok: true, status: 200 } as Response,
|
|
322
|
-
});
|
|
323
|
-
|
|
324
|
-
const result = await getAllCategoryPaths(mockClient, {
|
|
325
|
-
teamName: "test-team",
|
|
326
|
-
prefix: "dev",
|
|
327
|
-
suffix: "docs",
|
|
328
|
-
});
|
|
329
|
-
|
|
330
|
-
expect(mockClient.GET).toHaveBeenCalledWith(
|
|
331
|
-
"/v1/teams/{team_name}/categories/paths",
|
|
332
|
-
{
|
|
333
|
-
params: {
|
|
334
|
-
path: { team_name: "test-team" },
|
|
335
|
-
query: {
|
|
336
|
-
prefix: "dev",
|
|
337
|
-
suffix: "docs",
|
|
338
|
-
match: undefined,
|
|
339
|
-
exact_match: undefined,
|
|
340
|
-
},
|
|
341
|
-
},
|
|
342
|
-
},
|
|
343
|
-
);
|
|
344
|
-
|
|
345
|
-
expect(result.content[0].text).toContain("dev/docs");
|
|
346
|
-
});
|
|
347
|
-
|
|
348
|
-
it("should handle API errors", async () => {
|
|
349
|
-
const mockError = { error: "forbidden", message: "Access denied" };
|
|
350
|
-
|
|
351
|
-
mockClient.GET.mockResolvedValue({
|
|
352
|
-
data: undefined,
|
|
353
|
-
error: mockError,
|
|
354
|
-
response: { ok: false, status: 403 } as Response,
|
|
355
|
-
});
|
|
356
|
-
|
|
357
|
-
const result = await getAllCategoryPaths(mockClient, {
|
|
358
|
-
teamName: "test-team",
|
|
359
|
-
});
|
|
360
|
-
|
|
361
|
-
expect(result.content[0].text).toContain("forbidden");
|
|
362
|
-
});
|
|
363
|
-
|
|
364
|
-
it("should handle network errors", async () => {
|
|
365
|
-
const networkError = new Error("Network connection failed");
|
|
366
|
-
|
|
367
|
-
mockClient.GET.mockRejectedValue(networkError);
|
|
368
|
-
|
|
369
|
-
const result = await getAllCategoryPaths(mockClient, {
|
|
370
|
-
teamName: "test-team",
|
|
371
|
-
});
|
|
372
|
-
|
|
373
|
-
expect(result.content[0].text).toContain("Network connection failed");
|
|
374
|
-
});
|
|
375
|
-
|
|
376
|
-
it("should handle non-Error exceptions", async () => {
|
|
377
|
-
mockClient.GET.mockRejectedValue("Unexpected error");
|
|
378
|
-
|
|
379
|
-
const result = await getAllCategoryPaths(mockClient, {
|
|
380
|
-
teamName: "test-team",
|
|
381
|
-
});
|
|
382
|
-
|
|
383
|
-
expect(result.content[0].text).toContain("Unexpected error");
|
|
384
|
-
});
|
|
385
|
-
|
|
386
|
-
it("should throw MissingTeamNameError when teamName is empty", async () => {
|
|
387
|
-
const result = await getAllCategoryPaths(mockClient, {
|
|
388
|
-
teamName: "",
|
|
389
|
-
});
|
|
390
|
-
|
|
391
|
-
expect(result).toEqual({
|
|
392
|
-
content: [
|
|
393
|
-
{
|
|
394
|
-
type: "text",
|
|
395
|
-
text: "Error: Missing required parameter 'teamName'. Use esa_get_teams to list available teams, then retry with teamName specified.",
|
|
396
|
-
},
|
|
397
|
-
],
|
|
398
|
-
});
|
|
399
|
-
|
|
400
|
-
expect(mockClient.GET).not.toHaveBeenCalled();
|
|
401
|
-
});
|
|
402
|
-
});
|