harvesthealth commited on
Commit
ed01d4b
·
verified ·
1 Parent(s): 4798ce2

Upload folder using huggingface_hub

Browse files
Files changed (4) hide show
  1. api/analysis_api.txt +367 -0
  2. app.py +25 -6
  3. mcp/mcp_analysis.py +249 -0
  4. mcp/servers.txt +29 -29
api/analysis_api.txt ADDED
@@ -0,0 +1,367 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Analysis API
2
+
3
+
4
+
5
+ Introduction
6
+ The Analysis API lets you programmatically access Analysis's capabilities to automate and enhance your software development lifecycle. You can use the API to create custom workflows, automate tasks like bug fixing and code reviews, and embed Analysis's intelligence directly into the tools you use every day, such as Slack, Linear, and GitHub.
7
+
8
+ Note: The Analysis API is in an alpha release, which means it is experimental. Be aware that we may change specifications, API keys, and definitions as we work toward stabilization. In the future, we plan to maintain at least one stable and one experimental version.
9
+
10
+ Authentication
11
+ To get started with the Analysis API, you'll need an API key.
12
+
13
+ Generate Your API Key
14
+ In the Analysis web app, go to the Settings page to create a new API key. You can have at most 3 API keys at a time.
15
+
16
+ Analysis API Key creation interface
17
+ Use Your API Key
18
+ To authenticate your requests, pass the API key in the X-Goog-Api-Key header of your API calls.
19
+
20
+ Important: Keep your API keys secure. Don't share them or embed them in public code. For your protection, any API keys found to be publicly exposed will be automatically disabled to prevent abuse.
21
+
22
+ API concepts
23
+ The Analysis API is built around a few core resources. Understanding these will help you use the API effectively.
24
+
25
+ Source
26
+ An input source for the agent (e.g., a GitHub repository). Before using a source using the API, you must first install the Analysis GitHub app through the Analysis web app.
27
+ Session
28
+ A continuous unit of work within a specific context, similar to a chat session. A session is initiated with a prompt and a source.
29
+ Activity
30
+ A single unit of work within a Session. A Session contains multiple activities from both the user and the agent, such as generating a plan, sending a message, or updating progress.
31
+ Quickstart: Your first API call
32
+ We'll walk through creating your first session with the Analysis API using curl.
33
+
34
+ Step 1: List your available sources
35
+ First, you need to find the name of the source you want to work with (e.g., your GitHub repo). This command will return a list of all sources you have connected to Analysis.
36
+
37
+
38
+ curl 'https://jules.googleapis.com/v1alpha/sources' \
39
+ -H 'X-Goog-Api-Key: YOUR_API_KEY'
40
+ The response will look something like this:
41
+
42
+
43
+ {
44
+ "sources": [
45
+ {
46
+ "name": "sources/github/bobalover/boba",
47
+ "id": "github/bobalover/boba",
48
+ "githubRepo": {
49
+ "owner": "bobalover",
50
+ "repo": "boba"
51
+ }
52
+ }
53
+ ],
54
+ "nextPageToken": "github/bobalover/boba-web"
55
+ }
56
+ Step 2: Create a new session
57
+ Now, create a new session. You'll need the source name from the previous step. This request tells Analysis to create a boba app in the specified repository.
58
+
59
+
60
+ curl 'https://jules.googleapis.com/v1alpha/sessions' \
61
+ -X POST \
62
+ -H "Content-Type: application/json" \
63
+ -H 'X-Goog-Api-Key: YOUR_API_KEY' \
64
+ -d '{
65
+ "prompt": "Create a boba app!",
66
+ "sourceContext": {
67
+ "source": "sources/github/bobalover/boba",
68
+ "githubRepoContext": {
69
+ "startingBranch": "main"
70
+ }
71
+ },
72
+ "automationMode": "AUTO_CREATE_PR",
73
+ "title": "Boba App"
74
+ }'
75
+ The automationMode field is optional. By default, no PR will be automatically created.
76
+
77
+ The immediate response will look something like this:
78
+
79
+
80
+ {
81
+ "name": "sessions/31415926535897932384",
82
+ "id": "31415926535897932384",
83
+ "title": "Boba App",
84
+ "sourceContext": {
85
+ "source": "sources/github/bobalover/boba",
86
+ "githubRepoContext": {
87
+ "startingBranch": "main"
88
+ }
89
+ },
90
+ "prompt": "Create a boba app!"
91
+ }
92
+ You can poll the latest session information using GetSession or ListSessions. For example, if a PR was automatically created, you can see the PR in the session output.
93
+
94
+
95
+ {
96
+ "name": "sessions/31415926535897932384",
97
+ "id": "31415926535897932384",
98
+ "title": "Boba App",
99
+ "sourceContext": {
100
+ "source": "sources/github/bobalover/boba",
101
+ "githubRepoContext": {
102
+ "startingBranch": "main"
103
+ }
104
+ },
105
+ "prompt": "Create a boba app!",
106
+ "outputs": [
107
+ {
108
+ "pullRequest": {
109
+ "url": "https://github.com/bobalover/boba/pull/35",
110
+ "title": "Create a boba app",
111
+ "description": "This change adds the initial implementation of a boba app."
112
+ }
113
+ }
114
+ ]
115
+ }
116
+
117
+ By default, sessions created through the API will have their plans automatically approved. If you want to create a session that requires explicit plan approval, set the requirePlanApproval field to true.
118
+
119
+ Step 3: Listing sessions
120
+ You can list your sessions as follows.
121
+
122
+
123
+ curl 'https://jules.googleapis.com/v1alpha/sessions?pageSize=5' \
124
+ -H 'X-Goog-Api-Key: YOUR_API_KEY'
125
+ Step 4: Approve plan
126
+ If your session requires explicit plan approval, you can approve the latest plan as follows:
127
+
128
+
129
+ curl 'https://jules.googleapis.com/v1alpha/sessions/SESSION_ID:approvePlan' \
130
+ -X POST \
131
+ -H "Content-Type: application/json" \
132
+ -H 'X-Goog-Api-Key: YOUR_API_KEY'
133
+ Step 5: Activities and interacting with the agent
134
+ To list activities in a session:
135
+
136
+
137
+ curl 'https://jules.googleapis.com/v1alpha/sessions/SESSION_ID/activities?pageSize=30' \
138
+ -H 'X-Goog-Api-Key: YOUR_API_KEY'
139
+ To send a message to the agent:
140
+
141
+
142
+ curl 'https://jules.googleapis.com/v1alpha/sessions/SESSION_ID:sendMessage' \
143
+ -X POST \
144
+ -H "Content-Type: application/json" \
145
+ -H 'X-Goog-Api-Key: YOUR_API_KEY' \
146
+ -d '{
147
+ "prompt": "Can you make the app corgi themed?"
148
+ }'
149
+ The response will be empty because the agent will send its response in the next activity. To see the agent's response, list the activities again.
150
+ Here is an example of a ListActivities response.
151
+
152
+
153
+ {
154
+ "activities": [
155
+ {
156
+ "name": "sessions/14550388554331055113/activities/02200cce44f746308651037e4a18caed",
157
+ "createTime": "2025-10-03T05:43:42.801654Z",
158
+ "originator": "agent",
159
+ "planGenerated": {
160
+ "plan": {
161
+ "id": "5103d604240042cd9f59a4cb2355643a",
162
+ "steps": [
163
+ {
164
+ "id": "705a61fc8ec24a98abc9296a3956fb6b",
165
+ "title": "Setup the environment. I will install the dependencies to run the app."
166
+ },
167
+ {
168
+ "id": "bb5276efad354794a4527e9ad7c0cd42",
169
+ "title": "Modify `src/App.js`. I will replace the existing React boilerplate with a simple Boba-themed component. This will include a title and a list of boba options.",
170
+ "index": 1
171
+ },
172
+ {
173
+ "id": "377c9a1c91764dc794a618a06772e3d8",
174
+ "title": "Modify `src/App.css`. I will update the CSS to provide a fresh, modern look for the Boba app.",
175
+ "index": 2
176
+ },
177
+ {
178
+ "id": "335802b585b449aeabb855c722cd9c40",
179
+ "title": "Frontend Verification. I will use the `frontend_verification_instructions` tool to get instructions on how to write a Playwright script to verify the frontend application and generate a screenshot of the changes.",
180
+ "index": 3
181
+ },
182
+ {
183
+ "id": "3e4cc97c7b2448668d1ac75b8c7b7d69",
184
+ "title": "Submit the changes. Once the app is looking good and verified, I will submit my work.",
185
+ "index": 4
186
+ }
187
+ ]
188
+ }
189
+ },
190
+ "id": "02200cce44f746308651037e4a18caed"
191
+ },
192
+ {
193
+ "name": "sessions/14550388554331055113/activities/2918fac8bc54450a9cbda423b7688413",
194
+ "createTime": "2025-10-03T05:43:44.954030Z",
195
+ "originator": "user",
196
+ "planApproved": {
197
+ "planId": "5103d604240042cd9f59a4cb2355643a"
198
+ },
199
+ "id": "2918fac8bc54450a9cbda423b7688413"
200
+ },
201
+ {
202
+ "name": "sessions/14550388554331055113/activities/5b3acd1b3ca2439f9cbaefaccf7f709a",
203
+ "createTime": "2025-10-03T05:44:16.700231Z",
204
+ "originator": "agent",
205
+ "progressUpdated": {
206
+ "title": "Ran bash command",
207
+ "description": "Command: \nnpm install\nOutput: added 1326 packages, and audited 1327 packages in 25s\n\n268 packages are looking for fundingExit Code: 0"
208
+ },
209
+ "artifacts": [
210
+ {
211
+ "bashOutput": {
212
+ "command": "\nnpm install",
213
+ "output": "added 1326 packages, and audited 1327 packages in 25s\n\n268 packages are looking for funding"
214
+ }
215
+ }
216
+ ],
217
+ "id": "5b3acd1b3ca2439f9cbaefaccf7f709a"
218
+ },
219
+ {
220
+ "name": "sessions/14550388554331055113/activities/1ad545d812614a5b84c23a33f6120063",
221
+ "createTime": "2025-10-03T05:44:19.502115Z",
222
+ "originator": "agent",
223
+ "progressUpdated": {
224
+ "title": "I have installed the dependencies using `npm install`."
225
+ },
226
+ "artifacts": [
227
+ {
228
+ "changeSet": {
229
+ "source": "sources/github/bobalover/boba",
230
+ "gitPatch": {
231
+ "baseCommitId": "36ead0a4caefc451b9652ed926a15af9570f4f35"
232
+ }
233
+ }
234
+ }
235
+ ],
236
+ "id": "1ad545d812614a5b84c23a33f6120063"
237
+ },
238
+ {
239
+ "name": "sessions/14550388554331055113/activities/3a2b46329f894ebea1faf6b8fb956428",
240
+ "createTime": "2025-10-03T05:44:29.265425Z",
241
+ "originator": "agent",
242
+ "progressUpdated": {
243
+ "title": "I have replaced the boilerplate in `src/App.js` with a Boba-themed component."
244
+ },
245
+ "artifacts": [
246
+ {
247
+ "changeSet": {
248
+ "source": "sources/github/bobalover/boba",
249
+ "gitPatch": {
250
+ "unidiffPatch": "",
251
+ "baseCommitId": "36ead0a4caefc451b9652ed926a15af9570f4f35"
252
+ }
253
+ }
254
+ }
255
+ ],
256
+ "id": "3a2b46329f894ebea1faf6b8fb956428"
257
+ },
258
+ {
259
+ "name": "sessions/14550388554331055113/activities/4e8dd66f15e247e3ae5e48eeff89ef47",
260
+ "createTime": "2025-10-03T05:44:37.721013Z",
261
+ "originator": "agent",
262
+ "progressUpdated": {
263
+ "title": "I have updated the CSS in `src/App.css` to give the app a fresh, modern look."
264
+ },
265
+ "artifacts": [
266
+ {
267
+ "changeSet": {
268
+ "source": "sources/github/bobalover/boba",
269
+ "gitPatch": {
270
+ "unidiffPatch": ""
271
+ }
272
+ }
273
+ }
274
+ ],
275
+ "id": "4e8dd66f15e247e3ae5e48eeff89ef47"
276
+ },
277
+ {
278
+ "name": "sessions/14550388554331055113/activities/100901155a4141d3b37e8e8d2950f3b7",
279
+ "createTime": "2025-10-03T05:44:51.048996Z",
280
+ "originator": "agent",
281
+ "progressUpdated": {
282
+ "title": "Ran bash command",
283
+ "description": "Command: \nOutput: Command failed due to an internal error.\nExit Code: 1"
284
+ },
285
+ "artifacts": [
286
+ {
287
+ "bashOutput": {
288
+ "output": "Command failed due to an internal error.",
289
+ "exitCode": 1
290
+ }
291
+ }
292
+ ],
293
+ "id": "100901155a4141d3b37e8e8d2950f3b7"
294
+ },
295
+ {
296
+ "name": "sessions/14550388554331055113/activities/a76b35353eda42d09b1c37aedaa56047",
297
+ "createTime": "2025-10-03T05:47:49.628363Z",
298
+ "originator": "agent",
299
+ "progressUpdated": {
300
+ "title": "Frontend verification",
301
+ "description": "Agent provided UI verification."
302
+ },
303
+ "artifacts": [
304
+ {
305
+ "media": {
306
+ "data": "",
307
+ "mimeType": "image/png"
308
+ }
309
+ }
310
+ ],
311
+ "id": "a76b35353eda42d09b1c37aedaa56047"
312
+ },
313
+ {
314
+ "name": "sessions/14550388554331055113/activities/db089c7052024cbeb9e37b8c584bc964",
315
+ "createTime": "2025-10-03T05:47:53.669642Z",
316
+ "originator": "agent",
317
+ "progressUpdated": {
318
+ "title": "I have successfully verified the frontend changes by building the app, runnin...",
319
+ "description": "I have successfully verified the frontend changes by building the app, running a Playwright script, and inspecting the resulting screenshot."
320
+ },
321
+ "artifacts": [
322
+ {
323
+ "changeSet": {
324
+ "source": "sources/github/bobalover/boba",
325
+ "gitPatch": {
326
+ "unidiffPatch": "",
327
+ "baseCommitId": "36ead0a4caefc451b9652ed926a15af9570f4f35"
328
+ }
329
+ }
330
+ }
331
+ ],
332
+ "id": "db089c7052024cbeb9e37b8c584bc964"
333
+ },
334
+ {
335
+ "name": "sessions/14550388554331055113/activities/890e16e30dbb4bf99a92613bdccec212",
336
+ "createTime": "2025-10-03T05:48:14.434427Z",
337
+ "originator": "agent",
338
+ "progressUpdated": {
339
+ "title": "Code reviewed",
340
+ "description": "The user wants to create a \"boba app\". This is a very open-ended request, but the agent's implementation suggests the goal is to create a simple, static web page about boba."
341
+ },
342
+ "id": "890e16e30dbb4bf99a92613bdccec212"
343
+ },
344
+ {
345
+ "name": "sessions/14550388554331055113/activities/022837dbc0e940eabcc1bc53608e15fc",
346
+ "createTime": "2025-10-03T05:48:35.523200Z",
347
+ "originator": "agent",
348
+ "sessionCompleted": {},
349
+ "artifacts": [
350
+ {
351
+ "changeSet": {
352
+ "source": "sources/github/bobalover/boba",
353
+ "gitPatch": {
354
+ "unidiffPatch": "",
355
+ "baseCommitId": "36ead0a4caefc451b9652ed926a15af9570f4f35",
356
+ "suggestedCommitMessage": "feat: Create simple Boba App\n\nThis commit transforms the default Create React App boilerplate into a simple, visually appealing Boba-themed application."
357
+ }
358
+ }
359
+ }
360
+ ],
361
+ "id": "022837dbc0e940eabcc1bc53608e15fc"
362
+ }
363
+ ]
364
+ }
365
+ Full API reference
366
+ View the full API reference documentation for the Analysis API.
367
+
app.py CHANGED
@@ -158,7 +158,7 @@ except ImportError as e:
158
 
