返回 DeepSeek-Reasonix
mcp_apps.go
根目录 / internal / control / mcp_apps.go
1 package control
2
3 import (
4 "encoding/json"
5 "errors"
6 "fmt"
7 "time"
8
9 "reasonix/internal/agent"
10 "reasonix/internal/event"
11 "reasonix/internal/mcpinteraction"
12 "reasonix/internal/provider"
13 )
14
15 // MCPAppCallTool executes one App-initiated tools/call. Security model:
16 // plugin.AppInstanceTool enforces same-server, app visibility, and catalog
17 // generation; the controller then applies the session permission policy and
18 // hooks around the dispatch, records nested ToolDispatch/ToolResult events
19 // under the instance's parent id, and stores a LocalOnly transcript message —
20 // visible in the UI, never added to model context.
21 func (c *Controller) MCPAppCallTool(instanceToken, toolName string, args json.RawMessage) (string, error) {
22 if c == nil {
23 return "", fmt.Errorf("no controller")
24 }
25 host := c.mcp.hostRef()
26 if host == nil {
27 return "", fmt.Errorf("no MCP runtime")
28 }
29 ref, ok := host.AppInstanceTool(instanceToken, toolName)
30 if !ok {
31 return "", fmt.Errorf("app tool call refused: unknown instance, cross-server target, non-app tool, or stale catalog")
32 }
33 inst, _ := host.LookupAppInstance(instanceToken)
34 callCtx, ok := host.AppInstanceContext(instanceToken)
35 if !ok || callCtx.Err() != nil {
36 return "", fmt.Errorf("app tool call refused: instance is closed")
37 }
38 target := ref.UITool()
39 callID := fmt.Sprintf("mcp-app-%d", time.Now().UnixNano())
40 parentID := ""
41 if inst != nil {
42 parentID = inst.CallID
43 }
44
45 argsJSON := args
46 if len(argsJSON) == 0 {
47 argsJSON = json.RawMessage(`{}`)
48 }
49 toolEvent := event.Tool{
50 ID: callID, ParentID: parentID,
51 Name: target.Name(),
52 Args: string(argsJSON),
53 ReadOnly: target.ReadOnly(),
54 }
55 if err := event.EmitChecked(c.sink, event.Event{Kind: event.ToolDispatch, Tool: toolEvent}); err != nil {
56 return "", fmt.Errorf("persist app dispatch: %w", err)
57 }
58
59 ctx := agent.WithToolCallContext(callCtx, callID, c.sink, c, false)
60 ctx = mcpinteraction.WithBroker(ctx, c)
61
62 if c.hooks != nil {
63 c.hooks.PreToolUse(ctx, target.Name(), argsJSON)
64 }
65 rawResult, output, reportedError, err := target.ExecuteForApp(ctx, argsJSON)
66 hookErr := err
67 if hookErr == nil && reportedError {
68 hookErr = errors.New("MCP tool returned isError")
69 }
70 if c.hooks != nil {
71 if hookErr != nil {
72 c.hooks.PostToolUseFailure(ctx, target.Name(), argsJSON, output, hookErr)
73 } else {
74 c.hooks.PostToolUse(ctx, target.Name(), argsJSON, output)
75 }
76 }
77
78 toolEvent.Output = output
79 if hookErr != nil {
80 toolEvent.Err = hookErr.Error()
81 }
82 committedMessage := provider.Message{
83 ID: agent.NewMessageID(),
84 Role: provider.RoleTool, LocalOnly: true,
85 ToolCallID: callID, Name: target.Name(),
86 Content: output,
87 }
88 if emitErr := event.EmitChecked(c.sink, event.Event{Kind: event.ToolResult, Tool: toolEvent, CommittedMessage: &committedMessage}); emitErr != nil {
89 return string(rawResult), fmt.Errorf("persist app result: %w", emitErr)
90 }
91 if c.executor != nil && c.executor.Session() != nil {
92 c.executor.Session().Add(committedMessage)
93 }
94 return string(rawResult), err
95 }
96
96 lines GO