trak_flow 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. checksums.yaml +7 -0
  2. data/.envrc +3 -0
  3. data/CHANGELOG.md +69 -0
  4. data/COMMITS.md +196 -0
  5. data/Gemfile +8 -0
  6. data/Gemfile.lock +281 -0
  7. data/README.md +479 -0
  8. data/Rakefile +16 -0
  9. data/bin/tf +6 -0
  10. data/bin/tf_mcp +81 -0
  11. data/docs/.keep +0 -0
  12. data/docs/api/database.md +434 -0
  13. data/docs/api/ruby-library.md +349 -0
  14. data/docs/api/task-model.md +341 -0
  15. data/docs/assets/stylesheets/extra.css +53 -0
  16. data/docs/assets/trak_flow.jpg +0 -0
  17. data/docs/cli/admin-commands.md +369 -0
  18. data/docs/cli/dependency-commands.md +321 -0
  19. data/docs/cli/label-commands.md +222 -0
  20. data/docs/cli/overview.md +163 -0
  21. data/docs/cli/plan-commands.md +344 -0
  22. data/docs/cli/task-commands.md +333 -0
  23. data/docs/core-concepts/dependencies.md +232 -0
  24. data/docs/core-concepts/labels.md +217 -0
  25. data/docs/core-concepts/overview.md +178 -0
  26. data/docs/core-concepts/plans-workflows.md +264 -0
  27. data/docs/core-concepts/tasks.md +205 -0
  28. data/docs/getting-started/configuration.md +120 -0
  29. data/docs/getting-started/installation.md +79 -0
  30. data/docs/getting-started/quick-start.md +245 -0
  31. data/docs/index.md +169 -0
  32. data/docs/mcp/integration.md +302 -0
  33. data/docs/mcp/overview.md +206 -0
  34. data/docs/mcp/resources.md +284 -0
  35. data/docs/mcp/tools.md +457 -0
  36. data/examples/basic_usage.rb +365 -0
  37. data/examples/cli_demo.sh +314 -0
  38. data/examples/mcp/Gemfile +9 -0
  39. data/examples/mcp/Gemfile.lock +226 -0
  40. data/examples/mcp/http_demo.rb +232 -0
  41. data/examples/mcp/stdio_demo.rb +146 -0
  42. data/lib/trak_flow/cli/admin_commands.rb +136 -0
  43. data/lib/trak_flow/cli/config_commands.rb +260 -0
  44. data/lib/trak_flow/cli/dep_commands.rb +71 -0
  45. data/lib/trak_flow/cli/label_commands.rb +76 -0
  46. data/lib/trak_flow/cli/main_commands.rb +386 -0
  47. data/lib/trak_flow/cli/plan_commands.rb +185 -0
  48. data/lib/trak_flow/cli/workflow_commands.rb +133 -0
  49. data/lib/trak_flow/cli.rb +110 -0
  50. data/lib/trak_flow/config/defaults.yml +114 -0
  51. data/lib/trak_flow/config/section.rb +74 -0
  52. data/lib/trak_flow/config.rb +276 -0
  53. data/lib/trak_flow/graph/dependency_graph.rb +288 -0
  54. data/lib/trak_flow/id_generator.rb +52 -0
  55. data/lib/trak_flow/mcp/resources/base_resource.rb +25 -0
  56. data/lib/trak_flow/mcp/resources/dependency_graph.rb +31 -0
  57. data/lib/trak_flow/mcp/resources/label_list.rb +21 -0
  58. data/lib/trak_flow/mcp/resources/plan_by_id.rb +27 -0
  59. data/lib/trak_flow/mcp/resources/plan_list.rb +21 -0
  60. data/lib/trak_flow/mcp/resources/task_by_id.rb +31 -0
  61. data/lib/trak_flow/mcp/resources/task_list.rb +21 -0
  62. data/lib/trak_flow/mcp/resources/task_next.rb +30 -0
  63. data/lib/trak_flow/mcp/resources/workflow_by_id.rb +27 -0
  64. data/lib/trak_flow/mcp/resources/workflow_list.rb +21 -0
  65. data/lib/trak_flow/mcp/server.rb +140 -0
  66. data/lib/trak_flow/mcp/tools/base_tool.rb +29 -0
  67. data/lib/trak_flow/mcp/tools/comment_add.rb +33 -0
  68. data/lib/trak_flow/mcp/tools/dep_add.rb +34 -0
  69. data/lib/trak_flow/mcp/tools/dep_remove.rb +25 -0
  70. data/lib/trak_flow/mcp/tools/label_add.rb +28 -0
  71. data/lib/trak_flow/mcp/tools/label_remove.rb +25 -0
  72. data/lib/trak_flow/mcp/tools/plan_add_step.rb +35 -0
  73. data/lib/trak_flow/mcp/tools/plan_create.rb +33 -0
  74. data/lib/trak_flow/mcp/tools/plan_run.rb +58 -0
  75. data/lib/trak_flow/mcp/tools/plan_start.rb +58 -0
  76. data/lib/trak_flow/mcp/tools/task_block.rb +27 -0
  77. data/lib/trak_flow/mcp/tools/task_close.rb +26 -0
  78. data/lib/trak_flow/mcp/tools/task_create.rb +51 -0
  79. data/lib/trak_flow/mcp/tools/task_defer.rb +27 -0
  80. data/lib/trak_flow/mcp/tools/task_start.rb +25 -0
  81. data/lib/trak_flow/mcp/tools/task_update.rb +36 -0
  82. data/lib/trak_flow/mcp/tools/workflow_discard.rb +28 -0
  83. data/lib/trak_flow/mcp/tools/workflow_summarize.rb +34 -0
  84. data/lib/trak_flow/mcp.rb +38 -0
  85. data/lib/trak_flow/models/comment.rb +71 -0
  86. data/lib/trak_flow/models/dependency.rb +96 -0
  87. data/lib/trak_flow/models/label.rb +90 -0
  88. data/lib/trak_flow/models/task.rb +188 -0
  89. data/lib/trak_flow/storage/database.rb +638 -0
  90. data/lib/trak_flow/storage/jsonl.rb +259 -0
  91. data/lib/trak_flow/time_parser.rb +15 -0
  92. data/lib/trak_flow/version.rb +5 -0
  93. data/lib/trak_flow.rb +100 -0
  94. data/mkdocs.yml +143 -0
  95. metadata +392 -0