159
  # Configuration from environment variables
160
  GITHUB_TOKEN = os.environ.get("GITHUB_TOKEN") or os.environ.get("GITHUB_API_TOKEN") or os.environ.get("GITHUB_API_KEY")
161
- ANALYSIS_API_KEY = os.environ.get("JULES_API_KEY") or os.environ.get("ANALYSIS_API_KEY")
162
  BLABLADOR_API_KEY = os.environ.get("BLABLADOR_API_KEY")
163
  BLABLADOR_BASE_URL = "https://api.helmholtz-blablador.fz-juelich.de/v1"
164
  ANALYSIS_API_URL = "https://jules.googleapis.com/v1alpha"
@@ -918,12 +918,11 @@ def render_slides(repo_full_name, branch_name, report_path):
918
 
919
  if os.path.exists(f"{output_dir}/index.html"):
920
  # Return IFrame pointing to the generated site.
921
- # Use absolute path with 'file/' prefix which is standard in Gradio 4+
922
  abspath = os.path.abspath(f"{output_dir}/index.html")
923
  add_log(f"Slides rendered successfully: {abspath}")
924
 
925
- # Using 'file/' prefix with absolute path
926
- return f'<iframe src="file/{abspath}" width="100%" height="600px" frameborder="0"></iframe>'
927
  else:
