insika 0.3.0 → 0.8.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.
Files changed (204) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +296 -0
  3. data/README.md +48 -12
  4. data/bin/insika +725 -0
  5. data/bin/insika-router +87 -0
  6. data/docs/AGENTS.md +116 -406
  7. data/docs/API.md +5 -5
  8. data/docs/ARCHITECTURE.md +3 -2
  9. data/docs/ARTIFACTS.md +137 -0
  10. data/docs/BENCHMARK.md +2 -2
  11. data/docs/CHANNELS.md +14 -14
  12. data/docs/CONTEXT.md +63 -19
  13. data/docs/DEMO.md +80 -0
  14. data/docs/DEPLOY.md +87 -10
  15. data/docs/EMBEDDING.md +1 -1
  16. data/docs/EVALS.md +128 -3
  17. data/docs/FACTS.md +3 -3
  18. data/docs/HARVEST.md +5 -6
  19. data/docs/KNOWLEDGE.md +290 -0
  20. data/docs/LOADTEST.md +17 -29
  21. data/docs/MEDIA.md +128 -0
  22. data/docs/OBSERVABILITY.md +46 -12
  23. data/docs/OUTCOMES.md +137 -0
  24. data/docs/PLUGINS.md +51 -6
  25. data/docs/POLICY.md +222 -0
  26. data/docs/REFINEMENT.md +14 -9
  27. data/docs/RELEASING.md +4 -4
  28. data/docs/ROUTER.md +213 -0
  29. data/docs/RUNNING-LOCAL.md +5 -5
  30. data/docs/SCHEDULING.md +121 -0
  31. data/docs/SECURITY.md +23 -7
  32. data/docs/SKILLS.md +11 -2
  33. data/docs/SOAK.md +3 -3
  34. data/docs/TEMPLATES.md +134 -0
  35. data/docs/TOOLS.md +176 -27
  36. data/docs/WHY.md +1 -1
  37. data/docs/WORKFLOWS.md +2 -2
  38. data/docs/_includes/head_custom.html +5 -0
  39. data/docs/_includes/title.html +13 -0
  40. data/docs/_sass/color_schemes/insika.scss +32 -0
  41. data/docs/_sass/custom/custom.scss +199 -0
  42. data/docs/_sass/custom/setup.scss +26 -0
  43. data/docs/assets/img/favicon.svg +7 -0
  44. data/docs/assets/img/insika-mark.svg +7 -0
  45. data/docs/core-concepts.md +21 -0
  46. data/docs/domain.md +4 -4
  47. data/docs/improve.md +20 -0
  48. data/docs/index.md +8 -5
  49. data/docs/integrate.md +20 -0
  50. data/docs/operate.md +13 -6
  51. data/docs/prompts/ADD-TOOL.md +118 -0
  52. data/docs/prompts/DIAGNOSE-TURN.md +65 -0
  53. data/docs/prompts/GO-LIVE.md +138 -0
  54. data/docs/prompts/RUN-EXAMPLES.md +70 -0
  55. data/docs/reference.md +19 -0
  56. data/docs/ship.md +10 -2
  57. data/docs/start-here.md +18 -0
  58. data/lib/insika/agent_profile.rb +99 -17
  59. data/lib/insika/artifact_signing.rb +82 -0
  60. data/lib/insika/artifact_store.rb +160 -0
  61. data/lib/insika/channel_delivery.rb +1 -1
  62. data/lib/insika/chat_builder.rb +50 -19
  63. data/lib/insika/commands/agent_payload.rb +2 -2
  64. data/lib/insika/commands/backfill_knowledge.rb +145 -0
  65. data/lib/insika/commands/delete_artifact.rb +35 -0
  66. data/lib/insika/commands/delete_concept.rb +34 -0
  67. data/lib/insika/commands/delete_mcp.rb +6 -2
  68. data/lib/insika/commands/delete_tenant_data.rb +15 -3
  69. data/lib/insika/commands/gate_refinement.rb +1 -1
  70. data/lib/insika/commands/refresh_mcp_tools.rb +47 -0
  71. data/lib/insika/commands/restore_concept.rb +34 -0
  72. data/lib/insika/commands/seed_demo_data.rb +31 -0
  73. data/lib/insika/commands/upsert_mcp.rb +6 -3
  74. data/lib/insika/commands/write_concept.rb +57 -0
  75. data/lib/insika/compaction.rb +196 -0
  76. data/lib/insika/context/builder.rb +6 -2
  77. data/lib/insika/context/fragment.rb +4 -1
  78. data/lib/insika/context/priority.rb +8 -0
  79. data/lib/insika/context/providers/briefing.rb +53 -24
  80. data/lib/insika/context/providers/knowledge.rb +108 -0
  81. data/lib/insika/context/providers/prompt.rb +30 -24
  82. data/lib/insika/context/providers/session.rb +46 -10
  83. data/lib/insika/context_trace_store.rb +11 -1
  84. data/lib/insika/cron.rb +189 -0
  85. data/lib/insika/demo/agent_attrs.rb +43 -0
  86. data/lib/insika/demo/golden_cases.rb +81 -0
  87. data/lib/insika/demo/seeder.rb +336 -0
  88. data/lib/insika/doctor.rb +280 -17
  89. data/lib/insika/dsl/definition.rb +3 -2
  90. data/lib/insika/dsl/runtime.rb +64 -79
  91. data/lib/insika/dsl/server_boot.rb +23 -1
  92. data/lib/insika/dsl/system.rb +10 -2
  93. data/lib/insika/dsl.rb +103 -2
  94. data/lib/insika/env_schema.rb +21 -7
  95. data/lib/insika/evals/golden.rb +41 -4
  96. data/lib/insika/evals/judge.rb +47 -2
  97. data/lib/insika/evals/pairwise.rb +11 -0
  98. data/lib/insika/evals/persona.rb +98 -0
  99. data/lib/insika/evals/runner.rb +9 -0
  100. data/lib/insika/evals/simulator.rb +225 -0
  101. data/lib/insika/evals/transport.rb +84 -2
  102. data/lib/insika/event_stream.rb +10 -0
  103. data/lib/insika/executor.rb +295 -55
  104. data/lib/insika/followup_policy.rb +2 -25
  105. data/lib/insika/golden_store.rb +16 -1
  106. data/lib/insika/grounding/matcher.rb +1 -1
  107. data/lib/insika/knowledge.rb +680 -0
  108. data/lib/insika/knowledge_store.rb +140 -0
  109. data/lib/insika/loop_detector.rb +5 -34
  110. data/lib/insika/mcp_client.rb +94 -0
  111. data/lib/insika/mcp_json.rb +74 -0
  112. data/lib/insika/mcp_live_tool.rb +43 -0
  113. data/lib/insika/mcp_store.rb +98 -26
  114. data/lib/insika/mcp_tool_ingestor.rb +30 -8
  115. data/lib/insika/mcp_tool_registry.rb +100 -0
  116. data/lib/insika/media.rb +115 -31
  117. data/lib/insika/message_origin.rb +1 -1
  118. data/lib/insika/middleware.rb +9 -0
  119. data/lib/insika/onboarding.rb +17 -1
  120. data/lib/insika/outcome_store.rb +1 -1
  121. data/lib/insika/overlay_tool_registry.rb +37 -17
  122. data/lib/insika/packaging.rb +2 -2
  123. data/lib/insika/profile_source.rb +15 -1
  124. data/lib/insika/prompt_catalog.rb +10 -0
  125. data/lib/insika/retention.rb +36 -1
  126. data/lib/insika/router/app.rb +157 -0
  127. data/lib/insika/router/backend_pool.rb +98 -0
  128. data/lib/insika/router/hash_ring.rb +55 -0
  129. data/lib/insika/router/proxy_body.rb +34 -0
  130. data/lib/insika/router/session_key.rb +54 -0
  131. data/lib/insika/router.rb +18 -0
  132. data/lib/insika/schedule.rb +177 -0
  133. data/lib/insika/schedule_engine.rb +314 -0
  134. data/lib/insika/schedule_store.rb +208 -0
  135. data/lib/insika/server/app.rb +105 -15
  136. data/lib/insika/server/rack_app.rb +5 -1
  137. data/lib/insika/server/responses.rb +5 -5
  138. data/lib/insika/session_store.rb +34 -4
  139. data/lib/insika/settings_store.rb +8 -1
  140. data/lib/insika/skill_catalog.rb +12 -0
  141. data/lib/insika/soak/runner.rb +4 -4
  142. data/lib/insika/steer_injector.rb +21 -10
  143. data/lib/insika/studio/app.rb +591 -47
  144. data/lib/insika/studio/assets/dist/application.css +1 -1
  145. data/lib/insika/studio/assets/dist/application.js +21 -21
  146. data/lib/insika/studio/forms.rb +57 -5
  147. data/lib/insika/studio/nav_icons.rb +14 -1
  148. data/lib/insika/studio/views/_agent_tab_cache.erb +25 -0
  149. data/lib/insika/studio/views/_agent_tab_config.erb +514 -0
  150. data/lib/insika/studio/views/_agent_tab_history.erb +24 -0
  151. data/lib/insika/studio/views/_agent_tab_loops.erb +54 -0
  152. data/lib/insika/studio/views/_agent_tab_memory.erb +51 -0
  153. data/lib/insika/studio/views/_agent_tab_outcomes.erb +31 -0
  154. data/lib/insika/studio/views/_agent_tab_prompts.erb +108 -0
  155. data/lib/insika/studio/views/_agent_tab_skills.erb +38 -0
  156. data/lib/insika/studio/views/_agents_master.erb +44 -0
  157. data/lib/insika/studio/views/_message.erb +49 -32
  158. data/lib/insika/studio/views/agent_detail.erb +61 -820
  159. data/lib/insika/studio/views/agents.erb +70 -57
  160. data/lib/insika/studio/views/artifact.erb +23 -0
  161. data/lib/insika/studio/views/artifacts.erb +59 -0
  162. data/lib/insika/studio/views/evals.erb +2 -2
  163. data/lib/insika/studio/views/facts.erb +1 -1
  164. data/lib/insika/studio/views/funnel.erb +1 -1
  165. data/lib/insika/studio/views/home.erb +106 -67
  166. data/lib/insika/studio/views/knowledge.erb +123 -0
  167. data/lib/insika/studio/views/layout.erb +14 -11
  168. data/lib/insika/studio/views/mcp.erb +174 -80
  169. data/lib/insika/studio/views/session.erb +231 -177
  170. data/lib/insika/studio/views/settings.erb +50 -1
  171. data/lib/insika/studio/views/skills.erb +1 -1
  172. data/lib/insika/studio/views/tools.erb +24 -9
  173. data/lib/insika/telemetry/recorder.rb +49 -1
  174. data/lib/insika/templates/browser-agent/README.md +36 -0
  175. data/lib/insika/templates/browser-agent/agent.rb +49 -0
  176. data/lib/insika/templates/daily-digest/README.md +47 -0
  177. data/lib/insika/templates/daily-digest/agent.rb +77 -0
  178. data/lib/insika/templates/repo-explorer/README.md +36 -0
  179. data/lib/insika/templates/repo-explorer/agent.rb +45 -0
  180. data/lib/insika/templates/research-analyst/README.md +26 -0
  181. data/lib/insika/templates/research-analyst/agent.rb +68 -0
  182. data/lib/insika/templates/review-panel/README.md +20 -0
  183. data/lib/insika/templates/review-panel/agent.rb +50 -0
  184. data/lib/insika/templates/travel-planner/README.md +35 -0
  185. data/lib/insika/templates/travel-planner/agent.rb +87 -0
  186. data/lib/insika/templates.rb +112 -0
  187. data/lib/insika/tick.rb +24 -12
  188. data/lib/insika/timezone.rb +45 -0
  189. data/lib/insika/tool_batch.rb +67 -0
  190. data/lib/insika/tool_usage_report.rb +162 -0
  191. data/lib/insika/tools/generate_image.rb +52 -7
  192. data/lib/insika/tools/load_knowledge.rb +74 -0
  193. data/lib/insika/tools/run_persona_eval.rb +328 -0
  194. data/lib/insika/tools/save_artifact.rb +95 -0
  195. data/lib/insika/turn_budget.rb +91 -0
  196. data/lib/insika/turn_output.rb +1 -1
  197. data/lib/insika/turn_state.rb +15 -4
  198. data/lib/insika/version.rb +1 -1
  199. data/lib/insika/wiring/graph.rb +184 -12
  200. data/lib/insika/wiring/graph_chat.rb +102 -0
  201. data/lib/insika.rb +64 -0
  202. metadata +109 -5
  203. data/docs/build.md +0 -14
  204. data/docs/understand.md +0 -10
