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