Skip to content

Commit 620afb3

Browse files
committed
convert to official mcp sdk
1 parent af6efda commit 620afb3

File tree

7 files changed

+357
-78
lines changed

7 files changed

+357
-78
lines changed

Gemfile.lock

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ PATH
22
remote: .
33
specs:
44
chatwerk (0.1.0)
5-
mcp-rb (~> 0.3)
5+
mcp (~> 0.1)
66
query_packwerk (~> 0.1)
77
sorbet-runtime
88
thor (~> 1.2)
@@ -58,13 +58,15 @@ GEM
5858
rdoc (>= 4.0.0)
5959
reline (>= 0.4.2)
6060
json (2.10.2)
61+
json_rpc_handler (0.1.1)
6162
language_server-protocol (3.17.0.4)
6263
lint_roller (1.1.0)
6364
logger (1.7.0)
6465
loofah (2.24.0)
6566
crass (~> 1.0.2)
6667
nokogiri (>= 1.12.0)
67-
mcp-rb (0.3.3)
68+
mcp (0.1.0)
69+
json_rpc_handler (~> 0.1)
6870
minitest (5.25.5)
6971
nokogiri (1.18.7-arm64-darwin)
7072
racc (~> 1.4)

chatwerk.gemspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ Gem::Specification.new do |spec|
3333
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
3434
spec.require_paths = ['lib']
3535

36-
spec.add_dependency 'mcp-rb', '~> 0.3'
36+
spec.add_dependency 'mcp', '~> 0.1'
3737
spec.add_dependency 'query_packwerk', '~> 0.1'
3838
spec.add_dependency 'sorbet-runtime'
3939
spec.add_dependency 'thor', '~> 1.2'

lib/chatwerk/api.rb

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,18 @@
77
module Chatwerk
88
module API
99
class << self
10+
def print_env
11+
msg = <<~MESSAGE
12+
PWD: #{Dir.pwd}
13+
PWD from ENV: #{ENV.fetch('PWD', nil)}
14+
MESSAGE
15+
Helpers.chdir do
16+
msg << "Chdir'd to #{Helpers.pwd}\n"
17+
end
18+
msg << ENV.to_h.map { |key, value| "#{key}=#{value}" }.join("\n")
19+
msg
20+
end
21+
1022
def packages(package_path: '')
1123
packages = Helpers.all_packages(package_path)
1224

lib/chatwerk/cli.rb

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,18 @@
33

44
require 'thor'
55
require 'chatwerk'
6-
require 'chatwerk/mcp'
76
require 'chatwerk/api'
8-
require 'mcp'
97

108
module Chatwerk
119
# CLI interface for Chatwerk using Thor
1210
class CLI < Thor
1311
desc 'mcp', 'Start the Model Context Protocol server in stdio mode'
1412
def mcp
15-
MCP::Server.new(Chatwerk::Mcp.new).serve(MCP::Server::StdioClientConnection.new)
13+
require_relative 'mcp'
14+
require 'mcp/server/transports/stdio_transport'
15+
server = Chatwerk::Mcp.server
16+
transport = MCP::Server::Transports::StdioTransport.new(server)
17+
transport.open
1618
end
1719

1820
desc 'inspect [WORKING_DIRECTORY]', 'Run the MCP inspector with an optional working directory path (defaults to current directory)'
@@ -28,22 +30,22 @@ def print_env
2830

2931
desc 'packages [PACKAGE_PATH]', 'List all valid packwerk packages, optionally filtered by package path'
3032
def packages(package_path = nil)
31-
puts API.packages(package_path)
33+
puts API.packages(package_path:)
3234
end
3335

3436
desc 'package PACKAGE_PATH', 'Show details for a specific package'
3537
def package(package_path)
36-
puts API.package(package_path)
38+
puts API.package(package_path:)
3739
end
3840

3941
desc 'package_todos PACKAGE_PATH [CONSTANT_NAME]', 'Show dependency violations FROM this package TO others'
4042
def package_todos(package_path, constant_name = nil)
41-
puts API.package_todos(package_path, constant_name)
43+
puts API.package_todos(package_path:, constant_name:)
4244
end
4345

4446
desc 'package_violations PACKAGE_PATH [CONSTANT_NAME]', 'Show dependency violations TO this package FROM others'
4547
def package_violations(package_path, constant_name = nil)
46-
puts API.package_violations(package_path, constant_name)
48+
puts API.package_violations(package_path:, constant_name:)
4749
end
4850

4951
desc 'version', 'Display Chatwerk version'

lib/chatwerk/helpers.rb

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,14 @@ def chdir(&)
66
module_function :chdir
77

88
def env_pwd
9-
ENV.fetch('PWD', pwd)
9+
path = ENV.fetch('PWD', pwd)
10+
# Use realpath if the path exists, otherwise fall back to expand_path
11+
File.directory?(path) ? File.realpath(path) : File.expand_path(path)
1012
end
1113
module_function :env_pwd
1214