@@ -1,3 +1,13 @@
1
+ <%# MCP — miller columns: instances master | editor detail.
2
+ ?i= selects the instance whose editor fills the detail pane; nil shows the
3
+ empty state with the new/import forms. Master rows and the editor form
4
+ frame-navigate (mcp-detail); create/import/delete submit to the top window
5
+ so the master list re-renders with the change. Credentials never come back
6
+ in plaintext — each value shows as the sentinel; keeping it preserves the
7
+ secret, changing it replaces, clearing the line removes it. %>
8
+ <%# Roda renders with plain locals — no local_assigns hash (Rails-ism). %>
9
+ <% frame_only = defined?(frame_only) && frame_only %>
10
+ <% unless frame_only %>
1
11
  <header class="page-head">
2
12
  <div>
3
13
  <nav class="crumbs"><a href="/studio/agents">agents</a> / <span>mcp</span></nav>
@@ -5,87 +15,171 @@
5
15
  </div>
6
16
  </header>
7
17
 
8
- <p class="muted matrix-hint">MCP server instances (durable config). Credentials in <code>env</code> never come back in plaintext — each value shows as <code><%= secret_sentinel %></code>; keeping it preserves the secret, changing it replaces, clearing the line removes it.</p>
9
-
10
- <% if @instances.empty? %>
11
- <div class="empty">No MCP instances. Add one below (e.g. <code>tavily</code>) to expose an MCP server to your agents' tools.</div>
18
+ <div class="drill" data-controller="list-filter">
19
+ <aside class="drill-pane drill-master">
20
+ <div class="drill-pane-head">
21
+ <div class="title"><strong>MCP <span class="count"><%= @instances.size %></span></strong></div>
22
+ <a class="btn primary btn-sm" href="/studio/mcp?i=" data-turbo-frame="mcp-detail" data-turbo-action="advance">+ new</a>
23
+ </div>
24
+ <div class="drill-search">
25
+ <input type="search" class="filter-input" placeholder="Filter by name, transport or status…" aria-label="Filter MCP instances"
26
+ data-list-filter-target="query" data-action="input->list-filter#filter keydown->list-filter#clear">
27
+ </div>
28
+ <nav class="drill-pane-body drill-list" aria-label="MCP instances" data-controller="active-link">
29
+ <% if @instances.empty? %>
30
+ <div class="empty">No MCP instances. Create one on the right (e.g. <code>tavily</code>) to expose an MCP server to your agents' tools.</div>
31
+ <% else %>
32
+ <% @instances.each do |m| %>
33
+ <% status_label, status_cls = m["enabled"] ? mcp_status(m) : ["off", "warn"] %>
34
+ <a class="drill-item<%== ' active' if @selected_mcp && @selected_mcp["name"] == m["name"] %>"
35
+ href="/studio/mcp?i=<%= Rack::Utils.escape(m["name"]) %>"
36
+ data-turbo-frame="mcp-detail" data-turbo-action="advance"
37
+ data-list-filter-target="item" data-filter-text="<%= mcp_filter_text(m) %>"
38
+ data-active-link-target="item" data-action="click->active-link#select">
39
+ <div class="drill-item-name">
40
+ <span class="txt" title="<%= m["name"] %>"><%= m["name"] %></span>
41
+ <span class="pill <%= status_cls %>"><%= status_label %></span>
42
+ </div>
43
+ <div class="drill-item-sub"><%= m["transport"] %><%== " · #{m['url']}" if m["url"].to_s != "" %><%== " · #{Array(m['tools_cache']).size} tool(s)" if Array(m["tools_cache"]).any? %></div>
44
+ </a>
45
+ <% end %>
46
+ <div class="empty" data-list-filter-target="empty" hidden>No MCP instances match your filter.</div>
47
+ <% end %>
48
+ </nav>
49
+ </aside>
12
50
  <% end %>
