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
data/skills/gh-issue.md
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: gh-issue
|
|
3
|
+
description: "GitHub issue writing with WHAT/WHY/HOW framework. Activate when user creates issues, writes tickets, drafts GitHub issues, or edits issue descriptions."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# GitHub Issue Writing
|
|
7
|
+
|
|
8
|
+
Write GitHub issues with clear rationale using the WHAT/WHY/HOW framework. Every issue must answer three questions for the reader: What problem are we solving? Why does it matter? How will we solve it?
|
|
9
|
+
|
|
10
|
+
## Issue Structure
|
|
11
|
+
|
|
12
|
+
### Title
|
|
13
|
+
|
|
14
|
+
Action-oriented, concise (under 70 characters).
|
|
15
|
+
|
|
16
|
+
**Format:** `[Noun] [Action]` or `[Action] [Noun]`
|
|
17
|
+
|
|
18
|
+
**Examples:**
|
|
19
|
+
- "Assistant Model" (noun for foundational work)
|
|
20
|
+
- "Twitter Banner Verification" (noun phrase for feature)
|
|
21
|
+
- "Add story_context MCP tool" (action for specific implementation)
|
|
22
|
+
|
|
23
|
+
### Body Template
|
|
24
|
+
|
|
25
|
+
```markdown
|
|
26
|
+
## Problem to solve
|
|
27
|
+
[1-3 sentences: What gap, pain point, or need exists? Why can't we proceed without this?]
|
|
28
|
+
|
|
29
|
+
## Solution
|
|
30
|
+
[1-2 sentences: High-level approach to address the problem]
|
|
31
|
+
|
|
32
|
+
## [Details Section - varies by issue type]
|
|
33
|
+
[Fields, Routes, Flow, Requirements - whatever is relevant]
|
|
34
|
+
|
|
35
|
+
## Why [decision]?
|
|
36
|
+
[Explain non-obvious choices. Skip if decision is self-evident]
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## The Three Questions
|
|
40
|
+
|
|
41
|
+
### WHAT (Problem to solve)
|
|
42
|
+
|
|
43
|
+
State the problem from user/system perspective, not implementation perspective.
|
|
44
|
+
|
|
45
|
+
**Bad:** "We need to create an Assistant model with fields for name and API key."
|
|
46
|
+
**Good:** "The platform needs a core identity for AI agents. Without this model, agents cannot register or interact with the platform."
|
|
47
|
+
|
|
48
|
+
### WHY (Rationale)
|
|
49
|
+
|
|
50
|
+
Explain decisions that aren't self-evident. Include "Why X?" sections for:
|
|
51
|
+
- Architectural choices (Why separate models? Why MCP not REST?)
|
|
52
|
+
- Technology decisions (Why Twitter? Why no password?)
|
|
53
|
+
- Scope decisions (Why optional field? Why this validation?)
|
|
54
|
+
|
|
55
|
+
Skip rationale for obvious decisions. Don't explain why a user model has an email field.
|
|
56
|
+
|
|
57
|
+
### HOW (Solution details)
|
|
58
|
+
|
|
59
|
+
Include enough detail to implement without ambiguity:
|
|
60
|
+
- **Models:** Fields with types/constraints, associations, behaviors
|
|
61
|
+
- **Endpoints:** Routes, request/response formats, logic
|
|
62
|
+
- **Flows:** Step-by-step sequences with edge cases
|
|
63
|
+
- **Validation:** Rules with specific constraints
|
|
64
|
+
|
|
65
|
+
## Issue Types and Patterns
|
|
66
|
+
|
|
67
|
+
### Model/Entity Issues
|
|
68
|
+
|
|
69
|
+
```markdown
|
|
70
|
+
## Problem to solve
|
|
71
|
+
[Why this entity needs to exist in the system]
|
|
72
|
+
|
|
73
|
+
## Solution
|
|
74
|
+
Create `ModelName` model for [purpose].
|
|
75
|
+
|
|
76
|
+
## Fields
|
|
77
|
+
- `field_name` — description, constraints
|
|
78
|
+
- `belongs_to :other` — relationship explanation
|
|
79
|
+
|
|
80
|
+
## Behavior
|
|
81
|
+
- [Key behaviors and state transitions]
|
|
82
|
+
- [Validation rules]
|
|
83
|
+
|
|
84
|
+
## Why [architectural decision]?
|
|
85
|
+
[Explain non-obvious choices like STI vs separate models]
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Feature/Flow Issues
|
|
89
|
+
|
|
90
|
+
```markdown
|
|
91
|
+
## Problem to solve
|
|
92
|
+
[User need or system requirement]
|
|
93
|
+
|
|
94
|
+
## Solution
|
|
95
|
+
[High-level approach]
|
|
96
|
+
|
|
97
|
+
## Flow
|
|
98
|
+
1. [Step with actor]
|
|
99
|
+
2. [Step with system response]
|
|
100
|
+
3. [Step with outcome]
|
|
101
|
+
|
|
102
|
+
## Implementation Notes
|
|
103
|
+
- [Technical details]
|
|
104
|
+
- [Edge cases]
|
|
105
|
+
|
|
106
|
+
## Why [approach]?
|
|
107
|
+
[Rationale for chosen solution over alternatives]
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Research/Spike Issues
|
|
111
|
+
|
|
112
|
+
```markdown
|
|
113
|
+
## Problem to solve
|
|
114
|
+
[What we don't know that blocks progress]
|
|
115
|
+
|
|
116
|
+
## Research Questions
|
|
117
|
+
- [ ] [Specific question to answer]
|
|
118
|
+
- [ ] [Another question]
|
|
119
|
+
|
|
120
|
+
## Context
|
|
121
|
+
[What we already know, constraints]
|
|
122
|
+
|
|
123
|
+
## Acceptance Criteria
|
|
124
|
+
[What "done" looks like for research]
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Writing Guidelines
|
|
128
|
+
|
|
129
|
+
### Be Specific
|
|
130
|
+
|
|
131
|
+
**Bad:** "Handle edge cases"
|
|
132
|
+
**Good:** "Handle: already verified, name not found, Twitter API timeout"
|
|
133
|
+
|
|
134
|
+
### Use Concrete Examples
|
|
135
|
+
|
|
136
|
+
**Bad:** "Name must be valid"
|
|
137
|
+
**Good:** "Name must be URL-safe (alphanumeric, underscores, hyphens), 3-30 characters"
|
|
138
|
+
|
|
139
|
+
### Show, Don't Just Tell
|
|
140
|
+
|
|
141
|
+
Include code snippets for interfaces:
|
|
142
|
+
|
|
143
|
+
```markdown
|
|
144
|
+
## MCP Interface
|
|
145
|
+
\`\`\`
|
|
146
|
+
Tool: register_assistant
|
|
147
|
+
Input: { name: "bonk" }
|
|
148
|
+
Output: {
|
|
149
|
+
api_key: "oic_abc123...",
|
|
150
|
+
verification_url: "https://openinstaclaw.ai/bonk"
|
|
151
|
+
}
|
|
152
|
+
\`\`\`
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## gh CLI Usage
|
|
156
|
+
|
|
157
|
+
Create issues with proper formatting:
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
gh issue create \
|
|
161
|
+
--title "Assistant Model" \
|
|
162
|
+
--body "$(cat <<'EOF'
|
|
163
|
+
## Problem to solve
|
|
164
|
+
[Content here]
|
|
165
|
+
|
|
166
|
+
## Solution
|
|
167
|
+
[Content here]
|
|
168
|
+
EOF
|
|
169
|
+
)"
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## Quality Checklist
|
|
173
|
+
|
|
174
|
+
Before submitting an issue:
|
|
175
|
+
|
|
176
|
+
- [ ] Title is action-oriented and under 70 characters
|
|
177
|
+
- [ ] "Problem to solve" explains the need, not the implementation
|
|
178
|
+
- [ ] Solution is stated at high level before diving into details
|
|
179
|
+
- [ ] Non-obvious decisions have "Why X?" explanations
|
|
180
|
+
- [ ] Implementation details are specific enough to code from
|
|
181
|
+
- [ ] Edge cases and validation rules are explicit
|
|
182
|
+
- [ ] Code examples included for interfaces/APIs
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: mcp-server
|
|
3
|
+
description: "MCP server development in Ruby — tools, prompts, resources, transport. Activate when building Model Context Protocol servers, defining tools/prompts/resources, working with the mcp gem, or discussing LLM tool integrations."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# MCP Ruby SDK - Server Development Guide
|
|
7
|
+
|
|
8
|
+
Build Model Context Protocol servers in Ruby using the official `mcp` gem (maintained by Anthropic and Shopify).
|
|
9
|
+
|
|
10
|
+
## Design Philosophy
|
|
11
|
+
|
|
12
|
+
### Information Provider, Not Analyzer
|
|
13
|
+
|
|
14
|
+
MCP servers provide structured data; LLMs do the reasoning. Return comprehensive frameworks and raw information—let the client perform analysis and context-dependent decisions.
|
|
15
|
+
|
|
16
|
+
> "The MCP server's job is to be the world's best research assistant, not a competing analyst." — Matt Adams
|
|
17
|
+
|
|
18
|
+
### Context Preservation
|
|
19
|
+
|
|
20
|
+
Agents have limited context windows. Every byte returned that wasn't requested is a byte that could have held useful context. Treat context preservation as a first-class design constraint.
|
|
21
|
+
|
|
22
|
+
**Principles:**
|
|
23
|
+
- Never return data that wasn't explicitly requested
|
|
24
|
+
- Mutations are quiet—return confirmations, not data dumps
|
|
25
|
+
- Explicit over implicit—associations only when asked
|
|
26
|
+
- Filter large datasets before returning (10,000 rows → 5 relevant rows)
|
|
27
|
+
|
|
28
|
+
### Domain-Aligned Vocabulary
|
|
29
|
+
|
|
30
|
+
Tools should speak the language of your domain, not database/CRUD terminology. Agents are collaborators in your domain process, not database clients.
|
|
31
|
+
|
|
32
|
+
**Example:** A visual novel asset server uses `create_image`, `make_sprite`, `place_character`, `explore_variations`, `compare_images`—not `generate`, `remove_background`, `composite`, `batch_generate`, `get_diff`.
|
|
33
|
+
|
|
34
|
+
### Tool Budget Management
|
|
35
|
+
|
|
36
|
+
Too many tools overwhelm agents and increase costs. Design toolsets around clear use cases, not API endpoint mirrors.
|
|
37
|
+
|
|
38
|
+
- Group related functionality intelligently
|
|
39
|
+
- Use lazy loading for large tool sets (150K tokens → 2K via on-demand discovery)
|
|
40
|
+
- Tool names ≤64 characters, descriptions narrow and unambiguous
|
|
41
|
+
|
|
42
|
+
### Security: The Lethal Trifecta
|
|
43
|
+
|
|
44
|
+
Three capabilities that, when combined, create vulnerabilities (Simon Willison):
|
|
45
|
+
1. Access to private data
|
|
46
|
+
2. Exposure to untrusted content
|
|
47
|
+
3. External communication capabilities
|
|
48
|
+
|
|
49
|
+
**Required:** Explicit user consent before tool invocation, clear UI showing exposed tools, alerts when tool descriptions change.
|
|
50
|
+
|
|
51
|
+
## Domain Components
|
|
52
|
+
|
|
53
|
+
| Component | Purpose | Reference |
|
|
54
|
+
|-----------|---------|-----------|
|
|
55
|
+
| **Tools** | Define callable functions with input/output schemas | [`references/tools.md`](references/tools.md) |
|
|
56
|
+
| **Prompts** | Template-based message generators | [`references/prompts.md`](references/prompts.md) |
|
|
57
|
+
| **Resources** | Static and dynamic file/data registration | [`references/resources.md`](references/resources.md) |
|
|
58
|
+
| **Server** | Core server initialization and configuration | [`references/server.md`](references/server.md) |
|
|
59
|
+
| **Transport** | STDIO and HTTP transport options | [`references/transport.md`](references/transport.md) |
|
|
60
|
+
| **Gotchas** | Tricky behaviors and error handling | [`references/gotchas.md`](references/gotchas.md) |
|
|
61
|
+
|
|
62
|
+
## Key Concepts
|
|
63
|
+
|
|
64
|
+
| Concept | Purpose |
|
|
65
|
+
|---------|---------|
|
|
66
|
+
| `MCP::Tool` | Base class for defining callable tools |
|
|
67
|
+
| `MCP::Prompt` | Base class for prompt templates |
|
|
68
|
+
| `MCP::Resource` | Static resource registration |
|
|
69
|
+
| `MCP::ResourceTemplate` | Dynamic URI-based resources |
|
|
70
|
+
| `server_context` | Request-scoped data passed to handlers |
|
|
71
|
+
| `MCP::Tool::Response` | Structured tool return value |
|
|
72
|
+
|
|
73
|
+
## Tool Definition Patterns
|
|
74
|
+
|
|
75
|
+
| Pattern | Use Case |
|
|
76
|
+
|---------|----------|
|
|
77
|
+
| Class-based (`< MCP::Tool`) | Reusable tools with complex logic |
|
|
78
|
+
| Block-based (`MCP::Tool.define`) | Inline, simple tools |
|
|
79
|
+
| Dynamic (`server.define_tool`) | Runtime tool registration |
|
|
80
|
+
|
|
81
|
+
## Transport Decision Tree
|
|
82
|
+
|
|
83
|
+
```
|
|
84
|
+
What environment?
|
|
85
|
+
├── CLI tool / Local server
|
|
86
|
+
│ └── Use STDIO transport
|
|
87
|
+
└── Web server / Production
|
|
88
|
+
└── Need sessions and notifications?
|
|
89
|
+
├── YES → Use Streamable HTTP (stateful)
|
|
90
|
+
└── NO → Use Streamable HTTP (stateless)
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Quick Comparison
|
|
94
|
+
|
|
95
|
+
| Transport | Sessions | Notifications | Use For |
|
|
96
|
+
|-----------|----------|---------------|---------|
|
|
97
|
+
| STDIO | N/A | Yes | CLI tools, local dev |
|
|
98
|
+
| HTTP (stateful) | Yes | Yes | Web apps, long-lived connections |
|
|
99
|
+
| HTTP (stateless) | No | No | Simple request/response APIs |
|
|
100
|
+
|
|
101
|
+
## Protocol Version Features
|
|
102
|
+
|
|
103
|
+
| Feature | Minimum Version |
|
|
104
|
+
|---------|-----------------|
|
|
105
|
+
| `description` | 2025-11-25 |
|
|
106
|
+
| `instructions` | 2025-03-26 |
|
|
107
|
+
| `annotations` | 2025-03-26 |
|
|
108
|
+
| `output_schema` | 2025-03-26 |
|
|
109
|
+
|
|
110
|
+
## Best Practices
|
|
111
|
+
|
|
112
|
+
### Do
|
|
113
|
+
|
|
114
|
+
- Use `tool_name` for namespaced classes to avoid conflicts
|
|
115
|
+
- Use `additionalProperties: false` for strict schema validation
|
|
116
|
+
- Use mutex for shared state in HTTP transport (thread safety)
|
|
117
|
+
- Return error responses for business errors (`Response.new([...], error: true)`)
|
|
118
|
+
- Check protocol version before using newer features
|
|
119
|
+
- Use `server_context` for request-scoped data (user_id, env)
|
|
120
|
+
|
|
121
|
+
### Don't
|
|
122
|
+
|
|
123
|
+
- Don't use `$ref` in schemas (raises ArgumentError, inline only)
|
|
124
|
+
- Don't assume extra args are rejected (`additionalProperties` defaults to allowing extras)
|
|
125
|
+
- Don't use `rpc.` prefix (reserved for protocol methods)
|
|
126
|
+
- Don't send notifications in stateless mode (raises RuntimeError)
|
|
127
|
+
- Don't rely on validation order (required args checked before JSON Schema)
|
|
128
|
+
|
|
129
|
+
## Anti-Patterns Quick List
|
|
130
|
+
|
|
131
|
+
| Anti-Pattern | Solution |
|
|
132
|
+
|--------------|----------|
|
|
133
|
+
| Missing `additionalProperties: false` | Add to schema for strict validation |
|
|
134
|
+
| Using `$ref` in schemas | Inline all definitions |
|
|
135
|
+
| Notifications in stateless mode | Use stateful transport or skip notifications |
|
|
136
|
+
| Hardcoded server_context | Pass dynamically based on request |
|
|
137
|
+
| Ignoring protocol version | Check version before using gated features |
|
|
138
|
+
| Blocking in tool handlers | Use async patterns for long operations |
|
|
139
|
+
|
|
140
|
+
## Key Points
|
|
141
|
+
|
|
142
|
+
1. **Validation is multi-layered** - Required args checked first, then JSON Schema validation
|
|
143
|
+
2. **Notifications are fire-and-forget** - Errors reported but don't propagate
|
|
144
|
+
3. **Protocol version matters** - Features are gated by version
|
|
145
|
+
4. **Server context is opt-in** - Detected from method signature (must include `server_context:` parameter)
|
|
146
|
+
5. **Schemas are immutable** - Validated at class load time, not runtime
|
|
147
|
+
|
|
148
|
+
## Additional Resources
|
|
149
|
+
|
|
150
|
+
### Reference Files
|
|
151
|
+
|
|
152
|
+
For detailed DSL syntax by domain:
|
|
153
|
+
|
|
154
|
+
- **`references/tools.md`** - Tool definition, responses, schemas, annotations
|
|
155
|
+
- **`references/prompts.md`** - Prompt definition, arguments, content types
|
|
156
|
+
- **`references/resources.md`** - Resource registration, templates, read handlers
|
|
157
|
+
- **`references/server.md`** - Server initialization, configuration, custom methods
|
|
158
|
+
- **`references/transport.md`** - Transport config, protocol methods, sessions
|
|
159
|
+
- **`references/gotchas.md`** - Tricky behaviors, error handling, edge cases
|
|
160
|
+
|
|
161
|
+
### Example Files
|
|
162
|
+
|
|
163
|
+
Working examples in `examples/`:
|
|
164
|
+
|
|
165
|
+
- **`examples/stdio_server.rb`** - Complete STDIO server with tools, prompts, resources
|
|
166
|
+
- **`examples/http_server.rb`** - HTTP server with Rack and logging
|
|
167
|
+
- **`examples/rails_integration.rb`** - Rails controller, routes, and initializer
|
|
168
|
+
- **`examples/file_manager_tool.rb`** - Sandboxed file operations with security patterns
|
|
169
|
+
- **`examples/dynamic_tools.rb`** - Runtime tool registration with notifications
|
|
170
|
+
- **`examples/http_client.rb`** - HTTP client connecting to MCP server
|
|
171
|
+
- **`examples/streaming_client.rb`** - SSE streaming client for real-time notifications
|
|
172
|
+
|
|
173
|
+
### External Links
|
|
174
|
+
|
|
175
|
+
- [MCP Ruby SDK on GitHub](https://github.com/modelcontextprotocol/ruby-sdk)
|
|
176
|
+
- [MCP Protocol Specification](https://modelcontextprotocol.io)
|
|
177
|
+
- [RubyDoc API Reference](https://rubydoc.info/gems/mcp)
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Dynamic Tool Registration
|
|
4
|
+
# Demonstrates: server.define_tool, notify_tools_list_changed, plugin pattern
|
|
5
|
+
#
|
|
6
|
+
# See: ../references/server.md, ../references/tools.md
|
|
7
|
+
|
|
8
|
+
require "mcp"
|
|
9
|
+
|
|
10
|
+
class PluginManager
|
|
11
|
+
def initialize(server)
|
|
12
|
+
@server = server
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def load_plugin(name, &block)
|
|
16
|
+
@server.define_tool(
|
|
17
|
+
name: "plugin_#{name}",
|
|
18
|
+
description: "Plugin: #{name}",
|
|
19
|
+
input_schema: { properties: { input: { type: "string" } } }
|
|
20
|
+
) do |input:, server_context:|
|
|
21
|
+
result = block.call(input, server_context)
|
|
22
|
+
MCP::Tool::Response.new([{ type: "text", text: result.to_s }])
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
@server.notify_tools_list_changed
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Usage
|
|
30
|
+
server = MCP::Server.new(name: "plugin_server", tools: [])
|
|
31
|
+
transport = MCP::Server::Transports::StreamableHTTPTransport.new(server)
|
|
32
|
+
server.transport = transport
|
|
33
|
+
|
|
34
|
+
manager = PluginManager.new(server)
|
|
35
|
+
manager.load_plugin("uppercase") { |input, _| input.upcase }
|
|
36
|
+
manager.load_plugin("reverse") { |input, _| input.reverse }
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# File Manager Tool with sandboxed operations
|
|
4
|
+
# Demonstrates: Security patterns, annotations, error responses, tool_name
|
|
5
|
+
#
|
|
6
|
+
# See: ../references/tools.md, ../references/gotchas.md
|
|
7
|
+
|
|
8
|
+
require "mcp"
|
|
9
|
+
require "fileutils"
|
|
10
|
+
|
|
11
|
+
class FileManagerTool < MCP::Tool
|
|
12
|
+
tool_name "file_manager"
|
|
13
|
+
description "Manages files in a sandboxed directory"
|
|
14
|
+
|
|
15
|
+
input_schema(
|
|
16
|
+
properties: {
|
|
17
|
+
action: { type: "string", enum: %w[read write list delete] },
|
|
18
|
+
path: { type: "string" },
|
|
19
|
+
content: { type: "string" }
|
|
20
|
+
},
|
|
21
|
+
required: %w[action path]
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
annotations(
|
|
25
|
+
destructive_hint: true,
|
|
26
|
+
idempotent_hint: false,
|
|
27
|
+
read_only_hint: false
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
SANDBOX_DIR = "/tmp/mcp_sandbox"
|
|
31
|
+
|
|
32
|
+
class << self
|
|
33
|
+
def call(action:, path:, content: nil, server_context: nil)
|
|
34
|
+
full_path = File.join(SANDBOX_DIR, path)
|
|
35
|
+
|
|
36
|
+
# Security: Ensure path is within sandbox
|
|
37
|
+
unless full_path.start_with?(SANDBOX_DIR)
|
|
38
|
+
return error_response("Path traversal not allowed")
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
case action
|
|
42
|
+
when "read" then read_file(full_path)
|
|
43
|
+
when "write" then write_file(full_path, content)
|
|
44
|
+
when "list" then list_directory(full_path)
|
|
45
|
+
when "delete" then delete_file(full_path)
|
|
46
|
+
else error_response("Unknown action: #{action}")
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
def read_file(path)
|
|
53
|
+
return error_response("File not found") unless File.exist?(path)
|
|
54
|
+
|
|
55
|
+
MCP::Tool::Response.new([{ type: "text", text: File.read(path) }])
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def write_file(path, content)
|
|
59
|
+
FileUtils.mkdir_p(File.dirname(path))
|
|
60
|
+
File.write(path, content)
|
|
61
|
+
MCP::Tool::Response.new([{ type: "text", text: "Written #{content.bytesize} bytes" }])
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def list_directory(path)
|
|
65
|
+
return error_response("Directory not found") unless Dir.exist?(path)
|
|
66
|
+
|
|
67
|
+
files = Dir.entries(path).reject { |f| f.start_with?(".") }
|
|
68
|
+
MCP::Tool::Response.new(
|
|
69
|
+
[{ type: "text", text: files.join("\n") }],
|
|
70
|
+
structured_content: files
|
|
71
|
+
)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def delete_file(path)
|
|
75
|
+
return error_response("File not found") unless File.exist?(path)
|
|
76
|
+
|
|
77
|
+
File.delete(path)
|
|
78
|
+
MCP::Tool::Response.new([{ type: "text", text: "Deleted" }])
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def error_response(message)
|
|
82
|
+
MCP::Tool::Response.new([{ type: "text", text: message }], error: true)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# HTTP Client Example
|
|
5
|
+
# Demonstrates: MCP::Client, HTTP transport, tool/prompt discovery and invocation
|
|
6
|
+
#
|
|
7
|
+
# See: ../references/transport.md
|
|
8
|
+
|
|
9
|
+
require "mcp"
|
|
10
|
+
|
|
11
|
+
# Create HTTP transport
|
|
12
|
+
http = MCP::Client::HTTP.new(
|
|
13
|
+
url: "http://localhost:9292",
|
|
14
|
+
headers: { "Authorization" => "Bearer token123" }
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
client = MCP::Client.new(transport: http)
|
|
18
|
+
|
|
19
|
+
# List tools
|
|
20
|
+
tools = client.tools
|
|
21
|
+
puts "Available tools:"
|
|
22
|
+
tools.each do |tool|
|
|
23
|
+
puts " - #{tool.name}: #{tool.description}"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Call a tool
|
|
27
|
+
if (weather_tool = tools.find { |t| t.name == "weather" })
|
|
28
|
+
result = client.call_tool(
|
|
29
|
+
tool: weather_tool,
|
|
30
|
+
arguments: { location: "London", units: "celsius" }
|
|
31
|
+
)
|
|
32
|
+
puts "Weather: #{result.dig('result', 'content', 0, 'text')}"
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# List prompts
|
|
36
|
+
prompts = client.prompts
|
|
37
|
+
prompts.each do |prompt|
|
|
38
|
+
puts "Prompt: #{prompt['name']}"
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Get a prompt
|
|
42
|
+
if prompts.any?
|
|
43
|
+
prompt_result = client.get_prompt(
|
|
44
|
+
name: prompts.first["name"],
|
|
45
|
+
arguments: { code: "puts 'hello'" }
|
|
46
|
+
)
|
|
47
|
+
puts "Messages: #{prompt_result['messages'].length}"
|
|
48
|
+
end
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# HTTP MCP Server with Rack
|
|
5
|
+
# Demonstrates: StreamableHTTP transport, configuration, logging, output_schema
|
|
6
|
+
#
|
|
7
|
+
# See: ../references/transport.md, ../references/server.md, ../references/tools.md
|
|
8
|
+
#
|
|
9
|
+
# Run: ruby http_server.rb
|
|
10
|
+
# Server starts on http://localhost:9292
|
|
11
|
+
|
|
12
|
+
require "mcp"
|
|
13
|
+
require "rack"
|
|
14
|
+
require "rackup"
|
|
15
|
+
require "json"
|
|
16
|
+
require "logger"
|
|
17
|
+
|
|
18
|
+
class WeatherTool < MCP::Tool
|
|
19
|
+
description "Gets weather for a location"
|
|
20
|
+
input_schema(
|
|
21
|
+
properties: {
|
|
22
|
+
location: { type: "string" },
|
|
23
|
+
units: { type: "string", enum: %w[celsius fahrenheit] }
|
|
24
|
+
},
|
|
25
|
+
required: ["location"]
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
output_schema(
|
|
29
|
+
properties: {
|
|
30
|
+
temperature: { type: "number" },
|
|
31
|
+
condition: { type: "string" }
|
|
32
|
+
},
|
|
33
|
+
required: %w[temperature condition]
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
annotations(read_only_hint: true, destructive_hint: false)
|
|
37
|
+
|
|
38
|
+
class << self
|
|
39
|
+
def call(location:, units: "celsius", server_context: nil)
|
|
40
|
+
# Simulate API call
|
|
41
|
+
data = { temperature: 22, condition: "sunny" }
|
|
42
|
+
|
|
43
|
+
MCP::Tool::Response.new(
|
|
44
|
+
[{ type: "text", text: data.to_json }],
|
|
45
|
+
structured_content: data
|
|
46
|
+
)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
$logger = Logger.new($stdout)
|
|
52
|
+
$logger.level = Logger::INFO
|
|
53
|
+
|
|
54
|
+
config = MCP::Configuration.new(
|
|
55
|
+
protocol_version: "2025-11-25",
|
|
56
|
+
exception_reporter: ->(e, ctx) {
|
|
57
|
+
$logger.error("MCP Error: #{e.message}")
|
|
58
|
+
$logger.debug(ctx.inspect)
|
|
59
|
+
},
|
|
60
|
+
instrumentation_callback: ->(data) {
|
|
61
|
+
$logger.info("MCP: #{data[:method]} (#{data[:duration]}s)")
|
|
62
|
+
}
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
server = MCP::Server.new(
|
|
66
|
+
name: "weather_server",
|
|
67
|
+
version: "1.0.0",
|
|
68
|
+
description: "Weather information server",
|
|
69
|
+
tools: [WeatherTool],
|
|
70
|
+
configuration: config
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
transport = MCP::Server::Transports::StreamableHTTPTransport.new(server)
|
|
74
|
+
server.transport = transport
|
|
75
|
+
|
|
76
|
+
app = proc do |env|
|
|
77
|
+
request = Rack::Request.new(env)
|
|
78
|
+
|
|
79
|
+
if request.post?
|
|
80
|
+
body = request.body.read
|
|
81
|
+
request.body.rewind
|
|
82
|
+
$logger.debug("Request: #{body}")
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
response = transport.handle_request(request)
|
|
86
|
+
$logger.debug("Response: #{response[2].first}") if response[2].respond_to?(:first)
|
|
87
|
+
response
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
rack_app = Rack::Builder.new do
|
|
91
|
+
use Rack::CommonLogger, $logger
|
|
92
|
+
use Rack::ShowExceptions
|
|
93
|
+
run app
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
puts "Starting MCP server on http://localhost:9292"
|
|
97
|
+
Rackup::Handler.get("puma").run(rack_app, Port: 9292, Host: "localhost")
|