Files
2026-06-21 21:27:06 -04:00

164 lines
5.6 KiB
Objective-C

#import "IMCMCPServer.h"
#import "IMCViewController.h"
#import <arpa/inet.h>
#import <netinet/in.h>
#import <sys/socket.h>
#import <unistd.h>
@interface IMCMCPServer ()
@property (nonatomic, weak) IMCViewController *viewController;
@property (nonatomic, assign) NSInteger port;
@property (nonatomic, assign) BOOL running;
@end
@implementation IMCMCPServer
- (instancetype)initWithViewController:(IMCViewController *)viewController port:(NSInteger)port {
self = [super init];
if (self) {
_viewController = viewController;
_port = port;
}
return self;
}
- (void)start {
if (self.running) {
return;
}
self.running = YES;
[NSThread detachNewThreadSelector:@selector(serverLoop) toTarget:self withObject:nil];
}
- (void)serverLoop {
@autoreleasepool {
int serverSocket = socket(AF_INET, SOCK_STREAM, 0);
if (serverSocket < 0) {
return;
}
int yes = 1;
setsockopt(serverSocket, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes));
struct sockaddr_in address;
memset(&address, 0, sizeof(address));
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons((uint16_t)self.port);
if (bind(serverSocket, (struct sockaddr *)&address, sizeof(address)) < 0) {
close(serverSocket);
return;
}
if (listen(serverSocket, 8) < 0) {
close(serverSocket);
return;
}
while (self.running) {
@autoreleasepool {
struct sockaddr_in clientAddress;
socklen_t clientLength = sizeof(clientAddress);
int clientSocket = accept(serverSocket,
(struct sockaddr *)&clientAddress,
&clientLength);
if (clientSocket >= 0) {
[self handleClientSocket:clientSocket];
close(clientSocket);
}
}
}
close(serverSocket);
}
}
- (void)handleClientSocket:(int)clientSocket {
NSMutableData *requestData = [NSMutableData data];
char buffer[2048];
while (requestData.length < 1024 * 1024) {
ssize_t count = recv(clientSocket, buffer, sizeof(buffer), 0);
if (count <= 0) {
break;
}
[requestData appendBytes:buffer length:(NSUInteger)count];
NSString *partial = [[NSString alloc] initWithData:requestData encoding:NSUTF8StringEncoding];
NSRange separator = [partial rangeOfString:@"\r\n\r\n"];
if (separator.location == NSNotFound) {
continue;
}
NSInteger contentLength = [self contentLengthFromHeader:partial];
NSUInteger bodyStart = separator.location + separator.length;
if (requestData.length >= bodyStart + MAX(contentLength, 0)) {
break;
}
}
NSString *request = [[NSString alloc] initWithData:requestData encoding:NSUTF8StringEncoding];
NSDictionary *responseObject = [self responseObjectForHTTPRequest:request];
NSData *responseJSON = [NSJSONSerialization dataWithJSONObject:responseObject options:0 error:nil];
if (!responseJSON) {
responseJSON = [@"{}" dataUsingEncoding:NSUTF8StringEncoding];
}
NSString *status = responseObject[@"httpStatus"] ?: @"200 OK";
NSMutableDictionary *bodyObject = [responseObject mutableCopy];
[bodyObject removeObjectForKey:@"httpStatus"];
responseJSON = [NSJSONSerialization dataWithJSONObject:bodyObject options:0 error:nil];
NSMutableData *responseData = [NSMutableData data];
NSString *header = [NSString stringWithFormat:
@"HTTP/1.1 %@\r\nContent-Type: application/json\r\nContent-Length: %lu\r\nConnection: close\r\n\r\n",
status,
(unsigned long)responseJSON.length];
[responseData appendData:[header dataUsingEncoding:NSUTF8StringEncoding]];
[responseData appendData:responseJSON];
send(clientSocket, responseData.bytes, responseData.length, 0);
}
- (NSDictionary *)responseObjectForHTTPRequest:(NSString *)request {
if (![request hasPrefix:@"POST /api/mcp "]) {
return @{@"httpStatus": @"404 Not Found",
@"jsonrpc": @"2.0",
@"error": @{@"code": @-32601, @"message": @"POST /api/mcp required"},
@"id": [NSNull null]};
}
NSRange separator = [request rangeOfString:@"\r\n\r\n"];
if (separator.location == NSNotFound) {
return [self parseError:@"Missing HTTP body."];
}
NSString *body = [request substringFromIndex:separator.location + separator.length];
NSData *bodyData = [body dataUsingEncoding:NSUTF8StringEncoding];
NSError *jsonError = nil;
NSDictionary *rpcRequest = [NSJSONSerialization JSONObjectWithData:bodyData options:0 error:&jsonError];
if (jsonError || ![rpcRequest isKindOfClass:NSDictionary.class]) {
return [self parseError:@"Invalid JSON-RPC object."];
}
return [self.viewController handleRPCRequest:rpcRequest];
}
- (NSDictionary *)parseError:(NSString *)message {
return @{@"jsonrpc": @"2.0",
@"error": @{@"code": @-32700, @"message": message ?: @"Parse error"},
@"id": [NSNull null]};
}
- (NSInteger)contentLengthFromHeader:(NSString *)request {
NSArray *lines = [request componentsSeparatedByString:@"\r\n"];
for (NSString *line in lines) {
if ([[line lowercaseString] hasPrefix:@"content-length:"]) {
return [[line substringFromIndex:15] integerValue];
}
}
return 0;
}
@end