mcp-server-commands 0.3.0 → 0.4.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 CHANGED
@@ -74,6 +74,16 @@ On Windows: `%APPDATA%/Claude/claude_desktop_config.json`
74
74
  }
75
75
  ```
76
76
 
77
+ ### Logging
78
+
79
+ Claude Desktop app writes logs to `~/Library/Logs/Claude/mcp-server-mcp-server-commands.log`
80
+
81
+ By default, only important messages are logged (i.e. errors).
82
+ If you want to see more messages, add `--verbose` to the `args` when configuring the server.
83
+
84
+ By the way, logs are written to `STDERR` because that is what Claude Desktop routes to the log files.
85
+ In the future, I expect well formatted log messages to be written over the `STDIO` transport to the MCP client (note: not Claude Desktop app).
86
+
77
87
  ### Debugging
78
88
 
79
89
  Since MCP servers communicate over stdio, debugging can be challenging. We recommend using the [MCP Inspector](https://github.com/modelcontextprotocol/inspector), which is available as a package script:
@@ -83,3 +93,7 @@ npm run inspector
83
93
  ```
84
94
 
85
95
  The Inspector will provide a URL to access debugging tools in your browser.
96
+
97
+ ## TODOs
98
+
99
+ - Add some mechanism (likely in a new MCP server) to retain memory of past command failures, i.e. to use `python3` and not `python` and tie it to a machine or some context?
package/build/index.js CHANGED
@@ -5,19 +5,72 @@ import { CallToolRequestSchema, ListToolsRequestSchema, ListPromptsRequestSchema
5
5
  import { exec } from "node:child_process";
6
6
  import { promisify } from "node:util";
7
7
  import { execFileWithInput } from "./exec-utils.js";
8
- // TODO use .promises?
8
+ // TODO use .promises? in node api
9
9
  const execAsync = promisify(exec);
10
+ let verbose = false;
11
+ // check CLI args:
12
+ if (process.argv.includes("--verbose")) {
13
+ verbose = true;
14
+ }
10
15
  const server = new Server({
11
16
  name: "mcp-server-commands",
12
- version: "0.3.0",
17
+ version: "0.4.0",
13
18
  }, {
14
19
  capabilities: {
15
20
  //resources: {},
16
21
  tools: {},
17
22
  prompts: {},
23
+ //logging: {}, // for logging messages that don't seem to work yet or I am doing them wrong
18
24
  },
19
25
  });
26
+ function always_log(message, data) {
27
+ if (data) {
28
+ console.error(message + ": " + JSON.stringify(data));
29
+ }
30
+ else {
31
+ console.error(message);
32
+ }
33
+ }
34
+ if (verbose) {
35
+ always_log("INFO: verbose logging enabled");
36
+ }
37
+ else {
38
+ always_log("INFO: verbose logging disabled, enable it with --verbose");
39
+ }
40
+ function verbose_log(message, data) {
41
+ // https://modelcontextprotocol.io/docs/tools/debugging - mentions various ways to debug/troubleshoot (including dev tools)
42
+ //
43
+ // remember STDIO transport means can't log over STDOUT (client expects JSON messages per the spec)
44
+ // https://modelcontextprotocol.io/docs/tools/debugging#implementing-logging
45
+ // mentions STDERR is captured by the host app (i.e. Claude Desktop app)
46
+ // server.sendLoggingMessage is captured by MCP client (not Claude Desktop app)
47
+ // SO, IIUC use STDERR for logging into Claude Desktop app logs in:
48
+ // '~/Library/Logs/Claude/mcp.log'
49
+ if (verbose) {
50
+ always_log(message, data);
51
+ }
52
+ // inspector, catches these logs and shows them on left hand side of screen (sidebar)
53
+ // TODO add verbose parameter (CLI arg?)
54
+ // IF I wanted to log via MCP client logs (not sure what those are/do):
55
+ // I do not see inspector catching these logs :(, there is a server notifications section and it remains empty
56
+ //server.sendLoggingMessage({
57
+ // level: "info",
58
+ // data: message,
59
+ //});
60
+ // which results in something like:
61
+ //server.notification({
62
+ // method: "notifications/message",
63
+ // params: {
64
+ // level: "warning",
65
+ // logger: "mcp-server-commands",
66
+ // data: "ListToolsRequest2",
67
+ // },
68
+ //});
69
+ //
70
+ // FYI client should also requets a log level from the server, so that needs to be here at some point too
71
+ }
20
72
  server.setRequestHandler(ListToolsRequestSchema, async () => {
73
+ verbose_log("INFO: ListTools");
21
74
  return {
22
75
  tools: [
23
76
  {
@@ -74,6 +127,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
74
127
  };
75
128
  });
76
129
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
130
+ verbose_log("INFO: ToolRequest", request);
77
131
  switch (request.params.name) {
78
132
  case "run_command": {
79
133
  return {
@@ -108,11 +162,13 @@ async function runCommand(args) {
108
162
  }
109
163
  catch (error) {
110
164
  // TODO catch for other errors, not just ExecException
111
- return {
165
+ // FYI failure may not always be a bad thing if for example checking for a file to exist so just keep that in mind in terms of logging?
166
+ const response = {
112
167
  isError: true,
113
168
  content: messagesFor(error),
114
- //content: [{ type: "text", text: JSON.stringify(error) }],
115
169
  };
170
+ always_log("WARN: run_command failed", response);
171
+ return response;
116
172
  }
117
173
  }
118
174
  async function runScript(args) {
@@ -141,10 +197,12 @@ async function runScript(args) {
141
197
  };
142
198
  }
143
199
  catch (error) {
144
- return {
200
+ const response = {
145
201
  isError: true,
146
202
  content: messagesFor(error),
147
203
  };
204
+ always_log("WARN: run_script failed", response);
205
+ return response;
148
206
  }
149
207
  }
150
208
  function messagesFor(result) {
@@ -174,6 +232,7 @@ function messagesFor(result) {
174
232
  return messages;
175
233
  }
176
234
  server.setRequestHandler(ListPromptsRequestSchema, async () => {
235
+ verbose_log("INFO: ListPrompts");
177
236
  return {
178
237
  prompts: [
179
238
  {
@@ -193,6 +252,7 @@ server.setRequestHandler(GetPromptRequestSchema, async (request) => {
193
252
  if (request.params.name !== "run_command") {
194
253
  throw new Error("Unknown prompt");
195
254
  }
255
+ verbose_log("INFO: PromptRequest", request);
196
256
  const command = String(request.params.arguments?.command);
197
257
  if (!command) {
198
258
  throw new Error("Command is required");
@@ -231,6 +291,7 @@ server.setRequestHandler(GetPromptRequestSchema, async (request) => {
231
291
  },
232
292
  });
233
293
  }
294
+ verbose_log("INFO: PromptResponse", messages);
234
295
  return { messages };
235
296
  });
236
297
  async function main() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-server-commands",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "An MCP server to run arbitrary commands",
5
5
  "private": false,
6
6
  "type": "module",
@@ -15,13 +15,19 @@
15
15
  "build": "tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"",
16
16
  "prepare": "npm run build",
17
17
  "watch": "npm run build && tsc --watch",
18
- "inspector": "npx @modelcontextprotocol/inspector build/index.js"
18
+ "inspector": "npx @modelcontextprotocol/inspector build/index.js",
19
+ "test": "jest",
20
+ "test:watch": "jest --watch",
21
+ "test:integration": "jest tests/integration"
19
22
  },
20
23
  "dependencies": {
21
24
  "@modelcontextprotocol/sdk": "0.6.0"
22
25
  },
23
26
  "devDependencies": {
27
+ "@types/jest": "^29.5.11",
24
28
  "@types/node": "^20.11.24",
29
+ "jest": "^29.7.0",
30
+ "ts-jest": "^29.1.1",
25
31
  "typescript": "^5.3.3"
26
32
  }
27
33
  }