928
  add_log(f"ERROR: mkslides finished but {output_dir}/index.html not found.")
929
  return "Failed to render slides: index.html not found."
@@ -1054,7 +1053,12 @@ with gr.Blocks(title="UX Analysis Orchestrator") as demo:
1054
 
1055
  def sl_update_reports(repo_name, branch_name):
1056
  reports = get_reports_in_branch(repo_name, branch_name, filter_type="slides")
1057
- return gr.update(choices=reports, value=reports[0] if reports else None), reports
 
 
 
 
 
1058
 
1059
  sl_repo_select.change(fn=sl_update_branches, inputs=[sl_repo_select], outputs=[sl_branch_select])
1060
 
@@ -1079,8 +1083,23 @@ with gr.Blocks(title="UX Analysis Orchestrator") as demo:
1079
  return html, new_idx, counter_text
1080
 
1081
  sl_refresh_branches_btn.click(fn=sl_update_branches, inputs=[sl_repo_select], outputs=[sl_branch_select])
1082
- sl_branch_select.change(fn=sl_update_reports, inputs=[sl_repo_select, sl_branch_select], outputs=[sl_report_select, all_decks_state])
1083
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1084
  sl_render_btn.click(fn=sl_render_wrapper, inputs=[sl_repo_select, sl_branch_select, sl_report_select, sl_manual_path], outputs=[slideshow_display, carousel_controls])
