@lightdash-tools/mcp 0.1.1 → 0.1.2

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
@@ -70,6 +70,26 @@ Same set in both modes: `list_projects`, `get_project`, `list_explores`, `get_ex
70
70
 
71
71
  Tools with `destructiveHint: true` (e.g. `delete_member`) perform irreversible or high-impact actions. MCP clients should show a warning and/or require user confirmation before executing them. AI agents should ask the user for explicit confirmation before calling such tools.
72
72
 
73
+ ## Testing
74
+
75
+ This package includes unit tests and integration tests. Integration tests run against a real Lightdash API and are only executed if the required environment variables are set.
76
+
77
+ ### Running unit tests
78
+
79
+ ```bash
80
+ pnpm test
81
+ ```
82
+
83
+ ### Running integration tests
84
+
85
+ To run tests against a real Lightdash instance, provide your credentials:
86
+
87
+ ```bash
88
+ LIGHTDASH_URL=https://app.lightdash.cloud LIGHTDASH_API_KEY=your_api_key pnpm test
89
+ ```
90
+
91
+ The integration tests will automatically detect these environment variables and run additional scenarios, such as verifying authentication and tool execution against the live API.
92
+
73
93
  ## License
74
94
 
75
95
  Apache-2.0
package/dist/bin.js CHANGED
@@ -38,8 +38,8 @@ var __importStar = (this && this.__importStar) || (function () {
38
38
  })();
39
39
  const args = process.argv.slice(2);
40
40
  if (args.includes('--http')) {
41
- Promise.resolve().then(() => __importStar(require('./http.js')));
41
+ void Promise.resolve().then(() => __importStar(require('./http.js')));
42
42
  }
43
43
  else {
44
- Promise.resolve().then(() => __importStar(require('./index.js')));
44
+ void Promise.resolve().then(() => __importStar(require('./index.js')));
45
45
  }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ const vitest_1 = require("vitest");
13
+ const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
14
+ const index_js_1 = require("@modelcontextprotocol/sdk/client/index.js");
15
+ const inMemory_js_1 = require("@modelcontextprotocol/sdk/inMemory.js");
16
+ const config_1 = require("./config");
17
+ const tools_1 = require("./tools");
18
+ const shared_1 = require("./tools/shared");
19
+ const hasCredentials = !!process.env.LIGHTDASH_API_KEY && !!process.env.LIGHTDASH_URL;
20
+ vitest_1.describe.runIf(hasCredentials)('MCP Integration (Real API)', () => {
21
+ (0, vitest_1.it)('should authenticate and fetch current organization', () => __awaiter(void 0, void 0, void 0, function* () {
22
+ const client = (0, config_1.getClient)();
23
+ // getCurrentOrganization is a better test for connectivity
24
+ const org = yield client.v1.organizations.getCurrentOrganization();
25
+ (0, vitest_1.expect)(org).toBeDefined();
26
+ (0, vitest_1.expect)(org.organizationUuid).toBeDefined();
27
+ (0, vitest_1.expect)(org.name).toBeDefined();
28
+ console.error(`Authenticated to organization: ${org.name}`);
29
+ }));
30
+ (0, vitest_1.it)('should execute list_projects tool with real API', () => __awaiter(void 0, void 0, void 0, function* () {
31
+ const client = (0, config_1.getClient)();
32
+ const server = new mcp_js_1.McpServer({ name: 'test-server', version: '1.0.0' });
33
+ (0, tools_1.registerTools)(server, client);
34
+ const [serverTransport, clientTransport] = inMemory_js_1.InMemoryTransport.createLinkedPair();
35
+ yield server.connect(serverTransport);
36
+ const mcpClient = new index_js_1.Client({ name: 'test-client', version: '1.0.0' }, { capabilities: {} });
37
+ yield mcpClient.connect(clientTransport);
38
+ // Call the tool through the MCP client
39
+ const result = yield mcpClient.callTool({
40
+ name: shared_1.TOOL_PREFIX + 'list_projects',
41
+ arguments: {},
42
+ });
43
+ if (result.isError) {
44
+ console.error('Tool execution failed:', result.content);
45
+ }
46
+ (0, vitest_1.expect)(result).toBeDefined();
47
+ (0, vitest_1.expect)(result.isError).toBeFalsy();
48
+ (0, vitest_1.expect)(Array.isArray(result.content)).toBe(true);
49
+ const content = result.content;
50
+ const textContent = content[0];
51
+ if (textContent && 'text' in textContent) {
52
+ (0, vitest_1.expect)(typeof textContent.text).toBe('string');
53
+ console.error(`Tool list_projects output: ${textContent.text.slice(0, 100)}...`);
54
+ }
55
+ yield mcpClient.close();
56
+ yield server.close();
57
+ }));
58
+ });
@@ -10,6 +10,7 @@ export type TextContent = {
10
10
  type: 'text';
11
11
  text: string;
12
12
  }>;
13
+ isError?: boolean;
13
14
  };
14
15
  /** Tool handler type used to avoid deep instantiation with SDK/Zod. Accepts (args, extra) for SDK compatibility. */
15
16
  export type ToolHandler = (args: unknown, extra?: unknown) => Promise<TextContent>;
@@ -62,7 +62,7 @@ function wrapTool(client, fn) {
62
62
  }
63
63
  catch (err) {
64
64
  const text = (0, errors_js_1.toMcpErrorMessage)(err);
65
- return { content: [{ type: 'text', text }] };
65
+ return { content: [{ type: 'text', text }], isError: true };
66
66
  }
67
67
  });
68
68
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lightdash-tools/mcp",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "MCP server and utilities for Lightdash AI.",
5
5
  "keywords": [],
6
6
  "license": "Apache-2.0",
@@ -13,7 +13,7 @@
13
13
  "dependencies": {
14
14
  "@modelcontextprotocol/sdk": "^1.26.0",
15
15
  "zod": "^4.3.6",
16
- "@lightdash-tools/client": "0.1.1"
16
+ "@lightdash-tools/client": "0.1.2"
17
17
  },
18
18
  "devDependencies": {
19
19
  "@types/node": "^25.2.3"
package/src/bin.ts CHANGED
@@ -6,7 +6,7 @@
6
6
  const args = process.argv.slice(2);
7
7
 
8
8
  if (args.includes('--http')) {
9
- import('./http.js');
9
+ void import('./http.js');
10
10
  } else {
11
- import('./index.js');
11
+ void import('./index.js');
12
12
  }
@@ -0,0 +1,57 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
+ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
4
+ import { InMemoryTransport } from '@modelcontextprotocol/sdk/inMemory.js';
5
+ import { getClient } from './config';
6
+ import { registerTools } from './tools';
7
+ import { TOOL_PREFIX } from './tools/shared';
8
+
9
+ const hasCredentials = !!process.env.LIGHTDASH_API_KEY && !!process.env.LIGHTDASH_URL;
10
+
11
+ describe.runIf(hasCredentials)('MCP Integration (Real API)', () => {
12
+ it('should authenticate and fetch current organization', async () => {
13
+ const client = getClient();
14
+ // getCurrentOrganization is a better test for connectivity
15
+ const org = await client.v1.organizations.getCurrentOrganization();
16
+ expect(org).toBeDefined();
17
+ expect(org.organizationUuid).toBeDefined();
18
+ expect(org.name).toBeDefined();
19
+ console.error(`Authenticated to organization: ${org.name}`);
20
+ });
21
+
22
+ it('should execute list_projects tool with real API', async () => {
23
+ const client = getClient();
24
+ const server = new McpServer({ name: 'test-server', version: '1.0.0' });
25
+ registerTools(server, client);
26
+
27
+ const [serverTransport, clientTransport] = InMemoryTransport.createLinkedPair();
28
+
29
+ await server.connect(serverTransport);
30
+
31
+ const mcpClient = new Client({ name: 'test-client', version: '1.0.0' }, { capabilities: {} });
32
+ await mcpClient.connect(clientTransport);
33
+
34
+ // Call the tool through the MCP client
35
+ const result = await mcpClient.callTool({
36
+ name: TOOL_PREFIX + 'list_projects',
37
+ arguments: {},
38
+ });
39
+
40
+ if (result.isError) {
41
+ console.error('Tool execution failed:', result.content);
42
+ }
43
+ expect(result).toBeDefined();
44
+ expect(result.isError).toBeFalsy();
45
+ expect(Array.isArray(result.content)).toBe(true);
46
+ const content = result.content as { text: string }[];
47
+
48
+ const textContent = content[0];
49
+ if (textContent && 'text' in textContent) {
50
+ expect(typeof textContent.text).toBe('string');
51
+ console.error(`Tool list_projects output: ${textContent.text.slice(0, 100)}...`);
52
+ }
53
+
54
+ await mcpClient.close();
55
+ await server.close();
56
+ });
57
+ });
@@ -11,6 +11,7 @@ export const TOOL_PREFIX = 'lightdash_tools__';
11
11
 
12
12
  export type TextContent = {
13
13
  content: Array<{ type: 'text'; text: string }>;
14
+ isError?: boolean;
14
15
  };
15
16
 
16
17
  /** Tool handler type used to avoid deep instantiation with SDK/Zod. Accepts (args, extra) for SDK compatibility. */
@@ -95,7 +96,7 @@ export function wrapTool<T>(
95
96
  return await handler(args as T);
96
97
  } catch (err) {
97
98
  const text = toMcpErrorMessage(err);
98
- return { content: [{ type: 'text', text }] };
99
+ return { content: [{ type: 'text', text }], isError: true };
99
100
  }
100
101
  };
101
102
  }