Skip to content

Commit dfaa2ba

Browse files
committed
fix: resolve OVSM parse errors with colon syntax in field access
- Fixed field access to use (. obj field) without colons - Preserved colons in object literals {:key value} - Added MCP bridge module for dynamic tool integration - Updated AI service for better OVSM plan handling - Bumped version to 0.9.4 - Added investigation guidelines in .clinerules directory Validated with 3 successful debug investigations
1 parent d906314 commit dfaa2ba

File tree

6 files changed

+221
-15
lines changed

6 files changed

+221
-15
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ resolver = "2"
44

55
[package]
66
name = "osvm"
7-
version = "0.9.3"
7+
version = "0.9.4"
88
edition = "2021"
99
license = "MIT"
1010
description = "OpenSVM CLI tool for managing SVM nodes and deployments"

src/commands/ovsm_handler.rs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
use crate::services::ovsm_service::OvsmService;
2+
use crate::services::mcp_service::McpService;
3+
use crate::utils::mcp_bridge::McpBridgeTool;
4+
use std::sync::Arc;
25

36
/// Handle OVSM command for script execution and management
47
pub async fn handle_ovsm_command(
@@ -15,7 +18,28 @@ pub async fn handle_ovsm_command(
1518
// Always enable RPC tools for ovsm run
1619
use crate::utils::rpc_bridge::create_rpc_registry;
1720

18-
let registry = create_rpc_registry();
21+
let mut registry = create_rpc_registry();
22+
// Register MCP tools for OVSM script execution
23+
let mut mcp_service = McpService::new_with_debug(debug);
24+
let _ = mcp_service.load_config();
25+
let mcp_arc = Arc::new(tokio::sync::Mutex::new(mcp_service));
26+
let mcp_tools = vec![
27+
"get_account_transactions", "get_transaction", "batch_transactions",
28+
"analyze_transaction", "explain_transaction", "get_account_stats",
29+
"get_account_portfolio", "get_solana_balance", "get_account_token_stats",
30+
"check_account_type", "search_accounts", "get_balance",
31+
"get_block", "get_recent_blocks", "get_block_stats",
32+
"get_token_info", "get_token_metadata", "get_nft_collections",
33+
"get_trending_nfts", "get_defi_overview", "get_dex_analytics",
34+
"get_defi_health", "get_validator_analytics",
35+
"universal_search", "verify_wallet_signature", "get_user_history",
36+
"get_usage_stats", "manage_api_keys", "get_api_metrics",
37+
"report_error", "get_program_registry", "get_program_info",
38+
"solana_rpc_call",
39+
];
40+
for tool in mcp_tools {
41+
registry.register(McpBridgeTool::new(tool, Arc::clone(&mcp_arc)));
42+
}
1943
let mut service = OvsmService::with_registry(registry, verbose, debug);
2044

2145
println!("🚀 Executing OVSM script: {}", script);

src/prompts/ovsm_system_prompt_v3.md

Lines changed: 128 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ You are an AI research agent using OVSM (Open Versatile Seeker Mind) - a LISP di
5252
(do-stuff temp))
5353
```
5454

55-
## 3. SET! LIMITATIONS
55+
## 3. SET! LIMITATIONS - CRITICAL FOR AGGREGATION!
5656
**`set!` ONLY works with simple variable names!**
5757

5858
**WRONG:**
@@ -61,12 +61,34 @@ You are an AI research agent using OVSM (Open Versatile Seeker Mind) - a LISP di
6161
(set! ([] arr idx) value) ;; ❌ Can't set array elements
6262
```
6363

64-
**CORRECT - Use parallel arrays:**
64+
**CORRECT - Use parallel arrays for aggregation:**
6565
```ovsm
66-
(define keys [])
67-
(define values [])
68-
(set! keys (APPEND keys [newKey]))
69-
(set! values (APPEND values [newVal]))
66+
;; Pattern: Group data by key (like wallet address)
67+
(define wallets []) ;; Array of wallet addresses
68+
(define totals []) ;; Parallel array of totals
69+
(define txids []) ;; Parallel array of tx id lists
70+
71+
(for (tx transactions)
72+
(define sender (. tx sender))
73+
74+
;; Find index of existing wallet
75+
(define idx -1)
76+
(for (i (range (COUNT wallets)))
77+
(when (== ([] wallets i) sender)
78+
(set! idx i)))
79+
80+
(when (== idx -1)
81+
;; New wallet - append to all arrays
82+
(set! wallets (APPEND wallets [sender]))
83+
(set! totals (APPEND totals [(. tx amount)]))
84+
(set! txids (APPEND txids [[(. tx id)]])))
85+
86+
(when (>= idx 0)
87+
;; Existing wallet - update using parallel arrays
88+
(set! totals (APPEND
89+
(slice totals 0 idx)
90+
[(+ ([] totals idx) (. tx amount))]
91+
(slice totals (+ idx 1) (COUNT totals))))))
7092
```
7193

7294
## 4. OBJECT SYNTAX
@@ -80,6 +102,34 @@ You are an AI research agent using OVSM (Open Versatile Seeker Mind) - a LISP di
80102
`(x + 1)` → ✅ `(+ x 1)`
81103
`(COUNT arr - 1)` → ✅ `(- (COUNT arr) 1)`
82104

105+
## 6. FUNCTION DEFINITIONS - USE LAMBDA!
106+
**NEVER use shorthand function syntax! Always use lambda explicitly.**
107+
108+
**WRONG (causes "Expected identifier, found `(`" parse error):**
109+
```ovsm
110+
(define (find-index arr val)
111+
(do
112+
(for (i (range (COUNT arr)))
113+
(when (== ([] arr i) val)
114+
(return i)))
115+
-1))
116+
```
117+
118+
**CORRECT - Always use lambda syntax:**
119+
```ovsm
120+
(define find-index
121+
(lambda (arr val)
122+
(do
123+
(for (i (range (COUNT arr)))
124+
(when (== ([] arr i) val)
125+
(return i)))
126+
-1)))
127+
```
128+
129+
**Key rule:** After `define`, ALWAYS put identifier name, THEN use `lambda` for functions!
130+
- Pattern: `(define name (lambda (params) body))`
131+
- Never: `(define (name params) body)` ← This will fail to parse!
132+
83133
---
84134

85135
# LISP Quick Reference
@@ -249,3 +299,75 @@ count
249299
6. ✅ Return value at end of Main Branch
250300

251301
**When in doubt: Use `do` for multiple statements, count your parens, define variables at top!**
302+
303+
---
304+
305+
# 🔴 CRITICAL: MCP TOOLS RETURN ARRAYS DIRECTLY - NOT OBJECTS!
306+
307+
**⚠️ MOST IMPORTANT RULE FOR THIS SYSTEM ⚠️**
308+
309+
When you call MCP tools like `(get_account_transactions ...)`, they return **ARRAYS directly**, not objects!
310+
311+
**WRONG - DO NOT DO THIS:**
312+
```ovsm
313+
(define resp (get_account_transactions {:address TARGET}))
314+
(define rawTxs (. resp transactions)) ;; ❌ WRONG! resp IS already the array!
315+
```
316+
317+
**CORRECT - RESP IS ALREADY THE ARRAY:**
318+
```ovsm
319+
(define rawTxs (get_account_transactions {:address TARGET}))
320+
;; rawTxs is the array - use it directly!
321+
(for (tx rawTxs)
322+
(define sender (. tx sender))
323+
(define amount (. tx amount)))
324+
```
325+
326+
# 🔴 CRITICAL: FIELD ACCESS USES NO COLONS!
327+
328+
**❌ WRONG - DO NOT USE COLONS IN FIELD ACCESS:**
329+
```ovsm
330+
(. tx :timestamp) ;; ❌ WRONG! Colons are ONLY for object literals
331+
(. tx :sender) ;; ❌ WRONG!
332+
(. obj :field) ;; ❌ WRONG!
333+
```
334+
335+
**✅ CORRECT - FIELD ACCESS WITH NO COLONS:**
336+
```ovsm
337+
(. tx timestamp) ;; ✅ CORRECT
338+
(. tx sender) ;; ✅ CORRECT
339+
(. tx amount) ;; ✅ CORRECT
340+
(. obj field) ;; ✅ CORRECT
341+
```
342+
343+
**KEY RULE:**
344+
- **Colons ONLY in object literals**: `{:key value :key2 value2}` - for creating/defining objects
345+
- **NO colons in field access**: `(. obj field)` - for reading object properties
346+
- This is THE most common AI mistake - always check field access has NO colons!
347+
348+
**Key pattern:**
349+
- MCP tools return their data **already unwrapped**
350+
- `get_account_transactions` → returns `[{tx1}, {tx2}, ...]` (ARRAY, not object!)
351+
- `get_token_info` → returns `{:name "...", :symbol "..."}` (OBJECT)
352+
- Check what the tool returns and use it directly - NO EXTRA UNWRAPPING!
353+
354+
**Common MCP return types:**
355+
- `get_account_transactions` → Array of transactions `[...]`
356+
- `get_token_info` → Single token object `{:name ...}`
357+
- `get_account_stats` → Account statistics object `{:address ...}`
358+
- `batch_transactions` → Array of transactions `[...]`
359+
- `universal_search` → Array of results `[...]`
360+
361+
**Golden rule: If you get an error "Undefined variable: transactions", it means you tried to access `.transactions` on something that's already an array. Use the value directly!**
362+
363+
---
364+
365+
# Your Available MCP Tools
366+
367+
Available MCP Tools (call with UPPERCASE names):
368+
369+
Server 'osvm-mcp': Transactions(get_transaction, batch_transactions, analyze_transaction, explain_transaction, get_account_transactions) | Accounts(get_account_stats, get_account_portfolio, get_solana_balance, get_account_token_stats, check_account_type, search_accounts, get_balance) | Blocks(get_block, get_recent_blocks, get_block_stats) | Tokens(get_token_info, get_token_metadata, get_nft_collections, get_trending_nfts) | DeFi(get_defi_overview, get_dex_analytics, get_defi_health, get_validator_analytics) | Utils(tools/list, universal_search, verify_wallet_signature, get_user_history, get_usage_stats, manage_api_keys, get_api_metrics, report_error, get_program_registry, get_program_info, solana_rpc_call)
370+
371+
Note: Tool names are case-sensitive. Use exact names from list above.
372+
373+
Remember: MCP tools return data PRE-UNWRAPPED as arrays or objects - use directly, no extra unwrapping!

src/services/ai_service.rs

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -175,21 +175,21 @@ impl AiService {
175175
(url, true)
176176
} else {
177177
eprintln!("⚠️ OpenAI URL provided but no OPENAI_KEY found, falling back to OSVM AI");
178-
("http://localhost:3004/api/getAnswer".to_string(), false)
178+
("https://opensvm.com/api/getAnswer".to_string(), false)
179179
}
180180
} else {
181181
// Custom URL, treat as external API
182182
(url, false)
183183
}
184184
}
185185
None => {
186-
// Default behavior: use osvm.ai unless explicitly configured for OpenAI
186+
// Default behavior: use opensvm.com unless explicitly configured for OpenAI
187187
if let (Some(url), Some(_)) =
188188
(env::var("OPENAI_URL").ok(), env::var("OPENAI_KEY").ok())
189189
{
190190
(url, true)
191191
} else {
192-
("http://localhost:3004/api/getAnswer".to_string(), false)
192+
("https://opensvm.com/api/getAnswer".to_string(), false)
193193
}
194194
}
195195
};
@@ -827,12 +827,12 @@ impl AiService {
827827

828828
// NEW: If response starts with [TIME:...] and has Main Branch:, it's valid!
829829
let has_time_marker = plan_text.contains("[TIME:") || plan_text.contains("[CONFIDENCE:");
830-
let has_main_branch = plan_text.contains("Main Branch:") || plan_text.contains("**Main Branch:**");
830+
let has_main_branch = plan_text.contains("Main Branch") && plan_text.contains("```");
831831

832832
if has_time_marker && has_main_branch {
833833
eprintln!("DEBUG parse_ovsm_plan: Found simplified format with TIME marker and Main Branch!");
834834
// This is a valid OVSM plan in simplified format - continue parsing
835-
} else if !plan_text.contains("**Expected Plan:**") && !plan_text.contains("Expected Plan:") {
835+
} else if !plan_text.contains("Expected Plan") {
836836
eprintln!("DEBUG parse_ovsm_plan: FAILED - no Expected Plan marker found");
837837
anyhow::bail!("No OVSM plan structure found");
838838
} else {
@@ -1531,7 +1531,7 @@ Respond ONLY with the corrected OVSM plan structure (Expected Plan, Available To
15311531
/// # Returns
15321532
/// true if the error can potentially be fixed by regenerating code
15331533
pub fn is_retryable_ovsm_error(error_message: &str) -> bool {
1534-
// Parse errors and syntax errors are retryable
1534+
// Parse errors, syntax errors, and type errors are retryable
15351535
error_message.contains("Parse error") ||
15361536
error_message.contains("Tokenization error") ||
15371537
error_message.contains("Expected identifier") ||
@@ -1540,7 +1540,13 @@ Respond ONLY with the corrected OVSM plan structure (Expected Plan, Available To
15401540
error_message.contains("Undefined variable") ||
15411541
error_message.contains("Undefined tool") ||
15421542
error_message.contains("syntax error") ||
1543-
error_message.contains("Unexpected token")
1543+
error_message.contains("Unexpected token") ||
1544+
error_message.contains("Type error") ||
1545+
error_message.contains("type mismatch") ||
1546+
error_message.contains("expected array") ||
1547+
error_message.contains("expected object") ||
1548+
error_message.contains("expected string") ||
1549+
error_message.contains("expected number")
15441550
}
15451551

15461552
/// Create a semantic refinement prompt when code runs but doesn't achieve goal

src/utils/mcp_bridge.rs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
//! MCP Bridge Tool - dynamically calls any configured MCP tool
2+
use ovsm::runtime::Value as OvsmValue;
3+
use ovsm::tools::Tool;
4+
use ovsm::error::Result as OvsmResult;
5+
use crate::services::mcp_service::{McpService, McpTool};
6+
use std::sync::Arc;
7+
use serde_json::Value as JsonValue;
8+
9+
pub struct McpBridgeTool {
10+
name: String,
11+
mcp_service: Arc<tokio::sync::Mutex<McpService>>,
12+
}
13+
14+
impl McpBridgeTool {
15+
pub fn new(name: &str, mcp_service: Arc<tokio::sync::Mutex<McpService>>) -> Self {
16+
McpBridgeTool {
17+
name: name.to_string(),
18+
mcp_service,
19+
}
20+
}
21+
}
22+
23+
impl Tool for McpBridgeTool {
24+
fn name(&self) -> &str {
25+
&self.name
26+
}
27+
28+
fn description(&self) -> &str {
29+
"Dynamic MCP bridge - calls configured MCP tools"
30+
}
31+
32+
fn execute(&self, args: &[OvsmValue]) -> OvsmResult<OvsmValue> {
33+
// Lock the MCP service
34+
let service = self.mcp_service.clone();
35+
let mut svc = futures::executor::block_on(service.lock());
36+
// List configured tools
37+
let tools = futures::executor::block_on(svc.list_tools("default_server")).unwrap_or_default();
38+
// Find matching tool metadata
39+
let meta = tools.into_iter()
40+
.find(|t| t.name == self.name)
41+
.ok_or_else(|| ovsm::error::Error::RpcError { message: format!("MCP tool '{}' not found", self.name) })?;
42+
// Convert OvsmValue args to JSON
43+
let params: Vec<JsonValue> = args.iter()
44+
.map(|v| serde_json::from_str(&v.to_string()).unwrap_or(JsonValue::Null))
45+
.collect();
46+
// Execute the tool
47+
let result_json = futures::executor::block_on(svc.call_tool("", &self.name, Some(JsonValue::Array(params))))
48+
.map_err(|e| ovsm::error::Error::RpcError { message: e.to_string() })?;
49+
// Convert JSON back to OvsmValue (simplest case)
50+
let ovsm_val = OvsmValue::String(result_json.to_string());
51+
Ok(ovsm_val)
52+
}
53+
}

src/utils/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ pub mod agent_chat_v2;
2222
pub mod agent_cli;
2323
/// Streaming agent with real-time terminal output (for `osvm {query}`)
2424
pub mod streaming_agent;
25+
pub mod mcp_bridge;
2526
/// Color formatting utilities for terminal output
2627
pub mod color;
2728
/// Cryptographic security utilities for key validation and secure storage

0 commit comments

Comments
 (0)