fmea-api-mcp-server 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 ADDED
@@ -0,0 +1,87 @@
1
+ # API Docs MCP Server
2
+
3
+ This project is a Model Context Protocol (MCP) server that provides API specifications from the `endpoints` directory to AI assistants (Claude, Zed, etc.).
4
+
5
+ ## Installation & Execution (NPM)
6
+
7
+ This project runs in a Node.js environment.
8
+
9
+ 1. **Install Dependencies**
10
+ ```bash
11
+ cd mcp-server
12
+ npm install
13
+ ```
14
+
15
+ 2. **Build**
16
+ ```bash
17
+ npm run build
18
+ ```
19
+
20
+ 3. **Run**
21
+ ```bash
22
+ npm start
23
+ ```
24
+
25
+ ## Publishing
26
+
27
+ To publish the package to NPM, use the following command:
28
+
29
+ ```bash
30
+ cd mcp-server
31
+ npm publish --access public
32
+ ```
33
+
34
+ After publishing, you can run it from anywhere using `npx`:
35
+
36
+ ```bash
37
+ npx -y fmea-api-mcp-server
38
+ ```
39
+
40
+ ## Client Configuration (Load Configuration)
41
+
42
+ To use this server with clients like Claude Desktop or Zed, add the following to your configuration file.
43
+
44
+ ### Local Execution (Source-based)
45
+
46
+ ```json
47
+ {
48
+ "mcpServers": {
49
+ "api-docs": {
50
+ "command": "node",
51
+ "args": [
52
+ "/absolute/path/to/fmea-api-mcp/mcp-server/dist/index.js"
53
+ ],
54
+ "env": {
55
+ "ENDPOINTS_DIR": "/absolute/path/to/fmea-api-mcp/endpoints"
56
+ }
57
+ }
58
+ }
59
+ }
60
+ ```
61
+
62
+ ### NPX Execution (Published Package-based)
63
+
64
+ When the package is published to NPM:
65
+
66
+ ```json
67
+ {
68
+ "mcpServers": {
69
+ "api-docs": {
70
+ "command": "npx",
71
+ "args": [
72
+ "-y",
73
+ "fmea-api-mcp-server"
74
+ ],
75
+ "env": {
76
+ "ENDPOINTS_DIR": "/absolute/path/to/fmea-api-mcp/endpoints"
77
+ }
78
+ }
79
+ }
80
+ }
81
+ ```
82
+
83
+ - `ENDPOINTS_DIR`: The absolute path to the directory containing API specification files. The default is `../endpoints` (relative path from mcp-server), but it's recommended to set this explicitly when using npx.
84
+
85
+ ## Features
86
+ - **Resources**: Can read JSON files in the `endpoints` folder.
87
+ - **Tools**: Use the `search_apis` tool to search APIs by keyword.
package/dist/index.js ADDED
@@ -0,0 +1,191 @@
1
+ #!/usr/bin/env node
2
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { CallToolRequestSchema, ErrorCode, ListResourcesRequestSchema, ListToolsRequestSchema, McpError, ReadResourceRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
5
+ import * as fs from "fs/promises";
6
+ import * as fsSync from "fs";
7
+ import * as path from "path";
8
+ import { fileURLToPath } from "url";
9
+ const __filename = fileURLToPath(import.meta.url);
10
+ const __dirname = path.dirname(__filename);
11
+ // Directory where endpoint definitions are stored.
12
+ // Priority:
13
+ // 1. Environment variable ENDPOINTS_DIR
14
+ // 2. Packaged: dist/../endpoints (relative to bundled index.js)
15
+ // 3. Local Dev: dist/../../endpoints (relative to mcp-server/src)
16
+ const PACKAGED_ENDPOINTS_DIR = path.resolve(__dirname, "../endpoints");
17
+ const LOCAL_ENDPOINTS_DIR = path.resolve(__dirname, "../../endpoints");
18
+ const ENDPOINTS_DIR = process.env.ENDPOINTS_DIR ||
19
+ (fsSync.existsSync(PACKAGED_ENDPOINTS_DIR) ? PACKAGED_ENDPOINTS_DIR : LOCAL_ENDPOINTS_DIR);
20
+ class ApiDocsServer {
21
+ server;
22
+ constructor() {
23
+ this.server = new Server({
24
+ name: "api-docs-mcp",
25
+ version: "1.0.0",
26
+ }, {
27
+ capabilities: {
28
+ resources: {},
29
+ tools: {},
30
+ },
31
+ });
32
+ this.setupResourceHandlers();
33
+ this.setupToolHandlers();
34
+ // Error handling
35
+ this.server.onerror = (error) => console.error("[MCP Error]", error);
36
+ process.on("SIGINT", async () => {
37
+ await this.server.close();
38
+ process.exit(0);
39
+ });
40
+ }
41
+ async setupResourceHandlers() {
42
+ // List available resources (files in endpoints directory)
43
+ this.server.setRequestHandler(ListResourcesRequestSchema, async () => {
44
+ try {
45
+ const files = await this.getAllFiles(ENDPOINTS_DIR);
46
+ const resources = files.map((filePath) => {
47
+ const relativePath = path.relative(ENDPOINTS_DIR, filePath);
48
+ // URI scheme: endpoints://v1/system.json
49
+ const uri = `endpoints://${relativePath}`;
50
+ return {
51
+ uri,
52
+ name: relativePath,
53
+ mimeType: "application/json",
54
+ description: `API Documentation for ${relativePath}`
55
+ };
56
+ });
57
+ return {
58
+ resources,
59
+ };
60
+ }
61
+ catch (error) {
62
+ console.error("Error listing resources:", error);
63
+ return { resources: [] };
64
+ }
65
+ });
66
+ // Read specific resource
67
+ this.server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
68
+ const uri = request.params.uri;
69
+ if (!uri.startsWith("endpoints://")) {
70
+ throw new McpError(ErrorCode.InvalidRequest, "Unknown URI scheme");
71
+ }
72
+ const relativePath = uri.replace("endpoints://", "");
73
+ const finalPath = path.join(ENDPOINTS_DIR, relativePath);
74
+ // Security check: prevent directory traversal
75
+ if (!finalPath.startsWith(ENDPOINTS_DIR)) {
76
+ throw new McpError(ErrorCode.InvalidRequest, "Access denied");
77
+ }
78
+ try {
79
+ const content = await fs.readFile(finalPath, "utf-8");
80
+ return {
81
+ contents: [
82
+ {
83
+ uri,
84
+ mimeType: "application/json",
85
+ text: content,
86
+ },
87
+ ],
88
+ };
89
+ }
90
+ catch (error) {
91
+ throw new McpError(ErrorCode.InvalidRequest, `File not found: ${relativePath}`);
92
+ }
93
+ });
94
+ }
95
+ async setupToolHandlers() {
96
+ this.server.setRequestHandler(ListToolsRequestSchema, async () => {
97
+ return {
98
+ tools: [
99
+ {
100
+ name: "search_apis",
101
+ description: "Search for API endpoints by keyword across all documentation files",
102
+ inputSchema: {
103
+ type: "object",
104
+ properties: {
105
+ query: {
106
+ type: "string",
107
+ description: "Search query (e.g. 'user login', 'POST /api/v1')",
108
+ },
109
+ },
110
+ required: ["query"],
111
+ },
112
+ },
113
+ ],
114
+ };
115
+ });
116
+ this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
117
+ if (request.params.name === "search_apis") {
118
+ const query = String(request.params.arguments?.query).toLowerCase();
119
+ const results = await this.searchInFiles(query);
120
+ return {
121
+ content: [
122
+ {
123
+ type: "text",
124
+ text: JSON.stringify(results, null, 2),
125
+ },
126
+ ],
127
+ };
128
+ }
129
+ throw new McpError(ErrorCode.MethodNotFound, "Tool not found");
130
+ });
131
+ }
132
+ // Recursive file listing helper
133
+ async getAllFiles(dir) {
134
+ let results = [];
135
+ try {
136
+ const list = await fs.readdir(dir);
137
+ for (const file of list) {
138
+ const filePath = path.join(dir, file);
139
+ const stat = await fs.stat(filePath);
140
+ if (stat && stat.isDirectory()) {
141
+ results = results.concat(await this.getAllFiles(filePath));
142
+ }
143
+ else {
144
+ if (file.endsWith(".json")) {
145
+ results.push(filePath);
146
+ }
147
+ }
148
+ }
149
+ }
150
+ catch (err) {
151
+ // Directory might not exist or be empty
152
+ console.error(`Warning: Could not read directory ${dir}`);
153
+ }
154
+ return results;
155
+ }
156
+ // Simple search helper
157
+ async searchInFiles(query) {
158
+ const files = await this.getAllFiles(ENDPOINTS_DIR);
159
+ const matches = [];
160
+ for (const filePath of files) {
161
+ try {
162
+ const content = await fs.readFile(filePath, "utf-8");
163
+ const json = JSON.parse(content);
164
+ const fileName = path.relative(ENDPOINTS_DIR, filePath);
165
+ // Assuming structure has "endpoints" array
166
+ if (json.endpoints && Array.isArray(json.endpoints)) {
167
+ for (const endpoint of json.endpoints) {
168
+ const str = JSON.stringify(endpoint).toLowerCase();
169
+ if (str.includes(query)) {
170
+ matches.push({
171
+ source: fileName,
172
+ ...endpoint
173
+ });
174
+ }
175
+ }
176
+ }
177
+ }
178
+ catch (e) {
179
+ // Ignore parse errors
180
+ }
181
+ }
182
+ return matches;
183
+ }
184
+ async run() {
185
+ const transport = new StdioServerTransport();
186
+ await this.server.connect(transport);
187
+ console.error("MCP Server running on stdio");
188
+ }
189
+ }
190
+ const server = new ApiDocsServer();
191
+ server.run().catch(console.error);
@@ -0,0 +1,171 @@
1
+ {
2
+ "category": "Authentication",
3
+ "version": "v1",
4
+ "description": "인증/인가 관련 API",
5
+ "endpoints": [
6
+ {
7
+ "path": "/api/v1/auth/login",
8
+ "method": "POST",
9
+ "operationId": "login",
10
+ "summary": "User login",
11
+ "description": "Authenticate user with credentials",
12
+ "tags": [],
13
+ "parameters": [],
14
+ "requestBody": {
15
+ "content": {
16
+ "*/*": {
17
+ "schema": {
18
+ "$ref": "#/components/schemas/CredentialsDTO"
19
+ }
20
+ }
21
+ }
22
+ },
23
+ "responses": {
24
+ "default": {
25
+ "description": "default response",
26
+ "content": {
27
+ "*/*": {}
28
+ }
29
+ }
30
+ }
31
+ },
32
+ {
33
+ "path": "/api/v1/auth/logout",
34
+ "method": "POST",
35
+ "operationId": "logout",
36
+ "summary": "User logout",
37
+ "description": "Logout user with refresh token",
38
+ "tags": [],
39
+ "parameters": [],
40
+ "requestBody": {
41
+ "content": {
42
+ "application/json": {
43
+ "schema": {
44
+ "$ref": "#/components/schemas/TokenDTO"
45
+ }
46
+ }
47
+ }
48
+ },
49
+ "responses": {
50
+ "default": {
51
+ "description": "default response",
52
+ "content": {
53
+ "*/*": {}
54
+ }
55
+ }
56
+ }
57
+ },
58
+ {
59
+ "path": "/api/v1/auth/refresh",
60
+ "method": "POST",
61
+ "operationId": "refresh",
62
+ "summary": "Refresh access token",
63
+ "description": "Refresh authentication token using refresh token",
64
+ "tags": [],
65
+ "parameters": [],
66
+ "requestBody": {
67
+ "content": {
68
+ "*/*": {
69
+ "schema": {
70
+ "$ref": "#/components/schemas/TokenDTO"
71
+ }
72
+ }
73
+ }
74
+ },
75
+ "responses": {
76
+ "default": {
77
+ "description": "default response",
78
+ "content": {
79
+ "*/*": {}
80
+ }
81
+ }
82
+ }
83
+ },
84
+ {
85
+ "path": "/api/v1/auth/change-password",
86
+ "method": "POST",
87
+ "operationId": "changePassword",
88
+ "summary": "Change password",
89
+ "description": "Change user password",
90
+ "tags": [],
91
+ "parameters": [],
92
+ "requestBody": {
93
+ "content": {
94
+ "*/*": {
95
+ "schema": {
96
+ "$ref": "#/components/schemas/PasswordChangeDTO"
97
+ }
98
+ }
99
+ }
100
+ },
101
+ "responses": {
102
+ "default": {
103
+ "description": "default response",
104
+ "content": {
105
+ "*/*": {}
106
+ }
107
+ }
108
+ }
109
+ },
110
+ {
111
+ "path": "/api/v1/auth/password-reset-request",
112
+ "method": "POST",
113
+ "operationId": "passwordResetRequest",
114
+ "summary": "Password reset request",
115
+ "description": "Request password reset",
116
+ "tags": [],
117
+ "parameters": [],
118
+ "requestBody": {
119
+ "content": {
120
+ "*/*": {
121
+ "schema": {
122
+ "$ref": "#/components/schemas/PasswordResetDTO"
123
+ }
124
+ }
125
+ }
126
+ },
127
+ "responses": {
128
+ "default": {
129
+ "description": "default response",
130
+ "content": {
131
+ "*/*": {}
132
+ }
133
+ }
134
+ }
135
+ },
136
+ {
137
+ "path": "/api/v1/oauth/callback/{service}",
138
+ "method": "GET",
139
+ "operationId": "callback",
140
+ "summary": "OAuth callback",
141
+ "description": "OAuth authentication callback endpoint",
142
+ "tags": [],
143
+ "parameters": [
144
+ {
145
+ "name": "service",
146
+ "in": "path",
147
+ "required": true,
148
+ "schema": {
149
+ "type": "string"
150
+ }
151
+ },
152
+ {
153
+ "name": "code",
154
+ "in": "query",
155
+ "schema": {
156
+ "type": "string"
157
+ }
158
+ }
159
+ ],
160
+ "requestBody": null,
161
+ "responses": {
162
+ "default": {
163
+ "description": "default response",
164
+ "content": {
165
+ "*/*": {}
166
+ }
167
+ }
168
+ }
169
+ }
170
+ ]
171
+ }
@@ -0,0 +1,214 @@
1
+ {
2
+ "category": "Block Diagrams",
3
+ "version": "v1",
4
+ "description": "블록 다이어그램 관련 API",
5
+ "endpoints": [
6
+ {
7
+ "path": "/api/v1/projects/{projectId}/blockdiagrams",
8
+ "method": "GET",
9
+ "operationId": "list",
10
+ "summary": "List block diagrams",
11
+ "description": "Get all block diagrams for a project",
12
+ "tags": [],
13
+ "parameters": [
14
+ {
15
+ "name": "projectId",
16
+ "in": "path",
17
+ "required": true,
18
+ "schema": {
19
+ "type": "string"
20
+ }
21
+ }
22
+ ],
23
+ "requestBody": null,
24
+ "responses": {
25
+ "default": {
26
+ "description": "default response",
27
+ "content": {
28
+ "application/json": {}
29
+ }
30
+ }
31
+ }
32
+ },
33
+ {
34
+ "path": "/api/v1/projects/{projectId}/blockdiagrams/search",
35
+ "method": "GET",
36
+ "operationId": "search",
37
+ "summary": "Search block diagrams",
38
+ "description": "Search block diagrams by model, item, division, or block title",
39
+ "tags": [],
40
+ "parameters": [
41
+ {
42
+ "name": "projectId",
43
+ "in": "path",
44
+ "required": true,
45
+ "schema": {
46
+ "type": "string"
47
+ }
48
+ },
49
+ {
50
+ "name": "model",
51
+ "in": "query",
52
+ "schema": {
53
+ "type": "string"
54
+ }
55
+ },
56
+ {
57
+ "name": "item",
58
+ "in": "query",
59
+ "schema": {
60
+ "type": "string"
61
+ }
62
+ },
63
+ {
64
+ "name": "divisionId",
65
+ "in": "query",
66
+ "schema": {
67
+ "type": "integer",
68
+ "format": "int32"
69
+ }
70
+ },
71
+ {
72
+ "name": "blockTitle",
73
+ "in": "query",
74
+ "schema": {
75
+ "type": "string"
76
+ }
77
+ }
78
+ ],
79
+ "requestBody": null,
80
+ "responses": {
81
+ "default": {
82
+ "description": "default response",
83
+ "content": {
84
+ "application/json": {}
85
+ }
86
+ }
87
+ }
88
+ },
89
+ {
90
+ "path": "/api/v1/projects/{projectId}/blockdiagrams/{blockId}/nodes",
91
+ "method": "GET",
92
+ "operationId": "findNodesByIds",
93
+ "summary": "Find nodes by IDs",
94
+ "description": "Get nodes for a specific block diagram",
95
+ "tags": [],
96
+ "parameters": [
97
+ {
98
+ "name": "projectId",
99
+ "in": "path",
100
+ "required": true,
101
+ "schema": {
102
+ "type": "string"
103
+ }
104
+ },
105
+ {
106
+ "name": "blockId",
107
+ "in": "path",
108
+ "required": true,
109
+ "schema": {
110
+ "type": "string"
111
+ }
112
+ }
113
+ ],
114
+ "requestBody": null,
115
+ "responses": {
116
+ "default": {
117
+ "description": "default response",
118
+ "content": {
119
+ "application/json": {}
120
+ }
121
+ }
122
+ }
123
+ },
124
+ {
125
+ "path": "/api/v1/projects/{projectId}/blockdiagrams/{blockId}/nodes",
126
+ "method": "POST",
127
+ "operationId": "saveNodes",
128
+ "summary": "Save nodes",
129
+ "description": "Save nodes for a block diagram",
130
+ "tags": [],
131
+ "parameters": [
132
+ {
133
+ "name": "projectId",
134
+ "in": "path",
135
+ "required": true,
136
+ "schema": {
137
+ "type": "string"
138
+ }
139
+ },
140
+ {
141
+ "name": "blockId",
142
+ "in": "path",
143
+ "required": true,
144
+ "schema": {
145
+ "type": "string"
146
+ }
147
+ }
148
+ ],
149
+ "requestBody": {
150
+ "content": {
151
+ "application/json": {
152
+ "schema": {
153
+ "$ref": "#/components/schemas/BlockNodeSaveDTO"
154
+ }
155
+ }
156
+ }
157
+ },
158
+ "responses": {
159
+ "default": {
160
+ "description": "default response",
161
+ "content": {
162
+ "application/json": {}
163
+ }
164
+ }
165
+ }
166
+ },
167
+ {
168
+ "path": "/api/v1/projects/{projectId}/blockdiagrams/{blockId}/nodes/batch",
169
+ "method": "POST",
170
+ "operationId": "batchNodes",
171
+ "summary": "Batch create nodes",
172
+ "description": "Create multiple nodes in a batch operation",
173
+ "tags": [],
174
+ "parameters": [
175
+ {
176
+ "name": "projectId",
177
+ "in": "path",
178
+ "required": true,
179
+ "schema": {
180
+ "type": "string"
181
+ }
182
+ },
183
+ {
184
+ "name": "blockId",
185
+ "in": "path",
186
+ "required": true,
187
+ "schema": {
188
+ "type": "string"
189
+ }
190
+ }
191
+ ],
192
+ "requestBody": {
193
+ "content": {
194
+ "application/json": {
195
+ "schema": {
196
+ "type": "array",
197
+ "items": {
198
+ "$ref": "#/components/schemas/BlockNodeBatchDTO"
199
+ }
200
+ }
201
+ }
202
+ }
203
+ },
204
+ "responses": {
205
+ "default": {
206
+ "description": "default response",
207
+ "content": {
208
+ "application/json": {}
209
+ }
210
+ }
211
+ }
212
+ }
213
+ ]
214
+ }