@shibbirweb/mcp-db-read-only 0.1.0 → 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.
Files changed (40) hide show
  1. package/CHANGELOG.md +21 -1
  2. package/README.dockerhub.md +233 -234
  3. package/README.md +221 -269
  4. package/dist/ApplicationFactory.js +111 -14
  5. package/dist/cli/ViewerCommand.js +117 -0
  6. package/dist/config/EnvironmentConfigLoader.js +58 -0
  7. package/dist/drivers/BaseDriver.js +10 -1
  8. package/dist/drivers/document/MongoDriver.js +27 -30
  9. package/dist/drivers/keyvalue/RedisDriver.js +20 -5
  10. package/dist/drivers/search/ElasticsearchDriver.js +13 -9
  11. package/dist/drivers/sql/ClickHouseDriver.js +12 -13
  12. package/dist/drivers/sql/MsSqlDriver.js +6 -6
  13. package/dist/drivers/sql/MySqlDriver.js +7 -5
  14. package/dist/drivers/sql/MySqlSessionInitializer.js +17 -2
  15. package/dist/drivers/sql/PostgresDriver.js +6 -6
  16. package/dist/drivers/sql/SqliteDriver.js +3 -3
  17. package/dist/formatting/JsonSerializer.js +3 -2
  18. package/dist/index.js +16 -7
  19. package/dist/logging/CallLogger.js +139 -0
  20. package/dist/logging/LogChannel.js +18 -0
  21. package/dist/logging/LogFormatter.js +80 -0
  22. package/dist/logging/LogRecords.js +7 -0
  23. package/dist/logging/LogSink.js +38 -0
  24. package/dist/logging/RecordJson.js +39 -0
  25. package/dist/logging/Redactor.js +95 -0
  26. package/dist/logging/StatementTracer.js +9 -0
  27. package/dist/logging/ToolCallObserver.js +6 -0
  28. package/dist/logging/store/FolderLogChannel.js +56 -0
  29. package/dist/logging/store/FolderLogStore.js +214 -0
  30. package/dist/logging/store/LogFileNames.js +57 -0
  31. package/dist/logging/store/LogStore.js +18 -0
  32. package/dist/logging/store/MemoryLogStore.js +70 -0
  33. package/dist/logging/viewer/LiveLogViewer.js +264 -0
  34. package/dist/logging/viewer/LiveViewerObserver.js +62 -0
  35. package/dist/logging/viewer/ViewerAssets.js +625 -0
  36. package/dist/server/BackgroundService.js +1 -0
  37. package/dist/server/McpDbServer.js +16 -2
  38. package/dist/tools/BaseTool.js +8 -2
  39. package/dist/tools/connection/CurrentConnectionTool.js +11 -2
  40. package/package.json +1 -1
