@@ -14,12 +14,35 @@ import (
14
14
"golang.org/x/time/rate"
15
15
)
16
16
17
- // createTools creates the MCP tool definitions with appropriate rate limits
17
+ var MCP_USER_AGENT = fmt .Sprintf ("mcp-server-axiom/%s" , Version )
18
+
19
+ func createAxiomHTTPClient () * http.Client {
20
+ return & http.Client {
21
+ Transport : & mcpTransport {
22
+ base : http .DefaultTransport ,
23
+ },
24
+ }
25
+ }
26
+
27
+ type mcpTransport struct {
28
+ base http.RoundTripper
29
+ }
30
+
31
+ func (t * mcpTransport ) RoundTrip (req * http.Request ) (* http.Response , error ) {
32
+ // Clone the request to avoid modifying the original
33
+ reqCopy := req .Clone (req .Context ())
34
+ reqCopy .Header .Set ("User-Agent" , MCP_USER_AGENT )
35
+ return t .base .RoundTrip (reqCopy )
36
+ }
37
+
18
38
func createTools (cfg config ) ([]mcp.ToolDefinition , error ) {
39
+ httpClient := createAxiomHTTPClient ()
40
+
19
41
client , err := axiom .NewClient (
20
42
axiom .SetToken (cfg .token ),
21
43
axiom .SetURL (cfg .url ),
22
44
axiom .SetOrganizationID (cfg .orgID ),
45
+ axiom .SetClient (httpClient ),
23
46
)
24
47
if err != nil {
25
48
return nil , fmt .Errorf ("failed to create Axiom client: %w" , err )
@@ -81,7 +104,7 @@ func createTools(cfg config) ([]mcp.ToolDefinition, error) {
81
104
Properties : mcp.ToolInputSchemaProperties {},
82
105
},
83
106
},
84
- Execute : newGetSavedQueriesHandler (client , cfg ),
107
+ Execute : newGetSavedQueriesHandler (cfg , httpClient ),
85
108
RateLimit : rate .NewLimiter (rate .Limit (1 ), 1 ),
86
109
},
87
110
{
@@ -93,7 +116,7 @@ func createTools(cfg config) ([]mcp.ToolDefinition, error) {
93
116
Properties : mcp.ToolInputSchemaProperties {},
94
117
},
95
118
},
96
- Execute : newGetMonitorsHandler (client , cfg ),
119
+ Execute : newGetMonitorsHandler (cfg , httpClient ),
97
120
RateLimit : rate .NewLimiter (rate .Limit (cfg .monitorsRateLimit ), cfg .monitorsRateBurst ),
98
121
},
99
122
{
@@ -111,9 +134,35 @@ func createTools(cfg config) ([]mcp.ToolDefinition, error) {
111
134
},
112
135
},
113
136
},
114
- Execute : newGetMonitorsHistoryHandler (client , cfg ),
137
+ Execute : newGetMonitorsHistoryHandler (cfg , httpClient ),
115
138
RateLimit : rate .NewLimiter (rate .Limit (cfg .monitorsRateLimit ), cfg .monitorsRateBurst ),
116
139
},
140
+ {
141
+ Metadata : mcp.Tool {
142
+ Name : "getQueryHistory" ,
143
+ Description : ptr ("Get your recent APL query execution history" ),
144
+ InputSchema : mcp.ToolInputSchema {
145
+ Type : "object" ,
146
+ Properties : mcp.ToolInputSchemaProperties {
147
+ "limit" : map [string ]any {
148
+ "type" : "number" ,
149
+ "description" : "Maximum number of query history entries to return (default: 50, max: 500)" ,
150
+ "default" : 50 ,
151
+ },
152
+ "user" : map [string ]any {
153
+ "type" : "string" ,
154
+ "description" : "Filter by specific user ID (optional - defaults to current user)" ,
155
+ },
156
+ "dataset" : map [string ]any {
157
+ "type" : "string" ,
158
+ "description" : "Filter by dataset name (optional)" ,
159
+ },
160
+ },
161
+ },
162
+ },
163
+ Execute : newGetQueryHistoryHandler (client , cfg , httpClient ),
164
+ RateLimit : rate .NewLimiter (rate .Limit (cfg .queryRateLimit ), cfg .queryRateBurst ),
165
+ },
117
166
}, nil
118
167
}
119
168
@@ -290,8 +339,17 @@ type SavedQuery struct {
290
339
ID string `json:"id"`
291
340
}
292
341
342
+ // QueryHistoryEntry represents a query execution record from the axiom-history dataset
343
+ type QueryHistoryEntry struct {
344
+ Timestamp string `json:"timestamp"`
345
+ Dataset string `json:"dataset"`
346
+ Query string `json:"query"`
347
+ UserID string `json:"userId"`
348
+ Created string `json:"created"`
349
+ }
350
+
293
351
// newGetSavedQueriesHandler creates a handler for retrieving saved queries
294
- func newGetSavedQueriesHandler (client * axiom. Client , cfg config ) func (mcp.CallToolRequestParams ) (mcp.CallToolResult , error ) {
352
+ func newGetSavedQueriesHandler (cfg config , httpClient * http. Client ) func (mcp.CallToolRequestParams ) (mcp.CallToolResult , error ) {
295
353
return func (params mcp.CallToolRequestParams ) (mcp.CallToolResult , error ) {
296
354
ctx := context .Background ()
297
355
@@ -305,9 +363,9 @@ func newGetSavedQueriesHandler(client *axiom.Client, cfg config) func(mcp.CallTo
305
363
306
364
req .Header .Set ("Authorization" , "Bearer " + cfg .token )
307
365
req .Header .Set ("Accept" , "application/json" )
366
+ req .Header .Set ("X-AXIOM-ORG-ID" , cfg .orgID )
308
367
309
- client := & http.Client {}
310
- resp , err := client .Do (req )
368
+ resp , err := httpClient .Do (req )
311
369
if err != nil {
312
370
return mcp.CallToolResult {}, fmt .Errorf ("failed to execute request: %w" , err )
313
371
}
@@ -354,7 +412,7 @@ func newGetSavedQueriesHandler(client *axiom.Client, cfg config) func(mcp.CallTo
354
412
}
355
413
356
414
// newGetMonitorsHandler creates a handler for retrieving monitors
357
- func newGetMonitorsHandler (client * axiom. Client , cfg config ) func (mcp.CallToolRequestParams ) (mcp.CallToolResult , error ) {
415
+ func newGetMonitorsHandler (cfg config , httpClient * http. Client ) func (mcp.CallToolRequestParams ) (mcp.CallToolResult , error ) {
358
416
return func (params mcp.CallToolRequestParams ) (mcp.CallToolResult , error ) {
359
417
ctx := context .Background ()
360
418
@@ -368,9 +426,9 @@ func newGetMonitorsHandler(client *axiom.Client, cfg config) func(mcp.CallToolRe
368
426
369
427
req .Header .Set ("Authorization" , "Bearer " + cfg .token )
370
428
req .Header .Set ("Accept" , "application/json" )
429
+ req .Header .Set ("X-AXIOM-ORG-ID" , cfg .orgID )
371
430
372
- clientHTTP := & http.Client {}
373
- resp , err := clientHTTP .Do (req )
431
+ resp , err := httpClient .Do (req )
374
432
if err != nil {
375
433
return mcp.CallToolResult {}, fmt .Errorf ("failed to execute request: %w" , err )
376
434
}
@@ -405,7 +463,7 @@ func newGetMonitorsHandler(client *axiom.Client, cfg config) func(mcp.CallToolRe
405
463
}
406
464
407
465
// newGetMonitorsHistoryHandler creates a handler for retrieving monitor history
408
- func newGetMonitorsHistoryHandler (client * axiom. Client , cfg config ) func (mcp.CallToolRequestParams ) (mcp.CallToolResult , error ) {
466
+ func newGetMonitorsHistoryHandler (cfg config , httpClient * http. Client ) func (mcp.CallToolRequestParams ) (mcp.CallToolResult , error ) {
409
467
return func (params mcp.CallToolRequestParams ) (mcp.CallToolResult , error ) {
410
468
ctx := context .Background ()
411
469
@@ -435,9 +493,9 @@ func newGetMonitorsHistoryHandler(client *axiom.Client, cfg config) func(mcp.Cal
435
493
return mcp.CallToolResult {}, fmt .Errorf ("at least one valid monitor ID is required" )
436
494
}
437
495
438
- baseURL := cfg . url
439
- fullURL := fmt . Sprintf ( "%s/ api/internal/monitors/history?monitorIds=%s" ,
440
- baseURL ,
496
+ // Convert API URL to App URL for internal endpoint
497
+ baseURL := strings . Replace ( cfg . url , ":// api." , "://app." , 1 )
498
+ fullURL := fmt . Sprintf ( "%s/api/internal/monitors/history?monitorIds=%s" , baseURL ,
441
499
strings .Join (monitorIds , "," ))
442
500
443
501
req , err := http .NewRequestWithContext (ctx , "GET" , fullURL , nil )
@@ -447,9 +505,9 @@ func newGetMonitorsHistoryHandler(client *axiom.Client, cfg config) func(mcp.Cal
447
505
448
506
req .Header .Set ("Authorization" , "Bearer " + cfg .token )
449
507
req .Header .Set ("Accept" , "application/json" )
508
+ req .Header .Set ("X-AXIOM-ORG-ID" , cfg .orgID )
450
509
451
- clientHTTP := & http.Client {}
452
- resp , err := clientHTTP .Do (req )
510
+ resp , err := httpClient .Do (req )
453
511
if err != nil {
454
512
return mcp.CallToolResult {}, fmt .Errorf ("failed to execute request: %w" , err )
455
513
}
@@ -495,3 +553,111 @@ func newGetMonitorsHistoryHandler(client *axiom.Client, cfg config) func(mcp.Cal
495
553
}, nil
496
554
}
497
555
}
556
+
557
+ // getCurrentUserId gets the current user ID from /v2/user endpoint using PAT
558
+ func getCurrentUserId (cfg config , httpClient * http.Client ) (string , error ) {
559
+ ctx := context .Background ()
560
+
561
+ if cfg .token == "" {
562
+ return "" , fmt .Errorf ("personal Access Token (PAT) is required" )
563
+ }
564
+
565
+ baseURL := cfg .url
566
+ fullURL := baseURL + "/v2/user"
567
+
568
+ req , err := http .NewRequestWithContext (ctx , "GET" , fullURL , nil )
569
+ if err != nil {
570
+ return "" , fmt .Errorf ("failed to create request: %w" , err )
571
+ }
572
+
573
+ req .Header .Set ("Authorization" , "Bearer " + cfg .token )
574
+ req .Header .Set ("Accept" , "application/json" )
575
+
576
+ resp , err := httpClient .Do (req )
577
+ if err != nil {
578
+ return "" , fmt .Errorf ("failed to execute request: %w" , err )
579
+ }
580
+ defer resp .Body .Close ()
581
+
582
+ if resp .StatusCode != http .StatusOK {
583
+ body , _ := io .ReadAll (resp .Body )
584
+ return "" , fmt .Errorf ("request failed with status %d: %s" , resp .StatusCode , string (body ))
585
+ }
586
+
587
+ body , err := io .ReadAll (resp .Body )
588
+ if err != nil {
589
+ return "" , fmt .Errorf ("failed to read response body: %w" , err )
590
+ }
591
+ var userResponse struct {
592
+ ID string `json:"id"`
593
+ }
594
+ if err := json .Unmarshal (body , & userResponse ); err != nil {
595
+ return "" , fmt .Errorf ("failed to parse user response: %w" , err )
596
+ }
597
+
598
+ if userResponse .ID == "" {
599
+ return "" , fmt .Errorf ("user ID not found in response" )
600
+ }
601
+
602
+ return userResponse .ID , nil
603
+ }
604
+
605
+ // newGetQueryHistoryHandler creates a handler for retrieving query execution history from axiom-history dataset
606
+ func newGetQueryHistoryHandler (client * axiom.Client , cfg config , httpClient * http.Client ) func (mcp.CallToolRequestParams ) (mcp.CallToolResult , error ) {
607
+ return func (params mcp.CallToolRequestParams ) (mcp.CallToolResult , error ) {
608
+ ctx := context .Background ()
609
+
610
+ limit := 50
611
+ if limitParam , ok := params .Arguments ["limit" ].(float64 ); ok && limitParam > 0 {
612
+ limit = int (limitParam )
613
+ if limit > 500 {
614
+ limit = 500
615
+ }
616
+ }
617
+
618
+ currentUserId , err := getCurrentUserId (cfg , httpClient )
619
+ if err != nil {
620
+ return mcp.CallToolResult {}, fmt .Errorf ("failed to get current user: %w" , err )
621
+ }
622
+
623
+ var whereFilters []string
624
+ whereFilters = append (whereFilters , "kind == \" apl\" " )
625
+
626
+ // Use current user by default, but allow override - if the user provides another ID
627
+ userToFilter := currentUserId
628
+ if userParam , ok := params .Arguments ["user" ].(string ); ok && userParam != "" {
629
+ userToFilter = userParam
630
+ }
631
+ whereFilters = append (whereFilters , fmt .Sprintf ("who == \" %s\" " , userToFilter ))
632
+
633
+ // Optional dataset filter
634
+ if datasetParam , ok := params .Arguments ["dataset" ].(string ); ok && datasetParam != "" {
635
+ whereFilters = append (whereFilters , fmt .Sprintf ("dataset == \" %s\" " , datasetParam ))
636
+ }
637
+
638
+ aplQuery := fmt .Sprintf (
639
+ "[\" axiom-history\" ] | where %s | sort by _time desc | take %d | project _time, dataset, [\" query.apl\" ], who, created" ,
640
+ strings .Join (whereFilters , " and " ),
641
+ limit ,
642
+ )
643
+
644
+ result , err := client .Query (ctx , aplQuery )
645
+ if err != nil {
646
+ return mcp.CallToolResult {}, fmt .Errorf ("failed to execute query history query: %w" , err )
647
+ }
648
+
649
+ jsonData , err := json .MarshalIndent (result , "" , " " )
650
+ if err != nil {
651
+ return mcp.CallToolResult {}, fmt .Errorf ("failed to marshal query history response: %w" , err )
652
+ }
653
+
654
+ return mcp.CallToolResult {
655
+ Content : []any {
656
+ mcp.TextContent {
657
+ Text : string (jsonData ),
658
+ Type : "text" ,
659
+ },
660
+ },
661
+ }, nil
662
+ }
663
+ }
0 commit comments