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
3 changes: 3 additions & 0 deletions apps/daemon/internal/agent/mcode/declaration.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ func discoverWithCheck(parent context.Context, options agent.DiscoveryOptions, r
workspace := discoverWorkspace(parent, options, runtime)
if runtime.Info.Available {
runtime.Executor = NewExecutorFactory(workspace)
if SupportsExecution(version) {
runtime.View = discoverView(options)
}
}
if workspace != nil {
runtime.SessionCapabilityContext = false
Expand Down
49 changes: 28 additions & 21 deletions apps/daemon/internal/agent/mcode/discovery_workspace.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,31 +36,12 @@ func discoverWorkspace(parent context.Context, options agent.DiscoveryOptions, r
fail(err)
return nil
}
node := os.Getenv("OAC_RUNTIME_MCODE_NODE")
if node == "" {
node = "node"
}
node, err = exec.LookPath(node)
if err != nil {
fail(err)
return nil
}
node, err = filepath.Abs(node)
programs, err := findPrograms()
if err != nil {
fail(err)
return nil
}
binary, err := exec.LookPath(binpath.MCode())
if err != nil {
fail(err)
return nil
}
binary, err = filepath.Abs(binary)
if err != nil {
fail(err)
return nil
}
c, err := ConfigureLocal(binary, node, os.Getenv("OAC_RUNTIME_MCODE_WORKSPACE_BRIDGE"), root, os.Getenv("OAC_RUNTIME_WORKSPACE"), binding.NetworkPolicy())
c, err := ConfigureLocal(programs.binary, programs.node, programs.bridge, root, os.Getenv("OAC_RUNTIME_WORKSPACE"), binding.NetworkPolicy())
if err == nil {
err = CheckWorkspace(parent, c)
}
Expand All @@ -75,3 +56,29 @@ func discoverWorkspace(parent context.Context, options agent.DiscoveryOptions, r
caps.WorkspaceReadPreparation = proto.CapabilitySupported
return &c
}

// programs are the installed node, CLI entry and workspace bridge, as absolute
// host paths.
type programs struct{ node, binary, bridge string }

func findPrograms() (programs, error) {
node := os.Getenv("OAC_RUNTIME_MCODE_NODE")
if node == "" {
node = "node"
}
node, err := exec.LookPath(node)
if err != nil {
return programs{}, err
}
if node, err = filepath.Abs(node); err != nil {
return programs{}, err
}
binary, err := exec.LookPath(binpath.MCode())
if err != nil {
return programs{}, err
}
if binary, err = filepath.Abs(binary); err != nil {
return programs{}, err
}
return programs{node: node, binary: binary, bridge: os.Getenv("OAC_RUNTIME_MCODE_WORKSPACE_BRIDGE")}, nil
}
31 changes: 20 additions & 11 deletions apps/daemon/internal/agent/mcode/environment_mcp.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,26 +10,35 @@ import (
"github.com/MiniMax-AI/OpenAgentCore/internal/agentdaemon/proto"
)

func runtimeMCP(req proto.PromptRequestPayload) ([]map[string]any, error) {
func runtimeMCP(req proto.PromptRequestPayload) ([]map[string]any, []agent.MCPBinding, error) {
bindings, err := agent.ResolveMCPBindings(req)
if err != nil {
return nil, err
return nil, nil, err
}
servers, err := workspaceMCP(bindings, func(binding agent.MCPBinding) (map[string]any, error) {
command, args := localworkspace.MCPStdioCommand(*binding.Stdio)
return map[string]any{"name": binding.ServerLabel, "command": command, "args": args, "env": []map[string]string{}}, nil
})
return servers, bindings, err
}

// workspaceMCP renders the Session's MCP bindings as ACP servers; stdio
// renders a stdio binding.
func workspaceMCP(bindings []agent.MCPBinding, stdio func(agent.MCPBinding) (map[string]any, error)) ([]map[string]any, error) {
var servers []map[string]any
for _, binding := range bindings {
if binding.ServerLabel == "oac_workspace" || binding.ConnectionOrigin != "environment" || binding.AllowedTools != nil || binding.Required {
return nil, fmt.Errorf("mcode: unsupported MCP binding")
}
if binding.Transport == "http" {
server, err := environmentHTTPMCP(binding)
if err != nil {
return nil, err
}
servers = append(servers, server)
} else {
command, args := localworkspace.MCPStdioCommand(*binding.Stdio)
servers = append(servers, map[string]any{"name": binding.ServerLabel, "command": command, "args": args, "env": []map[string]string{}})
render := environmentHTTPMCP
if binding.Transport != "http" {
render = stdio
}
server, err := render(binding)
if err != nil {
return nil, err
}
servers = append(servers, server)
}
return servers, nil
}
Expand Down
47 changes: 27 additions & 20 deletions apps/daemon/internal/agent/mcode/executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,30 +36,37 @@ func NewExecutorFactory(config *WorkspaceConfig) agent.ExecutorFactory {
frozen = &value
}
return func(ctx context.Context, req proto.PromptRequestPayload) (agent.Executor, error) {
if ctx == nil {
ctx = context.Background()
}
if req.RunID != "" || len(req.Input) != 0 || req.ConversationID != "" {
return nil, fmt.Errorf("mcode: Executor configuration cannot contain Turn input")
}
var err error
var opts launchOptions
binary := defaultBinary()
if frozen == nil {
opts, err = prepareOptions(ctx, req)
} else {
if frozen != nil {
binary = frozen.Binary
opts, err = prepareWorkspaceOptions(ctx, *frozen, req)
}
if err != nil {
return nil, err
}
resource, err := newExecutor(ctx, req, opts, binary)
if resource == nil {
return nil, err
}
return resource, err
return startExecutor(ctx, req, binary, func(ctx context.Context) (launchOptions, error) {
if frozen == nil {
return prepareOptions(ctx, req)
}
return prepareWorkspaceOptions(ctx, *frozen, req)
})
}
}

// startExecutor prepares the native owner for req, which carries no Turn
// input, and starts binary.
func startExecutor(ctx context.Context, req proto.PromptRequestPayload, binary string, prepare func(context.Context) (launchOptions, error)) (agent.Executor, error) {
if ctx == nil {
ctx = context.Background()
}
if req.RunID != "" || len(req.Input) != 0 || req.ConversationID != "" {
return nil, fmt.Errorf("mcode: Executor configuration cannot contain Turn input")
}
opts, err := prepare(ctx)
if err != nil {
return nil, err
}
resource, err := newExecutor(ctx, req, opts, binary)
if resource == nil {
return nil, err
}
return resource, err
}

func newExecutor(ctx context.Context, req proto.PromptRequestPayload, opts launchOptions, binary string) (*executor, error) {
Expand Down
16 changes: 3 additions & 13 deletions apps/daemon/internal/agent/mcode/mcp_observations.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,9 @@ package mcode
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"slices"
"strings"

"github.com/MiniMax-AI/OpenAgentCore/apps/daemon/internal/agent"
"github.com/MiniMax-AI/OpenAgentCore/internal/agentdaemon/proto"
)

Expand All @@ -17,18 +14,11 @@ type mcpToolIdentity struct{ server, tool string }
// MiniMax 0.4.12 atomically writes these assignments before exposing tools.
// Read its exact mapping instead of reversing lossy native name normalization.
func (s *Session) environmentMCPIdentity(name string) (*mcpToolIdentity, error) {
if s.req.LocalEnvironment == nil ||
!strings.HasPrefix(name, "mcp__") || strings.HasPrefix(name, "mcp__oac_workspace__") {
bindings := s.opts.bindings
if len(bindings) == 0 || !strings.HasPrefix(name, "mcp__") || strings.HasPrefix(name, "mcp__oac_workspace__") {
return nil, nil
}
bindings, err := agent.ResolveMCPBindings(s.req)
if err != nil {
return nil, err
}
if len(bindings) == 0 {
return nil, nil
}
raw, err := os.ReadFile(filepath.Join(s.opts.DataDir, "mcp-runtime-names.json"))
raw, err := s.opts.readData("mcp-runtime-names.json")
if err != nil {
return nil, fmt.Errorf("mcode: native MCP identity registry unavailable")
}
Expand Down
8 changes: 8 additions & 0 deletions apps/daemon/internal/agent/mcode/mcp_observations_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"strings"
"testing"

"github.com/MiniMax-AI/OpenAgentCore/apps/daemon/internal/agent"
"github.com/MiniMax-AI/OpenAgentCore/internal/agentdaemon/proto"
)

Expand All @@ -18,6 +19,7 @@ func mcpObservationSession(t *testing.T) (*Session, chan proto.Envelope) {
req: proto.PromptRequestPayload{RunID: "run", ObserveToolObservations: true,
LocalEnvironment: &proto.LocalEnvironment{NetworkAccess: "enabled", MCP: []proto.EnvironmentMCP{environmentMCPFixture()}}},
out: out, tools: map[string]toolUpdate{}, completedTools: map[string]bool{}, active: true, sessionID: "native-session"}
resolveTestBindings(s)
if err := writeMCPRegistry(s.opts.DataDir, mcpRegistryEntry("proof.server", "proof_server_2", "read.status", "read_status_2")); err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -200,4 +202,10 @@ func usePublicMCP(s *Session) {
s.req.MCPHTTPServers = &[]proto.MCPHTTPServer{{
ConnectionOrigin: "environment", ServerLabel: "proof.server", ServerURL: "https://mcp.example.test",
}}
resolveTestBindings(s)
}

// resolveTestBindings records the request's bindings as preparation does.
func resolveTestBindings(s *Session) {
s.opts.bindings, _ = agent.ResolveMCPBindings(s.req)
}
Loading
Loading