agentic-astra-catalog 0.0.1

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.
@@ -0,0 +1,75 @@
1
+ import { Tool } from '@/lib/astraClient';
2
+
3
+ interface ToolListProps {
4
+ tools: Tool[];
5
+ selectedTool: Tool | null;
6
+ onSelectTool: (tool: Tool) => void;
7
+ onNewTool: () => void;
8
+ }
9
+
10
+ export default function ToolList({ tools, selectedTool, onSelectTool, onNewTool }: ToolListProps) {
11
+ return (
12
+ <div className="w-80 border-r border-gray-300 dark:border-gray-700 bg-gray-50 dark:bg-gray-900 h-screen overflow-y-auto">
13
+ <div className="p-4 border-b border-gray-300 dark:border-gray-700">
14
+ <div className="flex justify-between items-center mb-2">
15
+ <h2 className="text-xl font-semibold text-gray-800 dark:text-gray-200">Tools</h2>
16
+ </div>
17
+ <p className="text-sm text-gray-600 dark:text-gray-400 mb-3">{tools.length} tool{tools.length !== 1 ? 's' : ''}</p>
18
+ <button
19
+ onClick={onNewTool}
20
+ className="w-full px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors text-sm font-medium"
21
+ >
22
+ + New Tool
23
+ </button>
24
+ </div>
25
+ <div className="p-2">
26
+ {tools.length === 0 ? (
27
+ <div className="p-4 text-center text-gray-500 dark:text-gray-400">
28
+ No tools found
29
+ </div>
30
+ ) : (
31
+ <div className="space-y-1">
32
+ {tools.map((tool) => (
33
+ <button
34
+ key={tool._id || tool.name}
35
+ onClick={() => onSelectTool(tool)}
36
+ className={`w-full text-left p-3 rounded-lg transition-colors ${
37
+ selectedTool?._id === tool._id || selectedTool?.name === tool.name
38
+ ? 'bg-blue-100 dark:bg-blue-900 text-blue-900 dark:text-blue-100'
39
+ : 'bg-white dark:bg-gray-800 text-gray-800 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700'
40
+ } ${tool.enabled === false ? 'opacity-60' : ''}`}
41
+ >
42
+ <div className="flex items-center justify-between">
43
+ <div className="font-medium">{tool.name}</div>
44
+ {tool.enabled === false && (
45
+ <span className="text-xs px-2 py-0.5 rounded bg-red-100 dark:bg-red-900 text-red-700 dark:text-red-300">
46
+ Disabled
47
+ </span>
48
+ )}
49
+ </div>
50
+ {tool.description && (
51
+ <div className="text-sm text-gray-600 dark:text-gray-400 mt-1 line-clamp-2">
52
+ {tool.description}
53
+ </div>
54
+ )}
55
+ {tool.tags && tool.tags.length > 0 && (
56
+ <div className="flex flex-wrap gap-1 mt-2">
57
+ {tool.tags.map((tag, idx) => (
58
+ <span
59
+ key={idx}
60
+ className="text-xs px-2 py-0.5 rounded bg-gray-200 dark:bg-gray-700 text-gray-700 dark:text-gray-300"
61
+ >
62
+ {tag}
63
+ </span>
64
+ ))}
65
+ </div>
66
+ )}
67
+ </button>
68
+ ))}
69
+ </div>
70
+ )}
71
+ </div>
72
+ </div>
73
+ );
74
+ }
75
+
@@ -0,0 +1,264 @@
1
+ import { DataAPIClient } from '@datastax/astra-db-ts';
2
+
3
+ export interface ToolParameter {
4
+ param: string;
5
+ description?: string;
6
+ attribute?: string;
7
+ type?: string;
8
+ required?: boolean | number;
9
+ operator?: string;
10
+ expr?: string;
11
+ enum?: string[];
12
+ embedding_model?: string;
13
+ info?: string;
14
+ value?: any;
15
+ paramMode?: 'tool_param' | 'static' | 'expression';
16
+ }
17
+
18
+ export interface Tool {
19
+ _id?: string;
20
+ tags?: string[];
21
+ type: string;
22
+ name: string;
23
+ description?: string;
24
+ limit?: number;
25
+ method?: string;
26
+ collection_name?: string;
27
+ table_name?: string;
28
+ db_name?: string;
29
+ projection?: Record<string, number | string>;
30
+ parameters?: ToolParameter[];
31
+ enabled?: boolean;
32
+ }
33
+
34
+ class AstraClient {
35
+ private client: DataAPIClient | null = null;
36
+ private db: any = null;
37
+ private collection: any = null;
38
+ private token: string | null = null;
39
+ private endpoint: string | null = null;
40
+ private dbName: string | null = null;
41
+ private collectionName: string = 'tool_catalog';
42
+
43
+ async connect(): Promise<void> {
44
+ // Access server-side environment variables
45
+ this.token = process.env.ASTRA_DB_APPLICATION_TOKEN || '';
46
+ this.endpoint = process.env.ASTRA_DB_API_ENDPOINT || '';
47
+ this.dbName = process.env.ASTRA_DB_DB_NAME || '';
48
+ this.collectionName = process.env.ASTRA_DB_CATALOG_COLLECTION || 'tool_catalog';
49
+
50
+ if (!this.token || !this.endpoint || !this.dbName) {
51
+ throw new Error('Missing required environment variables: ASTRA_DB_APPLICATION_TOKEN, ASTRA_DB_API_ENDPOINT, and ASTRA_DB_DB_NAME are required');
52
+ }
53
+
54
+ try {
55
+ // Initialize the DataAPIClient with the token
56
+ this.client = new DataAPIClient(this.token);
57
+
58
+ // Get the database using the endpoint and token
59
+ // The endpoint format: https://<database-id>-<region>.apps.astra.datastax.com
60
+ this.db = this.client.db(this.endpoint, { token: this.token });
61
+
62
+ // Get the collection
63
+ this.collection = this.db.collection(this.collectionName);
64
+ } catch (error) {
65
+ throw new Error(`Failed to connect to Astra DB: ${error instanceof Error ? error.message : String(error)}`);
66
+ }
67
+ }
68
+
69
+ async getTools(): Promise<Tool[]> {
70
+ if (!this.collection) {
71
+ await this.connect();
72
+ }
73
+
74
+ try {
75
+ // Use find() with filter, which returns a cursor
76
+ const cursor = this.collection.find({ type: 'tool' });
77
+
78
+ // Convert cursor to array
79
+ const tools: Tool[] = [];
80
+ for await (const doc of cursor) {
81
+ tools.push(doc as Tool);
82
+ }
83
+
84
+ return tools;
85
+ } catch (error) {
86
+ throw new Error(`Failed to fetch tools: ${error instanceof Error ? error.message : String(error)}`);
87
+ }
88
+ }
89
+
90
+ async updateTool(tool: Tool): Promise<void> {
91
+ if (!this.collection) {
92
+ await this.connect();
93
+ }
94
+
95
+ try {
96
+ if (tool._id) {
97
+ // Update existing document
98
+ tool.enabled = tool.enabled === false ? false : true;
99
+ const _id = tool._id;
100
+ delete tool._id;
101
+ await this.collection.updateOne(
102
+ { _id: _id },
103
+ { $set: tool }
104
+ );
105
+ } else {
106
+ // Insert new document
107
+ await this.collection.insertOne({ document: tool });
108
+ }
109
+ } catch (error) {
110
+ throw new Error(`Failed to update tool: ${error instanceof Error ? error.message : String(error)}`);
111
+ }
112
+ }
113
+
114
+ async getSampleDocuments(tool: Tool, limit: number = 5): Promise<any[]> {
115
+ if (!this.db) {
116
+ await this.connect();
117
+ }
118
+
119
+ try {
120
+ const objectType = tool.collection_name ? 'collection' : 'table';
121
+ const objectName = tool.collection_name || tool.table_name;
122
+ const dbName = tool.db_name || this.dbName;
123
+
124
+ if (!objectName || !dbName) {
125
+ throw new Error('Collection/table name and database name are required');
126
+ }
127
+
128
+ // Get the target database
129
+ const targetDb = this.client!.db(this.endpoint!, { token: this.token });
130
+
131
+ let targetObject: any;
132
+ if (objectType === 'collection') {
133
+ targetObject = targetDb.collection(objectName);
134
+ } else {
135
+ targetObject = targetDb.table(objectName);
136
+ }
137
+
138
+ // Fetch sample documents
139
+ const cursor = targetObject.find({}).limit(limit);
140
+ const documents: any[] = [];
141
+ for await (const doc of cursor) {
142
+ documents.push(doc);
143
+ }
144
+
145
+ return documents;
146
+ } catch (error) {
147
+ throw new Error(`Failed to fetch sample documents: ${error instanceof Error ? error.message : String(error)}`);
148
+ }
149
+ }
150
+
151
+ async listKeyspaces(): Promise<string[]> {
152
+ if (!this.client) {
153
+ await this.connect();
154
+ }
155
+
156
+ try {
157
+ // Get list of databases (keyspaces)
158
+ const admin = this.client!.admin();
159
+ const databases = await admin.listDatabases();
160
+ return databases.map((db: any) => db.name || db.id).filter(Boolean);
161
+ } catch (error) {
162
+ throw new Error(`Failed to list keyspaces: ${error instanceof Error ? error.message : String(error)}`);
163
+ }
164
+ }
165
+
166
+ async listCollections(dbName?: string): Promise<string[]> {
167
+ if (!this.db) {
168
+ await this.connect();
169
+ }
170
+
171
+ try {
172
+ const targetDbName = dbName || this.dbName;
173
+ if (!targetDbName) {
174
+ throw new Error('Database name is required');
175
+ }
176
+
177
+ // Get the database
178
+ const targetDb = this.client!.db(this.endpoint!, { token: this.token });
179
+
180
+ // List collections
181
+ const collections = await targetDb.listCollections();
182
+ return collections.map((col: any) => col.name || col);
183
+ } catch (error) {
184
+ throw new Error(`Failed to list collections: ${error instanceof Error ? error.message : String(error)}`);
185
+ }
186
+ }
187
+
188
+ async listTables(dbName?: string): Promise<string[]> {
189
+ if (!this.db) {
190
+ await this.connect();
191
+ }
192
+
193
+ try {
194
+ const targetDbName = dbName || this.dbName;
195
+ if (!targetDbName) {
196
+ throw new Error('Database name is required');
197
+ }
198
+
199
+ // Get the database
200
+ const targetDb = this.client!.db(this.endpoint!, { token: this.token });
201
+
202
+ // List tables
203
+ const tables = await targetDb.listTables();
204
+ return tables.map((table: any) => table.name || table);
205
+ } catch (error) {
206
+ throw new Error(`Failed to list tables: ${error instanceof Error ? error.message : String(error)}`);
207
+ }
208
+ }
209
+ }
210
+
211
+ // Utility function to extract all attributes from documents (including nested)
212
+ export function extractAttributes(documents: any[]): string[] {
213
+ const attributes = new Set<string>();
214
+
215
+ function extractFromObject(obj: any, prefix: string = '') {
216
+ if (obj === null || obj === undefined) {
217
+ return;
218
+ }
219
+
220
+ if (Array.isArray(obj)) {
221
+ // For arrays, check the first element if it's an object
222
+ if (obj.length > 0 && typeof obj[0] === 'object' && obj[0] !== null) {
223
+ extractFromObject(obj[0], prefix);
224
+ }
225
+ return;
226
+ }
227
+
228
+ if (typeof obj === 'object') {
229
+ for (const [key, value] of Object.entries(obj)) {
230
+ // Transform keys starting with "$" to "_$"
231
+ const transformedKey = key.startsWith('$') ? `_${key}` : key;
232
+ const fullKey = prefix ? `${prefix}.${transformedKey}` : transformedKey;
233
+
234
+ attributes.add(fullKey);
235
+
236
+ // Recursively extract nested attributes
237
+ if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
238
+ extractFromObject(value, fullKey);
239
+ } else if (Array.isArray(value) && value.length > 0 && typeof value[0] === 'object') {
240
+ extractFromObject(value[0], fullKey);
241
+ }
242
+ }
243
+ }
244
+ }
245
+
246
+ // Extract attributes from all documents
247
+ for (const doc of documents) {
248
+ extractFromObject(doc);
249
+ }
250
+
251
+ return Array.from(attributes).sort();
252
+ }
253
+
254
+ // Singleton instance
255
+ let astraClientInstance: AstraClient | null = null;
256
+
257
+ export function getAstraClient(): AstraClient {
258
+ if (!astraClientInstance) {
259
+ astraClientInstance = new AstraClient();
260
+ }
261
+ return astraClientInstance;
262
+ }
263
+
264
+
package/next.config.js ADDED
@@ -0,0 +1,7 @@
1
+ /** @type {import('next').NextConfig} */
2
+ const nextConfig = {
3
+ reactStrictMode: true,
4
+ }
5
+
6
+ module.exports = nextConfig
7
+
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "agentic-astra-catalog",
3
+ "version": "0.0.1",
4
+ "description": "Next.js frontend for managing Astra DB tool catalog",
5
+ "main": "bin/agentic-astra-catalog.js",
6
+ "bin": {
7
+ "agentic-astra-catalog": "./bin/agentic-astra-catalog.js"
8
+ },
9
+ "keywords": [
10
+ "astra",
11
+ "datastax",
12
+ "catalog",
13
+ "tools",
14
+ "nextjs",
15
+ "react"
16
+ ],
17
+ "author": "Samuel Matioli",
18
+ "license": "MIT",
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "https://github.com/your-username/astra-mcp-server.git",
22
+ "directory": "frontend"
23
+ },
24
+ "files": [
25
+ "bin",
26
+ "app",
27
+ "components",
28
+ "lib",
29
+ "next.config.js",
30
+ "tailwind.config.js",
31
+ "tsconfig.json",
32
+ "postcss.config.js",
33
+ "package.json",
34
+ "README.md"
35
+ ],
36
+ "scripts": {
37
+ "dev": "next dev",
38
+ "build": "next build",
39
+ "start": "next start",
40
+ "lint": "next lint",
41
+ "postinstall": "echo '✅ Agentic Astra Catalog installed! Run with: npx agentic-astra-catalog'"
42
+ },
43
+ "dependencies": {
44
+ "@datastax/astra-db-ts": "^2.1.2",
45
+ "next": "^14.0.4",
46
+ "openai": "^4.20.1",
47
+ "react": "^18.2.0",
48
+ "react-dom": "^18.2.0"
49
+ },
50
+ "devDependencies": {
51
+ "@types/node": "^20.10.6",
52
+ "@types/react": "^18.2.43",
53
+ "@types/react-dom": "^18.2.17",
54
+ "autoprefixer": "^10.4.16",
55
+ "postcss": "^8.4.32",
56
+ "tailwindcss": "^3.4.0",
57
+ "typescript": "^5.3.3"
58
+ }
59
+ }
@@ -0,0 +1,6 @@
1
+ module.exports = {
2
+ plugins: {
3
+ tailwindcss: {},
4
+ autoprefixer: {},
5
+ },
6
+ }
@@ -0,0 +1,12 @@
1
+ /** @type {import('tailwindcss').Config} */
2
+ module.exports = {
3
+ content: [
4
+ './app/**/*.{js,ts,jsx,tsx,mdx}',
5
+ './components/**/*.{js,ts,jsx,tsx,mdx}',
6
+ ],
7
+ darkMode: 'class',
8
+ theme: {
9
+ extend: {},
10
+ },
11
+ plugins: [],
12
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "lib": ["dom", "dom.iterable", "esnext"],
5
+ "allowJs": true,
6
+ "skipLibCheck": true,
7
+ "strict": true,
8
+ "noEmit": true,
9
+ "esModuleInterop": true,
10
+ "module": "esnext",
11
+ "moduleResolution": "bundler",
12
+ "resolveJsonModule": true,
13
+ "isolatedModules": true,
14
+ "jsx": "preserve",
15
+ "incremental": true,
16
+ "plugins": [
17
+ {
18
+ "name": "next"
19
+ }
20
+ ],
21
+ "paths": {
22
+ "@/*": ["./*"]
23
+ }
24
+ },
25
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
26
+ "exclude": ["node_modules"]
27
+ }