http-client-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,84 @@
1
+ # ACW Repo Reader (HTTP Client MCP Server)
2
+
3
+ A Model Context Protocol (MCP) server that provides a set of tools to explore and read files from a repository. This is specifically designed to help AI agents understand and navigate large codebases.
4
+
5
+ ## Features
6
+
7
+ - **read_api_spec**: Read the content of any file within the configured repository.
8
+ - **list_directory**: List files and subdirectories.
9
+ - **search_files**: Search for files across the entire repository using keywords.
10
+
11
+ ---
12
+
13
+ ## Quick Start (No Install)
14
+
15
+ The easiest way to use this server is via `npx`. You can provide the target repository path as a command-line argument.
16
+
17
+ ### 1. Antigravity
18
+
19
+ Add the following to your `~/.gemini/antigravity/mcp_config.json`:
20
+
21
+ ```json
22
+ {
23
+ "mcpServers": {
24
+ "http-client-mcp-server": {
25
+ "command": "npx",
26
+ "args": ["-y", "http-client-mcp-server", "/absolute/path/to/your/target-repo"]
27
+ }
28
+ }
29
+ }
30
+ ```
31
+
32
+ ### 2. Claude Desktop
33
+
34
+ Add to your `claude_desktop_config.json`:
35
+
36
+ ```json
37
+ {
38
+ "mcpServers": {
39
+ "http-client-mcp-server": {
40
+ "command": "npx",
41
+ "args": ["-y", "http-client-mcp-server", "/absolute/path/to/your/target-repo"]
42
+ }
43
+ }
44
+ }
45
+ ```
46
+
47
+ ---
48
+
49
+ ## Manual Installation
50
+
51
+ If you prefer to install it locally:
52
+
53
+ ### 1. Clone & Build
54
+
55
+ ```bash
56
+ git clone https://github.com/noxhsxrk/http-client-mcp-server
57
+ cd http-client-mcp-server
58
+ npm install
59
+ npm run build
60
+ ```
61
+
62
+ ### 2. Configuration
63
+
64
+ You can point to the local `index.js` file:
65
+
66
+ ```json
67
+ {
68
+ "mcpServers": {
69
+ "http-client-mcp-server": {
70
+ "command": "node",
71
+ "args": ["/path/to/http-client-mcp-server/index.js", "/path/to/your/target-repo"]
72
+ }
73
+ }
74
+ }
75
+ ```
76
+
77
+ ---
78
+
79
+ ## Configuration Options
80
+
81
+ You can specify the repository path in two ways:
82
+
83
+ 1. Command-line Argument: `http-client-mcp-server /path/to/repo` (Recommended)
84
+ 2. Environment Variable: Set `REPO_BASE_PATH=/path/to/repo`
package/index.js ADDED
@@ -0,0 +1,198 @@
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, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
5
+ import fs from "node:fs/promises";
6
+ import path from "node:path";
7
+ const rawRepoPath = process.env.REPO_BASE_PATH || process.argv[2];
8
+ if (!rawRepoPath) {
9
+ const currentPath = process.cwd();
10
+ console.error("Error: REPO_BASE_PATH environment variable or a command-line argument is required.");
11
+ console.error(`\nHint: If you want to use the current directory, the absolute path is:\n${currentPath}`);
12
+ console.error(`\nUsage example:\nacw-repo-reader ${currentPath}`);
13
+ console.error("\nTips to find your repository path:");
14
+ console.error("- macOS: Right-click folder + hold 'Option' key -> Select 'Copy as Pathname'");
15
+ console.error("- Windows: Shift + Right-click folder -> Select 'Copy as path'");
16
+ process.exit(1);
17
+ }
18
+ const REPO_BASE_PATH = path.resolve(rawRepoPath);
19
+ const server = new Server({ name: "acw-repo-reader", version: "1.0.0" }, { capabilities: { tools: {} } });
20
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
21
+ return {
22
+ tools: [
23
+ {
24
+ name: "read_api_spec",
25
+ description: "Reads API specifications, sequence diagrams, or any files from repository.",
26
+ inputSchema: {
27
+ type: "object",
28
+ properties: {
29
+ filepath: {
30
+ type: "string",
31
+ description: "Relative path to the file inside the repo",
32
+ },
33
+ },
34
+ required: ["filepath"],
35
+ },
36
+ },
37
+ {
38
+ name: "list_directory",
39
+ description: "List files and folders in a specific directory inside repository. Use this to find the exact filename before reading.",
40
+ inputSchema: {
41
+ type: "object",
42
+ properties: {
43
+ dirPath: {
44
+ type: "string",
45
+ description: "Relative directory path to list (e.g., '.' for root, or 'docs')",
46
+ },
47
+ },
48
+ required: ["dirPath"],
49
+ },
50
+ },
51
+ {
52
+ name: "search_files",
53
+ description: "Search for files across the entire repository using a keyword (e.g., 'address', 'account'). Use this to find the exact filepath before reading.",
54
+ inputSchema: {
55
+ type: "object",
56
+ properties: {
57
+ keyword: {
58
+ type: "string",
59
+ description: "Keyword to search for in filenames (e.g., 'update-address' or '.http')",
60
+ },
61
+ },
62
+ required: ["keyword"],
63
+ },
64
+ },
65
+ ],
66
+ };
67
+ });
68
+ async function handleReadApiSpec(args) {
69
+ const filepath = typeof args?.filepath === "string" ? args.filepath : "";
70
+ if (!filepath) {
71
+ throw new Error("filepath is required and must be a string");
72
+ }
73
+ const fullPath = path.resolve(REPO_BASE_PATH, filepath);
74
+ if (!fullPath.startsWith(REPO_BASE_PATH)) {
75
+ console.error(`[Security Warning] Attempted path traversal: ${filepath}`);
76
+ throw new Error("Access denied: File is outside the repository base path.");
77
+ }
78
+ try {
79
+ const content = await fs.readFile(fullPath, "utf-8");
80
+ return {
81
+ content: [{ type: "text", text: content }],
82
+ };
83
+ }
84
+ catch (error) {
85
+ return {
86
+ content: [{ type: "text", text: `Error reading file: ${error.message}` }],
87
+ isError: true,
88
+ };
89
+ }
90
+ }
91
+ async function handleListDirectory(args) {
92
+ const dirPath = typeof args?.dirPath === "string" ? args.dirPath : ".";
93
+ const fullDirPath = path.resolve(REPO_BASE_PATH, dirPath);
94
+ if (!fullDirPath.startsWith(REPO_BASE_PATH)) {
95
+ throw new Error("Access denied: Path is outside the repository base path.");
96
+ }
97
+ try {
98
+ const files = await fs.readdir(fullDirPath, { withFileTypes: true });
99
+ const fileList = files
100
+ .map((dirent) => `${dirent.isDirectory() ? "[DIR]" : "[FILE]"} ${dirent.name}`)
101
+ .join("\n");
102
+ return {
103
+ content: [{ type: "text", text: `Contents of ${dirPath}:\n${fileList}` }],
104
+ };
105
+ }
106
+ catch (error) {
107
+ return {
108
+ content: [
109
+ { type: "text", text: `Error listing directory: ${error.message}` },
110
+ ],
111
+ isError: true,
112
+ };
113
+ }
114
+ }
115
+ async function findFilesRecursive(dir, keyword, basePath) {
116
+ let results = [];
117
+ const list = await fs.readdir(dir, { withFileTypes: true });
118
+ for (const file of list) {
119
+ if (file.isDirectory() &&
120
+ (file.name === "node_modules" ||
121
+ file.name === ".git" ||
122
+ file.name === ".idea" ||
123
+ file.name === ".vscode")) {
124
+ continue;
125
+ }
126
+ const fullPath = path.resolve(dir, file.name);
127
+ if (file.isDirectory()) {
128
+ const subResults = await findFilesRecursive(fullPath, keyword, basePath);
129
+ results = results.concat(subResults);
130
+ }
131
+ else if (file.name.toLowerCase().includes(keyword.toLowerCase())) {
132
+ results.push(path.relative(basePath, fullPath));
133
+ }
134
+ }
135
+ return results;
136
+ }
137
+ async function handleSearchFiles(args) {
138
+ const keyword = typeof args?.keyword === "string" ? args.keyword : "";
139
+ if (!keyword) {
140
+ throw new Error("keyword is required and must be a string");
141
+ }
142
+ try {
143
+ const matchedFiles = await findFilesRecursive(REPO_BASE_PATH, keyword, REPO_BASE_PATH);
144
+ if (matchedFiles.length === 0) {
145
+ return {
146
+ content: [
147
+ {
148
+ type: "text",
149
+ text: `No files found containing keyword: "${keyword}"`,
150
+ },
151
+ ],
152
+ };
153
+ }
154
+ return {
155
+ content: [
156
+ {
157
+ type: "text",
158
+ text: `Found ${matchedFiles.length} matching files:\n${matchedFiles.join("\n")}`,
159
+ },
160
+ ],
161
+ };
162
+ }
163
+ catch (error) {
164
+ return {
165
+ content: [
166
+ { type: "text", text: `Error searching files: ${error.message}` },
167
+ ],
168
+ isError: true,
169
+ };
170
+ }
171
+ }
172
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
173
+ const { name, arguments: args } = request.params;
174
+ switch (name) {
175
+ case "read_api_spec":
176
+ return await handleReadApiSpec(args);
177
+ case "list_directory":
178
+ return await handleListDirectory(args);
179
+ case "search_files":
180
+ return await handleSearchFiles(args);
181
+ default:
182
+ throw new Error("Tool not found");
183
+ }
184
+ });
185
+ async function run() {
186
+ const transport = new StdioServerTransport();
187
+ await server.connect(transport);
188
+ console.error(`MCP Server "acw-repo-reader" is running on stdio.`);
189
+ console.error(`Watching repository path: ${REPO_BASE_PATH}`);
190
+ }
191
+ try {
192
+ await run();
193
+ }
194
+ catch (error) {
195
+ console.error("Fatal error running server:", error);
196
+ process.exit(1);
197
+ }
198
+ //# sourceMappingURL=index.js.map
package/index.ts ADDED
@@ -0,0 +1,243 @@
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 {
5
+ CallToolRequestSchema,
6
+ ListToolsRequestSchema,
7
+ } from "@modelcontextprotocol/sdk/types.js";
8
+ import fs from "node:fs/promises";
9
+ import path from "node:path";
10
+
11
+ const rawRepoPath = process.env.REPO_BASE_PATH || process.argv[2];
12
+
13
+ if (!rawRepoPath) {
14
+ const currentPath = process.cwd();
15
+ console.error("Error: REPO_BASE_PATH environment variable or a command-line argument is required.");
16
+ console.error(`\nHint: If you want to use the current directory, the absolute path is:\n${currentPath}`);
17
+ console.error(`\nUsage example:\nacw-repo-reader ${currentPath}`);
18
+ console.error("\nTips to find your repository path:");
19
+ console.error("- macOS: Right-click folder + hold 'Option' key -> Select 'Copy as Pathname'");
20
+ console.error("- Windows: Shift + Right-click folder -> Select 'Copy as path'");
21
+ process.exit(1);
22
+ }
23
+
24
+ const REPO_BASE_PATH = path.resolve(rawRepoPath);
25
+
26
+ const server = new Server(
27
+ { name: "acw-repo-reader", version: "1.0.0" },
28
+ { capabilities: { tools: {} } },
29
+ );
30
+
31
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
32
+ return {
33
+ tools: [
34
+ {
35
+ name: "read_api_spec",
36
+ description:
37
+ "Reads API specifications, sequence diagrams, or any files from repository.",
38
+ inputSchema: {
39
+ type: "object",
40
+ properties: {
41
+ filepath: {
42
+ type: "string",
43
+ description: "Relative path to the file inside the repo",
44
+ },
45
+ },
46
+ required: ["filepath"],
47
+ },
48
+ },
49
+ {
50
+ name: "list_directory",
51
+ description:
52
+ "List files and folders in a specific directory inside repository. Use this to find the exact filename before reading.",
53
+ inputSchema: {
54
+ type: "object",
55
+ properties: {
56
+ dirPath: {
57
+ type: "string",
58
+ description:
59
+ "Relative directory path to list (e.g., '.' for root, or 'docs')",
60
+ },
61
+ },
62
+ required: ["dirPath"],
63
+ },
64
+ },
65
+ {
66
+ name: "search_files",
67
+ description:
68
+ "Search for files across the entire repository using a keyword (e.g., 'address', 'account'). Use this to find the exact filepath before reading.",
69
+ inputSchema: {
70
+ type: "object",
71
+ properties: {
72
+ keyword: {
73
+ type: "string",
74
+ description:
75
+ "Keyword to search for in filenames (e.g., 'update-address' or '.http')",
76
+ },
77
+ },
78
+ required: ["keyword"],
79
+ },
80
+ },
81
+ ],
82
+ };
83
+ });
84
+
85
+ async function handleReadApiSpec(args: any) {
86
+ const filepath = typeof args?.filepath === "string" ? args.filepath : "";
87
+
88
+ if (!filepath) {
89
+ throw new Error("filepath is required and must be a string");
90
+ }
91
+
92
+ const fullPath = path.resolve(REPO_BASE_PATH, filepath);
93
+
94
+ if (!fullPath.startsWith(REPO_BASE_PATH)) {
95
+ console.error(`[Security Warning] Attempted path traversal: ${filepath}`);
96
+ throw new Error("Access denied: File is outside the repository base path.");
97
+ }
98
+
99
+ try {
100
+ const content = await fs.readFile(fullPath, "utf-8");
101
+ return {
102
+ content: [{ type: "text", text: content }],
103
+ };
104
+ } catch (error: any) {
105
+ return {
106
+ content: [{ type: "text", text: `Error reading file: ${error.message}` }],
107
+ isError: true,
108
+ };
109
+ }
110
+ }
111
+
112
+ async function handleListDirectory(args: any) {
113
+ const dirPath = typeof args?.dirPath === "string" ? args.dirPath : ".";
114
+ const fullDirPath = path.resolve(REPO_BASE_PATH, dirPath);
115
+
116
+ if (!fullDirPath.startsWith(REPO_BASE_PATH)) {
117
+ throw new Error("Access denied: Path is outside the repository base path.");
118
+ }
119
+
120
+ try {
121
+ const files = await fs.readdir(fullDirPath, { withFileTypes: true });
122
+ const fileList = files
123
+ .map(
124
+ (dirent) =>
125
+ `${dirent.isDirectory() ? "[DIR]" : "[FILE]"} ${dirent.name}`,
126
+ )
127
+ .join("\n");
128
+
129
+ return {
130
+ content: [{ type: "text", text: `Contents of ${dirPath}:\n${fileList}` }],
131
+ };
132
+ } catch (error: any) {
133
+ return {
134
+ content: [
135
+ { type: "text", text: `Error listing directory: ${error.message}` },
136
+ ],
137
+ isError: true,
138
+ };
139
+ }
140
+ }
141
+
142
+ async function findFilesRecursive(
143
+ dir: string,
144
+ keyword: string,
145
+ basePath: string,
146
+ ): Promise<string[]> {
147
+ let results: string[] = [];
148
+ const list = await fs.readdir(dir, { withFileTypes: true });
149
+
150
+ for (const file of list) {
151
+ if (
152
+ file.isDirectory() &&
153
+ (file.name === "node_modules" ||
154
+ file.name === ".git" ||
155
+ file.name === ".idea" ||
156
+ file.name === ".vscode")
157
+ ) {
158
+ continue;
159
+ }
160
+
161
+ const fullPath = path.resolve(dir, file.name);
162
+
163
+ if (file.isDirectory()) {
164
+ const subResults = await findFilesRecursive(fullPath, keyword, basePath);
165
+ results = results.concat(subResults);
166
+ } else if (file.name.toLowerCase().includes(keyword.toLowerCase())) {
167
+ results.push(path.relative(basePath, fullPath));
168
+ }
169
+ }
170
+ return results;
171
+ }
172
+
173
+ async function handleSearchFiles(args: any) {
174
+ const keyword = typeof args?.keyword === "string" ? args.keyword : "";
175
+
176
+ if (!keyword) {
177
+ throw new Error("keyword is required and must be a string");
178
+ }
179
+
180
+ try {
181
+ const matchedFiles = await findFilesRecursive(
182
+ REPO_BASE_PATH,
183
+ keyword,
184
+ REPO_BASE_PATH,
185
+ );
186
+
187
+ if (matchedFiles.length === 0) {
188
+ return {
189
+ content: [
190
+ {
191
+ type: "text",
192
+ text: `No files found containing keyword: "${keyword}"`,
193
+ },
194
+ ],
195
+ };
196
+ }
197
+
198
+ return {
199
+ content: [
200
+ {
201
+ type: "text",
202
+ text: `Found ${matchedFiles.length} matching files:\n${matchedFiles.join("\n")}`,
203
+ },
204
+ ],
205
+ };
206
+ } catch (error: any) {
207
+ return {
208
+ content: [
209
+ { type: "text", text: `Error searching files: ${error.message}` },
210
+ ],
211
+ isError: true,
212
+ };
213
+ }
214
+ }
215
+
216
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
217
+ const { name, arguments: args } = request.params;
218
+
219
+ switch (name) {
220
+ case "read_api_spec":
221
+ return await handleReadApiSpec(args);
222
+ case "list_directory":
223
+ return await handleListDirectory(args);
224
+ case "search_files":
225
+ return await handleSearchFiles(args);
226
+ default:
227
+ throw new Error("Tool not found");
228
+ }
229
+ });
230
+
231
+ async function run() {
232
+ const transport = new StdioServerTransport();
233
+ await server.connect(transport);
234
+ console.error(`MCP Server "acw-repo-reader" is running on stdio.`);
235
+ console.error(`Watching repository path: ${REPO_BASE_PATH}`);
236
+ }
237
+
238
+ try {
239
+ await run();
240
+ } catch (error) {
241
+ console.error("Fatal error running server:", error);
242
+ process.exit(1);
243
+ }
package/package.json ADDED
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "http-client-mcp-server",
3
+ "version": "1.0.0",
4
+ "description": "MCP server to explore and read files from a repository",
5
+ "main": "index.js",
6
+ "bin": {
7
+ "http-client-mcp-server": "./index.js"
8
+ },
9
+ "scripts": {
10
+ "build": "tsc",
11
+ "prepare": "npm run build",
12
+ "test": "echo \"Error: no test specified\" && exit 1"
13
+ },
14
+ "keywords": [],
15
+ "author": "",
16
+ "license": "ISC",
17
+ "type": "module",
18
+ "dependencies": {
19
+ "@modelcontextprotocol/sdk": "^1.29.0"
20
+ },
21
+ "devDependencies": {
22
+ "@types/node": "^25.5.2",
23
+ "typescript": "^6.0.2"
24
+ }
25
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ // Visit https://aka.ms/tsconfig to read more about this file
3
+ "compilerOptions": {
4
+ // File Layout
5
+ // "rootDir": "./src",
6
+ // "outDir": "./dist",
7
+ // Environment Settings
8
+ // See also https://aka.ms/tsconfig/module
9
+ "module": "nodenext",
10
+ "target": "esnext",
11
+ "types": [
12
+ "node"
13
+ ],
14
+ // "lib": ["esnext"],
15
+ // "types": ["node"],
16
+ // and npm install -D @types/node
17
+ // Other Outputs
18
+ "sourceMap": true,
19
+ "declaration": true,
20
+ "declarationMap": true,
21
+ // Stricter Typechecking Options
22
+ "noUncheckedIndexedAccess": true,
23
+ "exactOptionalPropertyTypes": true,
24
+ // Style Options
25
+ // "noImplicitReturns": true,
26
+ // "noImplicitOverride": true,
27
+ // "noUnusedLocals": true,
28
+ // "noUnusedParameters": true,
29
+ // "noFallthroughCasesInSwitch": true,
30
+ // "noPropertyAccessFromIndexSignature": true,
31
+ // Recommended Options
32
+ "strict": true,
33
+ "jsx": "react-jsx",
34
+ "verbatimModuleSyntax": true,
35
+ "isolatedModules": true,
36
+ "noUncheckedSideEffectImports": true,
37
+ "moduleDetection": "force",
38
+ "skipLibCheck": true,
39
+ }
40
+ }