1085
 
1086
  sl_render_all_btn.click(fn=start_carousel, inputs=[sl_repo_select, sl_branch_select, all_decks_state], outputs=[slideshow_display, carousel_controls, current_deck_idx, deck_counter])
 
158
 
159
  # Configuration from environment variables
160
  GITHUB_TOKEN = os.environ.get("GITHUB_TOKEN") or os.environ.get("GITHUB_API_TOKEN") or os.environ.get("GITHUB_API_KEY")
161
+ ANALYSIS_API_KEY = os.environ.get("ANALYSIS_API_KEY") or os.environ.get("JULES_API_KEY")
162
  BLABLADOR_API_KEY = os.environ.get("BLABLADOR_API_KEY")
163
  BLABLADOR_BASE_URL = "https://api.helmholtz-blablador.fz-juelich.de/v1"
164
  ANALYSIS_API_URL = "https://jules.googleapis.com/v1alpha"
 
918
 
919
  if os.path.exists(f"{output_dir}/index.html"):
920
  # Return IFrame pointing to the generated site.
921
+ # Use standard Gradio 4 /file= format with absolute path
922
  abspath = os.path.abspath(f"{output_dir}/index.html")
923
  add_log(f"Slides rendered successfully: {abspath}")
924
 
925
+ return f'<iframe src="/file={abspath}" width="100%" height="600px" frameborder="0"></iframe>'
 
926
  else:
927
  add_log(f"ERROR: mkslides finished but {output_dir}/index.html not found.")
928
  return "Failed to render slides: index.html not found."
 
1053
 
1054
  def sl_update_reports(repo_name, branch_name):
1055
  reports = get_reports_in_branch(repo_name, branch_name, filter_type="slides")
1056
+ default_val = None
1057
+ if "user_experience_reports/slides" in reports:
1058
+ default_val = "user_experience_reports/slides"
1059
+ elif reports:
1060
+ default_val = reports[0]
1061
+ return gr.update(choices=reports, value=default_val), reports
1062
 
1063
  sl_repo_select.change(fn=sl_update_branches, inputs=[sl_repo_select], outputs=[sl_branch_select])
1064
 
 
1083
  return html, new_idx, counter_text
1084
 
1085
  sl_refresh_branches_btn.click(fn=sl_update_branches, inputs=[sl_repo_select], outputs=[sl_branch_select])
 
1086
 
1087
+ sl_branch_select.change(
1088
+ fn=sl_update_reports,
1089
+ inputs=[sl_repo_select, sl_branch_select],
1090
+ outputs=[sl_report_select, all_decks_state]
1091
+ ).then(
1092
+ fn=sl_render_wrapper,
1093
+ inputs=[sl_repo_select, sl_branch_select, sl_report_select, sl_manual_path],
1094
+ outputs=[slideshow_display, carousel_controls]
1095
+ )
1096
+
1097
+ sl_report_select.change(
1098
+ fn=sl_render_wrapper,
1099
+ inputs=[sl_repo_select, sl_branch_select, sl_report_select, sl_manual_path],
1100
+ outputs=[slideshow_display, carousel_controls]
1101
+ )
1102
+
1103
  sl_render_btn.click(fn=sl_render_wrapper, inputs=[sl_repo_select, sl_branch_select, sl_report_select, sl_manual_path], outputs=[slideshow_display, carousel_controls])
1104
 
1105
  sl_render_all_btn.click(fn=start_carousel, inputs=[sl_repo_select, sl_branch_select, all_decks_state], outputs=[slideshow_display, carousel_controls, current_deck_idx, deck_counter])
