mcpable 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +776 -0
- data/app/controllers/mcpable/rails/tools_controller.rb +23 -0
- data/lib/mcpable/active_record/column_inference.rb +43 -0
- data/lib/mcpable/active_record/source.rb +108 -0
- data/lib/mcpable/active_record.rb +15 -0
- data/lib/mcpable/argument.rb +29 -0
- data/lib/mcpable/definition.rb +36 -0
- data/lib/mcpable/dsl/resource_builder.rb +264 -0
- data/lib/mcpable/dsl/tool_builder.rb +66 -0
- data/lib/mcpable/dsl.rb +18 -0
- data/lib/mcpable/naming.rb +28 -0
- data/lib/mcpable/pipeline.rb +47 -0
- data/lib/mcpable/ports/middleware.rb +18 -0
- data/lib/mcpable/ports/schema_strategy.rb +11 -0
- data/lib/mcpable/ports/source.rb +23 -0
- data/lib/mcpable/ports/transport.rb +27 -0
- data/lib/mcpable/pundit/authorize.rb +31 -0
- data/lib/mcpable/pundit.rb +4 -0
- data/lib/mcpable/rails/engine.rb +29 -0
- data/lib/mcpable/rails/loader.rb +57 -0
- data/lib/mcpable/rails.rb +5 -0
- data/lib/mcpable/registry.rb +35 -0
- data/lib/mcpable/resource.rb +21 -0
- data/lib/mcpable/result.rb +17 -0
- data/lib/mcpable/runtime.rb +118 -0
- data/lib/mcpable/schema_strategies/explicit_schema.rb +49 -0
- data/lib/mcpable/sources/enumerable_source.rb +99 -0
- data/lib/mcpable/tool.rb +29 -0
- data/lib/mcpable/tool_call.rb +18 -0
- data/lib/mcpable/transports/official_mcp.rb +116 -0
- data/lib/mcpable/version.rb +5 -0
- data/lib/mcpable.rb +76 -0
- metadata +124 -0
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "mcp"
|
|
5
|
+
|
|
6
|
+
module Mcpable
|
|
7
|
+
module Transports
|
|
8
|
+
class OfficialMcp < Ports::Transport
|
|
9
|
+
DEFAULT_SERVER_NAME = "mcpable"
|
|
10
|
+
|
|
11
|
+
attr_reader :server_name, :server_version
|
|
12
|
+
|
|
13
|
+
def initialize(registry: nil, runtime: nil, profile: :default, config: nil,
|
|
14
|
+
server_name: DEFAULT_SERVER_NAME, server_version: Mcpable::VERSION)
|
|
15
|
+
super(
|
|
16
|
+
registry: registry || Mcpable.registry,
|
|
17
|
+
runtime: runtime || Mcpable.runtime,
|
|
18
|
+
profile: profile
|
|
19
|
+
)
|
|
20
|
+
@config = config
|
|
21
|
+
@server_name = server_name
|
|
22
|
+
@server_version = server_version
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def list_tools
|
|
26
|
+
build_tools({}).map(&:to_h)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def handle(raw_request, context: {})
|
|
30
|
+
server = build_server(context)
|
|
31
|
+
|
|
32
|
+
case raw_request
|
|
33
|
+
when String then server.handle_json(raw_request)
|
|
34
|
+
when Hash then server.handle(symbolize_deep(raw_request))
|
|
35
|
+
else raise ArgumentError, "unsupported request: #{raw_request.class}"
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def serve_stdio(context: {})
|
|
40
|
+
MCP::Server::Transports::StdioTransport.new(build_server(context)).open
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def build_server(context = {})
|
|
44
|
+
MCP::Server.new(
|
|
45
|
+
name: server_name,
|
|
46
|
+
version: server_version,
|
|
47
|
+
tools: build_tools(context)
|
|
48
|
+
)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
private
|
|
52
|
+
|
|
53
|
+
def current_config
|
|
54
|
+
@config || runtime_config || Mcpable.config
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def runtime_config
|
|
58
|
+
runtime.config if runtime.respond_to?(:config)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def definitions
|
|
62
|
+
registry.for_profile(profile)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def build_tools(context)
|
|
66
|
+
definitions.map { |definition| build_tool(definition, context) }
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def build_tool(definition, context)
|
|
70
|
+
runtime = self.runtime
|
|
71
|
+
name = definition.name
|
|
72
|
+
|
|
73
|
+
MCP::Tool.define(
|
|
74
|
+
name: name,
|
|
75
|
+
description: definition.description,
|
|
76
|
+
input_schema: normalize_schema(current_config.schema_strategy.input_schema(definition)),
|
|
77
|
+
annotations: annotations_for(definition)
|
|
78
|
+
) do |**args|
|
|
79
|
+
args.delete(:server_context)
|
|
80
|
+
OfficialMcp.to_mcp_response(runtime.call_tool(name, args: args, context: context))
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def annotations_for(definition)
|
|
85
|
+
{
|
|
86
|
+
read_only_hint: definition.read_only?,
|
|
87
|
+
destructive_hint: definition.destructive?,
|
|
88
|
+
open_world_hint: definition.open_world?
|
|
89
|
+
}
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def normalize_schema(schema)
|
|
93
|
+
JSON.parse(JSON.generate(schema))
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def symbolize_deep(value)
|
|
97
|
+
case value
|
|
98
|
+
when Hash then value.to_h { |k, v| [k.to_sym, symbolize_deep(v)] }
|
|
99
|
+
when Array then value.map { |v| symbolize_deep(v) }
|
|
100
|
+
else value
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def self.to_mcp_response(result)
|
|
105
|
+
if result.ok?
|
|
106
|
+
MCP::Tool::Response.new([{ type: "text", text: JSON.generate(result.payload) }])
|
|
107
|
+
else
|
|
108
|
+
MCP::Tool::Response.new(
|
|
109
|
+
[{ type: "text", text: "#{result.status}: #{result.error}" }],
|
|
110
|
+
error: true
|
|
111
|
+
)
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
data/lib/mcpable.rb
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "mcpable/version"
|
|
4
|
+
|
|
5
|
+
module Mcpable
|
|
6
|
+
class Error < StandardError; end
|
|
7
|
+
|
|
8
|
+
class DuplicateToolError < Error
|
|
9
|
+
def initialize(name) = super("tool already registered: #{name}")
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
class UnknownToolError < Error
|
|
13
|
+
def initialize(name) = super("unknown tool: #{name}")
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
class MissingScopeError < Error
|
|
17
|
+
def initialize(name) = super("policy #{name} has no Scope class")
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
require "mcpable/naming"
|
|
22
|
+
require "mcpable/argument"
|
|
23
|
+
require "mcpable/definition"
|
|
24
|
+
require "mcpable/result"
|
|
25
|
+
require "mcpable/tool_call"
|
|
26
|
+
require "mcpable/ports/source"
|
|
27
|
+
require "mcpable/ports/schema_strategy"
|
|
28
|
+
require "mcpable/ports/middleware"
|
|
29
|
+
require "mcpable/ports/transport"
|
|
30
|
+
require "mcpable/pipeline"
|
|
31
|
+
require "mcpable/registry"
|
|
32
|
+
require "mcpable/runtime"
|
|
33
|
+
require "mcpable/schema_strategies/explicit_schema"
|
|
34
|
+
require "mcpable/sources/enumerable_source"
|
|
35
|
+
require "mcpable/dsl"
|
|
36
|
+
require "mcpable/dsl/tool_builder"
|
|
37
|
+
require "mcpable/dsl/resource_builder"
|
|
38
|
+
require "mcpable/tool"
|
|
39
|
+
require "mcpable/resource"
|
|
40
|
+
|
|
41
|
+
module Mcpable
|
|
42
|
+
class Configuration
|
|
43
|
+
DEFAULT_ERROR_MAPPER = ->(_error) { Result.fail("internal error") }
|
|
44
|
+
DEFAULT_CONTEXT_BUILDER = ->(_env) { {} }
|
|
45
|
+
|
|
46
|
+
attr_reader :pipeline
|
|
47
|
+
attr_accessor :schema_strategy, :error_mapper, :context_builder
|
|
48
|
+
|
|
49
|
+
def initialize
|
|
50
|
+
@pipeline = Pipeline.new(config: self)
|
|
51
|
+
@schema_strategy = SchemaStrategies::ExplicitSchema.new
|
|
52
|
+
@error_mapper = DEFAULT_ERROR_MAPPER
|
|
53
|
+
@context_builder = DEFAULT_CONTEXT_BUILDER
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
class << self
|
|
58
|
+
def registry = @registry ||= Registry.new
|
|
59
|
+
|
|
60
|
+
def config = @config ||= Configuration.new
|
|
61
|
+
|
|
62
|
+
def configure
|
|
63
|
+
yield config
|
|
64
|
+
config
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def runtime = @runtime ||= Runtime.new
|
|
68
|
+
|
|
69
|
+
def reset!
|
|
70
|
+
@registry = Registry.new
|
|
71
|
+
@config = Configuration.new
|
|
72
|
+
@runtime = Runtime.new
|
|
73
|
+
self
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: mcpable
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Afshin
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: mcp
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '1.0'
|
|
19
|
+
- - "<"
|
|
20
|
+
- !ruby/object:Gem::Version
|
|
21
|
+
version: '2.0'
|
|
22
|
+
type: :runtime
|
|
23
|
+
prerelease: false
|
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
25
|
+
requirements:
|
|
26
|
+
- - ">="
|
|
27
|
+
- !ruby/object:Gem::Version
|
|
28
|
+
version: '1.0'
|
|
29
|
+
- - "<"
|
|
30
|
+
- !ruby/object:Gem::Version
|
|
31
|
+
version: '2.0'
|
|
32
|
+
- !ruby/object:Gem::Dependency
|
|
33
|
+
name: rake
|
|
34
|
+
requirement: !ruby/object:Gem::Requirement
|
|
35
|
+
requirements:
|
|
36
|
+
- - ">="
|
|
37
|
+
- !ruby/object:Gem::Version
|
|
38
|
+
version: '13.0'
|
|
39
|
+
type: :development
|
|
40
|
+
prerelease: false
|
|
41
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
42
|
+
requirements:
|
|
43
|
+
- - ">="
|
|
44
|
+
- !ruby/object:Gem::Version
|
|
45
|
+
version: '13.0'
|
|
46
|
+
- !ruby/object:Gem::Dependency
|
|
47
|
+
name: rspec
|
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
|
49
|
+
requirements:
|
|
50
|
+
- - "~>"
|
|
51
|
+
- !ruby/object:Gem::Version
|
|
52
|
+
version: '3.13'
|
|
53
|
+
type: :development
|
|
54
|
+
prerelease: false
|
|
55
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
56
|
+
requirements:
|
|
57
|
+
- - "~>"
|
|
58
|
+
- !ruby/object:Gem::Version
|
|
59
|
+
version: '3.13'
|
|
60
|
+
description: Mcpable turns plain Ruby classes and Rails models into Model Context
|
|
61
|
+
Protocol tools through a declarative DSL, a middleware pipeline and pluggable source,
|
|
62
|
+
schema and transport ports.
|
|
63
|
+
executables: []
|
|
64
|
+
extensions: []
|
|
65
|
+
extra_rdoc_files: []
|
|
66
|
+
files:
|
|
67
|
+
- LICENSE
|
|
68
|
+
- README.md
|
|
69
|
+
- app/controllers/mcpable/rails/tools_controller.rb
|
|
70
|
+
- lib/mcpable.rb
|
|
71
|
+
- lib/mcpable/active_record.rb
|
|
72
|
+
- lib/mcpable/active_record/column_inference.rb
|
|
73
|
+
- lib/mcpable/active_record/source.rb
|
|
74
|
+
- lib/mcpable/argument.rb
|
|
75
|
+
- lib/mcpable/definition.rb
|
|
76
|
+
- lib/mcpable/dsl.rb
|
|
77
|
+
- lib/mcpable/dsl/resource_builder.rb
|
|
78
|
+
- lib/mcpable/dsl/tool_builder.rb
|
|
79
|
+
- lib/mcpable/naming.rb
|
|
80
|
+
- lib/mcpable/pipeline.rb
|
|
81
|
+
- lib/mcpable/ports/middleware.rb
|
|
82
|
+
- lib/mcpable/ports/schema_strategy.rb
|
|
83
|
+
- lib/mcpable/ports/source.rb
|
|
84
|
+
- lib/mcpable/ports/transport.rb
|
|
85
|
+
- lib/mcpable/pundit.rb
|
|
86
|
+
- lib/mcpable/pundit/authorize.rb
|
|
87
|
+
- lib/mcpable/rails.rb
|
|
88
|
+
- lib/mcpable/rails/engine.rb
|
|
89
|
+
- lib/mcpable/rails/loader.rb
|
|
90
|
+
- lib/mcpable/registry.rb
|
|
91
|
+
- lib/mcpable/resource.rb
|
|
92
|
+
- lib/mcpable/result.rb
|
|
93
|
+
- lib/mcpable/runtime.rb
|
|
94
|
+
- lib/mcpable/schema_strategies/explicit_schema.rb
|
|
95
|
+
- lib/mcpable/sources/enumerable_source.rb
|
|
96
|
+
- lib/mcpable/tool.rb
|
|
97
|
+
- lib/mcpable/tool_call.rb
|
|
98
|
+
- lib/mcpable/transports/official_mcp.rb
|
|
99
|
+
- lib/mcpable/version.rb
|
|
100
|
+
homepage: https://github.com/afshmini/mcpable
|
|
101
|
+
licenses:
|
|
102
|
+
- MIT
|
|
103
|
+
metadata:
|
|
104
|
+
source_code_uri: https://github.com/afshmini/mcpable
|
|
105
|
+
bug_tracker_uri: https://github.com/afshmini/mcpable/issues
|
|
106
|
+
rubygems_mfa_required: 'true'
|
|
107
|
+
rdoc_options: []
|
|
108
|
+
require_paths:
|
|
109
|
+
- lib
|
|
110
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
111
|
+
requirements:
|
|
112
|
+
- - ">="
|
|
113
|
+
- !ruby/object:Gem::Version
|
|
114
|
+
version: '3.2'
|
|
115
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
116
|
+
requirements:
|
|
117
|
+
- - ">="
|
|
118
|
+
- !ruby/object:Gem::Version
|
|
119
|
+
version: '0'
|
|
120
|
+
requirements: []
|
|
121
|
+
rubygems_version: 4.0.15
|
|
122
|
+
specification_version: 4
|
|
123
|
+
summary: Expose Ruby objects and Rails models as MCP tools.
|
|
124
|
+
test_files: []
|