Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions .github/workflows/main.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ on:
- "*.*.*"
branches:
- actions*

jobs:
build_linux:
name: Build wheels on ubuntu-latest ${{ matrix.arch }} ${{ matrix.python }}
Expand Down Expand Up @@ -76,3 +77,31 @@ jobs:
with:
files: |
./wheelhouse/**
build_mcp_server:
name: Build and Test MCP Server Container
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.11"]
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5 # Use the latest version of setup-python
with:
python-version: ${{ matrix.python-version }}
cache: 'pip' # Option
- name: Set up Docker
uses: docker/setup-docker-action@v4
- name: Build MCP Server Container
run: docker build -f mcp_server/Dockerfile.mcp -t kstar-planner:v0.0 .
- name: Run MCP Server Container
run: docker run -p 8000:8000 --name kstar-planner -d kstar-planner:v0.0
- name: Install Test dependencies
run: |
pip install -r mcp_server/requirements.txt
pip install -r mcp_server/requirements_test.txt
- name: Test MCP Server Container
env:
MCP_SERVER_URL: http://localhost:8000/mcp
run: python -m pytest mcp_server/tests/test_container.py
9 changes: 0 additions & 9 deletions mcp/Dockerfile.mcp

This file was deleted.

7 changes: 7 additions & 0 deletions mcp_server/.vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"python.testing.pytestArgs": [
"tests"
],
"python.testing.unittestEnabled": false,
"python.testing.pytestEnabled": true
}
9 changes: 9 additions & 0 deletions mcp_server/Dockerfile.mcp
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
FROM python:3.11

RUN apt-get update && apt-get install -y cmake make g++
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need cmake and g++?

Copy link
Contributor Author

@jkeskingvillage jkeskingvillage Oct 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line was supposed to be checked at your pull request for using K* package binary. Did you check it?

COPY mcp_server /src/mcp_server
WORKDIR /src
RUN pip install -r mcp_server/requirements.txt

EXPOSE 8000
CMD ["fastmcp", "run", "mcp_server/server.py:mcp", "--transport", "http", "--port", "8000", "--host", "0.0.0.0"]
4 changes: 2 additions & 2 deletions mcp/README.md → mcp_server/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ K* MCP Server provides a containerized deployment of Top-K and Top-Q planners fr
To build the Docker image, navigate to the project root directory (where the `pyproject.toml` is located) and execute the following command:

```bash
docker build -f mcp/Dockerfile.mcp -t kstar-planner:v0.0 .
docker build -f mcp_server/Dockerfile.mcp -t kstar-planner:v0.0 .
```

* `-f mcp/Dockerfile.mcp`: Specifies the Dockerfile to use for building the image.
* `-f mcp_server/Dockerfile.mcp`: Specifies the Dockerfile to use for building the image.
* `-t kstar-planner:v0.0`: Tags the image as `kstar-planner` with the version `v0.0`. You can replace `v0.0` with your desired version tag.
* `.`: Indicates that the build context is the current directory.

Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
3 changes: 3 additions & 0 deletions mcp_server/requirements_test.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pytest
pytest-asyncio
mcp[cli]
File renamed without changes.
Empty file added mcp_server/tests/__init__.py
Empty file.
Empty file.
Empty file.
56 changes: 56 additions & 0 deletions mcp_server/tests/data/pddl/pddl_example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
domain = """
(define (domain blocksworld)
(:requirements :strips :typing)
(:types block)

(:predicates
(on ?x ?y - block) ; Block ?x is on top of block ?y
(ontable ?x - block) ; Block ?x is on the table
(clear ?x - block) ; Nothing is on top of block ?x
(handempty) ; The robot arm is empty
(holding ?x - block) ; The robot arm is holding block ?x
)

(:action pick-up
:parameters (?x - block)
:precondition (and (clear ?x) (ontable ?x) (handempty))
:effect (and (not (ontable ?x)) (not (clear ?x)) (not (handempty)) (holding ?x))
)

(:action put-down
:parameters (?x - block)
:precondition (holding ?x)
:effect (and (not (holding ?x)) (clear ?x) (handempty) (ontable ?x))
)

(:action stack
:parameters (?x ?y - block)
:precondition (and (holding ?x) (clear ?y))
:effect (and (not (holding ?x)) (not (clear ?y)) (clear ?x) (handempty) (on ?x ?y))
)

(:action unstack
:parameters (?x ?y - block)
:precondition (and (on ?x ?y) (clear ?x) (handempty))
:effect (and (not (on ?x ?y)) (not (clear ?x)) (clear ?y) (holding ?x) (not (handempty)))
)
)
"""

problem = """
(define (problem p01)
(:domain blocksworld)
(:objects A B - block) ; Two blocks, A and B
(:init
(ontable A) ; Block A is on the table
(on B A) ; Block B is on top of block A
(clear B) ; Nothing is on top of B
(handempty) ; The arm is empty
)
(:goal (and
(on A B) ; Block A should be on block B
(clear A) ; Block A should be clear
(ontable B) ; Block B should be on the table
))
)
"""
32 changes: 32 additions & 0 deletions mcp_server/tests/test_container.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import os
import pytest
from mcp_server.tests.data.pddl.pddl_example import domain, problem
from fastmcp import Client
from fastmcp.client.transports import StreamableHttpTransport


def get_client(server_url) -> Client:
transport = StreamableHttpTransport(url=server_url)
return Client(transport)

class TestMcpContainer:
@pytest.mark.skipif(
"MCP_SERVER_URL" not in os.environ, reason="Requires MCP_SERVER_URL to be set"
)
@pytest.mark.asyncio
async def test_planner_tool_t(self) -> None:
client = get_client(os.getenv("MCP_SERVER_URL", "http://localhost:8000/mcp"))

async with client:
tool_list = await client.list_tools()
assert tool_list is not None

payload = await client.call_tool(
"KstarPlannerUnorderedTopQ",
{"domain": domain, "problem": problem},
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should explicitly provide the important parameters here without depending on defaults not changing:
quality_bound: float = 1.0,
num_plans: int = 10
If the quality bound changes, there might be more than one plan returned.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This pull request is for establishing the CI pipeline for K* MCP Server nothing else.

)
assert payload is not None
assert len(payload.structured_content["plans"]) == 1
optimal_plan = payload.structured_content["plans"][0]
assert len(optimal_plan["actions"]) == 4
assert optimal_plan["cost"] == 4
Loading