1315
def pwd
14-
Dir.pwd
16+
File.realpath(Dir.pwd)
1517
end
1618
module_function :pwd
1719

lib/chatwerk/mcp.rb

Lines changed: 169 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,20 @@
44
require 'yaml'
55
require_relative 'views'
66
require_relative 'api'
7+
require_relative 'helpers'
78

89
module Chatwerk
9-
class Mcp < MCP::App
10-
name 'chatwerk'
11-
version '0.1.0'
10+
# Print environment information tool
11+
class PrintEnvTool < MCP::Tool
12+
description 'Get the current working directory and environment path of the MCP server, ensuring correct directory context'
1213

13-
# client_initialized do
14-
# # MCP servers booted by Cursor aren't started with the PWD being set correctly.
15-
# Dir.chdir(ENV["PWD"]) if ENV["PWD"]
16-
# end
14+
input_schema(
15+
properties: {},
16+
required: []
17+
)
1718

18-
tool 'print_env' do
19-
description 'Get the current working directory and environment path of the MCP server, ensuring correct directory context'
20-
21-
call do |_args|
19+
class << self
20+
def call(server_context:)
2221
msg = <<~MESSAGE
2322
Relay these exact details to the user:
2423
Current Directory: #{Dir.pwd}
@@ -28,78 +27,182 @@ class Mcp < MCP::App
2827
msg << "Chdir'd to #{Helpers.pwd}\n"
2928
end
3029
msg << ENV.to_h.map { |key, value| "#{key}=#{value}" }.join("\n")
30+
31+
MCP::Tool::Response.new([{
32+
type: 'text',
33+
text: msg
34+
}])
3135
end
3236
end
37+
end
3338

34-
tool 'packages' do
35-
description <<~DESC
36-
List all valid packwerk packages (aka packs) in the project.
37-
Use this to find or list packages, optionally matching a substring of the package_path.
38-
DESC
39-
argument :package_path, String, required: false, description: "A partial package path name to constrain the results (e.g. 'packs/product_services/payments/banks' or 'payments/banks')."
40-
call do |args|
41-
Helpers.chdir
42-
API.packages(package_path: args[:package_path])
39+
# List packages tool
40+
class PackagesTool < MCP::Tool
41+
description <<~DESC
42+
List all valid packwerk packages (aka packs) in the project.
43+
Use this to find or list packages, optionally matching a substring of the package_path.
44+
DESC
45+
46+
input_schema(
47+
properties: {
48+
package_path: {
49+
type: 'string',
50+
description: "A partial package path name to constrain the results (e.g. 'packs/product_services/payments/banks' or 'payments/banks')."
51+
}
52+
},
53+
required: []
54+
)
55+
56+
class << self
57+
def call(server_context:, package_path: nil)
58+
result = API.packages(package_path: package_path)
59+
60+
MCP::Tool::Response.new([{
61+
type: 'text',
62+
text: result
63+
}])
4364
end
4465
end
66+
end
67+
68+
# Show package details tool
69+
class PackageTool < MCP::Tool
70+
description <<~DESC
71+
Show the details for a specific package.
72+
73+
Output format:
74+
- Package details, including dependencies and configuration
75+
DESC
4576

46-
tool 'package' do
47-
description <<~DESC
48-
Show the details for a specific package.
77+
input_schema(
78+
properties: {
79+
package_path: {
80+
type: 'string',
81+
description: "A full relative package path (e.g. 'packs/product_services/payments/banks')."
82+
}
83+
},
84+
required: ['package_path']
85+
)
4986

