Skip to content

Commit 25c20c9

Browse files
committed
update
1 parent 3d7f369 commit 25c20c9

12 files changed

Lines changed: 135 additions & 58 deletions

File tree

Gemfile.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
PATH
22
remote: .
33
specs:
4-
stack-service-base (0.0.98)
4+
stack-service-base (0.0.99)
55
async
66
debug
77
dotenv

lib/stack-service-base/command_init.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,9 @@ def copy_folder(type, f_name)
6666

6767
def update_service_name(s_name)
6868
$stdout.puts "Update service name: #{s_name}"
69-
Dir.glob('**/*').each do |file|
69+
Dir.glob('**/{*,.*}', File::FNM_DOTMATCH).each do |file|
70+
path_parts = file.split('/')
71+
next if path_parts[0...-1].any? { |part| part.start_with?('.') }
7072
next unless File.file? file
7173
content = File.read(file)
7274
include = content.include?('${service_name}') ? '(found)' : nil
@@ -80,5 +82,3 @@ def update_service_name(s_name)
8082
def help = ['Create basic service file structure',
8183
'[... to_compose <deploy name>] ']
8284
end.new
83-
84-

lib/stack-service-base/examples/mcp_config.ru

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ require 'stack-service-base'
44
StackServiceBase.rack_setup self
55