mcp/mcp_analysis.py ADDED
@@ -0,0 +1,249 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from mcp.server.fastmcp import FastMCP
2
+ import httpx
3
+ import os
4
+ import logging
5
+ import json
6
+ import subprocess
7
+ import asyncio
8
+ import re
9
+
10
+ # Configure logging
11
+ logging.basicConfig(level=logging.INFO)
12
+ logger = logging.getLogger("mcp-analysis")
13
+
14
+ mcp = FastMCP("analysis")
15
+
16
+ API_BASE_URL = "https://analysis.googleapis.com/v1alpha"
17
+
18
+ class AnalysisClient:
19
+ def __init__(self, api_key: str):
20
+ self.api_key = api_key
21
+ self.base_url = API_BASE_URL
22
+ self.headers = {
23
+ "Content-Type": "application/json",
24
+ "x-goog-api-key": self.api_key
25
+ }
26
+
27
+ async def _request(self, method: str, path: str, **kwargs):
28
+ # Handle session_id prefixing
29
+ if path.startswith("/sessions/sessions/"):
30
+ path = path.replace("/sessions/sessions/", "/sessions/")
31
+
32
+ url = f"{self.base_url}{path}"
33
+ async with httpx.AsyncClient() as client:
34
+ response = await client.request(method, url, headers=self.headers, **kwargs)
35
+ if response.status_code not in (200, 204):
36
+ logger.error(f"Jules API Error ({method} {path}): {response.status_code} - {response.text}")
37
+ response.raise_for_status()
38
+ return response.json() if response.text else {}
39
+
40
+ async def list_sources(self):
41
+ return await self._request("GET", "/sources")
42
+
43
+ async def get_source(self, name: str):
44
+ if not name.startswith("sources/"): name = f"sources/{name}"
45
+ return await self._request("GET", f"/{name}")
46
+
47
+ async def create_session(self, prompt, source, branch="main", title="API Session", automation="AUTO_CREATE_PR"):
48
+ # Format source for API if it is just owner/repo
49
+ if "/" in source and "sources/github/" not in source:
50
+ source = f"sources/github/{source}"
51
+
52
+ payload = {
53
+ "prompt": prompt,
54
+ "sourceContext": {
55
+ "source": source,
56
+ "githubRepoContext": {"startingBranch": branch}
57
+ }
58
+ }
59
+ if title: payload["title"] = title
60
+ if automation: payload["automationMode"] = automation
61
+
62
+ logger.info(f"Creating session with payload: {json.dumps(payload)}")
63
+ try:
64
+ return await self._request("POST", "/sessions", json=payload)
65
+ except Exception as e:
66
+ err_msg = str(e).lower()
67
+ if "maximum number of sessions" in err_msg or "max sessions reached" in err_msg or "400" in err_msg:
68
+ logger.warning(f"Possible quota/session limit error detected: {e}. Triggering early key rotation.")
69
+ force_rotate_key()
70
+ return {"error": f"Session creation failed: {e}. API key has been rotated. Please retry."}
71
+
72
+ logger.warning(f"REST API create_session failed: {e}. Falling back to CLI.")
73
+ cli_source = source.replace("sources/github/", "")
74
+ return await self.create_session_cli(prompt, cli_source, branch, title)
75
+
76
+ async def create_session_cli(self, prompt, source, branch="main", title="API Session"):
77
+ """Fallback to analysis CLI for session creation."""
78
+ logger.info(f"Using analysis CLI to create session for {source}")
79
+ cmd = [
80
+ "analysis", "new",
81
+ "--repo", source,
82
+ prompt
83
+ ]
84
+ env = os.environ.copy()
85
+ env["JULES_API_KEY"] = self.api_key
86
+ npm_bin = subprocess.check_output(["npm", "config", "get", "prefix"]).decode().strip() + "/bin"
87
+ env["PATH"] = f"{env.get('PATH', '')}:{npm_bin}"
88
+
89
+ try:
90
+ process = await asyncio.create_subprocess_exec(
91
+ *cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, env=env
92
+ )
93
+ stdout, stderr = await process.communicate()
94
+ output = stdout.decode().strip()
95
+ match = re.search(r'sessions/(\d+)', output)
96
+ if match:
97
+ s_id = match.group(0)
98
+ return {"name": s_id, "id": match.group(1)}
99
+ return {"output": output}
100
+ except:
101
+ return {"error": "CLI failed"}
102
+
103
+ async def get_session(self, session_id):
104
+ session_id = str(session_id).strip()
105
+ if not session_id.startswith("sessions/"): session_id = f"sessions/{session_id}"
106
+ return await self._request("GET", f"/{session_id}")
107
+
108
+ async def list_sessions(self, page_size=30):
109
+ return await self._request("GET", "/sessions", params={"pageSize": page_size})
110
+
111
+ async def send_message(self, session_id, prompt):
112
+ session_id = str(session_id).strip()
113
+ if not session_id.startswith("sessions/"): session_id = f"sessions/{session_id}"
114
+ return await self._request("POST", f"/{session_id}:sendMessage", json={"prompt": prompt})
115
+
116
+ async def approve_plan(self, session_id):
117
+ session_id = str(session_id).strip()
118
+ if not session_id.startswith("sessions/"): session_id = f"sessions/{session_id}"
119
+ return await self._request("POST", f"/{session_id}:approvePlan", json={})
120
+
121
+ async def get_activity(self, name: str):
122
+ name = str(name).strip()
123
+ return await self._request("GET", f"/{name}")
124
+
125
+ async def list_activities(self, session_id, page_size=50):
126
+ session_id = str(session_id).strip()
127
+ if not session_id.startswith("sessions/"): session_id = f"sessions/{session_id}"
128
+ return await self._request("GET", f"/{session_id}/activities", params={"pageSize": page_size})
129
+
130
+ # API Key Rotation State
131
+ REQUEST_COUNT = 0
132
+ CURRENT_KEY_INDEX = 0
133
+
134
+ def force_rotate_key():
135
+ """Immediately switch to the next available API key and reset counter."""
136
+ global REQUEST_COUNT, CURRENT_KEY_INDEX
137
+ keys = [os.environ.get("JULES_API_KEY"), os.environ.get("JULES_API_KEY_FALLBACK")]
138
+ keys = [k for k in keys if k]
139
+ if len(keys) > 1:
140
+ CURRENT_KEY_INDEX = (CURRENT_KEY_INDEX + 1) % len(keys)
141
+ REQUEST_COUNT = 0
142
+ logger.info(f"FORCE ROTATION: Switched to Jules API Key index {CURRENT_KEY_INDEX}")
143
+
144
+ def get_rotated_api_key():
145
+ global REQUEST_COUNT, CURRENT_KEY_INDEX
146
+ keys = [os.environ.get("JULES_API_KEY"), os.environ.get("JULES_API_KEY_FALLBACK")]
147
+ # Filter out None or empty keys
148
+ keys = [k for k in keys if k]
149
+ if not keys:
150
+ logger.error("No Jules API keys found in environment.")
151
+ return None
152
+
153
+ key = keys[CURRENT_KEY_INDEX]
154
+
155
+ REQUEST_COUNT += 1
156
+ if REQUEST_COUNT >= 3:
157
+ REQUEST_COUNT = 0
158
+ old_index = CURRENT_KEY_INDEX
159
+ CURRENT_KEY_INDEX = (CURRENT_KEY_INDEX + 1) % len(keys)
160
+ if len(keys) > 1 and old_index != CURRENT_KEY_INDEX:
161
+ logger.info(f"Requests per key reached. Rotating Jules API Key from index {old_index} to {CURRENT_KEY_INDEX}")
162
+
163
+ return key
164
+
165
+ def get_client():
166
+ key = get_rotated_api_key()
167
+ return AnalysisClient(key)
168
+
169
+ @mcp.tool()
170
+ async def list_sources():
171
+ """List all available sources (repositories)."""
172
+ return await get_client().list_sources()
173
+
174
+ @mcp.tool()
175
+ async def get_source(name: str):
176
+ """
177
+ Get details about a specific source.
178
+ :param name: The source name (e.g., 'sources/github/owner/repo').
179
+ """
180
+ return await get_client().get_source(name)
181
+
182
+ @mcp.tool()
183
+ async def create_session(
184
+ prompt: str,
185
+ source: str,
186
+ branch: str = "main",
187
+ title: str = "API Session",
188
+ automation_mode: str = "AUTO_CREATE_PR"
189
+ ):
190
+ """
191
+ Initialize a new autonomous coding session for a repository.
192
+
193
+ Args:
194
+ prompt: The implementation task or instructions for Jules.
195
+ source: Source repository path (e.g., 'sources/github/owner/repo').
196
+ branch: The branch to use.
197
+ title: Title for the session.
198
+ automation_mode: Defaults to 'AUTO_CREATE_PR'.
199
+ """
200
+ return await get_client().create_session(prompt, source, branch, title, automation_mode)
201
+
202
+ @mcp.tool()
203
+ async def get_session(session_id: str):
204
+ """
205
+ Get the current status and details of a session.
206
+ :param session_id: The session ID (e.g., 'sessions/123').
207
+ """
208
+ return await get_client().get_session(session_id)
209
+
210
+ @mcp.tool()
211
+ async def list_sessions(page_size: int = 30):
212
+ """List recent Jules sessions."""
213
+ return await get_client().list_sessions(page_size)
214
+
215
+ @mcp.tool()
216
+ async def send_message(session_id: str, prompt: str):
217
+ """
218
+ Send a message or feedback to an existing session.
219
+ :param session_id: The session ID (e.g., 'sessions/123').
220
+ :param prompt: The message/instruction text.
221
+ """
222
+ return await get_client().send_message(session_id, prompt)
223
+
224
+ @mcp.tool()
225
+ async def approve_plan(session_id: str):
226
+ """
227
+ Approve the implementation plan for a session.
228
+ :param session_id: The session ID.
229
+ """
230
+ return await get_client().approve_plan(session_id)
231
+
232
+ @mcp.tool()
233
+ async def get_activity(name: str):
234
+ """
235
+ Get details about a specific activity.
236
+ :param name: Activity resource name (e.g., 'sessions/123/activities/456').
237
+ """
238
+ return await get_client().get_activity(name)
239
+
240
+ @mcp.tool()
241
+ async def list_activities(session_id: str, page_size: int = 50):
242
+ """
243
+ List activities for a specific session.
244
+ :param session_id: The session ID.
245
+ :param page_size: Number of activities to return.
246
+ """
247
+ return await get_client().list_activities(session_id, page_size)
248
+
249
+ if __name__ == "__main__": mcp.run()
mcp/servers.txt CHANGED
@@ -765,16 +765,16 @@ Hugging Face logo
765
  Hugging Face
