Skip to content

Commit aff8977

Browse files
committed
Merge branch 'feat/langchain-stdio-support'
2 parents a49f830 + cad7db9 commit aff8977

File tree

8 files changed

+103
-47
lines changed

8 files changed

+103
-47
lines changed

.github/workflows/release-docker.yml

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@ on:
2323
description: "Human-readable image title for OCI labels (e.g., AFM Ballerina Interpreter)"
2424
required: true
2525
type: string
26+
build_slim:
27+
description: "Whether to build and push a slim image variant"
28+
required: false
29+
default: false
30+
type: boolean
2631

2732
jobs:
2833
docker:
@@ -85,6 +90,7 @@ jobs:
8590
index:org.opencontainers.image.licenses=Apache-2.0
8691
8792
- name: Build and push slim image
93+
if: ${{ inputs.build_slim }}
8894
uses: docker/build-push-action@v5
8995
with:
9096
context: ${{ inputs.context }}
@@ -102,18 +108,37 @@ jobs:
102108
index:org.opencontainers.image.source=https://github.com/${{ github.repository }}
103109
index:org.opencontainers.image.licenses=Apache-2.0
104110
105-
- name: Scan Docker image for vulnerabilities
111+
- name: Scan full Docker image for vulnerabilities
106112
uses: aquasecurity/trivy-action@0.34.0
107113
with:
108114
image-ref: ${{ steps.docker-tags.outputs.FULL_IMAGE }}:v${{ inputs.version }}
109115
format: "sarif"
110-
output: "trivy-results.sarif"
116+
output: "trivy-results-full.sarif"
111117
severity: "CRITICAL,HIGH"
112118
limit-severities-for-sarif: true
113119
exit-code: "1"
114120

115-
- name: Upload Trivy scan results to GitHub Security tab
121+
- name: Upload full image Trivy scan results to GitHub Security tab
116122
uses: github/codeql-action/upload-sarif@v4
117123
if: always()
118124
with:
119-
sarif_file: "trivy-results.sarif"
125+
sarif_file: "trivy-results-full.sarif"
126+
category: "trivy-full-${{ inputs.image_name }}"
127+
128+
- name: Scan slim Docker image for vulnerabilities
129+
if: ${{ always() && inputs.build_slim }}
130+
uses: aquasecurity/trivy-action@0.34.0
131+
with:
132+
image-ref: ${{ steps.docker-tags.outputs.FULL_IMAGE }}:v${{ inputs.version }}-slim
133+
format: "sarif"
134+
output: "trivy-results-slim.sarif"
135+
severity: "CRITICAL,HIGH"
136+
limit-severities-for-sarif: true
137+
exit-code: "1"
138+
139+
- name: Upload slim image Trivy scan results to GitHub Security tab
140+
uses: github/codeql-action/upload-sarif@v4
141+
if: ${{ always() && inputs.build_slim }}
142+
with:
143+
sarif_file: "trivy-results-slim.sarif"
144+
category: "trivy-slim-${{ inputs.image_name }}"

.github/workflows/release-python.yml

Lines changed: 17 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,16 @@ on:
44
workflow_dispatch:
55
inputs:
66
package:
7-
description: 'Python package to release'
7+
description: "Python package to release"
88
required: true
99
type: choice
1010
options:
1111
- afm-core
1212
- afm-langchain
1313
branch:
14-
description: 'Branch to release from'
14+
description: "Branch to release from"
1515
required: false
16-
default: 'main'
16+
default: "main"
1717
type: string
1818
skip_pypi:
1919
description: 'Skip PyPI publishing'
@@ -126,12 +126,11 @@ jobs:
126126
pypi-publish:
127127
needs: [validate, test, docker]
128128
if: >-
129-
!cancelled()
130-
&& !inputs.skip_pypi
131-
&& needs.validate.result == 'success'
132-
&& needs.test.result == 'success'
133-
&& (needs.docker.result == 'success'
134-
|| needs.docker.result == 'skipped')
129+
!cancelled()
130+
&& needs.validate.result == 'success'
131+
&& needs.test.result == 'success'
132+
&& (needs.docker.result == 'success'
133+
|| needs.docker.result == 'skipped')
135134
runs-on: ubuntu-latest
136135
steps:
137136
- name: Checkout repository
@@ -187,19 +186,19 @@ jobs:
187186
version: ${{ needs.validate.outputs.release_version }}
188187
branch: ${{ inputs.branch }}
189188
image_title: AFM LangChain Interpreter
189+
build_slim: false
190190
permissions:
191191
packages: write
192192
security-events: write
193193

