| 1 | package plugin |
| 2 | |
| 3 | import ( |
| 4 | "context" |
| 5 | "encoding/json" |
| 6 | "log/slog" |
| 7 | ) |
| 8 | |
| 9 | func (c *Client) initialize(ctx context.Context) error { |
| 10 | res, err := c.call(ctx, "initialize", nil) |
| 11 | if err != nil { |
| 12 | return err |
| 13 | } |
| 14 | // Record which optional capabilities the server advertises. Presence of the |
| 15 | // key (even with an empty object) signals support. |
| 16 | var ir struct { |
| 17 | ProtocolVersion string `json:"protocolVersion"` |
| 18 | Capabilities map[string]json.RawMessage `json:"capabilities"` |
| 19 | } |
| 20 | if err := json.Unmarshal(res, &ir); err != nil { |
| 21 | slog.Warn("plugin: parse initialize capabilities", "server", c.name, "err", err) |
| 22 | } |
| 23 | c.protocolVersion = ir.ProtocolVersion |
| 24 | toolsCapability, hasTools := ir.Capabilities["tools"] |
| 25 | c.capabilities.tools = hasTools |
| 26 | c.capabilities.toolsListChanged = false |
| 27 | if hasTools && len(toolsCapability) > 0 { |
| 28 | var advertised struct { |
| 29 | ListChanged bool `json:"listChanged"` |
| 30 | } |
| 31 | if err := json.Unmarshal(toolsCapability, &advertised); err != nil { |
| 32 | slog.Warn("plugin: parse tools capability", "server", c.name, "err", err) |
| 33 | } else { |
| 34 | c.capabilities.toolsListChanged = advertised.ListChanged |
| 35 | } |
| 36 | } |
| 37 | promptsCapability, hasPrompts := ir.Capabilities["prompts"] |
| 38 | c.capabilities.prompts = hasPrompts |
| 39 | c.capabilities.promptsListChanged = capabilityListChanged(promptsCapability) |
| 40 | resourcesCapability, hasResources := ir.Capabilities["resources"] |
| 41 | c.capabilities.resources = hasResources |
| 42 | c.capabilities.resourcesListChanged = capabilityListChanged(resourcesCapability) |
| 43 | c.capabilities.serverExtensions = parseServerExtensions(ir.Capabilities["extensions"]) |
| 44 | return nil |
| 45 | } |
| 46 | |
| 47 | // parseServerExtensions reads the server's declared extension IDs from the |
| 48 | // initialize capabilities. MCP Apps requires two-way agreement, so the raw |
| 49 | // settings are kept server-side; only the ID set matters here. |
| 50 | func parseServerExtensions(raw json.RawMessage) map[string]bool { |
| 51 | if len(raw) == 0 { |
| 52 | return nil |
| 53 | } |
| 54 | var declared map[string]json.RawMessage |
| 55 | if err := json.Unmarshal(raw, &declared); err != nil || len(declared) == 0 { |
| 56 | return nil |
| 57 | } |
| 58 | extensions := make(map[string]bool, len(declared)) |
| 59 | for id := range declared { |
| 60 | extensions[id] = true |
| 61 | } |
| 62 | return extensions |
| 63 | } |
| 64 | |
| 65 | // elicitationUsable reports that a live negotiated session can deliver either |
| 66 | // legacy push-style or 2026 multi-round-trip elicitation to the client handler. |
| 67 | func (c *Client) elicitationUsable() bool { |
| 68 | return c.protocolVersion != "" |
| 69 | } |
| 70 | |
| 71 | func capabilityListChanged(capability json.RawMessage) bool { |
| 72 | if len(capability) == 0 { |
| 73 | return false |
| 74 | } |
| 75 | var advertised struct { |
| 76 | ListChanged bool `json:"listChanged"` |
| 77 | } |
| 78 | return json.Unmarshal(capability, &advertised) == nil && advertised.ListChanged |
| 79 | } |
| 80 |