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.
- checksums.yaml +4 -4
- data/.rubocop.yml +125 -0
- data/CHANGELOG.md +88 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +284 -0
- data/LICENSE +229 -21
- data/Makefile +82 -0
- data/README.md +3 -11
- data/Rakefile +63 -0
- data/bin/aictl +7 -0
- data/completions/_aictl +232 -0
- data/completions/aictl.bash +121 -0
- data/completions/aictl.fish +114 -0
- data/docs/architecture/agent-runtime.md +585 -0
- data/docs/dsl/SCHEMA_VERSION.md +250 -0
- data/docs/dsl/agent-reference.md +604 -0
- data/docs/dsl/best-practices.md +1078 -0
- data/docs/dsl/chat-endpoints.md +895 -0
- data/docs/dsl/constraints.md +671 -0
- data/docs/dsl/mcp-integration.md +1177 -0
- data/docs/dsl/webhooks.md +932 -0
- data/docs/dsl/workflows.md +744 -0
- data/lib/language_operator/agent/base.rb +110 -0
- data/lib/language_operator/agent/executor.rb +440 -0
- data/lib/language_operator/agent/instrumentation.rb +54 -0
- data/lib/language_operator/agent/metrics_tracker.rb +183 -0
- data/lib/language_operator/agent/safety/ast_validator.rb +272 -0
- data/lib/language_operator/agent/safety/audit_logger.rb +104 -0
- data/lib/language_operator/agent/safety/budget_tracker.rb +175 -0
- data/lib/language_operator/agent/safety/content_filter.rb +93 -0
- data/lib/language_operator/agent/safety/manager.rb +207 -0
- data/lib/language_operator/agent/safety/rate_limiter.rb +150 -0
- data/lib/language_operator/agent/safety/safe_executor.rb +127 -0
- data/lib/language_operator/agent/scheduler.rb +183 -0
- data/lib/language_operator/agent/telemetry.rb +116 -0
- data/lib/language_operator/agent/web_server.rb +610 -0
- data/lib/language_operator/agent/webhook_authenticator.rb +226 -0
- data/lib/language_operator/agent.rb +149 -0
- data/lib/language_operator/cli/commands/agent.rb +1205 -0
- data/lib/language_operator/cli/commands/cluster.rb +371 -0
- data/lib/language_operator/cli/commands/install.rb +404 -0
- data/lib/language_operator/cli/commands/model.rb +266 -0
- data/lib/language_operator/cli/commands/persona.rb +393 -0
- data/lib/language_operator/cli/commands/quickstart.rb +22 -0
- data/lib/language_operator/cli/commands/status.rb +143 -0
- data/lib/language_operator/cli/commands/system.rb +772 -0
- data/lib/language_operator/cli/commands/tool.rb +537 -0
- data/lib/language_operator/cli/commands/use.rb +47 -0
- data/lib/language_operator/cli/errors/handler.rb +180 -0
- data/lib/language_operator/cli/errors/suggestions.rb +176 -0
- data/lib/language_operator/cli/formatters/code_formatter.rb +77 -0
- data/lib/language_operator/cli/formatters/log_formatter.rb +288 -0
- data/lib/language_operator/cli/formatters/progress_formatter.rb +49 -0
- data/lib/language_operator/cli/formatters/status_formatter.rb +37 -0
- data/lib/language_operator/cli/formatters/table_formatter.rb +163 -0
- data/lib/language_operator/cli/formatters/value_formatter.rb +113 -0
- data/lib/language_operator/cli/helpers/cluster_context.rb +62 -0
- data/lib/language_operator/cli/helpers/cluster_validator.rb +101 -0
- data/lib/language_operator/cli/helpers/editor_helper.rb +58 -0
- data/lib/language_operator/cli/helpers/kubeconfig_validator.rb +167 -0
- data/lib/language_operator/cli/helpers/pastel_helper.rb +24 -0
- data/lib/language_operator/cli/helpers/resource_dependency_checker.rb +74 -0
- data/lib/language_operator/cli/helpers/schedule_builder.rb +108 -0
- data/lib/language_operator/cli/helpers/user_prompts.rb +69 -0
- data/lib/language_operator/cli/main.rb +236 -0
- data/lib/language_operator/cli/templates/tools/generic.yaml +66 -0
- data/lib/language_operator/cli/wizards/agent_wizard.rb +246 -0
- data/lib/language_operator/cli/wizards/quickstart_wizard.rb +588 -0
- data/lib/language_operator/client/base.rb +214 -0
- data/lib/language_operator/client/config.rb +136 -0
- data/lib/language_operator/client/cost_calculator.rb +37 -0
- data/lib/language_operator/client/mcp_connector.rb +123 -0
- data/lib/language_operator/client.rb +19 -0
- data/lib/language_operator/config/cluster_config.rb +101 -0
- data/lib/language_operator/config/tool_patterns.yaml +57 -0
- data/lib/language_operator/config/tool_registry.rb +96 -0
- data/lib/language_operator/config.rb +138 -0
- data/lib/language_operator/dsl/adapter.rb +124 -0
- data/lib/language_operator/dsl/agent_context.rb +90 -0
- data/lib/language_operator/dsl/agent_definition.rb +427 -0
- data/lib/language_operator/dsl/chat_endpoint_definition.rb +115 -0
- data/lib/language_operator/dsl/config.rb +119 -0
- data/lib/language_operator/dsl/context.rb +50 -0
- data/lib/language_operator/dsl/execution_context.rb +47 -0
- data/lib/language_operator/dsl/helpers.rb +109 -0
- data/lib/language_operator/dsl/http.rb +184 -0
- data/lib/language_operator/dsl/mcp_server_definition.rb +73 -0
- data/lib/language_operator/dsl/parameter_definition.rb +124 -0
- data/lib/language_operator/dsl/registry.rb +36 -0
- data/lib/language_operator/dsl/schema.rb +1102 -0
- data/lib/language_operator/dsl/shell.rb +125 -0
- data/lib/language_operator/dsl/tool_definition.rb +112 -0
- data/lib/language_operator/dsl/webhook_authentication.rb +114 -0
- data/lib/language_operator/dsl/webhook_definition.rb +106 -0
- data/lib/language_operator/dsl/workflow_definition.rb +259 -0
- data/lib/language_operator/dsl.rb +161 -0
- data/lib/language_operator/errors.rb +60 -0
- data/lib/language_operator/kubernetes/client.rb +279 -0
- data/lib/language_operator/kubernetes/resource_builder.rb +194 -0
- data/lib/language_operator/loggable.rb +47 -0
- data/lib/language_operator/logger.rb +141 -0
- data/lib/language_operator/retry.rb +123 -0
- data/lib/language_operator/retryable.rb +132 -0
- data/lib/language_operator/templates/README.md +23 -0
- data/lib/language_operator/templates/examples/agent_synthesis.tmpl +115 -0
- data/lib/language_operator/templates/examples/persona_distillation.tmpl +19 -0
- data/lib/language_operator/templates/schema/.gitkeep +0 -0
- data/lib/language_operator/templates/schema/CHANGELOG.md +93 -0
- data/lib/language_operator/templates/schema/agent_dsl_openapi.yaml +306 -0
- data/lib/language_operator/templates/schema/agent_dsl_schema.json +452 -0
- data/lib/language_operator/tool_loader.rb +242 -0
- data/lib/language_operator/validators.rb +170 -0
- data/lib/language_operator/version.rb +1 -1
- data/lib/language_operator.rb +65 -3
- data/requirements/tasks/challenge.md +9 -0
- data/requirements/tasks/iterate.md +36 -0
- data/requirements/tasks/optimize.md +21 -0
- data/requirements/tasks/tag.md +5 -0
- data/test_agent_dsl.rb +108 -0
- metadata +507 -20
|
@@ -0,0 +1,1177 @@
|
|
|
1
|
+
# MCP Integration Guide
|
|
2
|
+
|
|
3
|
+
Complete guide to using the Model Context Protocol (MCP) with Language Operator agents.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [Overview](#overview)
|
|
8
|
+
- [MCP Server: Exposing Tools](#mcp-server-exposing-tools)
|
|
9
|
+
- [Tool Definition](#tool-definition)
|
|
10
|
+
- [Parameter Definition](#parameter-definition)
|
|
11
|
+
- [Tool Execution](#tool-execution)
|
|
12
|
+
- [MCP Protocol Endpoints](#mcp-protocol-endpoints)
|
|
13
|
+
- [Complete Examples](#complete-examples)
|
|
14
|
+
- [Testing MCP Tools](#testing-mcp-tools)
|
|
15
|
+
- [Best Practices](#best-practices)
|
|
16
|
+
|
|
17
|
+
## Overview
|
|
18
|
+
|
|
19
|
+
The Model Context Protocol (MCP) enables agents to expose and consume tools in a standardized way. Language Operator provides two key MCP capabilities:
|
|
20
|
+
|
|
21
|
+
1. **MCP Server**: Agents can expose their own tools that other agents or MCP clients can call
|
|
22
|
+
2. **MCP Client**: Agents can connect to external MCP servers and use their tools (via configuration)
|
|
23
|
+
|
|
24
|
+
### What is MCP?
|
|
25
|
+
|
|
26
|
+
MCP is a standardized protocol for tool discovery and execution in LLM applications. It enables:
|
|
27
|
+
- Tool discovery via JSON-RPC protocol
|
|
28
|
+
- Type-safe parameter definitions with validation
|
|
29
|
+
- Remote tool execution
|
|
30
|
+
- Tool composition across different agents
|
|
31
|
+
|
|
32
|
+
### Key Concepts
|
|
33
|
+
|
|
34
|
+
- **MCP Server**: An agent that exposes tools via the MCP protocol
|
|
35
|
+
- **Tool**: A function that can be called with parameters and returns results
|
|
36
|
+
- **Parameter**: A typed input to a tool with validation rules
|
|
37
|
+
- **JSON-RPC**: The protocol used for MCP communication
|
|
38
|
+
|
|
39
|
+
## MCP Server: Exposing Tools
|
|
40
|
+
|
|
41
|
+
Any agent can act as an MCP server by defining tools in an `as_mcp_server` block.
|
|
42
|
+
|
|
43
|
+
### Basic MCP Server
|
|
44
|
+
|
|
45
|
+
```ruby
|
|
46
|
+
agent "data-processor" do
|
|
47
|
+
description "Data processing agent"
|
|
48
|
+
mode :reactive
|
|
49
|
+
|
|
50
|
+
as_mcp_server do
|
|
51
|
+
tool "process_csv" do
|
|
52
|
+
description "Process CSV data and return statistics"
|
|
53
|
+
|
|
54
|
+
parameter :csv_data do
|
|
55
|
+
type :string
|
|
56
|
+
required true
|
|
57
|
+
description "CSV data as string"
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
execute do |params|
|
|
61
|
+
# Tool implementation
|
|
62
|
+
lines = params['csv_data'].split("\n")
|
|
63
|
+
{ total_rows: lines.length }.to_json
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
**Key points:**
|
|
71
|
+
- Use `as_mcp_server` block to define MCP capabilities
|
|
72
|
+
- Agent automatically runs in `:reactive` mode when MCP server is defined
|
|
73
|
+
- MCP endpoint is automatically created at `/mcp`
|
|
74
|
+
- Tools are automatically registered and discoverable
|
|
75
|
+
|
|
76
|
+
### Custom Server Name
|
|
77
|
+
|
|
78
|
+
By default, the server name is `{agent-name}-mcp`. You can customize it:
|
|
79
|
+
|
|
80
|
+
```ruby
|
|
81
|
+
as_mcp_server do
|
|
82
|
+
name "custom-processor-server"
|
|
83
|
+
|
|
84
|
+
tool "my_tool" do
|
|
85
|
+
# Tool definition...
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Multiple Tools
|
|
91
|
+
|
|
92
|
+
Define multiple tools in one server:
|
|
93
|
+
|
|
94
|
+
```ruby
|
|
95
|
+
as_mcp_server do
|
|
96
|
+
tool "process_csv" do
|
|
97
|
+
description "Process CSV data"
|
|
98
|
+
# ...
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
tool "calculate_stats" do
|
|
102
|
+
description "Calculate statistics"
|
|
103
|
+
# ...
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
tool "format_json" do
|
|
107
|
+
description "Format and validate JSON"
|
|
108
|
+
# ...
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Tool Definition
|
|
114
|
+
|
|
115
|
+
Tools are the core of MCP integration. Each tool has a name, description, parameters, and execution logic.
|
|
116
|
+
|
|
117
|
+
### Basic Tool Structure
|
|
118
|
+
|
|
119
|
+
```ruby
|
|
120
|
+
tool "tool_name" do
|
|
121
|
+
description "What this tool does"
|
|
122
|
+
|
|
123
|
+
parameter :param_name do
|
|
124
|
+
type :string
|
|
125
|
+
required true
|
|
126
|
+
description "What this parameter is for"
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
execute do |params|
|
|
130
|
+
# Tool logic here
|
|
131
|
+
# Return value (string, hash, array, etc.)
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Tool Components
|
|
137
|
+
|
|
138
|
+
**Name** (String)
|
|
139
|
+
- Unique identifier for the tool
|
|
140
|
+
- Used in MCP protocol and API calls
|
|
141
|
+
- Convention: lowercase with underscores
|
|
142
|
+
|
|
143
|
+
**Description** (String)
|
|
144
|
+
- Human-readable description of tool functionality
|
|
145
|
+
- Should explain what the tool does and when to use it
|
|
146
|
+
- Helps LLMs decide when to call the tool
|
|
147
|
+
|
|
148
|
+
**Parameters** (Block)
|
|
149
|
+
- Define inputs the tool accepts
|
|
150
|
+
- Each parameter has type, validation, and metadata
|
|
151
|
+
- See [Parameter Definition](#parameter-definition) below
|
|
152
|
+
|
|
153
|
+
**Execute Block** (Proc)
|
|
154
|
+
- Contains the tool's implementation logic
|
|
155
|
+
- Receives `params` hash with validated parameters
|
|
156
|
+
- Should return a value (string, hash, array, etc.)
|
|
157
|
+
|
|
158
|
+
### Tool Examples
|
|
159
|
+
|
|
160
|
+
**Simple calculation tool:**
|
|
161
|
+
```ruby
|
|
162
|
+
tool "add" do
|
|
163
|
+
description "Add two numbers together"
|
|
164
|
+
|
|
165
|
+
parameter :a do
|
|
166
|
+
type :number
|
|
167
|
+
required true
|
|
168
|
+
description "First number"
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
parameter :b do
|
|
172
|
+
type :number
|
|
173
|
+
required true
|
|
174
|
+
description "Second number"
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
execute do |params|
|
|
178
|
+
(params['a'] + params['b']).to_s
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
**Data transformation tool:**
|
|
184
|
+
```ruby
|
|
185
|
+
tool "transform_json" do
|
|
186
|
+
description "Transform JSON data according to mapping rules"
|
|
187
|
+
|
|
188
|
+
parameter :data do
|
|
189
|
+
type :object
|
|
190
|
+
required true
|
|
191
|
+
description "Input JSON data"
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
parameter :mapping do
|
|
195
|
+
type :object
|
|
196
|
+
required true
|
|
197
|
+
description "Transformation mapping rules"
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
execute do |params|
|
|
201
|
+
data = params['data']
|
|
202
|
+
mapping = params['mapping']
|
|
203
|
+
|
|
204
|
+
result = {}
|
|
205
|
+
mapping.each do |source_key, target_key|
|
|
206
|
+
result[target_key] = data[source_key] if data.key?(source_key)
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
result.to_json
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
**HTTP API tool:**
|
|
215
|
+
```ruby
|
|
216
|
+
tool "fetch_user" do
|
|
217
|
+
description "Fetch user data from API by ID"
|
|
218
|
+
|
|
219
|
+
parameter :user_id do
|
|
220
|
+
type :string
|
|
221
|
+
required true
|
|
222
|
+
description "User ID to fetch"
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
parameter :include_profile do
|
|
226
|
+
type :boolean
|
|
227
|
+
required false
|
|
228
|
+
description "Include full profile data"
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
execute do |params|
|
|
232
|
+
require 'net/http'
|
|
233
|
+
require 'json'
|
|
234
|
+
|
|
235
|
+
url = "https://api.example.com/users/#{params['user_id']}"
|
|
236
|
+
url += "?include_profile=true" if params['include_profile']
|
|
237
|
+
|
|
238
|
+
response = Net::HTTP.get(URI(url))
|
|
239
|
+
JSON.parse(response).to_json
|
|
240
|
+
rescue StandardError => e
|
|
241
|
+
{ error: e.message }.to_json
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
## Parameter Definition
|
|
247
|
+
|
|
248
|
+
Parameters define the inputs a tool accepts, with type safety and validation.
|
|
249
|
+
|
|
250
|
+
### Parameter Types
|
|
251
|
+
|
|
252
|
+
Language Operator supports the following parameter types:
|
|
253
|
+
|
|
254
|
+
```ruby
|
|
255
|
+
parameter :string_param do
|
|
256
|
+
type :string # Text values
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
parameter :number_param do
|
|
260
|
+
type :number # Numeric values (integers or floats)
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
parameter :integer_param do
|
|
264
|
+
type :integer # Integer values only
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
parameter :boolean_param do
|
|
268
|
+
type :boolean # true/false values
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
parameter :array_param do
|
|
272
|
+
type :array # Array/list values
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
parameter :object_param do
|
|
276
|
+
type :object # Hash/object values
|
|
277
|
+
end
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
### Required vs Optional
|
|
281
|
+
|
|
282
|
+
```ruby
|
|
283
|
+
parameter :required_param do
|
|
284
|
+
type :string
|
|
285
|
+
required true # Must be provided
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
parameter :optional_param do
|
|
289
|
+
type :string
|
|
290
|
+
required false # Can be omitted
|
|
291
|
+
end
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
**Behavior:**
|
|
295
|
+
- Required parameters: Tool execution fails if not provided
|
|
296
|
+
- Optional parameters: Can be omitted, defaults to `nil` unless default set
|
|
297
|
+
|
|
298
|
+
### Default Values
|
|
299
|
+
|
|
300
|
+
```ruby
|
|
301
|
+
parameter :timeout do
|
|
302
|
+
type :number
|
|
303
|
+
required false
|
|
304
|
+
default 30 # Used if parameter not provided
|
|
305
|
+
description "Timeout in seconds (default: 30)"
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
parameter :format do
|
|
309
|
+
type :string
|
|
310
|
+
required false
|
|
311
|
+
default "json"
|
|
312
|
+
description "Output format: json, xml, or csv"
|
|
313
|
+
end
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
### Enum Values
|
|
317
|
+
|
|
318
|
+
Restrict parameter to specific allowed values:
|
|
319
|
+
|
|
320
|
+
```ruby
|
|
321
|
+
parameter :status do
|
|
322
|
+
type :string
|
|
323
|
+
required true
|
|
324
|
+
enum ["active", "inactive", "pending"]
|
|
325
|
+
description "User status"
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
parameter :log_level do
|
|
329
|
+
type :string
|
|
330
|
+
required false
|
|
331
|
+
default "info"
|
|
332
|
+
enum ["debug", "info", "warn", "error"]
|
|
333
|
+
description "Log level for output"
|
|
334
|
+
end
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
**Behavior:**
|
|
338
|
+
- Parameter value must match one of the enum values
|
|
339
|
+
- Validation error raised if value not in enum
|
|
340
|
+
|
|
341
|
+
### Parameter Validation
|
|
342
|
+
|
|
343
|
+
**Built-in validators:**
|
|
344
|
+
|
|
345
|
+
```ruby
|
|
346
|
+
# URL format validation
|
|
347
|
+
parameter :website do
|
|
348
|
+
type :string
|
|
349
|
+
required true
|
|
350
|
+
url_format # Validates http:// or https:// URLs
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
# Email format validation
|
|
354
|
+
parameter :email do
|
|
355
|
+
type :string
|
|
356
|
+
required true
|
|
357
|
+
email_format # Validates email address format
|
|
358
|
+
end
|
|
359
|
+
|
|
360
|
+
# Phone format validation
|
|
361
|
+
parameter :phone do
|
|
362
|
+
type :string
|
|
363
|
+
required true
|
|
364
|
+
phone_format # Validates +1234567890 format
|
|
365
|
+
end
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
**Regex validators:**
|
|
369
|
+
|
|
370
|
+
```ruby
|
|
371
|
+
parameter :zip_code do
|
|
372
|
+
type :string
|
|
373
|
+
required true
|
|
374
|
+
validate /^\d{5}(-\d{4})?$/ # US ZIP code format
|
|
375
|
+
description "US ZIP code (5 or 9 digits)"
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
parameter :product_code do
|
|
379
|
+
type :string
|
|
380
|
+
required true
|
|
381
|
+
validate /^[A-Z]{3}-\d{4}$/ # Custom format
|
|
382
|
+
description "Product code (e.g., ABC-1234)"
|
|
383
|
+
end
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
**Custom validators (Proc):**
|
|
387
|
+
|
|
388
|
+
```ruby
|
|
389
|
+
parameter :age do
|
|
390
|
+
type :number
|
|
391
|
+
required true
|
|
392
|
+
validate ->(value) {
|
|
393
|
+
if value < 0 || value > 150
|
|
394
|
+
"Age must be between 0 and 150"
|
|
395
|
+
else
|
|
396
|
+
true
|
|
397
|
+
end
|
|
398
|
+
}
|
|
399
|
+
description "Person's age"
|
|
400
|
+
end
|
|
401
|
+
|
|
402
|
+
parameter :username do
|
|
403
|
+
type :string
|
|
404
|
+
required true
|
|
405
|
+
validate ->(value) {
|
|
406
|
+
return "Username too short" if value.length < 3
|
|
407
|
+
return "Username too long" if value.length > 20
|
|
408
|
+
return "Username must be alphanumeric" unless value.match?(/^[a-zA-Z0-9_]+$/)
|
|
409
|
+
true
|
|
410
|
+
}
|
|
411
|
+
description "Username (3-20 alphanumeric characters)"
|
|
412
|
+
end
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
**Validation behavior:**
|
|
416
|
+
- Validators run before tool execution
|
|
417
|
+
- If validation fails, tool execution is prevented
|
|
418
|
+
- Error message is returned to caller
|
|
419
|
+
- Custom validators can return `String` (error message) or `Boolean`
|
|
420
|
+
|
|
421
|
+
### Complete Parameter Examples
|
|
422
|
+
|
|
423
|
+
**Simple required parameter:**
|
|
424
|
+
```ruby
|
|
425
|
+
parameter :message do
|
|
426
|
+
type :string
|
|
427
|
+
required true
|
|
428
|
+
description "Message to send"
|
|
429
|
+
end
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
**Optional parameter with default:**
|
|
433
|
+
```ruby
|
|
434
|
+
parameter :retries do
|
|
435
|
+
type :number
|
|
436
|
+
required false
|
|
437
|
+
default 3
|
|
438
|
+
description "Number of retry attempts (default: 3)"
|
|
439
|
+
end
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
**Enum with default:**
|
|
443
|
+
```ruby
|
|
444
|
+
parameter :environment do
|
|
445
|
+
type :string
|
|
446
|
+
required false
|
|
447
|
+
default "production"
|
|
448
|
+
enum ["development", "staging", "production"]
|
|
449
|
+
description "Deployment environment"
|
|
450
|
+
end
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
**Validated parameter:**
|
|
454
|
+
```ruby
|
|
455
|
+
parameter :api_key do
|
|
456
|
+
type :string
|
|
457
|
+
required true
|
|
458
|
+
validate /^[A-Za-z0-9_-]{32,}$/ # At least 32 alphanumeric chars
|
|
459
|
+
description "API authentication key"
|
|
460
|
+
end
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
## Tool Execution
|
|
464
|
+
|
|
465
|
+
The `execute` block contains the tool's implementation logic.
|
|
466
|
+
|
|
467
|
+
### Execute Block Basics
|
|
468
|
+
|
|
469
|
+
```ruby
|
|
470
|
+
execute do |params|
|
|
471
|
+
# Access parameters
|
|
472
|
+
input = params['input_param']
|
|
473
|
+
|
|
474
|
+
# Perform logic
|
|
475
|
+
result = process(input)
|
|
476
|
+
|
|
477
|
+
# Return result (any JSON-serializable value)
|
|
478
|
+
result
|
|
479
|
+
end
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
**Key points:**
|
|
483
|
+
- Receives `params` hash with validated parameter values
|
|
484
|
+
- Parameter names are strings (not symbols)
|
|
485
|
+
- Should return a value (string, number, hash, array, etc.)
|
|
486
|
+
- Exceptions are caught and returned as errors
|
|
487
|
+
|
|
488
|
+
### Accessing Parameters
|
|
489
|
+
|
|
490
|
+
Parameters are passed as a hash with string keys:
|
|
491
|
+
|
|
492
|
+
```ruby
|
|
493
|
+
tool "greet" do
|
|
494
|
+
parameter :name do
|
|
495
|
+
type :string
|
|
496
|
+
required true
|
|
497
|
+
end
|
|
498
|
+
|
|
499
|
+
parameter :greeting do
|
|
500
|
+
type :string
|
|
501
|
+
required false
|
|
502
|
+
default "Hello"
|
|
503
|
+
end
|
|
504
|
+
|
|
505
|
+
execute do |params|
|
|
506
|
+
name = params['name']
|
|
507
|
+
greeting = params['greeting']
|
|
508
|
+
"#{greeting}, #{name}!"
|
|
509
|
+
end
|
|
510
|
+
end
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
### Return Values
|
|
514
|
+
|
|
515
|
+
Tools can return various types:
|
|
516
|
+
|
|
517
|
+
**String:**
|
|
518
|
+
```ruby
|
|
519
|
+
execute do |params|
|
|
520
|
+
"Result: #{params['value']}"
|
|
521
|
+
end
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
**Number:**
|
|
525
|
+
```ruby
|
|
526
|
+
execute do |params|
|
|
527
|
+
params['a'] + params['b']
|
|
528
|
+
end
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
**Hash (returned as JSON):**
|
|
532
|
+
```ruby
|
|
533
|
+
execute do |params|
|
|
534
|
+
{
|
|
535
|
+
success: true,
|
|
536
|
+
result: "processed",
|
|
537
|
+
timestamp: Time.now.iso8601
|
|
538
|
+
}.to_json
|
|
539
|
+
end
|
|
540
|
+
```
|
|
541
|
+
|
|
542
|
+
**Array:**
|
|
543
|
+
```ruby
|
|
544
|
+
execute do |params|
|
|
545
|
+
[1, 2, 3, 4, 5].to_json
|
|
546
|
+
end
|
|
547
|
+
```
|
|
548
|
+
|
|
549
|
+
### Error Handling
|
|
550
|
+
|
|
551
|
+
Handle errors gracefully in execute blocks:
|
|
552
|
+
|
|
553
|
+
```ruby
|
|
554
|
+
execute do |params|
|
|
555
|
+
begin
|
|
556
|
+
# Risky operation
|
|
557
|
+
result = external_api_call(params['data'])
|
|
558
|
+
{ success: true, result: result }.to_json
|
|
559
|
+
rescue StandardError => e
|
|
560
|
+
{
|
|
561
|
+
success: false,
|
|
562
|
+
error: e.message,
|
|
563
|
+
error_type: e.class.name
|
|
564
|
+
}.to_json
|
|
565
|
+
end
|
|
566
|
+
end
|
|
567
|
+
```
|
|
568
|
+
|
|
569
|
+
**Best practices:**
|
|
570
|
+
- Always rescue exceptions in execute blocks
|
|
571
|
+
- Return error information in a consistent format
|
|
572
|
+
- Include error type and message for debugging
|
|
573
|
+
- Log errors for monitoring
|
|
574
|
+
|
|
575
|
+
### External Dependencies
|
|
576
|
+
|
|
577
|
+
Tools can use external libraries and services:
|
|
578
|
+
|
|
579
|
+
```ruby
|
|
580
|
+
tool "send_email" do
|
|
581
|
+
parameter :to do
|
|
582
|
+
type :string
|
|
583
|
+
required true
|
|
584
|
+
email_format
|
|
585
|
+
end
|
|
586
|
+
|
|
587
|
+
parameter :subject do
|
|
588
|
+
type :string
|
|
589
|
+
required true
|
|
590
|
+
end
|
|
591
|
+
|
|
592
|
+
parameter :body do
|
|
593
|
+
type :string
|
|
594
|
+
required true
|
|
595
|
+
end
|
|
596
|
+
|
|
597
|
+
execute do |params|
|
|
598
|
+
require 'mail'
|
|
599
|
+
|
|
600
|
+
Mail.deliver do
|
|
601
|
+
to params['to']
|
|
602
|
+
from ENV['SMTP_FROM']
|
|
603
|
+
subject params['subject']
|
|
604
|
+
body params['body']
|
|
605
|
+
end
|
|
606
|
+
|
|
607
|
+
{ success: true, sent_at: Time.now.iso8601 }.to_json
|
|
608
|
+
rescue StandardError => e
|
|
609
|
+
{ success: false, error: e.message }.to_json
|
|
610
|
+
end
|
|
611
|
+
end
|
|
612
|
+
```
|
|
613
|
+
|
|
614
|
+
## MCP Protocol Endpoints
|
|
615
|
+
|
|
616
|
+
When an agent acts as an MCP server, it automatically exposes MCP protocol endpoints.
|
|
617
|
+
|
|
618
|
+
### Automatic Endpoints
|
|
619
|
+
|
|
620
|
+
**MCP Protocol** - `POST /mcp`
|
|
621
|
+
- JSON-RPC 2.0 endpoint for tool discovery and execution
|
|
622
|
+
- Supports standard MCP methods: `tools/list`, `tools/call`
|
|
623
|
+
|
|
624
|
+
**Webhook** - `POST /webhook`
|
|
625
|
+
- Standard webhook endpoint (if defined)
|
|
626
|
+
- Can coexist with MCP server functionality
|
|
627
|
+
|
|
628
|
+
**Health Check** - `GET /health`
|
|
629
|
+
- Returns server health status
|
|
630
|
+
- Always available
|
|
631
|
+
|
|
632
|
+
**Readiness Check** - `GET /ready`
|
|
633
|
+
- Returns readiness status
|
|
634
|
+
- Always available
|
|
635
|
+
|
|
636
|
+
### MCP Protocol Methods
|
|
637
|
+
|
|
638
|
+
**List Tools** - `tools/list`
|
|
639
|
+
|
|
640
|
+
Request:
|
|
641
|
+
```json
|
|
642
|
+
{
|
|
643
|
+
"jsonrpc": "2.0",
|
|
644
|
+
"method": "tools/list",
|
|
645
|
+
"id": 1
|
|
646
|
+
}
|
|
647
|
+
```
|
|
648
|
+
|
|
649
|
+
Response:
|
|
650
|
+
```json
|
|
651
|
+
{
|
|
652
|
+
"jsonrpc": "2.0",
|
|
653
|
+
"id": 1,
|
|
654
|
+
"result": {
|
|
655
|
+
"tools": [
|
|
656
|
+
{
|
|
657
|
+
"name": "process_csv",
|
|
658
|
+
"description": "Process CSV data and return statistics",
|
|
659
|
+
"inputSchema": {
|
|
660
|
+
"type": "object",
|
|
661
|
+
"properties": {
|
|
662
|
+
"csv_data": {
|
|
663
|
+
"type": "string",
|
|
664
|
+
"description": "CSV data as string"
|
|
665
|
+
}
|
|
666
|
+
},
|
|
667
|
+
"required": ["csv_data"]
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
]
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
```
|
|
674
|
+
|
|
675
|
+
**Call Tool** - `tools/call`
|
|
676
|
+
|
|
677
|
+
Request:
|
|
678
|
+
```json
|
|
679
|
+
{
|
|
680
|
+
"jsonrpc": "2.0",
|
|
681
|
+
"method": "tools/call",
|
|
682
|
+
"params": {
|
|
683
|
+
"name": "process_csv",
|
|
684
|
+
"arguments": {
|
|
685
|
+
"csv_data": "name,age\nAlice,30\nBob,25"
|
|
686
|
+
}
|
|
687
|
+
},
|
|
688
|
+
"id": 2
|
|
689
|
+
}
|
|
690
|
+
```
|
|
691
|
+
|
|
692
|
+
Response:
|
|
693
|
+
```json
|
|
694
|
+
{
|
|
695
|
+
"jsonrpc": "2.0",
|
|
696
|
+
"id": 2,
|
|
697
|
+
"result": {
|
|
698
|
+
"content": [
|
|
699
|
+
{
|
|
700
|
+
"type": "text",
|
|
701
|
+
"text": "{\"total_rows\":2}"
|
|
702
|
+
}
|
|
703
|
+
]
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
```
|
|
707
|
+
|
|
708
|
+
## Complete Examples
|
|
709
|
+
|
|
710
|
+
### Data Processing MCP Server
|
|
711
|
+
|
|
712
|
+
```ruby
|
|
713
|
+
agent "data-processor-mcp" do
|
|
714
|
+
description "Data processing agent with MCP tools"
|
|
715
|
+
mode :reactive
|
|
716
|
+
|
|
717
|
+
as_mcp_server do
|
|
718
|
+
# Tool 1: Process CSV
|
|
719
|
+
tool "process_csv" do
|
|
720
|
+
description "Process CSV data and return summary statistics"
|
|
721
|
+
|
|
722
|
+
parameter :csv_data do
|
|
723
|
+
type :string
|
|
724
|
+
required true
|
|
725
|
+
description "CSV data as string"
|
|
726
|
+
end
|
|
727
|
+
|
|
728
|
+
execute do |params|
|
|
729
|
+
lines = params['csv_data'].split("\n")
|
|
730
|
+
headers = lines.first&.split(',') || []
|
|
731
|
+
data_rows = lines[1..]
|
|
732
|
+
|
|
733
|
+
{
|
|
734
|
+
total_rows: data_rows&.length || 0,
|
|
735
|
+
total_columns: headers.length,
|
|
736
|
+
headers: headers,
|
|
737
|
+
sample: data_rows&.first || ''
|
|
738
|
+
}.to_json
|
|
739
|
+
end
|
|
740
|
+
end
|
|
741
|
+
|
|
742
|
+
# Tool 2: Calculate statistics
|
|
743
|
+
tool "calculate_stats" do
|
|
744
|
+
description "Calculate basic statistics for a list of numbers"
|
|
745
|
+
|
|
746
|
+
parameter :numbers do
|
|
747
|
+
type :array
|
|
748
|
+
required true
|
|
749
|
+
description "Array of numbers"
|
|
750
|
+
end
|
|
751
|
+
|
|
752
|
+
execute do |params|
|
|
753
|
+
nums = params['numbers']
|
|
754
|
+
return { error: 'Empty array' }.to_json if nums.empty?
|
|
755
|
+
|
|
756
|
+
sum = nums.sum
|
|
757
|
+
mean = sum.to_f / nums.length
|
|
758
|
+
sorted = nums.sort
|
|
759
|
+
median = if nums.length.odd?
|
|
760
|
+
sorted[nums.length / 2]
|
|
761
|
+
else
|
|
762
|
+
(sorted[(nums.length / 2) - 1] + sorted[nums.length / 2]) / 2.0
|
|
763
|
+
end
|
|
764
|
+
|
|
765
|
+
{
|
|
766
|
+
count: nums.length,
|
|
767
|
+
sum: sum,
|
|
768
|
+
mean: mean,
|
|
769
|
+
median: median,
|
|
770
|
+
min: nums.min,
|
|
771
|
+
max: nums.max
|
|
772
|
+
}.to_json
|
|
773
|
+
end
|
|
774
|
+
end
|
|
775
|
+
|
|
776
|
+
# Tool 3: Format JSON
|
|
777
|
+
tool "format_json" do
|
|
778
|
+
description "Format and validate JSON data"
|
|
779
|
+
|
|
780
|
+
parameter :json_string do
|
|
781
|
+
type :string
|
|
782
|
+
required true
|
|
783
|
+
description "JSON string to format"
|
|
784
|
+
end
|
|
785
|
+
|
|
786
|
+
parameter :indent do
|
|
787
|
+
type :number
|
|
788
|
+
required false
|
|
789
|
+
default 2
|
|
790
|
+
description "Indentation spaces (default: 2)"
|
|
791
|
+
end
|
|
792
|
+
|
|
793
|
+
execute do |params|
|
|
794
|
+
indent = params['indent'] || 2
|
|
795
|
+
parsed = JSON.parse(params['json_string'])
|
|
796
|
+
JSON.pretty_generate(parsed, indent: ' ' * indent.to_i)
|
|
797
|
+
rescue JSON::ParserError => e
|
|
798
|
+
{ error: "Invalid JSON: #{e.message}" }.to_json
|
|
799
|
+
end
|
|
800
|
+
end
|
|
801
|
+
end
|
|
802
|
+
|
|
803
|
+
# Optional: Also expose webhook endpoint
|
|
804
|
+
webhook "/process" do
|
|
805
|
+
method :post
|
|
806
|
+
on_request do |_context|
|
|
807
|
+
{
|
|
808
|
+
status: 'processed',
|
|
809
|
+
tools_available: 3,
|
|
810
|
+
mcp_endpoint: '/mcp'
|
|
811
|
+
}
|
|
812
|
+
end
|
|
813
|
+
end
|
|
814
|
+
end
|
|
815
|
+
```
|
|
816
|
+
|
|
817
|
+
### Text Processing MCP Server
|
|
818
|
+
|
|
819
|
+
```ruby
|
|
820
|
+
agent "text-processor" do
|
|
821
|
+
description "Text processing and transformation tools"
|
|
822
|
+
mode :reactive
|
|
823
|
+
|
|
824
|
+
as_mcp_server do
|
|
825
|
+
name "text-tools-server"
|
|
826
|
+
|
|
827
|
+
tool "word_count" do
|
|
828
|
+
description "Count words, characters, and lines in text"
|
|
829
|
+
|
|
830
|
+
parameter :text do
|
|
831
|
+
type :string
|
|
832
|
+
required true
|
|
833
|
+
description "Text to analyze"
|
|
834
|
+
end
|
|
835
|
+
|
|
836
|
+
execute do |params|
|
|
837
|
+
text = params['text']
|
|
838
|
+
{
|
|
839
|
+
characters: text.length,
|
|
840
|
+
words: text.split.length,
|
|
841
|
+
lines: text.lines.count,
|
|
842
|
+
paragraphs: text.split("\n\n").length
|
|
843
|
+
}.to_json
|
|
844
|
+
end
|
|
845
|
+
end
|
|
846
|
+
|
|
847
|
+
tool "case_transform" do
|
|
848
|
+
description "Transform text case"
|
|
849
|
+
|
|
850
|
+
parameter :text do
|
|
851
|
+
type :string
|
|
852
|
+
required true
|
|
853
|
+
description "Text to transform"
|
|
854
|
+
end
|
|
855
|
+
|
|
856
|
+
parameter :format do
|
|
857
|
+
type :string
|
|
858
|
+
required true
|
|
859
|
+
enum ["uppercase", "lowercase", "titlecase", "capitalize"]
|
|
860
|
+
description "Target format"
|
|
861
|
+
end
|
|
862
|
+
|
|
863
|
+
execute do |params|
|
|
864
|
+
text = params['text']
|
|
865
|
+
case params['format']
|
|
866
|
+
when "uppercase"
|
|
867
|
+
text.upcase
|
|
868
|
+
when "lowercase"
|
|
869
|
+
text.downcase
|
|
870
|
+
when "titlecase"
|
|
871
|
+
text.split.map(&:capitalize).join(' ')
|
|
872
|
+
when "capitalize"
|
|
873
|
+
text.capitalize
|
|
874
|
+
else
|
|
875
|
+
text
|
|
876
|
+
end
|
|
877
|
+
end
|
|
878
|
+
end
|
|
879
|
+
|
|
880
|
+
tool "extract_emails" do
|
|
881
|
+
description "Extract email addresses from text"
|
|
882
|
+
|
|
883
|
+
parameter :text do
|
|
884
|
+
type :string
|
|
885
|
+
required true
|
|
886
|
+
description "Text to scan for emails"
|
|
887
|
+
end
|
|
888
|
+
|
|
889
|
+
execute do |params|
|
|
890
|
+
email_regex = /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/i
|
|
891
|
+
emails = params['text'].scan(email_regex).uniq
|
|
892
|
+
{ emails: emails, count: emails.length }.to_json
|
|
893
|
+
end
|
|
894
|
+
end
|
|
895
|
+
end
|
|
896
|
+
|
|
897
|
+
constraints do
|
|
898
|
+
timeout '30s'
|
|
899
|
+
requests_per_minute 60
|
|
900
|
+
end
|
|
901
|
+
end
|
|
902
|
+
```
|
|
903
|
+
|
|
904
|
+
### API Integration MCP Server
|
|
905
|
+
|
|
906
|
+
```ruby
|
|
907
|
+
agent "weather-api-server" do
|
|
908
|
+
description "Weather data API integration"
|
|
909
|
+
mode :reactive
|
|
910
|
+
|
|
911
|
+
as_mcp_server do
|
|
912
|
+
tool "get_current_weather" do
|
|
913
|
+
description "Get current weather for a location"
|
|
914
|
+
|
|
915
|
+
parameter :location do
|
|
916
|
+
type :string
|
|
917
|
+
required true
|
|
918
|
+
description "City name or ZIP code"
|
|
919
|
+
end
|
|
920
|
+
|
|
921
|
+
parameter :units do
|
|
922
|
+
type :string
|
|
923
|
+
required false
|
|
924
|
+
default "metric"
|
|
925
|
+
enum ["metric", "imperial"]
|
|
926
|
+
description "Temperature units"
|
|
927
|
+
end
|
|
928
|
+
|
|
929
|
+
execute do |params|
|
|
930
|
+
require 'net/http'
|
|
931
|
+
require 'json'
|
|
932
|
+
|
|
933
|
+
api_key = ENV['WEATHER_API_KEY']
|
|
934
|
+
location = params['location']
|
|
935
|
+
units = params['units'] || 'metric'
|
|
936
|
+
|
|
937
|
+
url = "https://api.openweathermap.org/data/2.5/weather?q=#{location}&units=#{units}&appid=#{api_key}"
|
|
938
|
+
response = Net::HTTP.get(URI(url))
|
|
939
|
+
data = JSON.parse(response)
|
|
940
|
+
|
|
941
|
+
{
|
|
942
|
+
location: data['name'],
|
|
943
|
+
temperature: data['main']['temp'],
|
|
944
|
+
feels_like: data['main']['feels_like'],
|
|
945
|
+
humidity: data['main']['humidity'],
|
|
946
|
+
description: data['weather'].first['description'],
|
|
947
|
+
units: units
|
|
948
|
+
}.to_json
|
|
949
|
+
rescue StandardError => e
|
|
950
|
+
{ error: e.message }.to_json
|
|
951
|
+
end
|
|
952
|
+
end
|
|
953
|
+
|
|
954
|
+
tool "get_forecast" do
|
|
955
|
+
description "Get 5-day weather forecast"
|
|
956
|
+
|
|
957
|
+
parameter :location do
|
|
958
|
+
type :string
|
|
959
|
+
required true
|
|
960
|
+
description "City name or ZIP code"
|
|
961
|
+
end
|
|
962
|
+
|
|
963
|
+
execute do |params|
|
|
964
|
+
require 'net/http'
|
|
965
|
+
require 'json'
|
|
966
|
+
|
|
967
|
+
api_key = ENV['WEATHER_API_KEY']
|
|
968
|
+
url = "https://api.openweathermap.org/data/2.5/forecast?q=#{params['location']}&appid=#{api_key}"
|
|
969
|
+
response = Net::HTTP.get(URI(url))
|
|
970
|
+
data = JSON.parse(response)
|
|
971
|
+
|
|
972
|
+
forecast = data['list'].map do |item|
|
|
973
|
+
{
|
|
974
|
+
datetime: item['dt_txt'],
|
|
975
|
+
temperature: item['main']['temp'],
|
|
976
|
+
description: item['weather'].first['description']
|
|
977
|
+
}
|
|
978
|
+
end
|
|
979
|
+
|
|
980
|
+
{ location: data['city']['name'], forecast: forecast }.to_json
|
|
981
|
+
rescue StandardError => e
|
|
982
|
+
{ error: e.message }.to_json
|
|
983
|
+
end
|
|
984
|
+
end
|
|
985
|
+
end
|
|
986
|
+
|
|
987
|
+
constraints do
|
|
988
|
+
timeout '10s'
|
|
989
|
+
requests_per_minute 30 # Respect API rate limits
|
|
990
|
+
daily_budget 500 # $5/day
|
|
991
|
+
end
|
|
992
|
+
end
|
|
993
|
+
```
|
|
994
|
+
|
|
995
|
+
## Testing MCP Tools
|
|
996
|
+
|
|
997
|
+
### Using curl
|
|
998
|
+
|
|
999
|
+
**List available tools:**
|
|
1000
|
+
```bash
|
|
1001
|
+
curl -X POST http://localhost:8080/mcp \
|
|
1002
|
+
-H "Content-Type: application/json" \
|
|
1003
|
+
-d '{
|
|
1004
|
+
"jsonrpc": "2.0",
|
|
1005
|
+
"method": "tools/list",
|
|
1006
|
+
"id": 1
|
|
1007
|
+
}'
|
|
1008
|
+
```
|
|
1009
|
+
|
|
1010
|
+
**Call a tool:**
|
|
1011
|
+
```bash
|
|
1012
|
+
curl -X POST http://localhost:8080/mcp \
|
|
1013
|
+
-H "Content-Type: application/json" \
|
|
1014
|
+
-d '{
|
|
1015
|
+
"jsonrpc": "2.0",
|
|
1016
|
+
"method": "tools/call",
|
|
1017
|
+
"params": {
|
|
1018
|
+
"name": "process_csv",
|
|
1019
|
+
"arguments": {
|
|
1020
|
+
"csv_data": "name,age\nAlice,30\nBob,25"
|
|
1021
|
+
}
|
|
1022
|
+
},
|
|
1023
|
+
"id": 2
|
|
1024
|
+
}'
|
|
1025
|
+
```
|
|
1026
|
+
|
|
1027
|
+
### Using RSpec
|
|
1028
|
+
|
|
1029
|
+
```ruby
|
|
1030
|
+
require 'spec_helper'
|
|
1031
|
+
|
|
1032
|
+
RSpec.describe 'MCP Tools' do
|
|
1033
|
+
let(:mcp_def) { LanguageOperator::Dsl::McpServerDefinition.new('test-agent') }
|
|
1034
|
+
|
|
1035
|
+
before do
|
|
1036
|
+
mcp_def.tool('greet') do
|
|
1037
|
+
description 'Greet a user'
|
|
1038
|
+
parameter :name do
|
|
1039
|
+
type :string
|
|
1040
|
+
required true
|
|
1041
|
+
end
|
|
1042
|
+
execute do |params|
|
|
1043
|
+
"Hello, #{params['name']}!"
|
|
1044
|
+
end
|
|
1045
|
+
end
|
|
1046
|
+
end
|
|
1047
|
+
|
|
1048
|
+
it 'executes tool successfully' do
|
|
1049
|
+
tool = mcp_def.tools['greet']
|
|
1050
|
+
result = tool.call('name' => 'Alice')
|
|
1051
|
+
expect(result).to eq('Hello, Alice!')
|
|
1052
|
+
end
|
|
1053
|
+
|
|
1054
|
+
it 'validates required parameters' do
|
|
1055
|
+
tool = mcp_def.tools['greet']
|
|
1056
|
+
expect {
|
|
1057
|
+
tool.call({}) # Missing required 'name' parameter
|
|
1058
|
+
}.to raise_error(ArgumentError, /Missing required parameter/)
|
|
1059
|
+
end
|
|
1060
|
+
end
|
|
1061
|
+
```
|
|
1062
|
+
|
|
1063
|
+
### Integration Testing
|
|
1064
|
+
|
|
1065
|
+
```ruby
|
|
1066
|
+
# Test full MCP server
|
|
1067
|
+
agent_def = LanguageOperator::Dsl.agent_registry.get('data-processor-mcp')
|
|
1068
|
+
|
|
1069
|
+
# Start server in test mode
|
|
1070
|
+
# Make HTTP requests to /mcp endpoint
|
|
1071
|
+
# Verify responses match MCP protocol
|
|
1072
|
+
```
|
|
1073
|
+
|
|
1074
|
+
## Best Practices
|
|
1075
|
+
|
|
1076
|
+
### Tool Design
|
|
1077
|
+
|
|
1078
|
+
1. **Keep tools focused** - Each tool should do one thing well
|
|
1079
|
+
2. **Use clear names** - Tool names should describe what they do
|
|
1080
|
+
3. **Write good descriptions** - Help LLMs understand when to use the tool
|
|
1081
|
+
4. **Validate inputs** - Use parameter validation to prevent errors
|
|
1082
|
+
5. **Handle errors gracefully** - Return error information, don't crash
|
|
1083
|
+
|
|
1084
|
+
### Parameter Design
|
|
1085
|
+
|
|
1086
|
+
1. **Use required for critical params** - Make essential parameters required
|
|
1087
|
+
2. **Provide defaults** - Set sensible defaults for optional parameters
|
|
1088
|
+
3. **Use enums for choices** - Restrict to valid values when applicable
|
|
1089
|
+
4. **Validate formats** - Use built-in or custom validators
|
|
1090
|
+
5. **Document clearly** - Write clear parameter descriptions
|
|
1091
|
+
|
|
1092
|
+
### Error Handling
|
|
1093
|
+
|
|
1094
|
+
1. **Always rescue exceptions** - Prevent tools from crashing
|
|
1095
|
+
2. **Return structured errors** - Use consistent error format
|
|
1096
|
+
3. **Include error details** - Return error type and message
|
|
1097
|
+
4. **Log errors** - Enable debugging with proper logging
|
|
1098
|
+
|
|
1099
|
+
```ruby
|
|
1100
|
+
execute do |params|
|
|
1101
|
+
begin
|
|
1102
|
+
result = risky_operation(params)
|
|
1103
|
+
{ success: true, result: result }.to_json
|
|
1104
|
+
rescue StandardError => e
|
|
1105
|
+
logger.error("Tool execution failed: #{e.message}")
|
|
1106
|
+
{
|
|
1107
|
+
success: false,
|
|
1108
|
+
error: e.message,
|
|
1109
|
+
error_type: e.class.name
|
|
1110
|
+
}.to_json
|
|
1111
|
+
end
|
|
1112
|
+
end
|
|
1113
|
+
```
|
|
1114
|
+
|
|
1115
|
+
### Performance
|
|
1116
|
+
|
|
1117
|
+
1. **Set timeouts** - Prevent tools from hanging
|
|
1118
|
+
2. **Limit rate** - Use constraints to control usage
|
|
1119
|
+
3. **Cache results** - Cache expensive operations when appropriate
|
|
1120
|
+
4. **Monitor usage** - Track tool execution metrics
|
|
1121
|
+
|
|
1122
|
+
```ruby
|
|
1123
|
+
agent "mcp-server" do
|
|
1124
|
+
as_mcp_server do
|
|
1125
|
+
# Tools...
|
|
1126
|
+
end
|
|
1127
|
+
|
|
1128
|
+
constraints do
|
|
1129
|
+
timeout '30s' # Per-request timeout
|
|
1130
|
+
requests_per_minute 60
|
|
1131
|
+
daily_budget 1000
|
|
1132
|
+
end
|
|
1133
|
+
end
|
|
1134
|
+
```
|
|
1135
|
+
|
|
1136
|
+
### Security
|
|
1137
|
+
|
|
1138
|
+
1. **Validate all inputs** - Never trust user input
|
|
1139
|
+
2. **Sanitize parameters** - Prevent injection attacks
|
|
1140
|
+
3. **Limit access** - Use authentication on MCP endpoints
|
|
1141
|
+
4. **Avoid secrets in responses** - Don't leak sensitive data
|
|
1142
|
+
5. **Log security events** - Monitor for abuse
|
|
1143
|
+
|
|
1144
|
+
```ruby
|
|
1145
|
+
parameter :sql_query do
|
|
1146
|
+
type :string
|
|
1147
|
+
required true
|
|
1148
|
+
validate ->(value) {
|
|
1149
|
+
# Prevent SQL injection
|
|
1150
|
+
return "Invalid query" if value.match?(/;\s*(DROP|DELETE|UPDATE)/i)
|
|
1151
|
+
true
|
|
1152
|
+
}
|
|
1153
|
+
end
|
|
1154
|
+
```
|
|
1155
|
+
|
|
1156
|
+
### Testing
|
|
1157
|
+
|
|
1158
|
+
1. **Test tool execution** - Verify tools work correctly
|
|
1159
|
+
2. **Test validation** - Ensure parameter validation works
|
|
1160
|
+
3. **Test error cases** - Verify error handling
|
|
1161
|
+
4. **Test edge cases** - Test boundary conditions
|
|
1162
|
+
5. **Integration test** - Test full MCP protocol
|
|
1163
|
+
|
|
1164
|
+
### Documentation
|
|
1165
|
+
|
|
1166
|
+
1. **Document each tool** - Explain purpose and usage
|
|
1167
|
+
2. **Document parameters** - Describe each parameter clearly
|
|
1168
|
+
3. **Provide examples** - Show example calls and responses
|
|
1169
|
+
4. **Document errors** - Explain possible error conditions
|
|
1170
|
+
5. **Keep docs updated** - Update when tools change
|
|
1171
|
+
|
|
1172
|
+
## See Also
|
|
1173
|
+
|
|
1174
|
+
- [Agent Reference](agent-reference.md) - Complete agent DSL reference
|
|
1175
|
+
- [Chat Endpoints](chat-endpoints.md) - OpenAI-compatible endpoint guide
|
|
1176
|
+
- [Webhooks](webhooks.md) - Reactive agent configuration
|
|
1177
|
+
- [Best Practices](best-practices.md) - Production deployment patterns
|