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.
- checksums.yaml +4 -4
- data/.reek.yml +27 -1
- data/CHANGELOG.md +19 -0
- data/README.md +213 -43
- data/agents/codebase-analyzer.md +88 -0
- data/agents/codebase-pattern-finder.md +83 -0
- data/agents/documentation-researcher.md +59 -0
- data/agents/thoughts-analyzer.md +102 -0
- data/agents/web-search-researcher.md +71 -0
- data/anima-core.gemspec +3 -0
- data/app/channels/session_channel.rb +195 -45
- data/app/decorators/user_message_decorator.rb +16 -5
- data/app/jobs/agent_request_job.rb +55 -2
- data/app/jobs/analytical_brain_job.rb +33 -0
- data/app/jobs/count_event_tokens_job.rb +15 -4
- data/app/models/concerns/event/broadcasting.rb +81 -0
- data/app/models/event.rb +20 -1
- data/app/models/goal.rb +91 -0
- data/app/models/session.rb +366 -21
- data/config/application.rb +2 -0
- data/config/initializers/event_subscribers.rb +0 -1
- data/config/routes.rb +0 -6
- data/db/migrate/20260313010000_add_status_to_events.rb +8 -0
- data/db/migrate/20260313020000_add_processing_to_sessions.rb +7 -0
- data/db/migrate/20260314075248_add_subagent_support_to_sessions.rb +6 -0
- data/db/migrate/20260314112417_add_granted_tools_to_sessions.rb +5 -0
- data/db/migrate/20260314140000_add_name_to_sessions.rb +7 -0
- data/db/migrate/20260314150000_add_viewport_event_ids_to_sessions.rb +7 -0
- data/db/migrate/20260315100000_add_active_skills_to_sessions.rb +7 -0
- data/db/migrate/20260315140843_create_goals.rb +16 -0
- data/db/migrate/20260315144837_add_completed_at_to_goals.rb +5 -0
- data/db/migrate/20260315191105_add_active_workflow_to_sessions.rb +5 -0
- data/lib/agent_loop.rb +65 -6
- data/lib/agents/definition.rb +116 -0
- data/lib/agents/registry.rb +106 -0
- data/lib/analytical_brain/runner.rb +276 -0
- data/lib/analytical_brain/tools/activate_skill.rb +52 -0
- data/lib/analytical_brain/tools/deactivate_skill.rb +43 -0
- data/lib/analytical_brain/tools/deactivate_workflow.rb +34 -0
- data/lib/analytical_brain/tools/everything_is_ready.rb +28 -0
- data/lib/analytical_brain/tools/finish_goal.rb +62 -0
- data/lib/analytical_brain/tools/read_workflow.rb +58 -0
- data/lib/analytical_brain/tools/rename_session.rb +63 -0
- data/lib/analytical_brain/tools/set_goal.rb +60 -0
- data/lib/analytical_brain/tools/update_goal.rb +60 -0
- data/lib/analytical_brain.rb +23 -0
- data/lib/anima/cli/mcp/secrets.rb +76 -0
- data/lib/anima/cli/mcp.rb +197 -0
- data/lib/anima/cli.rb +5 -40
- data/lib/anima/installer.rb +168 -0
- data/lib/anima/settings.rb +226 -0
- data/lib/anima/version.rb +1 -1
- data/lib/anima.rb +9 -0
- data/lib/credential_store.rb +103 -0
- data/lib/environment_probe.rb +232 -0
- data/lib/events/subscribers/persister.rb +1 -0
- data/lib/events/user_message.rb +17 -0
- data/lib/llm/client.rb +29 -10
- data/lib/mcp/client_manager.rb +86 -0
- data/lib/mcp/config.rb +213 -0
- data/lib/mcp/health_check.rb +77 -0
- data/lib/mcp/secrets.rb +73 -0
- data/lib/mcp/stdio_transport.rb +206 -0
- data/lib/providers/anthropic.rb +11 -20
- data/lib/shell_session.rb +11 -10
- data/lib/skills/definition.rb +97 -0
- data/lib/skills/registry.rb +105 -0
- data/lib/tools/edit.rb +226 -0
- data/lib/tools/mcp_tool.rb +114 -0
- data/lib/tools/read.rb +151 -0
- data/lib/tools/registry.rb +14 -12
- data/lib/tools/request_feature.rb +121 -0
- data/lib/tools/return_result.rb +81 -0
- data/lib/tools/spawn_specialist.rb +109 -0
- data/lib/tools/spawn_subagent.rb +111 -0
- data/lib/tools/subagent_prompts.rb +12 -0
- data/lib/tools/web_get.rb +8 -9
- data/lib/tools/write.rb +86 -0
- data/lib/tui/app.rb +985 -26
- data/lib/tui/cable_client.rb +69 -31
- data/lib/tui/message_store.rb +103 -8
- data/lib/tui/screens/chat.rb +293 -45
- data/lib/workflows/definition.rb +97 -0
- data/lib/workflows/registry.rb +89 -0
- data/skills/activerecord/SKILL.md +255 -0
- data/skills/activerecord/examples/associations/association_extensions.rb +298 -0
- data/skills/activerecord/examples/associations/basic_associations.rb +118 -0
- data/skills/activerecord/examples/associations/counter_caches.rb +215 -0
- data/skills/activerecord/examples/associations/polymorphic_associations.rb +217 -0
- data/skills/activerecord/examples/associations/self_referential.rb +302 -0
- data/skills/activerecord/examples/associations/through_associations.rb +203 -0
- data/skills/activerecord/examples/basics/crud_operations.rb +209 -0
- data/skills/activerecord/examples/basics/dirty_tracking.rb +218 -0
- data/skills/activerecord/examples/basics/inheritance.rb +377 -0
- data/skills/activerecord/examples/basics/type_casting.rb +317 -0
- data/skills/activerecord/examples/callbacks/alternatives_to_callbacks.rb +447 -0
- data/skills/activerecord/examples/callbacks/conditional_callbacks.rb +353 -0
- data/skills/activerecord/examples/callbacks/lifecycle_callbacks.rb +280 -0
- data/skills/activerecord/examples/callbacks/transaction_callbacks.rb +340 -0
- data/skills/activerecord/examples/migrations/indexes_and_constraints.rb +337 -0
- data/skills/activerecord/examples/migrations/reversible_patterns.rb +403 -0
- data/skills/activerecord/examples/migrations/safe_patterns.rb +420 -0
- data/skills/activerecord/examples/migrations/schema_changes.rb +277 -0
- data/skills/activerecord/examples/querying/batch_processing.rb +226 -0
- data/skills/activerecord/examples/querying/eager_loading.rb +259 -0
- data/skills/activerecord/examples/querying/finder_methods.rb +170 -0
- data/skills/activerecord/examples/querying/optimization.rb +275 -0
- data/skills/activerecord/examples/querying/scopes.rb +260 -0
- data/skills/activerecord/examples/validations/built_in_validators.rb +277 -0
- data/skills/activerecord/examples/validations/conditional_validations.rb +288 -0
- data/skills/activerecord/examples/validations/custom_validators.rb +381 -0
- data/skills/activerecord/examples/validations/database_constraints.rb +432 -0
- data/skills/activerecord/examples/validations/validation_contexts.rb +367 -0
- data/skills/activerecord/references/associations.md +709 -0
- data/skills/activerecord/references/basics.md +622 -0
- data/skills/activerecord/references/callbacks.md +738 -0
- data/skills/activerecord/references/migrations.md +657 -0
- data/skills/activerecord/references/querying.md +655 -0
- data/skills/activerecord/references/validations.md +596 -0
- data/skills/dragonruby/SKILL.md +250 -0
- data/skills/dragonruby/examples/audio/audio_events.rb +55 -0
- data/skills/dragonruby/examples/audio/background_music.rb +29 -0
- data/skills/dragonruby/examples/audio/crossfade.rb +51 -0
- data/skills/dragonruby/examples/audio/music_controls.rb +51 -0
- data/skills/dragonruby/examples/audio/sound_effects.rb +30 -0
- data/skills/dragonruby/examples/core/coordinate_system.rb +27 -0
- data/skills/dragonruby/examples/core/hello_world.rb +24 -0
- data/skills/dragonruby/examples/core/labels.rb +22 -0
- data/skills/dragonruby/examples/core/sprites.rb +35 -0
- data/skills/dragonruby/examples/core/state_management.rb +29 -0
- data/skills/dragonruby/examples/distribution/background_pause.rb +42 -0
- data/skills/dragonruby/examples/distribution/build_workflow.sh +26 -0
- data/skills/dragonruby/examples/distribution/cvars_production.txt +16 -0
- data/skills/dragonruby/examples/distribution/game_metadata_hd.txt +23 -0
- data/skills/dragonruby/examples/distribution/game_metadata_minimal.txt +9 -0
- data/skills/dragonruby/examples/distribution/game_metadata_mobile.txt +31 -0
- data/skills/dragonruby/examples/distribution/platform_detection.rb +36 -0
- data/skills/dragonruby/examples/distribution/steam_metadata.txt +19 -0
- data/skills/dragonruby/examples/entities/collision_detection.rb +43 -0
- data/skills/dragonruby/examples/entities/entity_lifecycle.rb +68 -0
- data/skills/dragonruby/examples/entities/entity_storage.rb +38 -0
- data/skills/dragonruby/examples/entities/factory_methods.rb +45 -0
- data/skills/dragonruby/examples/entities/random_spawning.rb +50 -0
- data/skills/dragonruby/examples/game-logic/reset_patterns.rb +98 -0
- data/skills/dragonruby/examples/game-logic/save_load.rb +101 -0
- data/skills/dragonruby/examples/game-logic/scoring.rb +104 -0
- data/skills/dragonruby/examples/game-logic/state_transitions.rb +103 -0
- data/skills/dragonruby/examples/game-logic/timers.rb +87 -0
- data/skills/dragonruby/examples/input/action_triggers.rb +36 -0
- data/skills/dragonruby/examples/input/analog_movement.rb +28 -0
- data/skills/dragonruby/examples/input/controller_input.rb +28 -0
- data/skills/dragonruby/examples/input/directional_input.rb +24 -0
- data/skills/dragonruby/examples/input/keyboard_input.rb +28 -0
- data/skills/dragonruby/examples/input/mouse_click.rb +26 -0
- data/skills/dragonruby/examples/input/movement_with_bounds.rb +22 -0
- data/skills/dragonruby/examples/input/normalized_movement.rb +32 -0
- data/skills/dragonruby/examples/rendering/frame_animation.rb +32 -0
- data/skills/dragonruby/examples/rendering/labels.rb +32 -0
- data/skills/dragonruby/examples/rendering/layering.rb +51 -0
- data/skills/dragonruby/examples/rendering/solids.rb +61 -0
- data/skills/dragonruby/examples/rendering/sprites.rb +33 -0
- data/skills/dragonruby/examples/rendering/spritesheet_animation.rb +39 -0
- data/skills/dragonruby/examples/scenes/case_dispatch.rb +60 -0
- data/skills/dragonruby/examples/scenes/class_based.rb +150 -0
- data/skills/dragonruby/examples/scenes/pause_overlay.rb +100 -0
- data/skills/dragonruby/examples/scenes/safe_transitions.rb +68 -0
- data/skills/dragonruby/examples/scenes/scene_transitions.rb +98 -0
- data/skills/dragonruby/examples/scenes/send_dispatch.rb +88 -0
- data/skills/dragonruby/references/audio.md +396 -0
- data/skills/dragonruby/references/core.md +385 -0
- data/skills/dragonruby/references/distribution.md +434 -0
- data/skills/dragonruby/references/entities.md +516 -0
- data/skills/dragonruby/references/game-logic/persistence.md +386 -0
- data/skills/dragonruby/references/game-logic/state.md +389 -0
- data/skills/dragonruby/references/input.md +414 -0
- data/skills/dragonruby/references/rendering/animation.md +467 -0
- data/skills/dragonruby/references/rendering/primitives.md +403 -0
- data/skills/dragonruby/references/scenes.md +443 -0
- data/skills/draper-decorators/SKILL.md +344 -0
- data/skills/draper-decorators/examples/application_decorator.rb +61 -0
- data/skills/draper-decorators/examples/decorator_spec.rb +253 -0
- data/skills/draper-decorators/examples/model_decorator.rb +152 -0
- data/skills/draper-decorators/references/anti-patterns.md +640 -0
- data/skills/draper-decorators/references/patterns.md +507 -0
- data/skills/draper-decorators/references/testing.md +559 -0
- data/skills/gh-issue.md +182 -0
- data/skills/mcp-server/SKILL.md +177 -0
- data/skills/mcp-server/examples/dynamic_tools.rb +36 -0
- data/skills/mcp-server/examples/file_manager_tool.rb +85 -0
- data/skills/mcp-server/examples/http_client.rb +48 -0
- data/skills/mcp-server/examples/http_server.rb +97 -0
- data/skills/mcp-server/examples/rails_integration.rb +88 -0
- data/skills/mcp-server/examples/stdio_server.rb +108 -0
- data/skills/mcp-server/examples/streaming_client.rb +95 -0
- data/skills/mcp-server/references/gotchas.md +183 -0
- data/skills/mcp-server/references/prompts.md +98 -0
- data/skills/mcp-server/references/resources.md +53 -0
- data/skills/mcp-server/references/server.md +140 -0
- data/skills/mcp-server/references/tools.md +146 -0
- data/skills/mcp-server/references/transport.md +104 -0
- data/skills/ratatui-ruby/SKILL.md +315 -0
- data/skills/ratatui-ruby/references/core-concepts.md +340 -0
- data/skills/ratatui-ruby/references/events.md +387 -0
- data/skills/ratatui-ruby/references/frameworks.md +522 -0
- data/skills/ratatui-ruby/references/layout.md +423 -0
- data/skills/ratatui-ruby/references/styling.md +268 -0
- data/skills/ratatui-ruby/references/testing.md +433 -0
- data/skills/ratatui-ruby/references/widgets.md +532 -0
- data/skills/rspec/SKILL.md +340 -0
- data/skills/rspec/examples/core/basic_structure.rb +69 -0
- data/skills/rspec/examples/core/configuration.rb +126 -0
- data/skills/rspec/examples/core/hooks.rb +126 -0
- data/skills/rspec/examples/core/memoized_helpers.rb +139 -0
- data/skills/rspec/examples/core/metadata_filtering.rb +144 -0
- data/skills/rspec/examples/core/shared_examples.rb +145 -0
- data/skills/rspec/examples/factory_bot/associations.rb +314 -0
- data/skills/rspec/examples/factory_bot/build_strategies.rb +272 -0
- data/skills/rspec/examples/factory_bot/callbacks.rb +320 -0
- data/skills/rspec/examples/factory_bot/custom_construction.rb +328 -0
- data/skills/rspec/examples/factory_bot/factory_definition.rb +191 -0
- data/skills/rspec/examples/factory_bot/inheritance.rb +314 -0
- data/skills/rspec/examples/factory_bot/traits.rb +293 -0
- data/skills/rspec/examples/factory_bot/transients.rb +229 -0
- data/skills/rspec/examples/matchers/change.rb +115 -0
- data/skills/rspec/examples/matchers/collections.rb +154 -0
- data/skills/rspec/examples/matchers/comparisons.rb +79 -0
- data/skills/rspec/examples/matchers/composing.rb +155 -0
- data/skills/rspec/examples/matchers/custom_matchers.rb +197 -0
- data/skills/rspec/examples/matchers/equality.rb +58 -0
- data/skills/rspec/examples/matchers/errors.rb +136 -0
- data/skills/rspec/examples/matchers/output.rb +103 -0
- data/skills/rspec/examples/matchers/predicates.rb +87 -0
- data/skills/rspec/examples/matchers/truthiness.rb +101 -0
- data/skills/rspec/examples/matchers/types.rb +82 -0
- data/skills/rspec/examples/matchers/yield.rb +147 -0
- data/skills/rspec/examples/mocks/any_instance.rb +172 -0
- data/skills/rspec/examples/mocks/argument_matchers.rb +206 -0
- data/skills/rspec/examples/mocks/constants.rb +177 -0
- data/skills/rspec/examples/mocks/doubles.rb +139 -0
- data/skills/rspec/examples/mocks/expectations.rb +137 -0
- data/skills/rspec/examples/mocks/message_chains.rb +173 -0
- data/skills/rspec/examples/mocks/ordering.rb +144 -0
- data/skills/rspec/examples/mocks/receive_counts.rb +181 -0
- data/skills/rspec/examples/mocks/responses.rb +223 -0
- data/skills/rspec/examples/mocks/spies.rb +149 -0
- data/skills/rspec/examples/mocks/stubbing.rb +133 -0
- data/skills/rspec/examples/rails/channels.rb +250 -0
- data/skills/rspec/examples/rails/controller_specs.rb +302 -0
- data/skills/rspec/examples/rails/helper_specs.rb +245 -0
- data/skills/rspec/examples/rails/job_specs.rb +256 -0
- data/skills/rspec/examples/rails/mailer_specs.rb +228 -0
- data/skills/rspec/examples/rails/matchers.rb +374 -0
- data/skills/rspec/examples/rails/model_specs.rb +193 -0
- data/skills/rspec/examples/rails/request_specs.rb +275 -0
- data/skills/rspec/examples/rails/routing_specs.rb +276 -0
- data/skills/rspec/examples/rails/system_specs.rb +294 -0
- data/skills/rspec/examples/rails/transactions.rb +254 -0
- data/skills/rspec/examples/rails/view_specs.rb +252 -0
- data/skills/rspec/references/core.md +816 -0
- data/skills/rspec/references/factory_bot.md +641 -0
- data/skills/rspec/references/matchers.md +516 -0
- data/skills/rspec/references/mocks.md +381 -0
- data/skills/rspec/references/rails.md +528 -0
- data/templates/soul.md +40 -0
- data/workflows/commit.md +45 -0
- data/workflows/create_handoff.md +98 -0
- data/workflows/create_note.md +82 -0
- data/workflows/create_plan.md +457 -0
- data/workflows/decompose_ticket.md +109 -0
- data/workflows/feature.md +91 -0
- data/workflows/implement_plan.md +87 -0
- data/workflows/iterate_plan.md +247 -0
- data/workflows/research_codebase.md +210 -0
- data/workflows/resume_handoff.md +217 -0
- data/workflows/review_pr.md +320 -0
- data/workflows/thoughts_init.md +71 -0
- data/workflows/validate_plan.md +166 -0
- metadata +290 -3
- data/app/controllers/api/sessions_controller.rb +0 -25
- 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
|