50-
Output format:
51-
- Package details, including dependencies and configuration
52-
DESC
53-
argument :package_path, String, required: true, description: "A full relative package path (e.g. 'packs/product_services/payments/banks')."
54-
call do |args|
87+
class << self
88+
def call(package_path:, server_context:)
5589
Helpers.chdir
56-
API.package(package_path: args[:package_path])
90+
result = API.package(package_path: package_path)
91+
92+
MCP::Tool::Response.new([{
93+
type: 'text',
94+
text: result
95+
}])
5796
end
5897
end
98+
end
99+
100+
# Package todos (violations FROM this package) tool
101+
class PackageTodosTool < MCP::Tool
102+
description <<~DESC
103+
Find code that violates dependency boundaries FROM this package TO other packages.
59104
60-
tool 'package_todos' do
61-
description <<~DESC
62-
Find code that violates dependency boundaries FROM this package TO other packages.
63-
64-
Output formats:
65-
- Without constant_name: List of violated constants with counts
66-
Example: "::OtherPackage::SomeClass # 3 violations"
67-
- With constant_name: Detailed examples and locations
68-
Example:
69-
::OtherPackage::SomeClass
70-
example: OtherPackage::SomeClass.new
71-
files:
72-
- app/services/my_service.rb
73-
DESC
74-
argument :package_path, String, required: true, description: "The relative path of a directory containing a package.yml file (e.g. 'packs/product_services/payments/origination_banks')."
75-
argument :constant_name, String, required: false, description: "The name of a constant to filter the results by. If provided, a more detailed list of code usage examples will be returned. (e.g. '::OtherPackage::SomeClass')"
76-
call do |args|
105+
Output formats:
106+
- Without constant_name: List of violated constants with counts
107+
Example: "::OtherPackage::SomeClass # 3 violations"
108+
- With constant_name: Detailed examples and locations
109+
Example:
110+
::OtherPackage::SomeClass
111+
example: OtherPackage::SomeClass.new
112+
files:
113+
- app/services/my_service.rb
114+
DESC
115+
116+
input_schema(
117+
properties: {
118+
package_path: {
119+
type: 'string',
120+
description: "The relative path of a directory containing a package.yml file (e.g. 'packs/product_services/payments/origination_banks')."
121+
},
122+
constant_name: {
123+
type: 'string',
124+
description: "The name of a constant to filter the results by. If provided, a more detailed list of code usage examples will be returned. (e.g. '::OtherPackage::SomeClass')"
125+
}
126+
},
127+
required: ['package_path']
128+
)
129+
130+
class << self
131+
def call(package_path:, server_context:, constant_name: nil)
77132
Helpers.chdir
78-
API.package_todos(package_path: args[:package_path], constant_name: args[:constant_name])
133+
result = API.package_todos(package_path: package_path, constant_name: constant_name)
134+
135+
MCP::Tool::Response.new([{
136+
type: 'text',
137+
text: result
138+
}])
79139
end
80140
end
141+
end
142+
143+
# Package violations (violations TO this package) tool
144+
class PackageViolationsTool < MCP::Tool
145+
description <<~DESC
146+
Find code that violates dependency boundaries TO this package FROM other packages.
147+
148+
Output formats:
149+
- Without constant_name: List of violated constants with counts
150+
Example: "::ThisPackage::SomeClass: 3 violations"
151+
- With constant_name: Detailed examples and locations
152+
Example:
153+
# Constant `::ThisPackage::SomeClass`
154+
## Example:
155+
ThisPackage::SomeClass.new
156+
### Files:
157+
app/services/other_service.rb
158+
DESC
159+
160+
input_schema(
161+
properties: {
162+
package_path: {
163+
type: 'string',
164+
description: "The relative path of a directory containing a package.yml file (e.g. 'packs/product_services/payments/origination_banks'). AKA a 'pack' or 'package'."
165+
},
166+
constant_name: {
167+
type: 'string',
168+
description: 'The name of a constant to filter the results by. If provided, a more detailed list of code usage examples will be returned.'
169+
}
170+
},
171+
required: ['package_path']
172+
)
81173

82-
tool 'package_violations' do
83-
description <<~DESC
84-
Find code that violates dependency boundaries TO this package FROM other packages.
85-
86-
Output formats:
87-
- Without constant_name: List of violated constants with counts
88-
Example: "::ThisPackage::SomeClass: 3 violations"
89-
- With constant_name: Detailed examples and locations
90-
Example:
91-
# Constant `::ThisPackage::SomeClass`
92-
## Example:
93-
ThisPackage::SomeClass.new
94-
### Files:
95-
app/services/other_service.rb
96-
DESC
97-
argument :package_path, String, required: true, description: "The relative path of a directory containing a package.yml file (e.g. 'packs/product_services/payments/origination_banks'). AKA a 'pack' or 'package'."
98-
argument :constant_name, String, required: false, description: 'The name of a constant to filter the results by. If provided, a more detailed list of code usage examples will be returned.'
99-
call do |args|
174+
class << self
175+
def call(package_path:, server_context:, constant_name: nil)
100176
Helpers.chdir
101-
API.package_violations(package_path: args[:package_path], constant_name: args[:constant_name])
177+
result = API.package_violations(package_path: package_path, constant_name: constant_name)
178+
179+
MCP::Tool::Response.new([{
180+
type: 'text',
181+
text: result
182+
}])
102183
end
103184
end
104185
end
186+
187+
# MCP Server setup
188+
class Mcp
189+
def self.server
190+
# Set up the server with all tools
191+
MCP::Server.new(
192+
name: name,
193+
version: Chatwerk::VERSION,
194+
tools: [
195+
PrintEnvTool,
196+
PackagesTool,
197+
PackageTool,
198+
PackageTodosTool,
199+
PackageViolationsTool
200+
]
201+
)
202+
end
203+
204+
def self.name
205+
'chatwerk'
206+
end
207+
end
105208
end

0 commit comments

Comments
 (0)