Skip to content

Commit 31ff87f

Browse files
authored
Merge pull request #225 from MISP/add-mcp-server
add: mcp server
2 parents 2ac1688 + a749daf commit 31ff87f

32 files changed

Lines changed: 4103 additions & 921 deletions

.env.dev.dist

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ PGADMIN_DEFAULT_PASSWORD=Admin123
3333
MODULES_HOST=modules
3434
MODULES_PORT=6666
3535

36+
MCP_AUTH_ENABLED=false
37+
3638
STORAGE_ENGINE=local
3739

3840
S3_ENDPOINT=garage:3900
@@ -46,3 +48,6 @@ GARAGE_ADMIN_TOKEN=garage-dev-admin-token
4648
GARAGE_METRICS_TOKEN=garage-dev-metrics-token
4749

4850
FLOWER_BASIC_AUTH=flower:flower
51+
52+
# Logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL)
53+
LOG_LEVEL=INFO

.env.dist

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ MAIL_SERVER=smtp
2929
MODULES_HOST=modules
3030
MODULES_PORT=6666
3131

32+
MCP_AUTH_ENABLED=true
33+
3234
STORAGE_ENGINE=s3
3335

3436
S3_ENDPOINT=garage:3900
@@ -41,4 +43,6 @@ GARAGE_ADMIN_URL=http://garage:3903
4143
GARAGE_ADMIN_TOKEN=
4244
GARAGE_METRICS_TOKEN=
4345

44-
FLOWER_BASIC_AUTH=flower:flower
46+
FLOWER_BASIC_AUTH=flower:flower
47+
48+
LOG_LEVEL=WARNING

.env.test

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ MAIL_SERVER=mailhog
3030
MODULES_HOST=modules
3131
MODULES_PORT=6666
3232

33+
MCP_AUTH_ENABLED=true
34+
3335
STORAGE_ENGINE=local
3436

3537
S3_ENDPOINT=garage:3900
@@ -43,3 +45,5 @@ GARAGE_ADMIN_TOKEN=garage-test-admin-token
4345
GARAGE_METRICS_TOKEN=garage-test-metrics-token
4446

4547
FLOWER_BASIC_AUTH=flower:flower
48+
49+
LOG_LEVEL=DEBUG

.github/workflows/api_test.yml

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,43 @@ jobs:
9191
PYTHONDONTWRITEBYTECODE: 1
9292
run: poetry install --no-interaction --no-root
9393

