llm_meta_client 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ee640298ecdea5af5852a316bf54188f52b3a924380b7dcb28d0fde6a786f679
4
- data.tar.gz: 1a35dd306cbb14c0144ea5b5b581ee136caf1b33513ce0ad53559d1015c453c1
3
+ metadata.gz: 620e720960f5e05d563885fa2e00900311d6a7b4543fa13f9d3c476515edd40c
4
+ data.tar.gz: 33981d0657e520260b569873c5547e3279eba4325f88a812d8bf28d18941ac88
5
5
  SHA512:
6
- metadata.gz: 6c61cbaed89ffc56e1e5112cd81895da2434715fb88ff00ab1c6abc8859cd9265d29b3526e84402636adec11b7a99e78318608c1c1ef9c395f3c2a7ecb8df6ec
7
- data.tar.gz: f71f3cedabba96f0ba6f336b409cac79b1f30f84c27bd3538857975f3db40d9f7eb2c73300aabbce35b922893c84a6cd351f469893fad01805631ddccf95bcac
6
+ metadata.gz: a557328075624b4ff7d17eda2ed181e178f5dfda39a23a9f87029141e04b22b78e6f648f81d751f6c92add2f0b54462b28c443bce2d4cf6fcc90f0dee5e1f175
7
+ data.tar.gz: f5b8ecaeec9af8153d8bbbed4d6056ba5213d267fba01381088d1f53bee80d6f1525d04992b79325476da5ce664976da9ab831844b02828e6b2db9279eb02ef0
data/CHANGELOG.md CHANGED
@@ -5,6 +5,43 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.4.0] - 2026-03-11
9
+
10
+ ### Added
11
+
12
+ - MCP (Model Context Protocol) tool selection support:
13
+ - `ServerResource.fetch_mcp_servers` and `ServerResource.fetch_mcp_tools` for retrieving MCP server/tool data from the LLM service
14
+ - `Api::McpServersController` with `index` and `tools` endpoints
15
+ - API routes for MCP servers (`/api/mcp_servers` and `/api/mcp_servers/:uuid/tools`)
16
+ - `tool_ids` parameter support through `ServerQuery`, `Chat` model, and `ChatsController`
17
+ - Tool selector UI component (Stimulus controller + view partial) for selecting MCP tools in chat forms
18
+
19
+ ### Changed
20
+
21
+ - Extracted `authenticated_get` helper in `ServerResource` to reduce duplication in authenticated API calls
22
+
23
+ ### Security
24
+
25
+ - Escape HTML attribute values (`server.uuid`, `tool.id`) in tool selector to prevent XSS
26
+ - Use `CSS.escape()` for `querySelector` and `encodeURIComponent()` for fetch URLs to prevent selector/URL injection
27
+
28
+ ## [0.3.0] - 2026-03-05
29
+
30
+ ### Changed
31
+
32
+ - Update Ruby version requirement from 3.4.8 to 4.0.1
33
+ - Update gem dependencies to latest versions
34
+
35
+ ## [0.2.0] - 2026-03-04
36
+
37
+ ### Changed
38
+
39
+ - Switch configuration to use Rails credentials instead of environment variables
40
+
41
+ ### Documentation
42
+
43
+ - Update README with architectural details, setup instructions, and Rails credentials usage
44
+
8
45
  ## [0.1.0] - 2026-02-27
9
46
 
10
47
  ### Added
@@ -20,6 +20,7 @@ module LlmMetaClient
20
20
  def create_controllers
21
21
  template "app/controllers/chats_controller.rb"
22
22
  template "app/controllers/prompts_controller.rb"
23
+ template "app/controllers/api/mcp_servers_controller.rb"
23
24
  end
24
25
 
25
26
  def create_views
@@ -32,6 +33,7 @@ module LlmMetaClient
32
33
  template "app/views/shared/_family_field.html.erb"
