utils.go

  1package mcp
  2
  3import (
  4	"encoding/json"
  5	"fmt"
  6
  7	"github.com/spf13/cast"
  8)
  9
 10// ClientRequest types
 11var _ ClientRequest = &PingRequest{}
 12var _ ClientRequest = &InitializeRequest{}
 13var _ ClientRequest = &CompleteRequest{}
 14var _ ClientRequest = &SetLevelRequest{}
 15var _ ClientRequest = &GetPromptRequest{}
 16var _ ClientRequest = &ListPromptsRequest{}
 17var _ ClientRequest = &ListResourcesRequest{}
 18var _ ClientRequest = &ReadResourceRequest{}
 19var _ ClientRequest = &SubscribeRequest{}
 20var _ ClientRequest = &UnsubscribeRequest{}
 21var _ ClientRequest = &CallToolRequest{}
 22var _ ClientRequest = &ListToolsRequest{}
 23
 24// ClientNotification types
 25var _ ClientNotification = &CancelledNotification{}
 26var _ ClientNotification = &ProgressNotification{}
 27var _ ClientNotification = &InitializedNotification{}
 28var _ ClientNotification = &RootsListChangedNotification{}
 29
 30// ClientResult types
 31var _ ClientResult = &EmptyResult{}
 32var _ ClientResult = &CreateMessageResult{}
 33var _ ClientResult = &ListRootsResult{}
 34
 35// ServerRequest types
 36var _ ServerRequest = &PingRequest{}
 37var _ ServerRequest = &CreateMessageRequest{}
 38var _ ServerRequest = &ListRootsRequest{}
 39
 40// ServerNotification types
 41var _ ServerNotification = &CancelledNotification{}
 42var _ ServerNotification = &ProgressNotification{}
 43var _ ServerNotification = &LoggingMessageNotification{}
 44var _ ServerNotification = &ResourceUpdatedNotification{}
 45var _ ServerNotification = &ResourceListChangedNotification{}
 46var _ ServerNotification = &ToolListChangedNotification{}
 47var _ ServerNotification = &PromptListChangedNotification{}
 48
 49// ServerResult types
 50var _ ServerResult = &EmptyResult{}
 51var _ ServerResult = &InitializeResult{}
 52var _ ServerResult = &CompleteResult{}
 53var _ ServerResult = &GetPromptResult{}
 54var _ ServerResult = &ListPromptsResult{}
 55var _ ServerResult = &ListResourcesResult{}
 56var _ ServerResult = &ReadResourceResult{}
 57var _ ServerResult = &CallToolResult{}
 58var _ ServerResult = &ListToolsResult{}
 59
 60// Helper functions for type assertions
 61
 62// asType attempts to cast the given interface to the given type
 63func asType[T any](content any) (*T, bool) {
 64	tc, ok := content.(T)
 65	if !ok {
 66		return nil, false
 67	}
 68	return &tc, true
 69}
 70
 71// AsTextContent attempts to cast the given interface to TextContent
 72func AsTextContent(content any) (*TextContent, bool) {
 73	return asType[TextContent](content)
 74}
 75
 76// AsImageContent attempts to cast the given interface to ImageContent
 77func AsImageContent(content any) (*ImageContent, bool) {
 78	return asType[ImageContent](content)
 79}
 80
 81// AsAudioContent attempts to cast the given interface to AudioContent
 82func AsAudioContent(content any) (*AudioContent, bool) {
 83	return asType[AudioContent](content)
 84}
 85
 86// AsEmbeddedResource attempts to cast the given interface to EmbeddedResource
 87func AsEmbeddedResource(content any) (*EmbeddedResource, bool) {
 88	return asType[EmbeddedResource](content)
 89}
 90
 91// AsTextResourceContents attempts to cast the given interface to TextResourceContents
 92func AsTextResourceContents(content any) (*TextResourceContents, bool) {
 93	return asType[TextResourceContents](content)
 94}
 95
 96// AsBlobResourceContents attempts to cast the given interface to BlobResourceContents
 97func AsBlobResourceContents(content any) (*BlobResourceContents, bool) {
 98	return asType[BlobResourceContents](content)
 99}
