@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.
Files changed (78) hide show
  1. package/README.en.md +10 -6
  2. package/README.md +16 -12
  3. package/bin/{index.js → index.mjs} +1 -1
  4. package/package.json +22 -3
  5. package/.dockerignore +0 -36
  6. package/.github/dependabot.yml +0 -23
  7. package/.github/workflows/docker-publish.yml +0 -120
  8. package/.github/workflows/main.yml +0 -41
  9. package/CLAUDE.md +0 -94
  10. package/Dockerfile +0 -34
  11. package/biome.json +0 -57
  12. package/src/__tests__/fixtures/mock-comment.ts +0 -90
  13. package/src/__tests__/fixtures/mock-post.ts +0 -79
  14. package/src/__tests__/index.test.ts +0 -216
  15. package/src/api_client/__tests__/index.test.ts +0 -149
  16. package/src/api_client/__tests__/middleware.test.ts +0 -120
  17. package/src/api_client/__tests__/with-context.test.ts +0 -98
  18. package/src/api_client/index.ts +0 -29
  19. package/src/api_client/middleware.ts +0 -21
  20. package/src/api_client/with-context.ts +0 -26
  21. package/src/config/__tests__/index.test.ts +0 -65
  22. package/src/config/index.ts +0 -20
  23. package/src/context/mcp-context.ts +0 -1
  24. package/src/context/stdio-context.ts +0 -6
  25. package/src/errors/missing-team-name-error.ts +0 -8
  26. package/src/formatters/__tests__/mcp-response.test.ts +0 -106
  27. package/src/formatters/mcp-response.ts +0 -95
  28. package/src/generated/api-types.ts +0 -2968
  29. package/src/i18n/__tests__/index.test.ts +0 -53
  30. package/src/i18n/index.ts +0 -39
  31. package/src/index.ts +0 -47
  32. package/src/locales/en.json +0 -13
  33. package/src/locales/ja.json +0 -13
  34. package/src/prompts/__tests__/index.test.ts +0 -48
  35. package/src/prompts/__tests__/summarize-post.test.ts +0 -291
  36. package/src/prompts/index.ts +0 -21
  37. package/src/prompts/summarize-post.ts +0 -94
  38. package/src/resources/__tests__/index.test.ts +0 -50
  39. package/src/resources/__tests__/recent-posts-list.test.ts +0 -92
  40. package/src/resources/__tests__/recent-posts.test.ts +0 -270
  41. package/src/resources/index.ts +0 -33
  42. package/src/resources/recent-posts-list.ts +0 -22
  43. package/src/resources/recent-posts.ts +0 -45
  44. package/src/schemas/team-name-schema.ts +0 -19
  45. package/src/tools/__tests__/attachments.test.ts +0 -460
  46. package/src/tools/__tests__/categories.test.ts +0 -402
  47. package/src/tools/__tests__/comments.test.ts +0 -970
  48. package/src/tools/__tests__/helps.test.ts +0 -222
  49. package/src/tools/__tests__/index.test.ts +0 -48
  50. package/src/tools/__tests__/post-actions.test.ts +0 -445
  51. package/src/tools/__tests__/posts.test.ts +0 -917
  52. package/src/tools/__tests__/search.test.ts +0 -339
  53. package/src/tools/__tests__/teams.test.ts +0 -615
  54. package/src/tools/attachments.ts +0 -167
  55. package/src/tools/categories.ts +0 -153
  56. package/src/tools/comments.ts +0 -258
  57. package/src/tools/helps.ts +0 -50
  58. package/src/tools/index.ts +0 -351
  59. package/src/tools/post-actions.ts +0 -132
  60. package/src/tools/posts.ts +0 -179
  61. package/src/tools/search.ts +0 -98
  62. package/src/tools/teams.ts +0 -157
  63. package/src/transformers/__tests__/category-transformer.test.ts +0 -161
  64. package/src/transformers/__tests__/comment-transformer.test.ts +0 -129
  65. package/src/transformers/__tests__/post-name-normalizer.test.ts +0 -53
  66. package/src/transformers/__tests__/post-transformer.test.ts +0 -70
  67. package/src/transformers/__tests__/query-normalizer.test.ts +0 -98
  68. package/src/transformers/__tests__/team-name-normalizer.test.ts +0 -21
  69. package/src/transformers/category-transformer.ts +0 -36
  70. package/src/transformers/comment-transformer.ts +0 -34
  71. package/src/transformers/post-name-normalizer.ts +0 -30
  72. package/src/transformers/post-transformer.ts +0 -38
  73. package/src/transformers/query-normalizer.ts +0 -36
  74. package/src/transformers/team-name-normalizer.ts +0 -7
  75. package/tsconfig.build.json +0 -4
  76. package/tsconfig.json +0 -30
  77. package/tsdown.config.ts +0 -13
  78. package/vitest.config.ts +0 -24
