home-lab/packages/mcp-server/mcp/server/jsonrpc.scm
Geir Okkenhaug Jerstad 52a9d544fc feat: comprehensive audio system and MCP server implementation
Audio System Enhancements:
- Complete PipeWire configuration with WirePlumber session management
- AI-powered noise suppression using RNNoise plugin
- GUI applications: EasyEffects, pavucontrol, Helvum, qpwgraph, pwvucontrol
- Pre-configured audio presets for microphone noise suppression
- Desktop integration with auto-start and helper scripts
- Validation tools and interactive audio management utilities
- Real-time audio processing with RTKit optimization
- Cross-application compatibility (Discord, Zoom, OBS, etc.)

MCP (Model Context Protocol) Implementation in Guile Scheme:
- Modular MCP server architecture with clean separation of concerns
- JSON-RPC transport layer with WebSocket and stdio support
- Protocol compliance with MCP specification
- Comprehensive error handling and validation
- Router system for tool and resource management
- Integration layer for NixOS Home Lab management
- Full test suite with unit and integration tests
- Documentation and usage examples

Technical Details:
- Removed conflicting ALSA udev rules while maintaining compatibility
- Fixed package dependencies and service configurations
- Successfully deployed and tested on congenital-optimist machine
- Functional programming approach using Guile Scheme modules
- Type-safe protocol implementation with validation
- Async/await pattern support for concurrent operations

This represents a significant enhancement to the Home Lab infrastructure,
providing both professional-grade audio capabilities and a robust MCP
server implementation for AI assistant integration.
2025-06-18 21:10:06 +02:00

228 lines
8.1 KiB
Scheme

