language-operator 0.0.1 → 0.1.31

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 (120) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +125 -0
  3. data/CHANGELOG.md +88 -0
  4. data/Gemfile +8 -0
  5. data/Gemfile.lock +284 -0
  6. data/LICENSE +229 -21
  7. data/Makefile +82 -0
  8. data/README.md +3 -11
  9. data/Rakefile +63 -0
  10. data/bin/aictl +7 -0
  11. data/completions/_aictl +232 -0
  12. data/completions/aictl.bash +121 -0
  13. data/completions/aictl.fish +114 -0
  14. data/docs/architecture/agent-runtime.md +585 -0
  15. data/docs/dsl/SCHEMA_VERSION.md +250 -0
  16. data/docs/dsl/agent-reference.md +604 -0
  17. data/docs/dsl/best-practices.md +1078 -0
  18. data/docs/dsl/chat-endpoints.md +895 -0
  19. data/docs/dsl/constraints.md +671 -0
  20. data/docs/dsl/mcp-integration.md +1177 -0
  21. data/docs/dsl/webhooks.md +932 -0
  22. data/docs/dsl/workflows.md +744 -0
  23. data/lib/language_operator/agent/base.rb +110 -0
  24. data/lib/language_operator/agent/executor.rb +440 -0
  25. data/lib/language_operator/agent/instrumentation.rb +54 -0
  26. data/lib/language_operator/agent/metrics_tracker.rb +183 -0
  27. data/lib/language_operator/agent/safety/ast_validator.rb +272 -0
  28. data/lib/language_operator/agent/safety/audit_logger.rb +104 -0
  29. data/lib/language_operator/agent/safety/budget_tracker.rb +175 -0
  30. data/lib/language_operator/agent/safety/content_filter.rb +93 -0
  31. data/lib/language_operator/agent/safety/manager.rb +207 -0
  32. data/lib/language_operator/agent/safety/rate_limiter.rb +150 -0
  33. data/lib/language_operator/agent/safety/safe_executor.rb +127 -0
  34. data/lib/language_operator/agent/scheduler.rb +183 -0
  35. data/lib/language_operator/agent/telemetry.rb +116 -0
  36. data/lib/language_operator/agent/web_server.rb +610 -0
  37. data/lib/language_operator/agent/webhook_authenticator.rb +226 -0
  38. data/lib/language_operator/agent.rb +149 -0
  39. data/lib/language_operator/cli/commands/agent.rb +1205 -0
  40. data/lib/language_operator/cli/commands/cluster.rb +371 -0
  41. data/lib/language_operator/cli/commands/install.rb +404 -0
  42. data/lib/language_operator/cli/commands/model.rb +266 -0
  43. data/lib/language_operator/cli/commands/persona.rb +393 -0
  44. data/lib/language_operator/cli/commands/quickstart.rb +22 -0
  45. data/lib/language_operator/cli/commands/status.rb +143 -0
  46. data/lib/language_operator/cli/commands/system.rb +772 -0
  47. data/lib/language_operator/cli/commands/tool.rb +537 -0
  48. data/lib/language_operator/cli/commands/use.rb +47 -0
  49. data/lib/language_operator/cli/errors/handler.rb +180 -0
  50. data/lib/language_operator/cli/errors/suggestions.rb +176 -0
  51. data/lib/language_operator/cli/formatters/code_formatter.rb +77 -0
  52. data/lib/language_operator/cli/formatters/log_formatter.rb +288 -0
  53. data/lib/language_operator/cli/formatters/progress_formatter.rb +49 -0
  54. data/lib/language_operator/cli/formatters/status_formatter.rb +37 -0
  55. data/lib/language_operator/cli/formatters/table_formatter.rb +163 -0
  56. data/lib/language_operator/cli/formatters/value_formatter.rb +113 -0
  57. data/lib/language_operator/cli/helpers/cluster_context.rb +62 -0
  58. data/lib/language_operator/cli/helpers/cluster_validator.rb +101 -0
  59. data/lib/language_operator/cli/helpers/editor_helper.rb +58 -0
  60. data/lib/language_operator/cli/helpers/kubeconfig_validator.rb +167 -0
  61. data/lib/language_operator/cli/helpers/pastel_helper.rb +24 -0
  62. data/lib/language_operator/cli/helpers/resource_dependency_checker.rb +74 -0
  63. data/lib/language_operator/cli/helpers/schedule_builder.rb +108 -0
  64. data/lib/language_operator/cli/helpers/user_prompts.rb +69 -0
  65. data/lib/language_operator/cli/main.rb +236 -0
  66. data/lib/language_operator/cli/templates/tools/generic.yaml +66 -0
  67. data/lib/language_operator/cli/wizards/agent_wizard.rb +246 -0
  68. data/lib/language_operator/cli/wizards/quickstart_wizard.rb +588 -0
  69. data/lib/language_operator/client/base.rb +214 -0
  70. data/lib/language_operator/client/config.rb +136 -0
  71. data/lib/language_operator/client/cost_calculator.rb +37 -0
  72. data/lib/language_operator/client/mcp_connector.rb +123 -0
  73. data/lib/language_operator/client.rb +19 -0
  74. data/lib/language_operator/config/cluster_config.rb +101 -0
  75. data/lib/language_operator/config/tool_patterns.yaml +57 -0
  76. data/lib/language_operator/config/tool_registry.rb +96 -0
  77. data/lib/language_operator/config.rb +138 -0
  78. data/lib/language_operator/dsl/adapter.rb +124 -0
  79. data/lib/language_operator/dsl/agent_context.rb +90 -0
  80. data/lib/language_operator/dsl/agent_definition.rb +427 -0
  81. data/lib/language_operator/dsl/chat_endpoint_definition.rb +115 -0
  82. data/lib/language_operator/dsl/config.rb +119 -0
  83. data/lib/language_operator/dsl/context.rb +50 -0
  84. data/lib/language_operator/dsl/execution_context.rb +47 -0
  85. data/lib/language_operator/dsl/helpers.rb +109 -0
  86. data/lib/language_operator/dsl/http.rb +184 -0
  87. data/lib/language_operator/dsl/mcp_server_definition.rb +73 -0
  88. data/lib/language_operator/dsl/parameter_definition.rb +124 -0
  89. data/lib/language_operator/dsl/registry.rb +36 -0
  90. data/lib/language_operator/dsl/schema.rb +1102 -0
  91. data/lib/language_operator/dsl/shell.rb +125 -0
  92. data/lib/language_operator/dsl/tool_definition.rb +112 -0
  93. data/lib/language_operator/dsl/webhook_authentication.rb +114 -0
  94. data/lib/language_operator/dsl/webhook_definition.rb +106 -0
  95. data/lib/language_operator/dsl/workflow_definition.rb +259 -0
  96. data/lib/language_operator/dsl.rb +161 -0
  97. data/lib/language_operator/errors.rb +60 -0
  98. data/lib/language_operator/kubernetes/client.rb +279 -0
  99. data/lib/language_operator/kubernetes/resource_builder.rb +194 -0
  100. data/lib/language_operator/loggable.rb +47 -0
  101. data/lib/language_operator/logger.rb +141 -0
  102. data/lib/language_operator/retry.rb +123 -0
  103. data/lib/language_operator/retryable.rb +132 -0
  104. data/lib/language_operator/templates/README.md +23 -0
  105. data/lib/language_operator/templates/examples/agent_synthesis.tmpl +115 -0
  106. data/lib/language_operator/templates/examples/persona_distillation.tmpl +19 -0
  107. data/lib/language_operator/templates/schema/.gitkeep +0 -0
  108. data/lib/language_operator/templates/schema/CHANGELOG.md +93 -0
  109. data/lib/language_operator/templates/schema/agent_dsl_openapi.yaml +306 -0
  110. data/lib/language_operator/templates/schema/agent_dsl_schema.json +452 -0
  111. data/lib/language_operator/tool_loader.rb +242 -0
  112. data/lib/language_operator/validators.rb +170 -0
  113. data/lib/language_operator/version.rb +1 -1
  114. data/lib/language_operator.rb +65 -3
  115. data/requirements/tasks/challenge.md +9 -0
  116. data/requirements/tasks/iterate.md +36 -0
  117. data/requirements/tasks/optimize.md +21 -0
  118. data/requirements/tasks/tag.md +5 -0
  119. data/test_agent_dsl.rb +108 -0
  120. metadata +507 -20
