mirror of
https://github.com/wu736139669/hapi.git
synced 2026-08-05 06:24:37 +00:00
* checkpoint codex multiagent UI state * fix codex multiagent event scoping * fix: stabilize codex subagent timeline * chore: remove codex subagent nesting prompt * test(web): stabilize tool result rendering tests * fix: collapse codex agent trace rows by default * fix(web): keep chat scrolled to bottom * fix(web): backfill agent-run-heavy message loads * fix(codex): fail stuck subagent spawns * fix(codex): preserve wrapped child event scope * fix(codex): surface agent tool completions
125 lines
3.8 KiB
TypeScript
125 lines
3.8 KiB
TypeScript
/**
|
|
* HAPI MCP server
|
|
* Provides HAPI CLI specific tools including chat session title management
|
|
*/
|
|
|
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
import { createServer } from "node:http";
|
|
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
import { AddressInfo } from "node:net";
|
|
import { z } from "zod";
|
|
import { logger } from "@/ui/logger";
|
|
import { ApiSessionClient } from "@/api/apiSession";
|
|
import { randomUUID } from "node:crypto";
|
|
|
|
type StartHappyServerOptions = {
|
|
emitTitleSummary?: boolean;
|
|
};
|
|
|
|
export async function startHappyServer(client: ApiSessionClient, options: StartHappyServerOptions = {}) {
|
|
const emitTitleSummary = options.emitTitleSummary ?? true;
|
|
|
|
// Handler that sends title updates via the client
|
|
const handler = async (title: string) => {
|
|
logger.debug('[hapiMCP] Changing title to:', title);
|
|
try {
|
|
if (emitTitleSummary) {
|
|
// Send title as a summary message, similar to title generator.
|
|
client.sendClaudeSessionMessage({
|
|
type: 'summary',
|
|
summary: title,
|
|
leafUuid: randomUUID()
|
|
});
|
|
}
|
|
|
|
return { success: true };
|
|
} catch (error) {
|
|
return { success: false, error: String(error) };
|
|
}
|
|
};
|
|
|
|
//
|
|
// Create the MCP server
|
|
//
|
|
|
|
const mcp = new McpServer({
|
|
name: "HAPI MCP",
|
|
version: "1.0.0",
|
|
});
|
|
|
|
// Avoid TS instantiation depth issues by widening the schema type.
|
|
const changeTitleInputSchema: z.ZodTypeAny = z.object({
|
|
title: z.string().describe('The new title for the chat session'),
|
|
});
|
|
|
|
mcp.registerTool<any, any>('change_title', {
|
|
description: 'Change the title of the current chat session',
|
|
title: 'Change Chat Title',
|
|
inputSchema: changeTitleInputSchema,
|
|
}, async (args: { title: string }) => {
|
|
const response = await handler(args.title);
|
|
logger.debug('[hapiMCP] Response:', response);
|
|
|
|
if (response.success) {
|
|
return {
|
|
content: [
|
|
{
|
|
type: 'text' as const,
|
|
text: `Successfully changed chat title to: "${args.title}"`,
|
|
},
|
|
],
|
|
isError: false,
|
|
};
|
|
} else {
|
|
return {
|
|
content: [
|
|
{
|
|
type: 'text' as const,
|
|
text: `Failed to change chat title: ${response.error || 'Unknown error'}`,
|
|
},
|
|
],
|
|
isError: true,
|
|
};
|
|
}
|
|
});
|
|
|
|
const transport = new StreamableHTTPServerTransport({
|
|
// NOTE: Returning session id here will result in claude
|
|
// sdk spawn to fail with `Invalid Request: Server already initialized`
|
|
sessionIdGenerator: undefined
|
|
});
|
|
await mcp.connect(transport);
|
|
|
|
//
|
|
// Create the HTTP server
|
|
//
|
|
|
|
const server = createServer(async (req, res) => {
|
|
try {
|
|
await transport.handleRequest(req, res);
|
|
} catch (error) {
|
|
logger.debug("Error handling request:", error);
|
|
if (!res.headersSent) {
|
|
res.writeHead(500).end();
|
|
}
|
|
}
|
|
});
|
|
|
|
const baseUrl = await new Promise<URL>((resolve) => {
|
|
server.listen(0, "127.0.0.1", () => {
|
|
const addr = server.address() as AddressInfo;
|
|
resolve(new URL(`http://127.0.0.1:${addr.port}`));
|
|
});
|
|
});
|
|
|
|
return {
|
|
url: baseUrl.toString(),
|
|
toolNames: ['change_title'],
|
|
stop: () => {
|
|
logger.debug('[hapiMCP] Stopping server');
|
|
mcp.close();
|
|
server.close();
|
|
}
|
|
}
|
|
}
|