Skip to content

Commit 36dbf1a

Browse files
committed
Adds writing to W&B Report
1 parent 495fa9f commit 36dbf1a

File tree

2 files changed

+409
-118
lines changed

2 files changed

+409
-118
lines changed

src/mcp_server/report.py

+186
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
#!/usr/bin/env python
2+
"""
3+
Weave MCP Server - Report creation and editing functionality for W&B reports.
4+
"""
5+
6+
import logging
7+
from pathlib import Path
8+
from typing import Dict, List, Optional, Union
9+
import re
10+
11+
import wandb_workspaces.reports.v2 as wr
12+
from dotenv import load_dotenv
13+
from wandb_workspaces.reports.v2 import H1, H2, H3, P, TableOfContents
14+
15+
import wandb
16+
17+
# Load environment variables
18+
load_dotenv(dotenv_path=Path(__file__).parent.parent.parent / ".env")
19+
20+
# Configure logging
21+
logging.basicConfig(level=logging.INFO)
22+
logger = logging.getLogger("weave-mcp-server")
23+
24+
def create_report(
25+
entity_name: str,
26+
project_name: str,
27+
title: str,
28+
description: Optional[str] = None,
29+
markdown_report_text: str = None,
30+
plots_html: Optional[Dict[str, str]] = None
31+
) -> wr.Report:
32+
"""
33+
Create a new Weights & Biases Report and add text and charts. Useful to save/document analysis and other findings.
34+
35+
Args:
36+
entity_name: The W&B entity (team or username)
37+
project_name: The W&B project name
38+
title: Title of the W&B Report
39+
description: Optional description of the W&B Report
40+
blocks: Optional list of W&B Report blocks (headers, paragraphs and tables of contents etc.)
41+
plot_htmls: Optional dict of plot name and html string of any charts created as part of an analysis
42+
"""
43+
try:
44+
wandb.init(
45+
entity=entity_name,
46+
project=project_name,
47+
job_type="mcp_report_creation"
48+
)
49+
50+
# Initialize the report
51+
report = wr.Report(
52+
entity=entity_name,
53+
project=project_name,
54+
title=title,
55+
description=description or "",
56+
width='fluid'
57+
)
58+
59+
# Log plots
60+
plots_dict = {}
61+
if plots_html:
62+
for plot_name, html in plots_html.items():
63+
wandb.log({plot_name: wandb.Html(html)})
64+
plots_dict[plot_name] = html
65+
wandb.finish()
66+
67+
pg = []
68+
for k,v in plots_dict.items():
69+
pg.append(wr.PanelGrid(
70+
panels=[
71+
wr.MediaBrowser(
72+
media_keys=[k],
73+
num_columns=1,
74+
layout=wr.Layout(w=20, h=60) #, x=5, y=5)
75+
),
76+
]
77+
))
78+
else:
79+
pg = None
80+
81+
blocks = parse_report_content_enhanced(markdown_report_text)
82+
83+
# Add blocks if provided
84+
if pg:
85+
report.blocks = blocks + pg
86+
else:
87+
report.blocks = blocks
88+
89+
print(report.blocks)
90+
91+
# Save the report
92+
report.save()
93+
logger.info(f"Created report: {title}")
94+
return report.url
95+
96+
except Exception as e:
97+
logger.error(f"Error creating report: {e}")
98+
raise
99+
100+
def edit_report(
101+
report_url: str,
102+
title: Optional[str] = None,
103+
description: Optional[str] = None,
104+
blocks: Optional[List[Union[wr.H1, wr.H2, wr.H3, wr.PanelGrid]]] = None,
105+
append_blocks: bool = False
106+
) -> wr.Report:
107+
"""
108+
Edit an existing W&B report.
109+
110+
Args:
111+
report_url: The URL of the report to edit
112+
title: Optional new title for the report
113+
description: Optional new description for the report
114+
blocks: Optional list of blocks to update or append
115+
append_blocks: If True, append new blocks to existing ones. If False, replace existing blocks.
116+
"""
117+
try:
118+
# Load the existing report
119+
report = wr.Report.from_url(report_url)
120+
121+
# Update title if provided
122+
if title:
123+
report.title = title
124+
125+
# Update description if provided
126+
if description:
127+
report.description = description
128+
129+
# Update blocks if provided
130+
if blocks:
131+
if append_blocks:
132+
# Append new blocks to existing ones
133+
report.blocks = (report.blocks or []) + blocks
134+
else:
135+
# Replace existing blocks
136+
report.blocks = blocks
137+
138+
# Save the changes
139+
report.save()
140+
141+
logger.info(f"Updated report: {report.title}")
142+
return report
143+
144+
except Exception as e:
145+
logger.error(f"Error editing report: {e}")
146+
raise
147+
148+
def parse_report_content_enhanced(text: str) -> List[Union[wr.H1, wr.H2, wr.H3, wr.P, wr.TableOfContents]]:
149+
"""
150+
Parse markdown-like text into W&B report blocks with paragraph grouping.
151+
"""
152+
blocks = []
153+
lines = text.strip().split('\n')
154+
155+
current_paragraph = []
156+
157+
for line in lines:
158+
# Check if this is a special line (header or TOC)
159+
h1_match = re.match(r'^# (.+)$', line)
160+
h2_match = re.match(r'^## (.+)$', line)
161+
h3_match = re.match(r'^### (.+)$', line)
162+
is_toc = line.strip().lower() == '[toc]'
163+
164+
# If we hit a special line and have paragraph content, finalize the paragraph
165+
if (h1_match or h2_match or h3_match or is_toc) and current_paragraph:
166+
blocks.append(wr.P("\n".join(current_paragraph)))
167+
current_paragraph = []
168+
169+
# Handle the current line
170+
if h1_match:
171+
blocks.append(wr.H1(h1_match.group(1)))
172+
elif h2_match:
173+
blocks.append(wr.H2(h2_match.group(1)))
174+
elif h3_match:
175+
blocks.append(wr.H3(h3_match.group(1)))
176+
elif is_toc:
177+
blocks.append(wr.TableOfContents())
178+
else:
179+
if line.strip(): # Only add non-empty lines
180+
current_paragraph.append(line)
181+
182+
# Don't forget any remaining paragraph content
183+
if current_paragraph:
184+
blocks.append(wr.P("\n".join(current_paragraph)))
185+
186+
return blocks

0 commit comments

Comments
 (0)