返回 DeepSeek-Reasonix
apps_meta.go
根目录 / internal / plugin / apps_meta.go
1 package plugin
2
3 import (
4 "context"
5 "encoding/json"
6
7 "reasonix/internal/tool"
8 )
9
10 type mcpTool struct {
11 Name string `json:"name"`
12 Description string `json:"description"`
13 InputSchema json.RawMessage `json:"inputSchema"`
14 OutputSchema json.RawMessage `json:"outputSchema,omitempty"`
15 // Annotations carries MCP's optional tool hints. readOnlyHint controls reader
16 // classification; destructiveHint remains destructive even when another hint
17 // claims the tool is read-only. Approval policy is applied separately.
18 Annotations *struct {
19 ReadOnlyHint bool `json:"readOnlyHint"`
20 DestructiveHint bool `json:"destructiveHint"`
21 } `json:"annotations"`
22 // Meta carries MCP Apps tool metadata: visibility ("model"/"app") and the
23 // ui resource the App renders from.
24 Meta *mcpToolMeta `json:"_meta,omitempty"`
25 }
26
27 type mcpToolMeta struct {
28 // Visibility is the legacy top-level shape. Stable MCP Apps nests the
29 // field under ui; the nested value wins when both are present.
30 Visibility []string `json:"visibility,omitempty"`
31 // ResourceURI is the pre-2026-01-26 flat key; ui.resourceUri wins.
32 ResourceURI string `json:"resourceUri,omitempty"`
33 // FlatResourceURI is the ext-apps "ui/resourceUri" flat key.
34 FlatResourceURI string `json:"ui/resourceUri,omitempty"`
35 UI *struct {
36 ResourceURI string `json:"resourceUri,omitempty"`
37 CSP map[string][]string `json:"csp,omitempty"`
38 Visibility []string `json:"visibility,omitempty"`
39 } `json:"ui,omitempty"`
40 }
41
42 func (m *mcpToolMeta) effectiveVisibility() []string {
43 if m == nil {
44 return nil
45 }
46 if m.UI != nil && len(m.UI.Visibility) > 0 {
47 return m.UI.Visibility
48 }
49 return m.Visibility
50 }
51
52 // appVisibility resolves the effective visibility: the spec default keeps a
53 // tool in both the model catalog and the App channel.
54 func (m *mcpToolMeta) appVisibility() (model, app bool) {
55 visibility := m.effectiveVisibility()
56 if len(visibility) == 0 {
57 return true, true
58 }
59 for _, v := range visibility {
60 switch v {
61 case "model":
62 model = true
63 case "app":
64 app = true
65 }
66 }
67 return model, app
68 }
69
70 // visibilityCopy is nil-safe for tools without _meta.
71 func (m *mcpToolMeta) visibilityCopy() []string {
72 return append([]string(nil), m.effectiveVisibility()...)
73 }
74
75 func (m *mcpToolMeta) uiResource() (uri string, csp map[string][]string) {
76 if m == nil {
77 return "", nil
78 }
79 if m.UI != nil {
80 if m.UI.ResourceURI != "" {
81 uri = m.UI.ResourceURI
82 }
83 if len(m.UI.CSP) > 0 {
84 csp = m.UI.CSP
85 }
86 }
87 if uri == "" {
88 uri = m.ResourceURI
89 }
90 if uri == "" {
91 uri = m.FlatResourceURI
92 }
93 return uri, csp
94 }
95
96 // stampMCPAppResult attaches the bounded MCP Apps presentation to the call
97 // context when the result carries App-relevant payload (a structured result
98 // or a tool with a ui resource). It never alters the text/image forms.
99 func stampMCPAppResult(ctx context.Context, t *remoteTool, res json.RawMessage) {
100 if t == nil || t.client == nil || !t.client.appsNegotiated() {
101 return
102 }
103 var wire struct {
104 StructuredContent json.RawMessage `json:"structuredContent"`
105 }
106 _ = json.Unmarshal(res, &wire)
107 if t.uiResourceURI == "" && len(wire.StructuredContent) == 0 {
108 return
109 }
110 tool.CollectMCPAppResult(ctx, (&tool.MCPAppResult{
111 Server: t.client.name,
112 Tool: t.rawName,
113 Generation: t.generation,
114 ResourceURI: t.uiResourceURI,
115 CSP: t.uiCSP,
116 RawResult: append(json.RawMessage(nil), res...),
117 Structured: append(json.RawMessage(nil), wire.StructuredContent...),
118 }).Sanitized())
119 }
120
120 lines GO