@kelpi/mcp 0.1.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 +312 -0
- package/bin/kelpi-mcp +3 -0
- package/dist/__tests__/integration-api/fixtures.d.ts +382 -0
- package/dist/__tests__/integration-api/fixtures.d.ts.map +1 -0
- package/dist/__tests__/integration-api/fixtures.js +478 -0
- package/dist/__tests__/integration-api/fixtures.js.map +1 -0
- package/dist/__tests__/integration-api/index.d.ts +19 -0
- package/dist/__tests__/integration-api/index.d.ts.map +1 -0
- package/dist/__tests__/integration-api/index.js +33 -0
- package/dist/__tests__/integration-api/index.js.map +1 -0
- package/dist/__tests__/integration-api/setup.d.ts +176 -0
- package/dist/__tests__/integration-api/setup.d.ts.map +1 -0
- package/dist/__tests__/integration-api/setup.js +329 -0
- package/dist/__tests__/integration-api/setup.js.map +1 -0
- package/dist/__tests__/setup.d.ts +2 -0
- package/dist/__tests__/setup.d.ts.map +1 -0
- package/dist/__tests__/setup.js +11 -0
- package/dist/__tests__/setup.js.map +1 -0
- package/dist/__tests__/unit/test-utils.d.ts +46 -0
- package/dist/__tests__/unit/test-utils.d.ts.map +1 -0
- package/dist/__tests__/unit/test-utils.js +50 -0
- package/dist/__tests__/unit/test-utils.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +34 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/api-client.d.ts +17 -0
- package/dist/lib/api-client.d.ts.map +1 -0
- package/dist/lib/api-client.js +169 -0
- package/dist/lib/api-client.js.map +1 -0
- package/dist/lib/auth-state.d.ts +54 -0
- package/dist/lib/auth-state.d.ts.map +1 -0
- package/dist/lib/auth-state.js +131 -0
- package/dist/lib/auth-state.js.map +1 -0
- package/dist/lib/config.d.ts +39 -0
- package/dist/lib/config.d.ts.map +1 -0
- package/dist/lib/config.js +170 -0
- package/dist/lib/config.js.map +1 -0
- package/dist/lib/error-formatter.d.ts +40 -0
- package/dist/lib/error-formatter.d.ts.map +1 -0
- package/dist/lib/error-formatter.js +149 -0
- package/dist/lib/error-formatter.js.map +1 -0
- package/dist/lib/errors.d.ts +44 -0
- package/dist/lib/errors.d.ts.map +1 -0
- package/dist/lib/errors.js +56 -0
- package/dist/lib/errors.js.map +1 -0
- package/dist/lib/tool-helpers.d.ts +49 -0
- package/dist/lib/tool-helpers.d.ts.map +1 -0
- package/dist/lib/tool-helpers.js +101 -0
- package/dist/lib/tool-helpers.js.map +1 -0
- package/dist/lib/tool-registry.d.ts +111 -0
- package/dist/lib/tool-registry.d.ts.map +1 -0
- package/dist/lib/tool-registry.js +112 -0
- package/dist/lib/tool-registry.js.map +1 -0
- package/dist/lib/version.d.ts +13 -0
- package/dist/lib/version.d.ts.map +1 -0
- package/dist/lib/version.js +13 -0
- package/dist/lib/version.js.map +1 -0
- package/dist/prompts/flow-generator.d.ts +45 -0
- package/dist/prompts/flow-generator.d.ts.map +1 -0
- package/dist/prompts/flow-generator.js +177 -0
- package/dist/prompts/flow-generator.js.map +1 -0
- package/dist/prompts/index.d.ts +7 -0
- package/dist/prompts/index.d.ts.map +1 -0
- package/dist/prompts/index.js +7 -0
- package/dist/prompts/index.js.map +1 -0
- package/dist/server.d.ts +66 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +140 -0
- package/dist/server.js.map +1 -0
- package/dist/tools/auth/index.d.ts +18 -0
- package/dist/tools/auth/index.d.ts.map +1 -0
- package/dist/tools/auth/index.js +50 -0
- package/dist/tools/auth/index.js.map +1 -0
- package/dist/tools/auth/login.d.ts +37 -0
- package/dist/tools/auth/login.d.ts.map +1 -0
- package/dist/tools/auth/login.js +257 -0
- package/dist/tools/auth/login.js.map +1 -0
- package/dist/tools/auth/schemas.d.ts +69 -0
- package/dist/tools/auth/schemas.d.ts.map +1 -0
- package/dist/tools/auth/schemas.js +36 -0
- package/dist/tools/auth/schemas.js.map +1 -0
- package/dist/tools/auth/status.d.ts +11 -0
- package/dist/tools/auth/status.d.ts.map +1 -0
- package/dist/tools/auth/status.js +50 -0
- package/dist/tools/auth/status.js.map +1 -0
- package/dist/tools/contacts/create.d.ts +11 -0
- package/dist/tools/contacts/create.d.ts.map +1 -0
- package/dist/tools/contacts/create.js +47 -0
- package/dist/tools/contacts/create.js.map +1 -0
- package/dist/tools/contacts/index.d.ts +10 -0
- package/dist/tools/contacts/index.d.ts.map +1 -0
- package/dist/tools/contacts/index.js +40 -0
- package/dist/tools/contacts/index.js.map +1 -0
- package/dist/tools/contacts/schemas.d.ts +37 -0
- package/dist/tools/contacts/schemas.d.ts.map +1 -0
- package/dist/tools/contacts/schemas.js +15 -0
- package/dist/tools/contacts/schemas.js.map +1 -0
- package/dist/tools/events/index.d.ts +10 -0
- package/dist/tools/events/index.d.ts.map +1 -0
- package/dist/tools/events/index.js +42 -0
- package/dist/tools/events/index.js.map +1 -0
- package/dist/tools/events/schemas.d.ts +37 -0
- package/dist/tools/events/schemas.d.ts.map +1 -0
- package/dist/tools/events/schemas.js +17 -0
- package/dist/tools/events/schemas.js.map +1 -0
- package/dist/tools/events/track.d.ts +11 -0
- package/dist/tools/events/track.d.ts.map +1 -0
- package/dist/tools/events/track.js +41 -0
- package/dist/tools/events/track.js.map +1 -0
- package/dist/tools/flows/activate.d.ts +11 -0
- package/dist/tools/flows/activate.d.ts.map +1 -0
- package/dist/tools/flows/activate.js +46 -0
- package/dist/tools/flows/activate.js.map +1 -0
- package/dist/tools/flows/create.d.ts +11 -0
- package/dist/tools/flows/create.d.ts.map +1 -0
- package/dist/tools/flows/create.js +72 -0
- package/dist/tools/flows/create.js.map +1 -0
- package/dist/tools/flows/index.d.ts +24 -0
- package/dist/tools/flows/index.d.ts.map +1 -0
- package/dist/tools/flows/index.js +183 -0
- package/dist/tools/flows/index.js.map +1 -0
- package/dist/tools/flows/list.d.ts +11 -0
- package/dist/tools/flows/list.d.ts.map +1 -0
- package/dist/tools/flows/list.js +34 -0
- package/dist/tools/flows/list.js.map +1 -0
- package/dist/tools/flows/schemas.d.ts +621 -0
- package/dist/tools/flows/schemas.d.ts.map +1 -0
- package/dist/tools/flows/schemas.js +135 -0
- package/dist/tools/flows/schemas.js.map +1 -0
- package/dist/tools/flows/transform.d.ts +39 -0
- package/dist/tools/flows/transform.d.ts.map +1 -0
- package/dist/tools/flows/transform.js +139 -0
- package/dist/tools/flows/transform.js.map +1 -0
- package/dist/tools/index.d.ts +34 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +46 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/sdk/index.d.ts +18 -0
- package/dist/tools/sdk/index.d.ts.map +1 -0
- package/dist/tools/sdk/index.js +69 -0
- package/dist/tools/sdk/index.js.map +1 -0
- package/dist/tools/sdk/public-key.d.ts +11 -0
- package/dist/tools/sdk/public-key.d.ts.map +1 -0
- package/dist/tools/sdk/public-key.js +24 -0
- package/dist/tools/sdk/public-key.js.map +1 -0
- package/dist/tools/sdk/schemas.d.ts +48 -0
- package/dist/tools/sdk/schemas.d.ts.map +1 -0
- package/dist/tools/sdk/schemas.js +35 -0
- package/dist/tools/sdk/schemas.js.map +1 -0
- package/dist/tools/sdk/snippet.d.ts +11 -0
- package/dist/tools/sdk/snippet.d.ts.map +1 -0
- package/dist/tools/sdk/snippet.js +50 -0
- package/dist/tools/sdk/snippet.js.map +1 -0
- package/dist/tools/sdk/templates/index.d.ts +5 -0
- package/dist/tools/sdk/templates/index.d.ts.map +1 -0
- package/dist/tools/sdk/templates/index.js +5 -0
- package/dist/tools/sdk/templates/index.js.map +1 -0
- package/dist/tools/sdk/templates/nextjs.d.ts +5 -0
- package/dist/tools/sdk/templates/nextjs.d.ts.map +1 -0
- package/dist/tools/sdk/templates/nextjs.js +71 -0
- package/dist/tools/sdk/templates/nextjs.js.map +1 -0
- package/dist/tools/sdk/templates/node.d.ts +9 -0
- package/dist/tools/sdk/templates/node.d.ts.map +1 -0
- package/dist/tools/sdk/templates/node.js +170 -0
- package/dist/tools/sdk/templates/node.js.map +1 -0
- package/dist/tools/sdk/templates/react.d.ts +5 -0
- package/dist/tools/sdk/templates/react.d.ts.map +1 -0
- package/dist/tools/sdk/templates/react.js +54 -0
- package/dist/tools/sdk/templates/react.js.map +1 -0
- package/dist/tools/sdk/templates/vanilla.d.ts +5 -0
- package/dist/tools/sdk/templates/vanilla.d.ts.map +1 -0
- package/dist/tools/sdk/templates/vanilla.js +61 -0
- package/dist/tools/sdk/templates/vanilla.js.map +1 -0
- package/dist/tools/templates/create.d.ts +11 -0
- package/dist/tools/templates/create.d.ts.map +1 -0
- package/dist/tools/templates/create.js +39 -0
- package/dist/tools/templates/create.js.map +1 -0
- package/dist/tools/templates/index.d.ts +17 -0
- package/dist/tools/templates/index.d.ts.map +1 -0
- package/dist/tools/templates/index.js +68 -0
- package/dist/tools/templates/index.js.map +1 -0
- package/dist/tools/templates/list.d.ts +11 -0
- package/dist/tools/templates/list.d.ts.map +1 -0
- package/dist/tools/templates/list.js +31 -0
- package/dist/tools/templates/list.js.map +1 -0
- package/dist/tools/templates/schemas.d.ts +90 -0
- package/dist/tools/templates/schemas.d.ts.map +1 -0
- package/dist/tools/templates/schemas.js +37 -0
- package/dist/tools/templates/schemas.js.map +1 -0
- package/dist/types.d.ts +55 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +76 -0
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Integration test setup for MCP API tests.
|
|
3
|
+
*
|
|
4
|
+
* Provides utilities for testing MCP tools against the real Kelpi HTTP API.
|
|
5
|
+
* These tests require both local Supabase and Next.js server running.
|
|
6
|
+
*
|
|
7
|
+
* CRITICAL: Tests only run against localhost to prevent accidents with prod/staging.
|
|
8
|
+
*/
|
|
9
|
+
import { SupabaseClient } from '@supabase/supabase-js';
|
|
10
|
+
/**
|
|
11
|
+
* Validate that we're running against localhost.
|
|
12
|
+
* CRITICAL safety feature to prevent accidental production/staging data access.
|
|
13
|
+
*
|
|
14
|
+
* @throws Error if Supabase URL or API URL is not localhost
|
|
15
|
+
*/
|
|
16
|
+
export declare function validateLocalhost(): void;
|
|
17
|
+
/**
|
|
18
|
+
* Get the Supabase client for integration tests.
|
|
19
|
+
* Uses service role key to bypass RLS for test setup/teardown.
|
|
20
|
+
*
|
|
21
|
+
* @returns Supabase client instance
|
|
22
|
+
*/
|
|
23
|
+
export declare function getTestSupabase(): SupabaseClient;
|
|
24
|
+
/**
|
|
25
|
+
* Get the API base URL for tests.
|
|
26
|
+
*
|
|
27
|
+
* @returns API base URL (default: http://localhost:3000/api/v1)
|
|
28
|
+
*/
|
|
29
|
+
export declare function getTestApiUrl(): string;
|
|
30
|
+
/**
|
|
31
|
+
* Workspace type for test context
|
|
32
|
+
*/
|
|
33
|
+
export interface TestWorkspace {
|
|
34
|
+
id: string;
|
|
35
|
+
name: string;
|
|
36
|
+
created_at: string;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Create a test workspace with a unique name.
|
|
40
|
+
*
|
|
41
|
+
* @param namePrefix - Optional prefix for workspace name (default: 'MCP Test')
|
|
42
|
+
* @returns Created workspace
|
|
43
|
+
*/
|
|
44
|
+
export declare function createTestWorkspace(namePrefix?: string): Promise<TestWorkspace>;
|
|
45
|
+
/**
|
|
46
|
+
* Delete a workspace and all related data (cascade).
|
|
47
|
+
* Safe to call even if workspace doesn't exist.
|
|
48
|
+
*
|
|
49
|
+
* @param workspaceId - ID of workspace to delete
|
|
50
|
+
*/
|
|
51
|
+
export declare function cleanupWorkspace(workspaceId: string): Promise<void>;
|
|
52
|
+
/**
|
|
53
|
+
* API key type constants
|
|
54
|
+
*/
|
|
55
|
+
export declare const KeyType: {
|
|
56
|
+
readonly PUBLIC: "public";
|
|
57
|
+
readonly SECRET: "secret";
|
|
58
|
+
};
|
|
59
|
+
export type KeyType = (typeof KeyType)[keyof typeof KeyType];
|
|
60
|
+
/**
|
|
61
|
+
* Generated API key result
|
|
62
|
+
*/
|
|
63
|
+
export interface GeneratedApiKey {
|
|
64
|
+
/** Raw API key to use in requests */
|
|
65
|
+
plaintext: string;
|
|
66
|
+
/** Key prefix for display/debugging */
|
|
67
|
+
prefix: string;
|
|
68
|
+
/** SHA-256 hash stored in database */
|
|
69
|
+
hash: string;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Create a test API key for a workspace.
|
|
73
|
+
* Returns the raw (unhashed) key for use in API requests.
|
|
74
|
+
*
|
|
75
|
+
* @param workspaceId - Workspace to create key for
|
|
76
|
+
* @param options - Key options (type, name, revoked)
|
|
77
|
+
* @returns Raw API key string
|
|
78
|
+
*/
|
|
79
|
+
export declare function createTestApiKey(workspaceId: string, options?: {
|
|
80
|
+
keyType?: KeyType;
|
|
81
|
+
name?: string;
|
|
82
|
+
revoked?: boolean;
|
|
83
|
+
}): Promise<string>;
|
|
84
|
+
/**
|
|
85
|
+
* Test context with workspace and API keys.
|
|
86
|
+
* Use this to set up and tear down test state.
|
|
87
|
+
*/
|
|
88
|
+
export interface TestContext {
|
|
89
|
+
workspace: TestWorkspace;
|
|
90
|
+
secretKey: string;
|
|
91
|
+
publicKey: string;
|
|
92
|
+
cleanup: () => Promise<void>;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Create a complete test context with workspace and both API key types.
|
|
96
|
+
* Call cleanup() in afterAll/afterEach to clean up resources.
|
|
97
|
+
*
|
|
98
|
+
* @param namePrefix - Optional prefix for workspace name
|
|
99
|
+
* @returns Test context with workspace, keys, and cleanup function
|
|
100
|
+
*
|
|
101
|
+
* @example
|
|
102
|
+
* ```ts
|
|
103
|
+
* describe('My tests', () => {
|
|
104
|
+
* let ctx: TestContext;
|
|
105
|
+
*
|
|
106
|
+
* beforeAll(async () => {
|
|
107
|
+
* ctx = await createTestContext();
|
|
108
|
+
* });
|
|
109
|
+
*
|
|
110
|
+
* afterAll(async () => {
|
|
111
|
+
* await ctx.cleanup();
|
|
112
|
+
* });
|
|
113
|
+
*
|
|
114
|
+
* it('should work', async () => {
|
|
115
|
+
* const response = await fetch(`${getTestApiUrl()}/contacts`, {
|
|
116
|
+
* headers: { Authorization: `Bearer ${ctx.secretKey}` }
|
|
117
|
+
* });
|
|
118
|
+
* });
|
|
119
|
+
* });
|
|
120
|
+
* ```
|
|
121
|
+
*/
|
|
122
|
+
export declare function createTestContext(namePrefix?: string): Promise<TestContext>;
|
|
123
|
+
/**
|
|
124
|
+
* Make an authenticated API request.
|
|
125
|
+
*
|
|
126
|
+
* @param method - HTTP method
|
|
127
|
+
* @param path - API path (without base URL)
|
|
128
|
+
* @param apiKey - API key for authentication
|
|
129
|
+
* @param body - Optional request body
|
|
130
|
+
* @returns Fetch response
|
|
131
|
+
*/
|
|
132
|
+
export declare function apiRequest(method: string, path: string, apiKey: string, body?: unknown): Promise<Response>;
|
|
133
|
+
/**
|
|
134
|
+
* Parse JSON response body.
|
|
135
|
+
* Handles non-JSON responses gracefully.
|
|
136
|
+
*
|
|
137
|
+
* @param response - Fetch response
|
|
138
|
+
* @returns Parsed JSON or null if not JSON
|
|
139
|
+
*/
|
|
140
|
+
export declare function parseJsonResponse<T = unknown>(response: Response): Promise<T | null>;
|
|
141
|
+
/**
|
|
142
|
+
* Wait for a condition to be true, with timeout.
|
|
143
|
+
* Useful for testing async operations.
|
|
144
|
+
*
|
|
145
|
+
* @param condition - Async function returning boolean
|
|
146
|
+
* @param options - Timeout and polling interval
|
|
147
|
+
* @throws Error if condition not met within timeout
|
|
148
|
+
*/
|
|
149
|
+
export declare function waitFor(condition: () => Promise<boolean>, options?: {
|
|
150
|
+
timeout?: number;
|
|
151
|
+
interval?: number;
|
|
152
|
+
}): Promise<void>;
|
|
153
|
+
/**
|
|
154
|
+
* Sleep for a specified duration.
|
|
155
|
+
*
|
|
156
|
+
* @param ms - Milliseconds to sleep
|
|
157
|
+
*/
|
|
158
|
+
export declare function sleep(ms: number): Promise<void>;
|
|
159
|
+
/**
|
|
160
|
+
* Get a record by ID from any table.
|
|
161
|
+
*
|
|
162
|
+
* @param table - Table name
|
|
163
|
+
* @param id - Record ID
|
|
164
|
+
* @param workspaceId - Optional workspace ID for scoped queries
|
|
165
|
+
* @returns Record or null if not found
|
|
166
|
+
*/
|
|
167
|
+
export declare function getById<T = unknown>(table: string, id: string, workspaceId?: string): Promise<T | null>;
|
|
168
|
+
/**
|
|
169
|
+
* Count records in a table within a workspace.
|
|
170
|
+
*
|
|
171
|
+
* @param table - Table name
|
|
172
|
+
* @param workspaceId - Workspace ID
|
|
173
|
+
* @returns Record count
|
|
174
|
+
*/
|
|
175
|
+
export declare function countInWorkspace(table: string, workspaceId: string): Promise<number>;
|
|
176
|
+
//# sourceMappingURL=setup.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../../../src/__tests__/integration-api/setup.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Integration test setup for MCP API tests.
|
|
3
|
+
*
|
|
4
|
+
* Provides utilities for testing MCP tools against the real Kelpi HTTP API.
|
|
5
|
+
* These tests require both local Supabase and Next.js server running.
|
|
6
|
+
*
|
|
7
|
+
* CRITICAL: Tests only run against localhost to prevent accidents with prod/staging.
|
|
8
|
+
*/
|
|
9
|
+
import { createClient } from '@supabase/supabase-js';
|
|
10
|
+
import crypto from 'crypto';
|
|
11
|
+
// =============================================================================
|
|
12
|
+
// Environment validation - fail fast if misconfigured
|
|
13
|
+
// =============================================================================
|
|
14
|
+
const SUPABASE_URL = process.env.NEXT_PUBLIC_SUPABASE_URL;
|
|
15
|
+
const SUPABASE_SERVICE_KEY = process.env.SUPABASE_SERVICE_ROLE_KEY;
|
|
16
|
+
const API_BASE_URL = process.env.TEST_API_URL || 'http://localhost:3000/api/v1';
|
|
17
|
+
/**
|
|
18
|
+
* Validate that we're running against localhost.
|
|
19
|
+
* CRITICAL safety feature to prevent accidental production/staging data access.
|
|
20
|
+
*
|
|
21
|
+
* @throws Error if Supabase URL or API URL is not localhost
|
|
22
|
+
*/
|
|
23
|
+
export function validateLocalhost() {
|
|
24
|
+
// Validate Supabase URL
|
|
25
|
+
if (!SUPABASE_URL) {
|
|
26
|
+
throw new Error('NEXT_PUBLIC_SUPABASE_URL must be set for integration tests.\n' +
|
|
27
|
+
'Make sure you have a .env or .env.local file with this variable.');
|
|
28
|
+
}
|
|
29
|
+
if (!SUPABASE_URL.includes('127.0.0.1') && !SUPABASE_URL.includes('localhost')) {
|
|
30
|
+
throw new Error('CRITICAL: Integration tests can only run against local Supabase.\n' +
|
|
31
|
+
`Got: ${SUPABASE_URL}\n\n` +
|
|
32
|
+
'This safety check prevents accidental modification of production or staging data.\n' +
|
|
33
|
+
'To run integration tests:\n' +
|
|
34
|
+
' 1. Start local Supabase: pnpm supabase:start\n' +
|
|
35
|
+
' 2. Ensure NEXT_PUBLIC_SUPABASE_URL points to localhost');
|
|
36
|
+
}
|
|
37
|
+
// Validate API URL
|
|
38
|
+
const apiUrl = new URL(API_BASE_URL);
|
|
39
|
+
if (apiUrl.hostname !== 'localhost' && apiUrl.hostname !== '127.0.0.1') {
|
|
40
|
+
throw new Error('CRITICAL: Integration tests can only run against local API server.\n' +
|
|
41
|
+
`Got: ${API_BASE_URL}\n\n` +
|
|
42
|
+
'Set TEST_API_URL to a localhost URL or leave unset to use default.');
|
|
43
|
+
}
|
|
44
|
+
// Validate service key exists
|
|
45
|
+
if (!SUPABASE_SERVICE_KEY) {
|
|
46
|
+
throw new Error('SUPABASE_SERVICE_ROLE_KEY must be set for integration tests.\n' +
|
|
47
|
+
'This key is needed to bypass RLS for test setup/teardown.');
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
// Run validation immediately when this module is imported
|
|
51
|
+
validateLocalhost();
|
|
52
|
+
// =============================================================================
|
|
53
|
+
// Supabase client singleton
|
|
54
|
+
// =============================================================================
|
|
55
|
+
let supabaseClient = null;
|
|
56
|
+
/**
|
|
57
|
+
* Get the Supabase client for integration tests.
|
|
58
|
+
* Uses service role key to bypass RLS for test setup/teardown.
|
|
59
|
+
*
|
|
60
|
+
* @returns Supabase client instance
|
|
61
|
+
*/
|
|
62
|
+
export function getTestSupabase() {
|
|
63
|
+
if (!supabaseClient) {
|
|
64
|
+
supabaseClient = createClient(SUPABASE_URL, SUPABASE_SERVICE_KEY);
|
|
65
|
+
}
|
|
66
|
+
return supabaseClient;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Get the API base URL for tests.
|
|
70
|
+
*
|
|
71
|
+
* @returns API base URL (default: http://localhost:3000/api/v1)
|
|
72
|
+
*/
|
|
73
|
+
export function getTestApiUrl() {
|
|
74
|
+
return API_BASE_URL;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Create a test workspace with a unique name.
|
|
78
|
+
*
|
|
79
|
+
* @param namePrefix - Optional prefix for workspace name (default: 'MCP Test')
|
|
80
|
+
* @returns Created workspace
|
|
81
|
+
*/
|
|
82
|
+
export async function createTestWorkspace(namePrefix = 'MCP Test') {
|
|
83
|
+
const supabase = getTestSupabase();
|
|
84
|
+
const uniqueName = `${namePrefix} ${Date.now()}`;
|
|
85
|
+
const { data, error } = await supabase
|
|
86
|
+
.from('workspaces')
|
|
87
|
+
.insert({ name: uniqueName, user_id: null })
|
|
88
|
+
.select('id, name, created_at')
|
|
89
|
+
.single();
|
|
90
|
+
if (error) {
|
|
91
|
+
throw new Error(`Failed to create test workspace: ${error.message}`);
|
|
92
|
+
}
|
|
93
|
+
return data;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Delete a workspace and all related data (cascade).
|
|
97
|
+
* Safe to call even if workspace doesn't exist.
|
|
98
|
+
*
|
|
99
|
+
* @param workspaceId - ID of workspace to delete
|
|
100
|
+
*/
|
|
101
|
+
export async function cleanupWorkspace(workspaceId) {
|
|
102
|
+
const supabase = getTestSupabase();
|
|
103
|
+
const { error } = await supabase.from('workspaces').delete().eq('id', workspaceId);
|
|
104
|
+
if (error) {
|
|
105
|
+
// Log but don't throw - cleanup errors shouldn't fail tests
|
|
106
|
+
console.error(`Warning: Failed to cleanup workspace ${workspaceId}: ${error.message}`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
// =============================================================================
|
|
110
|
+
// API key management
|
|
111
|
+
// =============================================================================
|
|
112
|
+
/**
|
|
113
|
+
* API key type constants
|
|
114
|
+
*/
|
|
115
|
+
export const KeyType = {
|
|
116
|
+
PUBLIC: 'public',
|
|
117
|
+
SECRET: 'secret',
|
|
118
|
+
};
|
|
119
|
+
/**
|
|
120
|
+
* Hash an API key using SHA-256.
|
|
121
|
+
* Matches production hashing algorithm.
|
|
122
|
+
*/
|
|
123
|
+
function hashApiKey(key) {
|
|
124
|
+
return crypto.createHash('sha256').update(key).digest('hex');
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Generate an API key with proper format and hash.
|
|
128
|
+
* Matches production key generation.
|
|
129
|
+
*
|
|
130
|
+
* @param type - Key type: 'public' (klp_pk_*) or 'secret' (klp_sk_*)
|
|
131
|
+
* @param orgSlug - Optional org slug to embed in key
|
|
132
|
+
* @returns Generated key with plaintext, prefix, and hash
|
|
133
|
+
*/
|
|
134
|
+
function generateApiKey(type, orgSlug) {
|
|
135
|
+
const keyPrefix = type === KeyType.PUBLIC ? 'klp_pk' : 'klp_sk';
|
|
136
|
+
const random = crypto.randomBytes(24).toString('base64url');
|
|
137
|
+
const sanitizedOrgSlug = orgSlug
|
|
138
|
+
? orgSlug
|
|
139
|
+
.toLowerCase()
|
|
140
|
+
.replace(/[^a-z0-9]/g, '')
|
|
141
|
+
.slice(0, 8)
|
|
142
|
+
: '';
|
|
143
|
+
const plaintext = sanitizedOrgSlug ? `${keyPrefix}_${sanitizedOrgSlug}_${random}` : `${keyPrefix}_${random}`;
|
|
144
|
+
const hash = hashApiKey(plaintext);
|
|
145
|
+
const prefixEnd = sanitizedOrgSlug ? `${keyPrefix}_${sanitizedOrgSlug}_`.length + 4 : 16;
|
|
146
|
+
const displayPrefix = plaintext.substring(0, prefixEnd) + '...';
|
|
147
|
+
return { plaintext, prefix: displayPrefix, hash };
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Create a test API key for a workspace.
|
|
151
|
+
* Returns the raw (unhashed) key for use in API requests.
|
|
152
|
+
*
|
|
153
|
+
* @param workspaceId - Workspace to create key for
|
|
154
|
+
* @param options - Key options (type, name, revoked)
|
|
155
|
+
* @returns Raw API key string
|
|
156
|
+
*/
|
|
157
|
+
export async function createTestApiKey(workspaceId, options = {}) {
|
|
158
|
+
const supabase = getTestSupabase();
|
|
159
|
+
const keyType = options.keyType ?? KeyType.SECRET;
|
|
160
|
+
const { plaintext, prefix, hash } = generateApiKey(keyType, 'test');
|
|
161
|
+
// Default scopes based on key type
|
|
162
|
+
const scopes = keyType === KeyType.PUBLIC ? ['track'] : ['read', 'write', 'track'];
|
|
163
|
+
const { error } = await supabase.from('api_keys').insert({
|
|
164
|
+
workspace_id: workspaceId,
|
|
165
|
+
key_hash: hash,
|
|
166
|
+
key_prefix: prefix,
|
|
167
|
+
key_type: keyType,
|
|
168
|
+
scopes,
|
|
169
|
+
name: options.name ?? `Test ${keyType} API Key`,
|
|
170
|
+
revoked_at: options.revoked ? new Date().toISOString() : null,
|
|
171
|
+
});
|
|
172
|
+
if (error) {
|
|
173
|
+
throw new Error(`Failed to create test API key: ${error.message}`);
|
|
174
|
+
}
|
|
175
|
+
return plaintext;
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Create a complete test context with workspace and both API key types.
|
|
179
|
+
* Call cleanup() in afterAll/afterEach to clean up resources.
|
|
180
|
+
*
|
|
181
|
+
* @param namePrefix - Optional prefix for workspace name
|
|
182
|
+
* @returns Test context with workspace, keys, and cleanup function
|
|
183
|
+
*
|
|
184
|
+
* @example
|
|
185
|
+
* ```ts
|
|
186
|
+
* describe('My tests', () => {
|
|
187
|
+
* let ctx: TestContext;
|
|
188
|
+
*
|
|
189
|
+
* beforeAll(async () => {
|
|
190
|
+
* ctx = await createTestContext();
|
|
191
|
+
* });
|
|
192
|
+
*
|
|
193
|
+
* afterAll(async () => {
|
|
194
|
+
* await ctx.cleanup();
|
|
195
|
+
* });
|
|
196
|
+
*
|
|
197
|
+
* it('should work', async () => {
|
|
198
|
+
* const response = await fetch(`${getTestApiUrl()}/contacts`, {
|
|
199
|
+
* headers: { Authorization: `Bearer ${ctx.secretKey}` }
|
|
200
|
+
* });
|
|
201
|
+
* });
|
|
202
|
+
* });
|
|
203
|
+
* ```
|
|
204
|
+
*/
|
|
205
|
+
export async function createTestContext(namePrefix = 'MCP Test') {
|
|
206
|
+
const workspace = await createTestWorkspace(namePrefix);
|
|
207
|
+
const [secretKey, publicKey] = await Promise.all([
|
|
208
|
+
createTestApiKey(workspace.id, { keyType: KeyType.SECRET }),
|
|
209
|
+
createTestApiKey(workspace.id, { keyType: KeyType.PUBLIC }),
|
|
210
|
+
]);
|
|
211
|
+
return {
|
|
212
|
+
workspace,
|
|
213
|
+
secretKey,
|
|
214
|
+
publicKey,
|
|
215
|
+
cleanup: async () => {
|
|
216
|
+
await cleanupWorkspace(workspace.id);
|
|
217
|
+
},
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
// =============================================================================
|
|
221
|
+
// API request helpers
|
|
222
|
+
// =============================================================================
|
|
223
|
+
/**
|
|
224
|
+
* Make an authenticated API request.
|
|
225
|
+
*
|
|
226
|
+
* @param method - HTTP method
|
|
227
|
+
* @param path - API path (without base URL)
|
|
228
|
+
* @param apiKey - API key for authentication
|
|
229
|
+
* @param body - Optional request body
|
|
230
|
+
* @returns Fetch response
|
|
231
|
+
*/
|
|
232
|
+
export async function apiRequest(method, path, apiKey, body) {
|
|
233
|
+
const url = `${API_BASE_URL}${path}`;
|
|
234
|
+
return fetch(url, {
|
|
235
|
+
method,
|
|
236
|
+
headers: {
|
|
237
|
+
'Content-Type': 'application/json',
|
|
238
|
+
Authorization: `Bearer ${apiKey}`,
|
|
239
|
+
},
|
|
240
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Parse JSON response body.
|
|
245
|
+
* Handles non-JSON responses gracefully.
|
|
246
|
+
*
|
|
247
|
+
* @param response - Fetch response
|
|
248
|
+
* @returns Parsed JSON or null if not JSON
|
|
249
|
+
*/
|
|
250
|
+
export async function parseJsonResponse(response) {
|
|
251
|
+
try {
|
|
252
|
+
return (await response.json());
|
|
253
|
+
}
|
|
254
|
+
catch {
|
|
255
|
+
return null;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
// =============================================================================
|
|
259
|
+
// Wait utilities
|
|
260
|
+
// =============================================================================
|
|
261
|
+
/**
|
|
262
|
+
* Wait for a condition to be true, with timeout.
|
|
263
|
+
* Useful for testing async operations.
|
|
264
|
+
*
|
|
265
|
+
* @param condition - Async function returning boolean
|
|
266
|
+
* @param options - Timeout and polling interval
|
|
267
|
+
* @throws Error if condition not met within timeout
|
|
268
|
+
*/
|
|
269
|
+
export async function waitFor(condition, options = {}) {
|
|
270
|
+
const { timeout = 5000, interval = 100 } = options;
|
|
271
|
+
const start = Date.now();
|
|
272
|
+
while (Date.now() - start < timeout) {
|
|
273
|
+
if (await condition()) {
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
await new Promise((resolve) => setTimeout(resolve, interval));
|
|
277
|
+
}
|
|
278
|
+
throw new Error(`waitFor timed out after ${timeout}ms`);
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Sleep for a specified duration.
|
|
282
|
+
*
|
|
283
|
+
* @param ms - Milliseconds to sleep
|
|
284
|
+
*/
|
|
285
|
+
export function sleep(ms) {
|
|
286
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
287
|
+
}
|
|
288
|
+
// =============================================================================
|
|
289
|
+
// Database query helpers
|
|
290
|
+
// =============================================================================
|
|
291
|
+
/**
|
|
292
|
+
* Get a record by ID from any table.
|
|
293
|
+
*
|
|
294
|
+
* @param table - Table name
|
|
295
|
+
* @param id - Record ID
|
|
296
|
+
* @param workspaceId - Optional workspace ID for scoped queries
|
|
297
|
+
* @returns Record or null if not found
|
|
298
|
+
*/
|
|
299
|
+
export async function getById(table, id, workspaceId) {
|
|
300
|
+
const supabase = getTestSupabase();
|
|
301
|
+
let query = supabase.from(table).select('*').eq('id', id);
|
|
302
|
+
if (workspaceId) {
|
|
303
|
+
query = query.eq('workspace_id', workspaceId);
|
|
304
|
+
}
|
|
305
|
+
const { data, error } = await query.single();
|
|
306
|
+
if (error && error.code !== 'PGRST116') {
|
|
307
|
+
throw new Error(`Failed to get ${table} by ID: ${error.message}`);
|
|
308
|
+
}
|
|
309
|
+
return data;
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Count records in a table within a workspace.
|
|
313
|
+
*
|
|
314
|
+
* @param table - Table name
|
|
315
|
+
* @param workspaceId - Workspace ID
|
|
316
|
+
* @returns Record count
|
|
317
|
+
*/
|
|
318
|
+
export async function countInWorkspace(table, workspaceId) {
|
|
319
|
+
const supabase = getTestSupabase();
|
|
320
|
+
const { count, error } = await supabase
|
|
321
|
+
.from(table)
|
|
322
|
+
.select('*', { count: 'exact', head: true })
|
|
323
|
+
.eq('workspace_id', workspaceId);
|
|
324
|
+
if (error) {
|
|
325
|
+
throw new Error(`Failed to count ${table}: ${error.message}`);
|
|
326
|
+
}
|
|
327
|
+
return count ?? 0;
|
|
328
|
+
}
|
|
329
|
+
//# sourceMappingURL=setup.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setup.js","sourceRoot":"","sources":["../../../src/__tests__/integration-api/setup.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../../src/__tests__/setup.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
beforeEach(() => {
|
|
3
|
+
vi.clearAllMocks();
|
|
4
|
+
});
|
|
5
|
+
afterEach(() => {
|
|
6
|
+
// Clean up any environment variables set during tests
|
|
7
|
+
delete process.env.KELPI_API_KEY;
|
|
8
|
+
delete process.env.KELPI_API_URL;
|
|
9
|
+
delete process.env.KELPI_CONFIG_PATH;
|
|
10
|
+
});
|
|
11
|
+
//# sourceMappingURL=setup.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setup.js","sourceRoot":"","sources":["../../src/__tests__/setup.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AAEnD,UAAU,CAAC,GAAG,EAAE;IACd,EAAE,CAAC,aAAa,EAAE,CAAC;AACrB,CAAC,CAAC,CAAC;AAEH,SAAS,CAAC,GAAG,EAAE;IACb,sDAAsD;IACtD,OAAO,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC;IACjC,OAAO,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC;IACjC,OAAO,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC;AACvC,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { vi } from 'vitest';
|
|
2
|
+
/**
|
|
3
|
+
* Type for mocked API client methods
|
|
4
|
+
*/
|
|
5
|
+
export type MockApiClient = {
|
|
6
|
+
get: ReturnType<typeof vi.fn>;
|
|
7
|
+
post: ReturnType<typeof vi.fn>;
|
|
8
|
+
put: ReturnType<typeof vi.fn>;
|
|
9
|
+
patch: ReturnType<typeof vi.fn>;
|
|
10
|
+
delete: ReturnType<typeof vi.fn>;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Creates a fresh mock API client with all methods as vi.fn()
|
|
14
|
+
*/
|
|
15
|
+
export declare function createMockApiClient(): MockApiClient;
|
|
16
|
+
/**
|
|
17
|
+
* Standard config path for tests
|
|
18
|
+
*/
|
|
19
|
+
export declare const CONFIG_PATH: string;
|
|
20
|
+
/**
|
|
21
|
+
* Clears all Kelpi-related environment variables
|
|
22
|
+
*/
|
|
23
|
+
export declare function clearKelpiEnv(): void;
|
|
24
|
+
/**
|
|
25
|
+
* Sets up a mock config file with the given API key
|
|
26
|
+
*/
|
|
27
|
+
export declare function setupMockConfig(apiKey: string, extraConfig?: Record<string, unknown>): void;
|
|
28
|
+
/**
|
|
29
|
+
* Parses the text content from a tool result as JSON
|
|
30
|
+
*/
|
|
31
|
+
export declare function parseToolResultJson<T = unknown>(result: {
|
|
32
|
+
content: Array<{
|
|
33
|
+
type: string;
|
|
34
|
+
text?: string;
|
|
35
|
+
}>;
|
|
36
|
+
}): T;
|
|
37
|
+
/**
|
|
38
|
+
* Gets the raw text content from a tool result
|
|
39
|
+
*/
|
|
40
|
+
export declare function getToolResultText(result: {
|
|
41
|
+
content: Array<{
|
|
42
|
+
type: string;
|
|
43
|
+
text?: string;
|
|
44
|
+
}>;
|
|
45
|
+
}): string;
|
|
46
|
+
//# sourceMappingURL=test-utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"test-utils.d.ts","sourceRoot":"","sources":["../../../src/__tests__/unit/test-utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAK5B;;GAEG;AACH,MAAM,MAAM,aAAa,GAAG;IAC1B,GAAG,EAAE,UAAU,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC,CAAC;IAC9B,IAAI,EAAE,UAAU,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC,CAAC;IAC/B,GAAG,EAAE,UAAU,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC,CAAC;IAC9B,KAAK,EAAE,UAAU,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC,CAAC;IAChC,MAAM,EAAE,UAAU,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC,CAAC;CAClC,CAAC;AAEF;;GAEG;AACH,wBAAgB,mBAAmB,IAAI,aAAa,CAQnD;AAED;;GAEG;AACH,eAAO,MAAM,WAAW,QAAgD,CAAC;AAEzE;;GAEG;AACH,wBAAgB,aAAa,IAAI,IAAI,CAIpC;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GAAG,IAAI,CAI/F;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,CAAC,GAAG,OAAO,EAAE,MAAM,EAAE;IACvD,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACjD,GAAG,CAAC,CAGJ;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE;IACxC,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACjD,GAAG,MAAM,CAET"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { vi } from 'vitest';
|
|
2
|
+
import { vol } from 'memfs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { homedir } from 'os';
|
|
5
|
+
/**
|
|
6
|
+
* Creates a fresh mock API client with all methods as vi.fn()
|
|
7
|
+
*/
|
|
8
|
+
export function createMockApiClient() {
|
|
9
|
+
return {
|
|
10
|
+
get: vi.fn(),
|
|
11
|
+
post: vi.fn(),
|
|
12
|
+
put: vi.fn(),
|
|
13
|
+
patch: vi.fn(),
|
|
14
|
+
delete: vi.fn(),
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Standard config path for tests
|
|
19
|
+
*/
|
|
20
|
+
export const CONFIG_PATH = path.join(homedir(), '.kelpi', 'config.json');
|
|
21
|
+
/**
|
|
22
|
+
* Clears all Kelpi-related environment variables
|
|
23
|
+
*/
|
|
24
|
+
export function clearKelpiEnv() {
|
|
25
|
+
delete process.env.KELPI_API_KEY;
|
|
26
|
+
delete process.env.KELPI_API_URL;
|
|
27
|
+
delete process.env.KELPI_CONFIG_PATH;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Sets up a mock config file with the given API key
|
|
31
|
+
*/
|
|
32
|
+
export function setupMockConfig(apiKey, extraConfig = {}) {
|
|
33
|
+
vol.fromJSON({
|
|
34
|
+
[CONFIG_PATH]: JSON.stringify({ api_key: apiKey, ...extraConfig }),
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Parses the text content from a tool result as JSON
|
|
39
|
+
*/
|
|
40
|
+
export function parseToolResultJson(result) {
|
|
41
|
+
const textContent = result.content[0]?.text ?? '';
|
|
42
|
+
return JSON.parse(textContent);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Gets the raw text content from a tool result
|
|
46
|
+
*/
|
|
47
|
+
export function getToolResultText(result) {
|
|
48
|
+
return result.content[0]?.text ?? '';
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=test-utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"test-utils.js","sourceRoot":"","sources":["../../../src/__tests__/unit/test-utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAC5B,OAAO,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC;AAC5B,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,OAAO,EAAE,MAAM,IAAI,CAAC;AAa7B;;GAEG;AACH,MAAM,UAAU,mBAAmB;IACjC,OAAO;QACL,GAAG,EAAE,EAAE,CAAC,EAAE,EAAE;QACZ,IAAI,EAAE,EAAE,CAAC,EAAE,EAAE;QACb,GAAG,EAAE,EAAE,CAAC,EAAE,EAAE;QACZ,KAAK,EAAE,EAAE,CAAC,EAAE,EAAE;QACd,MAAM,EAAE,EAAE,CAAC,EAAE,EAAE;KAChB,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,aAAa,CAAC,CAAC;AAEzE;;GAEG;AACH,MAAM,UAAU,aAAa;IAC3B,OAAO,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC;IACjC,OAAO,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC;IACjC,OAAO,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC;AACvC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,eAAe,CAAC,MAAc,EAAE,cAAuC,EAAE;IACvF,GAAG,CAAC,QAAQ,CAAC;QACX,CAAC,WAAW,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,WAAW,EAAE,CAAC;KACnE,CAAC,CAAC;AACL,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,mBAAmB,CAAc,MAEhD;IACC,MAAM,WAAW,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,IAAI,IAAI,EAAE,CAAC;IAClD,OAAO,IAAI,CAAC,KAAK,CAAC,WAAW,CAAM,CAAC;AACtC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,iBAAiB,CAAC,MAEjC;IACC,OAAO,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,IAAI,IAAI,EAAE,CAAC;AACvC,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
export { KelpiMcpServer, createServer, type ServerOptions } from './server.js';
|
|
3
|
+
export { ToolRegistry, ToolRegistryError } from './lib/tool-registry.js';
|
|
4
|
+
export type { ToolDefinition, ToolHandler, ToolResult, McpTool, RegisteredTool, ValidationResult, } from './lib/tool-registry.js';
|
|
5
|
+
export { ALL_TOOLS, TOOL_DEFINITIONS, TOOL_NAMES, type ToolWithHandler, } from './tools/index.js';
|
|
6
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAoCA,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AAC/E,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AACzE,YAAY,EACV,cAAc,EACd,WAAW,EACX,UAAU,EACV,OAAO,EACP,cAAc,EACd,gBAAgB,GACjB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EACL,SAAS,EACT,gBAAgB,EAChB,UAAU,EACV,KAAK,eAAe,GACrB,MAAM,kBAAkB,CAAC"}
|