51
+ <turbo-frame id="mcp-detail" class="drill-pane drill-detail">
52
+ <% if frame_only %>
53
+ <% if flash["error"] %><div class="flash err" role="alert"><%= flash["error"] %></div><% end %>
54
+ <% if flash["notice"] %><div class="flash ok" role="status"><%= flash["notice"] %></div><% end %>
55
+ <% end %>
56
+ <% if @selected_mcp %>
57
+ <% m = @selected_mcp %>
58
+ <div class="drill-pane-head">
59
+ <div class="title">
60
+ <strong class="mono"><%= m["name"] %></strong>
61
+ <span class="count"><%= m["transport"] %><%== " · #{m['description']}" if m["description"].to_s != "" %></span>
62
+ </div>
63
+ <div class="head-actions">
64
+ <form method="post" action="/studio/mcp/test">
65
+ <%== csrf_tag %>
66
+ <input type="hidden" name="name" value="<%= m["name"] %>">
67
+ <button type="submit" class="btn btn-sm" data-turbo-submits-with="Testing…">Test connection</button>
68
+ </form>
69
+ <form method="post" action="/studio/mcp/delete" data-turbo-frame="_top" data-turbo-confirm="Remove MCP instance &quot;<%= m["name"] %>&quot;? Agents stop seeing its tools next turn.">
70
+ <%== csrf_tag %>
71
+ <input type="hidden" name="name" value="<%= m["name"] %>">
72
+ <button type="submit" class="btn btn-sm danger" data-turbo-submits-with="Removing…">Remove</button>
73
+ </form>
74
+ </div>
75
+ </div>
76
+ <div class="drill-pane-body">
77
+ <% tools = Array(m["tools_cache"]) %>
78
+ <% if tools.any? %>
79
+ <details class="mcp-tools-details" open>
80
+ <summary><%= tools.size %> tool(s) · live cache</summary>
81
+ <ul class="mcp-tools muted">
82
+ <% tools.each do |t| %>
83
+ <li><code><%= t["name"] %></code><% if presence(t["description"]) %> — <%= t["description"] %><% end %></li>
84
+ <% end %>
85
+ </ul>
86
+ </details>
87
+ <% end %>
13
88
 
