@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 +20 -0
- package/dist/bin.js +2 -2
- package/dist/index.integration.test.d.ts +1 -0
- package/dist/index.integration.test.js +58 -0
- package/dist/tools/shared.d.ts +1 -0
- package/dist/tools/shared.js +1 -1
- package/package.json +2 -2
- package/src/bin.ts +2 -2
- package/src/index.integration.test.ts +57 -0
- package/src/tools/shared.ts +2 -1
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
|
+
});
|
package/dist/tools/shared.d.ts
CHANGED
|
@@ -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>;
|
package/dist/tools/shared.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lightdash-tools/mcp",
|
|
3
|
-
"version": "0.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.
|
|
16
|
+
"@lightdash-tools/client": "0.1.2"
|
|
17
17
|
},
|
|
18
18
|
"devDependencies": {
|
|
19
19
|
"@types/node": "^25.2.3"
|
package/src/bin.ts
CHANGED
|
@@ -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
|
+
});
|
package/src/tools/shared.ts
CHANGED
|
@@ -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
|
}
|