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.
- package/README.md +154 -0
- package/app/api/db/objects/route.ts +44 -0
- package/app/api/tools/attributes/route.ts +27 -0
- package/app/api/tools/generate/route.ts +146 -0
- package/app/api/tools/route.ts +38 -0
- package/app/globals.css +4 -0
- package/app/layout.tsx +20 -0
- package/app/page.tsx +119 -0
- package/bin/agentic-astra-catalog.js +77 -0
- package/components/ThemeToggle.tsx +49 -0
- package/components/ToolEditor.tsx +1000 -0
- package/components/ToolList.tsx +75 -0
- package/lib/astraClient.ts +264 -0
- package/next.config.js +7 -0
- package/package.json +59 -0
- package/postcss.config.js +6 -0
- package/tailwind.config.js +12 -0
- package/tsconfig.json +27 -0
|
@@ -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
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
|
+
}
|
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
|
+
}
|