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
2 changes: 1 addition & 1 deletion .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ ENV USERNAME=${USERNAME}
ENV USER_UID=${USER_UID}
ENV USER_GID=${USER_GID}
USER root
RUN apt-get update && apt-get install -y less python3-pip python3-venv python3-build
RUN apt-get update && apt-get install -y less python3-pip python3-venv python3-build lsof

# Add the group and user that match our ids
RUN groupadd -g ${USER_GID} ${USERNAME} && \
Expand Down
20 changes: 16 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,20 @@ Get the `FLUX_URI` so we can connect from another terminal.
echo $FLUX_URI
```

You likely want to install the server to your local environment.

```bash
pip install -e . --break-system-packages
```

### Server

To start the demo server, either will work:
To start the server, we provide it with a configuration file with function definitions for Flux:

```bash
flux-mcp-server
flux-mcp-server --config ./mcpserver.yaml
# or
python3 -m flux_mcp_server.server
python3 -m flux_mcp_server.server --config ./mcpserver.yaml
```

![img/server.png](img/server.png)
Expand Down Expand Up @@ -73,6 +79,12 @@ python3 ./tests/test_submit.py
- [CLEAN]
```

If you accidentally kill the server (and the port is still alive):

```bash
kill $(lsof -t -i :8089)
```

### Development