@@ -0,0 +1,226 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ trak_flow (0.1.0)
5
+ anyway_config (~> 2.0)
6
+ debug_me
7
+ fast-mcp
8
+ oj (~> 3.16)
9
+ pastel (~> 0.8)
10
+ puma (~> 6.0)
11
+ rackup (~> 2.0)
12
+ sequel (~> 5.0)
13
+ sqlite3 (~> 2.0)
14
+ thor (~> 1.3)
15
+ tty-spinner (~> 0.9)
16
+ tty-table (~> 0.12)
17
+
18
+ GEM
19
+ remote: https://rubygems.org/
20
+ specs:
21
+ addressable (2.8.8)
22
+ public_suffix (>= 2.0.2, < 8.0)
23
+ ansi (1.5.0)
24
+ anyway_config (2.7.2)
25
+ ruby-next-core (~> 1.0)
26
+ ast (2.4.3)
27
+ base64 (0.3.0)
28
+ bigdecimal (3.3.1)
29
+ builder (3.3.0)
30
+ concurrent-ruby (1.3.6)
31
+ debug_me (1.1.3)
32
+ docile (1.4.1)
33
+ dry-configurable (1.3.0)
34
+ dry-core (~> 1.1)
35
+ zeitwerk (~> 2.6)
36
+ dry-core (1.2.0)
37
+ concurrent-ruby (~> 1.0)
38
+ logger
39
+ zeitwerk (~> 2.6)
40
+ dry-inflector (1.2.0)
41
+ dry-initializer (3.2.0)
42
+ dry-logic (1.6.0)
43
+ bigdecimal
44
+ concurrent-ruby (~> 1.0)
45
+ dry-core (~> 1.1)
46
+ zeitwerk (~> 2.6)
47
+ dry-schema (1.14.1)
48
+ concurrent-ruby (~> 1.0)
49
+ dry-configurable (~> 1.0, >= 1.0.1)
50
+ dry-core (~> 1.1)
51
+ dry-initializer (~> 3.2)
52
+ dry-logic (~> 1.5)
53
+ dry-types (~> 1.8)
54
+ zeitwerk (~> 2.6)
55
+ dry-types (1.8.3)
56
+ bigdecimal (~> 3.0)
57
+ concurrent-ruby (~> 1.0)
58
+ dry-core (~> 1.0)
59
+ dry-inflector (~> 1.0)
60
+ dry-logic (~> 1.4)
61
+ zeitwerk (~> 2.6)
62
+ fast-mcp (1.6.0)
63
+ addressable (~> 2.8)
64
+ base64
65
+ dry-schema (~> 1.14)
66
+ json (~> 2.0)
67
+ mime-types (~> 3.4)
68
+ rack (>= 2.0, < 4.0)
69
+ json (2.18.0)
70
+ language_server-protocol (3.17.0.5)
71
+ lint_roller (1.1.0)
72
+ logger (1.7.0)
73
+ mime-types (3.7.0)
74
+ logger
75
+ mime-types-data (~> 3.2025, >= 3.2025.0507)
76
+ mime-types-data (3.2025.0924)
77
+ minitest (5.27.0)
78
+ minitest-reporters (1.7.1)
79
+ ansi
80
+ builder
81
+ minitest (>= 5.0)
82
+ ruby-progressbar
83
+ nio4r (2.7.5)
84
+ oj (3.16.13)
85
+ bigdecimal (>= 3.0)
86
+ ostruct (>= 0.2)
87
+ ostruct (0.6.3)
88
+ parallel (1.27.0)
89
+ parser (3.3.10.0)
90
+ ast (~> 2.4.1)
91
+ racc
92
+ pastel (0.8.0)
93
+ tty-color (~> 0.5)
94
+ prism (1.7.0)
95
+ public_suffix (7.0.0)
96
+ puma (6.6.1)
97
+ nio4r (~> 2.0)
98
+ racc (1.8.1)
99
+ rack (3.2.4)
100
+ rackup (2.3.1)
101
+ rack (>= 3)
102
+ rainbow (3.1.1)
103
+ rake (13.3.1)
104
+ regexp_parser (2.11.3)
105
+ rubocop (1.82.1)
106
+ json (~> 2.3)
107
+ language_server-protocol (~> 3.17.0.2)
108
+ lint_roller (~> 1.1.0)
109
+ parallel (~> 1.10)
110
+ parser (>= 3.3.0.2)
111
+ rainbow (>= 2.2.2, < 4.0)
112
+ regexp_parser (>= 2.9.3, < 3.0)
113
+ rubocop-ast (>= 1.48.0, < 2.0)
114
+ ruby-progressbar (~> 1.7)
115
+ unicode-display_width (>= 2.4.0, < 4.0)
116
+ rubocop-ast (1.49.0)
117
+ parser (>= 3.3.7.2)
118
+ prism (~> 1.7)
119
+ ruby-next-core (1.1.2)
120
+ ruby-progressbar (1.13.0)
121
+ sequel (5.100.0)
122
+ bigdecimal
123
+ simplecov (0.22.0)
124
+ docile (~> 1.1)
125
+ simplecov-html (~> 0.11)
126
+ simplecov_json_formatter (~> 0.1)
127
+ simplecov-html (0.13.2)
128
+ simplecov_json_formatter (0.1.4)
129
+ sqlite3 (2.9.0-arm64-darwin)
130
+ strings (0.2.1)
131
+ strings-ansi (~> 0.2)
132
+ unicode-display_width (>= 1.5, < 3.0)
133
+ unicode_utils (~> 1.4)
134
+ strings-ansi (0.2.0)
135
+ thor (1.4.0)
136
+ tty-color (0.6.0)
137
+ tty-cursor (0.7.1)
138
+ tty-screen (0.8.2)
139
+ tty-spinner (0.9.3)
140
+ tty-cursor (~> 0.7)
141
+ tty-table (0.12.0)
142
+ pastel (~> 0.8)
143
+ strings (~> 0.2.0)
144
+ tty-screen (~> 0.8)
145
+ unicode-display_width (2.6.0)
146
+ unicode_utils (1.4.0)
147
+ zeitwerk (2.7.4)
148
+
149
+ PLATFORMS
150
+ arm64-darwin-25
151
+
152
+ DEPENDENCIES
153
+ bundler
154
+ minitest (~> 5.0)
155
+ minitest-reporters (~> 1.6)
156
+ rake (~> 13.0)
157
+ rubocop (~> 1.0)
158
+ simplecov (~> 0.22)
159
+ trak_flow!
160
+
161
+ CHECKSUMS
162
+ addressable (2.8.8) sha256=7c13b8f9536cf6364c03b9d417c19986019e28f7c00ac8132da4eb0fe393b057
163
+ ansi (1.5.0)
164
+ anyway_config (2.7.2)
165
+ ast (2.4.3)
166
+ base64 (0.3.0) sha256=27337aeabad6ffae05c265c450490628ef3ebd4b67be58257393227588f5a97b
167
+ bigdecimal (3.3.1) sha256=eaa01e228be54c4f9f53bf3cc34fe3d5e845c31963e7fcc5bedb05a4e7d52218
168
+ builder (3.3.0)
169
+ concurrent-ruby (1.3.6)
170
+ debug_me (1.1.3)
171
+ docile (1.4.1)
172
+ dry-configurable (1.3.0)
173
+ dry-core (1.2.0)
174
+ dry-inflector (1.2.0)
175
+ dry-initializer (3.2.0)
176
+ dry-logic (1.6.0)
177
+ dry-schema (1.14.1)
178
+ dry-types (1.8.3)
179
+ fast-mcp (1.6.0)
180
+ json (2.18.0) sha256=b10506aee4183f5cf49e0efc48073d7b75843ce3782c68dbeb763351c08fd505
181
+ language_server-protocol (3.17.0.5)
182
+ lint_roller (1.1.0)
183
+ logger (1.7.0) sha256=196edec7cc44b66cfb40f9755ce11b392f21f7967696af15d274dde7edff0203
184
+ mime-types (3.7.0)
185
+ mime-types-data (3.2025.0924)
186
+ minitest (5.27.0)
187
+ minitest-reporters (1.7.1)
188
+ nio4r (2.7.5)
189
+ oj (3.16.13)
190
+ ostruct (0.6.3)
191
+ parallel (1.27.0)
192
+ parser (3.3.10.0)
193
+ pastel (0.8.0)
194
+ prism (1.7.0)
195
+ public_suffix (7.0.0) sha256=f7090b5beb0e56f9f10d79eed4d5fbe551b3b425da65877e075dad47a6a1b095
196
+ puma (6.6.1)
197
+ racc (1.8.1)
198
+ rack (3.2.4)
199
+ rackup (2.3.1)
200
+ rainbow (3.1.1)
201
+ rake (13.3.1)
202
+ regexp_parser (2.11.3)
203
+ rubocop (1.82.1)
204
+ rubocop-ast (1.49.0)
205
+ ruby-next-core (1.1.2)
206
+ ruby-progressbar (1.13.0)
207
+ sequel (5.100.0)
208
+ simplecov (0.22.0)
209
+ simplecov-html (0.13.2)
210
+ simplecov_json_formatter (0.1.4)
211
+ sqlite3 (2.9.0-arm64-darwin)
212
+ strings (0.2.1)
213
+ strings-ansi (0.2.0)
214
+ thor (1.4.0)
215
+ trak_flow (0.1.0)
216
+ tty-color (0.6.0)
217
+ tty-cursor (0.7.1)
218
+ tty-screen (0.8.2)
219
+ tty-spinner (0.9.3)
220
+ tty-table (0.12.0)
221
+ unicode-display_width (2.6.0)
222
+ unicode_utils (1.4.0)
223
+ zeitwerk (2.7.4) sha256=2bef90f356bdafe9a6c2bd32bcd804f83a4f9b8bc27f3600fff051eb3edcec8b
224
+
225
+ BUNDLED WITH
226
+ 4.0.3
@@ -0,0 +1,232 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Demo: TrakFlow MCP Server with HTTP Transport
5
+ #
6
+ # This example shows how to connect to the TrakFlow MCP server
7
+ # using HTTP/SSE transport with the ruby_llm and ruby_llm-mcp gems.
8
+ #
9
+ # The demo demonstrates:
10
+ # - Connecting via HTTP/SSE transport
11
+ # - Listing available tools and resources
12
+ # - Creating a task via MCP tool
13
+ # - Starting and closing the task (showing state changes)
14
+ # - Optionally using an LLM with MCP tools
15
+ #
16
+ # Prerequisites:
17
+ # - Ollama running: ollama serve
18
+ # - Model available: ollama pull gpt-oss (or set OLLAMA_MODEL env var)
19
+ #
20
+ # The demo will automatically start the MCP server if not running.
21
+ #
22
+ # Usage:
23
+ # cd examples/mcp
24
+ # bundle install
25
+ # ruby http_demo.rb
26
+
27
+ require "bundler/setup"
28
+ require "ruby_llm"
29
+ require "ruby_llm/mcp"
30
+ require "fileutils"
31
+ require "socket"
32
+ require "tmpdir"
33
+
34
+ # Configure RubyLLM to use Ollama
35
+ RubyLLM.configure do |config|
36
+ config.ollama_api_base = ENV["OLLAMA_API_BASE"] || ENV["OLLAMA_URL"] || "http://localhost:11434/v1"
37
+ end
38
+
39
+ # Model configuration
40
+ OLLAMA_MODEL = ENV.fetch("OLLAMA_MODEL", "gpt-oss:latest")
41
+
42
+ # MCP server HTTP endpoint (default port from TrakFlow config)
43
+ MCP_PORT = ENV.fetch("MCP_PORT", "3333").to_i
44
+ MCP_HTTP_URL = ENV.fetch("MCP_URL", "http://localhost:#{MCP_PORT}")
45
+
46
+ # Path to the TrakFlow MCP server executable and project root
47
+ PROJECT_ROOT = File.expand_path("../..", __dir__)
48
+ TF_MCP_PATH = File.join(PROJECT_ROOT, "bin/tf_mcp")
49
+ TF_CLI_PATH = File.join(PROJECT_ROOT, "bin/tf")
50
+ GEMFILE_PATH = File.join(PROJECT_ROOT, "Gemfile")
51
+
52
+ def port_open?(port, host = "127.0.0.1")
53
+ Socket.tcp(host, port, connect_timeout: 1) { true }
54
+ rescue Errno::ECONNREFUSED, Errno::ETIMEDOUT, SocketError
55
+ false
56
+ end
57
+
58
+ def wait_for_server(port, timeout: 10)
59
+ start = Time.now
60
+ until port_open?(port)
61
+ return false if Time.now - start > timeout
62
+ sleep 0.2
63
+ end
64
+ true
65
+ end
66
+
67
+ puts "TrakFlow MCP HTTP Demo"
68
+ puts "=" * 40
69
+ puts
70
+
71
+ # Check if server is running, start it if not
72
+ server_pid = nil
73
+ work_dir = nil
74
+
75
+ unless port_open?(MCP_PORT)
76
+ puts "MCP server not running on port #{MCP_PORT}. Starting it..."
77
+
78
+ # Create a temp directory and initialize TrakFlow there
79
+ work_dir = Dir.mktmpdir("trakflow_mcp_http_demo")
80
+ puts "Working directory: #{work_dir}"
81
+
82
+ # Initialize TrakFlow in the temp directory
83
+ Dir.chdir(work_dir) do
84
+ system({ "BUNDLE_GEMFILE" => GEMFILE_PATH }, "bundle", "exec", TF_CLI_PATH, "init", out: File::NULL, err: File::NULL)
85
+ end
86
+
87
+ # Start the MCP server in the background
88
+ server_pid = spawn(
89
+ { "BUNDLE_GEMFILE" => GEMFILE_PATH },
90
+ "bundle", "exec", TF_MCP_PATH, "--http", "--port", MCP_PORT.to_s,
91
+ chdir: work_dir,
92
+ out: File::NULL,
93
+ err: File::NULL
94
+ )
95
+ Process.detach(server_pid)
96
+
97
+ puts "Started MCP server (PID: #{server_pid})"
98
+
99
+ unless wait_for_server(MCP_PORT)
100
+ puts "ERROR: Server failed to start within timeout"
101
+ Process.kill("TERM", server_pid) rescue nil
102
+ FileUtils.rm_rf(work_dir) if work_dir
103
+ exit 1
104
+ end
105
+
106
+ puts "MCP server is ready."
107
+ else
108
+ puts "MCP server already running on port #{MCP_PORT}"
109
+ end
110
+
111
+ puts
112
+ puts "Connecting to: #{MCP_HTTP_URL}"
113
+ puts
114
+
115
+ # Create MCP client with SSE transport (HTTP-based)
116
+ client = RubyLLM::MCP.client(
117
+ name: "trakflow",
118
+ transport_type: :sse,
119
+ config: {
120
+ url: "#{MCP_HTTP_URL}/mcp/sse"
121
+ }
122
+ )
123
+
124
+ begin
125
+ # The client connects automatically on creation
126
+ puts "Connected to TrakFlow MCP server via HTTP/SSE..."
127
+ puts "Connection alive: #{client.alive?}"
128
+
129
+ # List available tools
130
+ puts "\nAvailable Tools:"
131
+ puts "-" * 40
132
+ client.tools.each do |tool|
133
+ puts " - #{tool.name}: #{tool.description}"
134
+ end
135
+
136
+ # List available resources
137
+ puts "\nAvailable Resources:"
138
+ puts "-" * 40
139
+ client.resources.each do |resource|
140
+ puts " - #{resource.uri}: #{resource.name}"
141
+ end
142
+
143
+ # Example: Create a task using the MCP tool
144
+ puts "\n" + "=" * 40
145
+ puts "Creating a test task..."
146
+ puts "-" * 40
147
+
148
+ tool = client.tool("task_create")
149
+ result = tool.execute(
150
+ title: "Demo task from HTTP",
151
+ description: "Created via MCP HTTP transport demo"
152
+ )
153
+
154
+ puts "Result: #{result}"
155
+
156
+ # Example: Create a plan
157
+ puts "\n" + "=" * 40
158
+ puts "Creating a plan..."
159
+ puts "-" * 40
160
+
161
+ plan_tool = client.tool("plan_create")
162
+ plan_result = plan_tool.execute(
163
+ title: "Example workflow plan",
164
+ description: "A demo plan with steps"
165
+ )
166
+ puts "Plan created: #{plan_result}"
167
+
168
+ # Extract the task ID from the creation result
169
+ # result may be a String or object with .to_s
170
+ result_str = result.to_s
171
+ task_id = result_str.match(/id: "([^"]+)"/)[1] rescue nil
172
+
173
+ # Example: Start the task to demonstrate a second tool call
174
+ if task_id
175
+ puts "\n" + "=" * 40
176
+ puts "Starting the task via MCP tool..."
177
+ puts "-" * 40
178
+
179
+ start_tool = client.tool("task_start")
180
+ start_result = start_tool.execute(id: task_id)
181
+ puts "Task started: #{start_result}"
182
+
183
+ # Close the task
184
+ puts "\n" + "=" * 40
185
+ puts "Closing the task via MCP tool..."
186
+ puts "-" * 40
187
+
188
+ close_tool = client.tool("task_close")
189
+ close_result = close_tool.execute(id: task_id)
190
+ puts "Task closed: #{close_result}"
191
+ end
192
+
193
+ # Example: Use with RubyLLM chat (optional - requires Ollama)
194
+ if ENV["SKIP_LLM"] != "1"
195
+ puts "\n" + "=" * 40
196
+ puts "Sending prompt to LLM with MCP tools..."
197
+ puts "-" * 40
198
+
199
+ chat = RubyLLM.chat(model: OLLAMA_MODEL, provider: :ollama, assume_model_exists: true)
200
+ chat.with_tools(*client.tools)
201
+ response = chat.ask("Use the available tools to list all tasks and summarize what you find.")
202
+
203
+ puts "\nLLM Response:"
204
+ puts response.content
205
+ else
206
+ puts "\n" + "=" * 40
207
+ puts "Skipping LLM test (SKIP_LLM=1)"
208
+ puts "-" * 40
209
+ end
210
+
211
+ rescue Errno::ECONNREFUSED
212
+ puts "Error: Could not connect to MCP server at #{MCP_HTTP_URL}"
213
+ rescue StandardError => e
214
+ puts "Error: #{e.message}"
215
+ puts e.backtrace.first(5).join("\n")
216
+ ensure
217
+ client.cleanup if client.respond_to?(:cleanup)
218
+
219
+ # Stop server if we started it
220
+ if server_pid
221
+ Process.kill("TERM", server_pid) rescue nil
222
+ puts "Stopped MCP server (PID: #{server_pid})"
223
+ end
224
+
225
+ # Clean up temp directory if we created it
226
+ if work_dir && File.exist?(work_dir)
227
+ FileUtils.rm_rf(work_dir)
228
+ puts "Cleaned up temp directory."
229
+ end
230
+
231
+ puts "\nCleaned up MCP client."
232
+ end
@@ -0,0 +1,146 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Demo: TrakFlow MCP Server with STDIO Transport
5
+ #
6
+ # This example shows how to connect to the TrakFlow MCP server
7
+ # using STDIO transport with the ruby_llm and ruby_llm-mcp gems.
8
+ #
9
+ # Prerequisites:
10
+ # - Ollama running: ollama serve
11
+ # - Model available: ollama pull gpt-oss (or set OLLAMA_MODEL env var)
12
+ #
13
+ # Usage:
14
+ # cd examples/mcp
15
+ # bundle install
16
+ # ruby stdio_demo.rb
17
+
18
+ require "bundler/setup"
19
+ require "ruby_llm"
20
+ require "ruby_llm/mcp"
21
+ require "tmpdir"
22
+ require "fileutils"
23
+
24
+ # Configure RubyLLM to use Ollama
25
+ RubyLLM.configure do |config|
26
+ config.ollama_api_base = ENV["OLLAMA_API_BASE"] || ENV["OLLAMA_URL"] || "http://localhost:11434/v1"
27
+ end
28
+
29
+ # Model configuration
30
+ OLLAMA_MODEL = ENV.fetch("OLLAMA_MODEL", "gpt-oss:latest")
31
+
32
+ # Path to the TrakFlow MCP server executable and project root
33
+ PROJECT_ROOT = File.expand_path("../..", __dir__)
34
+ TF_MCP_PATH = File.join(PROJECT_ROOT, "bin/tf_mcp")
35
+ TF_CLI_PATH = File.join(PROJECT_ROOT, "bin/tf")
36
+ GEMFILE_PATH = File.join(PROJECT_ROOT, "Gemfile")
37
+
38
+ puts "TrakFlow MCP STDIO Demo"
39
+ puts "=" * 40
40
+ puts
41
+
42
+ # Create a temp directory and initialize TrakFlow there
43
+ WORK_DIR = Dir.mktmpdir("trakflow_mcp_demo")
44
+ puts "Working directory: #{WORK_DIR}"
45
+
46
+ # Initialize TrakFlow in the temp directory
47
+ Dir.chdir(WORK_DIR) do
48
+ system({ "BUNDLE_GEMFILE" => GEMFILE_PATH }, "bundle", "exec", TF_CLI_PATH, "init", out: File::NULL, err: File::NULL)
49
+ end
50
+
51
+ puts "TrakFlow initialized."
52
+ puts
53
+
54
+ # Create MCP client with STDIO transport
55
+ # Use a shell wrapper to cd to work dir and run with proper bundle
56
+ client = RubyLLM::MCP.client(
57
+ name: "trakflow",
58
+ transport_type: :stdio,
59
+ config: {
60
+ command: "/bin/sh",
61
+ args: ["-c", "cd #{WORK_DIR} && BUNDLE_GEMFILE=#{GEMFILE_PATH} bundle exec #{TF_MCP_PATH}"]
62
+ }
63
+ )
64
+
65
+ begin
66
+ # The client connects automatically on creation
67
+ puts "Connected to TrakFlow MCP server via STDIO..."
68
+ puts "Connection alive: #{client.alive?}"
69
+
70
+ # List available tools
71
+ puts "\nAvailable Tools:"
72
+ puts "-" * 40
73
+ client.tools.each do |tool|
74
+ puts " - #{tool.name}: #{tool.description}"
75
+ end
76
+
77
+ # List available resources
78
+ puts "\nAvailable Resources:"
79
+ puts "-" * 40
80
+ client.resources.each do |resource|
81
+ puts " - #{resource.uri}: #{resource.name}"
82
+ end
83
+
84
+ # Example: Create a task using the MCP tool
85
+ puts "\n" + "=" * 40
86
+ puts "Creating a test task..."
87
+ puts "-" * 40
88
+
89
+ tool = client.tool("task_create")
90
+ result = tool.execute(
91
+ title: "Demo task from STDIO",
92
+ description: "Created via MCP STDIO transport demo"
93
+ )
94
+
95
+ puts "Result: #{result}"
96
+
97
+ # Extract the task ID from the creation result
98
+ result_str = result.to_s
99
+ task_id = result_str.match(/id: "([^"]+)"/)[1] rescue nil
100
+
101
+ # Example: Start the task to demonstrate a second tool call
102
+ if task_id
103
+ puts "\n" + "=" * 40
104
+ puts "Starting the task via MCP tool..."
105
+ puts "-" * 40
106
+
107
+ start_tool = client.tool("task_start")
108
+ start_result = start_tool.execute(id: task_id)
109
+ puts "Task started: #{start_result}"
110
+
111
+ # Close the task
112
+ puts "\n" + "=" * 40
113
+ puts "Closing the task via MCP tool..."
114
+ puts "-" * 40
115
+
116
+ close_tool = client.tool("task_close")
117
+ close_result = close_tool.execute(id: task_id)
118
+ puts "Task closed: #{close_result}"
119
+ end
120
+
121
+ # Example: Use with RubyLLM chat (optional - requires Ollama)
122
+ if ENV["SKIP_LLM"] != "1"
123
+ puts "\n" + "=" * 40
124
+ puts "Sending prompt to LLM with MCP tools..."
125
+ puts "-" * 40
126
+
127
+ chat = RubyLLM.chat(model: OLLAMA_MODEL, provider: :ollama, assume_model_exists: true)
128
+ chat.with_tools(*client.tools)
129
+ response = chat.ask("Use the available tools to list all tasks and summarize what you find.")
130
+
131
+ puts "\nLLM Response:"
132
+ puts response.content
133
+ else
134
+ puts "\n" + "=" * 40
135
+ puts "Skipping LLM test (SKIP_LLM=1)"
136
+ puts "-" * 40
137
+ end
138
+
139
+ rescue StandardError => e
140
+ puts "Error: #{e.message}"
141
+ puts e.backtrace.first(5).join("\n")
142
+ ensure
143
+ client.cleanup if client.respond_to?(:cleanup)
144
+ FileUtils.rm_rf(WORK_DIR) if defined?(WORK_DIR) && File.exist?(WORK_DIR)
145
+ puts "\nCleaned up MCP client and temp directory."
146
+ end