Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion golem-api-grpc/proto/golem/mcp/core.proto
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@ import "golem/registry/agent.proto";
import "golem/customapi/core.proto";

message CompiledMcp {
reserved 5;
reserved "agent_type_implementers";

golem.common.AccountId account_id = 1;
golem.common.EnvironmentId environment_id = 2;
uint64 deployment_revision = 3;
string domain = 4;
map<string, golem.registry.RegisteredAgentTypeImplementer> agent_type_implementers = 5;
golem.customapi.SecuritySchemeDetails security_scheme = 6;
repeated golem.registry.RegisteredAgentTypeSchema registered_agent_types = 7;
string account_email = 8;
Expand Down
9 changes: 4 additions & 5 deletions golem-registry-service/src/repo/model/deployment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,8 @@ use golem_common::model::tool::{
CompiledToolBinding, RegisteredTool, ToolDeploymentState, ToolName, ToolProvisionConfig,
ToolSource,
};
use golem_common::schema::AgentTypeSchema;
use golem_common::schema::tool::Tool;
use golem_common::schema::{AgentTypeSchema, RegisteredAgentTypeSchema};
use golem_service_base::custom_api::SecuritySchemeDetails;
use golem_service_base::mcp::CompiledMcp;
use golem_service_base::model::component::Component;
Expand Down Expand Up @@ -867,9 +867,9 @@ impl DeploymentRevisionCreationRecord {
#[derive(Debug, Clone, BinaryCodec)]
#[desert(evolution())]
pub struct CompiledMcpData {
pub implementers: golem_service_base::mcp::AgentTypeImplementers,
#[desert(default)]
Comment thread
noise64 marked this conversation as resolved.
pub security_scheme_name: Option<SecuritySchemeName>,
pub registered_agent_types: Vec<RegisteredAgentTypeSchema>,
}

#[derive(FromRow)]
Expand All @@ -891,8 +891,8 @@ impl DeploymentCompiledMcpRecord {
deployment_revision_id: compiled_mcp.deployment_revision.into(),
domain: compiled_mcp.domain.0.clone(),
mcp_data: Blob::new(CompiledMcpData {
implementers: compiled_mcp.agent_type_implementers,
security_scheme_name: compiled_mcp.security_scheme_name.clone(),
registered_agent_types: compiled_mcp.registered_agent_types,
}),
}
}
Expand All @@ -910,10 +910,9 @@ impl TryFrom<DeploymentCompiledMcpRecord> for CompiledMcp {
environment_id: EnvironmentId(value.environment_id),
deployment_revision: value.deployment_revision_id.try_into()?,
domain: Domain(value.domain),
agent_type_implementers: mcp_data.implementers,
security_scheme_name: mcp_data.security_scheme_name,
security_scheme: None, // Will be resolved at runtime
registered_agent_types: Vec::new(),
registered_agent_types: mcp_data.registered_agent_types,
})
}
}
Expand Down
104 changes: 92 additions & 12 deletions golem-registry-service/src/services/deployment/deployment_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,13 @@ use golem_common::model::tool::{
CompiledToolBinding, RegisteredTool, TOOL_METADATA_WIT_VERSION, ToolBindingInput,
ToolDeploymentMetadata, ToolName, ToolSource,
};
use golem_common::schema::AgentTypeSchema;
use golem_common::schema::agent::reachable_defs;
use golem_common::schema::graph::SchemaGraph;
use golem_common::schema::render;
use golem_common::schema::schema_type::SchemaType;
use golem_common::schema::tool::validation::validate_tool;
use golem_common::schema::validation::is_equivalent_cross_graph;
use golem_common::schema::{AgentTypeSchema, RegisteredAgentTypeSchema};
use golem_service_base::custom_api::SecuritySchemeDetails;
use golem_service_base::model::agent_secret::AgentSecret;
use golem_service_base::model::component::Component;
Expand Down Expand Up @@ -436,8 +436,7 @@ impl DeploymentContext {
let mut all_compiled_mcps = Vec::new();

for (domain, mcp_deployment) in &self.mcp_deployments {
let mut agent_type_implementers: golem_service_base::mcp::AgentTypeImplementers =
HashMap::new();
let mut registered_agent_types = Vec::new();

let mut unique_scheme_names: HashSet<&SecuritySchemeName> = HashSet::new();
for (agent_type, agent_options) in &mcp_deployment.agents {
Expand All @@ -451,13 +450,10 @@ impl DeploymentContext {
errors
);

agent_type_implementers.insert(
agent_type.clone(),
(
registered_agent_type.implemented_by.component_id,
registered_agent_type.implemented_by.component_revision,
),
);
registered_agent_types.push(RegisteredAgentTypeSchema {
agent_type: registered_agent_type.agent_type.clone(),
implemented_by: registered_agent_type.implemented_by.clone(),
});

if let Some(name) = &agent_options.security_scheme {
unique_scheme_names.insert(name);
Expand Down Expand Up @@ -490,10 +486,9 @@ impl DeploymentContext {
environment_id: self.environment.id,
deployment_revision,
domain: domain.clone(),
agent_type_implementers,
security_scheme_name,
security_scheme: None, // Will be resolved at runtime
registered_agent_types: Vec::new(),
registered_agent_types,
};
all_compiled_mcps.push(compiled_mcp);
}
Expand Down Expand Up @@ -1070,6 +1065,7 @@ fn validate_final_http_api_router(
#[cfg(test)]
mod tests {
use super::*;
use crate::repo::model::deployment::{CompiledMcpData, DeploymentCompiledMcpRecord};
use golem_common::model::Empty;
use golem_common::model::account::{AccountEmail, AccountId};
use golem_common::model::agent::{AgentMode, Snapshotting};
Expand All @@ -1079,6 +1075,9 @@ mod tests {
use golem_common::model::component_metadata::{ComponentMetadata, KnownExports};
use golem_common::model::environment::{EnvironmentId, EnvironmentName, EnvironmentRevision};
use golem_common::model::json::NormalizedJsonValue;
use golem_common::model::mcp_deployment::{
McpDeployment, McpDeploymentAgentOptions, McpDeploymentId, McpDeploymentRevision,
};
use golem_common::model::tool::{SecretKeyScope, ToolProvisionConfig};
use golem_common::schema::agent::{
AgentConfigDeclarationSchema, AgentConstructorSchema, InputSchema,
Expand All @@ -1088,6 +1087,8 @@ mod tests {
use golem_common::schema::schema_type::{QuotaTokenSpec, SchemaType, SecretSpec};
use golem_common::schema::schema_value::SchemaValue;
use golem_common::schema::tool::{CommandNode, CommandTree, Doc, Globals, Tool};
use golem_service_base::mcp::CompiledMcp;
use golem_service_base::repo::Blob;
use serde_json::json;
use std::collections::BTreeSet;
use test_r::test;
Expand Down Expand Up @@ -1236,6 +1237,85 @@ mod tests {
)
}

fn compile_test_mcp() -> (CompiledMcp, Vec<RegisteredAgentTypeSchema>) {
let environment = test_environment();
let domain = Domain("mcp.example.com".to_string());
let (agent_a_name, agent_a) = test_registered_agent_type("AgentA");
let (agent_b_name, agent_b) = test_registered_agent_type("AgentB");
let (agent_c_name, agent_c) = test_registered_agent_type("AgentC");
let expected = vec![
RegisteredAgentTypeSchema {
agent_type: agent_a.agent_type.clone(),
implemented_by: agent_a.implemented_by.clone(),
},
RegisteredAgentTypeSchema {
agent_type: agent_b.agent_type.clone(),
implemented_by: agent_b.implemented_by.clone(),
},
];
let mcp_deployment = McpDeployment {
id: McpDeploymentId::new(),
revision: McpDeploymentRevision::INITIAL,
environment_id: environment.id,
domain: domain.clone(),
hash: diff::Hash::empty(),
agents: BTreeMap::from([
(agent_b_name.clone(), McpDeploymentAgentOptions::default()),
(agent_a_name.clone(), McpDeploymentAgentOptions::default()),
]),
created_at: chrono::Utc::now(),
};
let context = DeploymentContext {
environment,
components: BTreeMap::new(),
http_api_deployments: BTreeMap::new(),
mcp_deployments: BTreeMap::from([(domain, mcp_deployment)]),
registered_agent_types: HashMap::from([
(agent_a_name, agent_a),
(agent_b_name, agent_b),
(agent_c_name, agent_c),
]),
};
let mut errors = Vec::new();
let mut compiled = context.compile_mcp_deployments(
AccountId::new(),
golem_common::model::deployment::DeploymentRevision::INITIAL,
&HashMap::new(),
&mut errors,
);

assert!(errors.is_empty());
assert_eq!(compiled.len(), 1);

(compiled.pop().unwrap(), expected)
}

#[test]
fn compile_mcp_deployments_includes_selected_registered_agent_types() {
let (compiled, expected) = compile_test_mcp();

assert_eq!(compiled.registered_agent_types, expected);
}

#[test]
fn compiled_mcp_blob_round_trip_preserves_registered_agent_types() {
let (compiled, expected) = compile_test_mcp();
let record = DeploymentCompiledMcpRecord::from_model(compiled);
let serialized = record.mcp_data.serialize().unwrap().clone();
let mcp_data: Blob<CompiledMcpData> = Blob::deserialze(serialized).unwrap();
let restored = CompiledMcp::try_from(DeploymentCompiledMcpRecord {
account_id: record.account_id,
account_email: record.account_email,
environment_id: record.environment_id,
deployment_revision_id: record.deployment_revision_id,
domain: record.domain,
mcp_data,
})
.unwrap();

assert_eq!(restored.registered_agent_types, expected);
}

#[test]
fn compile_tools_registers_unbound_tool_without_agent_bindings() {
let tool_name = ToolName::try_from("grep").unwrap();
Expand Down
40 changes: 0 additions & 40 deletions golem-registry-service/src/services/deployment/mcp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ use crate::repo::deployment::DeploymentRepo;
use crate::repo::model::deployment::DeployRepoError;
use crate::repo::security_scheme::SecuritySchemeRepo;
use golem_common::base_model::domain_registration::Domain;
use golem_common::model::agent::RegisteredAgentType;
use golem_common::schema::RegisteredAgentTypeSchema;
use golem_common::{SafeDisplay, error_forwarding};
use golem_service_base::custom_api::SecuritySchemeDetails;
use golem_service_base::mcp::CompiledMcp;
Expand Down Expand Up @@ -83,44 +81,6 @@ impl DeployedMcpService {
}
}

let mut registered_agent_types = Vec::new();
for agent_type_name in compiled_mcp.agent_type_implementers.keys() {
// Fail loudly on any inconsistency: an active MCP deployment
// must advertise the full set of agent types it claims, or
// none at all. Silently dropping a type would expose a
// partial/empty capability set and hide conversion
// regressions during the schema cutover.
let record = self
.deployment_repo
.get_deployed_agent_type(compiled_mcp.environment_id.0, &agent_type_name.0)
.await?
.ok_or_else(|| {
DeployedMcpError::InternalError(anyhow::anyhow!(
"Agent type {} not found for domain {}",
agent_type_name.0,
domain.0,
))
})?;

let deployed =
golem_common::model::agent::DeployedRegisteredAgentType::try_from(record)
.map_err(|e| {
DeployedMcpError::InternalError(anyhow::anyhow!(
"Failed to convert agent type {} for domain {}: {}",
agent_type_name.0,
domain.0,
e
))
})?;

let registered = RegisteredAgentType::from(deployed);
registered_agent_types.push(RegisteredAgentTypeSchema {
agent_type: registered.agent_type,
implemented_by: registered.implemented_by,
});
}
compiled_mcp.registered_agent_types = registered_agent_types;

Ok(compiled_mcp)
}
None => Err(DeployedMcpError::NoActiveMcpForDomain(domain.clone())),
Expand Down
12 changes: 0 additions & 12 deletions golem-service-base/src/mcp/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,32 +18,20 @@ use golem_common::base_model::account::AccountId;
use golem_common::base_model::deployment::DeploymentRevision;
use golem_common::base_model::domain_registration::Domain;
use golem_common::base_model::environment::EnvironmentId;
use golem_common::model::agent::AgentTypeName;
use golem_common::model::component::{ComponentId, ComponentRevision};
use golem_common::schema::RegisteredAgentTypeSchema;
use std::collections::HashMap;

use crate::custom_api::SecuritySchemeDetails;
use golem_common::model::account::AccountEmail;
use golem_common::model::security_scheme::SecuritySchemeName;

pub type AgentTypeImplementers = HashMap<AgentTypeName, (ComponentId, ComponentRevision)>;

#[derive(Clone)]
pub struct CompiledMcp {
pub account_id: AccountId,
pub account_email: AccountEmail,
pub environment_id: EnvironmentId,
pub deployment_revision: DeploymentRevision,
pub domain: Domain,
pub agent_type_implementers: AgentTypeImplementers,
pub security_scheme_name: Option<SecuritySchemeName>,
pub security_scheme: Option<SecuritySchemeDetails>, // Resolved at runtime
pub registered_agent_types: Vec<RegisteredAgentTypeSchema>,
}

impl CompiledMcp {
pub fn agent_types(&self) -> Vec<AgentTypeName> {
self.agent_type_implementers.keys().cloned().collect()
}
}
26 changes: 1 addition & 25 deletions golem-service-base/src/mcp/protobuf.rs
Original file line number Diff line number Diff line change
@@ -1,29 +1,18 @@
use crate::mcp::{AgentTypeImplementers, CompiledMcp};
use crate::mcp::CompiledMcp;
use golem_common::base_model::domain_registration::Domain;
use golem_common::model::account::AccountEmail;
use golem_common::model::agent::{AgentTypeName, RegisteredAgentTypeImplementer};
use golem_common::schema::RegisteredAgentTypeSchema;

impl From<CompiledMcp> for golem_api_grpc::proto::golem::mcp::CompiledMcp {
fn from(value: CompiledMcp) -> Self {
let registered_agent_types = value.registered_agent_types;
let agent_type_implementers = registered_agent_types
.iter()
.map(|registered_agent_type| {
(
registered_agent_type.agent_type.type_name.0.clone(),
registered_agent_type.implemented_by.clone().into(),
)
})
.collect();

Self {
account_id: Some(value.account_id.into()),
account_email: value.account_email.into_inner(),
environment_id: Some(value.environment_id.into()),
deployment_revision: value.deployment_revision.into(),
domain: value.domain.0,
agent_type_implementers,
security_scheme: value.security_scheme.map(|s| s.into()),
registered_agent_types: registered_agent_types
.into_iter()
Expand All @@ -39,18 +28,6 @@ impl TryFrom<golem_api_grpc::proto::golem::mcp::CompiledMcp> for CompiledMcp {
fn try_from(
value: golem_api_grpc::proto::golem::mcp::CompiledMcp,
) -> Result<Self, Self::Error> {
let agent_type_implementers: AgentTypeImplementers = value
.agent_type_implementers
.into_iter()
.map(|(name, implementer)| {
let implementer: RegisteredAgentTypeImplementer = implementer.try_into()?;
Ok((
AgentTypeName(name),
(implementer.component_id, implementer.component_revision),
))
})
.collect::<Result<_, String>>()?;

let registered_agent_types: Vec<RegisteredAgentTypeSchema> = value
.registered_agent_types
.into_iter()
Expand All @@ -74,7 +51,6 @@ impl TryFrom<golem_api_grpc::proto::golem::mcp::CompiledMcp> for CompiledMcp {
.try_into()
.map_err(|e| format!("Invalid deployment_revision: {}", e))?,
domain: Domain(value.domain),
agent_type_implementers,
security_scheme_name: value
.security_scheme
.as_ref()
Expand Down
Loading