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
3 changes: 2 additions & 1 deletion .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@
},
"ghcr.io/devcontainers/features/node:1": {
"version": "lts"
}
},
"ghcr.io/jsburckhardt/devcontainer-features/uv:1": {}
},
"customizations": {
"vscode": {
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,5 @@ implants/golem/embed_files_golem_prod/*

# Profiling
.pprof/

.venv
1 change: 1 addition & 0 deletions bin/tavern-mcp/.python-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.12
13 changes: 13 additions & 0 deletions bin/tavern-mcp/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
## Setup
```bash
uv venv
source .venv/bin/activate
uv sync

# Modify server_config.json with your REALM session token and domain

read -s OPENAI_API_KEY
sk-...5pgA
export OPENAI_API_KEY
mcp-cli chat --server tavern --model o4-mini --disable-filesystem
```
15 changes: 15 additions & 0 deletions bin/tavern-mcp/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[project]
name = "tavern-mcp"
version = "0.1.0"
description = "MCP server for Tavern c2 server API"
readme = "README.md"
requires-python = ">=3.12"
dependencies = [
"httpx>=0.28.1",
"mcp-cli",
"mcp[cli]>=1.6.0",
"requests>=2.32.3",
]

[tool.uv.sources]
mcp-cli = { git = "https://github.com/hulto/mcp-cli" }
19 changes: 19 additions & 0 deletions bin/tavern-mcp/server_config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"mcpServers": {
"tavern": {
"type": "stdio",
"command": "uv",
"args": [
"--directory", "/workspaces/realm/bin/tavern-mcp",
"run", "./tavern.py",
"--type", "stdio",
"--url", "https://example.com/graphql",
"--schema", "../../tavern/internal/graphql/schema.graphql"
],
"env": {
"TAVERN_AUTH_SESSION": "bps...zlw==",
"PATH": "/workspaces/realm/bin/tavern-mcp/.venv/bin:/vscode/vscode-server/bin/linux-x64/4949701c880d4bdb949e3c0e6b400288da7f474b/bin/remote-cli:/home/vscode/.local/bin:/usr/local/cargo/bin:/usr/local/python/current/bin:/usr/local/py-utils/bin:/usr/local/jupyter:/usr/local/share/nvm/versions/node/v22.14.0/bin:/usr/local/go/bin:/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/home/vscode/.vscode-server/extensions/ms-python.debugpy-2025.6.0-linux-x64/bundled/scripts/noConfigScripts:/home/vscode/.vscode-server/data/User/globalStorage/github.copilot-chat/debugCommand"
}
}
}
}
127 changes: 127 additions & 0 deletions bin/tavern-mcp/tavern.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import argparse
from dataclasses import dataclass
import os
from typing import Any
from pprint import pprint
from mcp.server.fastmcp import FastMCP
from mcp.server import Server
import mcp.types as types
from mcp.server.fastmcp import FastMCP
from mcp.server.fastmcp.prompts import base

import requests
import logging

logger = logging.getLogger(__name__)

# Initialize FastMCP server
mcp = FastMCP("tavern")

# Constants
USER_AGENT = "tavern-mcp/1.0"

MCPCLIENT = None


@dataclass
class TavernMCP:
graphql_url: str
auth_session: str
schema: str

def make_graphql_request(self, query, variables):
headers = {
"Content-Type": "application/json",
"Accept": "application/json",
}

data = {"query": query, "variables": variables}
cookies = {
"auth-session": self.auth_session,
}

response = requests.post(
self.graphql_url, json=data, headers=headers, cookies=cookies)
if response.status_code == 200:
return response.json()
else:
logging.error(f"Error {response.status_code}: {response.text}")
return None

def get_schema(self):
return self.schema


@mcp.tool()
async def get_schema() -> str:
"""Introspect the graphql Schema

Return the full graphql schema
"""
res = MCPCLIENT.get_schema()
return str(res)


@mcp.tool()
async def query_graphql(query: str, variables: dict) -> str:
logging.debug(f"query_graphql tool called with {query} {variables}")
"""Query the Tavern graphql API

Args:
query: The graphql formatted query string Eg. query getTag($input:TagWhereInput){ tags(where:$input) { id }}
variables: A dictionary defining the graphql query variables Eg. {"input": {"name": tag_name}}
"""
res = MCPCLIENT.make_graphql_request(query, variables)
return str(res)


@mcp.prompt()
def debug_error(error: str) -> list[base.Message]:
return [
base.UserMessage("I'm seeing this error:"),
base.UserMessage(error),
base.AssistantMessage(
"I'll help debug that. What have you tried so far?"),
]


if __name__ == "__main__":
logging.basicConfig(format='%(levelname)s:%(message)s',
level=logging.INFO)

parser = argparse.ArgumentParser(
prog="Tavern MCP server",
description="An MCP server exposing tools to interact with Tavern c2 server",
)

parser.add_argument(
"--type",
choices=["stdio", "sse"],
default="stdio",
help="The communication protocol the MCP server should use"
)

parser.add_argument(
"--url",
help="The URL of tavern example: `http://example.com/graphql`"
)

parser.add_argument('-s', '--schema',
help="Optional path to the schema file")

args = parser.parse_args()

auth_session = os.environ.get("TAVERN_AUTH_SESSION")

if auth_session is None:
print(
"No auth-session cookie found. Please set it using the environment variable TAVERN_AUTH_SESSION"
)
exit(1)

graphql_url = f"{args.url}"

days_file = open(args.schema, 'r')
graphql_schema = days_file.read()
MCPCLIENT = TavernMCP(graphql_url, auth_session, graphql_schema)
mcp.run(transport=args.type)
Loading