feat(chat): MCP server + WebSocket streaming, replace XML actions
- Backend: kanban binary gana subcomando `kanban mcp` que actua como MCP
server via stdio. Tools = mismo set que executeTool (14). El subprocess
llama de vuelta al backend via /api/tool/{name} con token interno.
- Backend: nuevo endpoint POST /api/tool/{name} (auth: X-Internal-Token).
- Backend: chat.go refactor — POST /api/chat reemplazado por GET
/api/chat/ws (WebSocket). Lanza claude -p con --output-format stream-json
--verbose --mcp-config y reenvia eventos (delta/tool_use/tool_result/
result/done/error) como mensajes JSON al cliente.
- Backend: usa funciones nuevas del registry claude_stream_go_core (spawn
+ parser NDJSON) y mcp_server_stdio_go_infra (JSON-RPC stdio).
- Frontend: streamChat sobre WebSocket. ChatPanel renderiza deltas en
vivo, chips para tool_use, badges teal/red para tool_result.
- Borrado: extractActions, actionsBlockMarker, XML system prompt.
- Tests: 7 nuevos en backend (chat_ws_test.go + endpoint /api/tool).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+16
-2
@@ -22,6 +22,15 @@ import (
|
||||
var frontendDist embed.FS
|
||||
|
||||
func main() {
|
||||
// Subcommand `kanban mcp` runs as MCP server over stdio (spawned by claude -p).
|
||||
if len(os.Args) > 1 && os.Args[1] == "mcp" {
|
||||
if err := runMCPServer(os.Args[2:]); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "kanban mcp: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
flags := flag.NewFlagSet("kanban", flag.ExitOnError)
|
||||
port := flags.Int("port", 8095, "HTTP port")
|
||||
dbPath := flags.String("db", "operations.db", "SQLite database path")
|
||||
@@ -37,10 +46,15 @@ func main() {
|
||||
bootstrapAdmin(db, *initialAdmin)
|
||||
startSessionCleanup(db)
|
||||
|
||||
internalToken := os.Getenv("KANBAN_INTERNAL_TOKEN")
|
||||
if internalToken == "" {
|
||||
internalToken = generateInternalToken()
|
||||
}
|
||||
|
||||
wd := chatWorkdir(*dbPath)
|
||||
logger := newChatLogger(filepath.Join(wd, "chat.log"))
|
||||
log.Printf("chat tool log: %s", logger.path)
|
||||
mux := infra.HTTPRouter(apiRoutes(db, wd, logger))
|
||||
mux := infra.HTTPRouter(apiRoutes(db, wd, logger, internalToken))
|
||||
|
||||
feHandler := frontendHandler()
|
||||
if feHandler != nil {
|
||||
@@ -53,7 +67,7 @@ func main() {
|
||||
authMW := infra.HTTPSessionCookieMiddleware(infra.SessionCookieConfig{
|
||||
DB: db.conn,
|
||||
CookieName: cookieName,
|
||||
SkipPaths: []string{"/api/auth/", "/health", "/assets/", "/index.html"},
|
||||
SkipPaths: []string{"/api/auth/", "/api/tool/", "/health", "/assets/", "/index.html"},
|
||||
UserCtxKey: userCtxKey,
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user