@@ -1,53 +0,0 @@
1
- import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2
- import { initI18n, setLanguage, t } from "../index.js";
3
-
4
- describe("i18n", () => {
5
- // Helper to set language environment variable and initialize i18n
6
- const initI18nWithLang = async (envVar: string, value: string) => {
7
- vi.stubEnv(envVar, value);
8
- return await initI18n();
9
- };
10
-
11
- beforeEach(() => {
12
- vi.resetModules();
13
- vi.stubEnv("LC_ALL", undefined);
14
- vi.stubEnv("LC_MESSAGES", undefined);
15
- vi.stubEnv("LANG", undefined);
16
- vi.stubEnv("LANGUAGE", undefined);
17
- });
18
-
19
- afterEach(() => {
20
- vi.unstubAllEnvs();
21
- });
22
-
23
- it("should detect language from LC_ALL env var", async () => {
24
- const i18n = await initI18nWithLang("LC_ALL", "ja_JP.UTF-8");
25
- expect(i18n.language).toBe("ja");
26
- });
27
-
28
- it("should detect language from LC_MESSAGES env var", async () => {
29
- const i18n = await initI18nWithLang("LC_MESSAGES", "ja_JP.UTF-8");
30
- expect(i18n.language).toBe("ja");
31
- });
32
-
33
- it("should detect language from LANG env var", async () => {
34
- const i18n = await initI18nWithLang("LANG", "ja_JP.UTF-8");
35
- expect(i18n.language).toBe("ja");
36
- });
37
-
38
- it("should detect language from LANGUAGE env var", async () => {
39
- const i18n = await initI18nWithLang("LANGUAGE", "ja_JP.UTF-8");
40
- expect(i18n.language).toBe("ja");
41
- });
42
-
43
- it("should fallback to en when no env vars are set", async () => {
44
- const i18n = await initI18n();
45
- expect(i18n.language).toBe("en");
46
- });
47
-
48
- it("should change language dynamically with setLanguage", async () => {
49
- await initI18n();
50
- await setLanguage("ja");
51
- expect(t("prompts.summarize_post.title")).toContain("要約");
52
- });
53
- });
package/src/i18n/index.ts DELETED
@@ -1,39 +0,0 @@
1
- import i18next from "i18next";
2
- import enTranslations from "../locales/en.json" with { type: "json" };
3
- import jaTranslations from "../locales/ja.json" with { type: "json" };
4
-
5
- export async function initI18n() {
6
- // https://github.com/neet/i18next-cli-language-detector/blob/main/src/i18next-cli-language-detector.ts
7
- // を参考に簡易版の環境変数からの自動検出
8
- const lng =
9
- process.env.LC_ALL?.split(/[-_.]/)[0] ||
10
- process.env.LC_MESSAGES?.split(/[-_.]/)[0] ||
11
- process.env.LANG?.split(/[-_.]/)[0] ||
12
- process.env.LANGUAGE?.split(/[-_.]/)[0] ||
13
- "en";
14
- await i18next.init({
15
- lng,
16
- fallbackLng: "en",
17
- resources: {
18
- ja: {
19
- translation: jaTranslations,
20
- },
21
- en: {
22
- translation: enTranslations,
23
- },
24
- },
25
- interpolation: {
26
- escapeValue: false,
27
- },
28
- });
29
-
30
- return i18next;
31
- }
32
-
33
- export function setLanguage(lng: string) {
34
- return i18next.changeLanguage(lng);
35
- }
36
-
37
- export function t(key: string, options?: Record<string, unknown>) {
38
- return i18next.t(key, options);
39
- }
package/src/index.ts DELETED
@@ -1,47 +0,0 @@
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 { config, validateConfig } from "./config/index.js";
5
- import { initI18n } from "./i18n/index.js";
6
- import { setupPrompts } from "./prompts/index.js";
7
- import { setupResources } from "./resources/index.js";
8
- import { setupTools } from "./tools/index.js";
9
-
10
- try {
11
- validateConfig();
12
- } catch (error) {
13
- console.error("Configuration error:", error);
14
- process.exit(1);
15
- }
16
-
17
- async function main() {
18
- await initI18n();
19
-
20
- const server = new McpServer({
21
- name: config.server.name,
22
- version: config.server.version,
23
- });
24
-
25
- setupTools(server, config.esa);
26
- setupResources(server, config.esa);
27
- setupPrompts(server, config.esa);
28
-
29
- const transport = new StdioServerTransport();
30
-
31
- // Handle transport errors gracefully
32
- transport.onclose = () => {
33
- console.error("Transport closed");
34
- };
35
-
36
- transport.onerror = (error) => {
37
- console.error("Transport error:", error);
38
- };
39
-
40
- await server.connect(transport);
41
- console.error(`${config.server.name} v${config.server.version} started`);
42
- }
43
-
44
- await main().catch((error) => {
45
- console.error("Server startup error:", error);
46
- process.exit(1);
47
- });
@@ -1,13 +0,0 @@
1
- {
2
- "prompts": {
3
- "summarize_post": {
4
- "title": "Summarize esa post",
5
- "description": "Summarize an esa post in various formats (bullet points, paragraph, or keywords)",
6
- "args": {
7
- "team_name": "The name of the esa team",
8
- "post_number": "The post number to summarize",
9
- "format": "Summary format (bullet/paragraph/keywords)"
10
- }
11
- }
12
- }
13
- }
@@ -1,13 +0,0 @@
1
- {
2
- "prompts": {
3
- "summarize_post": {
4
- "title": "esaの記事の要約",
5
- "description": "esa記事を指定した形式で要約します(bullet: 箇条書き, paragraph: 文章, keywords: キーワード)",
6
- "args": {
7
- "team_name": "esaチーム名",
8
- "post_number": "要約する記事番号",
9
- "format": "要約形式 (bullet/paragraph/keywords)"
10
- }
11
- }
12
- }
13
- }
@@ -1,48 +0,0 @@
1
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
- import type { MockInstance } from "vitest";
3
- import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
4
- import type { MCPContext } from "../../context/mcp-context.js";
5
- import { setupPrompts } from "../index.js";
6
-
7
- describe("setupPrompts", () => {
8
- let server: McpServer;
9
- let context: MCPContext;
10
- let consoleErrorSpy: MockInstance<typeof console.error>;
11
-
12
- beforeEach(() => {
13
- server = new McpServer({
14
- name: "test-server",
15
- version: "1.0.0",
16
- });
17
- context = {} as unknown as MCPContext;
18
-
19
- consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
20
- vi.clearAllMocks();
21
- });
22
-
23
- afterEach(() => {
24
- consoleErrorSpy.mockRestore();
25
- });
26
-
27
- it("should register all prompts with correct handlers", () => {
28
- const registerPromptSpy = vi.spyOn(server, "registerPrompt");
29
-
30
- setupPrompts(server, context);
31
-
32
- expect(registerPromptSpy).toHaveBeenCalledTimes(1);
33
-
34
- for (let i = 0; i < 1; i++) {
35
- const [promptName, schema, handler] = registerPromptSpy.mock.calls[i];
36
- expect(typeof promptName).toBe("string");
37
- expect(promptName).toMatch(/^esa_/); // All prompts should start with 'esa_'
38
- expect(schema).toBeTypeOf("object"); // Schema verification handled by individual prompt tests
39
- expect(handler).toBeTypeOf("function"); // Handler functionality tested in specific prompt tests
40
- }
41
- });
42
-
43
- it("should log setup completion message", () => {
44
- setupPrompts(server, context);
45
-
46
- expect(consoleErrorSpy).toHaveBeenCalledWith("Setting up MCP prompts...");
47
- });
48
- });
@@ -1,291 +0,0 @@
1
- import { beforeEach, describe, expect, it, vi } from "vitest";
2
- import type { createEsaClient } from "../../api_client/index.js";
3
- import { createSummarizePostSchema, summarizePost } from "../summarize-post.js";
4
-
5
- describe("createSummarizePostSchema", () => {
6
- it("should create schema with proper fields", () => {
7
- const schema = createSummarizePostSchema();
8
- const shape = schema.shape;
9
-
10
- expect(shape.teamName).toBeDefined();
11
- expect(shape.postNumber).toBeDefined();
12
- expect(shape.format).toBeDefined();
13
- });
14
-
15
- it("should validate correct input", () => {
16
- const schema = createSummarizePostSchema();
17
- const result = schema.safeParse({
18
- teamName: "test-team",
19
- postNumber: "123",
20
- format: "bullet",
21
- });
22
-
23
- expect(result.success).toBe(true);
24
- });
25
-
26
- it("should allow optional format", () => {
27
- const schema = createSummarizePostSchema();
28
- const result = schema.safeParse({
29
- teamName: "test-team",
30
- postNumber: "123",
31
- });
32
-
33
- expect(result.success).toBe(true);
34
- });
35
- });
36
-
37
- describe("summarizePost", () => {
38
- const mockClient = {
39
- GET: vi.fn(),
40
- } as unknown as ReturnType<typeof createEsaClient> & {
41
- GET: ReturnType<typeof vi.fn>;
42
- };
43
-
44
- beforeEach(() => {
45
- vi.clearAllMocks();
46
- });
47
-
48
- const mockPost = {
49
- number: 123,
50
- name: "test-post.md",
51
- full_name: "dev/test-post.md #tag1 #tag2",
52
- wip: false,
53
- body_md: "# Test Post\n\nThis is a test post content.",
54
- body_html: "<h1>Test Post</h1><p>This is a test post content.</p>",
55
- created_at: "2024-01-01T00:00:00+09:00",
56
- updated_at: "2024-01-02T00:00:00+09:00",
57
- message: "Update test post",
58
- url: "https://test-team.esa.example.com/posts/123",
59
- category: "dev",
60
- tags: ["tag1", "tag2"],
61
- revision_number: 3,
62
- created_by: {
63
- name: "user1",
64
- screen_name: "user1",
65
- icon: "https://example.com/icon1.png",
66
- },
67
- updated_by: {
68
- name: "user2",
69
- screen_name: "user2",
70
- icon: "https://example.com/icon2.png",
71
- },
72
- kind: "stock",
73
- comments_count: 5,
74
- tasks_count: 3,
75
- done_tasks_count: 2,
76
- stargazers_count: 10,
77
- watchers_count: 8,
78
- star: true,
79
- watch: false,
80
- };
81
-
82
- it("should generate a bullet format summary prompt", async () => {
83
- mockClient.GET.mockResolvedValue({
84
- data: mockPost,
85
- error: undefined,
86
- response: {
87
- ok: true,
88
- status: 200,
89
- } as Response,
90
- });
91
-
92
- const result = await summarizePost(mockClient, {
93
- teamName: "test-team",
94
- postNumber: "123",
95
- });
96
-
97
- expect(mockClient.GET).toHaveBeenCalledWith(
98
- "/v1/teams/{team_name}/posts/{post_number}",
99
- {
100
- params: {
101
- path: { team_name: "test-team", post_number: 123 },
102
- },
103
- },
104
- );
105
-
106
- expect(result.messages).toHaveLength(1);
107
- expect(result.messages[0].role).toBe("user");
108
- expect(result.messages[0].content.type).toBe("text");
109
-
110
- const promptText = result.messages[0].content.text;
111
- expect(promptText).toContain("Please summarize the following post:");
112
- expect(promptText).toContain("Title: test-post.md");
113
- expect(promptText).toContain(
114
- "URL: https://test-team.esa.example.com/posts/123",
115
- );
116
- expect(promptText).toContain("Author: user1");
117
- expect(promptText).toContain("Category: dev");
118
- expect(promptText).toContain("Tags: tag1, tag2");
119
- expect(promptText).toContain("# Test Post");
120
- expect(promptText).toContain("Please provide a summary in bullet points");
121
- });
122
-
123
- it("should generate a paragraph format summary prompt", async () => {
124
- mockClient.GET.mockResolvedValue({
125
- data: mockPost,
126
- error: undefined,
127
- response: {
128
- ok: true,
129
- status: 200,
130
- } as Response,
131
- });
132
-
133
- const result = await summarizePost(mockClient, {
134
- teamName: "test-team",
135
- postNumber: "123",
136
- format: "paragraph",
137
- });
138
-
139
- const promptText = result.messages[0].content.text;
140
- expect(promptText).toContain("Please provide a summary in 2-3 paragraphs");
141
- });
142
-
143
- it("should generate a keywords format summary prompt", async () => {
144
- mockClient.GET.mockResolvedValue({
145
- data: mockPost,
146
- error: undefined,
147
- response: {
148
- ok: true,
149
- status: 200,
150
- } as Response,
151
- });
152
-
153
- const result = await summarizePost(mockClient, {
154
- teamName: "test-team",
155
- postNumber: "123",
156
- format: "keywords",
157
- });
158
-
159
- const promptText = result.messages[0].content.text;
160
- expect(promptText).toContain(
161
- "Please extract and list 10-15 important keywords",
162
- );
163
- });
164
-
165
- it("should handle post without category and tags", async () => {
166
- const postWithoutCategoryAndTags = {
167
- ...mockPost,
168
- category: null,
169
- tags: [],
170
- };
171
-
172
- mockClient.GET.mockResolvedValue({
173
- data: postWithoutCategoryAndTags,
174
- error: undefined,
175
- });
176
-
177
- const result = await summarizePost(mockClient, {
178
- teamName: "test-team",
179
- postNumber: "456",
180
- });
181
-
182
- const promptText = result.messages[0].content.text;
183
- expect(promptText).not.toContain("Category:");
184
- expect(promptText).not.toContain("Tags:");
185
- });
186
-
187
- it("should handle post with null body_md", async () => {
188
- const postWithNullBody = {
189
- ...mockPost,
190
- body_md: null,
191
- };
192
-
193
- mockClient.GET.mockResolvedValue({
194
- data: postWithNullBody,
195
- error: undefined,
196
- response: {
197
- ok: true,
198
- status: 200,
199
- } as Response,
200
- });
201
-
202
- const result = await summarizePost(mockClient, {
203
- teamName: "test-team",
204
- postNumber: "789",
205
- });
206
-
207
- const promptText = result.messages[0].content.text;
208
- expect(promptText).not.toContain("Content:");
209
- expect(promptText).toContain("Please provide a summary in bullet points");
210
- });
211
-
212
- it("should handle API errors", async () => {
213
- const mockError = { error: "not_found", message: "Post not found" };
214
-
215
- mockClient.GET.mockResolvedValue({
216
- data: undefined,
217
- error: mockError,
218
- response: {
219
- ok: false,
220
- status: 404,
221
- } as Response,
222
- });
223
-
224
- const result = await summarizePost(mockClient, {
225
- teamName: "test-team",
226
- postNumber: "999",
227
- });
228
-
229
- expect(result.messages).toHaveLength(1);
230
- expect(result.messages[0].role).toBe("user");
231
- expect(result.messages[0].content.text).toContain(
232
- JSON.stringify(mockError, null, 2),
233
- );
234
- });
235
-
236
- it("should handle network errors", async () => {
237
- const networkError = new Error("Network connection failed");
238
-
239
- mockClient.GET.mockRejectedValue(networkError);
240
-
241
- const result = await summarizePost(mockClient, {
242
- teamName: "test-team",
243
- postNumber: "123",
244
- });
245
-
246
- expect(result.messages).toHaveLength(1);
247
- expect(result.messages[0].role).toBe("user");
248
- expect(result.messages[0].content.text).toContain(
249
- "Network connection failed",
250
- );
251
- });
252
-
253
- it("should handle non-Error exceptions", async () => {
254
- mockClient.GET.mockRejectedValue("Unexpected error");
255
-
256
- const result = await summarizePost(mockClient, {
257
- teamName: "test-team",
258
- postNumber: "123",
259
- });
260
-
261
- expect(result.messages).toHaveLength(1);
262
- expect(result.messages[0].role).toBe("user");
263
- expect(result.messages[0].content.text).toContain("Unexpected error");
264
- });
265
-
266
- it("should handle invalid post number", async () => {
267
- const result = await summarizePost(mockClient, {
268
- teamName: "test-team",
269
- postNumber: "invalid",
270
- });
271
-
272
- expect(result.messages).toHaveLength(1);
273
- expect(result.messages[0].role).toBe("user");
274
- expect(result.messages[0].content.text).toContain(
275
- "Post number must be a positive integer",
276
- );
277
- });
278
-
279
- it("should throw MissingTeamNameError when teamName is empty", async () => {
280
- await expect(
281
- summarizePost(mockClient, {
282
- teamName: "",
283
- postNumber: "123",
284
- }),
285
- ).rejects.toThrow(
286
- "Missing required parameter 'teamName'. Use esa_get_teams to list available teams, then retry with teamName specified.",
287
- );
288
-
289
- expect(mockClient.GET).not.toHaveBeenCalled();
290
- });
291
- });
@@ -1,21 +0,0 @@
1
- import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
- import type { z } from "zod";
3
- import { withContext } from "../api_client/with-context.js";
4
- import type { MCPContext } from "../context/mcp-context.js";
5
- import { t } from "../i18n/index.js";
6
- import { createSummarizePostSchema, summarizePost } from "./summarize-post.js";
7
-
8
- export function setupPrompts(server: McpServer, context: MCPContext): void {
9
- console.error("Setting up MCP prompts...");
10
- // NOTE: Streamable HTTP transport では ユーザーの LANG 設定に応じて i18next の lang 設定を変える
11
- server.registerPrompt(
12
- "esa_summarize_post",
13
- {
14
- title: t("prompts.summarize_post.title"),
15
- description: t("prompts.summarize_post.description"),
16
- argsSchema: createSummarizePostSchema().shape,
17
- },
18
- async (params: z.infer<ReturnType<typeof createSummarizePostSchema>>) =>
19
- withContext(context, summarizePost, params),
20
- );
21
- }
@@ -1,94 +0,0 @@
1
- import { z } from "zod";
2
- import type { createEsaClient } from "../api_client/index.js";
3
- import { MissingTeamNameError } from "../errors/missing-team-name-error.js";
4
- import {
5
- formatPromptError,
6
- formatPromptResponse,
7
- } from "../formatters/mcp-response.js";
8
- import type { components } from "../generated/api-types.js";
9
- import { t } from "../i18n/index.js";
10
-
11
- // 多言語化対応のために 関数化してスキーマを遅延定義している
12
- export const createSummarizePostSchema = () =>
13
- z.object({
14
- teamName: z.string().describe(t("prompts.summarize_post.args.team_name")),
15
- postNumber: z
16
- .string()
17
- .describe(t("prompts.summarize_post.args.post_number")),
18
- format: z
19
- .enum(["bullet", "paragraph", "keywords"])
20
- .optional()
21
- .describe(t("prompts.summarize_post.args.format")),
22
- });
23
-
24
- export async function summarizePost(
25
- client: ReturnType<typeof createEsaClient>,
26
- args: z.infer<ReturnType<typeof createSummarizePostSchema>>,
27
- ) {
28
- const { teamName, postNumber: postNumberStr, format = "bullet" } = args;
29
-
30
- if (!teamName) {
31
- throw new MissingTeamNameError();
32
- }
33
- const postNumber = Number.parseInt(postNumberStr, 10);
34
-
35
- if (Number.isNaN(postNumber) || postNumber <= 0) {
36
- return formatPromptError("Post number must be a positive integer");
37
- }
38
-
39
- try {
40
- const { data, error, response } = await client.GET(
41
- "/v1/teams/{team_name}/posts/{post_number}",
42
- {
43
- params: {
44
- path: { team_name: teamName, post_number: postNumber },
45
- },
46
- },
47
- );
48
-
49
- if (error || !response.ok) {
50
- return formatPromptError(error || response.status);
51
- }
52
-
53
- const post: components["schemas"]["Post"] = data;
54
-
55
- let prompt = `Please summarize the following post:\n\n`;
56
- prompt += `Title: ${post.name}\n`;
57
- prompt += `URL: ${post.url}\n`;
58
- prompt += `Author: ${post.created_by.name}\n`;
59
- prompt += `Created: ${post.created_at}\n`;
60
- prompt += `Updated: ${post.updated_at}\n`;
61
-
62
- if (post.category) {
63
- prompt += `Category: ${post.category}\n`;
64
- }
65
-
66
- if (post.tags && post.tags.length > 0) {
67
- prompt += `Tags: ${post.tags.join(", ")}\n`;
68
- }
69
-
70
- prompt += "\n---\n\n";
71
-
72
- if (post.body_md) {
73
- prompt += `Content:\n${post.body_md}\n\n---\n\n`;
74
- }
75
-
76
- switch (format) {
77
- case "bullet":
78
- prompt +=
79
- "Please provide a summary in bullet points (3-5 main points).";
80
- break;
81
- case "paragraph":
82
- prompt += "Please provide a summary in 2-3 paragraphs.";
83
- break;
84
- case "keywords":
85
- prompt +=
86
- "Please extract and list 10-15 important keywords from this post.";
87
- break;
88
- }
89
-
90
- return formatPromptResponse(prompt);
91
- } catch (error) {
92
- return formatPromptError(error);
93
- }
94
- }
@@ -1,50 +0,0 @@
1
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
- import type { MockInstance } from "vitest";
3
- import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
4
- import type { MCPContext } from "../../context/mcp-context.js";
5
- import { setupResources } from "../index.js";
6
-
7
- describe("setupResources", () => {
8
- let server: McpServer;
9
- let context: MCPContext;
10
- let consoleErrorSpy: MockInstance<typeof console.error>;
11
-
12
- beforeEach(() => {
13
- server = new McpServer({
14
- name: "test-server",
15
- version: "1.0.0",
16
- });
17
- context = {} as unknown as MCPContext;
18
-
19
- consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
20
- vi.clearAllMocks();
21
- });
22
-
23
- afterEach(() => {
24
- consoleErrorSpy.mockRestore();
25
- });
26
-
27
- it("should register all resources with correct handlers", () => {
28
- const registerResourceSpy = vi.spyOn(server, "registerResource");
29
-
30
- setupResources(server, context);
31
-
32
- expect(registerResourceSpy).toHaveBeenCalledTimes(1);
33
-
34
- for (let i = 0; i < 1; i++) {
35
- const [resourceName, template, metadata, handler] =
36
- registerResourceSpy.mock.calls[i];
37
- expect(typeof resourceName).toBe("string");
38
- expect(resourceName).toMatch(/^esa_/); // All resources should start with 'esa_'
39
- expect(template).toBeTypeOf("object"); // Template verification handled by specific resource tests
40
- expect(metadata).toBeTypeOf("object"); // Metadata verification handled by specific resource tests
41
- expect(handler).toBeTypeOf("function"); // Handler functionality tested in specific resource tests
42
- }
43
- });
44
-
45
- it("should log setup completion message", () => {
46
- setupResources(server, context);
47
-
48
- expect(consoleErrorSpy).toHaveBeenCalledWith("Setting up MCP resources...");
49
- });
50
- });