94+
- name: Create OpenSearch indices
95+
run: |
96+
OPENSEARCH_URL="http://localhost:9200"
97+
98+
echo "Waiting for OpenSearch..."
99+
for i in $(seq 1 30); do
100+
curl -s "${OPENSEARCH_URL}" >/dev/null 2>&1 && break
101+
echo "Not ready yet ($i/30), retrying in 3s..."
102+
sleep 3
103+
done
104+
105+
echo "Creating ingest pipelines..."
106+
for file in opensearch/pipelines/*.json; do
107+
NAME=$(basename "$file" .json)
108+
echo " pipeline: $NAME"
109+
curl -s -X PUT "${OPENSEARCH_URL}/_ingest/pipeline/${NAME}" \
110+
-H "Content-Type: application/json" -d "@${file}"
111+
done
112+
113+
echo "Creating index templates..."
114+
for file in opensearch/index-templates/*.json; do
115+
NAME=$(basename "$file" .json)
116+
echo " template: $NAME"
117+
curl -s -X PUT "${OPENSEARCH_URL}/_index_template/${NAME}" \
118+
-H "Content-Type: application/json" -d "@${file}"
119+
done
120+
121+
echo "Creating indices..."
122+
for file in opensearch/mappings/*.json; do
123+
NAME=$(basename "$file" .json)
124+
echo " index: $NAME"
125+
curl -s -X PUT "${OPENSEARCH_URL}/${NAME}" \
126+
-H "Content-Type: application/json" -d "@${file}"
127+
done
128+
129+
echo "OpenSearch setup complete."
130+
94131
- name: Run DB migrations
95132
working-directory: api
96133
run: |
@@ -137,6 +174,7 @@ jobs:
137174
MODULES_PORT: 6666
138175
STORAGE_ENGINE: local
139176
FLOWER_BASIC_AUTH: flower:flower
177+
MCP_AUTH_ENABLED: true
140178
- name: Upload coverage to Codecov
141179
uses: codecov/codecov-action@v4
142180
with:

.mcp.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"mcpServers": {
3+
"misp-workbench": {
4+
"type": "http",
5+
"url": "http://localhost:8080/mcp"
6+
}
7+
}
8+
}

api/app/auth/auth.py

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,6 @@
1515
from sqlalchemy.orm import Session
1616
from app.services.redis import get_redis_client
1717

18-
# see: https://fastapi.tiangolo.com/tutorial/security/oauth2-jwt/
19-
# see: https://fastapi.tiangolo.com/advanced/security/
20-
2118

2219
class Token(BaseModel):
2320
access_token: str
@@ -124,6 +121,29 @@ class TokenData(BaseModel):
124121
"hunts:run": "Run hunts.",
125122
"notifications:read": "Read notifications.",
126123
"notifications:update": "Update notifications.",
124+
"mcp:list_tools": "List MCP tools.",
125+
"mcp:list_resources": "List MCP resources.",
126+
"mcp:list_prompts": "List MCP prompts.",
127+
"mcp:search_events": "Search events via MCP.",
128+
"mcp:search_attributes": "Search attributes via MCP.",
129+
"mcp:get_event": "Get event details via MCP.",
130+
"mcp:get_correlations": "Get correlations via MCP.",
131+
"mcp:detect_indicator_type": "Detect indicator types via MCP.",
132+
"mcp:get_statistics": "Get statistics via MCP.",
133+
"mcp:get_tags": "List tags via MCP.",
134+
"mcp:get_index_mapping": "Get index mappings via MCP.",
135+
"mcp:search_galaxy": "Search galaxy clusters via MCP.",
136+
"mcp:search_taxonomy": "Search taxonomies via MCP.",
137+
"mcp:get_sightings": "Get sightings via MCP.",
138+
"mcp:list_hunts": "List hunts via MCP.",
139+
"mcp:get_hunt_results": "Get hunt results via MCP.",
140+
"mcp:run_hunt": "Run hunts via MCP.",
141+
"mcp:config": "Generate an MCP client configuration token.",
142+
"mcp:get_event_reports": "Get event reports via MCP.",
143+
"mcp:search_event_reports": "Search event reports via MCP.",
144+
"mcp:create_event_report": "Create event reports via MCP.",
145+
"mcp:get_notifications": "Get user notifications via MCP.",
146+
"mcp:list_modules": "List enrichment modules via MCP.",
127147
},
128148
)
129149

@@ -226,6 +246,7 @@ def get_scopes_for_user(user: user_schemas.User):
226246
scopes.add("settings:*")
227247
scopes.add("hunts:*")
228248
scopes.add("notifications:*")
249+
scopes.add("mcp:*")
229250

230251
if user.role.perm_auth:
231252
scopes.add("auth:login")

api/app/cli/__main__.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from typing import Optional
2+
13
import typer
24
from app.database import SessionLocal
35
from app.repositories import organisations as organisations_repository
@@ -46,12 +48,12 @@ def create_user(email: str, password: str, organisation_id: int, role_id: int):
4648

4749

4850
@app.command()
49-
def load_galaxies(user_id: int = None):
51+
def load_galaxies(user_id: Optional[int] = None):
5052
tasks.load_galaxies.delay(user_id)
5153

5254

5355
@app.command()
54-
def load_taxonomies(user_id: int = None):
56+
def load_taxonomies(user_id: Optional[int] = None):
5557
tasks.load_taxonomies.delay()
5658

5759

api/app/main.py

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
feeds,
1010
galaxies,
1111
hunts,
12+
mcp,
1213
modules,
1314
object_templates,
1415
objects,
@@ -32,17 +33,27 @@
3233
from fastapi_pagination import add_pagination
3334

3435
# setup loggers
35-
logging.config.fileConfig("logging.conf", disable_existing_loggers=False)
36+
# Import and use our custom logging setup
37+
import sys
38+
import os
39+
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
40+
from setup_logging import setup_logging
41+
42+
setup_logging()
43+
44+
# MCP server (mounted as ASGI sub-app)
45+
mcp_app = mcp.mcp.http_app(path="/")
3646

3747
# Bootstrap application
38-
app = FastAPI(title="misp-workbench API", version="0.1.0")
48+
app = FastAPI(title="misp-workbench API", version="0.1.0", lifespan=mcp_app.lifespan)
3949

4050
# Add CORS
4151
origins = [
4252
"http://localhost",
4353
"http://localhost:8080",
4454
"http://localhost:3000",
4555
"http://localhost:3001",
56+
"http://localhost:6274",
4657
]
4758
app.add_middleware(
4859
CORSMiddleware,
@@ -127,4 +138,10 @@
127138
# Notifications resource
128139
app.include_router(notifications.router, tags=["Notifications"])
129140

141+
# MCP config endpoint (must be registered before the /mcp mount)
142+
app.include_router(mcp.router, tags=["MCP"])
143+
144+
# MCP server (Streamable HTTP at /mcp)
145+
app.mount("/mcp", mcp_app)
146+
130147
add_pagination(app)

0 commit comments

Comments
 (0)