766
 
767
 
768
- Jules API
769
 
770
  Authentication
771
 
772
- To get started with the Jules REST API, you’ll need an API key.
773
  Generate Your API Key
774
 
775
- In the Jules web app, go to the Settings page to create a new API key. You can have at most 3 API keys at a time.
776
 
777
- Jules REST API Key creation interface
778
  Use Your API Key
779
 
780
  To authenticate your requests, pass the API key in the X-Goog-Api-Key header of your API calls.
@@ -782,19 +782,19 @@ To authenticate your requests, pass the API key in the X-Goog-Api-Key header of
782
  Keep your API keys secure. Don’t share them or embed them in public code. For your protection, any API keys found to be publicly exposed will be automatically disabled to prevent abuse.
783
  API concepts
784
 
785
- The Jules REST API is built around a few core resources. Understanding these will help you use the API effectively.
786
 
787
- Source — An input source for the agent (e.g., a GitHub repository). Before using a source using the API, you must first install the Jules GitHub app through the Jules web app.
788
  Session — A continuous unit of work within a specific context, similar to a chat session. A session is initiated with a prompt and a source.
789
  Activity — A single unit of work within a Session. A Session contains multiple activities from both the user and the agent, such as generating a plan, sending a message, or updating progress.
790
 
791
  Your first API call
