debug-mcp 0.1.2
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 +7 -0
- data/.rspec +3 -0
- data/CHANGELOG.md +83 -0
- data/LICENSE +21 -0
- data/README.ja.md +383 -0
- data/README.md +384 -0
- data/examples/01_simple_bug.rb +43 -0
- data/examples/02_data_pipeline.rb +93 -0
- data/examples/03_recursion.rb +96 -0
- data/examples/RAILS_SCENARIOS.md +350 -0
- data/examples/SCENARIOS.md +142 -0
- data/examples/rails_test_app/setup.sh +428 -0
- data/examples/rails_test_app/testapp/.dockerignore +10 -0
- data/examples/rails_test_app/testapp/.ruby-version +1 -0
- data/examples/rails_test_app/testapp/Dockerfile +23 -0
- data/examples/rails_test_app/testapp/Gemfile +17 -0
- data/examples/rails_test_app/testapp/README.md +65 -0
- data/examples/rails_test_app/testapp/Rakefile +6 -0
- data/examples/rails_test_app/testapp/app/assets/images/.keep +0 -0
- data/examples/rails_test_app/testapp/app/assets/stylesheets/application.css +1 -0
- data/examples/rails_test_app/testapp/app/controllers/application_controller.rb +4 -0
- data/examples/rails_test_app/testapp/app/controllers/concerns/.keep +0 -0
- data/examples/rails_test_app/testapp/app/controllers/dashboard_controller.rb +38 -0
- data/examples/rails_test_app/testapp/app/controllers/health_controller.rb +11 -0
- data/examples/rails_test_app/testapp/app/controllers/orders_controller.rb +100 -0
- data/examples/rails_test_app/testapp/app/controllers/posts_controller.rb +82 -0
- data/examples/rails_test_app/testapp/app/controllers/sessions_controller.rb +25 -0
- data/examples/rails_test_app/testapp/app/controllers/users_controller.rb +44 -0
- data/examples/rails_test_app/testapp/app/helpers/application_helper.rb +2 -0
- data/examples/rails_test_app/testapp/app/models/application_record.rb +3 -0
- data/examples/rails_test_app/testapp/app/models/comment.rb +8 -0
- data/examples/rails_test_app/testapp/app/models/concerns/.keep +0 -0
- data/examples/rails_test_app/testapp/app/models/order.rb +56 -0
- data/examples/rails_test_app/testapp/app/models/order_item.rb +16 -0
- data/examples/rails_test_app/testapp/app/models/post.rb +29 -0
- data/examples/rails_test_app/testapp/app/models/user.rb +34 -0
- data/examples/rails_test_app/testapp/app/services/order_report_service.rb +40 -0
- data/examples/rails_test_app/testapp/app/views/layouts/application.html.erb +28 -0
- data/examples/rails_test_app/testapp/app/views/pwa/manifest.json.erb +22 -0
- data/examples/rails_test_app/testapp/app/views/pwa/service-worker.js +26 -0
- data/examples/rails_test_app/testapp/bin/ci +6 -0
- data/examples/rails_test_app/testapp/bin/dev +2 -0
- data/examples/rails_test_app/testapp/bin/rails +4 -0
- data/examples/rails_test_app/testapp/bin/rake +4 -0
- data/examples/rails_test_app/testapp/bin/setup +35 -0
- data/examples/rails_test_app/testapp/config/application.rb +42 -0
- data/examples/rails_test_app/testapp/config/boot.rb +3 -0
- data/examples/rails_test_app/testapp/config/ci.rb +14 -0
- data/examples/rails_test_app/testapp/config/database.yml +32 -0
- data/examples/rails_test_app/testapp/config/environment.rb +5 -0
- data/examples/rails_test_app/testapp/config/environments/development.rb +54 -0
- data/examples/rails_test_app/testapp/config/environments/production.rb +67 -0
- data/examples/rails_test_app/testapp/config/environments/test.rb +42 -0
- data/examples/rails_test_app/testapp/config/initializers/content_security_policy.rb +29 -0
- data/examples/rails_test_app/testapp/config/initializers/filter_parameter_logging.rb +8 -0
- data/examples/rails_test_app/testapp/config/initializers/inflections.rb +16 -0
- data/examples/rails_test_app/testapp/config/locales/en.yml +31 -0
- data/examples/rails_test_app/testapp/config/puma.rb +39 -0
- data/examples/rails_test_app/testapp/config/routes.rb +34 -0
- data/examples/rails_test_app/testapp/config.ru +6 -0
- data/examples/rails_test_app/testapp/db/migrate/20260216002916_create_users.rb +12 -0
- data/examples/rails_test_app/testapp/db/migrate/20260216002919_create_posts.rb +13 -0
- data/examples/rails_test_app/testapp/db/migrate/20260216002922_create_comments.rb +11 -0
- data/examples/rails_test_app/testapp/db/migrate/20260222000001_create_orders.rb +14 -0
- data/examples/rails_test_app/testapp/db/migrate/20260222000002_create_order_items.rb +13 -0
- data/examples/rails_test_app/testapp/db/schema.rb +71 -0
- data/examples/rails_test_app/testapp/db/seeds.rb +85 -0
- data/examples/rails_test_app/testapp/docker-compose.yml +21 -0
- data/examples/rails_test_app/testapp/docker-entrypoint.sh +10 -0
- data/examples/rails_test_app/testapp/lib/tasks/.keep +0 -0
- data/examples/rails_test_app/testapp/log/.keep +0 -0
- data/examples/rails_test_app/testapp/public/400.html +135 -0
- data/examples/rails_test_app/testapp/public/404.html +135 -0
- data/examples/rails_test_app/testapp/public/406-unsupported-browser.html +135 -0
- data/examples/rails_test_app/testapp/public/422.html +135 -0
- data/examples/rails_test_app/testapp/public/500.html +135 -0
- data/examples/rails_test_app/testapp/public/icon.png +0 -0
- data/examples/rails_test_app/testapp/public/icon.svg +3 -0
- data/examples/rails_test_app/testapp/public/robots.txt +1 -0
- data/examples/rails_test_app/testapp/script/.keep +0 -0
- data/examples/rails_test_app/testapp/storage/.keep +0 -0
- data/examples/rails_test_app/testapp/tmp/.keep +0 -0
- data/examples/rails_test_app/testapp/tmp/pids/.keep +0 -0
- data/examples/rails_test_app/testapp/tmp/storage/.keep +0 -0
- data/examples/rails_test_app/testapp/vendor/.keep +0 -0
- data/exe/debug-mcp +39 -0
- data/exe/debug-rails +127 -0
- data/lib/debug_mcp/client_cleanup.rb +102 -0
- data/lib/debug_mcp/code_safety_analyzer.rb +124 -0
- data/lib/debug_mcp/debug_client.rb +1143 -0
- data/lib/debug_mcp/exit_message_builder.rb +112 -0
- data/lib/debug_mcp/pending_http_helper.rb +25 -0
- data/lib/debug_mcp/rails_helper.rb +155 -0
- data/lib/debug_mcp/server.rb +364 -0
- data/lib/debug_mcp/session_manager.rb +436 -0
- data/lib/debug_mcp/stop_event_annotator.rb +152 -0
- data/lib/debug_mcp/tcp_session_discovery.rb +226 -0
- data/lib/debug_mcp/tools/connect.rb +669 -0
- data/lib/debug_mcp/tools/continue_execution.rb +161 -0
- data/lib/debug_mcp/tools/disconnect.rb +169 -0
- data/lib/debug_mcp/tools/evaluate_code.rb +354 -0
- data/lib/debug_mcp/tools/finish.rb +84 -0
- data/lib/debug_mcp/tools/get_context.rb +217 -0
- data/lib/debug_mcp/tools/get_source.rb +193 -0
- data/lib/debug_mcp/tools/inspect_object.rb +107 -0
- data/lib/debug_mcp/tools/list_debug_sessions.rb +60 -0
- data/lib/debug_mcp/tools/list_files.rb +189 -0
- data/lib/debug_mcp/tools/list_paused_sessions.rb +108 -0
- data/lib/debug_mcp/tools/next.rb +70 -0
- data/lib/debug_mcp/tools/rails_info.rb +200 -0
- data/lib/debug_mcp/tools/rails_model.rb +362 -0
- data/lib/debug_mcp/tools/rails_routes.rb +186 -0
- data/lib/debug_mcp/tools/read_file.rb +214 -0
- data/lib/debug_mcp/tools/remove_breakpoint.rb +173 -0
- data/lib/debug_mcp/tools/run_debug_command.rb +55 -0
- data/lib/debug_mcp/tools/run_script.rb +293 -0
- data/lib/debug_mcp/tools/set_breakpoint.rb +206 -0
- data/lib/debug_mcp/tools/step.rb +67 -0
- data/lib/debug_mcp/tools/trigger_request.rb +515 -0
- data/lib/debug_mcp/version.rb +5 -0
- data/lib/debug_mcp.rb +40 -0
- metadata +251 -0
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
|
|
3
|
+
<html lang="en">
|
|
4
|
+
|
|
5
|
+
<head>
|
|
6
|
+
|
|
7
|
+
<title>We're sorry, but something went wrong (500 Internal Server Error)</title>
|
|
8
|
+
|
|
9
|
+
<meta charset="utf-8">
|
|
10
|
+
<meta name="viewport" content="initial-scale=1, width=device-width">
|
|
11
|
+
<meta name="robots" content="noindex, nofollow">
|
|
12
|
+
|
|
13
|
+
<style>
|
|
14
|
+
|
|
15
|
+
*, *::before, *::after {
|
|
16
|
+
box-sizing: border-box;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
* {
|
|
20
|
+
margin: 0;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
html {
|
|
24
|
+
font-size: 16px;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
body {
|
|
28
|
+
background: #FFF;
|
|
29
|
+
color: #261B23;
|
|
30
|
+
display: grid;
|
|
31
|
+
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, Aptos, Roboto, "Segoe UI", "Helvetica Neue", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
|
32
|
+
font-size: clamp(1rem, 2.5vw, 2rem);
|
|
33
|
+
-webkit-font-smoothing: antialiased;
|
|
34
|
+
font-style: normal;
|
|
35
|
+
font-weight: 400;
|
|
36
|
+
letter-spacing: -0.0025em;
|
|
37
|
+
line-height: 1.4;
|
|
38
|
+
min-height: 100dvh;
|
|
39
|
+
place-items: center;
|
|
40
|
+
text-rendering: optimizeLegibility;
|
|
41
|
+
-webkit-text-size-adjust: 100%;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
#error-description {
|
|
45
|
+
fill: #d30001;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
#error-id {
|
|
49
|
+
fill: #f0eff0;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
@media (prefers-color-scheme: dark) {
|
|
53
|
+
body {
|
|
54
|
+
background: #101010;
|
|
55
|
+
color: #e0e0e0;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
#error-description {
|
|
59
|
+
fill: #FF6161;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
#error-id {
|
|
63
|
+
fill: #2c2c2c;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
a {
|
|
68
|
+
color: inherit;
|
|
69
|
+
font-weight: 700;
|
|
70
|
+
text-decoration: underline;
|
|
71
|
+
text-underline-offset: 0.0925em;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
b, strong {
|
|
75
|
+
font-weight: 700;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
i, em {
|
|
79
|
+
font-style: italic;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
main {
|
|
83
|
+
display: grid;
|
|
84
|
+
gap: 1em;
|
|
85
|
+
padding: 2em;
|
|
86
|
+
place-items: center;
|
|
87
|
+
text-align: center;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
main header {
|
|
91
|
+
width: min(100%, 12em);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
main header svg {
|
|
95
|
+
height: auto;
|
|
96
|
+
max-width: 100%;
|
|
97
|
+
width: 100%;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
main article {
|
|
101
|
+
width: min(100%, 30em);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
main article p {
|
|
105
|
+
font-size: 75%;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
main article br {
|
|
109
|
+
display: none;
|
|
110
|
+
|
|
111
|
+
@media(min-width: 48em) {
|
|
112
|
+
display: inline;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
</style>
|
|
117
|
+
|
|
118
|
+
</head>
|
|
119
|
+
|
|
120
|
+
<body>
|
|
121
|
+
|
|
122
|
+
<!-- This file lives in public/500.html -->
|
|
123
|
+
|
|
124
|
+
<main>
|
|
125
|
+
<header>
|
|
126
|
+
<svg height="172" viewBox="0 0 480 172" width="480" xmlns="http://www.w3.org/2000/svg"><path d="m101.23 93.8427c-8.1103 0-15.4098 3.7849-19.7354 8.3813h-36.2269v-99.21891h103.8143v37.03791h-68.3984v24.8722c5.1366-2.7035 15.1396-5.9477 24.6014-5.9477 35.146 0 56.233 22.7094 56.233 55.4215 0 34.605-23.791 57.315-60.558 57.315-37.8492 0-61.64-22.169-63.8028-55.963h42.9857c1.0814 10.814 9.1919 19.195 21.6281 19.195 11.355 0 19.465-8.381 19.465-20.547 0-11.625-7.299-20.5463-20.006-20.5463zm138.833 77.8613c-40.822 0-64.884-35.146-64.884-85.7015 0-50.5554 24.062-85.700907 64.884-85.700907 40.823 0 64.884 35.145507 64.884 85.700907 0 50.5555-24.061 85.7015-64.884 85.7015zm0-133.2831c-17.572 0-22.709 21.8984-22.709 47.5816 0 25.6835 5.137 47.5815 22.709 47.5815 17.303 0 22.71-21.898 22.71-47.5815 0-25.6832-5.407-47.5816-22.71-47.5816zm140.456 133.2831c-40.823 0-64.884-35.146-64.884-85.7015 0-50.5554 24.061-85.700907 64.884-85.700907 40.822 0 64.884 35.145507 64.884 85.700907 0 50.5555-24.062 85.7015-64.884 85.7015zm0-133.2831c-17.573 0-22.71 21.8984-22.71 47.5816 0 25.6835 5.137 47.5815 22.71 47.5815 17.302 0 22.709-21.898 22.709-47.5815 0-25.6832-5.407-47.5816-22.709-47.5816z" id="error-id"/><path d="m23.1377 68.9967v34.0033h-8.9162v-34.0033zm4.3157 34.0033v-24.921h8.6947v2.1598c1.3845-1.5506 3.8212-2.7136 6.701-2.7136 5.538 0 8.8054 3.5997 8.8054 9.1377v16.3371h-8.6393v-14.2327c0-2.049-1.0522-3.5443-3.2674-3.5443-1.7168 0-3.1567.9969-3.5997 2.7136v15.0634zm29.9913-8.5839v-9.5807h-3.655v-6.7564h3.655v-6.8671h8.5839v6.8671h5.2058v6.7564h-5.2058v8.307c0 1.9383.9415 2.769 2.6583 2.769.9414 0 1.9937-.2216 2.769-.5538v7.3654c-.9969.443-2.8798.775-4.8181.775-5.8703 0-9.1931-2.769-9.1931-9.0819zm32.3666-.1108h8.0301c-.8861 5.7597-5.2057 9.2487-11.6852 9.2487-7.6424 0-12.682-5.2613-12.682-13.0145 0-7.6978 5.3165-13.0143 12.5159-13.0143 7.6424 0 11.9621 5.095 11.9621 12.5159v2.1598h-16.1156c.2769 2.9905 1.8275 4.5965 4.3196 4.5965 1.7722 0 3.1567-.7753 3.6551-2.4921zm-3.8212-10.0237c-2.0491 0-3.4336 1.2737-3.9874 3.5997h7.5317c-.1107-2.0491-1.3845-3.5997-3.5443-3.5997zm31.4299-6.3134v8.3624c-1.052-.5538-2.215-.7753-3.599-.7753-2.382 0-3.988 1.0522-4.431 2.8244v14.6203h-8.694v-24.921h8.694v2.2152c1.219-1.6614 3.157-2.769 5.649-2.769 1.108 0 1.994.2215 2.381.443zm2.949 25.0318v-24.921h8.694v2.1598c1.385-1.5506 3.821-2.7136 6.701-2.7136 5.538 0 8.806 3.5997 8.806 9.1377v16.3371h-8.64v-14.2327c0-2.049-1.052-3.5443-3.267-3.5443-1.717 0-3.157.9969-3.6 2.7136v15.0634zm50.371 0h-8.363v-1.274c-.83.831-3.323 1.717-5.981 1.717-4.929 0-9.082-2.769-9.082-8.0301 0-4.818 4.153-7.9193 9.581-7.9193 2.049 0 4.485.6646 5.482 1.3845v-1.606c0-1.606-.941-2.9905-3.046-2.9905-1.606 0-2.547.7199-2.935 1.8275h-8.196c.72-4.8181 4.984-8.6393 11.408-8.6393 7.089 0 11.132 3.7659 11.132 10.2453zm-8.363-6.9779v-1.4399c-.554-1.0522-2.049-1.7167-3.655-1.7167-1.717 0-3.433.7199-3.433 2.3813 0 1.7168 1.716 2.4367 3.433 2.4367 1.606 0 3.101-.6645 3.655-1.6614zm20.742-29.0191v35.997h-8.694v-35.997zm13.036 25.9178h9.248c.72 2.326 2.714 3.489 5.483 3.489 2.713 0 4.596-1.163 4.596-3.2674 0-1.6061-1.052-2.326-3.212-2.8244l-6.534-1.3845c-4.985-1.1076-8.751-3.7105-8.751-9.47 0-6.6456 5.538-11.0206 13.07-11.0206 8.307 0 13.014 4.5411 13.956 10.4114h-8.695c-.72-1.8829-2.27-3.3228-5.205-3.3228-2.548 0-4.265 1.1076-4.265 2.9905 0 1.4953 1.052 2.326 2.825 2.7137l6.645 1.5506c5.815 1.3845 9.027 4.5412 9.027 9.8023 0 6.9778-5.87 10.9654-13.291 10.9654-8.141 0-13.679-3.9322-14.897-10.6332zm46.509 1.3845h8.031c-.887 5.7597-5.206 9.2487-11.686 9.2487-7.642 0-12.682-5.2613-12.682-13.0145 0-7.6978 5.317-13.0143 12.516-13.0143 7.643 0 11.962 5.095 11.962 12.5159v2.1598h-16.115c.277 2.9905 1.827 4.5965 4.319 4.5965 1.773 0 3.157-.7753 3.655-2.4921zm-3.821-10.0237c-2.049 0-3.433 1.2737-3.987 3.5997h7.532c-.111-2.0491-1.385-3.5997-3.545-3.5997zm31.431-6.3134v8.3624c-1.053-.5538-2.216-.7753-3.6-.7753-2.381 0-3.988 1.0522-4.431 2.8244v14.6203h-8.694v-24.921h8.694v2.2152c1.219-1.6614 3.157-2.769 5.649-2.769 1.108 0 1.994.2215 2.382.443zm18.288 25.0318h-7.809l-9.47-24.921h8.861l4.763 14.288 4.652-14.288h8.528zm25.614-8.6947h8.03c-.886 5.7597-5.206 9.2487-11.685 9.2487-7.642 0-12.682-5.2613-12.682-13.0145 0-7.6978 5.316-13.0143 12.516-13.0143 7.642 0 11.962 5.095 11.962 12.5159v2.1598h-16.116c.277 2.9905 1.828 4.5965 4.32 4.5965 1.772 0 3.157-.7753 3.655-2.4921zm-3.821-10.0237c-2.049 0-3.434 1.2737-3.988 3.5997h7.532c-.111-2.0491-1.384-3.5997-3.544-3.5997zm31.43-6.3134v8.3624c-1.052-.5538-2.215-.7753-3.6-.7753-2.381 0-3.987 1.0522-4.43 2.8244v14.6203h-8.695v-24.921h8.695v2.2152c1.218-1.6614 3.157-2.769 5.649-2.769 1.107 0 1.993.2215 2.381.443zm13.703-8.9715h24.312v7.6424h-15.562v5.3165h14.232v7.4763h-14.232v5.8703h15.562v7.6978h-24.312zm44.667 8.9715v8.3624c-1.052-.5538-2.215-.7753-3.6-.7753-2.381 0-3.987 1.0522-4.43 2.8244v14.6203h-8.695v-24.921h8.695v2.2152c1.218-1.6614 3.156-2.769 5.648-2.769 1.108 0 1.994.2215 2.382.443zm19.673 0v8.3624c-1.053-.5538-2.216-.7753-3.6-.7753-2.381 0-3.987 1.0522-4.43 2.8244v14.6203h-8.695v-24.921h8.695v2.2152c1.218-1.6614 3.156-2.769 5.648-2.769 1.108 0 1.994.2215 2.382.443zm26.769 12.5713c0 7.6978-5.15 13.0145-12.737 13.0145-7.532 0-12.738-5.3167-12.738-13.0145s5.206-13.0143 12.738-13.0143c7.587 0 12.737 5.3165 12.737 13.0143zm-8.529 0c0-3.4336-1.495-5.8703-4.208-5.8703-2.659 0-4.154 2.4367-4.154 5.8703s1.495 5.8149 4.154 5.8149c2.713 0 4.208-2.3813 4.208-5.8149zm28.082-12.5713v8.3624c-1.052-.5538-2.215-.7753-3.6-.7753-2.381 0-3.987 1.0522-4.43 2.8244v14.6203h-8.695v-24.921h8.695v2.2152c1.218-1.6614 3.157-2.769 5.649-2.769 1.107 0 1.993.2215 2.381.443z" id="error-description"/></svg>
|
|
127
|
+
</header>
|
|
128
|
+
<article>
|
|
129
|
+
<p><strong>We're sorry, but something went wrong.</strong><br> If you're the application owner check the logs for more information.</p>
|
|
130
|
+
</article>
|
|
131
|
+
</main>
|
|
132
|
+
|
|
133
|
+
</body>
|
|
134
|
+
|
|
135
|
+
</html>
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# See https://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
data/exe/debug-mcp
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "debug_mcp"
|
|
5
|
+
require "optparse"
|
|
6
|
+
|
|
7
|
+
options = {}
|
|
8
|
+
|
|
9
|
+
OptionParser.new do |opts|
|
|
10
|
+
opts.banner = "Usage: debug-mcp [options]"
|
|
11
|
+
|
|
12
|
+
opts.on("-t", "--transport TRANSPORT", %w[stdio http], "Transport type: stdio (default) or http") do |t|
|
|
13
|
+
options[:transport] = t
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
opts.on("-p", "--port PORT", Integer, "HTTP port (default: 6029, only for http transport)") do |p|
|
|
17
|
+
options[:port] = p
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
opts.on("--host HOST", "HTTP host (default: 127.0.0.1, only for http transport)") do |h|
|
|
21
|
+
options[:host] = h
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
opts.on("--session-timeout SECONDS", Integer, "Session timeout in seconds (default: 1800)") do |t|
|
|
25
|
+
options[:session_timeout] = t
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
opts.on("-v", "--version", "Show version") do
|
|
29
|
+
puts "debug-mcp #{DebugMcp::VERSION}"
|
|
30
|
+
exit
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
opts.on("-h", "--help", "Show this help") do
|
|
34
|
+
puts opts
|
|
35
|
+
exit
|
|
36
|
+
end
|
|
37
|
+
end.parse!
|
|
38
|
+
|
|
39
|
+
DebugMcp::Server.new(**options).start
|
data/exe/debug-rails
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
class DebugRails
|
|
5
|
+
DEFAULT_DEBUG_PORT = 12321
|
|
6
|
+
|
|
7
|
+
def self.parse_args(argv)
|
|
8
|
+
debug_port = nil
|
|
9
|
+
show_help = false
|
|
10
|
+
|
|
11
|
+
i = 0
|
|
12
|
+
while i < argv.length
|
|
13
|
+
case argv[i]
|
|
14
|
+
when "--debug-port"
|
|
15
|
+
i += 1
|
|
16
|
+
raise ArgumentError, "--debug-port requires a port number" if i >= argv.length
|
|
17
|
+
debug_port = parse_port(argv[i])
|
|
18
|
+
i += 1
|
|
19
|
+
when /\A--debug-port=(.+)\z/
|
|
20
|
+
debug_port = parse_port($1)
|
|
21
|
+
i += 1
|
|
22
|
+
when "-h", "--help"
|
|
23
|
+
show_help = true
|
|
24
|
+
i += 1
|
|
25
|
+
else
|
|
26
|
+
break
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
rails_args = argv[i..] || []
|
|
31
|
+
|
|
32
|
+
if rails_args.empty? || rails_args.first.start_with?("-")
|
|
33
|
+
rails_args.unshift("server")
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
{ debug_port: debug_port, rails_args: rails_args, show_help: show_help }
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def self.parse_port(value)
|
|
40
|
+
port = Integer(value)
|
|
41
|
+
raise ArgumentError unless port.positive?
|
|
42
|
+
port
|
|
43
|
+
rescue ArgumentError, TypeError
|
|
44
|
+
raise ArgumentError, "--debug-port requires a valid port number, got: #{value.inspect}"
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def self.build_env(debug_port:, docker:)
|
|
48
|
+
if docker
|
|
49
|
+
{
|
|
50
|
+
"RUBY_DEBUG_HOST" => "0.0.0.0",
|
|
51
|
+
"RUBY_DEBUG_PORT" => (debug_port || DEFAULT_DEBUG_PORT).to_s,
|
|
52
|
+
}
|
|
53
|
+
else
|
|
54
|
+
env = { "RUBY_DEBUG_OPEN" => "true" }
|
|
55
|
+
env["RUBY_DEBUG_PORT"] = debug_port.to_s if debug_port
|
|
56
|
+
env
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def self.docker?
|
|
61
|
+
File.exist?("/.dockerenv")
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def self.print_help(io = $stdout)
|
|
65
|
+
io.puts <<~HELP
|
|
66
|
+
Usage: debug-rails [options] [rails-command] [rails-options]
|
|
67
|
+
|
|
68
|
+
Launch a Rails server with Ruby debug gem enabled for debug-mcp debugging.
|
|
69
|
+
|
|
70
|
+
Options:
|
|
71
|
+
--debug-port PORT Debug port for TCP connection (default: #{DEFAULT_DEBUG_PORT} in Docker)
|
|
72
|
+
-h, --help Show this help
|
|
73
|
+
|
|
74
|
+
Examples:
|
|
75
|
+
debug-rails # rails server with debug enabled
|
|
76
|
+
debug-rails s -p 4000 # rails server on port 4000
|
|
77
|
+
debug-rails --debug-port 3333 # use TCP debug port 3333
|
|
78
|
+
debug-rails console # rails console with debug enabled
|
|
79
|
+
|
|
80
|
+
Environment:
|
|
81
|
+
Outside Docker: Sets RUBY_DEBUG_OPEN=true
|
|
82
|
+
Inside Docker: Sets RUBY_DEBUG_HOST=0.0.0.0, RUBY_DEBUG_PORT=PORT
|
|
83
|
+
HELP
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def self.run(argv = ARGV)
|
|
87
|
+
parsed = parse_args(argv)
|
|
88
|
+
|
|
89
|
+
if parsed[:show_help]
|
|
90
|
+
print_help
|
|
91
|
+
exit 0
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
unless File.exist?("bin/rails")
|
|
95
|
+
$stderr.puts "Error: bin/rails not found in current directory."
|
|
96
|
+
$stderr.puts "Run this command from your Rails application root."
|
|
97
|
+
exit 1
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
env = build_env(debug_port: parsed[:debug_port], docker: docker?)
|
|
101
|
+
pid = spawn(env, "bin/rails", *parsed[:rails_args])
|
|
102
|
+
|
|
103
|
+
force_quit_deadline = nil
|
|
104
|
+
|
|
105
|
+
Signal.trap("INT") do
|
|
106
|
+
if force_quit_deadline && Time.now < force_quit_deadline
|
|
107
|
+
Process.kill("KILL", pid)
|
|
108
|
+
else
|
|
109
|
+
Process.kill("TERM", pid)
|
|
110
|
+
$stderr.write "\nStopping... Press Ctrl+C again within 3s to force quit\n"
|
|
111
|
+
force_quit_deadline = Time.now + 3
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
Signal.trap("TERM") do
|
|
116
|
+
Process.kill("TERM", pid)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
_, status = Process.waitpid2(pid)
|
|
120
|
+
exit(status.exitstatus || 1)
|
|
121
|
+
rescue ArgumentError => e
|
|
122
|
+
$stderr.puts "Error: #{e.message}"
|
|
123
|
+
exit 1
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
DebugRails.run unless defined?(RSpec)
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DebugMcp
|
|
4
|
+
# Shared cleanup logic for graceful disconnect.
|
|
5
|
+
# Used by Disconnect tool and SessionManager reaper to avoid ~100 lines of duplication.
|
|
6
|
+
# Performs: stdout restore, breakpoint deletion, SIGINT handler restore,
|
|
7
|
+
# process resume, and stale pause defense.
|
|
8
|
+
module ClientCleanup
|
|
9
|
+
# Perform best-effort cleanup and resume a paused debug client.
|
|
10
|
+
# The client MUST be paused before calling this method.
|
|
11
|
+
# @param client [DebugClient] the client to clean up
|
|
12
|
+
# @param deadline [Time] hard deadline for all cleanup operations
|
|
13
|
+
# @param max_stale_retries [Integer] max retries for stale pause defense
|
|
14
|
+
def self.cleanup_and_resume(client, deadline:, max_stale_retries: 2)
|
|
15
|
+
# Restore $stdout if evaluate_code left it redirected (its ensure block
|
|
16
|
+
# fails when send_command timeout sets @paused=false).
|
|
17
|
+
remaining = deadline - Time.now
|
|
18
|
+
if remaining > 0
|
|
19
|
+
begin
|
|
20
|
+
client.send_command(
|
|
21
|
+
'$stdout = STDOUT if $stdout != STDOUT',
|
|
22
|
+
timeout: [remaining, 1].min,
|
|
23
|
+
)
|
|
24
|
+
rescue DebugMcp::Error
|
|
25
|
+
# Best-effort
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Delete all breakpoints FIRST — this is the most critical step.
|
|
30
|
+
# If breakpoints remain and the process resumes without a client,
|
|
31
|
+
# it will hit a breakpoint and become stuck with no way to continue.
|
|
32
|
+
delete_all_breakpoints(client, deadline)
|
|
33
|
+
|
|
34
|
+
# Restore original SIGINT handler
|
|
35
|
+
remaining = deadline - Time.now
|
|
36
|
+
if remaining > 0
|
|
37
|
+
begin
|
|
38
|
+
client.send_command(
|
|
39
|
+
"p $_debug_mcp_orig_int ? (trap('INT',$_debug_mcp_orig_int);$_debug_mcp_orig_int=nil;:ok) : nil",
|
|
40
|
+
timeout: [remaining, 2].min,
|
|
41
|
+
)
|
|
42
|
+
rescue DebugMcp::Error
|
|
43
|
+
# Best-effort
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Resume the process. If a cleanup command timed out (setting
|
|
48
|
+
# @paused=false even though the process is actually still paused),
|
|
49
|
+
# use force: true to bypass the @paused check.
|
|
50
|
+
client.send_command_no_wait("c", force: true)
|
|
51
|
+
|
|
52
|
+
# Wait for the debug gem to settle after 'c'. After SIGINT recovery,
|
|
53
|
+
# the main thread needs to finish the interrupted eval and re-enter
|
|
54
|
+
# the command loop (sending `input PID`). If we close the socket
|
|
55
|
+
# before this completes, the debug gem's cleanup_reader closes @q_msg
|
|
56
|
+
# while the main thread is still pushing results, leaving it stuck
|
|
57
|
+
# on a futex. Draining here gives the debug gem time to settle.
|
|
58
|
+
client.ensure_paused(timeout: 2)
|
|
59
|
+
|
|
60
|
+
# Stale pause defense: after 'c' → ensure_paused, the process might
|
|
61
|
+
# have been re-paused by a stale `pause` message left in the debug
|
|
62
|
+
# gem's socket buffer. If still paused, delete remaining BPs and
|
|
63
|
+
# send 'c' again (bounded retries to prevent infinite loop).
|
|
64
|
+
stale_retries = 0
|
|
65
|
+
while client.paused && stale_retries < max_stale_retries
|
|
66
|
+
stale_retries += 1
|
|
67
|
+
remaining = deadline - Time.now
|
|
68
|
+
break if remaining <= 0
|
|
69
|
+
|
|
70
|
+
delete_all_breakpoints(client, deadline)
|
|
71
|
+
|
|
72
|
+
remaining = deadline - Time.now
|
|
73
|
+
break if remaining <= 0
|
|
74
|
+
|
|
75
|
+
client.send_command_no_wait("c", force: true)
|
|
76
|
+
client.ensure_paused(timeout: [remaining, 1].min)
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Delete all breakpoints from the debug session.
|
|
81
|
+
# @param client [DebugClient] the client
|
|
82
|
+
# @param deadline [Time] hard deadline
|
|
83
|
+
def self.delete_all_breakpoints(client, deadline)
|
|
84
|
+
remaining = deadline - Time.now
|
|
85
|
+
return if remaining <= 0
|
|
86
|
+
|
|
87
|
+
bp_output = client.send_command("info breakpoints", timeout: [remaining, 2].min)
|
|
88
|
+
return if bp_output.strip.empty?
|
|
89
|
+
|
|
90
|
+
bp_output.each_line do |line|
|
|
91
|
+
remaining = deadline - Time.now
|
|
92
|
+
break if remaining <= 0
|
|
93
|
+
|
|
94
|
+
if (match = line.match(/#(\d+)/))
|
|
95
|
+
client.send_command("delete #{match[1]}", timeout: [remaining, 2].min) rescue nil
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
rescue DebugMcp::Error
|
|
99
|
+
# Best-effort
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DebugMcp
|
|
4
|
+
module CodeSafetyAnalyzer
|
|
5
|
+
DANGEROUS_PATTERNS = {
|
|
6
|
+
file_operations: [
|
|
7
|
+
[/\bFile\s*\.\s*(write|delete|unlink|rename|chmod|chown)\b/, "File.write/delete/unlink/rename"],
|
|
8
|
+
[/\bFileUtils\b/, "FileUtils"],
|
|
9
|
+
[/\bIO\s*\.\s*(write|binwrite)\b/, "IO.write"],
|
|
10
|
+
[/\bDir\s*\.\s*(mkdir|rmdir|delete|unlink)\b/, "Dir.mkdir/rmdir"],
|
|
11
|
+
],
|
|
12
|
+
system_commands: [
|
|
13
|
+
[/\bsystem\s*\(/, "system()"],
|
|
14
|
+
[/\bexec\s*\(/, "exec()"],
|
|
15
|
+
[/\bspawn\s*\(/, "spawn()"],
|
|
16
|
+
[/`[^`]+`/, "backtick command"],
|
|
17
|
+
[/%x\{/, "%x{}"],
|
|
18
|
+
[/%x\[/, "%x[]"],
|
|
19
|
+
[/%x\(/, "%x()"],
|
|
20
|
+
[/\bOpen3\b/, "Open3"],
|
|
21
|
+
[/\bIO\s*\.\s*popen\b/, "IO.popen"],
|
|
22
|
+
],
|
|
23
|
+
process_manipulation: [
|
|
24
|
+
[/\bProcess\s*\.\s*(kill|fork|exit)\b/, "Process.kill/fork/exit"],
|
|
25
|
+
[/\bfork\s*[\s({]/, "fork"],
|
|
26
|
+
[/\bexit!/, "exit!"],
|
|
27
|
+
[/\babort\b/, "abort"],
|
|
28
|
+
],
|
|
29
|
+
network_operations: [
|
|
30
|
+
[/\bNet::HTTP\b/, "Net::HTTP"],
|
|
31
|
+
[/\bTCPSocket\b/, "TCPSocket"],
|
|
32
|
+
[/\bUDPSocket\b/, "UDPSocket"],
|
|
33
|
+
[/\bFaraday\b/, "Faraday"],
|
|
34
|
+
[/\bHTTParty\b/, "HTTParty"],
|
|
35
|
+
[/\bopen-uri\b/, "open-uri"],
|
|
36
|
+
[/\bURI\s*\.\s*open\b/, "URI.open"],
|
|
37
|
+
[/\bRestClient\b/, "RestClient"],
|
|
38
|
+
],
|
|
39
|
+
destructive_data: [
|
|
40
|
+
[/\.destroy_all\b/, ".destroy_all"],
|
|
41
|
+
[/\.delete_all\b/, ".delete_all"],
|
|
42
|
+
[/\.update_all\b/, ".update_all"],
|
|
43
|
+
[/\b(DROP|TRUNCATE)\s+(TABLE|DATABASE)\b/i, "DROP/TRUNCATE SQL"],
|
|
44
|
+
],
|
|
45
|
+
mutation_operations: [
|
|
46
|
+
[/\.save!/, ".save!"],
|
|
47
|
+
[/\.save\b(?![!?])/, ".save"],
|
|
48
|
+
[/\.update![\s(]/, ".update!"],
|
|
49
|
+
[/\.update[\s(]/, ".update"],
|
|
50
|
+
[/\.create![\s(]/, ".create!"],
|
|
51
|
+
[/\.create[\s(]/, ".create"],
|
|
52
|
+
[/\.destroy!/, ".destroy!"],
|
|
53
|
+
[/\.destroy\b(?![_!])/, ".destroy"],
|
|
54
|
+
[/\.touch\b/, ".touch"],
|
|
55
|
+
[/\.increment!/, ".increment!"],
|
|
56
|
+
[/\.decrement!/, ".decrement!"],
|
|
57
|
+
[/\.toggle!/, ".toggle!"],
|
|
58
|
+
],
|
|
59
|
+
}.freeze
|
|
60
|
+
|
|
61
|
+
# Filter out warnings whose categories have been acknowledged.
|
|
62
|
+
def self.filter_acknowledged(warnings, acknowledged_categories)
|
|
63
|
+
return warnings if acknowledged_categories.nil? || acknowledged_categories.empty?
|
|
64
|
+
|
|
65
|
+
warnings.reject { |w| acknowledged_categories.include?(w[:category]) }
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Analyze code for dangerous patterns.
|
|
69
|
+
# Returns an array of warnings: [{ category:, label:, matches: }]
|
|
70
|
+
def self.analyze(code)
|
|
71
|
+
warnings = []
|
|
72
|
+
|
|
73
|
+
DANGEROUS_PATTERNS.each do |category, patterns|
|
|
74
|
+
matches = []
|
|
75
|
+
patterns.each do |regexp, label|
|
|
76
|
+
matches << label if code.match?(regexp)
|
|
77
|
+
end
|
|
78
|
+
next if matches.empty?
|
|
79
|
+
|
|
80
|
+
warnings << { category: category, matches: matches }
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
warnings
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
CATEGORY_LABELS = {
|
|
87
|
+
file_operations: "File system operations",
|
|
88
|
+
system_commands: "System command execution",
|
|
89
|
+
process_manipulation: "Process manipulation",
|
|
90
|
+
network_operations: "Network operations",
|
|
91
|
+
destructive_data: "Destructive data operations",
|
|
92
|
+
mutation_operations: "Data mutation (modifies database records)",
|
|
93
|
+
}.freeze
|
|
94
|
+
|
|
95
|
+
# Format warnings into human-readable text.
|
|
96
|
+
# Returns nil if no warnings.
|
|
97
|
+
# Uses a compact "Note:" format when only mutation_operations are present,
|
|
98
|
+
# and a verbose "WARNING:" format when other categories are involved.
|
|
99
|
+
def self.format_warnings(warnings)
|
|
100
|
+
return nil if warnings.empty?
|
|
101
|
+
|
|
102
|
+
# Compact format for mutation-only warnings
|
|
103
|
+
if warnings.all? { |w| w[:category] == :mutation_operations }
|
|
104
|
+
matches = warnings.flat_map { |w| w[:matches] }
|
|
105
|
+
return "Note: Data mutation detected (#{matches.join(", ")}). " \
|
|
106
|
+
"Use acknowledge_mutations to suppress this notice."
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Verbose format for dangerous operations
|
|
110
|
+
lines = []
|
|
111
|
+
lines << "WARNING: Potentially dangerous operations detected in code."
|
|
112
|
+
lines << "evaluate_code should only be used for investigating runtime state."
|
|
113
|
+
lines << "Use the agent's own tools for file/system/network operations."
|
|
114
|
+
lines << ""
|
|
115
|
+
|
|
116
|
+
warnings.each do |w|
|
|
117
|
+
label = CATEGORY_LABELS[w[:category]] || w[:category].to_s
|
|
118
|
+
lines << " #{label}: #{w[:matches].join(", ")}"
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
lines.join("\n")
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|