33
34
  template "app/views/shared/_api_key_field.html.erb"
34
35
  template "app/views/shared/_model_field.html.erb"
36
+ template "app/views/shared/_tool_selector_field.html.erb"
35
37
  template "app/views/layouts/application.html.erb"
36
38
  template "app/views/layouts/_header.html.erb"
37
39
  template "app/views/layouts/_sidebar.html.erb"
@@ -41,6 +43,7 @@ module LlmMetaClient
41
43
  template "app/javascript/controllers/llm_selector_controller.js"
42
44
  template "app/javascript/controllers/chats_form_controller.js"
43
45
  template "app/javascript/controllers/chat_title_edit_controller.js"
46
+ template "app/javascript/controllers/tool_selector_controller.js"
44
47
  copy_file "app/javascript/popover.js"
45
48
  end
46
49
 
@@ -69,6 +72,12 @@ module LlmMetaClient
69
72
  end
70
73
  end
71
74
  resources :prompts, only: [ :show ]
75
+
76
+ namespace :api do
77
+ resources :mcp_servers, only: [ :index ], param: :uuid do
78
+ get :tools, on: :member
79
+ end
80
+ end
72
81
  RUBY
73
82
  end
74
83
 
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Api::McpServersController < ApplicationController
4
+ skip_before_action :authenticate_user!, raise: false
5
+ before_action :authenticate_user!
6
+
7
+ def index
8
+ jwt_token = current_user.id_token
9
+ mcp_servers = LlmMetaClient::ServerResource.fetch_mcp_servers(jwt_token)
10
+ render json: { mcp_servers: mcp_servers }
11
+ end
12
+
13
+ def tools
14
+ jwt_token = current_user.id_token
15
+ tools = LlmMetaClient::ServerResource.fetch_mcp_tools(jwt_token, params[:uuid])
16
+ render json: { tools: tools }
17
+ end
18
+ end
@@ -82,7 +82,7 @@ class ChatsController < ApplicationController
82
82
 
83
83
  # Send to LLM and get assistant response
84
84
  begin
85
- @assistant_message = @chat.add_assistant_response(@prompt_execution, jwt_token)
85
+ @assistant_message = @chat.add_assistant_response(@prompt_execution, jwt_token, tool_ids: tool_ids_param)
86
86
  # Generate chat title from the user's prompt (only if title is not yet set)
87
87
  @chat.generate_title(params[:message], jwt_token)
88
88
  rescue StandardError => e
@@ -172,7 +172,7 @@ class ChatsController < ApplicationController
172
172
 
173
173
  # Send to LLM and get assistant response
174
174
  begin
175
- @assistant_message = @chat.add_assistant_response(@prompt_execution, jwt_token)
175
+ @assistant_message = @chat.add_assistant_response(@prompt_execution, jwt_token, tool_ids: tool_ids_param)
176
176
  rescue StandardError => e
177
177
  Rails.logger.error "Error in chat response: #{e.class} - #{e.message}\n#{e.backtrace&.join("\n")}"
178
178
  @error_message = "An error occurred while getting the response. Please try again."
@@ -185,4 +185,10 @@ class ChatsController < ApplicationController
185
185
  format.html { redirect_to new_chat_path }
186
186
  end
187
187
  end
188
+
189
+ private
190
+
191
+ def tool_ids_param
192
+ params[:tool_ids].presence || []
193
+ end
188
194
  end
