tarvacode-agent-selector 1.0.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.
@@ -0,0 +1,95 @@
1
+ # MCP Server Instructions
2
+
3
+ ## Purpose
4
+
5
+ This MCP server enables Claude Code to select the optimal specialized agent for any given task using semantic similarity matching against a database of agent and skill embeddings.
6
+
7
+ ## When to Use
8
+
9
+ Use this server when:
10
+ - Processing complex, multi-domain tasks that require specialist agents
11
+ - The task description is ambiguous and you need to determine the best expert
12
+ - Building automated workflows that route work to appropriate agents
13
+ - The `find-best-agent` micro-agent needs to decompose and route PRD tasks
14
+
15
+ ## Tool Usage Guide
16
+
17
+ ### select_best_agent
18
+
19
+ **Primary tool for agent selection.**
20
+
21
+ Call this with a clear task description. The tool will:
22
+ 1. Generate a 1536-dimensional embedding using OpenAI
23
+ 2. Query Supabase for the best matching agent
24
+ 3. Return only the agent name
25
+
26
+ **Best practices:**
27
+ - Provide descriptive task text (50-500 characters works well)
28
+ - Include domain-specific keywords for better matching
29
+ - Use `match_threshold` of 0.5 (default) for balanced precision/recall
30
+ - Lower threshold (0.3-0.4) for exploratory matching
31
+ - Higher threshold (0.6-0.7) for high-confidence matching only
32
+
33
+ **Example calls:**
34
+ ```
35
+ // Database work
36
+ select_best_agent({ task: "Design a normalized PostgreSQL schema for user authentication with OAuth2 support" })
37
+ // → database-architect
38
+
39
+ // Frontend work
40
+ select_best_agent({ task: "Build a responsive React dashboard with data tables and charts" })
41
+ // → react-developer
42
+
43
+ // Architecture work
44
+ select_best_agent({ task: "Plan the microservices architecture for a real-time collaboration platform" })
45
+ // → chief-technology-architect
46
+
47
+ // Patent work
48
+ select_best_agent({ task: "Draft claims for a novel machine learning inference optimization method" })
49
+ // → patent-attorney
50
+ ```
51
+
52
+ ### list_agents
53
+
54
+ **Discovery tool for available agents.**
55
+
56
+ Use when you need to:
57
+ - See all available agents before making selection decisions
58
+ - Validate that an agent exists before routing work
59
+ - Present options to the user
60
+
61
+ ### get_agent_skills
62
+
63
+ **Drill-down tool for agent capabilities.**
64
+
65
+ Use when you need to:
66
+ - Understand what specific skills an agent has
67
+ - Validate that an agent can handle a specific deliverable type
68
+ - Explore domain coverage for an agent
69
+
70
+ ## Integration with find-best-agent
71
+
72
+ The `find-best-agent` micro-agent uses this server to:
73
+
74
+ 1. **Decompose** a complex PRD into logical tasks
75
+ 2. **Route** each task through `select_best_agent`
76
+ 3. **Map** tasks to their optimal agents
77
+ 4. **Execute** in parallel (independent tasks) or sequential (dependent tasks)
78
+
79
+ ## Caching
80
+
81
+ The server implements a 15-minute embedding cache to reduce OpenAI API calls. Similar task descriptions will reuse cached embeddings.
82
+
83
+ ## Error Handling
84
+
85
+ If no agent matches above the threshold, the server returns "chief-technology-architect" as a fallback. This ensures you always get a valid agent name to route work to.
86
+
87
+ ## Fallback Behavior
88
+
89
+ | Scenario | Result |
90
+ |----------|--------|
91
+ | No agents match threshold | Returns "chief-technology-architect" |
92
+ | Empty task description | Returns error |
93
+ | Invalid agent name (get_skills) | Returns empty skills array |
94
+ | OpenAI API error | Returns error with details |
95
+ | Supabase connection error | Returns error with details |
package/README.md ADDED
@@ -0,0 +1,163 @@
1
+ # tarvacode-agent-selector MCP Server
2
+
3
+ An MCP (Model Context Protocol) server for semantic agent selection using pgvector embeddings in Supabase.
4
+
5
+ ## Overview
6
+
7
+ This server provides tools to find the best specialized Claude Code agent for a given task using semantic similarity matching:
8
+
9
+ 1. **select_best_agent** - Find the optimal agent for a task description
10
+ 2. **list_agents** - List all available agents
11
+ 3. **get_agent_skills** - Get skills for a specific agent
12
+
13
+ ## Architecture
14
+
15
+ ```
16
+ Task Description
17
+
18
+ OpenAI Embedding (text-embedding-3-small)
19
+
20
+ Supabase Stored Procedure (two-tier selection)
21
+ - Top-K agent shortlist by embedding similarity
22
+ - Rerank by max skill similarity
23
+ - Fallback to chief-technology-architect
24
+
25
+ Agent Name
26
+ ```
27
+
28
+ ## Setup
29
+
30
+ ### Prerequisites
31
+
32
+ - Node.js >= 18.0.0
33
+ - npm or yarn
34
+ - Access to:
35
+ - Supabase project with pgvector extension
36
+ - OpenAI API key
37
+
38
+ ### Installation
39
+
40
+ ```bash
41
+ cd find-best-agent/mcp-server
42
+ npm install
43
+ npm run build
44
+ ```
45
+
46
+ ### Environment Variables
47
+
48
+ | Variable | Description | Required |
49
+ |----------|-------------|----------|
50
+ | `OPENAI_API_KEY` | OpenAI API key for embeddings | Yes |
51
+ | `SUPABASE_URL` | Supabase project URL | Yes |
52
+ | `SUPABASE_ANON_KEY` | Supabase anon/public key | Yes |
53
+
54
+ ### Claude Code Configuration
55
+
56
+ Add to your project's `.claude/mcp.json`:
57
+
58
+ ```json
59
+ {
60
+ "mcpServers": {
61
+ "tarvacode-agent-selector": {
62
+ "command": "node",
63
+ "args": ["./find-best-agent/mcp-server/dist/index.js"],
64
+ "env": {
65
+ "OPENAI_API_KEY": "${OPENAI_API_KEY}",
66
+ "SUPABASE_URL": "${SUPABASE_URL}",
67
+ "SUPABASE_ANON_KEY": "${SUPABASE_ANON_KEY}"
68
+ }
69
+ }
70
+ }
71
+ }
72
+ ```
73
+
74
+ ## Tools
75
+
76
+ ### select_best_agent
77
+
78
+ Finds the best agent for a task using two-tier semantic matching.
79
+
80
+ **Input:**
81
+ ```json
82
+ {
83
+ "task": "Design a PostgreSQL schema for a multi-tenant SaaS application",
84
+ "match_threshold": 0.5
85
+ }
86
+ ```
87
+
88
+ **Output:**
89
+ ```json
90
+ {
91
+ "agent_name": "database-architect"
92
+ }
93
+ ```
94
+
95
+ ### list_agents
96
+
97
+ Lists all available agents.
98
+
99
+ **Output:**
100
+ ```json
101
+ {
102
+ "agents": [
103
+ {
104
+ "name": "database-architect",
105
+ "description": "World-class Database Architect...",
106
+ "tools": ["Read", "Write", "Edit", "Grep", "Glob", "Bash", "Task"]
107
+ }
108
+ ],
109
+ "count": 20
110
+ }
111
+ ```
112
+
113
+ ### get_agent_skills
114
+
115
+ Gets skills for a specific agent.
116
+
117
+ **Input:**
118
+ ```json
119
+ {
120
+ "agent_name": "database-architect"
121
+ }
122
+ ```
123
+
124
+ **Output:**
125
+ ```json
126
+ {
127
+ "agent_name": "database-architect",
128
+ "skills": [
129
+ {
130
+ "skill_id": "A1",
131
+ "skill_name": "Workload Classification",
132
+ "domain": "A: Architecture strategy",
133
+ "deliverable": "classification_report"
134
+ }
135
+ ],
136
+ "count": 52
137
+ }
138
+ ```
139
+
140
+ ## Development
141
+
142
+ ```bash
143
+ # Run in development mode
144
+ npm run dev
145
+
146
+ # Type check
147
+ npm run typecheck
148
+
149
+ # Build
150
+ npm run build
151
+ ```
152
+
153
+ ## Selection Algorithm
154
+
155
+ The stored procedure implements a two-tier selection:
156
+
157
+ 1. **Agent-Level Shortlist**: Find top-K agents by cosine similarity between task embedding and agent embeddings
158
+ 2. **Skill-Level Rerank**: For each candidate, compute max similarity against skill embeddings
159
+ 3. **Final Score**: Use the higher of agent similarity or max skill similarity
160
+ 4. **Threshold**: Only return agents above the match threshold
161
+ 5. **Fallback**: Return "chief-technology-architect" if no match above threshold
162
+
163
+ This approach balances speed (coarse agent matching) with accuracy (granular skill validation).
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
package/dist/index.js ADDED
@@ -0,0 +1,122 @@
1
+ #!/usr/bin/env node
2
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
5
+ import { selectAgentTool } from "./tools/selectAgent.js";
6
+ import { listAgentsTool } from "./tools/listAgents.js";
7
+ import { getAgentSkillsTool } from "./tools/getAgentSkills.js";
8
+ const SERVER_NAME = "tarvacode-agent-selector";
9
+ const SERVER_VERSION = "1.0.0";
10
+ // Define available tools
11
+ const TOOLS = [
12
+ {
13
+ name: "select_best_agent",
14
+ description: "Finds the best specialized agent for a given task description using semantic similarity matching. Returns ONLY the agent name.",
15
+ inputSchema: {
16
+ type: "object",
17
+ properties: {
18
+ task: {
19
+ type: "string",
20
+ description: "The task description to find the best agent for",
21
+ },
22
+ match_threshold: {
23
+ type: "number",
24
+ description: "Minimum similarity score (0-1) to consider a match. Default: 0.5",
25
+ default: 0.5,
26
+ },
27
+ },
28
+ required: ["task"],
29
+ },
30
+ },
31
+ {
32
+ name: "list_agents",
33
+ description: "Lists all available specialized agents with their descriptions and available tools.",
34
+ inputSchema: {
35
+ type: "object",
36
+ properties: {},
37
+ required: [],
38
+ },
39
+ },
40
+ {
41
+ name: "get_agent_skills",
42
+ description: "Gets the skills available for a specific agent, organized by domain.",
43
+ inputSchema: {
44
+ type: "object",
45
+ properties: {
46
+ agent_name: {
47
+ type: "string",
48
+ description: "The name of the agent to get skills for",
49
+ },
50
+ },
51
+ required: ["agent_name"],
52
+ },
53
+ },
54
+ ];
55
+ async function main() {
56
+ const server = new Server({
57
+ name: SERVER_NAME,
58
+ version: SERVER_VERSION,
59
+ }, {
60
+ capabilities: {
61
+ tools: {},
62
+ },
63
+ });
64
+ // Handle list tools request
65
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
66
+ return { tools: TOOLS };
67
+ });
68
+ // Handle tool calls
69
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
70
+ const { name, arguments: args } = request.params;
71
+ try {
72
+ let result;
73
+ switch (name) {
74
+ case "select_best_agent":
75
+ result = await selectAgentTool({
76
+ task: args?.task,
77
+ match_threshold: args?.match_threshold,
78
+ });
79
+ break;
80
+ case "list_agents":
81
+ result = await listAgentsTool();
82
+ break;
83
+ case "get_agent_skills":
84
+ result = await getAgentSkillsTool({
85
+ agent_name: args?.agent_name,
86
+ });
87
+ break;
88
+ default:
89
+ throw new Error(`Unknown tool: ${name}`);
90
+ }
91
+ return {
92
+ content: [
93
+ {
94
+ type: "text",
95
+ text: JSON.stringify(result, null, 2),
96
+ },
97
+ ],
98
+ };
99
+ }
100
+ catch (error) {
101
+ const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
102
+ return {
103
+ content: [
104
+ {
105
+ type: "text",
106
+ text: JSON.stringify({ error: errorMessage }),
107
+ },
108
+ ],
109
+ isError: true,
110
+ };
111
+ }
112
+ });
113
+ // Start the server
114
+ const transport = new StdioServerTransport();
115
+ await server.connect(transport);
116
+ console.error(`${SERVER_NAME} v${SERVER_VERSION} started`);
117
+ }
118
+ main().catch((error) => {
119
+ console.error("Fatal error:", error);
120
+ process.exit(1);
121
+ });
122
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Generate embedding for a single text with caching.
3
+ */
4
+ export declare function generateEmbedding(text: string): Promise<number[]>;
5
+ /**
6
+ * Generate embeddings for multiple texts with batching.
7
+ */
8
+ export declare function generateEmbeddings(texts: string[]): Promise<number[][]>;
9
+ //# sourceMappingURL=embeddings.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"embeddings.d.ts","sourceRoot":"","sources":["../../src/lib/embeddings.ts"],"names":[],"mappings":"AAmDA;;GAEG;AACH,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CA8BvE;AAED;;GAEG;AACH,wBAAsB,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAS7E"}
@@ -0,0 +1,72 @@
1
+ import OpenAI from "openai";
2
+ const EMBEDDING_MODEL = "text-embedding-3-small";
3
+ let openaiClient = null;
4
+ const embeddingCache = new Map();
5
+ const CACHE_TTL_MS = 15 * 60 * 1000; // 15 minutes
6
+ function getOpenAIClient() {
7
+ if (openaiClient) {
8
+ return openaiClient;
9
+ }
10
+ const apiKey = process.env.OPENAI_API_KEY;
11
+ if (!apiKey) {
12
+ throw new Error("Missing OpenAI configuration. Set OPENAI_API_KEY environment variable.");
13
+ }
14
+ openaiClient = new OpenAI({ apiKey });
15
+ return openaiClient;
16
+ }
17
+ /**
18
+ * Normalize text for cache key.
19
+ */
20
+ function normalizeText(text) {
21
+ return text.trim().toLowerCase();
22
+ }
23
+ /**
24
+ * Clean expired cache entries.
25
+ */
26
+ function cleanCache() {
27
+ const now = Date.now();
28
+ for (const [key, entry] of embeddingCache.entries()) {
29
+ if (now - entry.timestamp > CACHE_TTL_MS) {
30
+ embeddingCache.delete(key);
31
+ }
32
+ }
33
+ }
34
+ /**
35
+ * Generate embedding for a single text with caching.
36
+ */
37
+ export async function generateEmbedding(text) {
38
+ const cacheKey = normalizeText(text);
39
+ // Check cache
40
+ const cached = embeddingCache.get(cacheKey);
41
+ if (cached && Date.now() - cached.timestamp < CACHE_TTL_MS) {
42
+ return cached.embedding;
43
+ }
44
+ // Clean old entries periodically
45
+ if (embeddingCache.size > 100) {
46
+ cleanCache();
47
+ }
48
+ const client = getOpenAIClient();
49
+ const response = await client.embeddings.create({
50
+ model: EMBEDDING_MODEL,
51
+ input: text,
52
+ });
53
+ const embedding = response.data[0].embedding;
54
+ // Cache the result
55
+ embeddingCache.set(cacheKey, {
56
+ embedding,
57
+ timestamp: Date.now(),
58
+ });
59
+ return embedding;
60
+ }
61
+ /**
62
+ * Generate embeddings for multiple texts with batching.
63
+ */
64
+ export async function generateEmbeddings(texts) {
65
+ const client = getOpenAIClient();
66
+ const response = await client.embeddings.create({
67
+ model: EMBEDDING_MODEL,
68
+ input: texts,
69
+ });
70
+ return response.data.map((item) => item.embedding);
71
+ }
72
+ //# sourceMappingURL=embeddings.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"embeddings.js","sourceRoot":"","sources":["../../src/lib/embeddings.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,QAAQ,CAAC;AAE5B,MAAM,eAAe,GAAG,wBAAwB,CAAC;AAEjD,IAAI,YAAY,GAAkB,IAAI,CAAC;AAQvC,MAAM,cAAc,GAAG,IAAI,GAAG,EAAsB,CAAC;AACrD,MAAM,YAAY,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,aAAa;AAElD,SAAS,eAAe;IACtB,IAAI,YAAY,EAAE,CAAC;QACjB,OAAO,YAAY,CAAC;IACtB,CAAC;IAED,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC;IAE1C,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CACb,wEAAwE,CACzE,CAAC;IACJ,CAAC;IAED,YAAY,GAAG,IAAI,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC;IACtC,OAAO,YAAY,CAAC;AACtB,CAAC;AAED;;GAEG;AACH,SAAS,aAAa,CAAC,IAAY;IACjC,OAAO,IAAI,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;AACnC,CAAC;AAED;;GAEG;AACH,SAAS,UAAU;IACjB,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACvB,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,cAAc,CAAC,OAAO,EAAE,EAAE,CAAC;QACpD,IAAI,GAAG,GAAG,KAAK,CAAC,SAAS,GAAG,YAAY,EAAE,CAAC;YACzC,cAAc,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC7B,CAAC;IACH,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,IAAY;IAClD,MAAM,QAAQ,GAAG,aAAa,CAAC,IAAI,CAAC,CAAC;IAErC,cAAc;IACd,MAAM,MAAM,GAAG,cAAc,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IAC5C,IAAI,MAAM,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,MAAM,CAAC,SAAS,GAAG,YAAY,EAAE,CAAC;QAC3D,OAAO,MAAM,CAAC,SAAS,CAAC;IAC1B,CAAC;IAED,iCAAiC;IACjC,IAAI,cAAc,CAAC,IAAI,GAAG,GAAG,EAAE,CAAC;QAC9B,UAAU,EAAE,CAAC;IACf,CAAC;IAED,MAAM,MAAM,GAAG,eAAe,EAAE,CAAC;IAEjC,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC;QAC9C,KAAK,EAAE,eAAe;QACtB,KAAK,EAAE,IAAI;KACZ,CAAC,CAAC;IAEH,MAAM,SAAS,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAE7C,mBAAmB;IACnB,cAAc,CAAC,GAAG,CAAC,QAAQ,EAAE;QAC3B,SAAS;QACT,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;KACtB,CAAC,CAAC;IAEH,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,KAAe;IACtD,MAAM,MAAM,GAAG,eAAe,EAAE,CAAC;IAEjC,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC;QAC9C,KAAK,EAAE,eAAe;QACtB,KAAK,EAAE,KAAK;KACb,CAAC,CAAC;IAEH,OAAO,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;AACrD,CAAC"}
@@ -0,0 +1,26 @@
1
+ import { SupabaseClient } from "@supabase/supabase-js";
2
+ export declare function getSupabaseClient(): SupabaseClient;
3
+ export interface Agent {
4
+ name: string;
5
+ description: string;
6
+ tools: string[];
7
+ }
8
+ export interface Skill {
9
+ skill_id: string;
10
+ skill_name: string;
11
+ domain: string;
12
+ deliverable: string | null;
13
+ }
14
+ /**
15
+ * Select the best agent for a task using the two-tier stored procedure.
16
+ */
17
+ export declare function selectBestAgent(taskEmbedding: number[], matchThreshold?: number, topK?: number): Promise<string>;
18
+ /**
19
+ * List all available agents.
20
+ */
21
+ export declare function listAgents(): Promise<Agent[]>;
22
+ /**
23
+ * Get skills for a specific agent.
24
+ */
25
+ export declare function getAgentSkills(agentName: string): Promise<Skill[]>;
26
+ //# sourceMappingURL=supabase.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"supabase.d.ts","sourceRoot":"","sources":["../../src/lib/supabase.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAIrE,wBAAgB,iBAAiB,IAAI,cAAc,CAgBlD;AAED,MAAM,WAAW,KAAK;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED,MAAM,WAAW,KAAK;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAED;;GAEG;AACH,wBAAsB,eAAe,CACnC,aAAa,EAAE,MAAM,EAAE,EACvB,cAAc,GAAE,MAAY,EAC5B,IAAI,GAAE,MAAU,GACf,OAAO,CAAC,MAAM,CAAC,CAcjB;AAED;;GAEG;AACH,wBAAsB,UAAU,IAAI,OAAO,CAAC,KAAK,EAAE,CAAC,CAUnD;AAED;;GAEG;AACH,wBAAsB,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC,CAYxE"}
@@ -0,0 +1,54 @@
1
+ import { createClient } from "@supabase/supabase-js";
2
+ let supabaseClient = null;
3
+ export function getSupabaseClient() {
4
+ if (supabaseClient) {
5
+ return supabaseClient;
6
+ }
7
+ const supabaseUrl = process.env.SUPABASE_URL;
8
+ const supabaseKey = process.env.SUPABASE_ANON_KEY || process.env.SUPABASE_SERVICE_ROLE_KEY;
9
+ if (!supabaseUrl || !supabaseKey) {
10
+ throw new Error("Missing Supabase configuration. Set SUPABASE_URL and SUPABASE_ANON_KEY environment variables.");
11
+ }
12
+ supabaseClient = createClient(supabaseUrl, supabaseKey);
13
+ return supabaseClient;
14
+ }
15
+ /**
16
+ * Select the best agent for a task using the two-tier stored procedure.
17
+ */
18
+ export async function selectBestAgent(taskEmbedding, matchThreshold = 0.5, topK = 5) {
19
+ const supabase = getSupabaseClient();
20
+ const { data, error } = await supabase.rpc("select_best_agent", {
21
+ task_embedding: taskEmbedding,
22
+ match_threshold: matchThreshold,
23
+ top_k: topK,
24
+ });
25
+ if (error) {
26
+ throw new Error(`Failed to select agent: ${error.message}`);
27
+ }
28
+ return data;
29
+ }
30
+ /**
31
+ * List all available agents.
32
+ */
33
+ export async function listAgents() {
34
+ const supabase = getSupabaseClient();
35
+ const { data, error } = await supabase.rpc("list_agents");
36
+ if (error) {
37
+ throw new Error(`Failed to list agents: ${error.message}`);
38
+ }
39
+ return data;
40
+ }
41
+ /**
42
+ * Get skills for a specific agent.
43
+ */
44
+ export async function getAgentSkills(agentName) {
45
+ const supabase = getSupabaseClient();
46
+ const { data, error } = await supabase.rpc("get_agent_skills", {
47
+ agent_name: agentName,
48
+ });
49
+ if (error) {
50
+ throw new Error(`Failed to get agent skills: ${error.message}`);
51
+ }
52
+ return data;
53
+ }
54
+ //# sourceMappingURL=supabase.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"supabase.js","sourceRoot":"","sources":["../../src/lib/supabase.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAkB,MAAM,uBAAuB,CAAC;AAErE,IAAI,cAAc,GAA0B,IAAI,CAAC;AAEjD,MAAM,UAAU,iBAAiB;IAC/B,IAAI,cAAc,EAAE,CAAC;QACnB,OAAO,cAAc,CAAC;IACxB,CAAC;IAED,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC;IAC7C,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC,iBAAiB,IAAI,OAAO,CAAC,GAAG,CAAC,yBAAyB,CAAC;IAE3F,IAAI,CAAC,WAAW,IAAI,CAAC,WAAW,EAAE,CAAC;QACjC,MAAM,IAAI,KAAK,CACb,+FAA+F,CAChG,CAAC;IACJ,CAAC;IAED,cAAc,GAAG,YAAY,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC;IACxD,OAAO,cAAc,CAAC;AACxB,CAAC;AAeD;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,aAAuB,EACvB,iBAAyB,GAAG,EAC5B,OAAe,CAAC;IAEhB,MAAM,QAAQ,GAAG,iBAAiB,EAAE,CAAC;IAErC,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,MAAM,QAAQ,CAAC,GAAG,CAAC,mBAAmB,EAAE;QAC9D,cAAc,EAAE,aAAa;QAC7B,eAAe,EAAE,cAAc;QAC/B,KAAK,EAAE,IAAI;KACZ,CAAC,CAAC;IAEH,IAAI,KAAK,EAAE,CAAC;QACV,MAAM,IAAI,KAAK,CAAC,2BAA2B,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;IAC9D,CAAC;IAED,OAAO,IAAc,CAAC;AACxB,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU;IAC9B,MAAM,QAAQ,GAAG,iBAAiB,EAAE,CAAC;IAErC,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,MAAM,QAAQ,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;IAE1D,IAAI,KAAK,EAAE,CAAC;QACV,MAAM,IAAI,KAAK,CAAC,0BAA0B,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;IAC7D,CAAC;IAED,OAAO,IAAe,CAAC;AACzB,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,SAAiB;IACpD,MAAM,QAAQ,GAAG,iBAAiB,EAAE,CAAC;IAErC,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,MAAM,QAAQ,CAAC,GAAG,CAAC,kBAAkB,EAAE;QAC7D,UAAU,EAAE,SAAS;KACtB,CAAC,CAAC;IAEH,IAAI,KAAK,EAAE,CAAC;QACV,MAAM,IAAI,KAAK,CAAC,+BAA+B,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;IAClE,CAAC;IAED,OAAO,IAAe,CAAC;AACzB,CAAC"}
@@ -0,0 +1,14 @@
1
+ import { Skill } from "../lib/supabase.js";
2
+ export interface GetAgentSkillsInput {
3
+ agent_name: string;
4
+ }
5
+ export interface GetAgentSkillsOutput {
6
+ agent_name: string;
7
+ skills: Skill[];
8
+ count: number;
9
+ }
10
+ /**
11
+ * Get the skills available for a specific agent.
12
+ */
13
+ export declare function getAgentSkillsTool(input: GetAgentSkillsInput): Promise<GetAgentSkillsOutput>;
14
+ //# sourceMappingURL=getAgentSkills.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getAgentSkills.d.ts","sourceRoot":"","sources":["../../src/tools/getAgentSkills.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAE3D,MAAM,WAAW,mBAAmB;IAClC,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,oBAAoB;IACnC,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,KAAK,EAAE,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;GAEG;AACH,wBAAsB,kBAAkB,CACtC,KAAK,EAAE,mBAAmB,GACzB,OAAO,CAAC,oBAAoB,CAAC,CAc/B"}
@@ -0,0 +1,17 @@
1
+ import { getAgentSkills } from "../lib/supabase.js";
2
+ /**
3
+ * Get the skills available for a specific agent.
4
+ */
5
+ export async function getAgentSkillsTool(input) {
6
+ const { agent_name } = input;
7
+ if (!agent_name || agent_name.trim().length === 0) {
8
+ throw new Error("Agent name is required");
9
+ }
10
+ const skills = await getAgentSkills(agent_name);
11
+ return {
12
+ agent_name,
13
+ skills,
14
+ count: skills.length,
15
+ };
16
+ }
17
+ //# sourceMappingURL=getAgentSkills.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getAgentSkills.js","sourceRoot":"","sources":["../../src/tools/getAgentSkills.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAS,MAAM,oBAAoB,CAAC;AAY3D;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,KAA0B;IAE1B,MAAM,EAAE,UAAU,EAAE,GAAG,KAAK,CAAC;IAE7B,IAAI,CAAC,UAAU,IAAI,UAAU,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAClD,MAAM,IAAI,KAAK,CAAC,wBAAwB,CAAC,CAAC;IAC5C,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,cAAc,CAAC,UAAU,CAAC,CAAC;IAEhD,OAAO;QACL,UAAU;QACV,MAAM;QACN,KAAK,EAAE,MAAM,CAAC,MAAM;KACrB,CAAC;AACJ,CAAC"}
@@ -0,0 +1,10 @@
1
+ import { Agent } from "../lib/supabase.js";
2
+ export interface ListAgentsOutput {
3
+ agents: Agent[];
4
+ count: number;
5
+ }
6
+ /**
7
+ * List all available agents with their descriptions and tools.
8
+ */
9
+ export declare function listAgentsTool(): Promise<ListAgentsOutput>;
10
+ //# sourceMappingURL=listAgents.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"listAgents.d.ts","sourceRoot":"","sources":["../../src/tools/listAgents.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAEvD,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,KAAK,EAAE,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;GAEG;AACH,wBAAsB,cAAc,IAAI,OAAO,CAAC,gBAAgB,CAAC,CAOhE"}
@@ -0,0 +1,12 @@
1
+ import { listAgents } from "../lib/supabase.js";
2
+ /**
3
+ * List all available agents with their descriptions and tools.
4
+ */
5
+ export async function listAgentsTool() {
6
+ const agents = await listAgents();
7
+ return {
8
+ agents,
9
+ count: agents.length,
10
+ };
11
+ }
12
+ //# sourceMappingURL=listAgents.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"listAgents.js","sourceRoot":"","sources":["../../src/tools/listAgents.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAS,MAAM,oBAAoB,CAAC;AAOvD;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc;IAClC,MAAM,MAAM,GAAG,MAAM,UAAU,EAAE,CAAC;IAElC,OAAO;QACL,MAAM;QACN,KAAK,EAAE,MAAM,CAAC,MAAM;KACrB,CAAC;AACJ,CAAC"}
@@ -0,0 +1,17 @@
1
+ export interface SelectAgentInput {
2
+ task: string;
3
+ match_threshold?: number;
4
+ }
5
+ export interface SelectAgentOutput {
6
+ agent_name: string;
7
+ }
8
+ /**
9
+ * Select the best agent for a given task description.
10
+ *
11
+ * This tool:
12
+ * 1. Generates an embedding for the task description
13
+ * 2. Calls the Supabase stored procedure to find the best matching agent
14
+ * 3. Returns ONLY the agent name
15
+ */
16
+ export declare function selectAgentTool(input: SelectAgentInput): Promise<SelectAgentOutput>;
17
+ //# sourceMappingURL=selectAgent.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"selectAgent.d.ts","sourceRoot":"","sources":["../../src/tools/selectAgent.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,iBAAiB;IAChC,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;GAOG;AACH,wBAAsB,eAAe,CACnC,KAAK,EAAE,gBAAgB,GACtB,OAAO,CAAC,iBAAiB,CAAC,CAgB5B"}
@@ -0,0 +1,24 @@
1
+ import { generateEmbedding } from "../lib/embeddings.js";
2
+ import { selectBestAgent } from "../lib/supabase.js";
3
+ /**
4
+ * Select the best agent for a given task description.
5
+ *
6
+ * This tool:
7
+ * 1. Generates an embedding for the task description
8
+ * 2. Calls the Supabase stored procedure to find the best matching agent
9
+ * 3. Returns ONLY the agent name
10
+ */
11
+ export async function selectAgentTool(input) {
12
+ const { task, match_threshold = 0.5 } = input;
13
+ if (!task || task.trim().length === 0) {
14
+ throw new Error("Task description is required");
15
+ }
16
+ // Generate embedding for the task
17
+ const taskEmbedding = await generateEmbedding(task);
18
+ // Call stored procedure to find best agent
19
+ const agentName = await selectBestAgent(taskEmbedding, match_threshold);
20
+ return {
21
+ agent_name: agentName,
22
+ };
23
+ }
24
+ //# sourceMappingURL=selectAgent.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"selectAgent.js","sourceRoot":"","sources":["../../src/tools/selectAgent.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AACzD,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAWrD;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,KAAuB;IAEvB,MAAM,EAAE,IAAI,EAAE,eAAe,GAAG,GAAG,EAAE,GAAG,KAAK,CAAC;IAE9C,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtC,MAAM,IAAI,KAAK,CAAC,8BAA8B,CAAC,CAAC;IAClD,CAAC;IAED,kCAAkC;IAClC,MAAM,aAAa,GAAG,MAAM,iBAAiB,CAAC,IAAI,CAAC,CAAC;IAEpD,2CAA2C;IAC3C,MAAM,SAAS,GAAG,MAAM,eAAe,CAAC,aAAa,EAAE,eAAe,CAAC,CAAC;IAExE,OAAO;QACL,UAAU,EAAE,SAAS;KACtB,CAAC;AACJ,CAAC"}
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "tarvacode-agent-selector",
3
+ "version": "1.0.0",
4
+ "description": "MCP server for semantic agent selection using pgvector embeddings",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "scripts": {
8
+ "build": "tsc",
9
+ "start": "node dist/index.js",
10
+ "dev": "tsx src/index.ts",
11
+ "clean": "rm -rf dist",
12
+ "typecheck": "tsc --noEmit"
13
+ },
14
+ "keywords": [
15
+ "mcp",
16
+ "claude",
17
+ "agent-selection",
18
+ "embeddings",
19
+ "pgvector"
20
+ ],
21
+ "author": "",
22
+ "license": "MIT",
23
+ "dependencies": {
24
+ "@modelcontextprotocol/sdk": "^1.0.0",
25
+ "@supabase/supabase-js": "^2.39.0",
26
+ "openai": "^4.28.0"
27
+ },
28
+ "devDependencies": {
29
+ "@types/node": "^20.11.0",
30
+ "tsx": "^4.7.0",
31
+ "typescript": "^5.3.3"
32
+ },
33
+ "engines": {
34
+ "node": ">=18.0.0"
35
+ }
36
+ }
package/src/index.ts ADDED
@@ -0,0 +1,149 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
4
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
5
+ import {
6
+ CallToolRequestSchema,
7
+ ListToolsRequestSchema,
8
+ Tool,
9
+ } from "@modelcontextprotocol/sdk/types.js";
10
+
11
+ import { selectAgentTool } from "./tools/selectAgent.js";
12
+ import { listAgentsTool } from "./tools/listAgents.js";
13
+ import { getAgentSkillsTool } from "./tools/getAgentSkills.js";
14
+
15
+ const SERVER_NAME = "tarvacode-agent-selector";
16
+ const SERVER_VERSION = "1.0.0";
17
+
18
+ // Define available tools
19
+ const TOOLS: Tool[] = [
20
+ {
21
+ name: "select_best_agent",
22
+ description:
23
+ "Finds the best specialized agent for a given task description using semantic similarity matching. Returns ONLY the agent name.",
24
+ inputSchema: {
25
+ type: "object" as const,
26
+ properties: {
27
+ task: {
28
+ type: "string",
29
+ description: "The task description to find the best agent for",
30
+ },
31
+ match_threshold: {
32
+ type: "number",
33
+ description:
34
+ "Minimum similarity score (0-1) to consider a match. Default: 0.5",
35
+ default: 0.5,
36
+ },
37
+ },
38
+ required: ["task"],
39
+ },
40
+ },
41
+ {
42
+ name: "list_agents",
43
+ description:
44
+ "Lists all available specialized agents with their descriptions and available tools.",
45
+ inputSchema: {
46
+ type: "object" as const,
47
+ properties: {},
48
+ required: [],
49
+ },
50
+ },
51
+ {
52
+ name: "get_agent_skills",
53
+ description:
54
+ "Gets the skills available for a specific agent, organized by domain.",
55
+ inputSchema: {
56
+ type: "object" as const,
57
+ properties: {
58
+ agent_name: {
59
+ type: "string",
60
+ description: "The name of the agent to get skills for",
61
+ },
62
+ },
63
+ required: ["agent_name"],
64
+ },
65
+ },
66
+ ];
67
+
68
+ async function main() {
69
+ const server = new Server(
70
+ {
71
+ name: SERVER_NAME,
72
+ version: SERVER_VERSION,
73
+ },
74
+ {
75
+ capabilities: {
76
+ tools: {},
77
+ },
78
+ }
79
+ );
80
+
81
+ // Handle list tools request
82
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
83
+ return { tools: TOOLS };
84
+ });
85
+
86
+ // Handle tool calls
87
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
88
+ const { name, arguments: args } = request.params;
89
+
90
+ try {
91
+ let result: unknown;
92
+
93
+ switch (name) {
94
+ case "select_best_agent":
95
+ result = await selectAgentTool({
96
+ task: args?.task as string,
97
+ match_threshold: args?.match_threshold as number | undefined,
98
+ });
99
+ break;
100
+
101
+ case "list_agents":
102
+ result = await listAgentsTool();
103
+ break;
104
+
105
+ case "get_agent_skills":
106
+ result = await getAgentSkillsTool({
107
+ agent_name: args?.agent_name as string,
108
+ });
109
+ break;
110
+
111
+ default:
112
+ throw new Error(`Unknown tool: ${name}`);
113
+ }
114
+
115
+ return {
116
+ content: [
117
+ {
118
+ type: "text",
119
+ text: JSON.stringify(result, null, 2),
120
+ },
121
+ ],
122
+ };
123
+ } catch (error) {
124
+ const errorMessage =
125
+ error instanceof Error ? error.message : "Unknown error occurred";
126
+
127
+ return {
128
+ content: [
129
+ {
130
+ type: "text",
131
+ text: JSON.stringify({ error: errorMessage }),
132
+ },
133
+ ],
134
+ isError: true,
135
+ };
136
+ }
137
+ });
138
+
139
+ // Start the server
140
+ const transport = new StdioServerTransport();
141
+ await server.connect(transport);
142
+
143
+ console.error(`${SERVER_NAME} v${SERVER_VERSION} started`);
144
+ }
145
+
146
+ main().catch((error) => {
147
+ console.error("Fatal error:", error);
148
+ process.exit(1);
149
+ });
@@ -0,0 +1,99 @@
1
+ import OpenAI from "openai";
2
+
3
+ const EMBEDDING_MODEL = "text-embedding-3-small";
4
+
5
+ let openaiClient: OpenAI | null = null;
6
+
7
+ // Simple in-memory cache with 15-minute TTL
8
+ interface CacheEntry {
9
+ embedding: number[];
10
+ timestamp: number;
11
+ }
12
+
13
+ const embeddingCache = new Map<string, CacheEntry>();
14
+ const CACHE_TTL_MS = 15 * 60 * 1000; // 15 minutes
15
+
16
+ function getOpenAIClient(): OpenAI {
17
+ if (openaiClient) {
18
+ return openaiClient;
19
+ }
20
+
21
+ const apiKey = process.env.OPENAI_API_KEY;
22
+
23
+ if (!apiKey) {
24
+ throw new Error(
25
+ "Missing OpenAI configuration. Set OPENAI_API_KEY environment variable."
26
+ );
27
+ }
28
+
29
+ openaiClient = new OpenAI({ apiKey });
30
+ return openaiClient;
31
+ }
32
+
33
+ /**
34
+ * Normalize text for cache key.
35
+ */
36
+ function normalizeText(text: string): string {
37
+ return text.trim().toLowerCase();
38
+ }
39
+
40
+ /**
41
+ * Clean expired cache entries.
42
+ */
43
+ function cleanCache(): void {
44
+ const now = Date.now();
45
+ for (const [key, entry] of embeddingCache.entries()) {
46
+ if (now - entry.timestamp > CACHE_TTL_MS) {
47
+ embeddingCache.delete(key);
48
+ }
49
+ }
50
+ }
51
+
52
+ /**
53
+ * Generate embedding for a single text with caching.
54
+ */
55
+ export async function generateEmbedding(text: string): Promise<number[]> {
56
+ const cacheKey = normalizeText(text);
57
+
58
+ // Check cache
59
+ const cached = embeddingCache.get(cacheKey);
60
+ if (cached && Date.now() - cached.timestamp < CACHE_TTL_MS) {
61
+ return cached.embedding;
62
+ }
63
+
64
+ // Clean old entries periodically
65
+ if (embeddingCache.size > 100) {
66
+ cleanCache();
67
+ }
68
+
69
+ const client = getOpenAIClient();
70
+
71
+ const response = await client.embeddings.create({
72
+ model: EMBEDDING_MODEL,
73
+ input: text,
74
+ });
75
+
76
+ const embedding = response.data[0].embedding;
77
+
78
+ // Cache the result
79
+ embeddingCache.set(cacheKey, {
80
+ embedding,
81
+ timestamp: Date.now(),
82
+ });
83
+
84
+ return embedding;
85
+ }
86
+
87
+ /**
88
+ * Generate embeddings for multiple texts with batching.
89
+ */
90
+ export async function generateEmbeddings(texts: string[]): Promise<number[][]> {
91
+ const client = getOpenAIClient();
92
+
93
+ const response = await client.embeddings.create({
94
+ model: EMBEDDING_MODEL,
95
+ input: texts,
96
+ });
97
+
98
+ return response.data.map((item) => item.embedding);
99
+ }
@@ -0,0 +1,89 @@
1
+ import { createClient, SupabaseClient } from "@supabase/supabase-js";
2
+
3
+ let supabaseClient: SupabaseClient | null = null;
4
+
5
+ export function getSupabaseClient(): SupabaseClient {
6
+ if (supabaseClient) {
7
+ return supabaseClient;
8
+ }
9
+
10
+ const supabaseUrl = process.env.SUPABASE_URL;
11
+ const supabaseKey = process.env.SUPABASE_ANON_KEY || process.env.SUPABASE_SERVICE_ROLE_KEY;
12
+
13
+ if (!supabaseUrl || !supabaseKey) {
14
+ throw new Error(
15
+ "Missing Supabase configuration. Set SUPABASE_URL and SUPABASE_ANON_KEY environment variables."
16
+ );
17
+ }
18
+
19
+ supabaseClient = createClient(supabaseUrl, supabaseKey);
20
+ return supabaseClient;
21
+ }
22
+
23
+ export interface Agent {
24
+ name: string;
25
+ description: string;
26
+ tools: string[];
27
+ }
28
+
29
+ export interface Skill {
30
+ skill_id: string;
31
+ skill_name: string;
32
+ domain: string;
33
+ deliverable: string | null;
34
+ }
35
+
36
+ /**
37
+ * Select the best agent for a task using the two-tier stored procedure.
38
+ */
39
+ export async function selectBestAgent(
40
+ taskEmbedding: number[],
41
+ matchThreshold: number = 0.5,
42
+ topK: number = 5
43
+ ): Promise<string> {
44
+ const supabase = getSupabaseClient();
45
+
46
+ const { data, error } = await supabase.rpc("select_best_agent", {
47
+ task_embedding: taskEmbedding,
48
+ match_threshold: matchThreshold,
49
+ top_k: topK,
50
+ });
51
+
52
+ if (error) {
53
+ throw new Error(`Failed to select agent: ${error.message}`);
54
+ }
55
+
56
+ return data as string;
57
+ }
58
+
59
+ /**
60
+ * List all available agents.
61
+ */
62
+ export async function listAgents(): Promise<Agent[]> {
63
+ const supabase = getSupabaseClient();
64
+
65
+ const { data, error } = await supabase.rpc("list_agents");
66
+
67
+ if (error) {
68
+ throw new Error(`Failed to list agents: ${error.message}`);
69
+ }
70
+
71
+ return data as Agent[];
72
+ }
73
+
74
+ /**
75
+ * Get skills for a specific agent.
76
+ */
77
+ export async function getAgentSkills(agentName: string): Promise<Skill[]> {
78
+ const supabase = getSupabaseClient();
79
+
80
+ const { data, error } = await supabase.rpc("get_agent_skills", {
81
+ agent_name: agentName,
82
+ });
83
+
84
+ if (error) {
85
+ throw new Error(`Failed to get agent skills: ${error.message}`);
86
+ }
87
+
88
+ return data as Skill[];
89
+ }
@@ -0,0 +1,32 @@
1
+ import { getAgentSkills, Skill } from "../lib/supabase.js";
2
+
3
+ export interface GetAgentSkillsInput {
4
+ agent_name: string;
5
+ }
6
+
7
+ export interface GetAgentSkillsOutput {
8
+ agent_name: string;
9
+ skills: Skill[];
10
+ count: number;
11
+ }
12
+
13
+ /**
14
+ * Get the skills available for a specific agent.
15
+ */
16
+ export async function getAgentSkillsTool(
17
+ input: GetAgentSkillsInput
18
+ ): Promise<GetAgentSkillsOutput> {
19
+ const { agent_name } = input;
20
+
21
+ if (!agent_name || agent_name.trim().length === 0) {
22
+ throw new Error("Agent name is required");
23
+ }
24
+
25
+ const skills = await getAgentSkills(agent_name);
26
+
27
+ return {
28
+ agent_name,
29
+ skills,
30
+ count: skills.length,
31
+ };
32
+ }
@@ -0,0 +1,18 @@
1
+ import { listAgents, Agent } from "../lib/supabase.js";
2
+
3
+ export interface ListAgentsOutput {
4
+ agents: Agent[];
5
+ count: number;
6
+ }
7
+
8
+ /**
9
+ * List all available agents with their descriptions and tools.
10
+ */
11
+ export async function listAgentsTool(): Promise<ListAgentsOutput> {
12
+ const agents = await listAgents();
13
+
14
+ return {
15
+ agents,
16
+ count: agents.length,
17
+ };
18
+ }
@@ -0,0 +1,39 @@
1
+ import { generateEmbedding } from "../lib/embeddings.js";
2
+ import { selectBestAgent } from "../lib/supabase.js";
3
+
4
+ export interface SelectAgentInput {
5
+ task: string;
6
+ match_threshold?: number;
7
+ }
8
+
9
+ export interface SelectAgentOutput {
10
+ agent_name: string;
11
+ }
12
+
13
+ /**
14
+ * Select the best agent for a given task description.
15
+ *
16
+ * This tool:
17
+ * 1. Generates an embedding for the task description
18
+ * 2. Calls the Supabase stored procedure to find the best matching agent
19
+ * 3. Returns ONLY the agent name
20
+ */
21
+ export async function selectAgentTool(
22
+ input: SelectAgentInput
23
+ ): Promise<SelectAgentOutput> {
24
+ const { task, match_threshold = 0.5 } = input;
25
+
26
+ if (!task || task.trim().length === 0) {
27
+ throw new Error("Task description is required");
28
+ }
29
+
30
+ // Generate embedding for the task
31
+ const taskEmbedding = await generateEmbedding(task);
32
+
33
+ // Call stored procedure to find best agent
34
+ const agentName = await selectBestAgent(taskEmbedding, match_threshold);
35
+
36
+ return {
37
+ agent_name: agentName,
38
+ };
39
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,19 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "NodeNext",
5
+ "moduleResolution": "NodeNext",
6
+ "outDir": "./dist",
7
+ "rootDir": "./src",
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true,
11
+ "forceConsistentCasingInFileNames": true,
12
+ "resolveJsonModule": true,
13
+ "declaration": true,
14
+ "declarationMap": true,
15
+ "sourceMap": true
16
+ },
17
+ "include": ["src/**/*"],
18
+ "exclude": ["node_modules", "dist"]
19
+ }