14
- <% @instances.each do |m| %>
15
- <div class="card promptfile">
16
- <div class="promptfile-head">
17
- <strong class="mono"><%= m["name"] %>
18
- <% if m["enabled"] %><span class="pill ok">on</span><% else %><span class="pill warn">off</span><% end %>
19
- </strong>
20
- <form method="post" action="/studio/mcp/delete" data-turbo-confirm="Remove MCP instance &quot;<%= m["name"] %>&quot;? Agents stop seeing its tools next turn.">
21
- <%== csrf_tag %>
22
- <input type="hidden" name="name" value="<%= m["name"] %>">
23
- <button type="submit" class="ghost danger" data-turbo-submits-with="Removing…">Remove</button>
24
- </form>
25
- </div>
26
- <form method="post" action="/studio/mcp">
27
- <%== csrf_tag %>
28
- <input type="hidden" name="name" value="<%= m["name"] %>">
29
- <div class="grid-2">
30
- <label>transport
31
- <select name="transport">
32
- <% %w[stdio http sse].each do |t| %>
33
- <option value="<%= t %>"<%== " selected" if m["transport"] == t %>><%= t %></option>
34
- <% end %>
35
- </select>
36
- </label>
37
- <label class="check">
38
- <input type="checkbox" name="enabled" value="1"<%== " checked" if m["enabled"] %>>
39
- enabled
40
- </label>
89
+ <form class="mcp-edit" method="post" action="/studio/mcp" data-controller="transport-fields">
90
+ <%== csrf_tag %>
91
+ <input type="hidden" name="name" value="<%= m["name"] %>">
92
+ <div class="grid-2">
93
+ <label>transport
94
+ <select name="transport" data-transport-fields-target="select" data-action="change->transport-fields#sync">
95
+ <% %w[stdio http sse].each do |t| %>
96
+ <option value="<%= t %>"<%== " selected" if m["transport"] == t %>><%= t %></option>
97
+ <% end %>
98
+ </select>
99
+ </label>
100
+ <label class="check">
101
+ <input type="checkbox" name="enabled" value="1"<%== " checked" if m["enabled"] %>>
102
+ enabled
103
+ </label>
104
+ </div>
105
+ <label data-transport-fields-target="stdio">command <span class="muted">(stdio)</span>
106
+ <input type="text" name="command" value="<%= m["command"] %>" placeholder="npx -y @modelcontextprotocol/server-...">
107
+ </label>
108
+ <label data-transport-fields-target="stdio">args <span class="muted">(stdio, one argv token per line)</span>
109
+ <textarea name="args" rows="3" placeholder="-y&#10;@modelcontextprotocol/server-filesystem&#10;/tmp"><%= list_lines(m["args"]) %></textarea>
110
+ </label>
111
+ <label data-transport-fields-target="http">url <span class="muted">(http/sse)</span>
112
+ <input type="text" name="url" value="<%= m["url"] %>" placeholder="https://mcp.example.com">
113
+ </label>
114
+ <label>description
115
+ <input type="text" name="description" value="<%= m["description"] %>">
116
+ </label>
117
+ <label data-transport-fields-target="stdio">env <span class="muted">(stdio process env, one <code>KEY=value</code> per line)</span>
118
+ <textarea name="env" rows="4"><%= env_lines(m["env"]) %></textarea>
119
+ </label>
120
+ <label data-transport-fields-target="http">headers <span class="muted">(http/sse request headers, one <code>Name=value</code> per line)</span>
121
+ <textarea name="headers" rows="4"><%= env_lines(m["headers"]) %></textarea>
122
+ </label>
123
+ <div class="actions">
124
+ <button type="submit" class="primary" data-turbo-submits-with="Saving…">Save instance</button>
125
+ </div>
126
+ </form>
41
127
  </div>