100
101// Helper function for JSON-RPC
102
103// NewJSONRPCResponse creates a new JSONRPCResponse with the given id and result
104func NewJSONRPCResponse(id RequestId, result Result) JSONRPCResponse {
105	return JSONRPCResponse{
106		JSONRPC: JSONRPC_VERSION,
107		ID:      id,
108		Result:  result,
109	}
110}
111
112// NewJSONRPCError creates a new JSONRPCResponse with the given id, code, and message
113func NewJSONRPCError(
114	id RequestId,
115	code int,
116	message string,
117	data any,
118) JSONRPCError {
119	return JSONRPCError{
120		JSONRPC: JSONRPC_VERSION,
121		ID:      id,
122		Error: struct {
123			Code    int    `json:"code"`
124			Message string `json:"message"`
125			Data    any    `json:"data,omitempty"`
126		}{
127			Code:    code,
128			Message: message,
129			Data:    data,
130		},
131	}
132}
133
134// NewProgressNotification
135// Helper function for creating a progress notification
136func NewProgressNotification(
137	token ProgressToken,
138	progress float64,
139	total *float64,
140	message *string,
141) ProgressNotification {
142	notification := ProgressNotification{
143		Notification: Notification{
144			Method: "notifications/progress",
145		},
146		Params: struct {
147			ProgressToken ProgressToken `json:"progressToken"`
148			Progress      float64       `json:"progress"`
149			Total         float64       `json:"total,omitempty"`
150			Message       string        `json:"message,omitempty"`
151		}{
152			ProgressToken: token,
153			Progress:      progress,
154		},
155	}
156	if total != nil {
157		notification.Params.Total = *total
158	}
159	if message != nil {
160		notification.Params.Message = *message
161	}
162	return notification
163}
164
165// NewLoggingMessageNotification
166// Helper function for creating a logging message notification
167func NewLoggingMessageNotification(
168	level LoggingLevel,
169	logger string,
170	data any,
171) LoggingMessageNotification {
172	return LoggingMessageNotification{
173		Notification: Notification{
174			Method: "notifications/message",
175		},
176		Params: struct {
177			Level  LoggingLevel `json:"level"`
178			Logger string       `json:"logger,omitempty"`
179			Data   any          `json:"data"`
180		}{
181			Level:  level,
182			Logger: logger,
183			Data:   data,
184		},
185	}
186}
187
188// NewPromptMessage
189// Helper function to create a new PromptMessage
190func NewPromptMessage(role Role, content Content) PromptMessage {
191	return PromptMessage{
192		Role:    role,
193		Content: content,
194	}
195}
196
197// NewTextContent
198// Helper function to create a new TextContent
199func NewTextContent(text string) TextContent {
200	return TextContent{
201		Type: "text",
202		Text: text,
203	}
204}
205
206// NewImageContent
207// Helper function to create a new ImageContent
208func NewImageContent(data, mimeType string) ImageContent {
209	return ImageContent{
210		Type:     "image",
211		Data:     data,
212		MIMEType: mimeType,
213	}
214}
215
216// Helper function to create a new AudioContent
217func NewAudioContent(data, mimeType string) AudioContent {
218	return AudioContent{
219		Type:     "audio",
220		Data:     data,
221		MIMEType: mimeType,
222	}
223}
224
225// Helper function to create a new ResourceLink
226func NewResourceLink(uri, name, description, mimeType string) ResourceLink {
227	return ResourceLink{
228		Type:        "resource_link",
229		URI:         uri,
230		Name:        name,
231		Description: description,
232		MIMEType:    mimeType,
233	}
234}
235
236// Helper function to create a new EmbeddedResource
237func NewEmbeddedResource(resource ResourceContents) EmbeddedResource {
238	return EmbeddedResource{
239		Type:     "resource",
240		Resource: resource,
241	}
242}
243
244// NewToolResultText creates a new CallToolResult with a text content
245func NewToolResultText(text string) *CallToolResult {
246	return &CallToolResult{
247		Content: []Content{
248			TextContent{
249				Type: "text",
250				Text: text,
251			},
252		},
253	}
254}
255
256// NewToolResultImage creates a new CallToolResult with both text and image content
257func NewToolResultImage(text, imageData, mimeType string) *CallToolResult {
258	return &CallToolResult{
259		Content: []Content{
260			TextContent{
261				Type: "text",
262				Text: text,
263			},
264			ImageContent{
265				Type:     "image",
266				Data:     imageData,
267				MIMEType: mimeType,
268			},
269		},
270	}
271}
272
273// NewToolResultAudio creates a new CallToolResult with both text and audio content
274func NewToolResultAudio(text, imageData, mimeType string) *CallToolResult {
275	return &CallToolResult{
276		Content: []Content{
277			TextContent{
278				Type: "text",
279				Text: text,
280			},
281			AudioContent{
282				Type:     "audio",
283				Data:     imageData,
284				MIMEType: mimeType,
285			},
286		},
287	}
288}
289
290// NewToolResultResource creates a new CallToolResult with an embedded resource
291func NewToolResultResource(
292	text string,
293	resource ResourceContents,
294) *CallToolResult {
295	return &CallToolResult{
296		Content: []Content{
297			TextContent{
298				Type: "text",
299				Text: text,
300			},
301			EmbeddedResource{
302				Type:     "resource",
303				Resource: resource,
304			},
305		},
306	}
307}
308
309// NewToolResultError creates a new CallToolResult with an error message.
310// Any errors that originate from the tool SHOULD be reported inside the result object.
311func NewToolResultError(text string) *CallToolResult {
312	return &CallToolResult{
313		Content: []Content{
314			TextContent{
315				Type: "text",
316				Text: text,
317			},
318		},
319		IsError: true,
320	}
321}
322
323// NewToolResultErrorFromErr creates a new CallToolResult with an error message.
324// If an error is provided, its details will be appended to the text message.
325// Any errors that originate from the tool SHOULD be reported inside the result object.
326func NewToolResultErrorFromErr(text string, err error) *CallToolResult {
327	if err != nil {
328		text = fmt.Sprintf("%s: %v", text, err)
329	}
330	return &CallToolResult{
331		Content: []Content{
332			TextContent{
333				Type: "text",
334				Text: text,
335			},
336		},
337		IsError: true,
338	}
339}
340
341// NewToolResultErrorf creates a new CallToolResult with an error message.
342// The error message is formatted using the fmt package.
343// Any errors that originate from the tool SHOULD be reported inside the result object.
344func NewToolResultErrorf(format string, a ...any) *CallToolResult {
345	return &CallToolResult{
346		Content: []Content{
347			TextContent{
348				Type: "text",
349				Text: fmt.Sprintf(format, a...),
350			},
351		},
352		IsError: true,
353	}
354}
355
356// NewListResourcesResult creates a new ListResourcesResult
357func NewListResourcesResult(
358	resources []Resource,
359	nextCursor Cursor,
360) *ListResourcesResult {
361	return &ListResourcesResult{
362		PaginatedResult: PaginatedResult{
363			NextCursor: nextCursor,
364		},
365		Resources: resources,
366	}
367}
368
369// NewListResourceTemplatesResult creates a new ListResourceTemplatesResult
370func NewListResourceTemplatesResult(
371	templates []ResourceTemplate,
372	nextCursor Cursor,
373) *ListResourceTemplatesResult {
374	return &ListResourceTemplatesResult{
375		PaginatedResult: PaginatedResult{
376			NextCursor: nextCursor,
377		},
378		ResourceTemplates: templates,
379	}
380}
381
382// NewReadResourceResult creates a new ReadResourceResult with text content
383func NewReadResourceResult(text string) *ReadResourceResult {
384	return &ReadResourceResult{
385		Contents: []ResourceContents{
386			TextResourceContents{
387				Text: text,
388			},
389		},
390	}
391}
392
393// NewListPromptsResult creates a new ListPromptsResult
394func NewListPromptsResult(
395	prompts []Prompt,
396	nextCursor Cursor,
397) *ListPromptsResult {
398	return &ListPromptsResult{
399		PaginatedResult: PaginatedResult{
400			NextCursor: nextCursor,
401		},
402		Prompts: prompts,
403	}
404}
405
406// NewGetPromptResult creates a new GetPromptResult
407func NewGetPromptResult(
408	description string,
409	messages []PromptMessage,
410) *GetPromptResult {
411	return &GetPromptResult{
412		Description: description,
413		Messages:    messages,
414	}
415}
416
417// NewListToolsResult creates a new ListToolsResult
418func NewListToolsResult(tools []Tool, nextCursor Cursor) *ListToolsResult {
419	return &ListToolsResult{
420		PaginatedResult: PaginatedResult{
421			NextCursor: nextCursor,
422		},
423		Tools: tools,
424	}
425}
426
427// NewInitializeResult creates a new InitializeResult
428func NewInitializeResult(
429	protocolVersion string,
430	capabilities ServerCapabilities,
431	serverInfo Implementation,
432	instructions string,
433) *InitializeResult {
434	return &InitializeResult{
435		ProtocolVersion: protocolVersion,
436		Capabilities:    capabilities,
437		ServerInfo:      serverInfo,
438		Instructions:    instructions,
439	}
440}
441
442// FormatNumberResult
443// Helper for formatting numbers in tool results
444func FormatNumberResult(value float64) *CallToolResult {
445	return NewToolResultText(fmt.Sprintf("%.2f", value))
446}
447
448func ExtractString(data map[string]any, key string) string {
449	if value, ok := data[key]; ok {
450		if str, ok := value.(string); ok {
451			return str
452		}
453	}
454	return ""
455}
456
457func ExtractMap(data map[string]any, key string) map[string]any {
458	if value, ok := data[key]; ok {
459		if m, ok := value.(map[string]any); ok {
460			return m
461		}
462	}
463	return nil
464}
465
466func ParseContent(contentMap map[string]any) (Content, error) {
467	contentType := ExtractString(contentMap, "type")
468
469	switch contentType {
470	case "text":
471		text := ExtractString(contentMap, "text")
472		return NewTextContent(text), nil
473
474	case "image":
475		data := ExtractString(contentMap, "data")
476		mimeType := ExtractString(contentMap, "mimeType")
477		if data == "" || mimeType == "" {
478			return nil, fmt.Errorf("image data or mimeType is missing")
479		}
480		return NewImageContent(data, mimeType), nil
481
482	case "audio":
483		data := ExtractString(contentMap, "data")
484		mimeType := ExtractString(contentMap, "mimeType")
485		if data == "" || mimeType == "" {
486			return nil, fmt.Errorf("audio data or mimeType is missing")
487		}
488		return NewAudioContent(data, mimeType), nil
489
490	case "resource_link":
491		uri := ExtractString(contentMap, "uri")
492		name := ExtractString(contentMap, "name")
493		description := ExtractString(contentMap, "description")
494		mimeType := ExtractString(contentMap, "mimeType")
495		if uri == "" || name == "" {
496			return nil, fmt.Errorf("resource_link uri or name is missing")
497		}
498		return NewResourceLink(uri, name, description, mimeType), nil
499
500	case "resource":
501		resourceMap := ExtractMap(contentMap, "resource")
502		if resourceMap == nil {
503			return nil, fmt.Errorf("resource is missing")
504		}
505
506		resourceContents, err := ParseResourceContents(resourceMap)
507		if err != nil {
508			return nil, err
509		}
510
511		return NewEmbeddedResource(resourceContents), nil
512	}
513
514	return nil, fmt.Errorf("unsupported content type: %s", contentType)
515}
516
517func ParseGetPromptResult(rawMessage *json.RawMessage) (*GetPromptResult, error) {
518	if rawMessage == nil {
519		return nil, fmt.Errorf("response is nil")
520	}
521
522	var jsonContent map[string]any
523	if err := json.Unmarshal(*rawMessage, &jsonContent); err != nil {
524		return nil, fmt.Errorf("failed to unmarshal response: %w", err)
525	}
526
527	result := GetPromptResult{}
528
529	meta, ok := jsonContent["_meta"]
530	if ok {
531		if metaMap, ok := meta.(map[string]any); ok {
532			result.Meta = metaMap
533		}
534	}
535
536	description, ok := jsonContent["description"]
537	if ok {
538		if descriptionStr, ok := description.(string); ok {
539			result.Description = descriptionStr
540		}
541	}
542
543	messages, ok := jsonContent["messages"]
544	if ok {
545		messagesArr, ok := messages.([]any)
546		if !ok {
547			return nil, fmt.Errorf("messages is not an array")
548		}
549
550		for _, message := range messagesArr {
551			messageMap, ok := message.(map[string]any)
552			if !ok {
553				return nil, fmt.Errorf("message is not an object")
554			}
555
556			// Extract role
557			roleStr := ExtractString(messageMap, "role")
558			if roleStr == "" || (roleStr != string(RoleAssistant) && roleStr != string(RoleUser)) {
559				return nil, fmt.Errorf("unsupported role: %s", roleStr)
560			}
561
562			// Extract content
563			contentMap, ok := messageMap["content"].(map[string]any)
564			if !ok {
565				return nil, fmt.Errorf("content is not an object")
566			}
567
568			// Process content
569			content, err := ParseContent(contentMap)
570			if err != nil {
571				return nil, err
572			}
573
574			// Append processed message
575			result.Messages = append(result.Messages, NewPromptMessage(Role(roleStr), content))
576
577		}
578	}
579
580	return &result, nil
581}
582
583func ParseCallToolResult(rawMessage *json.RawMessage) (*CallToolResult, error) {
584	if rawMessage == nil {
585		return nil, fmt.Errorf("response is nil")
586	}
587
588	var jsonContent map[string]any
589	if err := json.Unmarshal(*rawMessage, &jsonContent); err != nil {
590		return nil, fmt.Errorf("failed to unmarshal response: %w", err)
591	}
592
593	var result CallToolResult
594
595	meta, ok := jsonContent["_meta"]
596	if ok {
597		if metaMap, ok := meta.(map[string]any); ok {
598			result.Meta = metaMap
599		}
600	}
601
602	isError, ok := jsonContent["isError"]
603	if ok {
604		if isErrorBool, ok := isError.(bool); ok {
605			result.IsError = isErrorBool
606		}
607	}
608
609	contents, ok := jsonContent["content"]
610	if !ok {
611		return nil, fmt.Errorf("content is missing")
612	}
613
614	contentArr, ok := contents.([]any)
615	if !ok {
616		return nil, fmt.Errorf("content is not an array")
617	}
618
619	for _, content := range contentArr {
620		// Extract content
621		contentMap, ok := content.(map[string]any)
622		if !ok {
623			return nil, fmt.Errorf("content is not an object")
624		}
625
626		// Process content
627		content, err := ParseContent(contentMap)
628		if err != nil {
629			return nil, err
630		}
631
632		result.Content = append(result.Content, content)
633	}
634
635	return &result, nil
636}
637
638func ParseResourceContents(contentMap map[string]any) (ResourceContents, error) {
639	uri := ExtractString(contentMap, "uri")
640	if uri == "" {
641		return nil, fmt.Errorf("resource uri is missing")
642	}
643
644	mimeType := ExtractString(contentMap, "mimeType")
645
646	if text := ExtractString(contentMap, "text"); text != "" {
647		return TextResourceContents{
648			URI:      uri,
649			MIMEType: mimeType,
650			Text:     text,
651		}, nil
652	}
653
654	if blob := ExtractString(contentMap, "blob"); blob != "" {
655		return BlobResourceContents{
656			URI:      uri,
657			MIMEType: mimeType,
658			Blob:     blob,
659		}, nil
660	}
661
662	return nil, fmt.Errorf("unsupported resource type")
663}
664
665func ParseReadResourceResult(rawMessage *json.RawMessage) (*ReadResourceResult, error) {
666	if rawMessage == nil {
667		return nil, fmt.Errorf("response is nil")
668	}
669
670	var jsonContent map[string]any
671	if err := json.Unmarshal(*rawMessage, &jsonContent); err != nil {
672		return nil, fmt.Errorf("failed to unmarshal response: %w", err)
673	}
674
675	var result ReadResourceResult
676
677	meta, ok := jsonContent["_meta"]
678	if ok {
679		if metaMap, ok := meta.(map[string]any); ok {
680			result.Meta = metaMap
681		}
682	}
683
684	contents, ok := jsonContent["contents"]
685	if !ok {
686		return nil, fmt.Errorf("contents is missing")
687	}
688
689	contentArr, ok := contents.([]any)
690	if !ok {
691		return nil, fmt.Errorf("contents is not an array")
692	}
693
694	for _, content := range contentArr {
695		// Extract content
696		contentMap, ok := content.(map[string]any)
697		if !ok {
698			return nil, fmt.Errorf("content is not an object")
699		}
700
701		// Process content
702		content, err := ParseResourceContents(contentMap)
703		if err != nil {
704			return nil, err
705		}
706
707		result.Contents = append(result.Contents, content)
708	}
709
710	return &result, nil
711}
712
713func ParseArgument(request CallToolRequest, key string, defaultVal any) any {
714	args := request.GetArguments()
715	if _, ok := args[key]; !ok {
716		return defaultVal
717	} else {
718		return args[key]
719	}
720}
721
722// ParseBoolean extracts and converts a boolean parameter from a CallToolRequest.
723// If the key is not found in the Arguments map, the defaultValue is returned.
724// The function uses cast.ToBool for conversion which handles various string representations
725// such as "true", "yes", "1", etc.
726func ParseBoolean(request CallToolRequest, key string, defaultValue bool) bool {
727	v := ParseArgument(request, key, defaultValue)
728	return cast.ToBool(v)
729}
730
731// ParseInt64 extracts and converts an int64 parameter from a CallToolRequest.
732// If the key is not found in the Arguments map, the defaultValue is returned.
733func ParseInt64(request CallToolRequest, key string, defaultValue int64) int64 {
734	v := ParseArgument(request, key, defaultValue)
735	return cast.ToInt64(v)
736}
737
738// ParseInt32 extracts and converts an int32 parameter from a CallToolRequest.
739func ParseInt32(request CallToolRequest, key string, defaultValue int32) int32 {
740	v := ParseArgument(request, key, defaultValue)
741	return cast.ToInt32(v)
742}
743
744// ParseInt16 extracts and converts an int16 parameter from a CallToolRequest.
745func ParseInt16(request CallToolRequest, key string, defaultValue int16) int16 {
746	v := ParseArgument(request, key, defaultValue)
747	return cast.ToInt16(v)
748}
749
750// ParseInt8 extracts and converts an int8 parameter from a CallToolRequest.
751func ParseInt8(request CallToolRequest, key string, defaultValue int8) int8 {
752	v := ParseArgument(request, key, defaultValue)
753	return cast.ToInt8(v)
754}
755
756// ParseInt extracts and converts an int parameter from a CallToolRequest.
757func ParseInt(request CallToolRequest, key string, defaultValue int) int {
758	v := ParseArgument(request, key, defaultValue)
759	return cast.ToInt(v)
760}
761
762// ParseUInt extracts and converts an uint parameter from a CallToolRequest.
763func ParseUInt(request CallToolRequest, key string, defaultValue uint) uint {
764	v := ParseArgument(request, key, defaultValue)
765	return cast.ToUint(v)
766}
767
768// ParseUInt64 extracts and converts an uint64 parameter from a CallToolRequest.
769func ParseUInt64(request CallToolRequest, key string, defaultValue uint64) uint64 {
770	v := ParseArgument(request, key, defaultValue)
771	return cast.ToUint64(v)
772}
773
774// ParseUInt32 extracts and converts an uint32 parameter from a CallToolRequest.
775func ParseUInt32(request CallToolRequest, key string, defaultValue uint32) uint32 {
776	v := ParseArgument(request, key, defaultValue)
777	return cast.ToUint32(v)
778}
779
780// ParseUInt16 extracts and converts an uint16 parameter from a CallToolRequest.
781func ParseUInt16(request CallToolRequest, key string, defaultValue uint16) uint16 {
782	v := ParseArgument(request, key, defaultValue)
783	return cast.ToUint16(v)
784}
785
786// ParseUInt8 extracts and converts an uint8 parameter from a CallToolRequest.
787func ParseUInt8(request CallToolRequest, key string, defaultValue uint8) uint8 {
788	v := ParseArgument(request, key, defaultValue)
789	return cast.ToUint8(v)
790}
791
792// ParseFloat32 extracts and converts a float32 parameter from a CallToolRequest.
793func ParseFloat32(request CallToolRequest, key string, defaultValue float32) float32 {
794	v := ParseArgument(request, key, defaultValue)
795	return cast.ToFloat32(v)
796}
797
798// ParseFloat64 extracts and converts a float64 parameter from a CallToolRequest.
799func ParseFloat64(request CallToolRequest, key string, defaultValue float64) float64 {
800	v := ParseArgument(request, key, defaultValue)
801	return cast.ToFloat64(v)
802}
803
804// ParseString extracts and converts a string parameter from a CallToolRequest.
805func ParseString(request CallToolRequest, key string, defaultValue string) string {
806	v := ParseArgument(request, key, defaultValue)
807	return cast.ToString(v)
808}
809
810// ParseStringMap extracts and converts a string map parameter from a CallToolRequest.
811func ParseStringMap(request CallToolRequest, key string, defaultValue map[string]any) map[string]any {
812	v := ParseArgument(request, key, defaultValue)
813	return cast.ToStringMap(v)
814}
815
816// ToBoolPtr returns a pointer to the given boolean value
817func ToBoolPtr(b bool) *bool {
818	return &b
819}