Skip to content

Commit d2dfd03

Browse files
authored
Fixed an issue with agent tools and added the option to create customers (#18)
1 parent 8a0971b commit d2dfd03

File tree

6 files changed

+319
-9
lines changed

6 files changed

+319
-9
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ Cargo.lock
4141
.DS_Store
4242
Thumbs.db
4343
.windsurfrules
44-
CLAUDE.md
44+
CLAUDE.local.md
4545

4646
**/.claude/settings.local.json
4747
backup/

tap-mcp/src/tools/agent_tools.rs

Lines changed: 64 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -58,15 +58,73 @@ impl ToolHandler for CreateAgentTool {
5858

5959
debug!("Creating new agent with auto-generated DID");
6060

61-
// Create an ephemeral agent for simplicity
61+
// Create an ephemeral agent
6262
use std::sync::Arc;
63-
use tap_agent::TapAgent;
63+
use tap_agent::storage::KeyStorage;
64+
use tap_agent::{
65+
did::{DIDGenerationOptions, DIDKeyGenerator, KeyType},
66+
TapAgent,
67+
};
6468

65-
let (agent, generated_did) = TapAgent::from_ephemeral_key()
66-
.await
67-
.map_err(|e| Error::tool_execution(format!("Failed to create agent: {}", e)))?;
69+
// Generate a new key
70+
let generator = DIDKeyGenerator::new();
71+
let generated_key = generator
72+
.generate_did(DIDGenerationOptions {
73+
key_type: KeyType::Ed25519,
74+
})
75+
.map_err(|e| Error::tool_execution(format!("Failed to generate DID: {}", e)))?;
76+
77+
debug!("Generated new DID for agent: {}", generated_key.did);
78+
79+
// Create the agent from the generated key
80+
let (agent, generated_did) = TapAgent::from_private_key(
81+
&generated_key.private_key,
82+
generated_key.key_type,
83+
false, // debug mode
84+
)
85+
.await
86+
.map_err(|e| Error::tool_execution(format!("Failed to create agent: {}", e)))?;
87+
88+
// Save the key to storage with the label
89+
let mut key_storage = match KeyStorage::load_default() {
90+
Ok(storage) => storage,
91+
Err(e) => {
92+
debug!("Could not load existing key storage ({}), creating new", e);
93+
KeyStorage::new()
94+
}
95+
};
96+
97+
// Create a StoredKey with the label
98+
let stored_key = if let Some(ref label) = params.label {
99+
tap_agent::storage::KeyStorage::from_generated_key_with_label(&generated_key, label)
100+
} else {
101+
tap_agent::storage::KeyStorage::from_generated_key(&generated_key)
102+
};
103+
104+
// Add the key to storage
105+
key_storage.add_key(stored_key);
68106

69-
debug!("Generated new DID for agent: {}", generated_did);
107+
debug!("Current keys in storage: {}", key_storage.keys.len());
108+
for (did, key) in &key_storage.keys {
109+
debug!(" - {}: {}", did, key.label);
110+
}
111+
112+
// Save the storage
113+
match key_storage.save_default() {
114+
Ok(_) => {
115+
info!(
116+
"Successfully saved agent key to storage with label: {:?}",
117+
params.label
118+
);
119+
}
120+
Err(e) => {
121+
error!("Failed to save key storage: {}", e);
122+
return Err(Error::tool_execution(format!(
123+
"Failed to save key storage: {}",
124+
e
125+
)));
126+
}
127+
}
70128

71129
// Register the agent with the TapNode
72130
match self

tap-mcp/src/tools/customer_tools.rs

Lines changed: 225 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,12 @@ use crate::error::{Error, Result};
66
use crate::mcp::protocol::{CallToolResult, Tool};
77
use crate::tap_integration::TapIntegration;
88
use serde::{Deserialize, Serialize};
9-
use serde_json::Value;
9+
use serde_json::{json, Value};
1010
use std::collections::{HashMap, HashSet};
1111
use std::sync::Arc;
1212
use tap_msg::message::TapMessage;
1313
use tap_node::customer::CustomerManager;
14+
use tap_node::storage::models::{Customer, SchemaType};
1415
use tracing::{debug, error};
1516

1617
/// Tool for listing customers (parties that an agent acts for)
@@ -740,6 +741,229 @@ impl ToolHandler for UpdateCustomerProfileTool {
740741
}
741742
}
742743

744+
/// Tool for creating a new customer
745+
pub struct CreateCustomerTool {
746+
tap_integration: Arc<TapIntegration>,
747+
}
748+
749+
/// Parameters for creating a customer
750+
#[derive(Debug, Deserialize)]
751+
struct CreateCustomerParams {
752+
agent_did: String,
753+
customer_id: String,
754+
profile_data: Value,
755+
}
756+
757+
impl CreateCustomerTool {
758+
pub fn new(tap_integration: Arc<TapIntegration>) -> Self {
759+
Self { tap_integration }
760+
}
761+
762+
fn tap_integration(&self) -> &TapIntegration {
763+
&self.tap_integration
764+
}
765+
}
766+
767+
#[async_trait::async_trait]
768+
impl ToolHandler for CreateCustomerTool {
769+
async fn handle(&self, arguments: Option<Value>) -> Result<CallToolResult> {
770+
let params: CreateCustomerParams = match arguments {
771+
Some(args) => serde_json::from_value(args)
772+
.map_err(|e| Error::invalid_parameter(format!("Invalid parameters: {}", e)))?,
773+
None => {
774+
return Ok(error_text_response(
775+
"Missing required parameters".to_string(),
776+
))
777+
}
778+
};
779+
780+
debug!(
781+
"Creating customer {} via agent {}",
782+
params.customer_id, params.agent_did
783+
);
784+
785+
// Get storage for the agent
786+
let storage = match self
787+
.tap_integration()
788+
.storage_for_agent(&params.agent_did)
789+
.await
790+
{
791+
Ok(storage) => storage,
792+
Err(e) => {
793+
error!(
794+
"Failed to get storage for agent {}: {}",
795+
params.agent_did, e
796+
);
797+
return Ok(error_text_response(format!(
798+
"Failed to get storage for agent {}: {}",
799+
params.agent_did, e
800+
)));
801+
}
802+
};
803+
804+
// Create customer manager
805+
let customer_manager = CustomerManager::new(storage.clone());
806+
807+
// Check if customer already exists
808+
let existing = match storage.get_customer(&params.customer_id).await {
809+
Ok(existing) => existing,
810+
Err(e) => {
811+
error!("Failed to check existing customer: {}", e);
812+
return Ok(error_text_response(format!(
813+
"Failed to check existing customer: {}",
814+
e
815+
)));
816+
}
817+
};
818+
819+
if existing.is_none() {
820+
// Create new customer
821+
let display_name = params
822+
.profile_data
823+
.get("givenName")
824+
.and_then(|v| v.as_str())
825+
.map(|given| {
826+
if let Some(family) = params
827+
.profile_data
828+
.get("familyName")
829+
.and_then(|v| v.as_str())
830+
{
831+
format!("{} {}", given, family)
832+
} else {
833+
given.to_string()
834+
}
835+
});
836+
837+
// Create customer profile from schema.org data
838+
let mut profile = json!({
839+
"@context": "https://schema.org",
840+
"@type": "Person",
841+
"identifier": params.customer_id.clone(),
842+
});
843+
844+
// Merge provided profile data
845+
if let Value::Object(profile_obj) = &mut profile {
846+
if let Value::Object(data_obj) = &params.profile_data {
847+
for (key, value) in data_obj {
848+
profile_obj.insert(key.clone(), value.clone());
849+
}
850+
}
851+
}
852+
853+
// Determine schema type based on provided data
854+
let schema_type = if params.profile_data.get("@type").and_then(|v| v.as_str())
855+
== Some("Organization")
856+
{
857+
SchemaType::Organization
858+
} else {
859+
SchemaType::Person
860+
};
861+
862+
// Create Customer struct
863+
let customer = Customer {
864+
id: params.customer_id.clone(),
865+
agent_did: params.agent_did.clone(),
866+
schema_type,
867+
given_name: params
868+
.profile_data
869+
.get("givenName")
870+
.and_then(|v| v.as_str())
871+
.map(String::from),
872+
family_name: params
873+
.profile_data
874+
.get("familyName")
875+
.and_then(|v| v.as_str())
876+
.map(String::from),
877+
display_name,
878+
legal_name: params
879+
.profile_data
880+
.get("legalName")
881+
.and_then(|v| v.as_str())
882+
.map(String::from),
883+
lei_code: params
884+
.profile_data
885+
.get("leiCode")
886+
.and_then(|v| v.as_str())
887+
.map(String::from),
888+
mcc_code: params
889+
.profile_data
890+
.get("mccCode")
891+
.and_then(|v| v.as_str())
892+
.map(String::from),
893+
address_country: params
894+
.profile_data
895+
.get("addressCountry")
896+
.and_then(|v| v.as_str())
897+
.map(String::from),
898+
address_locality: params
899+
.profile_data
900+
.get("addressLocality")
901+
.and_then(|v| v.as_str())
902+
.map(String::from),
903+
postal_code: params
904+
.profile_data
905+
.get("postalCode")
906+
.and_then(|v| v.as_str())
907+
.map(String::from),
908+
street_address: params
909+
.profile_data
910+
.get("streetAddress")
911+
.and_then(|v| v.as_str())
912+
.map(String::from),
913+
profile,
914+
ivms101_data: None,
915+
verified_at: None,
916+
created_at: chrono::Utc::now().to_rfc3339(),
917+
updated_at: chrono::Utc::now().to_rfc3339(),
918+
};
919+
920+
// Create the customer
921+
match storage.upsert_customer(&customer).await {
922+
Ok(_) => {
923+
debug!("Created new customer {}", params.customer_id);
924+
Ok(success_text_response(format!(
925+
"Successfully created customer {}",
926+
params.customer_id
927+
)))
928+
}
929+
Err(e) => {
930+
error!("Failed to create customer: {}", e);
931+
Ok(error_text_response(format!(
932+
"Failed to create customer: {}",
933+
e
934+
)))
935+
}
936+
}
937+
} else {
938+
// Update existing customer
939+
match customer_manager
940+
.update_customer_profile(&params.customer_id, params.profile_data)
941+
.await
942+
{
943+
Ok(_) => Ok(success_text_response(format!(
944+
"Successfully updated existing customer {}",
945+
params.customer_id
946+
))),
947+
Err(e) => {
948+
error!("Failed to update customer: {}", e);
949+
Ok(error_text_response(format!(
950+
"Failed to update customer: {}",
951+
e
952+
)))
953+
}
954+
}
955+
}
956+
}
957+
958+
fn get_definition(&self) -> Tool {
959+
Tool {
960+
name: "tap_create_customer".to_string(),
961+
description: "Creates a new customer profile for an agent. The customer_id should be a DID or unique identifier. The profile_data should be a JSON object with schema.org fields (e.g., givenName, familyName, addressCountry). If a customer with the same ID already exists, their profile will be updated.".to_string(),
962+
input_schema: schema::create_customer_schema(),
963+
}
964+
}
965+
}
966+
743967
/// Tool for updating customer from IVMS101 data
744968
pub struct UpdateCustomerFromIvms101Tool {
745969
tap_integration: Arc<TapIntegration>,

tap-mcp/src/tools/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,10 @@ impl ToolRegistry {
140140
"tap_generate_ivms101".to_string(),
141141
Box::new(GenerateIvms101Tool::new(tap_integration.clone())),
142142
);
143+
tools.insert(
144+
"tap_create_customer".to_string(),
145+
Box::new(CreateCustomerTool::new(tap_integration.clone())),
146+
);
143147
tools.insert(
144148
"tap_update_customer_profile".to_string(),
145149
Box::new(UpdateCustomerProfileTool::new(tap_integration.clone())),

tap-mcp/src/tools/schema.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -445,6 +445,29 @@ pub fn update_customer_profile_schema() -> Value {
445445
})
446446
}
447447

448+
/// Schema for create_customer tool
449+
pub fn create_customer_schema() -> Value {
450+
json!({
451+
"type": "object",
452+
"properties": {
453+
"agent_did": {
454+
"type": "string",
455+
"description": "The DID of the agent that will manage this customer"
456+
},
457+
"customer_id": {
458+
"type": "string",
459+
"description": "The ID of the customer to create (should be a DID or unique identifier)"
460+
},
461+
"profile_data": {
462+
"type": "object",
463+
"description": "Schema.org profile data for the customer (e.g., givenName, familyName, email, telephone, addressCountry, addressLocality, postalCode)"
464+
}
465+
},
466+
"required": ["agent_did", "customer_id", "profile_data"],
467+
"additionalProperties": false
468+
})
469+
}
470+
448471
/// Schema for update_customer_from_ivms101 tool
449472
pub fn update_customer_from_ivms101_schema() -> Value {
450473
json!({

tap-mcp/tests/integration_tests.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ async fn test_list_tools() -> Result<()> {
174174

175175
if let Some(result) = response.result {
176176
let tools = result["tools"].as_array().unwrap();
177-
assert_eq!(tools.len(), 30); // All 30 tools should be available (including complete, revert, communication, delivery, customer, received message tools, database tools, agent management tools, and policy tools)
177+
assert_eq!(tools.len(), 31); // All 31 tools should be available (including complete, revert, communication, delivery, customer, received message tools, database tools, agent management tools, and policy tools)
178178

179179
let tool_names: Vec<&str> = tools.iter().map(|t| t["name"].as_str().unwrap()).collect();
180180

@@ -193,6 +193,7 @@ async fn test_list_tools() -> Result<()> {
193193
assert!(tool_names.contains(&"tap_list_deliveries_by_thread"));
194194
assert!(tool_names.contains(&"tap_list_customers"));
195195
assert!(tool_names.contains(&"tap_list_connections"));
196+
assert!(tool_names.contains(&"tap_create_customer"));
196197
assert!(tool_names.contains(&"tap_list_received"));
197198
assert!(tool_names.contains(&"tap_get_pending_received"));
198199
assert!(tool_names.contains(&"tap_view_raw_received"));

0 commit comments

Comments
 (0)