792
 
793
- We’ll walk through creating your first session with the Jules REST API using curl.
794
 
795
  List your available sources
796
 
797
- First, you need to find the name of the source you want to work with (e.g., your GitHub repo). This command will return a list of all sources you have connected to Jules.
798
 
799
  curl -H "x-goog-api-key: $JULES_API_KEY" \
800
  https://jules.googleapis.com/v1alpha/sources
@@ -817,7 +817,7 @@ The response will look something like this:
817
 
818
  Create a new session
819
 
820
- Now, create a new session. You’ll need the source name from the previous step. This request tells Jules to create a boba app in the specified repository.
821
 
822
  curl 'https://jules.googleapis.com/v1alpha/sessions' \
823
  -X POST \
@@ -915,7 +915,7 @@ The response will be empty because the agent will send its response in the next
915
 
916
  Sessions
917
 
918
- Sessions are the core resource in the Jules REST API. A session represents a unit of work where Jules executes a coding task on your repository.
919
  Create a Session
920
  POST
921
  /v1alpha/sessions
@@ -925,7 +925,7 @@ Request Body
925
  prompt
926
  required string
927
 
928
- The task description for Jules to execute.
929
  title
930
  string
931
 
@@ -1078,7 +1078,7 @@ POST
1078
 
1079
  Sends a message from the user to an active session.
1080
 
1081
- Use this endpoint to provide feedback, answer questions, or give additional instructions to Jules during an active session.
1082
  Path Parameters
1083
  session
1084
  required string path
@@ -1134,10 +1134,10 @@ Session States
1134
  Sessions progress through the following states:
1135
  State Description
1136
  QUEUED Session is waiting to be processed
1137
- PLANNING Jules is analyzing the task and creating a plan
1138
  AWAITING_PLAN_APPROVAL Plan is ready and waiting for user approval
1139
- AWAITING_USER_FEEDBACK Jules needs additional input from the user
1140
- IN_PROGRESS Jules is actively working on the task
1141
  PAUSED Session is paused
1142
  COMPLETED Task completed successfully
1143
  FAILED Task failed to complete
@@ -1262,7 +1262,7 @@ Activity Types
1262
  Activities have different types based on what occurred. Each activity will have exactly one of these event fields populated:
1263
  Plan Generated
1264
 
1265
- Indicates Jules has created a plan for the task:
1266
 