@@ -0,0 +1,452 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "https://github.com/language-operator/language-operator-gem/schema/agent-dsl.json",
4
+ "title": "Language Operator Agent DSL",
5
+ "description": "Schema for defining autonomous AI agents using the Language Operator DSL",
6
+ "version": "0.1.31",
7
+ "type": "object",
8
+ "properties": {
9
+ "name": {
10
+ "type": "string",
11
+ "description": "Unique agent identifier (lowercase, alphanumeric, hyphens)",
12
+ "pattern": "^[a-z0-9-]+$",
13
+ "minLength": 1,
14
+ "maxLength": 63
15
+ },
16
+ "description": {
17
+ "type": "string",
18
+ "description": "Human-readable description of agent purpose"
19
+ },
20
+ "persona": {
21
+ "type": "string",
22
+ "description": "System prompt or persona defining agent behavior and expertise"
23
+ },
24
+ "schedule": {
25
+ "type": "string",
26
+ "description": "Cron expression for scheduled execution (sets mode to :scheduled)",
27
+ "pattern": "^\\s*(\\S+\\s+){4}\\S+\\s*$"
28
+ },
29
+ "mode": {
30
+ "type": "string",
31
+ "description": "Execution mode for the agent",
32
+ "enum": [
33
+ "autonomous",
34
+ "scheduled",
35
+ "reactive"
36
+ ]
37
+ },
38
+ "objectives": {
39
+ "type": "array",
40
+ "description": "List of goals the agent should achieve",
41
+ "items": {
42
+ "type": "string"
43
+ },
44
+ "minItems": 0
45
+ },
46
+ "workflow": {
47
+ "$ref": "#/definitions/WorkflowDefinition"
48
+ },
49
+ "constraints": {
50
+ "$ref": "#/definitions/ConstraintsDefinition"
51
+ },
52
+ "output": {
53
+ "$ref": "#/definitions/OutputDefinition"
54
+ },
55
+ "webhooks": {
56
+ "type": "array",
57
+ "description": "Webhook endpoints for reactive agents",
58
+ "items": {
59
+ "$ref": "#/definitions/WebhookDefinition"
60
+ }
61
+ },
62
+ "mcp_server": {
63
+ "$ref": "#/definitions/McpServerDefinition"
64
+ },
65
+ "chat_endpoint": {
66
+ "$ref": "#/definitions/ChatEndpointDefinition"
67
+ }
68
+ },
69
+ "required": [
70
+ "name"
71
+ ],
72
+ "definitions": {
73
+ "WorkflowDefinition": {
74
+ "type": "object",
75
+ "description": "Multi-step workflow with dependencies",
76
+ "properties": {
77
+ "steps": {
78
+ "type": "array",
79
+ "description": "Ordered list of workflow steps",
80
+ "items": {
81
+ "$ref": "#/definitions/StepDefinition"
82
+ }
83
+ }
84
+ }
85
+ },
86
+ "StepDefinition": {
87
+ "type": "object",
88
+ "description": "Individual workflow step",
89
+ "properties": {
90
+ "name": {
91
+ "type": "string",
92
+ "description": "Step identifier (symbol or string)"
93
+ },
94
+ "tool": {
95
+ "type": "string",
96
+ "description": "Tool name to execute in this step"
97
+ },
98
+ "params": {
99
+ "type": "object",
100
+ "description": "Parameters to pass to the tool",
101
+ "additionalProperties": true
102
+ },
103
+ "depends_on": {
104
+ "oneOf": [
105
+ {
106
+ "type": "string"
107
+ },
108
+ {
109
+ "type": "array",
110
+ "items": {
111
+ "type": "string"
112
+ }
113
+ }
114
+ ],
115
+ "description": "Step dependencies (must complete before this step)"
116
+ },
117
+ "prompt": {
118
+ "type": "string",
119
+ "description": "LLM prompt template for this step"
120
+ }
121
+ },
122
+ "required": [
123
+ "name"
124
+ ]
125
+ },
126
+ "ConstraintsDefinition": {
127
+ "type": "object",
128
+ "description": "Execution constraints and limits",
129
+ "properties": {
130
+ "max_iterations": {
131
+ "type": "integer",
132
+ "description": "Maximum number of execution iterations",
133
+ "minimum": 1
134
+ },
135
+ "timeout": {
136
+ "type": "string",
137
+ "description": "Execution timeout (e.g., \"30s\", \"5m\", \"1h\")",
138
+ "pattern": "^\\d+[smh]$"
139
+ },
140
+ "memory": {
141
+ "type": "string",
142
+ "description": "Memory limit (e.g., \"512Mi\", \"1Gi\")"
143
+ },
144
+ "rate_limit": {
145
+ "type": "integer",
146
+ "description": "Maximum requests per time period",
147
+ "minimum": 1
148
+ },
149
+ "daily_budget": {
150
+ "type": "number",
151
+ "description": "Maximum daily cost in USD",
152
+ "minimum": 0
153
+ },
154
+ "hourly_budget": {
155
+ "type": "number",
156
+ "description": "Maximum hourly cost in USD",
157
+ "minimum": 0
158
+ },
159
+ "token_budget": {
160
+ "type": "integer",
161
+ "description": "Maximum total tokens allowed",
162
+ "minimum": 1
163
+ },
164
+ "requests_per_minute": {
165
+ "type": "integer",
166
+ "description": "Maximum requests per minute",
167
+ "minimum": 1
168
+ },
169
+ "requests_per_hour": {
170
+ "type": "integer",
171
+ "description": "Maximum requests per hour",
172
+ "minimum": 1
173
+ },
174
+ "requests_per_day": {
175
+ "type": "integer",
176
+ "description": "Maximum requests per day",
177
+ "minimum": 1
178
+ },
179
+ "blocked_patterns": {
180
+ "type": "array",
181
+ "description": "Content patterns to block",
182
+ "items": {
183
+ "type": "string"
184
+ }
185
+ },
186
+ "blocked_topics": {
187
+ "type": "array",
188
+ "description": "Topics to avoid",
189
+ "items": {
190
+ "type": "string"
191
+ }
192
+ }
193
+ }
194
+ },
195
+ "OutputDefinition": {
196
+ "type": "object",
197
+ "description": "Output destination configuration",
198
+ "properties": {
199
+ "workspace": {
200
+ "type": "string",
201
+ "description": "Workspace directory path for file outputs"
202
+ },
203
+ "slack": {
204
+ "type": "object",
205
+ "description": "Slack integration configuration",
206
+ "properties": {
207
+ "channel": {
208
+ "type": "string",
209
+ "description": "Slack channel name or ID"
210
+ }
211
+ },
212
+ "required": [
213
+ "channel"
214
+ ]
215
+ },
216
+ "email": {
217
+ "type": "object",
218
+ "description": "Email notification configuration",
219
+ "properties": {
220
+ "to": {
221
+ "type": "string",
222
+ "description": "Email recipient address",
223
+ "format": "email"
224
+ }
225
+ },
226
+ "required": [
227
+ "to"
228
+ ]
229
+ }
230
+ }
231
+ },
232
+ "WebhookDefinition": {
233
+ "type": "object",
234
+ "description": "Webhook endpoint configuration",
235
+ "properties": {
236
+ "path": {
237
+ "type": "string",
238
+ "description": "URL path for webhook endpoint",
239
+ "pattern": "^/"
240
+ },
241
+ "method": {
242
+ "type": "string",
243
+ "description": "HTTP method",
244
+ "enum": [
245
+ "get",
246
+ "post",
247
+ "put",
248
+ "delete",
249
+ "patch"
250
+ ],
251
+ "default": "post"
252
+ },
253
+ "authentication": {
254
+ "$ref": "#/definitions/WebhookAuthentication"
255
+ },
256
+ "validations": {
257
+ "type": "array",
258
+ "description": "Request validation rules",
259
+ "items": {
260
+ "type": "object",
261
+ "properties": {
262
+ "type": {
263
+ "type": "string",
264
+ "enum": [
265
+ "headers",
266
+ "content_type",
267
+ "custom"
268
+ ]
269
+ }
270
+ }
271
+ }
272
+ }
273
+ },
274
+ "required": [
275
+ "path"
276
+ ]
277
+ },
278
+ "WebhookAuthentication": {
279
+ "type": "object",
280
+ "description": "Webhook authentication configuration",
281
+ "properties": {
282
+ "type": {
283
+ "type": "string",
284
+ "description": "Authentication type",
285
+ "enum": [
286
+ "hmac",
287
+ "api_key",
288
+ "bearer",
289
+ "custom"
290
+ ]
291
+ },
292
+ "secret": {
293
+ "type": "string",
294
+ "description": "Secret key for authentication"
295
+ },
296
+ "header": {
297
+ "type": "string",
298
+ "description": "Header name containing signature/token"
299
+ },
300
+ "algorithm": {
301
+ "type": "string",
302
+ "description": "HMAC algorithm",
303
+ "enum": [
304
+ "sha1",
305
+ "sha256",
306
+ "sha512"
307
+ ]
308
+ },
309
+ "prefix": {
310
+ "type": "string",
311
+ "description": "Signature prefix (e.g., \"sha256=\")"
312
+ }
313
+ }
314
+ },
315
+ "McpServerDefinition": {
316
+ "type": "object",
317
+ "description": "MCP (Model Context Protocol) server configuration",
318
+ "properties": {
319
+ "name": {
320
+ "type": "string",
321
+ "description": "MCP server name"
322
+ },
323
+ "tools": {
324
+ "type": "object",
325
+ "description": "Tools exposed via MCP",
326
+ "additionalProperties": {
327
+ "$ref": "#/definitions/ToolDefinition"
328
+ }
329
+ }
330
+ }
331
+ },
332
+ "ChatEndpointDefinition": {
333
+ "type": "object",
334
+ "description": "OpenAI-compatible chat endpoint configuration",
335
+ "properties": {
336
+ "system_prompt": {
337
+ "type": "string",
338
+ "description": "System prompt for chat mode"
339
+ },
340
+ "temperature": {
341
+ "type": "number",
342
+ "description": "Sampling temperature (0.0-2.0)",
343
+ "minimum": 0.0,
344
+ "maximum": 2.0,
345
+ "default": 0.7
346
+ },
347
+ "max_tokens": {
348
+ "type": "integer",
349
+ "description": "Maximum tokens in response",
350
+ "minimum": 1,
351
+ "default": 2000
352
+ },
353
+ "model_name": {
354
+ "type": "string",
355
+ "description": "Model name exposed in API"
356
+ },
357
+ "top_p": {
358
+ "type": "number",
359
+ "description": "Nucleus sampling parameter (0.0-1.0)",
360
+ "minimum": 0.0,
361
+ "maximum": 1.0,
362
+ "default": 1.0
363
+ },
364
+ "frequency_penalty": {
365
+ "type": "number",
366
+ "description": "Frequency penalty (-2.0 to 2.0)",
367
+ "minimum": -2.0,
368
+ "maximum": 2.0,
369
+ "default": 0.0
370
+ },
371
+ "presence_penalty": {
372
+ "type": "number",
373
+ "description": "Presence penalty (-2.0 to 2.0)",
374
+ "minimum": -2.0,
375
+ "maximum": 2.0,
376
+ "default": 0.0
377
+ },
378
+ "stop_sequences": {
379
+ "type": "array",
380
+ "description": "Sequences that stop generation",
381
+ "items": {
382
+ "type": "string"
383
+ }
384
+ }
385
+ }
386
+ },
387
+ "ToolDefinition": {
388
+ "type": "object",
389
+ "description": "MCP tool definition",
390
+ "properties": {
391
+ "name": {
392
+ "type": "string",
393
+ "description": "Tool name (lowercase, alphanumeric, underscores)",
394
+ "pattern": "^[a-z0-9_]+$"
395
+ },
396
+ "description": {
397
+ "type": "string",
398
+ "description": "Human-readable tool description"
399
+ },
400
+ "parameters": {
401
+ "type": "object",
402
+ "description": "Tool parameters",
403
+ "additionalProperties": {
404
+ "$ref": "#/definitions/ParameterDefinition"
405
+ }
406
+ }
407
+ },
408
+ "required": [
409
+ "name",
410
+ "description"
411
+ ]
412
+ },
413
+ "ParameterDefinition": {
414
+ "type": "object",
415
+ "description": "Tool parameter definition",
416
+ "properties": {
417
+ "type": {
418
+ "type": "string",
419
+ "description": "Parameter type",
420
+ "enum": [
421
+ "string",
422
+ "number",
423
+ "integer",
424
+ "boolean",
425
+ "array",
426
+ "object"
427
+ ]
428
+ },
429
+ "description": {
430
+ "type": "string",
431
+ "description": "Parameter description"
432
+ },
433
+ "required": {
434
+ "type": "boolean",
435
+ "description": "Whether parameter is required",
436
+ "default": false
437
+ },
438
+ "default": {
439
+ "description": "Default value if not provided"
440
+ },
441
+ "enum": {
442
+ "type": "array",
443
+ "description": "Allowed values",
444
+ "items": {}
445
+ }
446
+ },
447
+ "required": [
448
+ "type"
449
+ ]
450
+ }
451
+ }
452
+ }
@@ -0,0 +1,242 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'dsl'
4
+ require 'mcp'
5
+ require 'opentelemetry/sdk'
6
+
7
+ module LanguageOperator
8
+ # Loads tool definitions from Ruby files
9
+ #
10
+ # Scans a directory for Ruby files containing tool definitions and loads them
11
+ # into a registry. Provides hot-reloading capability for development.
12
+ #
13
+ # @example Basic usage
14
+ # registry = LanguageOperator::Dsl::Registry.new
15
+ # loader = LanguageOperator::ToolLoader.new(registry, '/mcp/tools')
16
+ # loader.load_tools
17
+ # puts "Loaded #{registry.all.length} tools"
18
+ #
19
+ # @example With custom context
20
+ # loader = LanguageOperator::ToolLoader.new(registry)
21
+ # loader.load_tools
22
+ # loader.reload # Hot reload tools
23
+ #
24
+ # @example Start MCP server
25
+ # LanguageOperator::ToolLoader.start # Loads tools and starts MCP server
26
+ class ToolLoader
27
+ # Initialize tool loader
28
+ #
29
+ # @param registry [LanguageOperator::Dsl::Registry] Tool registry
30
+ # @param tools_dir [String] Directory containing tool definition files
31
+ def initialize(registry, tools_dir = '/mcp')
32
+ @registry = registry
33
+ @tools_dir = tools_dir
34
+ end
35
+
36
+ # Load all tool files from the tools directory
37
+ #
38
+ # @return [void]
39
+ def load_tools
40
+ @registry.clear
41
+
42
+ unless Dir.exist?(@tools_dir)
43
+ puts "Tools directory #{@tools_dir} does not exist. Skipping tool loading."
44
+ return
45
+ end
46
+
47
+ tool_files = Dir.glob(File.join(@tools_dir, '**', '*.rb'))
48
+
49
+ if tool_files.empty?
50
+ puts "No tool files found in #{@tools_dir}"
51
+ return
52
+ end
53
+
54
+ tool_files.each do |file|
55
+ load_tool_file(file)
56
+ end
57
+
58
+ puts "Loaded #{@registry.all.length} tools from #{tool_files.length} files"
59
+ end
60
+
61
+ # Load a single tool file
62
+ #
63
+ # @param file [String] Path to tool definition file
64
+ # @return [void]
65
+ def load_tool_file(file)
66
+ puts "Loading tools from: #{file}"
67
+
68
+ begin
69
+ context = LanguageOperator::Dsl::Context.new(@registry)
70
+ code = File.read(file)
71
+
72
+ # Execute in sandbox with validation
73
+ executor = LanguageOperator::Agent::Safety::SafeExecutor.new(context)
74
+ executor.eval(code, file)
75
+ rescue Agent::Safety::SafeExecutor::SecurityError, Agent::Safety::ASTValidator::SecurityError => e
76
+ # Re-raise security errors so they're not silently ignored
77
+ warn "Error loading tool file #{file}: #{e.message}"
78
+ warn e.backtrace.join("\n")
79
+ raise e
80
+ rescue StandardError => e
81
+ warn "Error loading tool file #{file}: #{e.message}"
82
+ warn e.backtrace.join("\n")
83
+ end
84
+ end
85
+
86
+ # Reload all tools (hot reload)
87
+ #
88
+ # @return [void]
89
+ def reload
90
+ puts 'Reloading tools...'
91
+ load_tools
92
+ end
93
+
94
+ # Start an MCP server with loaded tools
95
+ #
96
+ # This class method creates a registry, loads tools from /mcp directory,
97
+ # wraps them as MCP::Tool classes, and starts an MCP server.
98
+ #
99
+ # Transport mode is automatically detected:
100
+ # - If PORT environment variable is set: HTTP server mode (for Kubernetes)
101
+ # - Otherwise: stdio transport mode (for local development)
102
+ #
103
+ # @param tools_dir [String] Directory containing tool definition files (default: '/mcp')
104
+ # @param server_name [String] Name of the MCP server (default: 'language-operator-tool')
105
+ # @return [void]
106
+ def self.start(tools_dir: '/mcp', server_name: 'language-operator-tool')
107
+ # Create registry and load tools
108
+ registry = LanguageOperator::Dsl::Registry.new
109
+ loader = new(registry, tools_dir)
110
+ loader.load_tools
111
+
112
+ # Convert DSL tools to MCP::Tool classes
113
+ mcp_tools = registry.all.map do |tool_def|
114
+ create_mcp_tool(tool_def)
115
+ end
116
+
117
+ # Create MCP server
118
+ server = MCP::Server.new(
119
+ name: server_name,
120
+ tools: mcp_tools
121
+ )
122
+
123
+ # Auto-detect transport mode based on PORT environment variable
124
+ if ENV['PORT']
125
+ start_http_server(server, mcp_tools.length, ENV['PORT'].to_i)
126
+ else
127
+ start_stdio_server(server, mcp_tools.length)
128
+ end
129
+ end
130
+
131
+ # Start MCP server in HTTP mode
132
+ #
133
+ # @param server [MCP::Server] The MCP server instance
134
+ # @param tool_count [Integer] Number of tools loaded
135
+ # @param port [Integer] Port to bind to
136
+ # @return [void]
137
+ def self.start_http_server(server, tool_count, port)
138
+ require 'rack'
139
+ require 'rackup'
140
+
141
+ # Create the Streamable HTTP transport
142
+ transport = MCP::Server::Transports::StreamableHTTPTransport.new(server)
143
+ server.transport = transport
144
+
145
+ # Create the Rack application
146
+ app = proc do |env|
147
+ request = Rack::Request.new(env)
148
+ transport.handle_request(request)
149
+ end
150
+
151
+ # Build the Rack application with middleware
152
+ rack_app = Rack::Builder.new do
153
+ use Rack::CommonLogger
154
+ use Rack::ShowExceptions
155
+ run app
156
+ end
157
+
158
+ puts "Starting MCP HTTP server on http://0.0.0.0:#{port}"
159
+ puts "Loaded #{tool_count} tools"
160
+
161
+ # Start the server with Puma
162
+ Rackup::Handler.get('puma').run(rack_app, Port: port, Host: '0.0.0.0')
163
+ end
164
+
165
+ # Start MCP server in stdio mode
166
+ #
167
+ # @param server [MCP::Server] The MCP server instance
168
+ # @param tool_count [Integer] Number of tools loaded
169
+ # @return [void]
170
+ def self.start_stdio_server(server, tool_count)
171
+ # Use stdio transport
172
+ transport = MCP::Server::Transports::StdioTransport.new(server)
173
+ puts "Starting MCP server with #{tool_count} tools (stdio mode)"
174
+ transport.open
175
+ end
176
+
177
+ # Convert a DSL tool definition to an MCP::Tool class
178
+ #
179
+ # @param tool_def [LanguageOperator::Dsl::ToolDefinition] Tool definition from DSL
180
+ # @return [Class] MCP::Tool subclass
181
+ # rubocop:disable Metrics/MethodLength
182
+ def self.create_mcp_tool(tool_def)
183
+ # Capture tool name and tracer for use in the dynamic class
184
+ tool_name = tool_def.name
185
+ tracer = OpenTelemetry.tracer_provider.tracer('language-operator-agent', LanguageOperator::VERSION)
186
+
187
+ # Create a dynamic MCP::Tool class
188
+ Class.new(MCP::Tool) do
189
+ description tool_def.description || "Tool: #{tool_def.name}"
190
+
191
+ # Build input schema from parameters
192
+ properties = {}
193
+ required_params = []
194
+
195
+ tool_def.parameters.each do |param_name, param_def|
196
+ properties[param_name] = {
197
+ type: param_def.type&.to_s || 'string',
198
+ description: param_def.description
199
+ }
200
+ required_params << param_name if param_def.required?
201
+ end
202
+
203
+ input_schema(
204
+ properties: properties,
205
+ required: required_params
206
+ )
207
+
208
+ # Store the execute block
209
+ @execute_block = tool_def.execute_block
210
+
211
+ # Define the call method with OpenTelemetry instrumentation
212
+ define_singleton_method(:call) do |**params|
213
+ tracer.in_span('agent.tool.execute', attributes: {
214
+ 'tool.name' => tool_name,
215
+ 'tool.type' => 'custom'
216
+ }) do |span|
217
+ # Execute the tool's block
218
+ result = @execute_block.call(params)
219
+
220
+ # Set success attribute
221
+ span.set_attribute('tool.result', 'success')
222
+
223
+ # Return MCP response
224
+ MCP::Tool::Response.new([
225
+ {
226
+ type: 'text',
227
+ text: result.to_s
228
+ }
229
+ ])
230
+ rescue StandardError => e
231
+ # Record exception and set failure status
232
+ span.record_exception(e)
233
+ span.set_attribute('tool.result', 'failure')
234
+ span.status = OpenTelemetry::Trace::Status.error(e.message)
235
+ raise
236
+ end
237
+ end
238
+ end
239
+ end
240
+ # rubocop:enable Metrics/MethodLength
241
+ end
242
+ end