agent-harness 0.5.3 → 0.5.4
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/.release-please-manifest.json +1 -1
- data/CHANGELOG.md +7 -0
- data/lib/agent_harness/errors.rb +14 -0
- data/lib/agent_harness/mcp_server.rb +157 -0
- data/lib/agent_harness/providers/adapter.rb +52 -0
- data/lib/agent_harness/providers/anthropic.rb +103 -0
- data/lib/agent_harness/providers/base.rb +38 -0
- data/lib/agent_harness/providers/cursor.rb +14 -0
- data/lib/agent_harness/version.rb +1 -1
- data/lib/agent_harness.rb +1 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0ac511d4448bf777f9389cdba34c165a3b97ac607cfca1db90f0fd5c9de0a4af
|
|
4
|
+
data.tar.gz: 946efb4b7f13e36da7b4c3cc8c3efc3722421aa83ccaf9789efdaa4e776d4bc6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7c3afe5167530f2cd4f8b435b5098732a12b3630f4324cbfa002f3983d760e3a41488ae0d565a0a0694d9b08704d74f010845fb39251a8f92782c6c6d01a572e
|
|
7
|
+
data.tar.gz: 8a9d9706b997b2c8543a45a43cdf476a089eaded7283fe6b3dade1394242f9786c7458b68a2953bcb866611d21a74a360a35d5b1ae3ade40ebe222c819d3c7ce
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.5.4](https://github.com/viamin/agent-harness/compare/agent-harness/v0.5.3...agent-harness/v0.5.4) (2026-03-27)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* 44: feat(mcp): add first-class MCP server configuration to request execution ([#45](https://github.com/viamin/agent-harness/issues/45)) ([454cd9b](https://github.com/viamin/agent-harness/commit/454cd9be1c4bcd2eb92a4ca6f81cc012d4ce1f8c))
|
|
9
|
+
|
|
3
10
|
## [0.5.3](https://github.com/viamin/agent-harness/compare/agent-harness/v0.5.2...agent-harness/v0.5.3) (2026-03-27)
|
|
4
11
|
|
|
5
12
|
|
data/lib/agent_harness/errors.rb
CHANGED
|
@@ -57,6 +57,20 @@ module AgentHarness
|
|
|
57
57
|
# Configuration errors
|
|
58
58
|
class ConfigurationError < Error; end
|
|
59
59
|
|
|
60
|
+
# MCP-specific errors
|
|
61
|
+
class McpConfigurationError < ConfigurationError; end
|
|
62
|
+
|
|
63
|
+
class McpUnsupportedError < ProviderError
|
|
64
|
+
attr_reader :provider
|
|
65
|
+
|
|
66
|
+
def initialize(message = nil, provider: nil, **kwargs)
|
|
67
|
+
@provider = provider
|
|
68
|
+
super(message, **kwargs)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
class McpTransportUnsupportedError < McpUnsupportedError; end
|
|
73
|
+
|
|
60
74
|
# Orchestration errors
|
|
61
75
|
class NoProvidersAvailableError < Error
|
|
62
76
|
attr_reader :attempted_providers, :errors
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module AgentHarness
|
|
4
|
+
# Canonical representation of an MCP server for request-time execution.
|
|
5
|
+
#
|
|
6
|
+
# Provider-agnostic value object that can be translated by each provider
|
|
7
|
+
# adapter into its CLI-specific configuration.
|
|
8
|
+
#
|
|
9
|
+
# @example stdio server
|
|
10
|
+
# McpServer.new(
|
|
11
|
+
# name: "filesystem",
|
|
12
|
+
# transport: "stdio",
|
|
13
|
+
# command: ["npx", "-y", "@modelcontextprotocol/server-filesystem", "/workspace"],
|
|
14
|
+
# env: { "DEBUG" => "0" }
|
|
15
|
+
# )
|
|
16
|
+
#
|
|
17
|
+
# @example HTTP/URL server
|
|
18
|
+
# McpServer.new(
|
|
19
|
+
# name: "playwright",
|
|
20
|
+
# transport: "http",
|
|
21
|
+
# url: "http://mcp-playwright:3000/mcp"
|
|
22
|
+
# )
|
|
23
|
+
class McpServer
|
|
24
|
+
VALID_TRANSPORTS = %w[stdio http sse].freeze
|
|
25
|
+
|
|
26
|
+
attr_reader :name, :transport, :command, :args, :env, :url
|
|
27
|
+
|
|
28
|
+
# @param name [String] unique name for this MCP server
|
|
29
|
+
# @param transport [String] one of "stdio", "http", "sse"
|
|
30
|
+
# @param command [Array<String>, nil] command to launch (stdio only)
|
|
31
|
+
# @param args [Array<String>, nil] additional args for the command
|
|
32
|
+
# @param env [Hash<String,String>, nil] environment variables for the server process
|
|
33
|
+
# @param url [String, nil] URL for HTTP/SSE transport
|
|
34
|
+
def initialize(name:, transport:, command: nil, args: nil, env: nil, url: nil)
|
|
35
|
+
@name = name
|
|
36
|
+
@transport = transport.to_s
|
|
37
|
+
@command = command
|
|
38
|
+
@args = args || []
|
|
39
|
+
@env = env || {}
|
|
40
|
+
@url = url
|
|
41
|
+
|
|
42
|
+
validate!
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Build from a plain Hash (e.g. from user input or serialized config)
|
|
46
|
+
#
|
|
47
|
+
# @param hash [Hash] server definition
|
|
48
|
+
# @return [McpServer]
|
|
49
|
+
def self.from_hash(hash)
|
|
50
|
+
unless hash.is_a?(Hash)
|
|
51
|
+
raise McpConfigurationError, "MCP server definition must be a Hash, got #{hash.class}"
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
begin
|
|
55
|
+
hash = hash.transform_keys(&:to_sym)
|
|
56
|
+
rescue NoMethodError, TypeError => e
|
|
57
|
+
raise McpConfigurationError, "MCP server hash contains invalid keys: #{e.message}"
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
new(
|
|
61
|
+
name: hash[:name],
|
|
62
|
+
transport: hash[:transport],
|
|
63
|
+
command: hash[:command],
|
|
64
|
+
args: hash[:args],
|
|
65
|
+
env: hash[:env],
|
|
66
|
+
url: hash[:url]
|
|
67
|
+
)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def stdio?
|
|
71
|
+
@transport == "stdio"
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def http?
|
|
75
|
+
%w[http sse].include?(@transport)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def to_h
|
|
79
|
+
h = {name: @name, transport: @transport}
|
|
80
|
+
if stdio?
|
|
81
|
+
h[:command] = @command
|
|
82
|
+
h[:args] = @args unless @args.empty?
|
|
83
|
+
else
|
|
84
|
+
h[:url] = @url
|
|
85
|
+
end
|
|
86
|
+
h[:env] = @env unless @env.empty?
|
|
87
|
+
h
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
private
|
|
91
|
+
|
|
92
|
+
def validate!
|
|
93
|
+
raise McpConfigurationError, "MCP server name is required" if @name.nil? || @name.to_s.strip.empty?
|
|
94
|
+
|
|
95
|
+
unless VALID_TRANSPORTS.include?(@transport)
|
|
96
|
+
raise McpConfigurationError,
|
|
97
|
+
"Invalid MCP transport '#{@transport}' for server '#{@name}'. Valid transports: #{VALID_TRANSPORTS.join(", ")}"
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
validate_args!
|
|
101
|
+
validate_env!
|
|
102
|
+
validate_stdio! if stdio?
|
|
103
|
+
validate_http! if http?
|
|
104
|
+
validate_no_stdio_only_fields_on_http! if http?
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def validate_args!
|
|
108
|
+
return if @args.is_a?(Array) && @args.all? { |a| a.is_a?(String) }
|
|
109
|
+
|
|
110
|
+
raise McpConfigurationError,
|
|
111
|
+
"MCP server '#{@name}' args must be an Array of Strings"
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def validate_env!
|
|
115
|
+
return if @env.is_a?(Hash) && @env.keys.all? { |k| k.is_a?(String) } && @env.values.all? { |v| v.is_a?(String) }
|
|
116
|
+
|
|
117
|
+
raise McpConfigurationError,
|
|
118
|
+
"MCP server '#{@name}' env must be a Hash with String keys and values"
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def validate_stdio!
|
|
122
|
+
if @command.nil? || !@command.is_a?(Array) || @command.empty?
|
|
123
|
+
raise McpConfigurationError,
|
|
124
|
+
"MCP server '#{@name}' with stdio transport requires a non-empty command array"
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
unless @command.all? { |c| c.is_a?(String) }
|
|
128
|
+
raise McpConfigurationError,
|
|
129
|
+
"MCP server '#{@name}' command must contain only strings"
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
return if @url.nil?
|
|
133
|
+
|
|
134
|
+
raise McpConfigurationError,
|
|
135
|
+
"MCP server '#{@name}' with stdio transport should not have a url"
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def validate_http!
|
|
139
|
+
if @url.nil? || @url.to_s.strip.empty?
|
|
140
|
+
raise McpConfigurationError,
|
|
141
|
+
"MCP server '#{@name}' with #{@transport} transport requires a url"
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
return if @command.nil?
|
|
145
|
+
|
|
146
|
+
raise McpConfigurationError,
|
|
147
|
+
"MCP server '#{@name}' with #{@transport} transport should not have a command"
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def validate_no_stdio_only_fields_on_http!
|
|
151
|
+
return if @args.empty?
|
|
152
|
+
|
|
153
|
+
raise McpConfigurationError,
|
|
154
|
+
"MCP server '#{@name}' with #{@transport} transport should not have args (args are only valid for stdio)"
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
|
@@ -124,6 +124,58 @@ module AgentHarness
|
|
|
124
124
|
[]
|
|
125
125
|
end
|
|
126
126
|
|
|
127
|
+
# Supported MCP transport types for this provider
|
|
128
|
+
#
|
|
129
|
+
# @return [Array<String>] supported transports (e.g. ["stdio", "http"])
|
|
130
|
+
def supported_mcp_transports
|
|
131
|
+
[]
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Build provider-specific MCP flags/arguments for CLI invocation
|
|
135
|
+
#
|
|
136
|
+
# @param mcp_servers [Array<McpServer>] MCP server definitions
|
|
137
|
+
# @param working_dir [String, nil] working directory for temp files
|
|
138
|
+
# @return [Array<String>] CLI flags to append to the command
|
|
139
|
+
def build_mcp_flags(mcp_servers, working_dir: nil)
|
|
140
|
+
[]
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# Validate that this provider can handle the given MCP servers
|
|
144
|
+
#
|
|
145
|
+
# @param mcp_servers [Array<McpServer>] MCP server definitions
|
|
146
|
+
# @raise [McpUnsupportedError] if MCP is not supported
|
|
147
|
+
# @raise [McpTransportUnsupportedError] if a transport is not supported
|
|
148
|
+
def validate_mcp_servers!(mcp_servers)
|
|
149
|
+
return if mcp_servers.nil? || mcp_servers.empty?
|
|
150
|
+
|
|
151
|
+
unless supports_mcp?
|
|
152
|
+
raise McpUnsupportedError.new(
|
|
153
|
+
"Provider '#{self.class.provider_name}' does not support MCP servers",
|
|
154
|
+
provider: self.class.provider_name
|
|
155
|
+
)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
supported = supported_mcp_transports
|
|
159
|
+
|
|
160
|
+
if supported.empty?
|
|
161
|
+
raise McpUnsupportedError.new(
|
|
162
|
+
"Provider '#{self.class.provider_name}' does not support request-time MCP servers",
|
|
163
|
+
provider: self.class.provider_name
|
|
164
|
+
)
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
mcp_servers.each do |server|
|
|
168
|
+
next if supported.include?(server.transport)
|
|
169
|
+
|
|
170
|
+
raise McpTransportUnsupportedError.new(
|
|
171
|
+
"Provider '#{self.class.provider_name}' does not support MCP transport " \
|
|
172
|
+
"'#{server.transport}' (server: '#{server.name}'). " \
|
|
173
|
+
"Supported transports: #{supported.join(", ")}",
|
|
174
|
+
provider: self.class.provider_name
|
|
175
|
+
)
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
|
|
127
179
|
# Check if provider supports dangerous mode
|
|
128
180
|
#
|
|
129
181
|
# @return [Boolean] true if dangerous mode is supported
|
|
@@ -172,10 +172,27 @@ module AgentHarness
|
|
|
172
172
|
}
|
|
173
173
|
end
|
|
174
174
|
|
|
175
|
+
def send_message(prompt:, **options)
|
|
176
|
+
super
|
|
177
|
+
ensure
|
|
178
|
+
cleanup_mcp_tempfiles!
|
|
179
|
+
end
|
|
180
|
+
|
|
175
181
|
def supports_mcp?
|
|
176
182
|
true
|
|
177
183
|
end
|
|
178
184
|
|
|
185
|
+
def supported_mcp_transports
|
|
186
|
+
%w[stdio http sse]
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def build_mcp_flags(mcp_servers, working_dir: nil)
|
|
190
|
+
return [] if mcp_servers.empty?
|
|
191
|
+
|
|
192
|
+
config_path = write_mcp_config_file(mcp_servers, working_dir: working_dir)
|
|
193
|
+
["--mcp-config", config_path]
|
|
194
|
+
end
|
|
195
|
+
|
|
179
196
|
def supports_dangerous_mode?
|
|
180
197
|
true
|
|
181
198
|
end
|
|
@@ -266,6 +283,11 @@ module AgentHarness
|
|
|
266
283
|
cmd += dangerous_mode_flags
|
|
267
284
|
end
|
|
268
285
|
|
|
286
|
+
# Add MCP server flags (validated/normalized by Base#send_message)
|
|
287
|
+
if options[:mcp_servers]&.any?
|
|
288
|
+
cmd += build_mcp_flags(options[:mcp_servers])
|
|
289
|
+
end
|
|
290
|
+
|
|
269
291
|
# Add custom flags from config
|
|
270
292
|
cmd += @config.default_flags if @config.default_flags&.any?
|
|
271
293
|
|
|
@@ -376,6 +398,87 @@ module AgentHarness
|
|
|
376
398
|
servers
|
|
377
399
|
end
|
|
378
400
|
|
|
401
|
+
def write_mcp_config_file(mcp_servers, working_dir: nil)
|
|
402
|
+
require "tempfile"
|
|
403
|
+
require "tmpdir"
|
|
404
|
+
require "securerandom"
|
|
405
|
+
|
|
406
|
+
config = build_claude_mcp_config(mcp_servers)
|
|
407
|
+
config_json = JSON.generate(config)
|
|
408
|
+
|
|
409
|
+
if @executor.is_a?(DockerCommandExecutor)
|
|
410
|
+
# When running inside a Docker container, write the config file
|
|
411
|
+
# inside the container so the CLI process can read it.
|
|
412
|
+
# Track the path so cleanup_mcp_tempfiles! can remove it after execution.
|
|
413
|
+
container_path = "/tmp/agent_harness_mcp_#{SecureRandom.hex(8)}.json"
|
|
414
|
+
result = @executor.execute(
|
|
415
|
+
["sh", "-c", "cat > #{container_path}"],
|
|
416
|
+
stdin_data: config_json,
|
|
417
|
+
timeout: 5
|
|
418
|
+
)
|
|
419
|
+
unless result.success?
|
|
420
|
+
raise McpConfigurationError,
|
|
421
|
+
"Failed to write MCP config inside container: #{result.stderr}"
|
|
422
|
+
end
|
|
423
|
+
|
|
424
|
+
@mcp_docker_config_paths ||= []
|
|
425
|
+
@mcp_docker_config_paths << container_path
|
|
426
|
+
|
|
427
|
+
container_path
|
|
428
|
+
else
|
|
429
|
+
dir = working_dir || Dir.tmpdir
|
|
430
|
+
file = Tempfile.new(["agent_harness_mcp_", ".json"], dir)
|
|
431
|
+
file.write(config_json)
|
|
432
|
+
file.close
|
|
433
|
+
|
|
434
|
+
# Hold a reference so the Tempfile is not garbage-collected (and
|
|
435
|
+
# therefore deleted) before the CLI process reads it.
|
|
436
|
+
# Cleaned up by cleanup_mcp_tempfiles! after execution.
|
|
437
|
+
@mcp_config_tempfiles ||= []
|
|
438
|
+
@mcp_config_tempfiles << file
|
|
439
|
+
|
|
440
|
+
file.path
|
|
441
|
+
end
|
|
442
|
+
end
|
|
443
|
+
|
|
444
|
+
def build_claude_mcp_config(mcp_servers)
|
|
445
|
+
servers = {}
|
|
446
|
+
mcp_servers.each do |server|
|
|
447
|
+
h = if server.stdio?
|
|
448
|
+
entry = {command: server.command.first}
|
|
449
|
+
remaining_args = server.command[1..] + server.args
|
|
450
|
+
entry[:args] = remaining_args unless remaining_args.empty?
|
|
451
|
+
entry
|
|
452
|
+
else
|
|
453
|
+
{url: server.url}
|
|
454
|
+
end
|
|
455
|
+
h[:env] = server.env unless server.env.empty?
|
|
456
|
+
servers[server.name] = h
|
|
457
|
+
end
|
|
458
|
+
{mcpServers: servers}
|
|
459
|
+
end
|
|
460
|
+
|
|
461
|
+
def cleanup_mcp_tempfiles!
|
|
462
|
+
if @mcp_config_tempfiles
|
|
463
|
+
@mcp_config_tempfiles.each do |file|
|
|
464
|
+
file.close unless file.closed?
|
|
465
|
+
file.unlink
|
|
466
|
+
rescue
|
|
467
|
+
nil
|
|
468
|
+
end
|
|
469
|
+
@mcp_config_tempfiles = nil
|
|
470
|
+
end
|
|
471
|
+
|
|
472
|
+
if @mcp_docker_config_paths
|
|
473
|
+
@mcp_docker_config_paths.each do |path|
|
|
474
|
+
@executor.execute(["rm", "-f", path], timeout: 5)
|
|
475
|
+
rescue
|
|
476
|
+
nil
|
|
477
|
+
end
|
|
478
|
+
@mcp_docker_config_paths = nil
|
|
479
|
+
end
|
|
480
|
+
end
|
|
481
|
+
|
|
379
482
|
def log_debug(action, **context)
|
|
380
483
|
@logger&.debug("[AgentHarness::Anthropic] #{action}: #{context.inspect}")
|
|
381
484
|
end
|
|
@@ -63,6 +63,10 @@ module AgentHarness
|
|
|
63
63
|
def send_message(prompt:, **options)
|
|
64
64
|
log_debug("send_message_start", prompt_length: prompt.length, options: options.keys)
|
|
65
65
|
|
|
66
|
+
# Normalize and validate MCP servers
|
|
67
|
+
options = normalize_mcp_servers(options)
|
|
68
|
+
validate_mcp_servers!(options[:mcp_servers]) if options[:mcp_servers]&.any?
|
|
69
|
+
|
|
66
70
|
# Build command
|
|
67
71
|
command = build_command(prompt, options)
|
|
68
72
|
|
|
@@ -83,6 +87,8 @@ module AgentHarness
|
|
|
83
87
|
log_debug("send_message_complete", duration: duration, tokens: response.tokens)
|
|
84
88
|
|
|
85
89
|
response
|
|
90
|
+
rescue McpConfigurationError, McpUnsupportedError, McpTransportUnsupportedError
|
|
91
|
+
raise
|
|
86
92
|
rescue => e
|
|
87
93
|
handle_error(e, prompt: prompt, options: options)
|
|
88
94
|
end
|
|
@@ -145,6 +151,38 @@ module AgentHarness
|
|
|
145
151
|
|
|
146
152
|
private
|
|
147
153
|
|
|
154
|
+
def normalize_mcp_servers(options)
|
|
155
|
+
servers = options[:mcp_servers]
|
|
156
|
+
return options if servers.nil?
|
|
157
|
+
|
|
158
|
+
unless servers.is_a?(Array)
|
|
159
|
+
raise McpConfigurationError,
|
|
160
|
+
"mcp_servers must be an Array of Hash or McpServer, got #{servers.class}"
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
return options if servers.empty?
|
|
164
|
+
|
|
165
|
+
normalized = servers.map do |server|
|
|
166
|
+
if server.is_a?(McpServer)
|
|
167
|
+
server
|
|
168
|
+
elsif server.is_a?(Hash)
|
|
169
|
+
McpServer.from_hash(server)
|
|
170
|
+
else
|
|
171
|
+
raise McpConfigurationError, "MCP server must be a Hash or McpServer, got #{server.class}"
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# Ensure MCP server names are unique to avoid silent overwrites downstream
|
|
176
|
+
names = normalized.map(&:name)
|
|
177
|
+
duplicate_names = names.group_by { |n| n }.select { |_, v| v.size > 1 }.keys
|
|
178
|
+
unless duplicate_names.empty?
|
|
179
|
+
raise McpConfigurationError,
|
|
180
|
+
"Duplicate MCP server names detected: #{duplicate_names.join(", ")}"
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
options.merge(mcp_servers: normalized)
|
|
184
|
+
end
|
|
185
|
+
|
|
148
186
|
def execute_with_timeout(command, timeout:, env:)
|
|
149
187
|
@executor.execute(command, timeout: timeout, env: env)
|
|
150
188
|
end
|
|
@@ -109,6 +109,14 @@ module AgentHarness
|
|
|
109
109
|
true
|
|
110
110
|
end
|
|
111
111
|
|
|
112
|
+
# Cursor supports MCP for fetching existing server configurations (via
|
|
113
|
+
# fetch_mcp_servers) but does not support injecting request-time MCP
|
|
114
|
+
# servers into CLI invocations. Returning an empty list causes
|
|
115
|
+
# validate_mcp_servers! to raise McpUnsupportedError with a clear message.
|
|
116
|
+
def supported_mcp_transports
|
|
117
|
+
[]
|
|
118
|
+
end
|
|
119
|
+
|
|
112
120
|
def fetch_mcp_servers
|
|
113
121
|
# Try CLI first, then config file
|
|
114
122
|
fetch_mcp_servers_cli || fetch_mcp_servers_config
|
|
@@ -142,6 +150,10 @@ module AgentHarness
|
|
|
142
150
|
def send_message(prompt:, **options)
|
|
143
151
|
log_debug("send_message_start", prompt_length: prompt.length, options: options.keys)
|
|
144
152
|
|
|
153
|
+
# Normalize and validate MCP servers (same as Base#send_message)
|
|
154
|
+
options = normalize_mcp_servers(options)
|
|
155
|
+
validate_mcp_servers!(options[:mcp_servers]) if options[:mcp_servers]&.any?
|
|
156
|
+
|
|
145
157
|
# Build command (without prompt in args - we send via stdin)
|
|
146
158
|
command = [self.class.binary_name, "-p"]
|
|
147
159
|
|
|
@@ -162,6 +174,8 @@ module AgentHarness
|
|
|
162
174
|
log_debug("send_message_complete", duration: duration)
|
|
163
175
|
|
|
164
176
|
response
|
|
177
|
+
rescue McpConfigurationError, McpUnsupportedError, McpTransportUnsupportedError
|
|
178
|
+
raise
|
|
165
179
|
rescue => e
|
|
166
180
|
handle_error(e, prompt: prompt, options: options)
|
|
167
181
|
end
|
data/lib/agent_harness.rb
CHANGED
|
@@ -137,6 +137,7 @@ end
|
|
|
137
137
|
|
|
138
138
|
# Core components
|
|
139
139
|
require_relative "agent_harness/errors"
|
|
140
|
+
require_relative "agent_harness/mcp_server"
|
|
140
141
|
require_relative "agent_harness/configuration"
|
|
141
142
|
require_relative "agent_harness/command_executor"
|
|
142
143
|
require_relative "agent_harness/docker_command_executor"
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: agent-harness
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.5.
|
|
4
|
+
version: 0.5.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Bart Agapinan
|
|
@@ -84,6 +84,7 @@ files:
|
|
|
84
84
|
- lib/agent_harness/docker_command_executor.rb
|
|
85
85
|
- lib/agent_harness/error_taxonomy.rb
|
|
86
86
|
- lib/agent_harness/errors.rb
|
|
87
|
+
- lib/agent_harness/mcp_server.rb
|
|
87
88
|
- lib/agent_harness/orchestration/circuit_breaker.rb
|
|
88
89
|
- lib/agent_harness/orchestration/conductor.rb
|
|
89
90
|
- lib/agent_harness/orchestration/health_monitor.rb
|