@esaio/esa-mcp-server 0.2.1 → 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 +1 -1
  2. package/README.md +1 -1
  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
package/README.en.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
4
4
 
5
- [日本語](README.md) | **English**
5
+ [日本語](https://github.com/esaio/esa-mcp-server#readme) | **English**
6
6
 
7
7
  Official Model Context Protocol (MCP) server for esa.io - STDIO transport version.
8
8
 
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
4
4
 
5
- **日本語** | [English](README.en.md)
5
+ **日本語** | [English](https://github.com/esaio/esa-mcp-server/blob/main/README.en.md)
6
6
 
7
7
  esa.io の公式 MCP(Model Context Protocol)サーバー(STDIO Transport 版)
8
8
 
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import{McpServer as e,ResourceTemplate as t}from"@modelcontextprotocol/sdk/server/mcp.js";import{StdioServerTransport as n}from"@modelcontextprotocol/sdk/server/stdio.js";import r from"i18next";import i from"openapi-fetch";import{z as a}from"zod";var o=`0.1.0`;const s={esa:{apiAccessToken:process.env.ESA_ACCESS_TOKEN||``,apiBaseUrl:process.env.ESA_API_BASE_URL||`https://api.esa.io`},server:{name:`esa-mcp-server`,version:o,description:`Official MCP server for esa.io`}};function c(){if(!s.esa.apiAccessToken)throw Error(`ESA_ACCESS_TOKEN environment variable is required`)}var l={prompts:{summarize_post:{title:`Summarize esa post`,description:`Summarize an esa post in various formats (bullet points, paragraph, or keywords)`,args:{team_name:`The name of the esa team`,post_number:`The post number to summarize`,format:`Summary format (bullet/paragraph/keywords)`}}}},ee={prompts:{summarize_post:{title:`esaの記事の要約`,description:`esa記事を指定した形式で要約します(bullet: 箇条書き, paragraph: 文章, keywords: キーワード)`,args:{team_name:`esaチーム名`,post_number:`要約する記事番号`,format:`要約形式 (bullet/paragraph/keywords)`}}}};async function te(){let e=process.env.LC_ALL?.split(/[-_.]/)[0]||process.env.LC_MESSAGES?.split(/[-_.]/)[0]||process.env.LANG?.split(/[-_.]/)[0]||process.env.LANGUAGE?.split(/[-_.]/)[0]||`en`;return await r.init({lng:e,fallbackLng:`en`,resources:{ja:{translation:ee},en:{translation:l}},interpolation:{escapeValue:!1}}),r}function u(e,t){return r.t(e,t)}function d(e){return{async onRequest({request:t}){return t.headers.set(`Authorization`,`Bearer ${e}`),t},async onResponse({response:e}){let t=e.headers.get(`x-ratelimit-limit`),n=e.headers.get(`x-ratelimit-remaining`);return t&&n&&console.error(`Rate limit: ${n}/${t}`),e},async onError({error:e}){console.error(`Network Error:`,e)}}}const ne=o;function re(e){return{async onRequest({request:t}){return t.headers.set(`User-Agent`,`esa-mcp-server/${e} (official)`),t}}}function ie(e,t=`https://api.esa.io`){let n=i({baseUrl:t});return n.use(re(ne)),n.use(d(e)),n}async function f(e,t,...n){let r;if(`apiAccessToken`in e&&`apiBaseUrl`in e)r=ie(e.apiAccessToken,e.apiBaseUrl);else throw Error(`Unsupported context type. Only StdioContext is currently supported.`);return t(r,...n)}var p=class extends Error{constructor(){super(`Missing required parameter 'teamName'. Use esa_get_teams to list available teams, then retry with teamName specified.`),this.name=`MissingTeamNameError`}};function m(e){return e instanceof Error?`Error: ${e.message}`:typeof e==`number`&&e!==null?`Error: API Response(status: ${e})`:typeof e==`object`&&e?`Error: ${JSON.stringify(e,null,2)}`:`Error: ${String(e)}`}function h(e){return{content:[{type:`text`,text:JSON.stringify(e,null,2)}]}}function g(e,t){return{contents:[{uri:t,mimeType:`application/json`,text:JSON.stringify(e,null,2)}]}}function _(e){return{messages:[{role:`user`,content:{type:`text`,text:e}}]}}function v(e){return{content:[{type:`text`,text:m(e)}]}}function y(e,t){return{contents:[{uri:t,mimeType:`application/json`,text:m(e)}]}}function b(e){return{messages:[{role:`user`,content:{type:`text`,text:m(e)}}]}}const ae=()=>a.object({teamName:a.string().describe(u(`prompts.summarize_post.args.team_name`)),postNumber:a.string().describe(u(`prompts.summarize_post.args.post_number`)),format:a.enum([`bullet`,`paragraph`,`keywords`]).optional().describe(u(`prompts.summarize_post.args.format`))});async function oe(e,t){let{teamName:n,postNumber:r,format:i=`bullet`}=t;if(!n)throw new p;let a=Number.parseInt(r,10);if(Number.isNaN(a)||a<=0)return b(`Post number must be a positive integer`);try{let{data:t,error:r,response:o}=await e.GET(`/v1/teams/{team_name}/posts/{post_number}`,{params:{path:{team_name:n,post_number:a}}});if(r||!o.ok)return b(r||o.status);let s=t,c=`Please summarize the following post:
2
+ import{McpServer as e,ResourceTemplate as t}from"@modelcontextprotocol/sdk/server/mcp.js";import{StdioServerTransport as n}from"@modelcontextprotocol/sdk/server/stdio.js";import r from"i18next";import i from"openapi-fetch";import{z as a}from"zod";var o=`0.2.2`;const s={esa:{apiAccessToken:process.env.ESA_ACCESS_TOKEN||``,apiBaseUrl:process.env.ESA_API_BASE_URL||`https://api.esa.io`},server:{name:`esa-mcp-server`,version:o,description:`Official MCP server for esa.io`}};function c(){if(!s.esa.apiAccessToken)throw Error(`ESA_ACCESS_TOKEN environment variable is required`)}var l={prompts:{summarize_post:{title:`Summarize esa post`,description:`Summarize an esa post in various formats (bullet points, paragraph, or keywords)`,args:{team_name:`The name of the esa team`,post_number:`The post number to summarize`,format:`Summary format (bullet/paragraph/keywords)`}}}},ee={prompts:{summarize_post:{title:`esaの記事の要約`,description:`esa記事を指定した形式で要約します(bullet: 箇条書き, paragraph: 文章, keywords: キーワード)`,args:{team_name:`esaチーム名`,post_number:`要約する記事番号`,format:`要約形式 (bullet/paragraph/keywords)`}}}};async function te(){let e=process.env.LC_ALL?.split(/[-_.]/)[0]||process.env.LC_MESSAGES?.split(/[-_.]/)[0]||process.env.LANG?.split(/[-_.]/)[0]||process.env.LANGUAGE?.split(/[-_.]/)[0]||`en`;return await r.init({lng:e,fallbackLng:`en`,resources:{ja:{translation:ee},en:{translation:l}},interpolation:{escapeValue:!1}}),r}function u(e,t){return r.t(e,t)}function d(e){return{async onRequest({request:t}){return t.headers.set(`Authorization`,`Bearer ${e}`),t},async onResponse({response:e}){let t=e.headers.get(`x-ratelimit-limit`),n=e.headers.get(`x-ratelimit-remaining`);return t&&n&&console.error(`Rate limit: ${n}/${t}`),e},async onError({error:e}){console.error(`Network Error:`,e)}}}const ne=o;function re(e){return{async onRequest({request:t}){return t.headers.set(`User-Agent`,`esa-mcp-server/${e} (official)`),t}}}function ie(e,t=`https://api.esa.io`){let n=i({baseUrl:t});return n.use(re(ne)),n.use(d(e)),n}async function f(e,t,...n){let r;if(`apiAccessToken`in e&&`apiBaseUrl`in e)r=ie(e.apiAccessToken,e.apiBaseUrl);else throw Error(`Unsupported context type. Only StdioContext is currently supported.`);return t(r,...n)}var p=class extends Error{constructor(){super(`Missing required parameter 'teamName'. Use esa_get_teams to list available teams, then retry with teamName specified.`),this.name=`MissingTeamNameError`}};function m(e){return e instanceof Error?`Error: ${e.message}`:typeof e==`number`&&e!==null?`Error: API Response(status: ${e})`:typeof e==`object`&&e?`Error: ${JSON.stringify(e,null,2)}`:`Error: ${String(e)}`}function h(e){return{content:[{type:`text`,text:JSON.stringify(e,null,2)}]}}function g(e,t){return{contents:[{uri:t,mimeType:`application/json`,text:JSON.stringify(e,null,2)}]}}function _(e){return{messages:[{role:`user`,content:{type:`text`,text:e}}]}}function v(e){return{content:[{type:`text`,text:m(e)}]}}function y(e,t){return{contents:[{uri:t,mimeType:`application/json`,text:m(e)}]}}function b(e){return{messages:[{role:`user`,content:{type:`text`,text:m(e)}}]}}const ae=()=>a.object({teamName:a.string().describe(u(`prompts.summarize_post.args.team_name`)),postNumber:a.string().describe(u(`prompts.summarize_post.args.post_number`)),format:a.enum([`bullet`,`paragraph`,`keywords`]).optional().describe(u(`prompts.summarize_post.args.format`))});async function oe(e,t){let{teamName:n,postNumber:r,format:i=`bullet`}=t;if(!n)throw new p;let a=Number.parseInt(r,10);if(Number.isNaN(a)||a<=0)return b(`Post number must be a positive integer`);try{let{data:t,error:r,response:o}=await e.GET(`/v1/teams/{team_name}/posts/{post_number}`,{params:{path:{team_name:n,post_number:a}}});if(r||!o.ok)return b(r||o.status);let s=t,c=`Please summarize the following post:
3
3
 
4
4
  `;switch(c+=`Title: ${s.name}\n`,c+=`URL: ${s.url}\n`,c+=`Author: ${s.created_by.name}\n`,c+=`Created: ${s.created_at}\n`,c+=`Updated: ${s.updated_at}\n`,s.category&&(c+=`Category: ${s.category}\n`),s.tags&&s.tags.length>0&&(c+=`Tags: ${s.tags.join(`, `)}\n`),c+=`
5
5
  ---
package/package.json CHANGED
@@ -1,12 +1,30 @@
1
1
  {
2
2
  "name": "@esaio/esa-mcp-server",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "Official MCP server for esa.io - STDIO transport version",
5
5
  "main": "bin/index.js",
6
6
  "type": "module",
7
7
  "bin": {
8
8
  "esa-mcp-server": "./bin/index.js"
9
9
  },
10
+ "files": [
11
+ "bin",
12
+ "README.md",
13
+ "README.en.md",
14
+ "LICENSE"
15
+ ],
16
+ "publishConfig": {
17
+ "registry": "https://registry.npmjs.org/",
18
+ "access": "public"
19
+ },
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "git+https://github.com/esaio/esa-mcp-server.git"
23
+ },
24
+ "bugs": {
25
+ "url": "https://github.com/esaio/esa-mcp-server/issues"
26
+ },
27
+ "homepage": "https://github.com/esaio/esa-mcp-server#readme",
10
28
  "scripts": {
11
29
  "build": "tsdown",
12
30
  "test": "vitest",
@@ -15,7 +33,8 @@
15
33
  "lint": "biome check . --error-on-warnings",
16
34
  "lint:fix": "biome check --write .",
17
35
  "type-check": "tsc --noEmit",
18
- "update-esa-api": "openapi-typescript ../esa/api/openapi.yaml --output src/generated/api-types.ts && npm run lint:fix"
36
+ "update-esa-api": "openapi-typescript ../esa/api/openapi.yaml --output src/generated/api-types.ts && npm run lint:fix",
37
+ "prepublishOnly": "npm run build && npm run test:run && npm run lint && npm run type-check"
19
38
  },
20
39
  "keywords": [
21
40
  "mcp",
@@ -38,7 +57,7 @@
38
57
  "@types/node": "^20.19.15",
39
58
  "@vitest/coverage-v8": "^4.0.5",
40
59
  "openapi-typescript": "^7.9.1",
41
- "tsdown": "^0.15.1",
60
+ "tsdown": "^0.16.0",
42
61
  "tsx": "^4.20.5",
43
62
  "typescript": "^5.9.2",
44
63
  "vitest": "^4.0.5"
package/.dockerignore DELETED
@@ -1,36 +0,0 @@
1
- .git
2
- .github
3
-
4
- .env
5
- .env.*
6
- .envrc
7
-
8
- .vscode
9
- .idea
10
- *.swp
11
- *.swo
12
- *~
13
- .DS_Store
14
-
15
- *.log
16
- npm-debug.log*
17
-
18
- tmp
19
- temp
20
- .tmp
21
-
22
- node_modules
23
- .node-version
24
-
25
- README.md
26
- LICENSE
27
-
28
- coverage
29
-
30
- .claude
31
- CLAUDE.md
32
- Dockerfile
33
-
34
- bin
35
- build
36
- dist
@@ -1,23 +0,0 @@
1
- version: 2
2
- updates:
3
- - package-ecosystem: "github-actions"
4
- directory: "/"
5
- schedule:
6
- interval: daily
7
- - package-ecosystem: npm
8
- directory: "/"
9
- schedule:
10
- interval: daily
11
- time: "03:00"
12
- timezone: Asia/Tokyo
13
- open-pull-requests-limit: 10
14
- groups:
15
- vitest:
16
- patterns:
17
- - "vitest"
18
- - "@vitest/*"
19
- ignore:
20
- - dependency-name: "zod"
21
- update-types: ["version-update:semver-major"]
22
- - dependency-name: "@types/node"
23
- update-types: ["version-update:semver-major"]
@@ -1,120 +0,0 @@
1
- name: Docker Build and Push
2
-
3
- on:
4
- push:
5
- tags: [ 'v*' ]
6
-
7
- env:
8
- REGISTRY: ghcr.io
9
- IMAGE_NAME: ${{ github.repository }}
10
-
11
- jobs:
12
- build:
13
- strategy:
14
- fail-fast: true
15
- matrix:
16
- include:
17
- - platform: linux/amd64
18
- runner: ubuntu-latest
19
- - platform: linux/arm64
20
- runner: ubuntu-24.04-arm
21
- runs-on: ${{ matrix.runner }}
22
- permissions:
23
- contents: read
24
- packages: write
25
-
26
- steps:
27
- - name: Prepare
28
- run: |
29
- platform=${{ matrix.platform }}
30
- echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
31
-
32
- - name: Checkout repository
33
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # https://github.com/actions/checkout/tree/v5
34
-
35
- - name: Log in to Container Registry
36
- uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # https://github.com/docker/login-action/tree/v3
37
- with:
38
- registry: ${{ env.REGISTRY }}
39
- username: ${{ github.actor }}
40
- password: ${{ secrets.GITHUB_TOKEN }}
41
-
42
- - name: Extract metadata
43
- id: meta
44
- uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f # https://github.com/docker/metadata-action/tree/v5
45
- with:
46
- images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
47
-
48
- - name: Set up Docker Buildx
49
- uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # https://github.com/docker/setup-buildx-action/tree/v3
50
-
51
- - name: Build and push by digest
52
- id: build
53
- uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # https://github.com/docker/build-push-action/tree/v6
54
- with:
55
- context: .
56
- platforms: ${{ matrix.platform }}
57
- labels: ${{ steps.meta.outputs.labels }}
58
- outputs: type=image,name=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=true
59
- cache-from: type=gha
60
- cache-to: type=gha,mode=max
61
-
62
- - name: Export digest
63
- run: |
64
- mkdir -p /tmp/digests
65
- digest="${{ steps.build.outputs.digest }}"
66
- touch "/tmp/digests/${digest#sha256:}"
67
-
68
- - name: Upload digest
69
- uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # https://github.com/actions/upload-artifact/tree/v4
70
- with:
71
- name: digests-${{ env.PLATFORM_PAIR }}
72
- path: /tmp/digests/*
73
- if-no-files-found: error
74
- retention-days: 1
75
-
76
- merge:
77
- runs-on: ubuntu-latest
78
- needs:
79
- - build
80
- permissions:
81
- contents: read
82
- packages: write
83
-
84
- steps:
85
- - name: Download digests
86
- uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # https://github.com/actions/download-artifact/tree/v5
87
- with:
88
- path: /tmp/digests
89
- pattern: digests-*
90
- merge-multiple: true
91
-
92
- - name: Log in to Container Registry
93
- uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # https://github.com/docker/login-action/tree/v3
94
- with:
95
- registry: ${{ env.REGISTRY }}
96
- username: ${{ github.actor }}
97
- password: ${{ secrets.GITHUB_TOKEN }}
98
-
99
- - name: Set up Docker Buildx
100
- uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # https://github.com/docker/setup-buildx-action/tree/v3
101
-
102
- - name: Extract metadata
103
- id: meta
104
- uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f # https://github.com/docker/metadata-action/tree/v5
105
- with:
106
- images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
107
- tags: |
108
- type=semver,pattern={{version}}
109
- type=semver,pattern={{major}}.{{minor}}
110
- type=semver,pattern={{major}}
111
-
112
- - name: Create manifest list and push
113
- working-directory: /tmp/digests
114
- run: |
115
- docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
116
- $(printf '${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@sha256:%s ' *)
117
-
118
- - name: Inspect image
119
- run: |
120
- docker buildx imagetools inspect ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.version }}
@@ -1,41 +0,0 @@
1
- name: CI
2
- on:
3
- push:
4
- branches:
5
- - main
6
- pull_request:
7
- types: [opened, synchronize, reopened]
8
-
9
- env:
10
- NODE_VERSION: 20.19.4
11
-
12
- jobs:
13
- test:
14
- runs-on: ubuntu-latest
15
- steps:
16
- - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # https://github.com/actions/checkout/tree/v5
17
- - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # https://github.com/actions/setup-node/tree/v5
18
- with:
19
- node-version: ${{ env.NODE_VERSION }}
20
- cache: "npm"
21
- - name: Install dependencies
22
- run: npm ci
23
- - name: Run linting
24
- run: |
25
- output=$(npm run lint 2>&1)
26
- echo "$output"
27
- if echo "$output" | grep -q "schema version does not match"; then
28
- echo "::error::Biome schema version mismatch detected. Please run 'npx biome migrate --write' to update biome.json"
29
- exit 1
30
- fi
31
- - name: Run type checking
32
- run: npm run type-check
33
- - name: Run tests
34
- run: npm run test:coverage
35
- - name: Upload coverage reports
36
- uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # https://github.com/actions/upload-artifact/tree/v4
37
- with:
38
- name: coverage
39
- path: coverage/
40
- - name: Build
41
- run: npm run build
package/CLAUDE.md DELETED
@@ -1,94 +0,0 @@
1
- # CLAUDE.md
2
-
3
- This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
-
5
- ## Build and Development Commands
6
-
7
- ```bash
8
- # Install dependencies
9
- npm install
10
-
11
- # Build the project
12
- npm run build
13
-
14
- # Run tests
15
- npm test # Watch mode
16
- npm run test:run # Single run
17
- npm run test:coverage # With coverage report
18
-
19
- # Linting
20
- npm run lint # Check for linting issues
21
- npm run lint:fix # Auto-fix linting and formatting issues
22
-
23
- # Type checking
24
- npm run type-check # Check TypeScript types without building
25
- ```
26
-
27
- ## Architecture Overview
28
-
29
- This is an MCP (Model Context Protocol) server implementation for esa.io, using STDIO transport for communication. The architecture follows a simple structure:
30
-
31
- ### Core Components
32
-
33
- 1. **MCP Server Setup** (`src/index.ts`): Entry point that initializes the MCP server with STDIO transport. Handles transport lifecycle events and graceful error handling.
34
-
35
- 2. **Configuration** (`src/config/index.ts`): Centralized configuration management that reads from environment variables. Required env vars:
36
- - `ESA_ACCESS_TOKEN`: Required for esa.io API authentication
37
- - `ESA_API_BASE_URL`: Optional API base URL (defaults to https://api.esa.io)
38
-
39
- ### Key Technical Details
40
-
41
- - **Module System**: ES modules (type: "module" in package.json)
42
- - **TypeScript**: Strict mode enabled with comprehensive type checking
43
- - **Code Style**: Enforced by Biome (2 spaces, double quotes, semicolons, trailing commas)
44
- - **Node Version**: Requires Node.js >= 20.19.4
45
-
46
- ### MCP Server Pattern
47
-
48
- The server follows the standard MCP pattern:
49
- 1. Validate configuration on startup
50
- 2. Create McpServer instance with name and version
51
- 3. Initialize StdioServerTransport for STDIO communication
52
- 4. Handle transport lifecycle (onclose, onerror)
53
- 5. Connect server to transport
54
-
55
- When extending functionality, new tools and resources should be registered with the server instance before connecting to transport.
56
-
57
- ## Testing Guidelines
58
-
59
- ### Test Writing Principles
60
-
61
- When writing tests, follow these principles for maintainable and readable test code:
62
-
63
- 1. **Import Order Optimization**
64
- - Type imports first (`import type`)
65
- - External dependencies next
66
- - Internal modules last
67
- - Group related imports together
68
-
69
- 2. **Mock Creation Patterns**
70
- - Use `as unknown as` pattern for type casting to keep mocks minimal and focused
71
- - Create helper functions for complex mock objects (e.g., `createMockConfig`, `createMockServer`)
72
- - Extract repetitive mock setup into shared helper functions (e.g., `setupServerMocks`)
73
- - Return mock instances from helper functions for assertion access
74
-
75
- 3. **Mock Documentation**
76
- - Add concise comments explaining the purpose of each mock
77
- - Focus on WHY the mock is needed, not WHAT it does
78
- - Keep comments brief and directly above the mock definition
79
-
80
- 4. **Module Mocking with vi.doMock**
81
- - Use `vi.doMock` for replacing entire modules during import resolution
82
- - Always call `vi.doMock` before the module import
83
- - Use dynamic imports (`await import()`) after setting up module mocks
84
- - Remember that `vi.doMock` is fundamentally different from `vi.fn()` - it intercepts module loading
85
-
86
- 5. **Test Structure**
87
- - Keep each test focused on a single behavior
88
- - Use descriptive test names that explain the expected outcome
89
- - Group related tests with `describe` blocks
90
- - Clean up mocks in `beforeEach`/`afterEach` hooks
91
-
92
- ### Example Test Pattern
93
-
94
- - READ src/__tests__/index.test.ts
package/Dockerfile DELETED
@@ -1,34 +0,0 @@
1
- # syntax=docker/dockerfile:1
2
-
3
- FROM node:alpine AS base
4
-
5
- WORKDIR /app
6
-
7
- FROM base AS deps
8
-
9
- RUN --mount=type=bind,source=package.json,target=package.json \
10
- --mount=type=bind,source=package-lock.json,target=package-lock.json \
11
- --mount=type=cache,target=/root/.npm \
12
- npm ci --only=production
13
-
14
- FROM base AS build
15
-
16
- RUN --mount=type=bind,source=package.json,target=package.json \
17
- --mount=type=bind,source=package-lock.json,target=package-lock.json \
18
- --mount=type=cache,target=/root/.npm \
19
- npm ci
20
-
21
- COPY . .
22
-
23
- RUN npm run build
24
-
25
- FROM base AS production
26
-
27
- ENV NODE_ENV=production
28
-
29
- USER node
30
-
31
- COPY --from=deps /app/node_modules ./node_modules
32
- COPY --from=build /app/bin ./bin
33
-
34
- ENTRYPOINT ["node", "bin/index.js"]
package/biome.json DELETED
@@ -1,57 +0,0 @@
1
- {
2
- "$schema": "./node_modules/@biomejs/biome/configuration_schema.json",
3
- "assist": {
4
- "actions": {
5
- "source": {
6
- "organizeImports": "on"
7
- }
8
- }
9
- },
10
- "linter": {
11
- "enabled": true,
12
- "rules": {
13
- "recommended": true,
14
- "complexity": {
15
- "noForEach": "off"
16
- },
17
- "suspicious": {
18
- "noExplicitAny": "error",
19
- "noConsole": "off"
20
- },
21
- "style": {
22
- "useFilenamingConvention": {
23
- "level": "error",
24
- "options": {
25
- "filenameCases": ["kebab-case"]
26
- }
27
- }
28
- }
29
- }
30
- },
31
- "formatter": {
32
- "enabled": true,
33
- "formatWithErrors": false,
34
- "indentStyle": "space",
35
- "indentWidth": 2,
36
- "lineWidth": 80,
37
- "lineEnding": "lf"
38
- },
39
- "javascript": {
40
- "formatter": {
41
- "quoteStyle": "double",
42
- "semicolons": "always",
43
- "trailingCommas": "all"
44
- }
45
- },
46
- "files": {
47
- "includes": [
48
- "**",
49
- "!**/node_modules",
50
- "!**/bin",
51
- "!**/coverage",
52
- "!**/*.config.js",
53
- "!**/*.config.ts",
54
- "!.claude"
55
- ]
56
- }
57
- }
@@ -1,90 +0,0 @@
1
- import type { components } from "../../generated/api-types.js";
2
- import { transformComment } from "../../transformers/comment-transformer.js";
3
-
4
- export function createMockComment(
5
- overrides?: Partial<components["schemas"]["Comment"]>,
6
- ): components["schemas"]["Comment"] {
7
- return {
8
- id: 123,
9
- post_number: 456,
10
- body_md: "This is a test comment content",
11
- body_html: "<p>This is a test comment content</p>",
12
- created_at: "2024-01-01T00:00:00+09:00",
13
- updated_at: "2024-01-01T01:00:00+09:00",
14
- url: "https://test-team.esa.example.com/posts/456#comment-123",
15
- created_by: {
16
- name: "Test User",
17
- screen_name: "testuser",
18
- icon: "https://example.com/icon.png",
19
- myself: true,
20
- },
21
- stargazers_count: 5,
22
- star: true,
23
- stargazers: [
24
- {
25
- created_at: "2024-01-01T02:00:00+09:00",
26
- body: "Great comment!",
27
- user: {
28
- name: "Stargazer User",
29
- screen_name: "stargazer",
30
- icon: "https://example.com/star-icon.png",
31
- myself: false,
32
- },
33
- name: "Stargazer User",
34
- screen_name: "stargazer",
35
- icon: "https://example.com/star-icon.png",
36
- myself: false,
37
- email: "stargazer@example.com",
38
- role: "member" as const,
39
- posts_count: 10,
40
- joined_at: "2024-01-01T00:00:00+09:00",
41
- last_accessed_at: "2024-01-01T02:00:00+09:00",
42
- },
43
- ],
44
- ...overrides,
45
- };
46
- }
47
-
48
- export function createLongContentComment(
49
- length: number,
50
- ): components["schemas"]["Comment"] {
51
- const longContent = "a".repeat(length);
52
- return createMockComment({
53
- body_md: longContent,
54
- body_html: `<p>${longContent}</p>`,
55
- });
56
- }
57
-
58
- export function createNullBodyComment(
59
- overrides?: Partial<components["schemas"]["Comment"]>,
60
- ): components["schemas"]["Comment"] {
61
- return createMockComment({
62
- body_md: "",
63
- body_html: "",
64
- ...overrides,
65
- });
66
- }
67
-
68
- export function createExpectedTransformedComment(
69
- comment: components["schemas"]["Comment"],
70
- ) {
71
- return transformComment(comment);
72
- }
73
-
74
- export function createMockCommentList(
75
- overrides?: Partial<components["schemas"]["CommentList"]>,
76
- ): components["schemas"]["CommentList"] {
77
- return {
78
- comments: [
79
- createMockComment({ id: 1, post_number: 123, body_md: "First comment" }),
80
- createMockComment({ id: 2, post_number: 124, body_md: "Second comment" }),
81
- ],
82
- prev_page: null,
83
- next_page: 2,
84
- total_count: 10,
85
- page: 1,
86
- per_page: 20,
87
- max_per_page: 100,
88
- ...overrides,
89
- };
90
- }
@@ -1,79 +0,0 @@
1
- import type { components } from "../../generated/api-types.js";
2
- import {
3
- type PostTransformOptions,
4
- transformPost,
5
- } from "../../transformers/post-transformer.js";
6
-
7
- export const createMockPost = (
8
- overrides: Partial<components["schemas"]["Post"]> = {},
9
- ): components["schemas"]["Post"] => ({
10
- number: 123,
11
- name: "test-post.md",
12
- tags: ["tag1", "tag2"],
13
- category: "dev",
14
- full_name: "dev/test-post.md #tag1 #tag2",
15
- wip: false,
16
- body_md: "# Test Post\n\nThis is a test post content.",
17
- body_html: "<h1>Test Post</h1><p>This is a test post content.</p>",
18
- created_at: "2024-01-01T00:00:00+09:00",
19
- updated_at: "2024-01-02T00:00:00+09:00",
20
- message: "Update test post",
21
- url: "https://test-team.esa.example.com/posts/123",
22
- revision_number: 3,
23
- created_by: {
24
- name: "user1",
25
- screen_name: "user1",
26
- icon: "https://example.com/icon1.png",
27
- myself: false,
28
- },
29
- updated_by: {
30
- name: "user2",
31
- screen_name: "user2",
32
- icon: "https://example.com/icon2.png",
33
- myself: false,
34
- },
35
- kind: "stock",
36
- comments_count: 5,
37
- tasks_count: 3,
38
- done_tasks_count: 2,
39
- stargazers_count: 10,
40
- watchers_count: 8,
41
- star: true,
42
- watch: false,
43
- ...overrides,
44
- });
45
-
46
- export const createWipPost = (
47
- overrides: Partial<components["schemas"]["Post"]> = {},
48
- ): components["schemas"]["Post"] =>
49
- createMockPost({
50
- wip: true,
51
- number: 456,
52
- name: "wip-post.md",
53
- full_name: "docs/wip-post.md #wip",
54
- url: "https://test-team.esa.example.com/posts/456",
55
- kind: "flow",
56
- ...overrides,
57
- });
58
-
59
- export const createLongContentPost = (
60
- contentLength = 600,
61
- ): components["schemas"]["Post"] =>
62
- createMockPost({
63
- body_md: "a".repeat(contentLength),
64
- body_html: `<p>${"a".repeat(contentLength)}</p>`,
65
- });
66
-
67
- export const createNullBodyPost = (
68
- overrides: Partial<components["schemas"]["Post"]> = {},
69
- ): components["schemas"]["Post"] =>
70
- createMockPost({
71
- body_md: undefined,
72
- body_html: undefined,
73
- ...overrides,
74
- });
75
-
76
- export const createExpectedTransformed = (
77
- post: components["schemas"]["Post"],
78
- options: PostTransformOptions = {},
79
- ) => transformPost(post, options);