42
- <label>command <span class="muted">(stdio)</span>
43
- <input type="text" name="command" value="<%= m["command"] %>" placeholder="npx -y @modelcontextprotocol/server-...">
44
- </label>
45
- <label>url <span class="muted">(http/sse)</span>
46
- <input type="text" name="url" value="<%= m["url"] %>" placeholder="https://mcp.example.com">
47
- </label>
48
- <label>description
49
- <input type="text" name="description" value="<%= m["description"] %>">
50
- </label>
51
- <label>env <span class="muted">(one <code>KEY=value</code> per line)</span>
52
- <textarea name="env" rows="4"><%= env_lines(m["env"]) %></textarea>
53
- </label>
54
- <div class="actions"><button type="submit" class="primary" data-turbo-submits-with="Saving…">Save instance</button></div>
55
- </form>
56
- </div>
57
- <% end %>
128
+ <% else %>
129
+ <div class="drill-pane-head">
130
+ <div class="title">
131
+ <strong>Pick an instance</strong>
132
+ <span class="count">select on the left, or add one below</span>
133
+ </div>
134
+ </div>
135
+ <div class="drill-pane-body">
136
+ <form method="post" action="/studio/mcp" data-controller="transport-fields" data-turbo-frame="_top">
137
+ <%== csrf_tag %>
138
+ <div class="grid-2">
139
+ <label>name <span class="muted">(slug)</span>
140
+ <input type="text" name="name" placeholder="tavily" required>
141
+ </label>
142
+ <label>transport
143
+ <select name="transport" data-transport-fields-target="select" data-action="change->transport-fields#sync">
144
+ <% %w[stdio http sse].each do |t| %><option value="<%= t %>"><%= t %></option><% end %>
145
+ </select>
146
+ </label>
147
+ </div>
148
+ <label class="check">
149
+ <input type="checkbox" name="enabled" value="1" checked>
150
+ enabled
151
+ </label>
152
+ <label data-transport-fields-target="stdio">command <span class="muted">(stdio)</span>
153
+ <input type="text" name="command" placeholder="npx -y @modelcontextprotocol/server-...">
154
+ </label>
155
+ <label data-transport-fields-target="stdio">args <span class="muted">(stdio, one argv token per line)</span>
156
+ <textarea name="args" rows="3" placeholder="-y&#10;@modelcontextprotocol/server-filesystem&#10;/tmp"></textarea>
157
+ </label>
158
+ <label data-transport-fields-target="http">url <span class="muted">(http/sse)</span>
159
+ <input type="text" name="url" placeholder="https://mcp.example.com">
160
+ </label>
161
+ <label>description
162
+ <input type="text" name="description">
163
+ </label>
164
+ <label data-transport-fields-target="stdio">env <span class="muted">(stdio process env, one <code>KEY=value</code> per line)</span>
165
+ <textarea name="env" rows="4" placeholder="TAVILY_API_KEY=tvly-…"></textarea>
166
+ </label>
167
+ <label data-transport-fields-target="http">headers <span class="muted">(http/sse request headers, one <code>Name=value</code> per line)</span>
168
+ <textarea name="headers" rows="4" placeholder="Authorization=Bearer tvly-…"></textarea>
169
+ </label>
170
+ <div class="actions"><button type="submit" class="primary" data-turbo-submits-with="Creating…">Create instance</button></div>
171
+ </form>
58
172
 