194194
finalize:
195195
needs: [validate, pypi-publish, docker]
196196
if: >-
197-
!cancelled()
198-
&& needs.validate.result == 'success'
199-
&& (needs.pypi-publish.result == 'success'
200-
|| needs.pypi-publish.result == 'skipped')
201-
&& (needs.docker.result == 'success'
202-
|| needs.docker.result == 'skipped')
197+
!cancelled()
198+
&& needs.validate.result == 'success'
199+
&& needs.pypi-publish.result == 'success'
200+
&& (needs.docker.result == 'success'
201+
|| needs.docker.result == 'skipped')
203202
uses: ./.github/workflows/release-finalize.yml
204203
with:
205204
tag: ${{ needs.validate.outputs.tag }}
@@ -214,9 +213,9 @@ jobs:
214213
bump-version:
215214
needs: [validate, finalize]
216215
if: >-
217-
!cancelled()
218-
&& needs.validate.result == 'success'
219-
&& needs.finalize.result == 'success'
216+
!cancelled()
217+
&& needs.validate.result == 'success'
218+
&& needs.finalize.result == 'success'
220219
runs-on: ubuntu-latest
221220
permissions:
222221
contents: write

ballerina-interpreter/Dependencies.toml

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
[ballerina]
77
dependencies-toml-version = "2"
8-
distribution-version = "2201.12.7"
8+
distribution-version = "2201.13.1"
99