```bash
Expand All @@ -95,7 +107,7 @@ pyproject-build
- Then (custom) something with passing OAuth2-like to submit as a Flux user.
- [x] Example: user manually submits a job, can query database for state
- [ ] Example: Agent submits work, and can find state later.
- [ ] Migrate to container, then Flux Operator / Kubernetes service.
- [x] Migrate to container, then Flux Operator / Kubernetes service.
- [ ] Tests in CI and automated build.

## License
Expand Down
21 changes: 0 additions & 21 deletions flux_mcp_server/registry.py

This file was deleted.

112 changes: 42 additions & 70 deletions flux_mcp_server/server/__main__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
#!/usr/bin/env python3

import argparse
import asyncio
import os
import warnings
from contextlib import asynccontextmanager
Expand All @@ -19,28 +18,22 @@
warnings.filterwarnings("ignore", category=DeprecationWarning, module="websockets.legacy")

from fastapi import FastAPI
from fastmcp.tools.tool import Tool
from mcpserver.app import init_mcp
from mcpserver.cli.args import populate_start_args
from mcpserver.cli.manager import get_manager
from mcpserver.core.config import MCPConfig
from mcpserver.routes import *

from flux_mcp_server.db import get_db
from flux_mcp_server.events.engine import EventsEngine
from flux_mcp_server.events.receiver import LocalReceiver
from flux_mcp_server.registry import TOOLS

from .app import init_mcp


def get_parser():
parser = argparse.ArgumentParser(description="Flux MCP Server")

# Server
parser.add_argument("--host", default="0.0.0.0", help="Host to bind to")
parser.add_argument("--port", type=int, default=8089, help="Port to listen on")
parser.add_argument(
"--transport", default="http", choices=["sse", "stdio", "http"], help="MCP Transport type"
)
parser.add_argument(
"--mount-to", default="/mcp", help="Mount path for server (defaults to /mcp)"
)
# Server start arguments (add port, host, config, function additions, etc.)
populate_start_args(parser)

# Database
parser.add_argument(
Expand All @@ -59,18 +52,6 @@ def get_parser():
return parser


def register_tools(server_instance):
"""
Registers selected tools from the registry with the server.
We want this to fail if a function does not register.
"""
print("🔌 Registering tools...")
for func in TOOLS:
tool_obj = Tool.from_function(func)
server_instance.add_tool(tool_obj)
print(f" ✅ Registered: {func.__name__}")


# TODO (vsoch) do we want to add other hooks?
_HOOKS = {"instance": None}

Expand Down Expand Up @@ -122,58 +103,49 @@ def main():
os.environ["FLUX_MCP_DATABASE"] = args.db_path

# Get the database instance, which can be any supported in sqlalchemy.
# TODO try subbing in here my mcp-server library
try:
db = get_db()
except Exception as e:
logger.exit(f"🌐 Database configuration error: {e}")

if args.config is not None:
print(f"📖 Loading config from {args.config}")
cfg = MCPConfig.from_yaml(args.config)
else:
cfg = MCPConfig.from_args(args)

# Initialize MCP server and register Flux functions
mcp = init_mcp()
register_tools(mcp)
mcp = init_mcp(cfg.exclude, cfg.include, args.mask_error_details)
get_manager(mcp, cfg)

# Force running with sse / http for now
# Note from vsoch: we should not be using sse (will be deprecated)
if args.transport in ["sse", "http"]:

mcp_app = mcp.http_app(path=args.mount_to)

@asynccontextmanager
async def lifespan(app: FastAPI):
# A. Custom Startup
await server_startup(args, db)

# B. Chain FastMCP Startup (Required for Task Groups/SSE)
# We use the router's context helper to activate the MCP app's internal logic
async with mcp_app.router.lifespan_context(app):
yield

# C. Custom Shutdown
await server_shutdown(db)

# Create ASGI app and mount to /mcp (or other destination)
app = FastAPI(title="Flux MCP", lifespan=lifespan)
app.mount("/", mcp_app)

print(f"🌍 Flux MCP Server listening on http://{args.host}:{args.port}")
try:
uvicorn.run(app, host=args.host, port=args.port)
# TODO: vsoch: this doesn't work with startup / shudown
# mcp.run(transport=args.transport, port=args.port, host=args.host)
except KeyboardInterrupt:
print("\n👋 Shutting down...")

elif args.transport == "stdio":

async def run_stdio():
await server_startup(args, db)
try:
await mcp.run_stdio()
finally:
await server_shutdown(db)

try:
asyncio.run(run_stdio())
except KeyboardInterrupt:
pass
if cfg.server.transport not in ["sse", "http"]:
raise ValueError("Currently supported transports: sse/http")

mcp_app = mcp.http_app(path=cfg.server.path)

@asynccontextmanager
async def lifespan(app: FastAPI):
await server_startup(args, db)

# We use the router's context helper to activate the MCP app's internal logic
async with mcp_app.router.lifespan_context(app):
yield
await server_shutdown(db)

# create ASGI app and mount to /mcp (or other destination)
app = FastAPI(title="Flux MCP", lifespan=lifespan)
app.mount("/", mcp_app)

print(f"🌍 Flux MCP Server listening on http://{cfg.server.host}:{cfg.server.port}")
try:
uvicorn.run(app, host=cfg.server.host, port=cfg.server.port)
# TODO: vsoch: this doesn't work with startup / shudown
# mcp.run(transport=args.transport, port=args.port, host=args.host)
except KeyboardInterrupt:
print("\n👋 Shutting down...")


if __name__ == "__main__":
Expand Down
51 changes: 0 additions & 51 deletions flux_mcp_server/server/app.py

This file was deleted.

1 change: 0 additions & 1 deletion flux_mcp_server/server/middleware/__init__.py

This file was deleted.

46 changes: 0 additions & 46 deletions flux_mcp_server/server/middleware/token_auth.py

This file was deleted.

3 changes: 2 additions & 1 deletion flux_mcp_server/version.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# TODO: delete and completely replace with pyproject.toml
__version__ = "0.0.1"
__version__ = "0.0.11"
AUTHOR = "Vanessa Sochat"
AUTHOR_EMAIL = "vsoch@users.noreply.github.com"
NAME = "flux-mcp-server"
Expand All @@ -17,6 +17,7 @@
("fastmcp", {"min_version": None}),
("rich", {"min_version": None}),
("fastapi", {"min_version": None}),
("mcp-serve", {"min_version": None}),
# For Flux
("pyyaml", {"min_version": None}),
("ply", {"min_version": None}),
Expand Down
14 changes: 14 additions & 0 deletions mcpserver.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
server:
transport: http
port: 8089
host: "0.0.0.0"

tools:
- path: flux_mcp.job.flux_handle_delegation
- path: flux_mcp.job.flux_submit_job
- path: flux_mcp.job.flux_cancel_job
- path: flux_mcp.job.flux_get_job_info

# prompts:
# - path: hpc_mcp.t.build.docker.docker_build_persona_prompt
# name: build_expert
2 changes: 2 additions & 0 deletions tests/test_submit.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,5 +107,7 @@ async def run_test():
)


# 278783281 jan 13 2026 feb 11 26

if __name__ == "__main__":
asyncio.run(run_test())