1267
  {
1268
  "planGenerated": {
@@ -1298,7 +1298,7 @@ A message from the user:
1298
 
1299
  Agent Messaged
1300
 
1301
- A message from Jules:
1302
 
1303
  {
1304
  "agentMessaged": {
@@ -1384,9 +1384,9 @@ Media
1384
 
1385
  Sources
1386
 
1387
- Sources represent repositories connected to Jules. Currently, Jules supports GitHub repositories. Use the Sources API to list available repositories and get details about specific sources.
1388
 
1389
- Sources are created when you connect a GitHub repository to Jules through the web interface. The API currently only supports reading sources, not creating them.
1390
  List Sources
1391
  GET
1392
  /v1alpha/sources
@@ -1527,11 +1527,11 @@ Next
1527
  Types
1528
 
1529
 
1530
- This page documents all data types used in the Jules REST API.
1531
  Core Resources
1532
  Session
1533
 
1534
- A session represents a unit of work where Jules executes a coding task.
1535
  Session
1536
 
1537
  A session is a contiguous amount of work within the same context.
@@ -1546,7 +1546,7 @@ Output only. The session ID.
1546
  prompt
1547
  required string
1548
 
1549
- The task description for Jules to execute.
1550
  title
1551
  string
1552
 
@@ -1558,7 +1558,7 @@ Output only. Current state of the session.
1558
  url
1559
  string
1560
 
1561
- Output only. URL to view the session in the Jules web app.
1562
  sourceContext
1563
  required SourceContext
1564
 
@@ -1589,10 +1589,10 @@ Enum representing the current state of a session:
1589
  Value Description
1590
  STATE_UNSPECIFIED State is unspecified
1591
  QUEUED Session is waiting to be processed
1592
- PLANNING Jules is creating a plan
1593
  AWAITING_PLAN_APPROVAL Plan is ready for user approval
1594
- AWAITING_USER_FEEDBACK Jules needs user input
1595
- IN_PROGRESS Jules is actively working
1596
  PAUSED Session is paused
1597
  FAILED Session failed
1598
  COMPLETED Session completed successfully
@@ -1647,7 +1647,7 @@ The user posted a message.
1647
  agentMessaged
1648
  AgentMessaged
1649
 
1650
- Jules posted a message.
1651
  progressUpdated
1652
  ProgressUpdated
1653
 
@@ -1682,7 +1682,7 @@ Plans
1682
  Plan
1683
  Plan
1684
 
1685
- A sequence of steps that Jules will take to complete the task.
1686
  id
1687
  string
1688
 
@@ -1895,7 +1895,7 @@ The message content.
1895
  AgentMessaged
1896
  AgentMessaged
1897
 
1898
- Jules posted a message.
1899
  agentMessage
1900
  string
1901
 
 
765
  Hugging Face
766
 
767
 
768
+ Analysis API
769
 
770
  Authentication
771
 
772
+ To get started with the Analysis REST API, you’ll need an API key.
773
  Generate Your API Key
774
 
775
+ In the Analysis web app, go to the Settings page to create a new API key. You can have at most 3 API keys at a time.
776
 
777
+ Analysis REST API Key creation interface
778
  Use Your API Key
779
 
780
  To authenticate your requests, pass the API key in the X-Goog-Api-Key header of your API calls.
 
782
  Keep your API keys secure. Don’t share them or embed them in public code. For your protection, any API keys found to be publicly exposed will be automatically disabled to prevent abuse.
783
  API concepts
784
 
785
+ The Analysis REST API is built around a few core resources. Understanding these will help you use the API effectively.
786
 
787
+ Source — An input source for the agent (e.g., a GitHub repository). Before using a source using the API, you must first install the Analysis GitHub app through the Analysis web app.
788
  Session — A continuous unit of work within a specific context, similar to a chat session. A session is initiated with a prompt and a source.
789
  Activity — A single unit of work within a Session. A Session contains multiple activities from both the user and the agent, such as generating a plan, sending a message, or updating progress.
790
 
791
  Your first API call
792
 
793
+ We’ll walk through creating your first session with the Analysis REST API using curl.
794
 
795
  List your available sources
796
 
797
+ First, you need to find the name of the source you want to work with (e.g., your GitHub repo). This command will return a list of all sources you have connected to Analysis.
798
 
799
  curl -H "x-goog-api-key: $JULES_API_KEY" \
800
  https://jules.googleapis.com/v1alpha/sources
 
817
 
818
  Create a new session
819
 
820
+ Now, create a new session. You’ll need the source name from the previous step. This request tells Analysis to create a boba app in the specified repository.
821
 
822
  curl 'https://jules.googleapis.com/v1alpha/sessions' \
823
  -X POST \
 
915
 
916
  Sessions
917
 
918
+ Sessions are the core resource in the Analysis REST API. A session represents a unit of work where Analysis executes a coding task on your repository.
919
  Create a Session
920
  POST
921
  /v1alpha/sessions
 
925
  prompt
926
  required string
927
 
928
+ The task description for Analysis to execute.
929
  title
930
  string
931
 
 
1078
 
1079
  Sends a message from the user to an active session.
1080
 
1081
+ Use this endpoint to provide feedback, answer questions, or give additional instructions to Analysis during an active session.
1082
  Path Parameters
1083
  session
1084
  required string path
 
1134
  Sessions progress through the following states:
1135
  State Description
1136
  QUEUED Session is waiting to be processed
1137
+ PLANNING Analysis is analyzing the task and creating a plan
1138
  AWAITING_PLAN_APPROVAL Plan is ready and waiting for user approval
1139
+ AWAITING_USER_FEEDBACK Analysis needs additional input from the user
1140
+ IN_PROGRESS Analysis is actively working on the task
1141
  PAUSED Session is paused
1142
  COMPLETED Task completed successfully
1143
  FAILED Task failed to complete
 
1262
  Activities have different types based on what occurred. Each activity will have exactly one of these event fields populated:
1263
  Plan Generated
1264
 
1265
+ Indicates Analysis has created a plan for the task:
1266
 
1267
  {
1268
  "planGenerated": {
 
1298
 
1299
  Agent Messaged
1300
 
1301
+ A message from Analysis:
1302
 
1303
  {
1304
  "agentMessaged": {
 
1384
 
1385
  Sources
1386
 
1387
+ Sources represent repositories connected to Analysis. Currently, Analysis supports GitHub repositories. Use the Sources API to list available repositories and get details about specific sources.
1388
 
1389
+ Sources are created when you connect a GitHub repository to Analysis through the web interface. The API currently only supports reading sources, not creating them.
1390
  List Sources
1391
  GET
1392
  /v1alpha/sources
 
1527
  Types
1528
 
1529
 
1530
+ This page documents all data types used in the Analysis REST API.
1531
  Core Resources
1532
  Session
1533
 
1534
+ A session represents a unit of work where Analysis executes a coding task.
1535
  Session
1536
 
1537
  A session is a contiguous amount of work within the same context.
 
1546
  prompt
1547
  required string
1548
 
1549
+ The task description for Analysis to execute.
1550
  title
1551
  string
1552
 
 
1558
  url
1559
  string
1560
 
1561
+ Output only. URL to view the session in the Analysis web app.
1562
  sourceContext
1563
  required SourceContext
1564
 
 
1589
  Value Description
1590
  STATE_UNSPECIFIED State is unspecified
1591
  QUEUED Session is waiting to be processed
1592
+ PLANNING Analysis is creating a plan
1593
  AWAITING_PLAN_APPROVAL Plan is ready for user approval
1594
+ AWAITING_USER_FEEDBACK Analysis needs user input
1595
+ IN_PROGRESS Analysis is actively working
1596
  PAUSED Session is paused
1597
  FAILED Session failed
1598
  COMPLETED Session completed successfully
 
1647
  agentMessaged
1648
  AgentMessaged
1649
 
1650
+ Analysis posted a message.
1651
  progressUpdated
1652
  ProgressUpdated
1653
 
 
1682
  Plan
1683
  Plan
1684
 
1685
+ A sequence of steps that Analysis will take to complete the task.
1686
  id
1687
  string
1688
 
 
1895
  AgentMessaged
1896
  AgentMessaged
1897
 
1898
+ Analysis posted a message.
1899
  agentMessage
1900
  string
1901