;; JSON-RPC 2.0 Protocol Implementation for MCP
;; This module implements the foundational JSON-RPC 2.0 protocol handling
;; as required by the Model Context Protocol (MCP) specification.
(define-module (mcp server jsonrpc)
#:use-module (ice-9 match)
#:use-module (ice-9 receive)
#:use-module (json)
#:use-module (srfi srfi-1)
#:use-module (srfi srfi-9)
#:use-module (srfi srfi-26)
#:export (make-jsonrpc-request
make-jsonrpc-response
make-jsonrpc-error
make-jsonrpc-notification
parse-jsonrpc-message
validate-jsonrpc-message
jsonrpc-request?
jsonrpc-response?
jsonrpc-notification?
jsonrpc-error?
jsonrpc-request-id
jsonrpc-request-method
jsonrpc-request-params
jsonrpc-response-id
jsonrpc-response-result
jsonrpc-error-code
jsonrpc-error-message
jsonrpc-error-data
jsonrpc-error-id
jsonrpc-notification-method
jsonrpc-notification-params
handle-jsonrpc-batch
jsonrpc-message->json
*jsonrpc-error-codes*))
;; JSON-RPC 2.0 Error Codes
(define *jsonrpc-error-codes*
'((parse-error . -32700)
(invalid-request . -32600)
(method-not-found . -32601)
(invalid-params . -32602)
(internal-error . -32603)
(server-error-start . -32099)
(server-error-end . -32000)))
;; Record types for JSON-RPC messages
(define-record-type <jsonrpc-request>
(make-jsonrpc-request id method params)
jsonrpc-request?
(id jsonrpc-request-id)
(method jsonrpc-request-method)
(params jsonrpc-request-params))
(define-record-type <jsonrpc-response>
(make-jsonrpc-response id result)
jsonrpc-response?
(id jsonrpc-response-id)
(result jsonrpc-response-result))
(define-record-type <jsonrpc-error>
(make-jsonrpc-error id code message data)
jsonrpc-error?
(id jsonrpc-error-id)
(code jsonrpc-error-code)
(message jsonrpc-error-message)
(data jsonrpc-error-data))
(define-record-type <jsonrpc-notification>
(make-jsonrpc-notification method params)
jsonrpc-notification?
(method jsonrpc-notification-method)
(params jsonrpc-notification-params))
;; Validation functions
(define (valid-jsonrpc-version? version)
"Check if the JSON-RPC version is valid (must be '2.0')"
(and (string? version) (string=? version "2.0")))
(define (valid-method-name? method)
"Check if the method name is valid (string, not starting with 'rpc.')"
(and (string? method)
(not (string-prefix? "rpc." method))))
(define (valid-id? id)
"Check if the ID is valid (string, number, or null)"
(or (string? id)
(number? id)
(null? id)))
;; Message parsing and validation
(define (parse-jsonrpc-message json-string)
"Parse a JSON-RPC message from a JSON string"
(catch 'json-invalid
(lambda ()
(let ((parsed (json-string->scm json-string)))
(validate-and-create-message parsed)))
(lambda (key . args)
(make-jsonrpc-error #f
(assoc-ref *jsonrpc-error-codes* 'parse-error)
"Parse error"
#f))))
(define (validate-jsonrpc-message message)
"Validate a parsed JSON-RPC message structure"
(cond
((not (list? message))
(make-jsonrpc-error #f
(assoc-ref *jsonrpc-error-codes* 'invalid-request)
"Invalid Request: message must be an object"
#f))
((not (valid-jsonrpc-version? (assoc-ref message "jsonrpc")))
(make-jsonrpc-error (assoc-ref message "id")
(assoc-ref *jsonrpc-error-codes* 'invalid-request)
"Invalid Request: jsonrpc version must be '2.0'"
#f))
(else #t)))
(define (validate-and-create-message parsed)
"Validate and create appropriate message type from parsed JSON"
(let ((validation-result (validate-jsonrpc-message parsed)))
(if (jsonrpc-error? validation-result)
validation-result
(create-message-from-parsed parsed))))
(define (create-message-from-parsed parsed)
"Create appropriate message type from validated parsed JSON"
(let ((method (assoc-ref parsed "method"))
(id (assoc-ref parsed "id"))
(params (assoc-ref parsed "params"))
(result (assoc-ref parsed "result"))
(error (assoc-ref parsed "error")))
(cond
;; Response with result
((and (not method) result (not error))
(if (not id)
(make-jsonrpc-error #f
(assoc-ref *jsonrpc-error-codes* 'invalid-request)
"Invalid Request: response must have id"
#f)
(make-jsonrpc-response id result)))
;; Error response
((and (not method) (not result) error)
(if (not id)
(make-jsonrpc-error #f
(assoc-ref *jsonrpc-error-codes* 'invalid-request)
"Invalid Request: error response must have id"
#f)
(let ((error-code (assoc-ref error "code"))
(error-message (assoc-ref error "message"))
(error-data (assoc-ref error "data")))
(make-jsonrpc-error id error-code error-message error-data))))
;; Request or notification
((and method (string? method))
(if (not (valid-method-name? method))
(make-jsonrpc-error id
(assoc-ref *jsonrpc-error-codes* 'invalid-request)
"Invalid Request: invalid method name"
#f)
(if (not id)
;; Notification (no id)
(make-jsonrpc-notification method params)
;; Request (has id)
(if (not (valid-id? id))
(make-jsonrpc-error id
(assoc-ref *jsonrpc-error-codes* 'invalid-request)
"Invalid Request: invalid id"
#f)
(make-jsonrpc-request id method params)))))
;; Invalid message
(else
(make-jsonrpc-error id
(assoc-ref *jsonrpc-error-codes* 'invalid-request)
"Invalid Request: message structure is invalid"
#f)))))
;; Batch request handling
(define (handle-jsonrpc-batch messages)
"Handle a batch of JSON-RPC messages"
(if (and (list? messages) (not (null? messages)))
(map parse-jsonrpc-message messages)
(list (make-jsonrpc-error #f
(assoc-ref *jsonrpc-error-codes* 'invalid-request)
"Invalid Request: batch must be non-empty array"
#f))))
;; Message serialization helpers
(define (jsonrpc-message->json message)
"Convert a JSON-RPC message to JSON string"
(cond
((jsonrpc-request? message)
(scm->json-string
`(("jsonrpc" . "2.0")
("id" . ,(jsonrpc-request-id message))
("method" . ,(jsonrpc-request-method message))
,@(if (jsonrpc-request-params message)
`(("params" . ,(jsonrpc-request-params message)))
'()))))
((jsonrpc-response? message)
(scm->json-string
`(("jsonrpc" . "2.0")
("id" . ,(jsonrpc-response-id message))
("result" . ,(jsonrpc-response-result message)))))
((jsonrpc-error? message)
(scm->json-string
`(("jsonrpc" . "2.0")
("id" . ,(jsonrpc-error-id message))
("error" . (("code" . ,(jsonrpc-error-code message))
("message" . ,(jsonrpc-error-message message))
,@(if (jsonrpc-error-data message)
`(("data" . ,(jsonrpc-error-data message)))
'()))))))
((jsonrpc-notification? message)
(scm->json-string
`(("jsonrpc" . "2.0")
("method" . ,(jsonrpc-notification-method message))
,@(if (jsonrpc-notification-params message)
`(("params" . ,(jsonrpc-notification-params message)))
'()))))
(else
(throw 'invalid-message "Unknown message type" message))))