1010
[[package]]
1111
org = "ballerina"
@@ -90,7 +90,7 @@ dependencies = [
9090
[[package]]
9191
org = "ballerina"
9292
name = "data.xmldata"
93-
version = "1.5.2"
93+
version = "1.6.0"
9494
dependencies = [
9595
{org = "ballerina", name = "jballerina.java"},
9696
{org = "ballerina", name = "lang.object"}
@@ -121,7 +121,7 @@ dependencies = [
121121
[[package]]
122122
org = "ballerina"
123123
name = "http"
124-
version = "2.14.9"
124+
version = "2.15.4"
125125
dependencies = [
126126
{org = "ballerina", name = "auth"},
127127
{org = "ballerina", name = "cache"},
@@ -154,7 +154,7 @@ modules = [
154154
[[package]]
155155
org = "ballerina"
156156
name = "io"
157-
version = "1.8.1"
157+
version = "1.8.0"
158158
dependencies = [
159159
{org = "ballerina", name = "jballerina.java"},
160160
{org = "ballerina", name = "lang.value"}
@@ -278,7 +278,7 @@ dependencies = [
278278
[[package]]
279279
org = "ballerina"
280280
name = "log"
281-
version = "2.14.0"
281+
version = "2.17.0"
282282
dependencies = [
283283
{org = "ballerina", name = "io"},
284284
{org = "ballerina", name = "jballerina.java"},
@@ -318,7 +318,7 @@ dependencies = [
318318
[[package]]
319319
org = "ballerina"
320320
name = "oauth2"
321-
version = "2.14.1"
321+
version = "2.15.0"
322322
dependencies = [
323323
{org = "ballerina", name = "cache"},
324324
{org = "ballerina", name = "crypto"},
@@ -331,7 +331,7 @@ dependencies = [
331331
[[package]]
332332
org = "ballerina"
333333
name = "observe"
334-
version = "1.6.0"
334+
version = "1.7.0"
335335
dependencies = [
336336
{org = "ballerina", name = "jballerina.java"}
337337
]
@@ -375,7 +375,7 @@ modules = [
375375
[[package]]
376376
org = "ballerina"
377377
name = "time"
378-
version = "2.8.1"
378+
version = "2.8.0"
379379
dependencies = [
380380
{org = "ballerina", name = "jballerina.java"}
381381
]

ballerina-interpreter/agent.bal

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,8 @@ function createAgent(AFMRecord afmRecord) returns ai:Agent|error {
3131
if mcpServers is MCPServer[] {
3232
foreach MCPServer mcpConn in mcpServers {
3333
Transport transport = mcpConn.transport;
34-
if transport.'type != "http" {
35-
log:printWarn(string `Unsupported transport type: ${transport.'type}, only 'http' is supported`);
36-
continue;
34+
if transport is StdioTransport {
35+
return error("Stdio transport is not yet supported by the Ballerina interpreter");
3736
}
3837

3938
string[]? filteredTools = getFilteredTools(mcpConn.tool_filter);

ballerina-interpreter/parser.bal

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -246,12 +246,38 @@ function validateHttpVariables(AFMRecord afmRecord) returns error? {
246246
}
247247

248248
Transport transport = server.transport;
249-
if containsHttpVariable(transport.url) {
250-
erroredKeys.push("tools.mcp.transport.url");
251-
}
252-
253-
if authenticationContainsHttpVariable(transport.authentication) {
254-
erroredKeys.push("tools.mcp.transport.authentication");
249+
if transport is HttpTransport {
250+
if containsHttpVariable(transport.url) {
251+
erroredKeys.push("tools.mcp.transport.url");
252+
}
253+
254+
if authenticationContainsHttpVariable(transport.authentication) {
255+
erroredKeys.push("tools.mcp.transport.authentication");
256+
}
257+
} else {
258+
if containsHttpVariable(transport.command) {
259+
erroredKeys.push("tools.mcp.transport.command");
260+
}
261+
262+
string[]? args = transport.args;
263+
if args is string[] {
264+
foreach string arg in args {
265+
if containsHttpVariable(arg) {
266+
erroredKeys.push("tools.mcp.transport.args");
267+
break;
268+
}
269+
}
270+
}
271+
272+
map<string>? env = transport.env;
273+
if env is map<string> {
274+
foreach string val in env {
275+
if containsHttpVariable(val) {
276+
erroredKeys.push("tools.mcp.transport.env");
277+
break;
278+
}
279+
}
280+
}
255281
}
256282

257283
if toolFilterContainsHttpVariable(server.tool_filter) {

ballerina-interpreter/types.bal

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,25 @@ type Model record {|
2727
|};
2828

2929
enum TransportType {
30-
http
30+
http,
31+
stdio
3132
}
3233

33-
type Transport record {|
34+
type HttpTransport record {|
3435
http 'type = http;
3536
string url;
3637
ClientAuthentication authentication?;
3738
|};
3839

40+
type StdioTransport record {|
41+
stdio 'type = stdio;
42+
string command;
43+
string[] args?;
44+
map<string> env?;
45+
|};
46+
47+
type Transport HttpTransport|StdioTransport;
48+
3949
type ClientAuthentication record {
4050
string 'type;
4151
};

python-interpreter/Dockerfile

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@ ARG VARIANT=full
3535

3636
RUN if [ "$VARIANT" = "full" ]; then \
3737
apk add --no-cache nodejs npm git && \
38-
echo "Full variant: nodejs, npm, git installed"; \
3938
fi
4039

4140
# Install uv and uvx for Python-based MCP server support (full variant only)

python-interpreter/packages/afm-langchain/src/afm_langchain/tools/mcp.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,6 @@
1919
import logging
2020

2121
import httpx
22-
from langchain_core.tools import BaseTool
23-
from langchain_mcp_adapters.client import MultiServerMCPClient
24-
from langchain_mcp_adapters.sessions import StdioConnection, StreamableHttpConnection
25-
2622
from afm.exceptions import (
2723
MCPAuthenticationError,
2824
MCPConnectionError,
@@ -36,6 +32,9 @@
3632
StdioTransport,
3733
ToolFilter,
3834
)
35+
from langchain_core.tools import BaseTool
36+
from langchain_mcp_adapters.client import MultiServerMCPClient
37+
from langchain_mcp_adapters.sessions import StdioConnection, StreamableHttpConnection
3938

4039
logger = logging.getLogger(__name__)
4140

@@ -164,7 +163,6 @@ def _build_connection_config(self) -> StreamableHttpConnection | StdioConnection
164163
return config
165164

166165
else:
167-
# StdioTransport
168166
config: StdioConnection = {
169167
"transport": "stdio",
170168
"command": self.transport.command,

0 commit comments

Comments
 (0)