anima-core 0.2.1 → 1.0.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 (280) hide show
  1. checksums.yaml +4 -4
  2. data/.reek.yml +27 -1
  3. data/CHANGELOG.md +19 -0
  4. data/README.md +213 -43
  5. data/agents/codebase-analyzer.md +88 -0
  6. data/agents/codebase-pattern-finder.md +83 -0
  7. data/agents/documentation-researcher.md +59 -0
  8. data/agents/thoughts-analyzer.md +102 -0
  9. data/agents/web-search-researcher.md +71 -0
  10. data/anima-core.gemspec +3 -0
  11. data/app/channels/session_channel.rb +195 -45
  12. data/app/decorators/user_message_decorator.rb +16 -5
  13. data/app/jobs/agent_request_job.rb +55 -2
  14. data/app/jobs/analytical_brain_job.rb +33 -0
  15. data/app/jobs/count_event_tokens_job.rb +15 -4
  16. data/app/models/concerns/event/broadcasting.rb +81 -0
  17. data/app/models/event.rb +20 -1
  18. data/app/models/goal.rb +91 -0
  19. data/app/models/session.rb +366 -21
  20. data/config/application.rb +2 -0
  21. data/config/initializers/event_subscribers.rb +0 -1
  22. data/config/routes.rb +0 -6
  23. data/db/migrate/20260313010000_add_status_to_events.rb +8 -0
  24. data/db/migrate/20260313020000_add_processing_to_sessions.rb +7 -0
  25. data/db/migrate/20260314075248_add_subagent_support_to_sessions.rb +6 -0
  26. data/db/migrate/20260314112417_add_granted_tools_to_sessions.rb +5 -0
  27. data/db/migrate/20260314140000_add_name_to_sessions.rb +7 -0
  28. data/db/migrate/20260314150000_add_viewport_event_ids_to_sessions.rb +7 -0
  29. data/db/migrate/20260315100000_add_active_skills_to_sessions.rb +7 -0
  30. data/db/migrate/20260315140843_create_goals.rb +16 -0
  31. data/db/migrate/20260315144837_add_completed_at_to_goals.rb +5 -0
  32. data/db/migrate/20260315191105_add_active_workflow_to_sessions.rb +5 -0
  33. data/lib/agent_loop.rb +65 -6
  34. data/lib/agents/definition.rb +116 -0
  35. data/lib/agents/registry.rb +106 -0
  36. data/lib/analytical_brain/runner.rb +276 -0
  37. data/lib/analytical_brain/tools/activate_skill.rb +52 -0
  38. data/lib/analytical_brain/tools/deactivate_skill.rb +43 -0
  39. data/lib/analytical_brain/tools/deactivate_workflow.rb +34 -0
  40. data/lib/analytical_brain/tools/everything_is_ready.rb +28 -0
  41. data/lib/analytical_brain/tools/finish_goal.rb +62 -0
  42. data/lib/analytical_brain/tools/read_workflow.rb +58 -0
  43. data/lib/analytical_brain/tools/rename_session.rb +63 -0
  44. data/lib/analytical_brain/tools/set_goal.rb +60 -0
  45. data/lib/analytical_brain/tools/update_goal.rb +60 -0
  46. data/lib/analytical_brain.rb +23 -0
  47. data/lib/anima/cli/mcp/secrets.rb +76 -0
  48. data/lib/anima/cli/mcp.rb +197 -0
  49. data/lib/anima/cli.rb +5 -40
  50. data/lib/anima/installer.rb +168 -0
  51. data/lib/anima/settings.rb +226 -0
  52. data/lib/anima/version.rb +1 -1
  53. data/lib/anima.rb +9 -0
  54. data/lib/credential_store.rb +103 -0
  55. data/lib/environment_probe.rb +232 -0
  56. data/lib/events/subscribers/persister.rb +1 -0
  57. data/lib/events/user_message.rb +17 -0
  58. data/lib/llm/client.rb +29 -10
  59. data/lib/mcp/client_manager.rb +86 -0
  60. data/lib/mcp/config.rb +213 -0
  61. data/lib/mcp/health_check.rb +77 -0
  62. data/lib/mcp/secrets.rb +73 -0
  63. data/lib/mcp/stdio_transport.rb +206 -0
  64. data/lib/providers/anthropic.rb +11 -20
  65. data/lib/shell_session.rb +11 -10
  66. data/lib/skills/definition.rb +97 -0
  67. data/lib/skills/registry.rb +105 -0
  68. data/lib/tools/edit.rb +226 -0
  69. data/lib/tools/mcp_tool.rb +114 -0
  70. data/lib/tools/read.rb +151 -0
  71. data/lib/tools/registry.rb +14 -12
  72. data/lib/tools/request_feature.rb +121 -0
  73. data/lib/tools/return_result.rb +81 -0
  74. data/lib/tools/spawn_specialist.rb +109 -0
  75. data/lib/tools/spawn_subagent.rb +111 -0
  76. data/lib/tools/subagent_prompts.rb +12 -0
  77. data/lib/tools/web_get.rb +8 -9
  78. data/lib/tools/write.rb +86 -0
  79. data/lib/tui/app.rb +985 -26
  80. data/lib/tui/cable_client.rb +69 -31
  81. data/lib/tui/message_store.rb +103 -8
  82. data/lib/tui/screens/chat.rb +293 -45
  83. data/lib/workflows/definition.rb +97 -0
  84. data/lib/workflows/registry.rb +89 -0
  85. data/skills/activerecord/SKILL.md +255 -0
  86. data/skills/activerecord/examples/associations/association_extensions.rb +298 -0
  87. data/skills/activerecord/examples/associations/basic_associations.rb +118 -0
  88. data/skills/activerecord/examples/associations/counter_caches.rb +215 -0
  89. data/skills/activerecord/examples/associations/polymorphic_associations.rb +217 -0
  90. data/skills/activerecord/examples/associations/self_referential.rb +302 -0
  91. data/skills/activerecord/examples/associations/through_associations.rb +203 -0
  92. data/skills/activerecord/examples/basics/crud_operations.rb +209 -0
  93. data/skills/activerecord/examples/basics/dirty_tracking.rb +218 -0
  94. data/skills/activerecord/examples/basics/inheritance.rb +377 -0
  95. data/skills/activerecord/examples/basics/type_casting.rb +317 -0
  96. data/skills/activerecord/examples/callbacks/alternatives_to_callbacks.rb +447 -0
  97. data/skills/activerecord/examples/callbacks/conditional_callbacks.rb +353 -0
  98. data/skills/activerecord/examples/callbacks/lifecycle_callbacks.rb +280 -0
  99. data/skills/activerecord/examples/callbacks/transaction_callbacks.rb +340 -0
  100. data/skills/activerecord/examples/migrations/indexes_and_constraints.rb +337 -0
  101. data/skills/activerecord/examples/migrations/reversible_patterns.rb +403 -0
  102. data/skills/activerecord/examples/migrations/safe_patterns.rb +420 -0
  103. data/skills/activerecord/examples/migrations/schema_changes.rb +277 -0
  104. data/skills/activerecord/examples/querying/batch_processing.rb +226 -0
  105. data/skills/activerecord/examples/querying/eager_loading.rb +259 -0
  106. data/skills/activerecord/examples/querying/finder_methods.rb +170 -0
  107. data/skills/activerecord/examples/querying/optimization.rb +275 -0
  108. data/skills/activerecord/examples/querying/scopes.rb +260 -0
  109. data/skills/activerecord/examples/validations/built_in_validators.rb +277 -0
  110. data/skills/activerecord/examples/validations/conditional_validations.rb +288 -0
  111. data/skills/activerecord/examples/validations/custom_validators.rb +381 -0
  112. data/skills/activerecord/examples/validations/database_constraints.rb +432 -0
  113. data/skills/activerecord/examples/validations/validation_contexts.rb +367 -0
  114. data/skills/activerecord/references/associations.md +709 -0
  115. data/skills/activerecord/references/basics.md +622 -0
  116. data/skills/activerecord/references/callbacks.md +738 -0
  117. data/skills/activerecord/references/migrations.md +657 -0
  118. data/skills/activerecord/references/querying.md +655 -0
  119. data/skills/activerecord/references/validations.md +596 -0
  120. data/skills/dragonruby/SKILL.md +250 -0
  121. data/skills/dragonruby/examples/audio/audio_events.rb +55 -0
  122. data/skills/dragonruby/examples/audio/background_music.rb +29 -0
  123. data/skills/dragonruby/examples/audio/crossfade.rb +51 -0
  124. data/skills/dragonruby/examples/audio/music_controls.rb +51 -0
  125. data/skills/dragonruby/examples/audio/sound_effects.rb +30 -0
  126. data/skills/dragonruby/examples/core/coordinate_system.rb +27 -0
  127. data/skills/dragonruby/examples/core/hello_world.rb +24 -0
  128. data/skills/dragonruby/examples/core/labels.rb +22 -0
  129. data/skills/dragonruby/examples/core/sprites.rb +35 -0
  130. data/skills/dragonruby/examples/core/state_management.rb +29 -0
  131. data/skills/dragonruby/examples/distribution/background_pause.rb +42 -0
  132. data/skills/dragonruby/examples/distribution/build_workflow.sh +26 -0
  133. data/skills/dragonruby/examples/distribution/cvars_production.txt +16 -0
  134. data/skills/dragonruby/examples/distribution/game_metadata_hd.txt +23 -0
  135. data/skills/dragonruby/examples/distribution/game_metadata_minimal.txt +9 -0
  136. data/skills/dragonruby/examples/distribution/game_metadata_mobile.txt +31 -0
  137. data/skills/dragonruby/examples/distribution/platform_detection.rb +36 -0
  138. data/skills/dragonruby/examples/distribution/steam_metadata.txt +19 -0
  139. data/skills/dragonruby/examples/entities/collision_detection.rb +43 -0
  140. data/skills/dragonruby/examples/entities/entity_lifecycle.rb +68 -0
  141. data/skills/dragonruby/examples/entities/entity_storage.rb +38 -0
  142. data/skills/dragonruby/examples/entities/factory_methods.rb +45 -0
  143. data/skills/dragonruby/examples/entities/random_spawning.rb +50 -0
  144. data/skills/dragonruby/examples/game-logic/reset_patterns.rb +98 -0
  145. data/skills/dragonruby/examples/game-logic/save_load.rb +101 -0
  146. data/skills/dragonruby/examples/game-logic/scoring.rb +104 -0
  147. data/skills/dragonruby/examples/game-logic/state_transitions.rb +103 -0
  148. data/skills/dragonruby/examples/game-logic/timers.rb +87 -0
  149. data/skills/dragonruby/examples/input/action_triggers.rb +36 -0
  150. data/skills/dragonruby/examples/input/analog_movement.rb +28 -0
  151. data/skills/dragonruby/examples/input/controller_input.rb +28 -0
  152. data/skills/dragonruby/examples/input/directional_input.rb +24 -0
  153. data/skills/dragonruby/examples/input/keyboard_input.rb +28 -0
  154. data/skills/dragonruby/examples/input/mouse_click.rb +26 -0
  155. data/skills/dragonruby/examples/input/movement_with_bounds.rb +22 -0
  156. data/skills/dragonruby/examples/input/normalized_movement.rb +32 -0
  157. data/skills/dragonruby/examples/rendering/frame_animation.rb +32 -0
  158. data/skills/dragonruby/examples/rendering/labels.rb +32 -0
  159. data/skills/dragonruby/examples/rendering/layering.rb +51 -0
  160. data/skills/dragonruby/examples/rendering/solids.rb +61 -0
  161. data/skills/dragonruby/examples/rendering/sprites.rb +33 -0
  162. data/skills/dragonruby/examples/rendering/spritesheet_animation.rb +39 -0
  163. data/skills/dragonruby/examples/scenes/case_dispatch.rb +60 -0
  164. data/skills/dragonruby/examples/scenes/class_based.rb +150 -0
  165. data/skills/dragonruby/examples/scenes/pause_overlay.rb +100 -0
  166. data/skills/dragonruby/examples/scenes/safe_transitions.rb +68 -0
  167. data/skills/dragonruby/examples/scenes/scene_transitions.rb +98 -0
  168. data/skills/dragonruby/examples/scenes/send_dispatch.rb +88 -0
  169. data/skills/dragonruby/references/audio.md +396 -0
  170. data/skills/dragonruby/references/core.md +385 -0
  171. data/skills/dragonruby/references/distribution.md +434 -0
  172. data/skills/dragonruby/references/entities.md +516 -0
  173. data/skills/dragonruby/references/game-logic/persistence.md +386 -0
  174. data/skills/dragonruby/references/game-logic/state.md +389 -0
  175. data/skills/dragonruby/references/input.md +414 -0
  176. data/skills/dragonruby/references/rendering/animation.md +467 -0
  177. data/skills/dragonruby/references/rendering/primitives.md +403 -0
  178. data/skills/dragonruby/references/scenes.md +443 -0
  179. data/skills/draper-decorators/SKILL.md +344 -0
  180. data/skills/draper-decorators/examples/application_decorator.rb +61 -0
  181. data/skills/draper-decorators/examples/decorator_spec.rb +253 -0
  182. data/skills/draper-decorators/examples/model_decorator.rb +152 -0
  183. data/skills/draper-decorators/references/anti-patterns.md +640 -0
  184. data/skills/draper-decorators/references/patterns.md +507 -0
  185. data/skills/draper-decorators/references/testing.md +559 -0
  186. data/skills/gh-issue.md +182 -0
  187. data/skills/mcp-server/SKILL.md +177 -0
  188. data/skills/mcp-server/examples/dynamic_tools.rb +36 -0
  189. data/skills/mcp-server/examples/file_manager_tool.rb +85 -0
  190. data/skills/mcp-server/examples/http_client.rb +48 -0
  191. data/skills/mcp-server/examples/http_server.rb +97 -0
  192. data/skills/mcp-server/examples/rails_integration.rb +88 -0
  193. data/skills/mcp-server/examples/stdio_server.rb +108 -0
  194. data/skills/mcp-server/examples/streaming_client.rb +95 -0
  195. data/skills/mcp-server/references/gotchas.md +183 -0
  196. data/skills/mcp-server/references/prompts.md +98 -0
  197. data/skills/mcp-server/references/resources.md +53 -0
  198. data/skills/mcp-server/references/server.md +140 -0
  199. data/skills/mcp-server/references/tools.md +146 -0
  200. data/skills/mcp-server/references/transport.md +104 -0
  201. data/skills/ratatui-ruby/SKILL.md +315 -0
  202. data/skills/ratatui-ruby/references/core-concepts.md +340 -0
  203. data/skills/ratatui-ruby/references/events.md +387 -0
  204. data/skills/ratatui-ruby/references/frameworks.md +522 -0
  205. data/skills/ratatui-ruby/references/layout.md +423 -0
  206. data/skills/ratatui-ruby/references/styling.md +268 -0
  207. data/skills/ratatui-ruby/references/testing.md +433 -0
  208. data/skills/ratatui-ruby/references/widgets.md +532 -0
  209. data/skills/rspec/SKILL.md +340 -0
  210. data/skills/rspec/examples/core/basic_structure.rb +69 -0
  211. data/skills/rspec/examples/core/configuration.rb +126 -0
  212. data/skills/rspec/examples/core/hooks.rb +126 -0
  213. data/skills/rspec/examples/core/memoized_helpers.rb +139 -0
  214. data/skills/rspec/examples/core/metadata_filtering.rb +144 -0
  215. data/skills/rspec/examples/core/shared_examples.rb +145 -0
  216. data/skills/rspec/examples/factory_bot/associations.rb +314 -0
  217. data/skills/rspec/examples/factory_bot/build_strategies.rb +272 -0
  218. data/skills/rspec/examples/factory_bot/callbacks.rb +320 -0
  219. data/skills/rspec/examples/factory_bot/custom_construction.rb +328 -0
  220. data/skills/rspec/examples/factory_bot/factory_definition.rb +191 -0
  221. data/skills/rspec/examples/factory_bot/inheritance.rb +314 -0
  222. data/skills/rspec/examples/factory_bot/traits.rb +293 -0
  223. data/skills/rspec/examples/factory_bot/transients.rb +229 -0
  224. data/skills/rspec/examples/matchers/change.rb +115 -0
  225. data/skills/rspec/examples/matchers/collections.rb +154 -0
  226. data/skills/rspec/examples/matchers/comparisons.rb +79 -0
  227. data/skills/rspec/examples/matchers/composing.rb +155 -0
  228. data/skills/rspec/examples/matchers/custom_matchers.rb +197 -0
  229. data/skills/rspec/examples/matchers/equality.rb +58 -0
  230. data/skills/rspec/examples/matchers/errors.rb +136 -0
  231. data/skills/rspec/examples/matchers/output.rb +103 -0
  232. data/skills/rspec/examples/matchers/predicates.rb +87 -0
  233. data/skills/rspec/examples/matchers/truthiness.rb +101 -0
  234. data/skills/rspec/examples/matchers/types.rb +82 -0
  235. data/skills/rspec/examples/matchers/yield.rb +147 -0
  236. data/skills/rspec/examples/mocks/any_instance.rb +172 -0
  237. data/skills/rspec/examples/mocks/argument_matchers.rb +206 -0
  238. data/skills/rspec/examples/mocks/constants.rb +177 -0
  239. data/skills/rspec/examples/mocks/doubles.rb +139 -0
  240. data/skills/rspec/examples/mocks/expectations.rb +137 -0
  241. data/skills/rspec/examples/mocks/message_chains.rb +173 -0
  242. data/skills/rspec/examples/mocks/ordering.rb +144 -0
  243. data/skills/rspec/examples/mocks/receive_counts.rb +181 -0
  244. data/skills/rspec/examples/mocks/responses.rb +223 -0
  245. data/skills/rspec/examples/mocks/spies.rb +149 -0
  246. data/skills/rspec/examples/mocks/stubbing.rb +133 -0
  247. data/skills/rspec/examples/rails/channels.rb +250 -0
  248. data/skills/rspec/examples/rails/controller_specs.rb +302 -0
  249. data/skills/rspec/examples/rails/helper_specs.rb +245 -0
  250. data/skills/rspec/examples/rails/job_specs.rb +256 -0
  251. data/skills/rspec/examples/rails/mailer_specs.rb +228 -0
  252. data/skills/rspec/examples/rails/matchers.rb +374 -0
  253. data/skills/rspec/examples/rails/model_specs.rb +193 -0
  254. data/skills/rspec/examples/rails/request_specs.rb +275 -0
  255. data/skills/rspec/examples/rails/routing_specs.rb +276 -0
  256. data/skills/rspec/examples/rails/system_specs.rb +294 -0
  257. data/skills/rspec/examples/rails/transactions.rb +254 -0
  258. data/skills/rspec/examples/rails/view_specs.rb +252 -0
  259. data/skills/rspec/references/core.md +816 -0
  260. data/skills/rspec/references/factory_bot.md +641 -0
  261. data/skills/rspec/references/matchers.md +516 -0
  262. data/skills/rspec/references/mocks.md +381 -0
  263. data/skills/rspec/references/rails.md +528 -0
  264. data/templates/soul.md +40 -0
  265. data/workflows/commit.md +45 -0
  266. data/workflows/create_handoff.md +98 -0
  267. data/workflows/create_note.md +82 -0
  268. data/workflows/create_plan.md +457 -0
  269. data/workflows/decompose_ticket.md +109 -0
  270. data/workflows/feature.md +91 -0
  271. data/workflows/implement_plan.md +87 -0
  272. data/workflows/iterate_plan.md +247 -0
  273. data/workflows/research_codebase.md +210 -0
  274. data/workflows/resume_handoff.md +217 -0
  275. data/workflows/review_pr.md +320 -0
  276. data/workflows/thoughts_init.md +71 -0
  277. data/workflows/validate_plan.md +166 -0
  278. metadata +290 -3
  279. data/app/controllers/api/sessions_controller.rb +0 -25
  280. data/lib/events/subscribers/action_cable_bridge.rb +0 -59
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Rails MCP Integration
4
+ # Demonstrates: server_context with auth, configuration in initializer, permission checks
5
+ #
6
+ # See: ../references/server.md, ../references/transport.md, ../references/tools.md
7
+ #
8
+ # Add these files to your Rails application
9
+
10
+ # config/routes.rb
11
+ Rails.application.routes.draw do
12
+ post "/mcp", to: "mcp#handle"
13
+ end
14
+
15
+ # app/controllers/mcp_controller.rb
16
+ class McpController < ApplicationController
17
+ skip_before_action :verify_authenticity_token
18
+
19
+ def handle
20
+ server = build_server
21
+ response = server.handle_json(request.body.read)
22
+ render json: response
23
+ end
24
+
25
+ private
26
+
27
+ def build_server
28
+ MCP::Server.new(
29
+ name: "rails_mcp",
30
+ version: Rails.application.config.version,
31
+ tools: [
32
+ UserSearchTool,
33
+ CreateRecordTool,
34
+ DatabaseQueryTool
35
+ ],
36
+ server_context: {
37
+ user_id: current_user&.id,
38
+ request_id: request.uuid,
39
+ permissions: current_user&.permissions || []
40
+ },
41
+ configuration: Rails.application.config.mcp_configuration
42
+ )
43
+ end
44
+ end
45
+
46
+ # config/initializers/mcp.rb
47
+ Rails.application.config.mcp_configuration = MCP::Configuration.new(
48
+ protocol_version: "2025-11-25",
49
+ exception_reporter: ->(e, ctx) {
50
+ Rails.logger.error("MCP Error: #{e.message}")
51
+ Bugsnag.notify(e) { |r| r.add_metadata(:mcp, ctx) }
52
+ },
53
+ instrumentation_callback: ->(data) {
54
+ ActiveSupport::Notifications.instrument("mcp.request", data)
55
+ }
56
+ )
57
+
58
+ # app/tools/user_search_tool.rb
59
+ class UserSearchTool < MCP::Tool
60
+ description "Searches for users by name or email"
61
+ input_schema(
62
+ properties: {
63
+ query: { type: "string", minLength: 2 },
64
+ limit: { type: "integer", minimum: 1, maximum: 100 }
65
+ },
66
+ required: ["query"]
67
+ )
68
+
69
+ class << self
70
+ def call(query:, limit: 10, server_context:)
71
+ unless server_context[:permissions].include?("users:read")
72
+ return MCP::Tool::Response.new(
73
+ [{ type: "text", text: "Permission denied" }],
74
+ error: true
75
+ )
76
+ end
77
+
78
+ users = User.where("name ILIKE ? OR email ILIKE ?", "%#{query}%", "%#{query}%")
79
+ .limit(limit)
80
+ .select(:id, :name, :email)
81
+
82
+ MCP::Tool::Response.new(
83
+ [{ type: "text", text: users.to_json }],
84
+ structured_content: users.as_json
85
+ )
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,108 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Complete STDIO MCP Server
5
+ # Demonstrates: Class-based tools, prompts, resources, dynamic tool definition
6
+ #
7
+ # See: ../references/tools.md, ../references/prompts.md, ../references/transport.md
8
+ #
9
+ # Run: ruby stdio_server.rb
10
+ # Then type JSON-RPC requests:
11
+ # {"jsonrpc":"2.0","id":1,"method":"ping"}
12
+ # {"jsonrpc":"2.0","id":2,"method":"tools/list"}
13
+ # {"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"add","arguments":{"a":5,"b":3}}}
14
+
15
+ require "mcp"
16
+
17
+ class AddTool < MCP::Tool
18
+ description "Adds two numbers together"
19
+ input_schema(
20
+ properties: {
21
+ a: { type: "number" },
22
+ b: { type: "number" }
23
+ },
24
+ required: %w[a b]
25
+ )
26
+
27
+ class << self
28
+ def call(a:, b:, server_context: nil)
29
+ MCP::Tool::Response.new([{
30
+ type: "text",
31
+ text: "#{a} + #{b} = #{a + b}"
32
+ }])
33
+ end
34
+ end
35
+ end
36
+
37
+ class CodeReviewPrompt < MCP::Prompt
38
+ description "Generates a code review prompt"
39
+ arguments [
40
+ MCP::Prompt::Argument.new(
41
+ name: "code",
42
+ description: "The code to review",
43
+ required: true
44
+ ),
45
+ MCP::Prompt::Argument.new(
46
+ name: "language",
47
+ description: "Programming language",
48
+ required: false
49
+ )
50
+ ]
51
+
52
+ class << self
53
+ def template(args, server_context:)
54
+ lang = args[:language] || "unknown"
55
+ MCP::Prompt::Result.new(
56
+ description: "Code review request",
57
+ messages: [
58
+ MCP::Prompt::Message.new(
59
+ role: "user",
60
+ content: MCP::Content::Text.new(
61
+ "Review this #{lang} code:\n\n```#{lang}\n#{args[:code]}\n```"
62
+ )
63
+ )
64
+ ]
65
+ )
66
+ end
67
+ end
68
+ end
69
+
70
+ config_resource = MCP::Resource.new(
71
+ uri: "config://app",
72
+ name: "app-config",
73
+ description: "Application configuration",
74
+ mime_type: "application/json"
75
+ )
76
+
77
+ server = MCP::Server.new(
78
+ name: "example_server",
79
+ version: "1.0.0",
80
+ tools: [AddTool],
81
+ prompts: [CodeReviewPrompt],
82
+ resources: [config_resource]
83
+ )
84
+
85
+ # Dynamic tool example
86
+ server.define_tool(
87
+ name: "echo",
88
+ description: "Echoes input back",
89
+ input_schema: {
90
+ properties: { message: { type: "string" } },
91
+ required: ["message"]
92
+ }
93
+ ) do |message:|
94
+ MCP::Tool::Response.new([{ type: "text", text: message }])
95
+ end
96
+
97
+ # Resource handler
98
+ server.resources_read_handler do |params|
99
+ case params[:uri]
100
+ when "config://app"
101
+ { uri: params[:uri], mimeType: "application/json", text: '{"env":"prod"}' }
102
+ else
103
+ { uri: params[:uri], mimeType: "text/plain", text: "Unknown resource" }
104
+ end
105
+ end
106
+
107
+ transport = MCP::Server::Transports::StdioTransport.new(server)
108
+ transport.open
@@ -0,0 +1,95 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # SSE Streaming Client
5
+ # Demonstrates: SSE connections, session management, real-time notifications
6
+ #
7
+ # See: ../references/transport.md, ../references/gotchas.md
8
+
9
+ require "net/http"
10
+ require "json"
11
+
12
+ class MCPStreamingClient
13
+ def initialize(base_url)
14
+ @base_url = base_url
15
+ @session_id = nil
16
+ end
17
+
18
+ def connect
19
+ response = post_request("initialize", {})
20
+ @session_id = response["Mcp-Session-Id"]
21
+
22
+ @sse_thread = Thread.new { listen_sse }
23
+ sleep(0.5)
24
+ end
25
+
26
+ def call_tool(name, arguments)
27
+ post_request("tools/call", { name:, arguments: })
28
+ end
29
+
30
+ def disconnect
31
+ @sse_thread&.kill
32
+ delete_request if @session_id
33
+ @session_id = nil
34
+ end
35
+
36
+ private
37
+
38
+ def post_request(method, params)
39
+ uri = URI(@base_url)
40
+ http = Net::HTTP.new(uri.host, uri.port)
41
+
42
+ request = Net::HTTP::Post.new(uri.path.empty? ? "/" : uri.path)
43
+ request["Content-Type"] = "application/json"
44
+ request["Mcp-Session-Id"] = @session_id if @session_id
45
+
46
+ request.body = {
47
+ jsonrpc: "2.0",
48
+ method:,
49
+ params:,
50
+ id: rand(10000)
51
+ }.to_json
52
+
53
+ response = http.request(request)
54
+ @session_id ||= response["Mcp-Session-Id"]
55
+
56
+ JSON.parse(response.body)
57
+ end
58
+
59
+ def listen_sse
60
+ uri = URI(@base_url)
61
+ Net::HTTP.start(uri.host, uri.port) do |http|
62
+ request = Net::HTTP::Get.new(uri)
63
+ request["Mcp-Session-Id"] = @session_id
64
+ request["Accept"] = "text/event-stream"
65
+
66
+ http.request(request) do |response|
67
+ response.read_body do |chunk|
68
+ chunk.split("\n").each do |line|
69
+ next unless line.start_with?("data: ")
70
+
71
+ data = JSON.parse(line[6..])
72
+ puts "SSE Event: #{data['method']}"
73
+ end
74
+ end
75
+ end
76
+ end
77
+ rescue StandardError => e
78
+ puts "SSE Error: #{e.message}"
79
+ end
80
+
81
+ def delete_request
82
+ uri = URI(@base_url)
83
+ http = Net::HTTP.new(uri.host, uri.port)
84
+ request = Net::HTTP::Delete.new(uri.path.empty? ? "/" : uri.path)
85
+ request["Mcp-Session-Id"] = @session_id
86
+ http.request(request)
87
+ end
88
+ end
89
+
90
+ # Usage
91
+ client = MCPStreamingClient.new("http://localhost:9292")
92
+ client.connect
93
+ result = client.call_tool("echo", { message: "Hello!" })
94
+ puts result
95
+ client.disconnect
@@ -0,0 +1,183 @@
1
+ # MCP Ruby SDK - Gotchas and Error Handling
2
+
3
+ ## Tricky Behaviors
4
+
5
+ ### Schema Transformation
6
+
7
+ **Symbol keys become string keys:**
8
+ ```ruby
9
+ input_schema({ required: [:message] })
10
+ # Stored as: { required: ["message"] }
11
+ ```
12
+
13
+ ### JSON-RPC ID Validation
14
+
15
+ **IDs are validated against alphanumeric pattern:**
16
+ ```ruby
17
+ id: "<script>alert('xss')</script>" # REJECTED
18
+ id: "request-123_ABC" # OK
19
+ id: "550e8400-e29b-41d4-a716-446655440000" # OK (UUID)
20
+ ```
21
+
22
+ **Invalid IDs in error responses become nil:**
23
+ ```ruby
24
+ error_response(id: "<invalid>")
25
+ # Returns: { ..., id: nil, ... }
26
+ ```
27
+
28
+ ### Batch Requests
29
+
30
+ **Single-item batch responses are unwrapped:**
31
+ ```ruby
32
+ # Request: [{ ... }] (array with one item)
33
+ # Response: { ... } (single object, NOT array)
34
+
35
+ # Request: [{ ... }, { ... }] (two items)
36
+ # Response: [{ ... }, { ... }] (array)
37
+ ```
38
+
39
+ ### Server Context Detection
40
+
41
+ **Framework inspects method signature:**
42
+ ```ruby
43
+ # These receive server_context:
44
+ def call(message:, server_context: nil) # Detected
45
+ def call(message:, server_context:) # Detected
46
+ def call(**kwargs) # Detected (keyrest)
47
+
48
+ # This doesn't:
49
+ def call(message:) # NOT detected
50
+ ```
51
+
52
+ ### Tool Name Derivation
53
+
54
+ **Class names auto-convert to snake_case:**
55
+ ```ruby
56
+ class MyCustomTool < MCP::Tool; end
57
+ # Name: "my_custom_tool"
58
+
59
+ class HTMLParser < MCP::Tool; end
60
+ # Name: "html_parser"
61
+ ```
62
+
63
+ ### Notifications
64
+
65
+ **No response for notifications (requests without id):**
66
+ ```ruby
67
+ server.handle({ jsonrpc: "2.0", method: "ping" }) # => nil
68
+ ```
69
+
70
+ **Silent no-op if no transport:**
71
+ ```ruby
72
+ server.notify_tools_list_changed # Does nothing if transport not set
73
+ ```
74
+
75
+ ### StreamableHTTP Transport
76
+
77
+ **GET only works in stateful mode:**
78
+ ```ruby
79
+ # Stateless: GET returns 405
80
+ # Stateful: GET sets up SSE stream
81
+ ```
82
+
83
+ **Session cleanup on stream errors:**
84
+ ```ruby
85
+ # IOError/EPIPE automatically cleans up session
86
+ ```
87
+
88
+ ### Configuration Merging
89
+
90
+ **Non-nil values from other config take precedence:**
91
+ ```ruby
92
+ config1 = Configuration.new(protocol_version: "2025-03-26")
93
+ config2 = Configuration.new # Uses default
94
+
95
+ merged = config1.merge(config2)
96
+ # Uses config1's version (config2 didn't set explicitly)
97
+ ```
98
+
99
+ ### Validation Flow
100
+
101
+ **Two-step validation:**
102
+ ```ruby
103
+ # Step 1: Check required fields (always)
104
+ # Step 2: JSON Schema validation (if validate_tool_call_arguments = true)
105
+ ```
106
+
107
+ ---
108
+
109
+ ## Error Handling
110
+
111
+ ### Error Classes
112
+
113
+ ```ruby
114
+ MCP::Server::RequestHandlerError # General request errors
115
+ # error_type: :internal_error, :tool_not_found, :prompt_not_found,
116
+ # :missing_required_arguments, :invalid_schema
117
+
118
+ MCP::Server::MethodAlreadyDefinedError # Duplicate custom method
119
+ MCP::Server::ToolNotUnique # Duplicate tool names
120
+ MCP::Tool::InputSchema::ValidationError # Schema validation failed
121
+ MCP::Methods::MissingRequiredCapabilityError # Capability not supported
122
+ ```
123
+
124
+ ### JSON-RPC Error Codes
125
+
126
+ ```ruby
127
+ PARSE_ERROR = -32700 # Invalid JSON
128
+ INVALID_REQUEST = -32600 # Malformed request
129
+ METHOD_NOT_FOUND = -32601 # Method doesn't exist
130
+ INVALID_PARAMS = -32602 # Invalid parameters
131
+ INTERNAL_ERROR = -32603 # Server error
132
+ ```
133
+
134
+ ### Tool Errors vs JSON-RPC Errors
135
+
136
+ ```ruby
137
+ # Tool errors (business logic) - return response with isError: true
138
+ { content: [...], isError: true }
139
+
140
+ # JSON-RPC errors (protocol) - return error object
141
+ { jsonrpc: "2.0", id: 1, error: { code: -32600, ... } }
142
+ ```
143
+
144
+ ### Client Error Handling
145
+
146
+ ```ruby
147
+ client.call_tool(tool:, arguments:)
148
+ rescue MCP::Client::RequestHandlerError => e
149
+ case e.error_type
150
+ when :bad_request # 400
151
+ when :unauthorized # 401
152
+ when :forbidden # 403
153
+ when :not_found # 404
154
+ when :unprocessable_entity # 422
155
+ when :internal_error # Other
156
+ end
157
+ end
158
+ ```
159
+
160
+ ### Transport Error Handling
161
+
162
+ **StdioTransport catches Interrupt:**
163
+ ```ruby
164
+ # Ctrl-C exits with status 130 (SIGINT)
165
+ ```
166
+
167
+ **StreamableHTTP handles stream errors:**
168
+ ```ruby
169
+ # IOError/EPIPE caught, session cleaned up, returns false
170
+ ```
171
+
172
+ ---
173
+
174
+ ## See Also
175
+
176
+ ### Related References
177
+ - **[Tools](tools.md)** - Schema definition and validation
178
+ - **[Server](server.md)** - Configuration and notifications
179
+ - **[Transport](transport.md)** - Transport-specific behaviors
180
+
181
+ ### Related Examples
182
+ - **[`../examples/stdio_server.rb`](../examples/stdio_server.rb)** - Basic patterns avoiding common pitfalls
183
+ - **[`../examples/http_server.rb`](../examples/http_server.rb)** - HTTP error handling
@@ -0,0 +1,98 @@
1
+ # MCP Prompts Reference
2
+
3
+ ## Prompt Definition
4
+
5
+ ### Class-Based
6
+
7
+ ```ruby
8
+ class MyPrompt < MCP::Prompt
9
+ prompt_name "custom_name" # Optional
10
+ title "Prompt Title"
11
+ description "What this prompt does"
12
+
13
+ arguments [
14
+ MCP::Prompt::Argument.new(
15
+ name: "message",
16
+ title: "Message Title",
17
+ description: "The input message",
18
+ required: true
19
+ ),
20
+ MCP::Prompt::Argument.new(
21
+ name: "context",
22
+ description: "Optional context",
23
+ required: false
24
+ )
25
+ ]
26
+
27
+ meta(version: "1.0", category: "general")
28
+
29
+ class << self
30
+ def template(args, server_context:)
31
+ MCP::Prompt::Result.new(
32
+ description: "Result description",
33
+ messages: [
34
+ MCP::Prompt::Message.new(
35
+ role: "user",
36
+ content: MCP::Content::Text.new(args[:message])
37
+ ),
38
+ MCP::Prompt::Message.new(
39
+ role: "assistant",
40
+ content: MCP::Content::Text.new("Response")
41
+ )
42
+ ]
43
+ )
44
+ end
45
+ end
46
+ end
47
+ ```
48
+
49
+ ### Block-Based
50
+
51
+ ```ruby
52
+ prompt = MCP::Prompt.define(
53
+ name: "prompt_name",
54
+ title: "Title",
55
+ description: "Description",
56
+ arguments: [
57
+ MCP::Prompt::Argument.new(name: "arg", required: true)
58
+ ],
59
+ meta: { version: "1.0" }
60
+ ) do |args, server_context:|
61
+ MCP::Prompt::Result.new(
62
+ messages: [
63
+ MCP::Prompt::Message.new(
64
+ role: "user",
65
+ content: MCP::Content::Text.new(args[:arg])
66
+ )
67
+ ]
68
+ )
69
+ end
70
+ ```
71
+
72
+ ## Content Types
73
+
74
+ ```ruby
75
+ # Text content
76
+ MCP::Content::Text.new(
77
+ "Text content",
78
+ annotations: { key: "value" } # Optional
79
+ )
80
+
81
+ # Image content
82
+ MCP::Content::Image.new(
83
+ "base64_encoded_data",
84
+ "image/png",
85
+ annotations: { source: "camera" }
86
+ )
87
+ ```
88
+
89
+ ---
90
+
91
+ ## See Also
92
+
93
+ ### Related References
94
+ - **[Server](server.md)** - Prompt registration and handler overrides
95
+ - **[Transport](transport.md)** - Protocol methods (`prompts/list`, `prompts/get`)
96
+
97
+ ### Related Examples
98
+ - **[`../examples/stdio_server.rb`](../examples/stdio_server.rb)** - Class-based prompt definition (`CodeReviewPrompt`)
@@ -0,0 +1,53 @@
1
+ # MCP Resources Reference
2
+
3
+ ## Resource Definition
4
+
5
+ ```ruby
6
+ # Static resource
7
+ MCP::Resource.new(
8
+ uri: "file:///path/to/file",
9
+ name: "resource-name",
10
+ title: "Resource Title", # Optional
11
+ description: "Description", # Optional
12
+ mime_type: "text/plain" # Optional
13
+ )
14
+
15
+ # Resource template (dynamic URIs)
16
+ MCP::ResourceTemplate.new(
17
+ uri_template: "file:///{path}", # RFC 6570
18
+ name: "template-name",
19
+ title: "Template Title",
20
+ description: "Description",
21
+ mime_type: "text/plain"
22
+ )
23
+ ```
24
+
25
+ ## Resource Contents
26
+
27
+ ```ruby
28
+ # Text content
29
+ MCP::Resource::TextContents.new(
30
+ text: "File content",
31
+ uri: "file:///path",
32
+ mime_type: "text/plain"
33
+ )
34
+
35
+ # Binary content
36
+ MCP::Resource::BlobContents.new(
37
+ data: "base64_encoded",
38
+ uri: "file:///image.png",
39
+ mime_type: "image/png"
40
+ )
41
+ ```
42
+
43
+ ---
44
+
45
+ ## See Also
46
+
47
+ ### Related References
48
+ - **[Server](server.md)** - Resource registration and `resources_read_handler`
49
+ - **[Transport](transport.md)** - Protocol methods (`resources/list`, `resources/read`, `resources/templates/list`)
50
+
51
+ ### Related Examples
52
+ - **[`../examples/stdio_server.rb`](../examples/stdio_server.rb)** - Static resource with read handler
53
+ - **[`../examples/file_manager_tool.rb`](../examples/file_manager_tool.rb)** - File-based resources with security