Skip to content

Commit 1b98aeb

Browse files
authored
feat(fast-time): rmcp /mcp transport + legacy SSE shim (#5299)
* feat: add rust fast-time sse transport Signed-off-by: lucarlig <luca.carlig@ibm.com> * refactor: split rust fast-time transports Signed-off-by: lucarlig <luca.carlig@ibm.com> * fix: negotiate MCP protocol version and evict idle sessions - Echo the client's requested protocolVersion when supported, else the latest version this server speaks (both SSE and streamable HTTP). - Track last-seen time per streamable-HTTP session and evict idle ones past SESSION_IDLE_TTL instead of leaking until the cap is reached. - get_stats now counts its own invocation like every other tool, and the unreachable session-id header fallback is dropped. Signed-off-by: lucarlig <luca.carlig@ibm.com> * refactor(fast-time): use rmcp SDK for Streamable HTTP transport and tools Replace the hand-rolled MCP layer with the official Rust SDK (rmcp): - /mcp Streamable HTTP transport is now served by rmcp's StreamableHttpService + LocalSessionManager, removing the bespoke session map and JSON-RPC dispatch (streamable_http.rs). A lightweight gate caps concurrent sessions at MAX_ACTIVE_SESSIONS (rmcp's session manager has no built-in cap); existing sessions are cleaned up by rmcp on disconnect/DELETE. - Tools are declared once via #[tool] macros with schemas derived from their parameter types, removing the hand-written tool schemas and dispatch (mcp.rs). The schema_error/schema_success fixtures retain their declared outputSchema. - The legacy HTTP+SSE transport remains a hand-rolled shim (documented exception) because rmcp does not reproduce the bespoke ContextForge legacy-SSE semantics (endpoint event, sessionId/session_id aliases, 202/404/410 codes). It delegates tools/list and tools/call back to the rmcp server so schemas and logic never drift. - serverInfo is built explicitly so /mcp reports the server identity rather than rmcp's own crate name. Behavior parity (DST conversion, echo delay limit, protocol-version echo, output schemas) is covered by unit tests; both transports verified end-to-end. Signed-off-by: lucarlig <luca.carlig@ibm.com> * feat(fast-time): add MCP resources, prompts, and tool annotations Port the Go fast-time-server's resource and prompt surface to the Rust server so it advertises the same MCP capabilities: - 4 resources: timezone://info, time://current/world, time://formats, time://business-hours. - 3 prompts: compare_timezones, schedule_meeting, convert_time_detailed. - Advertise resources + prompts capabilities; add readOnly / idempotent / title annotations to get_system_time and convert_time. Resources and prompts are shared by both the /mcp transport (rmcp ServerHandler) and the legacy SSE shim (resources/list, resources/read, prompts/list, prompts/get). Signed-off-by: lucarlig <luca.carlig@ibm.com> * feat(fast-time): add CLI, stdio transport, and Bearer auth Bring the Rust server up to the Go transport/CLI surface: - clap CLI: --transport (stdio|sse|http|dual|rest), --addr/--listen/--port, --public-url, --auth-token, --log-level. With no arguments it still serves every HTTP route on BIND_ADDRESS (default 0.0.0.0:9080), preserving the container/Makefile behavior; the HTTP modes all serve the full router. - stdio transport via rmcp serve(), for `--transport stdio`. - Bearer-token auth middleware for the HTTP transports, exempting /health and /version, mirroring the Go authMiddleware (401 + WWW-Authenticate). - Send tracing logs to stderr so the stdio transport keeps stdout as a pure JSON-RPC stream. Signed-off-by: lucarlig <luca.carlig@ibm.com> * feat(fast-time): add the /api/v1 REST surface and CORS Port the Go fast-time-server's REST API to the Rust server, alongside the existing high-throughput /api/echo and /api/time benchmark endpoints: - /api/v1/time (+ /{tz}), /api/v1/convert, /api/v1/convert/batch - /api/v1/timezones (+ /{tz}), /api/v1/resources (+ /{slug}), /api/v1/prompts (+ /{name}/execute) - /api/v1/test/echo, /api/v1/test/validate, /api/v1/test/performance - permissive CORS with a 204 preflight, applied as the outermost layer The REST resources/prompts mirror the Go REST handlers' simpler payloads, which differ from the richer MCP resource/prompt content. Signed-off-by: lucarlig <luca.carlig@ibm.com> * feat(fast-time): add OpenAPI spec and Swagger docs endpoints Serve /api/v1/openapi.json (an OpenAPI 3.0 document describing the REST surface) and /api/v1/docs (a Swagger UI page), matching the Go server. Signed-off-by: lucarlig <luca.carlig@ibm.com> * fix: restore fast-time Rust CI parity Signed-off-by: lucarlig <luca.carlig@ibm.com> * fix: publish Rust fast-time server image Signed-off-by: lucarlig <luca.carlig@ibm.com> * fix: stabilize fast-time CI Signed-off-by: lucarlig <luca.carlig@ibm.com> * fix: use Rust fast-time in wrapper paths Signed-off-by: lucarlig <luca.carlig@ibm.com> * fix: align fast-time references with Rust server Signed-off-by: lucarlig <luca.carlig@ibm.com> * fix: build Rust server container images Signed-off-by: lucarlig <luca.carlig@ibm.com> * fix: disable gunicorn control socket in read-only containers Signed-off-by: lucarlig <luca.carlig@ibm.com> --------- Signed-off-by: lucarlig <luca.carlig@ibm.com>
1 parent 1867330 commit 1b98aeb

39 files changed

Lines changed: 3357 additions & 1215 deletions
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
name: Fast Time Server Image
2+
3+
on:
4+
push:
5+
branches: ["main"]
6+
tags: ["v*"]
7+
paths:
8+
- "Cargo.toml"
9+
- "Cargo.lock"
10+
- "rust-toolchain.toml"
11+
- "crates/**"
12+
- "mcp-servers/rust/fast-time-server/**"
13+
- ".github/workflows/fast-time-server-image.yml"
14+
pull_request:
15+
types: [opened, synchronize, ready_for_review]
16+
branches: ["main"]
17+
paths:
18+
- "Cargo.toml"
19+
- "Cargo.lock"
20+
- "rust-toolchain.toml"
21+
- "crates/**"
22+
- "mcp-servers/rust/fast-time-server/**"
23+
- ".github/workflows/fast-time-server-image.yml"
24+
workflow_dispatch:
25+
26+
concurrency:
27+
group: ${{ github.workflow }}-${{ github.ref }}
28+
cancel-in-progress: true
29+
30+
permissions:
31+
contents: read
32+
33+
env:
34+
REGISTRY: ghcr.io
35+
IMAGE_NAME: ibm/fast-time-server
36+
37+
jobs:
38+
build:
39+
if: github.event_name != 'pull_request' || !github.event.pull_request.draft
40+
name: Build fast-time-server image
41+
runs-on: ubuntu-latest
42+
timeout-minutes: 60
43+
permissions:
44+
contents: read
45+
packages: write
46+
47+
steps:
48+
- name: Checkout
49+
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
50+
with:
51+
persist-credentials: false
52+
53+
- name: Set up QEMU
54+
uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3
55+
with:
56+
platforms: arm64
57+
58+
- name: Set up Docker Buildx
59+
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
60+
with:
61+
version: latest
62+
63+
- name: Log in to GHCR
64+
if: github.event_name == 'push' || github.event_name == 'workflow_dispatch'
65+
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3
66+
with:
67+
registry: ${{ env.REGISTRY }}
68+
username: ${{ github.actor }}
69+
password: ${{ secrets.GITHUB_TOKEN }}
70+
71+
- name: Extract metadata
72+
id: meta
73+
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5
74+
with:
75+
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
76+
tags: |
77+
type=sha,format=long
78+
type=ref,event=tag
79+
type=raw,value=latest,enable={{is_default_branch}}
80+
81+
- name: Build and push
82+
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6
83+
with:
84+
context: .
85+
file: mcp-servers/rust/fast-time-server/Containerfile
86+
platforms: linux/amd64,linux/arm64
87+
push: ${{ github.event_name == 'push' || github.event_name == 'workflow_dispatch' }}
88+
tags: ${{ steps.meta.outputs.tags }}
89+
labels: ${{ steps.meta.outputs.labels }}
90+
cache-from: type=gha,scope=fast-time-server-image
91+
cache-to: type=gha,mode=max,scope=fast-time-server-image
92+
provenance: false

.github/workflows/sql-sanitizer.yml

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,8 @@ jobs:
7878
- name: Install Rust stable 1
7979
run: |
8080
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
81-
echo "$HOME/.cargo/bin" >> $GITHUB_PATH
82-
source $HOME/.cargo/env
81+
echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
82+
source "$HOME/.cargo/env"
8383
rustc --version
8484
8585
@@ -171,6 +171,7 @@ jobs:
171171
sleep 1
172172
173173
nohup target/debug/fast-time-server \
174+
-transport=dual -listen=0.0.0.0 -port=9080 -log-level=info \
174175
> fast-time-server.log 2>&1 &
175176
echo $! > fast-time-server.pid
176177
@@ -268,10 +269,10 @@ jobs:
268269
269270
- name: Local Node 22 Install
270271
run: |
271-
mkdir -p $GITHUB_WORKSPACE/node22
272+
mkdir -p "$GITHUB_WORKSPACE/node22"
272273
curl -fsSL https://nodejs.org/dist/v22.22.0/node-v22.22.0-linux-x64.tar.xz -o node22.tar.xz
273-
tar -xJf node22.tar.xz --strip-components=1 -C $GITHUB_WORKSPACE/node22
274-
$GITHUB_WORKSPACE/node22/bin/node -v
274+
tar -xJf node22.tar.xz --strip-components=1 -C "$GITHUB_WORKSPACE/node22"
275+
"$GITHUB_WORKSPACE/node22/bin/node" -v
275276
276277
- name: Test sql-sanitizer
277278
run: |

.github/workflows/wrapper.yml

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ on:
66
types: [opened, synchronize, reopened]
77
paths:
88
- "crates/wrapper/**"
9-
- "mcp-servers/go/fast-time-server/**"
9+
- "mcp-servers/rust/fast-time-server/**"
1010
- "Cargo.toml"
1111
- "Cargo.lock"
1212
- "rust-toolchain.toml"
@@ -123,25 +123,16 @@ jobs:
123123
-H "Authorization: Bearer ${{ steps.generate_jwt.outputs.token }}" \
124124
-H "Content-Type: application/json"
125125
126-
- name: Setup Go
127-
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
128-
with:
129-
go-version: "1.23"
130-
cache-dependency-path: mcp-servers/go/fast-time-server/go.sum
131-
cache: true
132-
133126
- name: Build fast-time-server
134-
working-directory: mcp-servers/go/fast-time-server
135127
run: |
136-
go build -o fast-time-server .
137-
chmod +x fast-time-server
128+
cargo build -p fast-time-server
138129
139130
- name: Start fast-time-server
140131
run: |
141132
pkill -9 -f "fast-time-server" 2>/dev/null || true
142133
sleep 1
143134
144-
nohup mcp-servers/go/fast-time-server/fast-time-server \
135+
nohup target/debug/fast-time-server \
145136
-transport=dual -listen=0.0.0.0 -port=8080 -log-level=info \
146137
> fast-time-server.log 2>&1 &
147138
echo $! > fast-time-server.pid

0 commit comments

Comments
 (0)