mcp-client-general 0.0.1 → 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/LICENSE +21 -0
- package/README.md +215 -0
- package/dist/execution/planner.js +31 -0
- package/dist/index.js +393 -0
- package/dist/jsonrpc.js +10 -0
- package/dist/profiles/index.js +10 -0
- package/dist/profiles/types.js +1 -0
- package/dist/profiles/web-dev.profile.js +24 -0
- package/dist/prompt.js +32 -0
- package/dist/runner.js +234 -0
- package/package.json +59 -4
- package/index.js +0 -1
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Antal Mátyás Nagy
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
# mcp-client-general
|
|
2
|
+
|
|
3
|
+
General, streaming-friendly MCP (Model Context Protocol) client for Node.js.
|
|
4
|
+
|
|
5
|
+
[](https://npmjs.com/package/mcp-client-general)
|
|
6
|
+

|
|
7
|
+

|
|
8
|
+

|
|
9
|
+
|
|
10
|
+
- Spawns an MCP-compatible server as a child process
|
|
11
|
+
- Speaks JSON-RPC 2.0 over stdin / stdout
|
|
12
|
+
- Ignores fragile `Content-Length` headers and uses a robust JSON object scanner
|
|
13
|
+
- Supports multiple requests per process (piped line-by-line)
|
|
14
|
+
- CLI + programmatic TypeScript API
|
|
15
|
+
|
|
16
|
+
> This package is designed to be a generic, open-source MCP client. It works with any MCP-compliant server implementation, including the reference mcp-server-general package.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Features
|
|
21
|
+
|
|
22
|
+
- **Zero-config profiles** – run built-in MCP stacks without manual wiring
|
|
23
|
+
- **Child process orchestration** – monitors stderr, exit, error
|
|
24
|
+
- **Handshake detection** – first valid JSON object = handshake
|
|
25
|
+
- **Framing-agnostic parsing** – safely ignores `Content-Length`
|
|
26
|
+
- **JSON-RPC 2.0 support** – id-based pending map, timeouts
|
|
27
|
+
- **CLI & Library** – usable from terminal or TypeScript
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Installation
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
npm install -g mcp-client-general
|
|
35
|
+
# or
|
|
36
|
+
npm install mcp-client-general --save-dev
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## CLI Usage
|
|
42
|
+
|
|
43
|
+
### Run an MCP server
|
|
44
|
+
```bash
|
|
45
|
+
mcp run "node dist/server.js"
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Send a single JSON-RPC request
|
|
49
|
+
```bash
|
|
50
|
+
echo '{"jsonrpc":"2.0","id":1,"method":"providers.list"}' \
|
|
51
|
+
| mcp run "node ../mcp-server-general/dist/server.js"
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Example output
|
|
55
|
+
```json
|
|
56
|
+
{
|
|
57
|
+
"jsonrpc": "2.0",
|
|
58
|
+
"id": 1,
|
|
59
|
+
"result": {
|
|
60
|
+
"providers": [
|
|
61
|
+
{
|
|
62
|
+
"provider": "openai",
|
|
63
|
+
"model": "gpt-4o-mini"
|
|
64
|
+
}
|
|
65
|
+
]
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
### Multiple requests in a single run
|
|
73
|
+
```bash
|
|
74
|
+
printf '%s\n%s\n' \
|
|
75
|
+
'{"jsonrpc":"2.0","id":1,"method":"providers.list"}' \
|
|
76
|
+
'{"jsonrpc":"2.0","id":2,"method":"steps.list"}' \
|
|
77
|
+
| mcp run "node ../mcp-server-general/dist/server.js"
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Example output
|
|
81
|
+
```json
|
|
82
|
+
{
|
|
83
|
+
"jsonrpc": "2.0",
|
|
84
|
+
"id": 1,
|
|
85
|
+
"result": { "providers": [ /* ... */ ] }
|
|
86
|
+
}
|
|
87
|
+
{
|
|
88
|
+
"jsonrpc": "2.0",
|
|
89
|
+
"id": 2,
|
|
90
|
+
"result": { "steps": [ /* ... */ ] }
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
### Error handling (JSON-RPC native)
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
echo '{"jsonrpc":"2.0","id":3,"method":"scoring.schema"}' \
|
|
100
|
+
| mcp run "node ../mcp-server-general/dist/server.js"
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
```json
|
|
104
|
+
{
|
|
105
|
+
"jsonrpc": "2.0",
|
|
106
|
+
"id": 3,
|
|
107
|
+
"error": {
|
|
108
|
+
"code": -32601,
|
|
109
|
+
"message": "Method not found: scoring.schema"
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## Programmatic Usage (TypeScript)
|
|
117
|
+
|
|
118
|
+
```ts
|
|
119
|
+
import { MCPProcess } from "mcp-client-general";
|
|
120
|
+
import type { JSONRPCRequest } from "mcp-client-general/jsonrpc";
|
|
121
|
+
|
|
122
|
+
async function main() {
|
|
123
|
+
const proc = new MCPProcess({
|
|
124
|
+
command: "node",
|
|
125
|
+
args: ["../mcp-server-general/dist/server.js"],
|
|
126
|
+
startupTimeoutMs: 4000,
|
|
127
|
+
shutdownTimeoutMs: 3000
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
proc.on("stderr", (msg) => process.stderr.write(String(msg)));
|
|
131
|
+
|
|
132
|
+
await proc.start();
|
|
133
|
+
|
|
134
|
+
const req: JSONRPCRequest = {
|
|
135
|
+
jsonrpc: "2.0",
|
|
136
|
+
id: 1,
|
|
137
|
+
method: "providers.list",
|
|
138
|
+
params: {}
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
const response = await proc.send(req);
|
|
142
|
+
console.log(JSON.stringify(response, null, 2));
|
|
143
|
+
|
|
144
|
+
await proc.close();
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
main().catch((err) => {
|
|
148
|
+
console.error(err);
|
|
149
|
+
process.exit(1);
|
|
150
|
+
});
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## Design Notes
|
|
156
|
+
|
|
157
|
+
### Handshake detection
|
|
158
|
+
The first valid JSON object received from stdout is treated as the handshake.
|
|
159
|
+
If the server delays or prints logs first, the client still proceeds safely.
|
|
160
|
+
|
|
161
|
+
### Framing strategy
|
|
162
|
+
Many servers emit:
|
|
163
|
+
|
|
164
|
+
```
|
|
165
|
+
Content-Length: 2888\r\n\r\n{ ... JSON ... }
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
But `Content-Length` is often wrong or mixed with logs.
|
|
169
|
+
|
|
170
|
+
This client instead:
|
|
171
|
+
|
|
172
|
+
- **ignores Content-Length**
|
|
173
|
+
- uses a **streaming JSON scanner**:
|
|
174
|
+
- finds `{`
|
|
175
|
+
- tracks nested `{` / `}`
|
|
176
|
+
- handles JSON strings & escapes
|
|
177
|
+
- extracts full JSON frames
|
|
178
|
+
|
|
179
|
+
Works even with imperfect/experimental MCP servers.
|
|
180
|
+
|
|
181
|
+
### Pending RPC requests
|
|
182
|
+
- Requests stored in `Map<id, PendingEntry>`
|
|
183
|
+
- Responses resolve Promises
|
|
184
|
+
- Timeouts reject automatically
|
|
185
|
+
|
|
186
|
+
---
|
|
187
|
+
|
|
188
|
+
## Environment Variables
|
|
189
|
+
|
|
190
|
+
Enable verbose debugging:
|
|
191
|
+
|
|
192
|
+
```bash
|
|
193
|
+
MCP_DEBUG=1 mcp run "node dist/server.js"
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Shows:
|
|
197
|
+
- handshake detection
|
|
198
|
+
- scan events
|
|
199
|
+
- JSON parse errors
|
|
200
|
+
- child process exits
|
|
201
|
+
- forwarded stderr
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
## Limitations
|
|
206
|
+
|
|
207
|
+
- Server must output valid JSON frames
|
|
208
|
+
- First JSON object is always treated as handshake
|
|
209
|
+
- JSON-like logs printed before handshake may be misinterpreted
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
## License
|
|
214
|
+
|
|
215
|
+
MIT – see LICENSE.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
function hasLocalBin(command) {
|
|
4
|
+
const binPath = path.join(process.cwd(), "node_modules", ".bin", command);
|
|
5
|
+
return fs.existsSync(binPath);
|
|
6
|
+
}
|
|
7
|
+
export async function planExecution(profile) {
|
|
8
|
+
const { command, autoInstall } = profile.server;
|
|
9
|
+
// 1) local node_modules/.bin
|
|
10
|
+
if (hasLocalBin(command)) {
|
|
11
|
+
return {
|
|
12
|
+
command,
|
|
13
|
+
args: [],
|
|
14
|
+
source: "local-bin",
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
// 2) npm exec (preferred auto-install path)
|
|
18
|
+
if (autoInstall) {
|
|
19
|
+
return {
|
|
20
|
+
command: "npm",
|
|
21
|
+
args: ["exec", command],
|
|
22
|
+
source: "npm-exec",
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
// 3) fallback npx
|
|
26
|
+
return {
|
|
27
|
+
command: "npx",
|
|
28
|
+
args: [command],
|
|
29
|
+
source: "npx",
|
|
30
|
+
};
|
|
31
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// src/index.ts
|
|
3
|
+
import { MCPProcess } from "./runner.js";
|
|
4
|
+
import { createRequest } from "./jsonrpc.js";
|
|
5
|
+
import { getProfile, listProfiles } from "./profiles/index.js";
|
|
6
|
+
import { planExecution } from "./execution/planner.js";
|
|
7
|
+
function printUsage() {
|
|
8
|
+
console.error(`
|
|
9
|
+
General MCP Client
|
|
10
|
+
------------------
|
|
11
|
+
|
|
12
|
+
Usage:
|
|
13
|
+
mcp run "<serverCommand>"
|
|
14
|
+
mcp run --profile web-dev
|
|
15
|
+
mcp run --profile <profile>
|
|
16
|
+
mcp list profiles
|
|
17
|
+
mcp list profiles --json
|
|
18
|
+
mcp describe profile web-dev
|
|
19
|
+
mcp describe profile <profile>
|
|
20
|
+
|
|
21
|
+
Examples:
|
|
22
|
+
# Run against any MCP-compliant server
|
|
23
|
+
echo '{"jsonrpc":"2.0","id":1,"method":"providers.list"}' |
|
|
24
|
+
mcp run "node dist/server.js"
|
|
25
|
+
|
|
26
|
+
# Run with a built-in profile
|
|
27
|
+
echo '{"jsonrpc":"2.0","id":1,"method":"providers.list"}' |
|
|
28
|
+
mcp run --profile web-dev
|
|
29
|
+
|
|
30
|
+
Environment Variables:
|
|
31
|
+
MCP_DEBUG=1 Enables verbose debug output (framing, handshake, events)
|
|
32
|
+
MCP_PROFILE_SERVER Override profile server command (advanced)
|
|
33
|
+
|
|
34
|
+
Debug example:
|
|
35
|
+
MCP_DEBUG=1 mcp run "node dist/server.js"
|
|
36
|
+
|
|
37
|
+
Compatible with:
|
|
38
|
+
- Any MCP-compliant server
|
|
39
|
+
- The General MCP Server (reference implementation)
|
|
40
|
+
|
|
41
|
+
More documentation:
|
|
42
|
+
https://dapo.run/mcp
|
|
43
|
+
`);
|
|
44
|
+
}
|
|
45
|
+
async function readStdin() {
|
|
46
|
+
return new Promise((resolve) => {
|
|
47
|
+
let data = "";
|
|
48
|
+
process.stdin.setEncoding("utf8");
|
|
49
|
+
process.stdin.on("data", (chunk) => {
|
|
50
|
+
data += chunk;
|
|
51
|
+
});
|
|
52
|
+
process.stdin.on("end", () => resolve(data));
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
async function main() {
|
|
56
|
+
const args = process.argv.slice(2);
|
|
57
|
+
if (args.length === 0 || args.includes("--help") || args.includes("-h")) {
|
|
58
|
+
printUsage();
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
let profileId;
|
|
62
|
+
// very simple flag parsing (no dependency)
|
|
63
|
+
for (let i = 0; i < args.length; i++) {
|
|
64
|
+
if (args[i] === "--profile") {
|
|
65
|
+
if (!args[i + 1]) {
|
|
66
|
+
console.error("--profile requires a value");
|
|
67
|
+
process.exitCode = 1;
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
profileId = args[i + 1];
|
|
71
|
+
args.splice(i, 2);
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
let dryRun = false;
|
|
76
|
+
let jsonOutput = false;
|
|
77
|
+
let explain = false;
|
|
78
|
+
for (let i = 0; i < args.length; i++) {
|
|
79
|
+
if (args[i] === "--dry-run") {
|
|
80
|
+
dryRun = true;
|
|
81
|
+
args.splice(i, 1);
|
|
82
|
+
i--;
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
if (args[i] === "--json") {
|
|
86
|
+
jsonOutput = true;
|
|
87
|
+
args.splice(i, 1);
|
|
88
|
+
i--;
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
if (args[i] === "--explain") {
|
|
92
|
+
explain = true;
|
|
93
|
+
args.splice(i, 1);
|
|
94
|
+
i--;
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
const explainLines = [];
|
|
99
|
+
// ---------------------------------------
|
|
100
|
+
// COMMAND: list profiles
|
|
101
|
+
// ---------------------------------------
|
|
102
|
+
if (args[0] === "list") {
|
|
103
|
+
if (args[1] === "profiles") {
|
|
104
|
+
const profiles = listProfiles();
|
|
105
|
+
if (jsonOutput) {
|
|
106
|
+
console.log(JSON.stringify(profiles, null, 2));
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
if (profiles.length === 0) {
|
|
110
|
+
console.log("No profiles available.");
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
console.log("Available profiles:\n");
|
|
114
|
+
for (const p of profiles) {
|
|
115
|
+
console.log(` ${p.id.padEnd(8)} ${p.description}`);
|
|
116
|
+
}
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
console.error(`Unknown list target: ${args[1] ?? ""}
|
|
120
|
+
|
|
121
|
+
Usage:
|
|
122
|
+
mcp list profiles
|
|
123
|
+
mcp list profiles --json
|
|
124
|
+
`);
|
|
125
|
+
process.exitCode = 1;
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
// ---------------------------------------
|
|
129
|
+
// COMMAND: describe profile
|
|
130
|
+
// ---------------------------------------
|
|
131
|
+
if (args[0] === "describe") {
|
|
132
|
+
if (args[1] === "profile") {
|
|
133
|
+
const profileId = args[2];
|
|
134
|
+
if (!profileId) {
|
|
135
|
+
console.error(`Missing profile id.
|
|
136
|
+
|
|
137
|
+
Usage:
|
|
138
|
+
mcp describe profile <profile>
|
|
139
|
+
`);
|
|
140
|
+
process.exitCode = 1;
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
const profile = getProfile(profileId);
|
|
144
|
+
if (!profile) {
|
|
145
|
+
console.error(`Unknown profile: ${profileId}`);
|
|
146
|
+
process.exitCode = 1;
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
if (jsonOutput) {
|
|
150
|
+
console.log(JSON.stringify(profile, null, 2));
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
console.log(`Profile: ${profile.id}\n`);
|
|
154
|
+
console.log("Description:");
|
|
155
|
+
console.log(` ${profile.description}\n`);
|
|
156
|
+
console.log("Server:");
|
|
157
|
+
console.log(` command: ${profile.server.command}`);
|
|
158
|
+
console.log(` kind: ${profile.server.kind}`);
|
|
159
|
+
if (profile.server.autoInstall) {
|
|
160
|
+
console.log(` auto-install: yes`);
|
|
161
|
+
}
|
|
162
|
+
if (profile.server.package) {
|
|
163
|
+
console.log(` package: ${profile.server.package}`);
|
|
164
|
+
}
|
|
165
|
+
console.log("");
|
|
166
|
+
if (profile.ui?.enabled) {
|
|
167
|
+
console.log("UI:");
|
|
168
|
+
console.log(" enabled: yes");
|
|
169
|
+
if (profile.ui.hint) {
|
|
170
|
+
console.log(` hint: ${profile.ui.hint}`);
|
|
171
|
+
}
|
|
172
|
+
console.log("");
|
|
173
|
+
}
|
|
174
|
+
if (profile.plugins.length > 0) {
|
|
175
|
+
console.log("Plugins:");
|
|
176
|
+
for (const p of profile.plugins) {
|
|
177
|
+
console.log(` - ${p.name} (${p.entry})`);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
console.log("Plugins:");
|
|
182
|
+
console.log(" (none)");
|
|
183
|
+
}
|
|
184
|
+
if (profile.notes && profile.notes.length > 0) {
|
|
185
|
+
console.log("\nNotes:");
|
|
186
|
+
for (const note of profile.notes) {
|
|
187
|
+
console.log(` - ${note}`);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
console.error(`Unknown describe target: ${args[1] ?? ""}
|
|
193
|
+
|
|
194
|
+
Usage:
|
|
195
|
+
mcp describe profile <profile>
|
|
196
|
+
`);
|
|
197
|
+
process.exitCode = 1;
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
if (args[0] !== "run") {
|
|
201
|
+
console.error(`Unknown command: ${args[0]}
|
|
202
|
+
|
|
203
|
+
Usage:
|
|
204
|
+
mcp run "node dist/server.js"
|
|
205
|
+
mcp run --profile web-dev
|
|
206
|
+
mcp run --profile <profile>
|
|
207
|
+
mcp list profiles
|
|
208
|
+
|
|
209
|
+
More documentation:
|
|
210
|
+
https://dapo.run/mcp
|
|
211
|
+
`);
|
|
212
|
+
process.exitCode = 1;
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
let execution;
|
|
216
|
+
// 1) If a profile was provided, ALWAYS prefer profile execution.
|
|
217
|
+
if (profileId) {
|
|
218
|
+
const profile = getProfile(profileId);
|
|
219
|
+
explainLines.push(`Mode: profile (${profileId})`);
|
|
220
|
+
if (!profile) {
|
|
221
|
+
console.error(`Unknown profile: ${profileId}`);
|
|
222
|
+
process.exitCode = 1;
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
execution = {
|
|
226
|
+
kind: "profile",
|
|
227
|
+
profile,
|
|
228
|
+
command: process.env.MCP_PROFILE_SERVER ?? profile.server.command,
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
else {
|
|
232
|
+
explainLines.push(`Mode: explicit`);
|
|
233
|
+
// 2) Otherwise try to treat the first non-flag token after "run" as explicit command.
|
|
234
|
+
const maybeCmd = args
|
|
235
|
+
.slice(1) // tokens after "run"
|
|
236
|
+
.find((t) => !t.startsWith("-"));
|
|
237
|
+
if (maybeCmd) {
|
|
238
|
+
execution = {
|
|
239
|
+
kind: "explicit",
|
|
240
|
+
command: maybeCmd,
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
else {
|
|
244
|
+
console.error(`Missing server command.
|
|
245
|
+
|
|
246
|
+
Examples:
|
|
247
|
+
mcp run "node dist/server.js"
|
|
248
|
+
mcp run --profile web-dev
|
|
249
|
+
mcp run --profile <profile>
|
|
250
|
+
|
|
251
|
+
More documentation:
|
|
252
|
+
https://dapo.run/mcp
|
|
253
|
+
`);
|
|
254
|
+
process.exitCode = 1;
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
const hasProfileServerOverride = execution.kind === "profile" &&
|
|
259
|
+
process.env.MCP_PROFILE_SERVER !== undefined;
|
|
260
|
+
let command;
|
|
261
|
+
let cmdArgs;
|
|
262
|
+
let executionSource;
|
|
263
|
+
if (execution.kind === "profile" &&
|
|
264
|
+
execution.profile.server.kind === "builtin" &&
|
|
265
|
+
!hasProfileServerOverride) {
|
|
266
|
+
explainLines.push("Server kind: builtin");
|
|
267
|
+
explainLines.push("Execution planner: enabled");
|
|
268
|
+
const plan = await planExecution(execution.profile);
|
|
269
|
+
if (plan.source === "local-bin") {
|
|
270
|
+
explainLines.push("Local binary: found");
|
|
271
|
+
}
|
|
272
|
+
else {
|
|
273
|
+
explainLines.push("Local binary: not found");
|
|
274
|
+
explainLines.push("Auto-install: enabled");
|
|
275
|
+
}
|
|
276
|
+
explainLines.push(`Selected execution: ${plan.source} (${[plan.command, ...plan.args].join(" ")})`);
|
|
277
|
+
command = plan.command;
|
|
278
|
+
cmdArgs = plan.args;
|
|
279
|
+
executionSource = plan.source;
|
|
280
|
+
if (process.env.MCP_DEBUG) {
|
|
281
|
+
process.stderr.write(`[CLIENT] Using ${plan.source} execution\n`);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
else {
|
|
285
|
+
explainLines.push("Execution planner: skipped");
|
|
286
|
+
explainLines.push(`Selected execution: explicit (${execution.command})`);
|
|
287
|
+
const cmd = execution.command.split(" ");
|
|
288
|
+
command = cmd[0];
|
|
289
|
+
cmdArgs = cmd.slice(1);
|
|
290
|
+
executionSource = "explicit";
|
|
291
|
+
}
|
|
292
|
+
if (dryRun) {
|
|
293
|
+
if (jsonOutput) {
|
|
294
|
+
console.log(JSON.stringify({
|
|
295
|
+
mode: execution.kind,
|
|
296
|
+
profile: execution.kind === "profile"
|
|
297
|
+
? execution.profile.id
|
|
298
|
+
: undefined,
|
|
299
|
+
explain: explain ? explainLines : undefined,
|
|
300
|
+
plan: {
|
|
301
|
+
command,
|
|
302
|
+
args: cmdArgs,
|
|
303
|
+
source: executionSource,
|
|
304
|
+
},
|
|
305
|
+
}, null, 2));
|
|
306
|
+
}
|
|
307
|
+
else {
|
|
308
|
+
if (explain && !jsonOutput) {
|
|
309
|
+
console.log("Execution explanation:");
|
|
310
|
+
for (const line of explainLines) {
|
|
311
|
+
console.log(`- ${line}`);
|
|
312
|
+
}
|
|
313
|
+
console.log("");
|
|
314
|
+
}
|
|
315
|
+
console.log("Execution plan:");
|
|
316
|
+
console.log(` resolver: ${executionSource}`);
|
|
317
|
+
console.log(` command: ${[command, ...cmdArgs].join(" ")}`);
|
|
318
|
+
}
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
const proc = new MCPProcess({
|
|
322
|
+
command,
|
|
323
|
+
args: cmdArgs
|
|
324
|
+
});
|
|
325
|
+
// optional debug logs
|
|
326
|
+
proc.on("stderr", (msg) => process.stderr.write(String(msg)));
|
|
327
|
+
if (process.env.MCP_DEBUG) {
|
|
328
|
+
proc.on("exit", ({ code, signal }) => {
|
|
329
|
+
process.stderr.write(`[CLIENT] Child process exit code=${code} signal=${String(signal)}\n`);
|
|
330
|
+
});
|
|
331
|
+
proc.on("error", (err) => {
|
|
332
|
+
process.stderr.write(`[CLIENT] Child process error: ${String(err)}\n`);
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
// staring server + handshake
|
|
336
|
+
await proc.start();
|
|
337
|
+
if (process.stdin.isTTY) {
|
|
338
|
+
if (process.env.MCP_DEBUG) {
|
|
339
|
+
process.stderr.write("[CLIENT] No stdin detected (TTY). Server started successfully.\n");
|
|
340
|
+
}
|
|
341
|
+
await proc.close();
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
const stdinData = await readStdin();
|
|
345
|
+
if (!stdinData.trim()) {
|
|
346
|
+
if (process.env.MCP_DEBUG) {
|
|
347
|
+
process.stderr.write("[CLIENT] No stdin data, nothing to send.\n");
|
|
348
|
+
}
|
|
349
|
+
await proc.close();
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
let payloads;
|
|
353
|
+
// 1) trying by whole JSON
|
|
354
|
+
try {
|
|
355
|
+
const parsed = JSON.parse(stdinData);
|
|
356
|
+
payloads = Array.isArray(parsed) ? parsed : [parsed];
|
|
357
|
+
}
|
|
358
|
+
catch {
|
|
359
|
+
// 2) trying by lines
|
|
360
|
+
const lines = stdinData
|
|
361
|
+
.split("\n")
|
|
362
|
+
.map((x) => x.trim())
|
|
363
|
+
.filter((x) => x.length > 0);
|
|
364
|
+
payloads = lines.map((line) => JSON.parse(line));
|
|
365
|
+
}
|
|
366
|
+
try {
|
|
367
|
+
for (const payload of payloads) {
|
|
368
|
+
const base = payload;
|
|
369
|
+
const isFullJsonRpc = base.id !== undefined &&
|
|
370
|
+
typeof base.jsonrpc === "string" &&
|
|
371
|
+
base.jsonrpc.length > 0;
|
|
372
|
+
const req = isFullJsonRpc
|
|
373
|
+
? base // JSONRPCRequest match
|
|
374
|
+
: createRequest(base.method ?? "", base.params);
|
|
375
|
+
const response = await proc.send(req);
|
|
376
|
+
process.stdout.write(JSON.stringify(response, null, 2) + "\n");
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
finally {
|
|
380
|
+
await proc.close();
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
main()
|
|
384
|
+
.then(() => {
|
|
385
|
+
// success here
|
|
386
|
+
if (process.exitCode === undefined) {
|
|
387
|
+
process.exitCode = 0;
|
|
388
|
+
}
|
|
389
|
+
})
|
|
390
|
+
.catch((err) => {
|
|
391
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
392
|
+
process.exitCode = 1;
|
|
393
|
+
});
|
package/dist/jsonrpc.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { webDevProfile } from "./web-dev.profile.js";
|
|
2
|
+
const profiles = new Map([
|
|
3
|
+
[webDevProfile.id, webDevProfile],
|
|
4
|
+
]);
|
|
5
|
+
export function getProfile(id) {
|
|
6
|
+
return profiles.get(id);
|
|
7
|
+
}
|
|
8
|
+
export function listProfiles() {
|
|
9
|
+
return [...profiles.values()];
|
|
10
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export const webDevProfile = {
|
|
2
|
+
id: "web-dev",
|
|
3
|
+
description: "Zero-config web development MCP stack",
|
|
4
|
+
server: {
|
|
5
|
+
kind: "builtin",
|
|
6
|
+
command: "mcp-server-general",
|
|
7
|
+
autoInstall: true,
|
|
8
|
+
package: "mcp-server-general",
|
|
9
|
+
},
|
|
10
|
+
plugins: [
|
|
11
|
+
{
|
|
12
|
+
name: "web-tools",
|
|
13
|
+
entry: "@mcp/plugin-web-tools",
|
|
14
|
+
},
|
|
15
|
+
],
|
|
16
|
+
ui: {
|
|
17
|
+
enabled: true,
|
|
18
|
+
hint: "Launches browser-based MCP UI for web development",
|
|
19
|
+
},
|
|
20
|
+
notes: [
|
|
21
|
+
"Automatically installs and runs the reference MCP server",
|
|
22
|
+
"Designed for zero-config onboarding",
|
|
23
|
+
],
|
|
24
|
+
};
|
package/dist/prompt.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import readline from "node:readline";
|
|
2
|
+
import { createRequest } from "./jsonrpc.js";
|
|
3
|
+
export function startPrompt(proc) {
|
|
4
|
+
const rl = readline.createInterface({
|
|
5
|
+
input: process.stdin,
|
|
6
|
+
output: process.stdout,
|
|
7
|
+
historySize: 200
|
|
8
|
+
});
|
|
9
|
+
console.error("MCP Prompt Ready");
|
|
10
|
+
proc.on("rpc-response", (msg) => {
|
|
11
|
+
console.error("RESPONSE:", JSON.stringify(msg, null, 2));
|
|
12
|
+
});
|
|
13
|
+
rl.setPrompt("mcp> ");
|
|
14
|
+
rl.prompt();
|
|
15
|
+
rl.on("line", (line) => {
|
|
16
|
+
const trimmed = line.trim();
|
|
17
|
+
if (trimmed === "exit") {
|
|
18
|
+
rl.close();
|
|
19
|
+
proc.close();
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
try {
|
|
23
|
+
const json = JSON.parse(trimmed);
|
|
24
|
+
const req = createRequest(json.method, json.params);
|
|
25
|
+
proc.send(req);
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
console.error("Invalid JSON input");
|
|
29
|
+
}
|
|
30
|
+
rl.prompt();
|
|
31
|
+
});
|
|
32
|
+
}
|
package/dist/runner.js
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
// src/runner.ts
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
import { EventEmitter, once } from "node:events";
|
|
4
|
+
/**
|
|
5
|
+
* MCPProcess
|
|
6
|
+
* - Spawns the provider server
|
|
7
|
+
* - Handles Content-Length framed JSON RPC
|
|
8
|
+
* - Waits for handshake before allowing requests
|
|
9
|
+
*/
|
|
10
|
+
export class MCPProcess extends EventEmitter {
|
|
11
|
+
proc = null;
|
|
12
|
+
opts;
|
|
13
|
+
startupTimeout;
|
|
14
|
+
shutdownTimeout;
|
|
15
|
+
closed = false;
|
|
16
|
+
pending = new Map();
|
|
17
|
+
handshakeEmitted = false;
|
|
18
|
+
constructor(opts) {
|
|
19
|
+
super();
|
|
20
|
+
this.opts = opts;
|
|
21
|
+
this.startupTimeout = opts.startupTimeoutMs ?? 4000;
|
|
22
|
+
this.shutdownTimeout = opts.shutdownTimeoutMs ?? 3000;
|
|
23
|
+
}
|
|
24
|
+
// ---------------------------------------------------------------------
|
|
25
|
+
// START
|
|
26
|
+
// ---------------------------------------------------------------------
|
|
27
|
+
async start() {
|
|
28
|
+
if (this.proc) {
|
|
29
|
+
throw new Error("Process already running.");
|
|
30
|
+
}
|
|
31
|
+
const { command, args = [], cwd, env } = this.opts;
|
|
32
|
+
this.proc = spawn(command, args, {
|
|
33
|
+
cwd,
|
|
34
|
+
env,
|
|
35
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
36
|
+
});
|
|
37
|
+
const spawned = this.proc;
|
|
38
|
+
this.closed = false;
|
|
39
|
+
this.attachRouter();
|
|
40
|
+
spawned.stderr.on("data", (chunk) => {
|
|
41
|
+
this.emit("stderr", chunk.toString());
|
|
42
|
+
});
|
|
43
|
+
spawned.on("exit", (code, signal) => {
|
|
44
|
+
this.closed = true;
|
|
45
|
+
this.emit("exit", { code, signal });
|
|
46
|
+
for (const [id, entry] of this.pending.entries()) {
|
|
47
|
+
clearTimeout(entry.timer);
|
|
48
|
+
entry.reject(new Error(`Process exited before response ${id}`));
|
|
49
|
+
}
|
|
50
|
+
this.pending.clear();
|
|
51
|
+
});
|
|
52
|
+
spawned.on("error", (err) => this.emit("error", err));
|
|
53
|
+
// waiting for handshake but handling the timeout
|
|
54
|
+
try {
|
|
55
|
+
await Promise.race([
|
|
56
|
+
this.waitForHandshake(),
|
|
57
|
+
this.timeout(this.startupTimeout, "Handshake timeout")
|
|
58
|
+
]);
|
|
59
|
+
}
|
|
60
|
+
catch (err) {
|
|
61
|
+
if (process.env.MCP_DEBUG) {
|
|
62
|
+
this.emit("stderr", `[CLIENT] Handshake wait failed (continuing anyway): ${err instanceof Error ? err.message : String(err)}\n`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return { pid: spawned.pid };
|
|
66
|
+
}
|
|
67
|
+
// ---------------------------------------------------------------------
|
|
68
|
+
// SEND REQUEST
|
|
69
|
+
// ---------------------------------------------------------------------
|
|
70
|
+
send(req, timeoutMs = 5000) {
|
|
71
|
+
const proc = this.proc;
|
|
72
|
+
if (!proc || this.closed) {
|
|
73
|
+
return Promise.reject(new Error("Process not running."));
|
|
74
|
+
}
|
|
75
|
+
const id = req.id;
|
|
76
|
+
return new Promise((resolve, reject) => {
|
|
77
|
+
const timer = setTimeout(() => {
|
|
78
|
+
this.pending.delete(id);
|
|
79
|
+
reject(new Error(`Request ${id} timed out after ${timeoutMs}ms`));
|
|
80
|
+
}, timeoutMs);
|
|
81
|
+
this.pending.set(id, { resolve, reject, timer });
|
|
82
|
+
try {
|
|
83
|
+
const body = JSON.stringify(req);
|
|
84
|
+
const header = `Content-Length: ${Buffer.byteLength(body, "utf8")}\r\n\r\n`;
|
|
85
|
+
proc.stdin.write(header + body, "utf8");
|
|
86
|
+
}
|
|
87
|
+
catch (err) {
|
|
88
|
+
clearTimeout(timer);
|
|
89
|
+
this.pending.delete(id);
|
|
90
|
+
reject(new Error(`Failed to write request ${id}: ${err.message}`));
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
// ---------------------------------------------------------------------
|
|
95
|
+
// CLOSE
|
|
96
|
+
// ---------------------------------------------------------------------
|
|
97
|
+
async close() {
|
|
98
|
+
if (this.closed || !this.proc)
|
|
99
|
+
return;
|
|
100
|
+
this.closed = true;
|
|
101
|
+
// Reject all pending
|
|
102
|
+
for (const [id, entry] of this.pending.entries()) {
|
|
103
|
+
clearTimeout(entry.timer);
|
|
104
|
+
entry.reject(new Error(`Process closed before response ${id}`));
|
|
105
|
+
}
|
|
106
|
+
this.pending.clear();
|
|
107
|
+
const p = this.proc;
|
|
108
|
+
try {
|
|
109
|
+
p.kill("SIGTERM");
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
// ignore
|
|
113
|
+
}
|
|
114
|
+
try {
|
|
115
|
+
await Promise.race([
|
|
116
|
+
once(p, "exit"),
|
|
117
|
+
this.timeout(this.shutdownTimeout, "Shutdown timeout")
|
|
118
|
+
]);
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
try {
|
|
122
|
+
p.kill("SIGKILL");
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
// ignore
|
|
126
|
+
}
|
|
127
|
+
await once(p, "exit");
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
// ---------------------------------------------------------------------
|
|
131
|
+
// ROUTER – streaming JSON object scanner (ignores Content-Length)
|
|
132
|
+
// ---------------------------------------------------------------------
|
|
133
|
+
attachRouter() {
|
|
134
|
+
if (!this.proc)
|
|
135
|
+
return;
|
|
136
|
+
let buffer = "";
|
|
137
|
+
this.proc.stdout.on("data", (chunk) => {
|
|
138
|
+
buffer += chunk.toString("utf8");
|
|
139
|
+
parseLoop: while (true) {
|
|
140
|
+
// Find first '{' – skip logs, headers, etc.
|
|
141
|
+
const start = buffer.indexOf("{");
|
|
142
|
+
if (start === -1) {
|
|
143
|
+
// no JSON start yet, wait for more data
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
let depth = 0;
|
|
147
|
+
let inString = false;
|
|
148
|
+
let escape = false;
|
|
149
|
+
let end = -1;
|
|
150
|
+
// Scan forward and find matching '}' that closes the outermost object
|
|
151
|
+
for (let i = start; i < buffer.length; i++) {
|
|
152
|
+
const ch = buffer[i];
|
|
153
|
+
if (escape) {
|
|
154
|
+
// current char is escaped, just skip
|
|
155
|
+
escape = false;
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
if (ch === "\\") {
|
|
159
|
+
if (inString) {
|
|
160
|
+
escape = true;
|
|
161
|
+
}
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
if (ch === "\"") {
|
|
165
|
+
inString = !inString;
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
if (!inString) {
|
|
169
|
+
if (ch === "{") {
|
|
170
|
+
depth++;
|
|
171
|
+
}
|
|
172
|
+
else if (ch === "}") {
|
|
173
|
+
depth--;
|
|
174
|
+
if (depth === 0) {
|
|
175
|
+
end = i;
|
|
176
|
+
break;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
// If depth != 0, JSON is incomplete; wait for more data
|
|
182
|
+
if (depth !== 0 || end === -1) {
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
const jsonStr = buffer.slice(start, end + 1);
|
|
186
|
+
// Drop everything up to the end of this JSON object
|
|
187
|
+
buffer = buffer.slice(end + 1);
|
|
188
|
+
let msg;
|
|
189
|
+
try {
|
|
190
|
+
msg = JSON.parse(jsonStr);
|
|
191
|
+
}
|
|
192
|
+
catch (err) {
|
|
193
|
+
this.emit("stderr", `Invalid JSON from server (scanner): ${err.message}\n`);
|
|
194
|
+
// Try to continue from the next possible JSON in the buffer
|
|
195
|
+
continue parseLoop;
|
|
196
|
+
}
|
|
197
|
+
// First successfully parsed JSON -> treat as handshake
|
|
198
|
+
if (!this.handshakeEmitted) {
|
|
199
|
+
this.handshakeEmitted = true;
|
|
200
|
+
if (process.env.MCP_DEBUG) {
|
|
201
|
+
this.emit("stderr", "[CLIENT] First JSON object parsed, treating as handshake\n");
|
|
202
|
+
}
|
|
203
|
+
this.emit("handshake", msg);
|
|
204
|
+
// Do not treat this as a normal RPC response
|
|
205
|
+
continue parseLoop;
|
|
206
|
+
}
|
|
207
|
+
// Normal RPC response from here on
|
|
208
|
+
this.emit("rpc-response", msg);
|
|
209
|
+
const maybeId = msg.id;
|
|
210
|
+
if (maybeId != null && this.pending.has(Number(maybeId))) {
|
|
211
|
+
const entry = this.pending.get(Number(maybeId));
|
|
212
|
+
clearTimeout(entry.timer);
|
|
213
|
+
this.pending.delete(Number(maybeId));
|
|
214
|
+
entry.resolve(msg);
|
|
215
|
+
}
|
|
216
|
+
// loop to see if another JSON object is already in buffer
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
// ---------------------------------------------------------------------
|
|
221
|
+
// WAIT FOR HANDSHAKE EVENT
|
|
222
|
+
// ---------------------------------------------------------------------
|
|
223
|
+
waitForHandshake() {
|
|
224
|
+
return new Promise((resolve) => this.once("handshake", () => resolve()));
|
|
225
|
+
}
|
|
226
|
+
timeout(ms, message) {
|
|
227
|
+
return new Promise((_resolve, reject) => {
|
|
228
|
+
const t = setTimeout(() => {
|
|
229
|
+
clearTimeout(t);
|
|
230
|
+
reject(new Error(message));
|
|
231
|
+
}, ms);
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
}
|
package/package.json
CHANGED
|
@@ -1,8 +1,63 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mcp-client-general",
|
|
3
|
-
"version": "0.0
|
|
4
|
-
"
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "General-purpose MCP Client and cross-platform CLI for the Model Context Protocol (MCP). Supports JSON-RPC over stdio with robust framing, handshake detection, and multi-request workflows.",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"mcp": "dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc -p tsconfig.json",
|
|
12
|
+
"clean": "rm -rf dist",
|
|
13
|
+
"dev": "tsx src/index.ts",
|
|
14
|
+
"test": "vitest --run",
|
|
15
|
+
"prepare": "npm run build",
|
|
16
|
+
"start": "node dist/index.js",
|
|
17
|
+
"link": "npm link",
|
|
18
|
+
"unlink": "npm unlink -g mcp-client-general || true",
|
|
19
|
+
"lint": "eslint . --ext .ts",
|
|
20
|
+
"lint:fix": "eslint . --ext .ts --fix",
|
|
21
|
+
"format": "prettier --check ."
|
|
22
|
+
},
|
|
23
|
+
"repository": {
|
|
24
|
+
"type": "git",
|
|
25
|
+
"url": "git+https://github.com/daporun/mcp-client-general.git"
|
|
26
|
+
},
|
|
27
|
+
"keywords": [
|
|
28
|
+
"mcp client",
|
|
29
|
+
"mcp",
|
|
30
|
+
"model-context-protocol",
|
|
31
|
+
"jsonrpc",
|
|
32
|
+
"cli",
|
|
33
|
+
"client",
|
|
34
|
+
"stdio",
|
|
35
|
+
"agent",
|
|
36
|
+
"protocol",
|
|
37
|
+
"framework"
|
|
38
|
+
],
|
|
39
|
+
"author": "DAPO / daporun <info@dapo.run>",
|
|
5
40
|
"license": "MIT",
|
|
6
|
-
"
|
|
7
|
-
|
|
41
|
+
"bugs": {
|
|
42
|
+
"url": "https://github.com/daporun/mcp-client-general/issues"
|
|
43
|
+
},
|
|
44
|
+
"homepage": "https://github.com/daporun/mcp-client-general#readme",
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@types/node": "^25.0.0",
|
|
47
|
+
"@typescript-eslint/eslint-plugin": "^8.49.0",
|
|
48
|
+
"@typescript-eslint/parser": "^8.49.0",
|
|
49
|
+
"eslint": "^9.39.1",
|
|
50
|
+
"eslint-config-prettier": "^10.1.8",
|
|
51
|
+
"eslint-plugin-prettier": "^5.5.4",
|
|
52
|
+
"prettier": "^3.7.4",
|
|
53
|
+
"tsx": "^4.21.0",
|
|
54
|
+
"typescript": "^5.9.3",
|
|
55
|
+
"vitest": "^4.0.15"
|
|
56
|
+
},
|
|
57
|
+
"files": [
|
|
58
|
+
"dist",
|
|
59
|
+
"docs",
|
|
60
|
+
"README.md",
|
|
61
|
+
"LICENSE"
|
|
62
|
+
]
|
|
8
63
|
}
|
package/index.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
module.exports = {};
|