@@ -0,0 +1,229 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ // Connects to data-controller="tool-selector"
4
+ export default class extends Controller {
5
+ static targets = [
6
+ "toggleButton",
7
+ "toggleIcon",
8
+ "countBadge",
9
+ "panel",
10
+ "loading",
11
+ "serverList",
12
+ ]
13
+
14
+ connect() {
15
+ this.mcpServers = []
16
+ this.expanded = false
17
+ this.selectedToolIds = new Set()
18
+ this.#ensureHiddenFields()
19
+ }
20
+
21
+ toggle() {
22
+ if (!this.hasPanelTarget) return
23
+
24
+ this.expanded = !this.expanded
25
+ this.panelTarget.style.display = this.expanded ? "block" : "none"
26
+
27
+ if (this.hasToggleIconTarget) {
28
+ this.toggleIconTarget.classList.toggle("bi-chevron-down", !this.expanded)
29
+ this.toggleIconTarget.classList.toggle("bi-chevron-up", this.expanded)
30
+ }
31
+
32
+ if (this.expanded && this.mcpServers.length === 0) {
33
+ this.#fetchMcpServers()
34
+ }
35
+ }
36
+
37
+ toggleServer(event) {
38
+ const serverUuid = event.currentTarget.dataset.serverUuid
39
+ const toolsContainer = this.serverListTarget.querySelector(
40
+ `[data-server-tools="${CSS.escape(serverUuid)}"]`
41
+ )
42
+ const icon = event.currentTarget.querySelector(".server-toggle-icon")
43
+
44
+ if (!toolsContainer) return
45
+
46
+ const isVisible = toolsContainer.style.display !== "none"
47
+ toolsContainer.style.display = isVisible ? "none" : "block"
48
+ icon.classList.toggle("bi-chevron-right", isVisible)
49
+ icon.classList.toggle("bi-chevron-down", !isVisible)
50
+
51
+ // Fetch tools if not yet loaded
52
+ if (
53
+ !isVisible &&
54
+ toolsContainer.dataset.loaded !== "true"
55
+ ) {
56
+ this.#fetchToolsForServer(serverUuid, toolsContainer)
57
+ }
58
+ }
59
+
60
+ toggleTool(event) {
61
+ const toolId = event.currentTarget.value
62
+ if (event.currentTarget.checked) {
63
+ this.selectedToolIds.add(toolId)
64
+ } else {
65
+ this.selectedToolIds.delete(toolId)
66
+ }
67
+ this.#updateCountBadge()
68
+ this.#updateHiddenFields()
69
+ }
70
+
71
+ async #fetchMcpServers() {
72
+ this.loadingTarget.style.display = "block"
73
+ this.serverListTarget.innerHTML = ""
74
+
75
+ try {
76
+ const response = await fetch("/api/mcp_servers", {
77
+ headers: { Accept: "application/json" },
78
+ })
79
+
80
+ if (!response.ok) {
81
+ throw new Error(`HTTP ${response.status}`)
82
+ }
83
+
84
+ const data = await response.json()
85
+ this.mcpServers = data.mcp_servers || []
86
+
87
+ if (this.mcpServers.length === 0) {
88
+ this.serverListTarget.innerHTML =
89
+ '<div class="no-servers">No MCP servers available</div>'
90
+ } else {
91
+ this.#renderServerList()
92
+ }
93
+ } catch (e) {
94
+ console.error("Failed to fetch MCP servers:", e)
95
+ this.serverListTarget.innerHTML =
96
+ '<div class="no-servers">Failed to load MCP servers</div>'
97
+ } finally {
98
+ this.loadingTarget.style.display = "none"
99
+ }
100
+ }
101
+
102
+ #renderServerList() {
103
+ this.serverListTarget.innerHTML = ""
104
+
105
+ for (const server of this.mcpServers) {
106
+ if (!server.active) continue
107
+
108
+ const serverDiv = document.createElement("div")
109
+ serverDiv.className = "mcp-server-item"
110
+ const escapedUuid = this.#escapeAttr(server.uuid)
111
+ serverDiv.innerHTML = `
112
+ <div class="mcp-server-header" data-action="click->tool-selector#toggleServer" data-server-uuid="${escapedUuid}">
113
+ <i class="bi bi-chevron-right server-toggle-icon"></i>
114
+ <i class="bi bi-server"></i>
115
+ <span class="mcp-server-name">${this.#escapeHtml(server.name)}</span>
116
+ ${server.tools && server.tools.length > 0 ? `<span class="tool-available-count">${server.tools.filter((t) => t.active).length} tools</span>` : ""}
117
+ </div>
118
+ <div class="mcp-server-tools" data-server-tools="${escapedUuid}" style="display: none;" data-loaded="${server.tools && server.tools.length > 0 ? "true" : "false"}">
119
+ ${server.tools && server.tools.length > 0 ? this.#renderTools(server.tools) : '<div class="tool-loading-inline">Click to load tools...</div>'}
120
+ </div>
121
+ `
122
+ this.serverListTarget.appendChild(serverDiv)
123
+ }
124
+ }
125
+
126
+ #renderTools(tools) {
127
+ const activeTools = tools.filter((t) => t.active)
128
+ if (activeTools.length === 0) {
129
+ return '<div class="no-tools">No active tools</div>'
130
+ }
131
+
132
+ return activeTools
133
+ .map(
134
+ (tool) => `
135
+ <label class="tool-item">
136
+ <input type="checkbox"
137
+ value="${this.#escapeAttr(String(tool.id))}"
138
+ data-action="change->tool-selector#toggleTool"
139
+ ${this.selectedToolIds.has(String(tool.id)) ? "checked" : ""}>
140
+ <div class="tool-info">
141
+ <span class="tool-name">${this.#escapeHtml(tool.name)}</span>
142
+ ${tool.description ? `<span class="tool-description">${this.#escapeHtml(tool.description)}</span>` : ""}
143
+ </div>
144
+ </label>
145
+ `
146
+ )
147
+ .join("")
148
+ }
149
+
150
+ async #fetchToolsForServer(serverUuid, container) {
151
+ container.innerHTML =
152
+ '<div class="tool-loading-inline">Loading tools...</div>'
153
+
154
+ try {
155
+ const response = await fetch(
156
+ `/api/mcp_servers/${encodeURIComponent(serverUuid)}/tools`,
157
+ {
158
+ headers: { Accept: "application/json" },
159
+ }
160
+ )
161
+
162
+ if (!response.ok) {
163
+ throw new Error(`HTTP ${response.status}`)
164
+ }
165
+
166
+ const data = await response.json()
167
+ const tools = data.tools || []
168
+
169
+ container.dataset.loaded = "true"
170
+ container.innerHTML = this.#renderTools(tools)
171
+
172
+ // Update cached server data
173
+ const server = this.mcpServers.find((s) => s.uuid === serverUuid)
174
+ if (server) {
175
+ server.tools = tools
176
+ }
177
+ } catch (e) {
178
+ console.error("Failed to fetch tools:", e)
179
+ container.innerHTML =
180
+ '<div class="no-tools">Failed to load tools</div>'
181
+ }
182
+ }
183
+
184
+ #updateCountBadge() {
185
+ const count = this.selectedToolIds.size
186
+ this.countBadgeTarget.textContent = count
187
+ this.countBadgeTarget.style.display = count > 0 ? "inline-block" : "none"
188
+ }
189
+
190
+ #ensureHiddenFields() {
191
+ // Container for hidden tool_ids fields
192
+ let container = this.element.querySelector(".tool-ids-hidden-fields")
193
+ if (!container) {
194
+ container = document.createElement("div")
195
+ container.className = "tool-ids-hidden-fields"
196
+ container.style.display = "none"
197
+ this.element.appendChild(container)
198
+ }
199
+ }
200
+
201
+ #updateHiddenFields() {
202
+ const container = this.element.querySelector(".tool-ids-hidden-fields")
203
+ if (!container) return
204
+
205
+ container.innerHTML = ""
206
+ for (const toolId of this.selectedToolIds) {
207
+ const input = document.createElement("input")
208
+ input.type = "hidden"
209
+ input.name = "tool_ids[]"
210
+ input.value = toolId
211
+ container.appendChild(input)
212
+ }
213
+ }
214
+
215
+ #escapeHtml(text) {
216
+ const div = document.createElement("div")
217
+ div.textContent = text
218
+ return div.innerHTML
219
+ }
220
+
221
+ #escapeAttr(text) {
222
+ return String(text)
223
+ .replace(/&/g, "&amp;")
224
+ .replace(/"/g, "&quot;")
225
+ .replace(/'/g, "&#39;")
226
+ .replace(/</g, "&lt;")
227
+ .replace(/>/g, "&gt;")
228
+ }
229
+ }
@@ -67,8 +67,8 @@ class Chat < ApplicationRecord
67
67
  end
68
68
 
69
69
  # Add assistant response by sending to LLM
70
- def add_assistant_response(prompt_execution, jwt_token)
71
- response_content = send_to_llm(jwt_token)
70
+ def add_assistant_response(prompt_execution, jwt_token, tool_ids: [])
71
+ response_content = send_to_llm(jwt_token, tool_ids: tool_ids)
72
72
  prompt_execution.update!(
73
73
  llm_platform: llm_type(jwt_token),
74
74
  response: response_content
@@ -122,7 +122,7 @@ class Chat < ApplicationRecord
122
122
  end
123
123
 
124
124
  # Send messages to LLM and get response
125
- def send_to_llm(jwt_token)
125
+ def send_to_llm(jwt_token, tool_ids: [])
126
126
  # Get LLM options
127
127
  llm_options = LlmMetaClient::ServerResource.available_llm_options(jwt_token)
128
128
 
@@ -148,7 +148,7 @@ class Chat < ApplicationRecord
148
148
 
149
149
  summarized_context += "Additional prompt: Responses from the assistant must consist solely of the response body."
150
150
 
151
- # Send chat request using LlmMetaServerQuery
152
- LlmMetaClient::ServerQuery.new.call(jwt_token, llm_uuid, model, summarized_context, prompt)
151
+ # Send chat request using LlmMetaClient::ServerQuery
152
+ LlmMetaClient::ServerQuery.new.call(jwt_token, llm_uuid, model, summarized_context, prompt, tool_ids: tool_ids)
153
153
  end
154
154
  end
@@ -15,6 +15,9 @@
15
15
  <%%= render "shared/api_key_field", stimulus_controller: "llm-selector" %>
16
16
  <%%= render "shared/model_field", stimulus_controller: "llm-selector" %>
17
17
  </div>
18
+ <%% if user_signed_in? %>
19
+ <%%= render "shared/tool_selector_field", stimulus_controller: "tool-selector" %>
20
+ <%% end %>
18
21
  <%% end %>
19
22
  <div class="input-wrapper">
20
23
  <%%= f.text_area :message,
@@ -15,6 +15,9 @@
15
15
  <%%= render "shared/api_key_field", stimulus_controller: "llm-selector" %>
16
16
  <%%= render "shared/model_field", stimulus_controller: "llm-selector" %>
17
17
  </div>
18
+ <%% if user_signed_in? %>
19
+ <%%= render "shared/tool_selector_field", stimulus_controller: "tool-selector" %>
20
+ <%% end %>
18
21
  <%% end %>
19
22
  <div class="input-wrapper">
20
23
  <%%= f.text_area :message,
@@ -0,0 +1,23 @@
1
+ <%%
2
+ stimulus_controller = local_assigns[:stimulus_controller] || "tool-selector"
3
+ %>
4
+ <div class="tool-selector-field" data-controller="<%%= stimulus_controller %>">
5
+ <div class="tool-selector-toggle">
6
+ <button type="button"
7
+ class="tool-toggle-button"
8
+ data-<%%= stimulus_controller %>-target="toggleButton"
9
+ data-action="click-><%%= stimulus_controller %>#toggle">
10
+ <i class="bi bi-tools"></i>
11
+ Tools
12
+ <span class="tool-count-badge" data-<%%= stimulus_controller %>-target="countBadge" style="display: none;">0</span>
13
+ <i class="bi bi-chevron-down toggle-icon" data-<%%= stimulus_controller %>-target="toggleIcon"></i>
14
+ </button>
15
+ </div>
16
+ <div class="tool-selector-panel" data-<%%= stimulus_controller %>-target="panel" style="display: none;">
17
+ <div class="tool-loading" data-<%%= stimulus_controller %>-target="loading" style="display: none;">
18
+ Loading tools...
19
+ </div>
20
+ <div class="mcp-server-list" data-<%%= stimulus_controller %>-target="serverList">
21
+ </div>
22
+ </div>
23
+ </div>
@@ -1,11 +1,11 @@
1
1
  module LlmMetaClient
2
2
  class ServerQuery
3
- def call(id_token, api_key_uuid, model_id, context, user_content)
3
+ def call(id_token, api_key_uuid, model_id, context, user_content, tool_ids: [])
4
4
  debug_log "Context: #{context}"
5
5
  context_and_user_content = "Context:#{context}, User Prompt: #{user_content}"
6
6
  debug_log "Request to LLM: \n===>\n#{context_and_user_content}\n===>"
7
7
 
8
- response = request(api_key_uuid, id_token, model_id, context_and_user_content)
8
+ response = request(api_key_uuid, id_token, model_id, context_and_user_content, tool_ids)
9
9
 
10
10
  raise Exceptions::ServerError, "LLM server returned HTTP #{response.code}" unless response.success?
11
11
 
@@ -28,14 +28,17 @@ module LlmMetaClient
28
28
  Rails.logger.info(message) if Rails.env.development?
29
29
  end
30
30
 
31
- def request(api_key_uuid, id_token, model_id, user_content)
31
+ def request(api_key_uuid, id_token, model_id, user_content, tool_ids)
32
32
  headers = { "Content-Type" => "application/json" }
33
33
  headers["Authorization"] = "Bearer #{id_token}" if id_token.present?
34
34
 
35
+ body = { prompt: user_content.to_s }
36
+ body[:tool_ids] = tool_ids if tool_ids.present?
37
+
35
38
  HTTParty.post(
36
39
  url(api_key_uuid, model_id),
37
40
  headers: headers,
38
- body: { prompt: "#{user_content}" }.to_json,
41
+ body: body.to_json,
39
42
  timeout: 300 # 5 minute timeout setting (both read and connect)
40
43
  )
41
44
  end
@@ -53,6 +53,38 @@ module LlmMetaClient
53
53
  build_families(ollama_opts, api_keys)
54
54
  end
55
55
 
56
+ def fetch_mcp_servers(jwt_token)
57
+ return [] if jwt_token.blank?
58
+
59
+ response = authenticated_get(jwt_token, "api/mcp_servers")
60
+
61
+ if response.success?
62
+ response.parsed_response["mcp_servers"] || []
63
+ else
64
+ Rails.logger.error "Failed to fetch MCP servers: HTTP #{response.code}"
65
+ []
66
+ end
67
+ rescue StandardError => e
68
+ Rails.logger.error "Error fetching MCP servers: #{e.class} - #{e.message}"
69
+ []
70
+ end
71
+
72
+ def fetch_mcp_tools(jwt_token, mcp_server_uuid)
73
+ return [] if jwt_token.blank? || mcp_server_uuid.blank?
74
+
75
+ response = authenticated_get(jwt_token, "api/mcp_servers/#{mcp_server_uuid}/tools")
76
+
77
+ if response.success?
78
+ response.parsed_response["tools"] || []
79
+ else
80
+ Rails.logger.error "Failed to fetch MCP tools for #{mcp_server_uuid}: HTTP #{response.code}"
81
+ []
82
+ end
83
+ rescue StandardError => e
84
+ Rails.logger.error "Error fetching MCP tools: #{e.class} - #{e.message}"
85
+ []
86
+ end
87
+
56
88
  private
57
89
 
58
90
  def build_families(ollama_opts, api_keys)
@@ -110,10 +142,7 @@ module LlmMetaClient
110
142
  end
111
143
 
112
144
  def llm_api_keys(jwt_token)
113
- api_url = "#{Rails.configuration.llm_service_base_url}/api/llm_api_keys"
114
- headers = { "Content-Type" => "application/json", "Authorization" => "Bearer #{jwt_token}" }
115
-
116
- response = HTTParty.get api_url, headers: headers
145
+ response = authenticated_get(jwt_token, "api/llm_api_keys")
117
146
 
118
147
  if response.success?
119
148
  response.parsed_response["llm_api_keys"] || []
@@ -122,6 +151,15 @@ module LlmMetaClient
122
151
  []
123
152
  end
124
153
  end
154
+
155
+ def authenticated_get(jwt_token, path)
156
+ api_url = "#{Rails.configuration.llm_service_base_url}/#{path}"
157
+ headers = {
158
+ "Content-Type" => "application/json",
159
+ "Authorization" => "Bearer #{jwt_token}"
160
+ }
161
+ HTTParty.get(api_url, headers: headers)
162
+ end
125
163
  end
126
164
  end
127
165
  end
@@ -1,3 +1,3 @@
1
1
  module LlmMetaClient
2
- VERSION = "0.3.0"
2
+ VERSION = "0.4.0"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: llm_meta_client
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - dhq_boiler
@@ -101,11 +101,13 @@ files:
101
101
  - lib/generators/llm_meta_client/authentication/templates/config/locales/devise.en.yml
102
102
  - lib/generators/llm_meta_client/authentication/templates/db/migrate/create_users.rb
103
103
  - lib/generators/llm_meta_client/scaffold/scaffold_generator.rb
104
+ - lib/generators/llm_meta_client/scaffold/templates/app/controllers/api/mcp_servers_controller.rb
104
105
  - lib/generators/llm_meta_client/scaffold/templates/app/controllers/chats_controller.rb
105
106
  - lib/generators/llm_meta_client/scaffold/templates/app/controllers/prompts_controller.rb
106
107
  - lib/generators/llm_meta_client/scaffold/templates/app/javascript/controllers/chat_title_edit_controller.js
107
108
  - lib/generators/llm_meta_client/scaffold/templates/app/javascript/controllers/chats_form_controller.js
108
109
  - lib/generators/llm_meta_client/scaffold/templates/app/javascript/controllers/llm_selector_controller.js
110
+ - lib/generators/llm_meta_client/scaffold/templates/app/javascript/controllers/tool_selector_controller.js
109
111
  - lib/generators/llm_meta_client/scaffold/templates/app/javascript/popover.js
110
112
  - lib/generators/llm_meta_client/scaffold/templates/app/models/chat.rb
111
113
  - lib/generators/llm_meta_client/scaffold/templates/app/models/message.rb
@@ -121,6 +123,7 @@ files:
121
123
  - lib/generators/llm_meta_client/scaffold/templates/app/views/shared/_api_key_field.html.erb
122
124
  - lib/generators/llm_meta_client/scaffold/templates/app/views/shared/_family_field.html.erb
123
125
  - lib/generators/llm_meta_client/scaffold/templates/app/views/shared/_model_field.html.erb
126
+ - lib/generators/llm_meta_client/scaffold/templates/app/views/shared/_tool_selector_field.html.erb
124
127
  - lib/generators/llm_meta_client/scaffold/templates/config/initializers/llm_service.rb
125
128
  - lib/generators/llm_meta_client/scaffold/templates/db/migrate/create_chats.rb
126
129
  - lib/generators/llm_meta_client/scaffold/templates/db/migrate/create_messages.rb