1+ #!/usr/bin/env python3
2+ """
3+ Final production wrapper for plumber2mcp
4+ Handles all R jsonlite serialization quirks
5+ """
6+
7+ import sys
8+ import json
9+ import urllib .request
10+ import urllib .error
11+
12+ MCP_ENDPOINT = "http://localhost:8000/mcp/messages"
13+
14+ def fix_r_json (obj ):
15+ """Fix all R jsonlite serialization issues"""
16+ if obj is None :
17+ return None
18+ elif isinstance (obj , list ):
19+ if len (obj ) == 1 :
20+ # Convert single-element arrays to scalars
21+ return fix_r_json (obj [0 ])
22+ else :
23+ # Process array elements
24+ return [fix_r_json (item ) for item in obj ]
25+ elif isinstance (obj , dict ):
26+ if len (obj ) == 0 :
27+ # Empty dict - context dependent
28+ return None # Most cases empty dict should be None
29+
30+ # Process dictionary recursively
31+ fixed = {}
32+ for k , v in obj .items ():
33+ if k == "id" and isinstance (v , dict ) and len (v ) == 0 :
34+ # Empty dict for id should be null
35+ fixed [k ] = None
36+ elif k == "id" and isinstance (v , list ) and len (v ) == 1 :
37+ # Single element array for id should be scalar
38+ fixed [k ] = v [0 ]
39+ elif k == "tools" and isinstance (v , list ) and len (v ) == 0 :
40+ # Empty tools array should be empty object for capabilities
41+ fixed [k ] = {}
42+ elif k == "properties" and isinstance (v , list ) and len (v ) == 0 :
43+ # Empty properties array should be empty object
44+ fixed [k ] = {}
45+ elif k == "required" and isinstance (v , dict ) and len (v ) == 0 :
46+ # Empty required dict should be empty array
47+ fixed [k ] = []
48+ elif k == "content" and isinstance (v , list ):
49+ # content is already an array, just fix nested arrays
50+ fixed [k ] = [fix_r_json (item ) for item in v ]
51+ elif k == "error" and isinstance (v , dict ):
52+ # Process error object specially
53+ error_fixed = {}
54+ for ek , ev in v .items ():
55+ if isinstance (ev , list ) and len (ev ) == 1 :
56+ error_fixed [ek ] = ev [0 ]
57+ else :
58+ error_fixed [ek ] = ev
59+ fixed [k ] = error_fixed
60+ else :
61+ # Recursive fix
62+ fixed [k ] = fix_r_json (v )
63+ return fixed
64+ else :
65+ return obj
66+
67+ def main ():
68+ while True :
69+ try :
70+ line = sys .stdin .readline ()
71+ if not line :
72+ break
73+
74+ request = json .loads (line .strip ())
75+
76+ # Forward to HTTP endpoint
77+ data = json .dumps (request ).encode ('utf-8' )
78+ req = urllib .request .Request (MCP_ENDPOINT ,
79+ data = data ,
80+ headers = {'Content-Type' : 'application/json' })
81+
82+ with urllib .request .urlopen (req ) as response :
83+ result = json .loads (response .read ().decode ('utf-8' ))
84+
85+ # Fix R serialization issues
86+ fixed_result = fix_r_json (result )
87+
88+ # Send response
89+ print (json .dumps (fixed_result ))
90+ sys .stdout .flush ()
91+
92+ except KeyboardInterrupt :
93+ break
94+ except Exception as e :
95+ # Send error response
96+ error_response = {
97+ "jsonrpc" : "2.0" ,
98+ "id" : request .get ("id" ) if 'request' in locals () else None ,
99+ "error" : {
100+ "code" : - 32603 ,
101+ "message" : f"Internal error: { str (e )} "
102+ }
103+ }
104+ print (json .dumps (error_response ))
105+ sys .stdout .flush ()
106+
107+ if __name__ == "__main__" :
108+ main ()
0 commit comments