66
SERVICES = {
7-
"database-backend" => {
8-
status: "running",
9-
uptime: 72 * 3600, # seconds
7+
'database-backend' => {
8+
status: 'running',
9+
uptime: 72 * 3600, # seconds
1010
last_restart: Time.now - 72 * 3600
1111
}
1212
}
@@ -16,29 +16,29 @@ helpers McpHelper
1616

1717
Tool :search do
1818
description 'Search for a term in the database'
19-
input query: { type: "string", description: "Term to search for", required: true }
19+
input query: { type: 'string', description: 'Term to search for', required: true }
2020
call do |inputs|
2121
query = inputs[:query]
22-
{ results: [{id:"doc-1",title:"...",url:"..."}] }
22+
{ results: [{id: 'doc-1', title: '...', url: '...'}] }
2323
end
2424
end
2525

2626
Tool :fetch do
2727
description 'Fetch a resource from the database'
28-
input resource_id: { type: "string", description: "Resource ID to fetch", required: true }
28+
input resource_id: { type: 'string', description: 'Resource ID to fetch', required: true }
2929
call do |inputs|
3030
id = inputs[:id]
31-
{ id: "doc-1", title: "...", text: "full text...", url: "https://example.com/doc", metadata: { source: "vector_store" } }
31+
{ id: 'doc-1', title: '...', text: 'full text...', url: 'https://example.com/doc', metadata: { source: 'vector_store' } }
3232
end
3333
end
3434

3535
Tool :schema_echo do
3636
description 'Echo a value using a direct JSON schema'
37-
input_schema type: "object",
37+
input_schema type: 'object',
3838
properties: {
39-
value: { type: "string", description: "Value to echo" }
39+
value: { type: 'string', description: 'Value to echo' }
4040
},
41-
required: ["value"]
41+
required: ['value']
4242
annotations readOnlyHint: true
4343
call do |inputs|
4444
{ value: inputs[:value] }
@@ -47,11 +47,11 @@ end
4747

4848
Tool :full_response_echo do
4949
description 'Echo a value using a complete MCP tool response'
50-
input value: { type: "string", description: "Value to echo", required: true }
50+
input value: { type: 'string', description: 'Value to echo', required: true }
5151
call do |inputs|
5252
{
5353
content: [
54-
{ type: "text", text: inputs[:value] }
54+
{ type: 'text', text: inputs[:value] }
5555
],
5656
structuredContent: {
5757
value: inputs[:value]
@@ -63,7 +63,7 @@ end
6363

6464
Tool :service_status do
6565
description 'Check current status of a service'
66-
input service_name: { type: "string", description: "Service name to inspect", required: true }
66+
input service_name: { type: 'string', description: 'Service name to inspect', required: true }
6767
call do |inputs|
6868
service_name = inputs[:service_name]
6969
service = SERVICES[service_name]
@@ -79,14 +79,14 @@ end
7979

8080
Tool :restart_service do
8181
description 'Restart a service'
82-
input service_name: { type: "string", description: "Service name to restart", required: true },
83-
force: { type: "boolean", default: false, description: "Force restart if graceful fails" }
82+
input service_name: { type: 'string', description: 'Service name to restart', required: true },
83+
force: { type: 'boolean', default: false, description: 'Force restart if graceful fails' }
8484
call do |inputs|
8585
service_name = inputs[:service_name]
8686
service = SERVICES[service_name]
8787
rpc_error!(-32000, "Unknown service #{service_name}") unless service
8888

89-
service[:status] = "running"
89+
service[:status] = 'running'
9090
service[:last_restart] = Time.now
9191
service[:uptime] = 0
9292
{

lib/stack-service-base/mcp/mcp_helper.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ def self.included(base)
5858

5959
stream true do |s|
6060
s.callback { LOGGER.debug "stream closed: #{s}" }
61-
s << "event: message\ndata: #{response_body}\n\n"
61+
s << ['event: message', "data: #{response_body}", '', ''].join($/)
6262
s.close
6363
end
6464
end

lib/stack-service-base/mcp/mcp_processor.rb

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ class McpProcessor
3030
def initialize(body:, status:)
3131
@body = body
3232
@status = status
33-
super("MCP parse error")
33+
super('MCP parse error')
3434
end
3535
end
3636

@@ -46,17 +46,17 @@ def root_endpoint
4646

4747
def rpc_endpoint(raw_body)
4848
req = JSON.parse(raw_body.to_s)
49-
method = req["method"]
50-
params = req["params"]
49+
method = req['method']
50+
params = req['params']
5151

52-
if req.key?("id")
53-
rpc_response(id: req["id"], method: method, params: params)
52+
if req.key?('id')
53+
rpc_response(id: req['id'], method: method, params: params)
5454
else
5555
notification_response(method: method, params: params)
5656
end
5757
rescue JSON::ParserError => e
5858
@logger&.warn("MCP JSON parse failed: #{e.message}")
59-
body = error_response(id: nil, code: -32700, message: "Parse error")
59+
body = error_response(id: nil, code: -32_700, message: 'Parse error')
6060
raise ParseError.new(body: body, status: 400)
6161
end
6262

@@ -86,21 +86,21 @@ def notification_response(method:, params:)
8686

8787
def handle(method:, params:)
8888
case method
89-
when "tools/list" then list_tools
90-
# when "resources/list" then {}
91-
# when "prompts/list" then {}
92-
when "tools/call" then call_tool(params || {})
93-
when "initialize" then initialize_response
94-
when "notifications/initialized" then @logger&.debug(params); {}
95-
when "logging/setLevel" then @logger&.debug(params); {}
89+
when 'tools/list' then list_tools
90+
# when 'resources/list' then {}
91+
# when 'prompts/list' then {}
92+
when 'tools/call' then call_tool(params || {})
93+
when 'initialize' then initialize_response
94+
when 'notifications/initialized' then @logger&.debug(params); {}
95+
when 'logging/setLevel' then @logger&.debug(params); {}
9696
else
97-
rpc_error!(-32601, "Unknown method #{method}")
97+
rpc_error!(-32_601, "Unknown method #{method}")
9898
end
9999
end
100100

101101
def handle_notification(method:, params:)
102102
case method
103-
when "notifications/initialized", "notifications/cancelled"
103+
when 'notifications/initialized', 'notifications/cancelled'
104104
@logger&.debug("MCP notification accepted: #{method}")
105105
else
106106
@logger&.debug("MCP notification ignored: #{method}")
@@ -127,7 +127,7 @@ def initialize_response
127127
private
128128

129129
def json_rpc_response(id:)
130-
body = { jsonrpc: "2.0", id: id }
130+
body = { jsonrpc: '2.0', id: id }
131131

132132
begin
133133
result = yield
@@ -136,17 +136,17 @@ def json_rpc_response(id:)
136136
body[:error] = { code: e.code, message: e.message }
137137
rescue => e
138138
@logger&.error("Unhandled RPC error: #{e.class}: #{e.message}\n#{e.backtrace&.first}")
139-
body[:error] = { code: -32603, message: "Internal error" }
139+
body[:error] = { code: -32_603, message: 'Internal error' }
140140
end
141141

142142
body.delete(:result) if body[:error]
143143
JSON.dump(body)
144144
end
145145

146146
def call_tool(params)
147-
name = params["name"]
148-
arguments = params["arguments"] || {}
149-
tool = registry.fetch(name) || rpc_error!(-32601, "Unknown tool #{name}")
147+
name = params['name']
148+
arguments = params['arguments'] || {}
149+
tool = registry.fetch(name) || rpc_error!(-32_601, "Unknown tool #{name}")
150150
response = tool.call_tool(arguments)
151151
return response if mcp_tool_response?(response)
152152

@@ -160,15 +160,15 @@ def registry
160160
def mcp_tool_response?(response)
161161
return false unless response.is_a?(Hash)
162162

163-
[:content, :structuredContent, :isError, "content", "structuredContent", "isError"].any? do |key|
163+
[:content, :structuredContent, :isError, 'content', 'structuredContent', 'isError'].any? do |key|
164164
response.key?(key)
165165
end
166166
end
167167

168168
def wrap_tool_response(response)
169169
{
170170
content: [
171-
{ "type": "text", "text": response.is_a?(String) ? response : JSON.dump(response) }
171+
{ 'type' => 'text', 'text' => response.is_a?(String) ? response : JSON.dump(response) }
172172
],
173173
isError: false
174174
}

lib/stack-service-base/mcp/mcp_tool_registry.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ def build_input_schema
8181

8282
@inputs.each do |field, config|
8383
cfg = stringify_keys(config)
84-
required << field if cfg.delete("required")
84+
required << field if cfg.delete('required')
8585
properties[field] = cfg
8686
end
8787

Lines changed: 42 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,48 @@
11
stages:
2+
- test
23
- build
34

4-
build-job:
5-
stage: build
6-
image: docker:latest
5+
.build_block: &build_block
6+
# tags: [ build ]
7+
image: docker:27.5.1-cli
78
services:
8-
- docker:dind
9+
- name: docker:27.5.1-dind
10+
alias: docker
911
variables:
10-
DOCKER_TLS_CERTDIR: "/certs"
11-
12-
before_script:
13-
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
12+
DOCKER_HOST: tcp://docker:2375
13+
DOCKER_TLS_CERTDIR: ""
1414
script:
15-
- docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA -t $CI_REGISTRY_IMAGE:latest -f docker/Dockerfile ./src
16-
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
17-
- docker push $CI_REGISTRY_IMAGE:latest
15+
- export CI_COMMIT_TAG="${CI_COMMIT_TAG:-0.0.0}"
16+
- |
17+
if [ -n "${CI_REGISTRY:-}" ] && [ -n "${CI_REGISTRY_USER:-}" ] && [ -n "${CI_REGISTRY_PASSWORD:-}" ]; then
18+
echo "${CI_REGISTRY_PASSWORD}" | docker login "${CI_REGISTRY}" -u "${CI_REGISTRY_USER}" --password-stdin
19+
fi
20+
- docker buildx create --name "${CI_PROJECT_NAME}-wrapper-builder" --driver docker-container --use || docker buildx use "${CI_PROJECT_NAME}-wrapper-builder"
21+
- docker buildx inspect --bootstrap
22+
- |
23+
docker buildx build --load \
24+
-t build/${CI_PROJECT_NAME} \
25+
-f docker/Dockerfile.build \
26+
--cache-from type=registry,ref=${CI_REGISTRY_IMAGE}/ci-wrapper:${CI_COMMIT_REF_SLUG}-cache \
27+
--cache-to type=registry,ref=${CI_REGISTRY_IMAGE}/ci-wrapper:${CI_COMMIT_REF_SLUG}-cache,mode=max \
28+
.
29+
- docker run --rm `env | grep -o '^CI_[^=]*' | sed 's/^/-e /'`
30+
-e REGISTRY_HOST=$CI_REGISTRY/$CI_PROJECT_NAMESPACE
31+
-v /var/run/docker.sock:/var/run/docker.sock
32+
-v /root/.docker:/root/.docker
33+
build/${CI_PROJECT_NAME} 2>&1
34+
35+
build push:
36+
<<: *build_block
37+
stage: build
38+
needs: [ tests ]
39+
40+
tests:
41+
<<: *build_block
42+
stage: test
43+
variables:
44+
DOCKER_HOST: tcp://docker:2375
45+
DOCKER_TLS_CERTDIR: ""
46+
CI_SKIP_PUSH: true
47+
# build-labels sets target for each service in docker-compose.yml
48+
CI_BUILD_TARGET: tests
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
FROM ruby:3.4.4-slim-bookworm AS base
2+
RUN apt-get update && apt-get install -y --no-install-recommends bash ca-certificates curl git tar \
3+
&& rm -rf /var/lib/apt/lists/*
4+
RUN gem install build-labels:0.0.79
5+
RUN BUILDX_VERSION=v0.20.1 COMPOSE_VERSION=v2.33.0 install-docker-static 27.5.1
6+
7+
WORKDIR /build
8+
9+
COPY .. .
10+
11+
CMD ["bash", "-euo", "pipefail", "-c", "\
12+
cd /build/docker; \
13+
if [ -n \"${CI_REGISTRY:-}\" ] && [ -n \"${CI_REGISTRY_USER:-}\" ] && [ -n \"${CI_REGISTRY_PASSWORD:-}\" ]; then \
14+
echo \"$CI_REGISTRY_PASSWORD\" | docker login \"$CI_REGISTRY\" -u \"$CI_REGISTRY_USER\" --password-stdin; \
15+
fi; \
16+
build-labels -n -c docker-compose.yml changed gitlab set_version cache to_dockerfiles to_compose | tee bake.yml; \
17+
export OTEL_RESOURCE_ATTRIBUTES=\"service.name=docker-builder,pipeline.id=${CI_PIPELINE_ID:-local},project.name=${service_name}\"; \
18+
export REGISTRY_HOST=\"${REGISTRY_HOST:-${CI_REGISTRY_HOST:-${CI_REGISTRY_IMAGE:-}}}\"; \
19+
: \"${REGISTRY_HOST:?REGISTRY_HOST, CI_REGISTRY_IMAGE, or CI_REGISTRY_HOST is required}\"; \
20+
export BUILDX_BAKE_ENTITLEMENTS_FS=0; \
21+
if grep -q \"services: {}\" bake.yml; then \
22+
echo \"No changed services to build.\"; \
23+
exit 0; \
24+
fi; \
25+
docker buildx create --name \"${service_name}-builder\" --driver docker-container --use || docker buildx use \"${service_name}-builder\"; \
26+
docker buildx inspect --bootstrap; \
27+
if [ -n \"${CI_SKIP_PUSH:-}\" ]; then \
28+
docker buildx bake --load --allow=fs.read=../src -f bake.yml; \
29+
else \
30+
docker buildx bake --push --allow=fs.read=../src -f bake.yml; \
31+
fi; \
32+
if [ \"${CI_BUILD_TARGET:-}\" = \"tests\" ]; then \
33+
docker compose -f bake.yml down --remove-orphans; \
34+
docker compose -f bake.yml up --no-build --force-recreate --abort-on-container-failure; \
35+
fi \
36+
"]

lib/stack-service-base/project_template/gitlab/docker/docker-compose.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
services:
22
${service_name}:
3-
image: ${REGISTRY_HOST}/${service_name}
3+
image: ${REGISTRY_HOST}/${service_name}/${CI_COMMIT_REF_SLUG:-local}
44
build:
55
context: ../src
66
additional_contexts:
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
export CI_PROJECT_NAME=${service_name}
2+
export CI_PIPELINE_ID=local
3+
export CI_PIPELINE_IID=0
4+
export CI_REGISTRY_HOST=localhost
5+
export CI_SKIP_PUSH=true
6+
7+
# -v /root/.docker:/root/.docker \
8+
docker run --rm --env-file <(env | grep ^CI_) \
9+
-v /var/run/docker.sock:/var/run/docker.sock \
10+
$(docker build -q -t build/${CI_PROJECT_NAME} -f Dockerfile.build ..) 2>&1

0 commit comments

Comments
 (0)