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 +84 -0
- package/index.js +198 -0
- package/index.ts +243 -0
- package/package.json +25 -0
- package/tsconfig.json +40 -0
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
|
+
}
|