59
- <details class="card newfile">
60
- <summary>+ new MCP instance</summary>
61
- <form method="post" action="/studio/mcp">
62
- <%== csrf_tag %>
63
- <div class="grid-2">
64
- <label>name <span class="muted">(slug)</span>
65
- <input type="text" name="name" placeholder="tavily" required>
66
- </label>
67
- <label>transport
68
- <select name="transport">
69
- <% %w[stdio http sse].each do |t| %><option value="<%= t %>"><%= t %></option><% end %>
70
- </select>
71
- </label>
72
- </div>
73
- <label class="check">
74
- <input type="checkbox" name="enabled" value="1" checked>
75
- enabled
76
- </label>
77
- <label>command <span class="muted">(stdio)</span>
78
- <input type="text" name="command" placeholder="npx -y @modelcontextprotocol/server-...">
79
- </label>
80
- <label>url <span class="muted">(http/sse)</span>
81
- <input type="text" name="url" placeholder="https://mcp.example.com">
82
- </label>
83
- <label>description
84
- <input type="text" name="description">
85
- </label>
86
- <label>env <span class="muted">(one <code>KEY=value</code> per line)</span>
87
- <textarea name="env" rows="4" placeholder="TAVILY_API_KEY=tvly-…"></textarea>
88
- </label>
89
- <div class="actions"><button type="submit" class="primary" data-turbo-submits-with="Creating…">Create instance</button></div>
90
- </form>
91
- </details>
173
+ <form method="post" action="/studio/mcp/import" data-turbo-frame="_top">
174
+ <%== csrf_tag %>
175
+ <label>mcpServers JSON <span class="muted">(paste a <code>{"mcpServers": {...}}</code> document — the same format Claude Desktop/Cursor use)</span>
176
+ <textarea name="json" rows="8" placeholder='{"mcpServers": {"tavily": {"url": "https://mcp.tavily.com/mcp", "headers": {"Authorization": "Bearer …"}}}}'></textarea>
177
+ </label>
178
+ <div class="actions"><button type="submit" class="primary" data-turbo-submits-with="Importing…">Import</button></div>
179
+ </form>
180
+ </div>
181
+ <% end %>
182
+ </turbo-frame>
183
+ <% unless frame_only %>
184
+ </div>
185
+ <% end %>