@@ -11,6 +11,8 @@ export class McpDbServer {
11
11
  tools;
12
12
  drivers;
13
13
  logger;
14
+ observer;
15
+ services;
14
16
  server;
15
17
  shuttingDown = false;
16
18
  /**
@@ -18,20 +20,31 @@ export class McpDbServer {
18
20
  * second place to remember at release time, and the one that silently wins
19
21
  * when it is forgotten. The composition root reads it from `package.json`.
20
22
  */
21
- constructor(tools, drivers, logger, version, name = "db-readonly-switchable") {
23
+ constructor(tools, drivers, logger, observer, services, version, name = "db-readonly-switchable") {
22
24
  this.tools = tools;
23
25
  this.drivers = drivers;
24
26
  this.logger = logger;
27
+ this.observer = observer;
28
+ this.services = services;
25
29
  this.server = new McpServer({ name, version });
26
30
  }
31
+ /** The MCP client's name from its handshake, e.g. "claude-ai", or null before one. */
32
+ clientName() {
33
+ return this.server.server.getClientVersion()?.name ?? null;
34
+ }
27
35
  async start() {
28
36
  // Registered before the transport connects, so a tools/list arriving
29
37
  // immediately after the handshake can be answered.
30
38
  for (const tool of this.tools) {
31
- tool.register(this.server);
39
+ tool.register(this.server, this.observer);
32
40
  }
33
41
  this.installSignalHandlers();
34
42
  await this.server.connect(new StdioServerTransport());
43
+ // After the transport, so a slow or failing helper cannot delay the
44
+ // handshake. Each one reports its own problems and never rejects.
45
+ for (const service of this.services) {
46
+ await service.start().catch((error) => this.logger(`background service failed to start: ${String(error)}`));
47
+ }
35
48
  // stderr, always. On stdio transport stdout carries JSON-RPC and a single
36
49
  // stray byte corrupts the stream.
37
50
  this.logger("MCP database server running (read-only, switchable connection)");
@@ -63,6 +76,7 @@ export class McpDbServer {
63
76
  return;
64
77
  }
65
78
  this.shuttingDown = true;
79
+ await Promise.all(this.services.map((service) => service.stop().catch(() => undefined)));
66
80
  await this.drivers.closeAll();
67
81
  process.exit(0);
68
82
  }
@@ -1,4 +1,5 @@
1
1
  import { ToolResponse } from "../formatting/ToolResponse.js";
2
+ import { SilentObserver } from "../logging/ToolCallObserver.js";
2
3
  /**
3
4
  * Template Method base for every tool.
4
5
  *
@@ -13,8 +14,13 @@ export class BaseTool {
13
14
  * argument type from the schema it was given, which it cannot do for a
14
15
  * schema held in an abstract property. Every subclass declares its own
15
16
  * argument interface, so the type is recovered immediately below.
17
+ *
18
+ * The observer sees the call from outside `invoke`, so it receives the
19
+ * final result, error results included, and a tool cannot opt out of it
20
+ * any more than it can opt out of the error contract. When call logging is
21
+ * off it is a SilentObserver and adds nothing.
16
22
  */
17
- register(server) {
23
+ register(server, observer = new SilentObserver()) {
18
24
  server.registerTool(this.name, {
19
25
  // Sent at the top level as well as inside the annotations: newer
20
26
  // clients read the top-level field, older ones only the annotation.
@@ -22,7 +28,7 @@ export class BaseTool {
22
28
  description: this.description,
23
29
  inputSchema: this.inputSchema,
24
30
  annotations: this.annotations,
25
- }, (args) => this.invoke(args));
31
+ }, (args) => observer.observe(this.name, args, () => this.invoke(args)));
26
32
  }
27
33
  /**
28
34
  * The one place a thrown error becomes a tool error.
@@ -4,6 +4,7 @@ import { ToolResponse } from "../../formatting/ToolResponse.js";
4
4
  /** Reports the active connection, its engine and its database. */
5
5
  export class CurrentConnectionTool extends BaseTool {
6
6
  connections;
7
+ viewerStatus;
7
8
  name = "current_connection";
8
9
  description = "Show which database server, engine and database the read-only tools are currently pointed at";
9
10
  annotations = {
@@ -14,23 +15,31 @@ export class CurrentConnectionTool extends BaseTool {
14
15
  openWorldHint: true,
15
16
  };
16
17
  inputSchema = {};
17
- constructor(connections) {
18
+ /**
19
+ * @param viewerStatus the live log viewer's state, when it is configured,
20
+ * so "where are my logs?" has an answer in the chat itself.
21
+ */
22
+ constructor(connections, viewerStatus = () => null) {
18
23
  super();
19
24
  this.connections = connections;
25
+ this.viewerStatus = viewerStatus;
20
26
  }
21
27
  async execute() {
22
28
  const target = this.connections.getActiveTarget();
23
29
  // Reads the nullable accessor rather than the asserting one, so an
24
30
  // unconfigured server reports its state as ordinary output instead of an
25
31
  // error. Nothing has gone wrong; nothing has been chosen yet.
32
+ const viewer = this.viewerStatus();
33
+ const viewerLine = viewer ? [`Live log viewer: ${viewer}`] : [];
26
34
  if (!target) {
27
- return ToolResponse.text("No active connection. Call connect or use_connection to set one.");
35
+ return ToolResponse.text(["No active connection. Call connect or use_connection to set one.", ...viewerLine].join("\n"));
28
36
  }
29
37
  // Rendering through describe() is what keeps the password out of output.
30
38
  return ToolResponse.text([
31
39
  `Active profile: ${this.connections.getActiveName()}`,
32
40
  `Engine: ${EngineCatalog.label(target.engine)}`,
33
41
  `Target: ${target.describe()}`,
42
+ ...viewerLine,
34
43
  ].join("\n"));
35
44
  }
36
45
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shibbirweb/mcp-db-read-only",
3
- "version": "0.1.0",
3
+ "version": "1.0.0",
4
4
  "description": "A read-only MCP server for MySQL, MariaDB, PostgreSQL, SQLite, SQL Server, ClickHouse, MongoDB, Redis and Elasticsearch, with connections switchable at runtime.",
5
5
  "license": "MIT",
6
6
  "author": "Md. Shibbir Ahmed <shibbirweb@gmail.com>",