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.
@@ -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
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mcpable
4
+ VERSION = "0.1.0"
5
+ 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: []