usb-workday-agent 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- usb_workday_agent/__init__.py +9 -0
- usb_workday_agent/server.py +607 -0
- usb_workday_agent-1.0.0.dist-info/METADATA +178 -0
- usb_workday_agent-1.0.0.dist-info/RECORD +7 -0
- usb_workday_agent-1.0.0.dist-info/WHEEL +4 -0
- usb_workday_agent-1.0.0.dist-info/entry_points.txt +2 -0
- usb_workday_agent-1.0.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,607 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
USB Workday Agent MCP Server
|
|
4
|
+
|
|
5
|
+
A FastMCP server for watsonx Orchestrate integration with Workday automation.
|
|
6
|
+
Provides tools for interacting with Workday APIs and automating common tasks.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import os
|
|
10
|
+
import json
|
|
11
|
+
import subprocess
|
|
12
|
+
import requests
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Optional, Dict, Any, List
|
|
15
|
+
from datetime import datetime
|
|
16
|
+
from fastmcp import FastMCP
|
|
17
|
+
|
|
18
|
+
# Initialize FastMCP server
|
|
19
|
+
mcp = FastMCP("usb-workday-agent")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def execute_command(command: str, cwd: Optional[str] = None) -> str:
|
|
23
|
+
"""Execute a shell command and return output."""
|
|
24
|
+
try:
|
|
25
|
+
env = os.environ.copy()
|
|
26
|
+
result = subprocess.run(
|
|
27
|
+
command,
|
|
28
|
+
shell=True,
|
|
29
|
+
cwd=cwd or os.getcwd(),
|
|
30
|
+
capture_output=True,
|
|
31
|
+
text=True,
|
|
32
|
+
check=True,
|
|
33
|
+
env=env
|
|
34
|
+
)
|
|
35
|
+
return result.stdout
|
|
36
|
+
except subprocess.CalledProcessError as e:
|
|
37
|
+
raise Exception(f"Command failed: {e.stderr or e.stdout}")
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
# Workday API Configuration
|
|
41
|
+
WORKDAY_BASE_URL = os.environ.get("WORKDAY_BASE_URL", "https://<tenant>.workday.com")
|
|
42
|
+
WORKDAY_TENANT = os.environ.get("WORKDAY_TENANT", "")
|
|
43
|
+
WORKDAY_USERNAME = os.environ.get("WORKDAY_USERNAME", "")
|
|
44
|
+
WORKDAY_PASSWORD = os.environ.get("WORKDAY_PASSWORD", "")
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def make_workday_request(
|
|
48
|
+
endpoint: str,
|
|
49
|
+
params: Optional[Dict[str, Any]] = None,
|
|
50
|
+
method: str = "GET"
|
|
51
|
+
) -> Dict[str, Any]:
|
|
52
|
+
"""
|
|
53
|
+
Make a request to Workday API.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
endpoint: API endpoint path
|
|
57
|
+
params: Query parameters
|
|
58
|
+
method: HTTP method
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
JSON response from Workday API
|
|
62
|
+
"""
|
|
63
|
+
url = f"{WORKDAY_BASE_URL}{endpoint}"
|
|
64
|
+
auth = (WORKDAY_USERNAME, WORKDAY_PASSWORD) if WORKDAY_USERNAME else None
|
|
65
|
+
|
|
66
|
+
try:
|
|
67
|
+
response = requests.request(
|
|
68
|
+
method=method,
|
|
69
|
+
url=url,
|
|
70
|
+
params=params,
|
|
71
|
+
auth=auth,
|
|
72
|
+
headers={"Accept": "application/json"}
|
|
73
|
+
)
|
|
74
|
+
response.raise_for_status()
|
|
75
|
+
return response.json()
|
|
76
|
+
except requests.exceptions.RequestException as e:
|
|
77
|
+
return {
|
|
78
|
+
"error": str(e),
|
|
79
|
+
"message": "Failed to connect to Workday API. Check credentials and configuration."
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
except subprocess.CalledProcessError as e:
|
|
83
|
+
raise Exception(f"Command failed: {e.stderr or e.stdout}")
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@mcp.tool()
|
|
87
|
+
def get_workday_status() -> str:
|
|
88
|
+
"""
|
|
89
|
+
Get the current status of Workday integration.
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
Status information about the Workday connection
|
|
93
|
+
"""
|
|
94
|
+
return json.dumps({
|
|
95
|
+
"status": "ready",
|
|
96
|
+
"message": "USB Workday Agent is operational",
|
|
97
|
+
"version": "1.0.0"
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
@mcp.tool()
|
|
102
|
+
def query_workday_data(
|
|
103
|
+
endpoint: str,
|
|
104
|
+
query_params: Optional[Dict[str, Any]] = None
|
|
105
|
+
) -> str:
|
|
106
|
+
"""
|
|
107
|
+
Query data from Workday API endpoint.
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
endpoint: Workday API endpoint to query
|
|
111
|
+
query_params: Optional query parameters as key-value pairs
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
JSON response from Workday API
|
|
115
|
+
"""
|
|
116
|
+
# Placeholder implementation - will be expanded with actual Workday API integration
|
|
117
|
+
return json.dumps({
|
|
118
|
+
"endpoint": endpoint,
|
|
119
|
+
"query_params": query_params or {},
|
|
120
|
+
"message": "Workday query functionality - to be implemented",
|
|
121
|
+
"status": "placeholder"
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
@mcp.tool()
|
|
126
|
+
def create_workday_record(
|
|
127
|
+
record_type: str,
|
|
128
|
+
data: Dict[str, Any]
|
|
129
|
+
) -> str:
|
|
130
|
+
"""
|
|
131
|
+
Create a new record in Workday.
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
record_type: Type of record to create (e.g., employee, position, organization)
|
|
135
|
+
data: Record data as key-value pairs
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
JSON response with created record details
|
|
139
|
+
"""
|
|
140
|
+
return json.dumps({
|
|
141
|
+
"record_type": record_type,
|
|
142
|
+
"data": data,
|
|
143
|
+
"message": "Workday record creation - to be implemented",
|
|
144
|
+
"status": "placeholder"
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
@mcp.tool()
|
|
149
|
+
def update_workday_record(
|
|
150
|
+
record_type: str,
|
|
151
|
+
record_id: str,
|
|
152
|
+
data: Dict[str, Any]
|
|
153
|
+
) -> str:
|
|
154
|
+
"""
|
|
155
|
+
Update an existing record in Workday.
|
|
156
|
+
|
|
157
|
+
Args:
|
|
158
|
+
record_type: Type of record to update
|
|
159
|
+
record_id: Unique identifier of the record
|
|
160
|
+
data: Updated data as key-value pairs
|
|
161
|
+
|
|
162
|
+
Returns:
|
|
163
|
+
JSON response with update status
|
|
164
|
+
"""
|
|
165
|
+
return json.dumps({
|
|
166
|
+
"record_type": record_type,
|
|
167
|
+
"record_id": record_id,
|
|
168
|
+
"data": data,
|
|
169
|
+
"message": "Workday record update - to be implemented",
|
|
170
|
+
"status": "placeholder"
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
@mcp.tool()
|
|
175
|
+
def search_workday_records(
|
|
176
|
+
record_type: str,
|
|
177
|
+
search_criteria: Dict[str, Any],
|
|
178
|
+
limit: int = 100
|
|
179
|
+
) -> str:
|
|
180
|
+
"""
|
|
181
|
+
Search for records in Workday.
|
|
182
|
+
|
|
183
|
+
Args:
|
|
184
|
+
record_type: Type of records to search
|
|
185
|
+
search_criteria: Search criteria as key-value pairs
|
|
186
|
+
limit: Maximum number of results to return (default: 100)
|
|
187
|
+
|
|
188
|
+
Returns:
|
|
189
|
+
JSON array of matching records
|
|
190
|
+
"""
|
|
191
|
+
return json.dumps({
|
|
192
|
+
"record_type": record_type,
|
|
193
|
+
"search_criteria": search_criteria,
|
|
194
|
+
"limit": limit,
|
|
195
|
+
"message": "Workday search functionality - to be implemented",
|
|
196
|
+
"status": "placeholder",
|
|
197
|
+
"results": []
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
@mcp.tool()
|
|
202
|
+
def execute_workday_report(
|
|
203
|
+
report_name: str,
|
|
204
|
+
parameters: Optional[Dict[str, Any]] = None
|
|
205
|
+
) -> str:
|
|
206
|
+
"""
|
|
207
|
+
Execute a Workday report and retrieve results.
|
|
208
|
+
|
|
209
|
+
Args:
|
|
210
|
+
report_name: Name of the Workday report to execute
|
|
211
|
+
parameters: Optional report parameters
|
|
212
|
+
|
|
213
|
+
Returns:
|
|
214
|
+
JSON response with report data
|
|
215
|
+
"""
|
|
216
|
+
return json.dumps({
|
|
217
|
+
"report_name": report_name,
|
|
218
|
+
"parameters": parameters or {},
|
|
219
|
+
"message": "Workday report execution - to be implemented",
|
|
220
|
+
"status": "placeholder",
|
|
221
|
+
"data": []
|
|
222
|
+
})
|
|
223
|
+
|
|
224
|
+
# @mcp.tool()
|
|
225
|
+
# def get_workday_attendance(
|
|
226
|
+
# from_date: str,
|
|
227
|
+
# to_date: str,
|
|
228
|
+
# worker_ids: Optional[List[str]] = None,
|
|
229
|
+
# limit: int = 20,
|
|
230
|
+
# offset: int = 0
|
|
231
|
+
# ) -> str:
|
|
232
|
+
# """
|
|
233
|
+
# Retrieves time clock events (check-in/check-out) for workers within a specified date range.
|
|
234
|
+
|
|
235
|
+
# Args:
|
|
236
|
+
# from_date: Start date in yyyy-mm-dd format
|
|
237
|
+
# to_date: End date in yyyy-mm-dd format
|
|
238
|
+
# worker_ids: Optional list of worker IDs
|
|
239
|
+
# limit: Maximum records to return (default 20, max 100)
|
|
240
|
+
# offset: Pagination offset
|
|
241
|
+
|
|
242
|
+
# Returns:
|
|
243
|
+
# JSON string with attendance data
|
|
244
|
+
# """
|
|
245
|
+
# params = {
|
|
246
|
+
# "fromDate": from_date,
|
|
247
|
+
# "toDate": to_date,
|
|
248
|
+
# "limit": min(limit, 100),
|
|
249
|
+
# "offset": offset
|
|
250
|
+
# }
|
|
251
|
+
|
|
252
|
+
# if worker_ids:
|
|
253
|
+
# params["worker"] = worker_ids
|
|
254
|
+
|
|
255
|
+
# result = make_workday_request("/timeTracking/v4/timeClockEvents", params)
|
|
256
|
+
# return json.dumps(result, indent=2)
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
# @mcp.tool()
|
|
260
|
+
# def get_workday_holidays(
|
|
261
|
+
# from_date: str,
|
|
262
|
+
# to_date: str,
|
|
263
|
+
# worker_ids: Optional[List[str]] = None,
|
|
264
|
+
# limit: int = 20,
|
|
265
|
+
# offset: int = 0
|
|
266
|
+
# ) -> str:
|
|
267
|
+
# """
|
|
268
|
+
# Retrieves holiday events for specified workers and time period.
|
|
269
|
+
|
|
270
|
+
# Args:
|
|
271
|
+
# from_date: Start date in yyyy-mm-dd format
|
|
272
|
+
# to_date: End date in yyyy-mm-dd format
|
|
273
|
+
# worker_ids: Optional list of worker IDs
|
|
274
|
+
# limit: Maximum records to return (default 20, max 100)
|
|
275
|
+
# offset: Pagination offset
|
|
276
|
+
|
|
277
|
+
# Returns:
|
|
278
|
+
# JSON string with holiday data
|
|
279
|
+
# """
|
|
280
|
+
# params = {
|
|
281
|
+
# "fromDate": from_date,
|
|
282
|
+
# "toDate": to_date,
|
|
283
|
+
# "limit": min(limit, 100),
|
|
284
|
+
# "offset": offset
|
|
285
|
+
# }
|
|
286
|
+
|
|
287
|
+
# if worker_ids:
|
|
288
|
+
# params["worker"] = worker_ids
|
|
289
|
+
|
|
290
|
+
# result = make_workday_request("/holiday/v1/holidayEvents", params)
|
|
291
|
+
# return json.dumps(result, indent=2)
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
# @mcp.tool()
|
|
295
|
+
# def get_workday_time_off(
|
|
296
|
+
# worker_id: str,
|
|
297
|
+
# from_date: Optional[str] = None,
|
|
298
|
+
# to_date: Optional[str] = None,
|
|
299
|
+
# status: Optional[List[str]] = None,
|
|
300
|
+
# time_off_types: Optional[List[str]] = None,
|
|
301
|
+
# limit: int = 20,
|
|
302
|
+
# offset: int = 0
|
|
303
|
+
# ) -> str:
|
|
304
|
+
# """
|
|
305
|
+
# Retrieves time off details for a specified worker.
|
|
306
|
+
|
|
307
|
+
# Args:
|
|
308
|
+
# worker_id: Worker ID or 'me' for current user
|
|
309
|
+
# from_date: Optional start date in yyyy-mm-dd format
|
|
310
|
+
# to_date: Optional end date in yyyy-mm-dd format
|
|
311
|
+
# status: Optional list of statuses (Approved, Submitted, etc.)
|
|
312
|
+
# time_off_types: Optional list of time off type IDs
|
|
313
|
+
# limit: Maximum records to return (default 20, max 100)
|
|
314
|
+
# offset: Pagination offset
|
|
315
|
+
|
|
316
|
+
# Returns:
|
|
317
|
+
# JSON string with time off data
|
|
318
|
+
# """
|
|
319
|
+
# params = {
|
|
320
|
+
# "limit": min(limit, 100),
|
|
321
|
+
# "offset": offset
|
|
322
|
+
# }
|
|
323
|
+
|
|
324
|
+
# if from_date:
|
|
325
|
+
# params["fromDate"] = from_date
|
|
326
|
+
# if to_date:
|
|
327
|
+
# params["toDate"] = to_date
|
|
328
|
+
# if status:
|
|
329
|
+
# params["status"] = status
|
|
330
|
+
# if time_off_types:
|
|
331
|
+
# params["timeOffType"] = time_off_types
|
|
332
|
+
|
|
333
|
+
# endpoint = f"/absenceManagement/v5/workers/{worker_id}/timeOffDetails"
|
|
334
|
+
# result = make_workday_request(endpoint, params)
|
|
335
|
+
# return json.dumps(result, indent=2)
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
# @mcp.tool()
|
|
339
|
+
# def get_workday_time_and_attendance(
|
|
340
|
+
# worker_ids: List[str],
|
|
341
|
+
# from_date: str,
|
|
342
|
+
# to_date: str,
|
|
343
|
+
# include_attendance: bool = True,
|
|
344
|
+
# include_time_off: bool = True,
|
|
345
|
+
# include_holidays: bool = True,
|
|
346
|
+
# limit: int = 20
|
|
347
|
+
# ) -> str:
|
|
348
|
+
# """
|
|
349
|
+
# Retrieves comprehensive time and attendance data combining clock events and time off.
|
|
350
|
+
|
|
351
|
+
# Args:
|
|
352
|
+
# worker_ids: List of worker IDs
|
|
353
|
+
# from_date: Start date in yyyy-mm-dd format
|
|
354
|
+
# to_date: End date in yyyy-mm-dd format
|
|
355
|
+
# include_attendance: Include time clock events
|
|
356
|
+
# include_time_off: Include time off details
|
|
357
|
+
# include_holidays: Include holiday events
|
|
358
|
+
# limit: Maximum records per data type
|
|
359
|
+
|
|
360
|
+
# Returns:
|
|
361
|
+
# JSON string with combined time and attendance data
|
|
362
|
+
# """
|
|
363
|
+
# combined_data = {
|
|
364
|
+
# "workers": worker_ids,
|
|
365
|
+
# "date_range": {"from": from_date, "to": to_date},
|
|
366
|
+
# "data": {}
|
|
367
|
+
# }
|
|
368
|
+
|
|
369
|
+
# if include_attendance:
|
|
370
|
+
# attendance = make_workday_request(
|
|
371
|
+
# "/timeTracking/v4/timeClockEvents",
|
|
372
|
+
# {"fromDate": from_date, "toDate": to_date, "worker": worker_ids, "limit": limit}
|
|
373
|
+
# )
|
|
374
|
+
# combined_data["data"]["attendance"] = attendance
|
|
375
|
+
|
|
376
|
+
# if include_time_off:
|
|
377
|
+
# time_off_data = []
|
|
378
|
+
# for worker_id in worker_ids:
|
|
379
|
+
# endpoint = f"/absenceManagement/v5/workers/{worker_id}/timeOffDetails"
|
|
380
|
+
# result = make_workday_request(
|
|
381
|
+
# endpoint,
|
|
382
|
+
# {"fromDate": from_date, "toDate": to_date, "limit": limit}
|
|
383
|
+
# )
|
|
384
|
+
# time_off_data.append({"worker_id": worker_id, "time_off": result})
|
|
385
|
+
# combined_data["data"]["time_off"] = time_off_data
|
|
386
|
+
|
|
387
|
+
# if include_holidays:
|
|
388
|
+
# holidays = make_workday_request(
|
|
389
|
+
# "/holiday/v1/holidayEvents",
|
|
390
|
+
# {"fromDate": from_date, "toDate": to_date, "worker": worker_ids, "limit": limit}
|
|
391
|
+
# )
|
|
392
|
+
# combined_data["data"]["holidays"] = holidays
|
|
393
|
+
|
|
394
|
+
# return json.dumps(combined_data, indent=2)
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
# @mcp.tool()
|
|
398
|
+
# def get_workday_time_and_attendance_by_date(
|
|
399
|
+
# from_date: Optional[str] = None,
|
|
400
|
+
# to_date: Optional[str] = None,
|
|
401
|
+
# date: Optional[str] = None,
|
|
402
|
+
# worker_ids: Optional[List[str]] = None,
|
|
403
|
+
# include_attendance: bool = True,
|
|
404
|
+
# include_time_off: bool = True,
|
|
405
|
+
# include_holidays: bool = True,
|
|
406
|
+
# limit: int = 100
|
|
407
|
+
# ) -> str:
|
|
408
|
+
# """
|
|
409
|
+
# Retrieves time and attendance data for a specific date or date range.
|
|
410
|
+
|
|
411
|
+
# Args:
|
|
412
|
+
# from_date: Start date in yyyy-mm-dd format
|
|
413
|
+
# to_date: End date in yyyy-mm-dd format
|
|
414
|
+
# date: Specific date (overrides from_date/to_date if provided)
|
|
415
|
+
# worker_ids: Optional list of worker IDs
|
|
416
|
+
# include_attendance: Include time clock events
|
|
417
|
+
# include_time_off: Include time off details
|
|
418
|
+
# include_holidays: Include holiday events
|
|
419
|
+
# limit: Maximum records per data type
|
|
420
|
+
|
|
421
|
+
# Returns:
|
|
422
|
+
# JSON string with date-based time and attendance data
|
|
423
|
+
# """
|
|
424
|
+
# if date:
|
|
425
|
+
# from_date = to_date = date
|
|
426
|
+
|
|
427
|
+
# combined_data = {
|
|
428
|
+
# "date_range": {"from": from_date, "to": to_date},
|
|
429
|
+
# "data": {}
|
|
430
|
+
# }
|
|
431
|
+
|
|
432
|
+
# params = {"fromDate": from_date, "toDate": to_date, "limit": limit}
|
|
433
|
+
# if worker_ids:
|
|
434
|
+
# params["worker"] = worker_ids
|
|
435
|
+
|
|
436
|
+
# if include_attendance:
|
|
437
|
+
# combined_data["data"]["attendance"] = make_workday_request(
|
|
438
|
+
# "/timeTracking/v4/timeClockEvents", params
|
|
439
|
+
# )
|
|
440
|
+
|
|
441
|
+
# if include_holidays:
|
|
442
|
+
# combined_data["data"]["holidays"] = make_workday_request(
|
|
443
|
+
# "/holiday/v1/holidayEvents", params
|
|
444
|
+
# )
|
|
445
|
+
|
|
446
|
+
# if include_time_off and worker_ids:
|
|
447
|
+
# time_off_data = []
|
|
448
|
+
# for worker_id in worker_ids:
|
|
449
|
+
# endpoint = f"/absenceManagement/v5/workers/{worker_id}/timeOffDetails"
|
|
450
|
+
# result = make_workday_request(
|
|
451
|
+
# endpoint,
|
|
452
|
+
# {"fromDate": from_date, "toDate": to_date, "limit": limit}
|
|
453
|
+
# )
|
|
454
|
+
# time_off_data.append({"worker_id": worker_id, "time_off": result})
|
|
455
|
+
# combined_data["data"]["time_off"] = time_off_data
|
|
456
|
+
|
|
457
|
+
# return json.dumps(combined_data, indent=2)
|
|
458
|
+
|
|
459
|
+
|
|
460
|
+
# @mcp.tool()
|
|
461
|
+
# def get_workday_time_and_attendance_by_employee(
|
|
462
|
+
# worker_id: str,
|
|
463
|
+
# from_date: str,
|
|
464
|
+
# to_date: str,
|
|
465
|
+
# include_attendance: bool = True,
|
|
466
|
+
# include_time_off: bool = True,
|
|
467
|
+
# include_holidays: bool = True,
|
|
468
|
+
# time_off_status: Optional[List[str]] = None,
|
|
469
|
+
# limit: int = 100
|
|
470
|
+
# ) -> str:
|
|
471
|
+
# """
|
|
472
|
+
# Retrieves comprehensive time and attendance data for a specific employee.
|
|
473
|
+
|
|
474
|
+
# Args:
|
|
475
|
+
# worker_id: Worker ID or 'me' for current user
|
|
476
|
+
# from_date: Start date in yyyy-mm-dd format
|
|
477
|
+
# to_date: End date in yyyy-mm-dd format
|
|
478
|
+
# include_attendance: Include time clock events
|
|
479
|
+
# include_time_off: Include time off details
|
|
480
|
+
# include_holidays: Include holiday events
|
|
481
|
+
# time_off_status: Optional filter for time off status
|
|
482
|
+
# limit: Maximum records per data type
|
|
483
|
+
|
|
484
|
+
# Returns:
|
|
485
|
+
# JSON string with employee time and attendance data
|
|
486
|
+
# """
|
|
487
|
+
# combined_data = {
|
|
488
|
+
# "worker_id": worker_id,
|
|
489
|
+
# "date_range": {"from": from_date, "to": to_date},
|
|
490
|
+
# "data": {}
|
|
491
|
+
# }
|
|
492
|
+
|
|
493
|
+
# if include_attendance:
|
|
494
|
+
# combined_data["data"]["attendance"] = make_workday_request(
|
|
495
|
+
# "/timeTracking/v4/timeClockEvents",
|
|
496
|
+
# {"fromDate": from_date, "toDate": to_date, "worker": [worker_id], "limit": limit}
|
|
497
|
+
# )
|
|
498
|
+
|
|
499
|
+
# if include_time_off:
|
|
500
|
+
# params = {"fromDate": from_date, "toDate": to_date, "limit": limit}
|
|
501
|
+
# if time_off_status:
|
|
502
|
+
# params["status"] = time_off_status
|
|
503
|
+
|
|
504
|
+
# endpoint = f"/absenceManagement/v5/workers/{worker_id}/timeOffDetails"
|
|
505
|
+
# combined_data["data"]["time_off"] = make_workday_request(endpoint, params)
|
|
506
|
+
|
|
507
|
+
# if include_holidays:
|
|
508
|
+
# combined_data["data"]["holidays"] = make_workday_request(
|
|
509
|
+
# "/holiday/v1/holidayEvents",
|
|
510
|
+
# {"fromDate": from_date, "toDate": to_date, "worker": [worker_id], "limit": limit}
|
|
511
|
+
# )
|
|
512
|
+
|
|
513
|
+
# return json.dumps(combined_data, indent=2)
|
|
514
|
+
|
|
515
|
+
|
|
516
|
+
# @mcp.tool()
|
|
517
|
+
# def get_workday_time_and_attendance_by_manager(
|
|
518
|
+
# manager_id: str,
|
|
519
|
+
# from_date: str,
|
|
520
|
+
# to_date: str,
|
|
521
|
+
# include_attendance: bool = True,
|
|
522
|
+
# include_time_off: bool = True,
|
|
523
|
+
# include_holidays: bool = True,
|
|
524
|
+
# time_off_status: Optional[List[str]] = None,
|
|
525
|
+
# include_indirect_reports: bool = False,
|
|
526
|
+
# limit: int = 100
|
|
527
|
+
# ) -> str:
|
|
528
|
+
# """
|
|
529
|
+
# Retrieves time and attendance data for all workers reporting to a manager.
|
|
530
|
+
|
|
531
|
+
# Args:
|
|
532
|
+
# manager_id: Manager ID or 'me' for current user
|
|
533
|
+
# from_date: Start date in yyyy-mm-dd format
|
|
534
|
+
# to_date: End date in yyyy-mm-dd format
|
|
535
|
+
# include_attendance: Include time clock events
|
|
536
|
+
# include_time_off: Include time off requests
|
|
537
|
+
# include_holidays: Include holiday events
|
|
538
|
+
# time_off_status: Optional filter for time off status (e.g., Submitted for approval)
|
|
539
|
+
# include_indirect_reports: Include indirect reports
|
|
540
|
+
# limit: Maximum records per worker
|
|
541
|
+
|
|
542
|
+
# Returns:
|
|
543
|
+
# JSON string with manager team time and attendance data
|
|
544
|
+
# """
|
|
545
|
+
# combined_data = {
|
|
546
|
+
# "manager_id": manager_id,
|
|
547
|
+
# "date_range": {"from": from_date, "to": to_date},
|
|
548
|
+
# "include_indirect_reports": include_indirect_reports,
|
|
549
|
+
# "data": {},
|
|
550
|
+
# "note": "This is a placeholder. Actual implementation requires Workday organizational hierarchy API."
|
|
551
|
+
# }
|
|
552
|
+
|
|
553
|
+
# # Note: This would require additional Workday API calls to:
|
|
554
|
+
# # 1. Get list of direct reports for the manager
|
|
555
|
+
# # 2. Optionally get indirect reports
|
|
556
|
+
# # 3. Then fetch time and attendance data for each report
|
|
557
|
+
|
|
558
|
+
# # Placeholder implementation
|
|
559
|
+
# combined_data["data"]["message"] = (
|
|
560
|
+
# "Manager view requires organizational hierarchy API integration. "
|
|
561
|
+
# "Use get_workday_time_and_attendance with specific worker_ids for now."
|
|
562
|
+
# )
|
|
563
|
+
|
|
564
|
+
# return json.dumps(combined_data, indent=2)
|
|
565
|
+
|
|
566
|
+
@mcp.resource("workday://config")
|
|
567
|
+
def get_workday_config() -> str:
|
|
568
|
+
"""
|
|
569
|
+
Get Workday configuration information.
|
|
570
|
+
|
|
571
|
+
Returns:
|
|
572
|
+
Configuration details for Workday integration
|
|
573
|
+
"""
|
|
574
|
+
return json.dumps({
|
|
575
|
+
"tenant": "placeholder",
|
|
576
|
+
"environment": "sandbox",
|
|
577
|
+
"api_version": "v1",
|
|
578
|
+
"endpoints": {
|
|
579
|
+
"base_url": "https://api.workday.com",
|
|
580
|
+
"auth_url": "https://auth.workday.com"
|
|
581
|
+
}
|
|
582
|
+
})
|
|
583
|
+
|
|
584
|
+
|
|
585
|
+
@mcp.resource("workday://status")
|
|
586
|
+
def get_connection_status() -> str:
|
|
587
|
+
"""
|
|
588
|
+
Get current connection status to Workday.
|
|
589
|
+
|
|
590
|
+
Returns:
|
|
591
|
+
Connection status information
|
|
592
|
+
"""
|
|
593
|
+
return json.dumps({
|
|
594
|
+
"connected": False,
|
|
595
|
+
"last_check": "2026-05-05T21:57:00Z",
|
|
596
|
+
"message": "Connection not yet configured"
|
|
597
|
+
})
|
|
598
|
+
|
|
599
|
+
|
|
600
|
+
def main():
|
|
601
|
+
"""Run the MCP server."""
|
|
602
|
+
mcp.run()
|
|
603
|
+
|
|
604
|
+
|
|
605
|
+
if __name__ == "__main__":
|
|
606
|
+
main()
|
|
607
|
+
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: usb-workday-agent
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: USB Workday Agent - MCP integration for Workday automation
|
|
5
|
+
Project-URL: Homepage, https://github.com/amandawinkles/usb-workday-agent
|
|
6
|
+
Project-URL: Repository, https://github.com/amandawinkles/usb-workday-agent
|
|
7
|
+
Project-URL: Issues, https://github.com/amandawinkles/usb-workday-agent/issues
|
|
8
|
+
Author-email: Amanda Winkles <amanda.winkles@ibm.com>
|
|
9
|
+
License: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: automation,fastmcp,mcp,usb,workday
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
20
|
+
Requires-Python: >=3.10
|
|
21
|
+
Requires-Dist: fastmcp>=3.0.0
|
|
22
|
+
Requires-Dist: mcp>=0.9.0
|
|
23
|
+
Requires-Dist: requests>=2.31.0
|
|
24
|
+
Provides-Extra: dev
|
|
25
|
+
Requires-Dist: black>=23.0.0; extra == 'dev'
|
|
26
|
+
Requires-Dist: pytest>=7.0.0; extra == 'dev'
|
|
27
|
+
Requires-Dist: ruff>=0.1.0; extra == 'dev'
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
|
|
30
|
+
# USB Workday Agent
|
|
31
|
+
|
|
32
|
+
MCP integration for Workday automation using the wxO-nit virtual environment.
|
|
33
|
+
|
|
34
|
+
## Overview
|
|
35
|
+
|
|
36
|
+
This project provides an MCP (Model Context Protocol) server for automating Workday operations. It leverages the FastMCP framework to expose tools and resources for Workday integration.
|
|
37
|
+
|
|
38
|
+
## Prerequisites
|
|
39
|
+
|
|
40
|
+
- Python 3.10 or higher
|
|
41
|
+
- wxO-nit virtual environment (located at `../wxO-nit`)
|
|
42
|
+
- MCP-compatible client (e.g., Claude Desktop, Bob)
|
|
43
|
+
|
|
44
|
+
## Installation
|
|
45
|
+
|
|
46
|
+
### Using the wxO-nit Virtual Environment
|
|
47
|
+
|
|
48
|
+
This project uses the existing wxO-nit virtual environment:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
# Activate the wxO-nit virtual environment
|
|
52
|
+
source ../wxO-nit/bin/activate
|
|
53
|
+
|
|
54
|
+
# Install this package in development mode
|
|
55
|
+
pip install -e .
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Standalone Installation
|
|
59
|
+
|
|
60
|
+
Alternatively, create a new virtual environment:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
# Create virtual environment
|
|
64
|
+
python -m venv venv
|
|
65
|
+
|
|
66
|
+
# Activate virtual environment
|
|
67
|
+
source venv/bin/activate # On Windows: venv\Scripts\activate
|
|
68
|
+
|
|
69
|
+
# Install dependencies
|
|
70
|
+
pip install -e .
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Development
|
|
74
|
+
|
|
75
|
+
### Install Development Dependencies
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
pip install -e ".[dev]"
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Code Formatting
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
# Format code with black
|
|
85
|
+
black .
|
|
86
|
+
|
|
87
|
+
# Lint with ruff
|
|
88
|
+
ruff check .
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Testing
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
pytest
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Project Structure
|
|
98
|
+
|
|
99
|
+
```
|
|
100
|
+
usb-workday-agent/
|
|
101
|
+
├── README.md
|
|
102
|
+
├── pyproject.toml
|
|
103
|
+
├── setup.py
|
|
104
|
+
└── (source files to be added)
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Dependencies
|
|
108
|
+
|
|
109
|
+
- **mcp** (>=0.9.0): Model Context Protocol core library
|
|
110
|
+
- **fastmcp** (>=3.0.0): FastMCP framework for building MCP servers
|
|
111
|
+
|
|
112
|
+
## Running the MCP Server
|
|
113
|
+
|
|
114
|
+
### Start the Server
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
# Activate the wxO-nit virtual environment
|
|
118
|
+
source ../wxO-nit/bin/activate
|
|
119
|
+
|
|
120
|
+
# Run the server directly
|
|
121
|
+
python3 server.py
|
|
122
|
+
|
|
123
|
+
# Or use the installed command (after pip install -e .)
|
|
124
|
+
usb-workday-agent
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### MCP Client Configuration
|
|
128
|
+
|
|
129
|
+
Add to your MCP client configuration (e.g., Claude Desktop):
|
|
130
|
+
|
|
131
|
+
```json
|
|
132
|
+
{
|
|
133
|
+
"mcpServers": {
|
|
134
|
+
"usb-workday-agent": {
|
|
135
|
+
"command": "python",
|
|
136
|
+
"args": ["/Users/amandawinkles/bob/usb-workday-agent/server.py"],
|
|
137
|
+
"env": {
|
|
138
|
+
"PYTHONPATH": "/Users/amandawinkles/bob/wxO-nit/lib/python3.10/site-packages"
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Available Tools
|
|
146
|
+
|
|
147
|
+
The server provides the following tools for watsonx Orchestrate:
|
|
148
|
+
|
|
149
|
+
- **get_workday_status**: Check Workday integration status
|
|
150
|
+
- **query_workday_data**: Query data from Workday API endpoints
|
|
151
|
+
- **create_workday_record**: Create new records in Workday
|
|
152
|
+
- **update_workday_record**: Update existing Workday records
|
|
153
|
+
- **search_workday_records**: Search for records with criteria
|
|
154
|
+
- **execute_workday_report**: Run Workday reports and retrieve results
|
|
155
|
+
|
|
156
|
+
## Available Resources
|
|
157
|
+
|
|
158
|
+
- **workday://config**: Get Workday configuration details
|
|
159
|
+
- **workday://status**: Check connection status to Workday
|
|
160
|
+
|
|
161
|
+
## Configuration
|
|
162
|
+
|
|
163
|
+
Workday API credentials and configuration should be set via environment variables:
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
export WORKDAY_TENANT="your-tenant"
|
|
167
|
+
export WORKDAY_USERNAME="your-username"
|
|
168
|
+
export WORKDAY_PASSWORD="your-password"
|
|
169
|
+
export WORKDAY_API_VERSION="v1"
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## License
|
|
173
|
+
|
|
174
|
+
MIT License
|
|
175
|
+
|
|
176
|
+
## Author
|
|
177
|
+
|
|
178
|
+
Amanda Winkles
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
usb_workday_agent/__init__.py,sha256=HEM0dlCAMuIEx8S7m9K7AvBOpJJ57Uyi0q6dkiqlKOs,184
|
|
2
|
+
usb_workday_agent/server.py,sha256=eZy_zD5kxHvCCePfAz8Ewg3Ps9AzhgcQO-KbpOusdN8,18287
|
|
3
|
+
usb_workday_agent-1.0.0.dist-info/METADATA,sha256=lN2YLRldShkaqW7Ap_-djTvaUpELWjEhWPYeAACLZlY,4269
|
|
4
|
+
usb_workday_agent-1.0.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
5
|
+
usb_workday_agent-1.0.0.dist-info/entry_points.txt,sha256=XG_3SzRVZ4hJYje6Ajxv0kR5Zil4jefdhWd-PcIoazw,68
|
|
6
|
+
usb_workday_agent-1.0.0.dist-info/licenses/LICENSE,sha256=CKn_by5Ze62wMjBRyBdhlsD---XINMaH3N0jDK8VELM,1070
|
|
7
|
+
usb_workday_agent-1.0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Amanda Winkles
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|