decibel-tools-mcp 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +335 -0
- package/dist/agentic/compiler.d.ts +21 -0
- package/dist/agentic/compiler.d.ts.map +1 -0
- package/dist/agentic/compiler.js +267 -0
- package/dist/agentic/compiler.js.map +1 -0
- package/dist/agentic/golden.d.ts +25 -0
- package/dist/agentic/golden.d.ts.map +1 -0
- package/dist/agentic/golden.js +255 -0
- package/dist/agentic/golden.js.map +1 -0
- package/dist/agentic/index.d.ts +17 -0
- package/dist/agentic/index.d.ts.map +1 -0
- package/dist/agentic/index.js +153 -0
- package/dist/agentic/index.js.map +1 -0
- package/dist/agentic/linter.d.ts +20 -0
- package/dist/agentic/linter.d.ts.map +1 -0
- package/dist/agentic/linter.js +340 -0
- package/dist/agentic/linter.js.map +1 -0
- package/dist/agentic/renderer.d.ts +17 -0
- package/dist/agentic/renderer.d.ts.map +1 -0
- package/dist/agentic/renderer.js +277 -0
- package/dist/agentic/renderer.js.map +1 -0
- package/dist/agentic/types.d.ts +199 -0
- package/dist/agentic/types.d.ts.map +1 -0
- package/dist/agentic/types.js +8 -0
- package/dist/agentic/types.js.map +1 -0
- package/dist/architectAdrs.d.ts +19 -0
- package/dist/architectAdrs.d.ts.map +1 -0
- package/dist/architectAdrs.js +123 -0
- package/dist/architectAdrs.js.map +1 -0
- package/dist/config.d.ts +8 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +19 -0
- package/dist/config.js.map +1 -0
- package/dist/dataRoot.d.ts +45 -0
- package/dist/dataRoot.d.ts.map +1 -0
- package/dist/dataRoot.js +201 -0
- package/dist/dataRoot.js.map +1 -0
- package/dist/decibelPaths.d.ts +42 -0
- package/dist/decibelPaths.d.ts.map +1 -0
- package/dist/decibelPaths.js +150 -0
- package/dist/decibelPaths.js.map +1 -0
- package/dist/httpServer.d.ts +49 -0
- package/dist/httpServer.d.ts.map +1 -0
- package/dist/httpServer.js +1472 -0
- package/dist/httpServer.js.map +1 -0
- package/dist/lib/benchmark.d.ts +110 -0
- package/dist/lib/benchmark.d.ts.map +1 -0
- package/dist/lib/benchmark.js +338 -0
- package/dist/lib/benchmark.js.map +1 -0
- package/dist/lib/supabase.d.ts +123 -0
- package/dist/lib/supabase.d.ts.map +1 -0
- package/dist/lib/supabase.js +91 -0
- package/dist/lib/supabase.js.map +1 -0
- package/dist/projectPaths.d.ts +27 -0
- package/dist/projectPaths.d.ts.map +1 -0
- package/dist/projectPaths.js +86 -0
- package/dist/projectPaths.js.map +1 -0
- package/dist/projectRegistry.d.ts +97 -0
- package/dist/projectRegistry.d.ts.map +1 -0
- package/dist/projectRegistry.js +362 -0
- package/dist/projectRegistry.js.map +1 -0
- package/dist/sentinelIssues.d.ts +44 -0
- package/dist/sentinelIssues.d.ts.map +1 -0
- package/dist/sentinelIssues.js +213 -0
- package/dist/sentinelIssues.js.map +1 -0
- package/dist/server.d.ts +3 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +93 -0
- package/dist/server.js.map +1 -0
- package/dist/test.d.ts +7 -0
- package/dist/test.d.ts.map +1 -0
- package/dist/test.js +77 -0
- package/dist/test.js.map +1 -0
- package/dist/tools/agentic/index.d.ts +7 -0
- package/dist/tools/agentic/index.d.ts.map +1 -0
- package/dist/tools/agentic/index.js +180 -0
- package/dist/tools/agentic/index.js.map +1 -0
- package/dist/tools/architect/index.d.ts +9 -0
- package/dist/tools/architect/index.d.ts.map +1 -0
- package/dist/tools/architect/index.js +383 -0
- package/dist/tools/architect/index.js.map +1 -0
- package/dist/tools/architect.d.ts +19 -0
- package/dist/tools/architect.d.ts.map +1 -0
- package/dist/tools/architect.js +88 -0
- package/dist/tools/architect.js.map +1 -0
- package/dist/tools/bench.d.ts +89 -0
- package/dist/tools/bench.d.ts.map +1 -0
- package/dist/tools/bench.js +826 -0
- package/dist/tools/bench.js.map +1 -0
- package/dist/tools/context/index.d.ts +11 -0
- package/dist/tools/context/index.d.ts.map +1 -0
- package/dist/tools/context/index.js +439 -0
- package/dist/tools/context/index.js.map +1 -0
- package/dist/tools/context.d.ts +146 -0
- package/dist/tools/context.d.ts.map +1 -0
- package/dist/tools/context.js +481 -0
- package/dist/tools/context.js.map +1 -0
- package/dist/tools/crit.d.ts +63 -0
- package/dist/tools/crit.d.ts.map +1 -0
- package/dist/tools/crit.js +159 -0
- package/dist/tools/crit.js.map +1 -0
- package/dist/tools/data-inspector.d.ts +188 -0
- package/dist/tools/data-inspector.d.ts.map +1 -0
- package/dist/tools/data-inspector.js +650 -0
- package/dist/tools/data-inspector.js.map +1 -0
- package/dist/tools/deck.d.ts +11 -0
- package/dist/tools/deck.d.ts.map +1 -0
- package/dist/tools/deck.js +170 -0
- package/dist/tools/deck.js.map +1 -0
- package/dist/tools/designer/index.d.ts +6 -0
- package/dist/tools/designer/index.d.ts.map +1 -0
- package/dist/tools/designer/index.js +177 -0
- package/dist/tools/designer/index.js.map +1 -0
- package/dist/tools/designer.d.ts +20 -0
- package/dist/tools/designer.d.ts.map +1 -0
- package/dist/tools/designer.js +75 -0
- package/dist/tools/designer.js.map +1 -0
- package/dist/tools/dojo/index.d.ts +13 -0
- package/dist/tools/dojo/index.d.ts.map +1 -0
- package/dist/tools/dojo/index.js +547 -0
- package/dist/tools/dojo/index.js.map +1 -0
- package/dist/tools/dojo.d.ts +254 -0
- package/dist/tools/dojo.d.ts.map +1 -0
- package/dist/tools/dojo.js +933 -0
- package/dist/tools/dojo.js.map +1 -0
- package/dist/tools/dojoBench.d.ts +49 -0
- package/dist/tools/dojoBench.d.ts.map +1 -0
- package/dist/tools/dojoBench.js +205 -0
- package/dist/tools/dojoBench.js.map +1 -0
- package/dist/tools/dojoGraduated.d.ts +50 -0
- package/dist/tools/dojoGraduated.d.ts.map +1 -0
- package/dist/tools/dojoGraduated.js +174 -0
- package/dist/tools/dojoGraduated.js.map +1 -0
- package/dist/tools/dojoPolicy.d.ts +65 -0
- package/dist/tools/dojoPolicy.d.ts.map +1 -0
- package/dist/tools/dojoPolicy.js +263 -0
- package/dist/tools/dojoPolicy.js.map +1 -0
- package/dist/tools/friction/index.d.ts +7 -0
- package/dist/tools/friction/index.d.ts.map +1 -0
- package/dist/tools/friction/index.js +239 -0
- package/dist/tools/friction/index.js.map +1 -0
- package/dist/tools/friction.d.ts +82 -0
- package/dist/tools/friction.d.ts.map +1 -0
- package/dist/tools/friction.js +331 -0
- package/dist/tools/friction.js.map +1 -0
- package/dist/tools/index.d.ts +5 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +52 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/learnings/index.d.ts +5 -0
- package/dist/tools/learnings/index.d.ts.map +1 -0
- package/dist/tools/learnings/index.js +123 -0
- package/dist/tools/learnings/index.js.map +1 -0
- package/dist/tools/learnings.d.ts +41 -0
- package/dist/tools/learnings.d.ts.map +1 -0
- package/dist/tools/learnings.js +149 -0
- package/dist/tools/learnings.js.map +1 -0
- package/dist/tools/oracle/index.d.ts +5 -0
- package/dist/tools/oracle/index.d.ts.map +1 -0
- package/dist/tools/oracle/index.js +97 -0
- package/dist/tools/oracle/index.js.map +1 -0
- package/dist/tools/oracle.d.ts +90 -0
- package/dist/tools/oracle.d.ts.map +1 -0
- package/dist/tools/oracle.js +529 -0
- package/dist/tools/oracle.js.map +1 -0
- package/dist/tools/policy.d.ts +119 -0
- package/dist/tools/policy.d.ts.map +1 -0
- package/dist/tools/policy.js +406 -0
- package/dist/tools/policy.js.map +1 -0
- package/dist/tools/provenance/index.d.ts +4 -0
- package/dist/tools/provenance/index.d.ts.map +1 -0
- package/dist/tools/provenance/index.js +58 -0
- package/dist/tools/provenance/index.js.map +1 -0
- package/dist/tools/provenance.d.ts +75 -0
- package/dist/tools/provenance.d.ts.map +1 -0
- package/dist/tools/provenance.js +197 -0
- package/dist/tools/provenance.js.map +1 -0
- package/dist/tools/rateLimiter.d.ts +45 -0
- package/dist/tools/rateLimiter.d.ts.map +1 -0
- package/dist/tools/rateLimiter.js +91 -0
- package/dist/tools/rateLimiter.js.map +1 -0
- package/dist/tools/registry/index.d.ts +10 -0
- package/dist/tools/registry/index.d.ts.map +1 -0
- package/dist/tools/registry/index.js +467 -0
- package/dist/tools/registry/index.js.map +1 -0
- package/dist/tools/registry.d.ts +3 -0
- package/dist/tools/registry.d.ts.map +1 -0
- package/dist/tools/registry.js +189 -0
- package/dist/tools/registry.js.map +1 -0
- package/dist/tools/roadmap/index.d.ts +9 -0
- package/dist/tools/roadmap/index.d.ts.map +1 -0
- package/dist/tools/roadmap/index.js +250 -0
- package/dist/tools/roadmap/index.js.map +1 -0
- package/dist/tools/roadmap.d.ts +88 -0
- package/dist/tools/roadmap.d.ts.map +1 -0
- package/dist/tools/roadmap.js +365 -0
- package/dist/tools/roadmap.js.map +1 -0
- package/dist/tools/sentinel/index.d.ts +19 -0
- package/dist/tools/sentinel/index.d.ts.map +1 -0
- package/dist/tools/sentinel/index.js +832 -0
- package/dist/tools/sentinel/index.js.map +1 -0
- package/dist/tools/sentinel-scan-data.d.ts +90 -0
- package/dist/tools/sentinel-scan-data.d.ts.map +1 -0
- package/dist/tools/sentinel-scan-data.js +122 -0
- package/dist/tools/sentinel-scan-data.js.map +1 -0
- package/dist/tools/sentinel.d.ts +156 -0
- package/dist/tools/sentinel.d.ts.map +1 -0
- package/dist/tools/sentinel.js +603 -0
- package/dist/tools/sentinel.js.map +1 -0
- package/dist/tools/shared/index.d.ts +4 -0
- package/dist/tools/shared/index.d.ts.map +1 -0
- package/dist/tools/shared/index.js +7 -0
- package/dist/tools/shared/index.js.map +1 -0
- package/dist/tools/shared/project.d.ts +17 -0
- package/dist/tools/shared/project.d.ts.map +1 -0
- package/dist/tools/shared/project.js +36 -0
- package/dist/tools/shared/project.js.map +1 -0
- package/dist/tools/shared/response.d.ts +10 -0
- package/dist/tools/shared/response.d.ts.map +1 -0
- package/dist/tools/shared/response.js +36 -0
- package/dist/tools/shared/response.js.map +1 -0
- package/dist/tools/shared/validation.d.ts +10 -0
- package/dist/tools/shared/validation.d.ts.map +1 -0
- package/dist/tools/shared/validation.js +26 -0
- package/dist/tools/shared/validation.js.map +1 -0
- package/dist/tools/studio/cloud-spine.d.ts +27 -0
- package/dist/tools/studio/cloud-spine.d.ts.map +1 -0
- package/dist/tools/studio/cloud-spine.js +773 -0
- package/dist/tools/studio/cloud-spine.js.map +1 -0
- package/dist/tools/studio/index.d.ts +154 -0
- package/dist/tools/studio/index.d.ts.map +1 -0
- package/dist/tools/studio/index.js +525 -0
- package/dist/tools/studio/index.js.map +1 -0
- package/dist/tools/testSpec.d.ts +122 -0
- package/dist/tools/testSpec.d.ts.map +1 -0
- package/dist/tools/testSpec.js +525 -0
- package/dist/tools/testSpec.js.map +1 -0
- package/dist/tools/toolsIndex.d.ts +5 -0
- package/dist/tools/toolsIndex.d.ts.map +1 -0
- package/dist/tools/toolsIndex.js +37 -0
- package/dist/tools/toolsIndex.js.map +1 -0
- package/dist/tools/types.d.ts +30 -0
- package/dist/tools/types.d.ts.map +1 -0
- package/dist/tools/types.js +7 -0
- package/dist/tools/types.js.map +1 -0
- package/dist/tools/voice/index.d.ts +8 -0
- package/dist/tools/voice/index.d.ts.map +1 -0
- package/dist/tools/voice/index.js +176 -0
- package/dist/tools/voice/index.js.map +1 -0
- package/dist/tools/voice.d.ts +291 -0
- package/dist/tools/voice.d.ts.map +1 -0
- package/dist/tools/voice.js +734 -0
- package/dist/tools/voice.js.map +1 -0
- package/package.json +54 -0
|
@@ -0,0 +1,1472 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP Server Mode for Decibel MCP
|
|
3
|
+
*
|
|
4
|
+
* Exposes the MCP server over HTTP for remote access (e.g., ChatGPT, Mother).
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* node dist/server.js --http --port 8787
|
|
8
|
+
* node dist/server.js --http --port 8787 --auth-token YOUR_SECRET
|
|
9
|
+
*
|
|
10
|
+
* Endpoints:
|
|
11
|
+
* GET /health - Health check
|
|
12
|
+
* GET /tools - List available tools
|
|
13
|
+
* POST /call - Execute any tool: { tool: string, arguments: object }
|
|
14
|
+
* POST /dojo/wish - Shorthand for dojo_add_wish
|
|
15
|
+
* POST /dojo/propose - Shorthand for dojo_create_proposal
|
|
16
|
+
* POST /dojo/scaffold - Shorthand for dojo_scaffold_experiment
|
|
17
|
+
* POST /dojo/run - Shorthand for dojo_run_experiment
|
|
18
|
+
* POST /dojo/results - Shorthand for dojo_get_results
|
|
19
|
+
* POST /dojo/artifact - Shorthand for dojo_read_artifact
|
|
20
|
+
* GET /dojo/list - Shorthand for dojo_list
|
|
21
|
+
* POST /mcp - Full MCP protocol endpoint
|
|
22
|
+
*
|
|
23
|
+
* All responses use status envelope:
|
|
24
|
+
* { "status": "executed", ...data }
|
|
25
|
+
* { "status": "error", "error": "...", "code": "..." }
|
|
26
|
+
*/
|
|
27
|
+
import { createServer } from 'http';
|
|
28
|
+
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
29
|
+
import { readFileSync } from 'fs';
|
|
30
|
+
import { fileURLToPath } from 'url';
|
|
31
|
+
import { dirname, join } from 'path';
|
|
32
|
+
import { log } from './config.js';
|
|
33
|
+
import { isSupabaseConfigured } from './lib/supabase.js';
|
|
34
|
+
import { modularToolMap, modularTools } from './tools/index.js';
|
|
35
|
+
import { createProposal, scaffoldExperiment, listDojo, runExperiment, getExperimentResults, addWish, listWishes, canGraduate, readArtifact, isDojoError, } from './tools/dojo.js';
|
|
36
|
+
import { dojoBench, isDojoBenchError, } from './tools/dojoBench.js';
|
|
37
|
+
import { decibelBench, decibelBenchCompare, isDecibelBenchError, isBenchCompareError, } from './tools/bench.js';
|
|
38
|
+
import { contextRefresh, contextPin, contextUnpin, contextList, eventAppend, eventSearch, artifactList, artifactRead, isContextError, } from './tools/context.js';
|
|
39
|
+
import { createPolicy, listPolicies, getPolicy, compileOversight, isPolicyError, } from './tools/policy.js';
|
|
40
|
+
import { createTestSpec, listTestSpecs, compileTests, auditPolicies, isTestSpecError, } from './tools/testSpec.js';
|
|
41
|
+
import { voiceInboxAdd, } from './tools/voice.js';
|
|
42
|
+
import {
|
|
43
|
+
// Image
|
|
44
|
+
generateImage, getImageStatus,
|
|
45
|
+
// Meshy 3D
|
|
46
|
+
meshyGenerate, getMeshyStatus, meshyDownload,
|
|
47
|
+
// Tripo 3D
|
|
48
|
+
tripoGenerate, getTripoStatus, tripoDownload,
|
|
49
|
+
// Kling Video
|
|
50
|
+
klingGenerateVideo, klingGenerateTextVideo, klingGenerateAvatar, getKlingStatus,
|
|
51
|
+
// Utility
|
|
52
|
+
listTasks, } from './tools/studio/index.js';
|
|
53
|
+
import { listProjects } from './projectRegistry.js';
|
|
54
|
+
import { listEpics, listRepoIssues, isProjectResolutionError, } from './tools/sentinel.js';
|
|
55
|
+
// ============================================================================
|
|
56
|
+
// Version Info
|
|
57
|
+
// ============================================================================
|
|
58
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
59
|
+
const __dirname = dirname(__filename);
|
|
60
|
+
function getVersion() {
|
|
61
|
+
try {
|
|
62
|
+
// Try to read from package.json (works in both dev and prod)
|
|
63
|
+
const pkgPath = join(__dirname, '..', 'package.json');
|
|
64
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
65
|
+
return { version: pkg.version || '0.0.0', name: pkg.name || 'decibel-tools-mcp' };
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
return { version: '0.3.0', name: 'decibel-tools-mcp' };
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
const PKG = getVersion();
|
|
72
|
+
// ============================================================================
|
|
73
|
+
// Landing Page HTML
|
|
74
|
+
// ============================================================================
|
|
75
|
+
const LANDING_PAGE_HTML = `<!DOCTYPE html>
|
|
76
|
+
<html lang="en">
|
|
77
|
+
<head>
|
|
78
|
+
<meta charset="UTF-8">
|
|
79
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
80
|
+
<title>Decibel Tools - Project Intelligence for AI Agents</title>
|
|
81
|
+
<style>
|
|
82
|
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
83
|
+
body {
|
|
84
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
|
|
85
|
+
background: #0a0a0a;
|
|
86
|
+
color: #e5e5e5;
|
|
87
|
+
min-height: 100vh;
|
|
88
|
+
display: flex;
|
|
89
|
+
flex-direction: column;
|
|
90
|
+
align-items: center;
|
|
91
|
+
padding: 2rem;
|
|
92
|
+
}
|
|
93
|
+
.container { max-width: 720px; width: 100%; }
|
|
94
|
+
h1 {
|
|
95
|
+
font-size: 2.5rem;
|
|
96
|
+
font-weight: 700;
|
|
97
|
+
margin-bottom: 0.5rem;
|
|
98
|
+
background: linear-gradient(135deg, #fff 0%, #888 100%);
|
|
99
|
+
-webkit-background-clip: text;
|
|
100
|
+
-webkit-text-fill-color: transparent;
|
|
101
|
+
}
|
|
102
|
+
.tagline { font-size: 1.25rem; color: #888; margin-bottom: 2rem; }
|
|
103
|
+
.cta {
|
|
104
|
+
background: #1a1a1a;
|
|
105
|
+
border: 1px solid #333;
|
|
106
|
+
border-radius: 12px;
|
|
107
|
+
padding: 2rem;
|
|
108
|
+
margin-bottom: 2rem;
|
|
109
|
+
text-align: center;
|
|
110
|
+
}
|
|
111
|
+
.cta h2 { font-size: 1.1rem; font-weight: 500; margin-bottom: 1rem; color: #ccc; }
|
|
112
|
+
.manual { font-size: 0.85rem; color: #666; margin-top: 1rem; }
|
|
113
|
+
.manual code { background: #222; padding: 0.2rem 0.4rem; border-radius: 4px; font-family: 'SF Mono', Monaco, monospace; }
|
|
114
|
+
.features { display: grid; grid-template-columns: repeat(2, 1fr); gap: 1rem; margin-bottom: 2rem; }
|
|
115
|
+
.feature { background: #111; border: 1px solid #222; border-radius: 8px; padding: 1.25rem; }
|
|
116
|
+
.feature h3 { font-size: 0.9rem; font-weight: 600; margin-bottom: 0.5rem; color: #fff; }
|
|
117
|
+
.feature p { font-size: 0.85rem; color: #888; line-height: 1.5; }
|
|
118
|
+
.tools { background: #111; border: 1px solid #222; border-radius: 8px; padding: 1.5rem; margin-bottom: 2rem; }
|
|
119
|
+
.tools h2 { font-size: 1rem; font-weight: 600; margin-bottom: 1rem; }
|
|
120
|
+
.tool-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 0.75rem; }
|
|
121
|
+
.tool { font-size: 0.8rem; color: #888; padding: 0.5rem; background: #1a1a1a; border-radius: 4px; text-align: center; }
|
|
122
|
+
.tool strong { color: #ccc; display: block; margin-bottom: 0.25rem; }
|
|
123
|
+
footer { margin-top: auto; padding-top: 2rem; font-size: 0.8rem; color: #444; }
|
|
124
|
+
footer a { color: #666; text-decoration: none; }
|
|
125
|
+
footer a:hover { color: #888; }
|
|
126
|
+
@media (max-width: 600px) { .features, .tool-grid { grid-template-columns: 1fr; } }
|
|
127
|
+
pre { background: #0d0d0d; padding: 1rem; border-radius: 6px; overflow-x: auto; text-align: left; font-size: 0.85rem; color: #888; }
|
|
128
|
+
</style>
|
|
129
|
+
</head>
|
|
130
|
+
<body>
|
|
131
|
+
<div class="container">
|
|
132
|
+
<h1>Decibel Tools</h1>
|
|
133
|
+
<p class="tagline">Project intelligence for AI coding agents</p>
|
|
134
|
+
<div class="cta">
|
|
135
|
+
<h2>Get Started</h2>
|
|
136
|
+
<p class="manual" style="margin-bottom: 1rem; color: #aaa;">1. Clone the beta repo</p>
|
|
137
|
+
<pre><code>git clone https://github.com/decibelsystems/decibel-tools-beta.git</code></pre>
|
|
138
|
+
<p class="manual" style="margin-top: 1rem; margin-bottom: 1rem; color: #aaa;">2. Add to your MCP config</p>
|
|
139
|
+
<pre><code>{
|
|
140
|
+
"mcpServers": {
|
|
141
|
+
"decibel-tools": {
|
|
142
|
+
"command": "node",
|
|
143
|
+
"args": ["/path/to/decibel-tools-beta/server.js"]
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}</code></pre>
|
|
147
|
+
<p class="manual" style="margin-top: 1rem;">
|
|
148
|
+
Works with <strong style="color: #ccc;">Cursor</strong>, <strong style="color: #ccc;">Claude Desktop</strong>, and any MCP client
|
|
149
|
+
</p>
|
|
150
|
+
</div>
|
|
151
|
+
<div class="features">
|
|
152
|
+
<div class="feature"><h3>Sentinel</h3><p>Track epics, issues, and incidents. Your agent knows what's in flight.</p></div>
|
|
153
|
+
<div class="feature"><h3>Architect</h3><p>Record ADRs and decisions. Context persists across sessions.</p></div>
|
|
154
|
+
<div class="feature"><h3>Dojo</h3><p>Incubate ideas with wishes, proposals, and experiments.</p></div>
|
|
155
|
+
<div class="feature"><h3>Oracle</h3><p>Get AI-powered recommendations on what to work on next.</p></div>
|
|
156
|
+
</div>
|
|
157
|
+
<div class="tools">
|
|
158
|
+
<h2>50+ MCP Tools</h2>
|
|
159
|
+
<div class="tool-grid">
|
|
160
|
+
<div class="tool"><strong>sentinel_log_epic</strong>Create epics</div>
|
|
161
|
+
<div class="tool"><strong>sentinel_createIssue</strong>Track issues</div>
|
|
162
|
+
<div class="tool"><strong>architect_createAdr</strong>Record decisions</div>
|
|
163
|
+
<div class="tool"><strong>dojo_add_wish</strong>Capture ideas</div>
|
|
164
|
+
<div class="tool"><strong>oracle_next_actions</strong>Get guidance</div>
|
|
165
|
+
<div class="tool"><strong>friction_log</strong>Track pain points</div>
|
|
166
|
+
</div>
|
|
167
|
+
</div>
|
|
168
|
+
<footer>
|
|
169
|
+
<a href="https://github.com/decibelsystems/decibel-tools-beta">GitHub</a> ·
|
|
170
|
+
<a href="https://modelcontextprotocol.io">MCP Protocol</a>
|
|
171
|
+
</footer>
|
|
172
|
+
</div>
|
|
173
|
+
</body>
|
|
174
|
+
</html>`;
|
|
175
|
+
// ============================================================================
|
|
176
|
+
// Response Helpers
|
|
177
|
+
// ============================================================================
|
|
178
|
+
/**
|
|
179
|
+
* Wrap a successful result in status envelope
|
|
180
|
+
*/
|
|
181
|
+
function wrapSuccess(data) {
|
|
182
|
+
return { status: 'executed', ...data };
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Wrap an error in status envelope
|
|
186
|
+
*/
|
|
187
|
+
function wrapError(error, code) {
|
|
188
|
+
return { status: 'error', error, ...(code && { code }) };
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Send JSON response with status envelope
|
|
192
|
+
*/
|
|
193
|
+
function sendJson(res, statusCode, data) {
|
|
194
|
+
res.writeHead(statusCode, { 'Content-Type': 'application/json' });
|
|
195
|
+
res.end(JSON.stringify(data));
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Parse JSON body from request
|
|
199
|
+
*/
|
|
200
|
+
async function parseBody(req) {
|
|
201
|
+
return new Promise((resolve, reject) => {
|
|
202
|
+
let body = '';
|
|
203
|
+
req.on('data', (chunk) => { body += chunk; });
|
|
204
|
+
req.on('end', () => {
|
|
205
|
+
try {
|
|
206
|
+
resolve(body ? JSON.parse(body) : {});
|
|
207
|
+
}
|
|
208
|
+
catch {
|
|
209
|
+
reject(new Error('Invalid JSON'));
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
req.on('error', reject);
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
// ============================================================================
|
|
216
|
+
// Tool Executor
|
|
217
|
+
// ============================================================================
|
|
218
|
+
/**
|
|
219
|
+
* Execute a Dojo tool and return normalized result
|
|
220
|
+
*/
|
|
221
|
+
async function executeDojoTool(tool, args) {
|
|
222
|
+
try {
|
|
223
|
+
let result;
|
|
224
|
+
switch (tool) {
|
|
225
|
+
case 'dojo_add_wish':
|
|
226
|
+
result = await addWish(args);
|
|
227
|
+
break;
|
|
228
|
+
case 'dojo_create_proposal':
|
|
229
|
+
result = await createProposal(args);
|
|
230
|
+
break;
|
|
231
|
+
case 'dojo_scaffold_experiment':
|
|
232
|
+
result = await scaffoldExperiment(args);
|
|
233
|
+
break;
|
|
234
|
+
case 'dojo_run_experiment':
|
|
235
|
+
result = await runExperiment(args);
|
|
236
|
+
break;
|
|
237
|
+
case 'dojo_get_results':
|
|
238
|
+
result = await getExperimentResults(args);
|
|
239
|
+
break;
|
|
240
|
+
case 'dojo_list':
|
|
241
|
+
result = await listDojo(args);
|
|
242
|
+
break;
|
|
243
|
+
case 'dojo_list_wishes':
|
|
244
|
+
result = await listWishes(args);
|
|
245
|
+
break;
|
|
246
|
+
case 'dojo_can_graduate':
|
|
247
|
+
result = await canGraduate(args);
|
|
248
|
+
break;
|
|
249
|
+
case 'dojo_read_artifact':
|
|
250
|
+
result = await readArtifact(args);
|
|
251
|
+
break;
|
|
252
|
+
case 'dojo_bench':
|
|
253
|
+
result = await dojoBench(args);
|
|
254
|
+
break;
|
|
255
|
+
// Benchmark tools (ISS-0014)
|
|
256
|
+
case 'decibel_bench':
|
|
257
|
+
result = await decibelBench(args);
|
|
258
|
+
break;
|
|
259
|
+
case 'decibel_bench_compare':
|
|
260
|
+
result = await decibelBenchCompare(args);
|
|
261
|
+
break;
|
|
262
|
+
// Context Pack tools
|
|
263
|
+
case 'decibel_context_refresh':
|
|
264
|
+
result = await contextRefresh(args);
|
|
265
|
+
break;
|
|
266
|
+
case 'decibel_context_pin':
|
|
267
|
+
result = await contextPin(args);
|
|
268
|
+
break;
|
|
269
|
+
case 'decibel_context_unpin':
|
|
270
|
+
result = await contextUnpin(args);
|
|
271
|
+
break;
|
|
272
|
+
case 'decibel_context_list':
|
|
273
|
+
result = await contextList(args);
|
|
274
|
+
break;
|
|
275
|
+
case 'decibel_event_append':
|
|
276
|
+
result = await eventAppend(args);
|
|
277
|
+
break;
|
|
278
|
+
case 'decibel_event_search':
|
|
279
|
+
result = await eventSearch(args);
|
|
280
|
+
break;
|
|
281
|
+
case 'decibel_artifact_list':
|
|
282
|
+
result = await artifactList(args);
|
|
283
|
+
break;
|
|
284
|
+
case 'decibel_artifact_read':
|
|
285
|
+
result = await artifactRead(args);
|
|
286
|
+
break;
|
|
287
|
+
// Architect Policy tools (ADR-004 Oversight Pack)
|
|
288
|
+
case 'architect_createPolicy':
|
|
289
|
+
result = await createPolicy(args);
|
|
290
|
+
break;
|
|
291
|
+
case 'architect_listPolicies':
|
|
292
|
+
result = await listPolicies(args);
|
|
293
|
+
break;
|
|
294
|
+
case 'architect_getPolicy':
|
|
295
|
+
result = await getPolicy(args);
|
|
296
|
+
break;
|
|
297
|
+
case 'architect_compileOversight':
|
|
298
|
+
result = await compileOversight(args);
|
|
299
|
+
break;
|
|
300
|
+
// Sentinel Test tools (ADR-004 Oversight Pack)
|
|
301
|
+
case 'sentinel_createTestSpec':
|
|
302
|
+
result = await createTestSpec(args);
|
|
303
|
+
break;
|
|
304
|
+
case 'sentinel_listTestSpecs':
|
|
305
|
+
result = await listTestSpecs(args);
|
|
306
|
+
break;
|
|
307
|
+
case 'sentinel_compileTests':
|
|
308
|
+
result = await compileTests(args);
|
|
309
|
+
break;
|
|
310
|
+
case 'sentinel_auditPolicies':
|
|
311
|
+
result = await auditPolicies(args);
|
|
312
|
+
break;
|
|
313
|
+
default: {
|
|
314
|
+
// Fallback to modular tools (deck, studio, etc.)
|
|
315
|
+
const modularTool = modularToolMap.get(tool);
|
|
316
|
+
if (modularTool) {
|
|
317
|
+
const modularResult = await modularTool.handler(args);
|
|
318
|
+
// Parse the JSON from the text result
|
|
319
|
+
const text = modularResult.content[0]?.text;
|
|
320
|
+
if (text) {
|
|
321
|
+
try {
|
|
322
|
+
result = JSON.parse(text);
|
|
323
|
+
}
|
|
324
|
+
catch {
|
|
325
|
+
result = { message: text };
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
else {
|
|
329
|
+
result = { success: true };
|
|
330
|
+
}
|
|
331
|
+
break;
|
|
332
|
+
}
|
|
333
|
+
return wrapError(`Unknown tool: ${tool}`, 'UNKNOWN_TOOL');
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
// Check for Dojo error response
|
|
337
|
+
if (isDojoError(result)) {
|
|
338
|
+
return wrapError(result.error, `EXIT_${result.exitCode}`);
|
|
339
|
+
}
|
|
340
|
+
// Check for Bench error response
|
|
341
|
+
if (isDojoBenchError(result)) {
|
|
342
|
+
return wrapError(result.error, `EXIT_${result.exitCode}`);
|
|
343
|
+
}
|
|
344
|
+
// Check for Decibel Bench error response
|
|
345
|
+
if (isDecibelBenchError(result)) {
|
|
346
|
+
return wrapError(result.error, `EXIT_${result.exitCode}`);
|
|
347
|
+
}
|
|
348
|
+
// Check for Bench Compare error response
|
|
349
|
+
if (isBenchCompareError(result)) {
|
|
350
|
+
return wrapError(result.error, `EXIT_${result.exitCode}`);
|
|
351
|
+
}
|
|
352
|
+
// Check for Context error response
|
|
353
|
+
if (isContextError(result)) {
|
|
354
|
+
return wrapError(result.error, 'CONTEXT_ERROR');
|
|
355
|
+
}
|
|
356
|
+
// Check for Policy error response
|
|
357
|
+
if (isPolicyError(result)) {
|
|
358
|
+
return wrapError(result.message, result.error);
|
|
359
|
+
}
|
|
360
|
+
// Check for TestSpec error response
|
|
361
|
+
if (isTestSpecError(result)) {
|
|
362
|
+
return wrapError(result.message, result.error);
|
|
363
|
+
}
|
|
364
|
+
return wrapSuccess(result);
|
|
365
|
+
}
|
|
366
|
+
catch (error) {
|
|
367
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
368
|
+
// Check for specific error types
|
|
369
|
+
if (message.includes('Rate limit')) {
|
|
370
|
+
return wrapError(message, 'RATE_LIMITED');
|
|
371
|
+
}
|
|
372
|
+
if (message.includes('Access denied')) {
|
|
373
|
+
return wrapError(message, 'ACCESS_DENIED');
|
|
374
|
+
}
|
|
375
|
+
if (message.includes('not found')) {
|
|
376
|
+
return wrapError(message, 'NOT_FOUND');
|
|
377
|
+
}
|
|
378
|
+
return wrapError(message, 'EXECUTION_ERROR');
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* Get list of available tools
|
|
383
|
+
*/
|
|
384
|
+
function getAvailableTools() {
|
|
385
|
+
return [
|
|
386
|
+
{ name: 'dojo_add_wish', description: 'Log a capability wish with optional context' },
|
|
387
|
+
{ name: 'dojo_create_proposal', description: 'Create a proposal (can link to wish_id)' },
|
|
388
|
+
{ name: 'dojo_scaffold_experiment', description: 'Create experiment from proposal' },
|
|
389
|
+
{ name: 'dojo_run_experiment', description: 'Run experiment in sandbox mode' },
|
|
390
|
+
{ name: 'dojo_get_results', description: 'Get experiment results' },
|
|
391
|
+
{ name: 'dojo_list', description: 'List proposals, experiments, wishes' },
|
|
392
|
+
{ name: 'dojo_list_wishes', description: 'List wishes' },
|
|
393
|
+
{ name: 'dojo_can_graduate', description: 'Check graduation eligibility' },
|
|
394
|
+
{ name: 'dojo_read_artifact', description: 'Read artifact from experiment results' },
|
|
395
|
+
{ name: 'dojo_bench', description: 'Run benchmark on a Dojo experiment' },
|
|
396
|
+
// Context Pack tools (ADR-002)
|
|
397
|
+
{ name: 'decibel_context_refresh', description: 'Compile full context pack' },
|
|
398
|
+
{ name: 'decibel_context_pin', description: 'Pin a fact to persistent memory' },
|
|
399
|
+
{ name: 'decibel_context_unpin', description: 'Remove a pinned fact' },
|
|
400
|
+
{ name: 'decibel_context_list', description: 'List pinned facts' },
|
|
401
|
+
{ name: 'decibel_event_append', description: 'Append event to journal' },
|
|
402
|
+
{ name: 'decibel_event_search', description: 'Search events' },
|
|
403
|
+
{ name: 'decibel_artifact_list', description: 'List artifacts for a run' },
|
|
404
|
+
{ name: 'decibel_artifact_read', description: 'Read artifact by run_id and name' },
|
|
405
|
+
// Architect Policy tools (ADR-004 Oversight Pack)
|
|
406
|
+
{ name: 'architect_createPolicy', description: 'Create a policy atom' },
|
|
407
|
+
{ name: 'architect_listPolicies', description: 'List policies (filter by severity/tags)' },
|
|
408
|
+
{ name: 'architect_getPolicy', description: 'Get a specific policy by ID' },
|
|
409
|
+
{ name: 'architect_compileOversight', description: 'Compile policies into documentation' },
|
|
410
|
+
// Sentinel Test tools (ADR-004 Oversight Pack)
|
|
411
|
+
{ name: 'sentinel_createTestSpec', description: 'Create a test specification' },
|
|
412
|
+
{ name: 'sentinel_listTestSpecs', description: 'List test specifications' },
|
|
413
|
+
{ name: 'sentinel_compileTests', description: 'Compile test manifest' },
|
|
414
|
+
{ name: 'sentinel_auditPolicies', description: 'Audit policy compliance' },
|
|
415
|
+
// Include modular tools (deck, studio, etc.)
|
|
416
|
+
...modularTools.map(t => ({
|
|
417
|
+
name: t.definition.name,
|
|
418
|
+
description: t.definition.description,
|
|
419
|
+
})),
|
|
420
|
+
];
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* Start an HTTP server that handles MCP requests
|
|
424
|
+
*
|
|
425
|
+
* Note: This creates a single stateless transport. Each request is handled
|
|
426
|
+
* independently. For full session support, this would need to be expanded.
|
|
427
|
+
*/
|
|
428
|
+
export async function startHttpServer(server, options) {
|
|
429
|
+
const { port, authToken, host = '0.0.0.0' } = options;
|
|
430
|
+
// Create transport in STATELESS mode (better for ChatGPT compatibility)
|
|
431
|
+
// Setting sessionIdGenerator to undefined disables session tracking
|
|
432
|
+
const transport = new StreamableHTTPServerTransport({
|
|
433
|
+
sessionIdGenerator: undefined, // Stateless mode
|
|
434
|
+
});
|
|
435
|
+
// Connect the MCP server to the transport
|
|
436
|
+
await server.connect(transport);
|
|
437
|
+
const httpServer = createServer(async (req, res) => {
|
|
438
|
+
const url = new URL(req.url || '/', `http://${req.headers.host}`);
|
|
439
|
+
const path = url.pathname;
|
|
440
|
+
log(`HTTP: ${req.method} ${path}`);
|
|
441
|
+
// CORS headers for browser access (required for ChatGPT connector)
|
|
442
|
+
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
443
|
+
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, DELETE, OPTIONS');
|
|
444
|
+
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, Mcp-Session-Id, Accept');
|
|
445
|
+
res.setHeader('Access-Control-Expose-Headers', 'Mcp-Session-Id');
|
|
446
|
+
// (a) Handle preflight OPTIONS requests
|
|
447
|
+
if (req.method === 'OPTIONS') {
|
|
448
|
+
res.writeHead(204);
|
|
449
|
+
res.end();
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
// (c) Root health check - GET / returns 200
|
|
453
|
+
if (path === '/' && req.method === 'GET') {
|
|
454
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
455
|
+
res.end(JSON.stringify({
|
|
456
|
+
status: 'ok',
|
|
457
|
+
name: PKG.name,
|
|
458
|
+
version: PKG.version,
|
|
459
|
+
api_version: 'v1',
|
|
460
|
+
}));
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
// Health check at /health too
|
|
464
|
+
if (path === '/health') {
|
|
465
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
466
|
+
res.end(JSON.stringify({
|
|
467
|
+
status: 'ok',
|
|
468
|
+
version: PKG.version,
|
|
469
|
+
api_version: 'v1',
|
|
470
|
+
supabase_configured: isSupabaseConfigured(),
|
|
471
|
+
env_check: {
|
|
472
|
+
has_supabase_url: !!process.env.SUPABASE_URL,
|
|
473
|
+
has_supabase_service_key: !!process.env.SUPABASE_SERVICE_KEY,
|
|
474
|
+
},
|
|
475
|
+
}));
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
// Landing page at /tools
|
|
479
|
+
if (path === '/tools' && req.method === 'GET') {
|
|
480
|
+
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
481
|
+
res.end(LANDING_PAGE_HTML);
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
// Serve OpenAPI spec for ChatGPT Actions (handle GET and POST)
|
|
485
|
+
if ((path === '/openapi.yaml' || path === '/openapi.json') && (req.method === 'GET' || req.method === 'POST')) {
|
|
486
|
+
try {
|
|
487
|
+
const specPath = join(__dirname, '..', 'openapi.yaml');
|
|
488
|
+
const spec = readFileSync(specPath, 'utf-8');
|
|
489
|
+
if (path === '/openapi.json') {
|
|
490
|
+
// Convert YAML to JSON if requested
|
|
491
|
+
const yaml = await import('yaml');
|
|
492
|
+
const parsed = yaml.parse(spec);
|
|
493
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
494
|
+
res.end(JSON.stringify(parsed, null, 2));
|
|
495
|
+
}
|
|
496
|
+
else {
|
|
497
|
+
res.writeHead(200, { 'Content-Type': 'text/yaml' });
|
|
498
|
+
res.end(spec);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
catch (error) {
|
|
502
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
503
|
+
res.end(JSON.stringify({ error: 'OpenAPI spec not found' }));
|
|
504
|
+
}
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
507
|
+
// (d) OAuth discovery routes - return 404 (not 400) to keep connector wizard happy
|
|
508
|
+
if (path === '/.well-known/oauth-authorization-server' ||
|
|
509
|
+
path === '/.well-known/openid-configuration' ||
|
|
510
|
+
path === '/oauth/authorize' ||
|
|
511
|
+
path === '/oauth/token' ||
|
|
512
|
+
path === '/oauth/register') {
|
|
513
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
514
|
+
res.end(JSON.stringify({ error: 'Not found' }));
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
517
|
+
// Auth check (if token provided)
|
|
518
|
+
if (authToken) {
|
|
519
|
+
const authHeader = req.headers.authorization;
|
|
520
|
+
if (!authHeader || authHeader !== `Bearer ${authToken}`) {
|
|
521
|
+
log('HTTP: Unauthorized request');
|
|
522
|
+
sendJson(res, 401, wrapError('Unauthorized', 'UNAUTHORIZED'));
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
// ========================================================================
|
|
527
|
+
// Simple REST Endpoints (for Mother and other AI agents)
|
|
528
|
+
// ========================================================================
|
|
529
|
+
// GET /tools - List available tools
|
|
530
|
+
if (path === '/tools' && req.method === 'GET') {
|
|
531
|
+
sendJson(res, 200, wrapSuccess({
|
|
532
|
+
version: PKG.version,
|
|
533
|
+
api_version: 'v1',
|
|
534
|
+
tools: getAvailableTools(),
|
|
535
|
+
}));
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
538
|
+
// POST /call - Execute any tool
|
|
539
|
+
if (path === '/call' && req.method === 'POST') {
|
|
540
|
+
try {
|
|
541
|
+
const body = await parseBody(req);
|
|
542
|
+
const tool = body.tool;
|
|
543
|
+
const args = (body.arguments || {});
|
|
544
|
+
if (!tool) {
|
|
545
|
+
sendJson(res, 400, wrapError('Missing "tool" field', 'MISSING_TOOL'));
|
|
546
|
+
return;
|
|
547
|
+
}
|
|
548
|
+
log(`HTTP: /call tool=${tool}`);
|
|
549
|
+
const result = await executeDojoTool(tool, args);
|
|
550
|
+
sendJson(res, result.status === 'error' ? 400 : 200, result);
|
|
551
|
+
}
|
|
552
|
+
catch (error) {
|
|
553
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
554
|
+
sendJson(res, 400, wrapError(message, 'PARSE_ERROR'));
|
|
555
|
+
}
|
|
556
|
+
return;
|
|
557
|
+
}
|
|
558
|
+
// ========================================================================
|
|
559
|
+
// Dojo Convenience Endpoints
|
|
560
|
+
// ========================================================================
|
|
561
|
+
// POST /dojo/wish - Add a wish
|
|
562
|
+
if (path === '/dojo/wish' && req.method === 'POST') {
|
|
563
|
+
try {
|
|
564
|
+
const body = await parseBody(req);
|
|
565
|
+
log('HTTP: /dojo/wish');
|
|
566
|
+
const result = await executeDojoTool('dojo_add_wish', body);
|
|
567
|
+
sendJson(res, result.status === 'error' ? 400 : 200, result);
|
|
568
|
+
}
|
|
569
|
+
catch (error) {
|
|
570
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
571
|
+
sendJson(res, 400, wrapError(message, 'PARSE_ERROR'));
|
|
572
|
+
}
|
|
573
|
+
return;
|
|
574
|
+
}
|
|
575
|
+
// POST /dojo/propose - Create a proposal
|
|
576
|
+
if (path === '/dojo/propose' && req.method === 'POST') {
|
|
577
|
+
try {
|
|
578
|
+
const body = await parseBody(req);
|
|
579
|
+
log('HTTP: /dojo/propose');
|
|
580
|
+
const result = await executeDojoTool('dojo_create_proposal', body);
|
|
581
|
+
sendJson(res, result.status === 'error' ? 400 : 200, result);
|
|
582
|
+
}
|
|
583
|
+
catch (error) {
|
|
584
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
585
|
+
sendJson(res, 400, wrapError(message, 'PARSE_ERROR'));
|
|
586
|
+
}
|
|
587
|
+
return;
|
|
588
|
+
}
|
|
589
|
+
// POST /dojo/scaffold - Scaffold experiment
|
|
590
|
+
if (path === '/dojo/scaffold' && req.method === 'POST') {
|
|
591
|
+
try {
|
|
592
|
+
const body = await parseBody(req);
|
|
593
|
+
log('HTTP: /dojo/scaffold');
|
|
594
|
+
const result = await executeDojoTool('dojo_scaffold_experiment', body);
|
|
595
|
+
sendJson(res, result.status === 'error' ? 400 : 200, result);
|
|
596
|
+
}
|
|
597
|
+
catch (error) {
|
|
598
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
599
|
+
sendJson(res, 400, wrapError(message, 'PARSE_ERROR'));
|
|
600
|
+
}
|
|
601
|
+
return;
|
|
602
|
+
}
|
|
603
|
+
// POST /dojo/run - Run experiment
|
|
604
|
+
if (path === '/dojo/run' && req.method === 'POST') {
|
|
605
|
+
try {
|
|
606
|
+
const body = await parseBody(req);
|
|
607
|
+
log('HTTP: /dojo/run');
|
|
608
|
+
const result = await executeDojoTool('dojo_run_experiment', body);
|
|
609
|
+
sendJson(res, result.status === 'error' ? 400 : 200, result);
|
|
610
|
+
}
|
|
611
|
+
catch (error) {
|
|
612
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
613
|
+
sendJson(res, 400, wrapError(message, 'PARSE_ERROR'));
|
|
614
|
+
}
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
617
|
+
// POST /dojo/results - Get experiment results
|
|
618
|
+
if (path === '/dojo/results' && req.method === 'POST') {
|
|
619
|
+
try {
|
|
620
|
+
const body = await parseBody(req);
|
|
621
|
+
log('HTTP: /dojo/results');
|
|
622
|
+
const result = await executeDojoTool('dojo_get_results', body);
|
|
623
|
+
sendJson(res, result.status === 'error' ? 400 : 200, result);
|
|
624
|
+
}
|
|
625
|
+
catch (error) {
|
|
626
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
627
|
+
sendJson(res, 400, wrapError(message, 'PARSE_ERROR'));
|
|
628
|
+
}
|
|
629
|
+
return;
|
|
630
|
+
}
|
|
631
|
+
// GET /dojo/list - List all (or POST with filter)
|
|
632
|
+
if (path === '/dojo/list') {
|
|
633
|
+
try {
|
|
634
|
+
const body = req.method === 'POST' ? await parseBody(req) : {};
|
|
635
|
+
// For GET, try to get project_id from query params
|
|
636
|
+
if (req.method === 'GET') {
|
|
637
|
+
const projectId = url.searchParams.get('project_id');
|
|
638
|
+
if (projectId) {
|
|
639
|
+
body.project_id = projectId;
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
log('HTTP: /dojo/list');
|
|
643
|
+
const result = await executeDojoTool('dojo_list', body);
|
|
644
|
+
sendJson(res, result.status === 'error' ? 400 : 200, result);
|
|
645
|
+
}
|
|
646
|
+
catch (error) {
|
|
647
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
648
|
+
sendJson(res, 400, wrapError(message, 'PARSE_ERROR'));
|
|
649
|
+
}
|
|
650
|
+
return;
|
|
651
|
+
}
|
|
652
|
+
// GET /dojo/wishes - List wishes
|
|
653
|
+
if (path === '/dojo/wishes') {
|
|
654
|
+
try {
|
|
655
|
+
const body = req.method === 'POST' ? await parseBody(req) : {};
|
|
656
|
+
if (req.method === 'GET') {
|
|
657
|
+
const projectId = url.searchParams.get('project_id');
|
|
658
|
+
if (projectId) {
|
|
659
|
+
body.project_id = projectId;
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
log('HTTP: /dojo/wishes');
|
|
663
|
+
const result = await executeDojoTool('dojo_list_wishes', body);
|
|
664
|
+
sendJson(res, result.status === 'error' ? 400 : 200, result);
|
|
665
|
+
}
|
|
666
|
+
catch (error) {
|
|
667
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
668
|
+
sendJson(res, 400, wrapError(message, 'PARSE_ERROR'));
|
|
669
|
+
}
|
|
670
|
+
return;
|
|
671
|
+
}
|
|
672
|
+
// POST /dojo/can-graduate - Check graduation eligibility
|
|
673
|
+
if (path === '/dojo/can-graduate' && req.method === 'POST') {
|
|
674
|
+
try {
|
|
675
|
+
const body = await parseBody(req);
|
|
676
|
+
log('HTTP: /dojo/can-graduate');
|
|
677
|
+
const result = await executeDojoTool('dojo_can_graduate', body);
|
|
678
|
+
sendJson(res, result.status === 'error' ? 400 : 200, result);
|
|
679
|
+
}
|
|
680
|
+
catch (error) {
|
|
681
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
682
|
+
sendJson(res, 400, wrapError(message, 'PARSE_ERROR'));
|
|
683
|
+
}
|
|
684
|
+
return;
|
|
685
|
+
}
|
|
686
|
+
// POST /dojo/artifact - Read artifact from experiment results
|
|
687
|
+
if (path === '/dojo/artifact' && req.method === 'POST') {
|
|
688
|
+
try {
|
|
689
|
+
const body = await parseBody(req);
|
|
690
|
+
log('HTTP: /dojo/artifact');
|
|
691
|
+
const result = await executeDojoTool('dojo_read_artifact', body);
|
|
692
|
+
sendJson(res, result.status === 'error' ? 400 : 200, result);
|
|
693
|
+
}
|
|
694
|
+
catch (error) {
|
|
695
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
696
|
+
sendJson(res, 400, wrapError(message, 'PARSE_ERROR'));
|
|
697
|
+
}
|
|
698
|
+
return;
|
|
699
|
+
}
|
|
700
|
+
// POST /dojo/bench - Run benchmark on experiment
|
|
701
|
+
if (path === '/dojo/bench' && req.method === 'POST') {
|
|
702
|
+
try {
|
|
703
|
+
const body = await parseBody(req);
|
|
704
|
+
log('HTTP: /dojo/bench');
|
|
705
|
+
const result = await executeDojoTool('dojo_bench', body);
|
|
706
|
+
sendJson(res, result.status === 'error' ? 400 : 200, result);
|
|
707
|
+
}
|
|
708
|
+
catch (error) {
|
|
709
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
710
|
+
sendJson(res, 400, wrapError(message, 'PARSE_ERROR'));
|
|
711
|
+
}
|
|
712
|
+
return;
|
|
713
|
+
}
|
|
714
|
+
// ========================================================================
|
|
715
|
+
// Benchmark Endpoints (ISS-0014)
|
|
716
|
+
// ========================================================================
|
|
717
|
+
// POST /bench/run - Run a benchmark suite
|
|
718
|
+
if (path === '/bench/run' && req.method === 'POST') {
|
|
719
|
+
try {
|
|
720
|
+
const body = await parseBody(req);
|
|
721
|
+
log('HTTP: /bench/run');
|
|
722
|
+
const result = await executeDojoTool('decibel_bench', body);
|
|
723
|
+
sendJson(res, result.status === 'error' ? 400 : 200, result);
|
|
724
|
+
}
|
|
725
|
+
catch (error) {
|
|
726
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
727
|
+
sendJson(res, 400, wrapError(message, 'PARSE_ERROR'));
|
|
728
|
+
}
|
|
729
|
+
return;
|
|
730
|
+
}
|
|
731
|
+
// POST /bench/compare - Compare two baselines
|
|
732
|
+
if (path === '/bench/compare' && req.method === 'POST') {
|
|
733
|
+
try {
|
|
734
|
+
const body = await parseBody(req);
|
|
735
|
+
log('HTTP: /bench/compare');
|
|
736
|
+
const result = await executeDojoTool('decibel_bench_compare', body);
|
|
737
|
+
sendJson(res, result.status === 'error' ? 400 : 200, result);
|
|
738
|
+
}
|
|
739
|
+
catch (error) {
|
|
740
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
741
|
+
sendJson(res, 400, wrapError(message, 'PARSE_ERROR'));
|
|
742
|
+
}
|
|
743
|
+
return;
|
|
744
|
+
}
|
|
745
|
+
// ========================================================================
|
|
746
|
+
// Context Pack Endpoints (ADR-002)
|
|
747
|
+
// ========================================================================
|
|
748
|
+
// POST /context/refresh - Compile full context pack
|
|
749
|
+
if (path === '/context/refresh' && req.method === 'POST') {
|
|
750
|
+
try {
|
|
751
|
+
const body = await parseBody(req);
|
|
752
|
+
log('HTTP: /context/refresh');
|
|
753
|
+
const result = await executeDojoTool('decibel_context_refresh', body);
|
|
754
|
+
sendJson(res, result.status === 'error' ? 400 : 200, result);
|
|
755
|
+
}
|
|
756
|
+
catch (error) {
|
|
757
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
758
|
+
sendJson(res, 400, wrapError(message, 'PARSE_ERROR'));
|
|
759
|
+
}
|
|
760
|
+
return;
|
|
761
|
+
}
|
|
762
|
+
// POST /context/pin - Pin a fact
|
|
763
|
+
if (path === '/context/pin' && req.method === 'POST') {
|
|
764
|
+
try {
|
|
765
|
+
const body = await parseBody(req);
|
|
766
|
+
log('HTTP: /context/pin');
|
|
767
|
+
const result = await executeDojoTool('decibel_context_pin', body);
|
|
768
|
+
sendJson(res, result.status === 'error' ? 400 : 200, result);
|
|
769
|
+
}
|
|
770
|
+
catch (error) {
|
|
771
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
772
|
+
sendJson(res, 400, wrapError(message, 'PARSE_ERROR'));
|
|
773
|
+
}
|
|
774
|
+
return;
|
|
775
|
+
}
|
|
776
|
+
// POST /context/unpin - Unpin a fact
|
|
777
|
+
if (path === '/context/unpin' && req.method === 'POST') {
|
|
778
|
+
try {
|
|
779
|
+
const body = await parseBody(req);
|
|
780
|
+
log('HTTP: /context/unpin');
|
|
781
|
+
const result = await executeDojoTool('decibel_context_unpin', body);
|
|
782
|
+
sendJson(res, result.status === 'error' ? 400 : 200, result);
|
|
783
|
+
}
|
|
784
|
+
catch (error) {
|
|
785
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
786
|
+
sendJson(res, 400, wrapError(message, 'PARSE_ERROR'));
|
|
787
|
+
}
|
|
788
|
+
return;
|
|
789
|
+
}
|
|
790
|
+
// GET/POST /context/list - List pinned facts
|
|
791
|
+
if (path === '/context/list' && (req.method === 'GET' || req.method === 'POST')) {
|
|
792
|
+
try {
|
|
793
|
+
const body = req.method === 'POST' ? await parseBody(req) : {};
|
|
794
|
+
if (req.method === 'GET') {
|
|
795
|
+
const projectId = url.searchParams.get('project_id');
|
|
796
|
+
if (projectId) {
|
|
797
|
+
body.project_id = projectId;
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
log('HTTP: /context/list');
|
|
801
|
+
const result = await executeDojoTool('decibel_context_list', body);
|
|
802
|
+
sendJson(res, result.status === 'error' ? 400 : 200, result);
|
|
803
|
+
}
|
|
804
|
+
catch (error) {
|
|
805
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
806
|
+
sendJson(res, 400, wrapError(message, 'PARSE_ERROR'));
|
|
807
|
+
}
|
|
808
|
+
return;
|
|
809
|
+
}
|
|
810
|
+
// POST /event/append - Append event to journal
|
|
811
|
+
if (path === '/event/append' && req.method === 'POST') {
|
|
812
|
+
try {
|
|
813
|
+
const body = await parseBody(req);
|
|
814
|
+
log('HTTP: /event/append');
|
|
815
|
+
const result = await executeDojoTool('decibel_event_append', body);
|
|
816
|
+
sendJson(res, result.status === 'error' ? 400 : 200, result);
|
|
817
|
+
}
|
|
818
|
+
catch (error) {
|
|
819
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
820
|
+
sendJson(res, 400, wrapError(message, 'PARSE_ERROR'));
|
|
821
|
+
}
|
|
822
|
+
return;
|
|
823
|
+
}
|
|
824
|
+
// GET/POST /event/search - Search events
|
|
825
|
+
if (path === '/event/search' && (req.method === 'GET' || req.method === 'POST')) {
|
|
826
|
+
try {
|
|
827
|
+
const body = req.method === 'POST' ? await parseBody(req) : {};
|
|
828
|
+
if (req.method === 'GET') {
|
|
829
|
+
const projectId = url.searchParams.get('project_id');
|
|
830
|
+
const query = url.searchParams.get('query');
|
|
831
|
+
const limit = url.searchParams.get('limit');
|
|
832
|
+
if (projectId) {
|
|
833
|
+
body.project_id = projectId;
|
|
834
|
+
}
|
|
835
|
+
if (query) {
|
|
836
|
+
body.query = query;
|
|
837
|
+
}
|
|
838
|
+
if (limit) {
|
|
839
|
+
body.limit = parseInt(limit, 10);
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
log('HTTP: /event/search');
|
|
843
|
+
const result = await executeDojoTool('decibel_event_search', body);
|
|
844
|
+
sendJson(res, result.status === 'error' ? 400 : 200, result);
|
|
845
|
+
}
|
|
846
|
+
catch (error) {
|
|
847
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
848
|
+
sendJson(res, 400, wrapError(message, 'PARSE_ERROR'));
|
|
849
|
+
}
|
|
850
|
+
return;
|
|
851
|
+
}
|
|
852
|
+
// POST /artifact/list - List artifacts for a run
|
|
853
|
+
if (path === '/artifact/list' && req.method === 'POST') {
|
|
854
|
+
try {
|
|
855
|
+
const body = await parseBody(req);
|
|
856
|
+
log('HTTP: /artifact/list');
|
|
857
|
+
const result = await executeDojoTool('decibel_artifact_list', body);
|
|
858
|
+
sendJson(res, result.status === 'error' ? 400 : 200, result);
|
|
859
|
+
}
|
|
860
|
+
catch (error) {
|
|
861
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
862
|
+
sendJson(res, 400, wrapError(message, 'PARSE_ERROR'));
|
|
863
|
+
}
|
|
864
|
+
return;
|
|
865
|
+
}
|
|
866
|
+
// POST /artifact/read - Read artifact by run_id and name
|
|
867
|
+
if (path === '/artifact/read' && req.method === 'POST') {
|
|
868
|
+
try {
|
|
869
|
+
const body = await parseBody(req);
|
|
870
|
+
log('HTTP: /artifact/read');
|
|
871
|
+
const result = await executeDojoTool('decibel_artifact_read', body);
|
|
872
|
+
sendJson(res, result.status === 'error' ? 400 : 200, result);
|
|
873
|
+
}
|
|
874
|
+
catch (error) {
|
|
875
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
876
|
+
sendJson(res, 400, wrapError(message, 'PARSE_ERROR'));
|
|
877
|
+
}
|
|
878
|
+
return;
|
|
879
|
+
}
|
|
880
|
+
// ========================================================================
|
|
881
|
+
// iOS Mobile App Endpoint
|
|
882
|
+
// ========================================================================
|
|
883
|
+
// Helper: Call ML classifier sidecar (optional, graceful fallback)
|
|
884
|
+
async function classifyWithML(transcript) {
|
|
885
|
+
try {
|
|
886
|
+
const controller = new AbortController();
|
|
887
|
+
const timeout = setTimeout(() => controller.abort(), 1000); // 1s timeout
|
|
888
|
+
const resp = await fetch('http://127.0.0.1:8790/classify', {
|
|
889
|
+
method: 'POST',
|
|
890
|
+
headers: { 'Content-Type': 'application/json' },
|
|
891
|
+
body: JSON.stringify({ transcript }),
|
|
892
|
+
signal: controller.signal,
|
|
893
|
+
});
|
|
894
|
+
clearTimeout(timeout);
|
|
895
|
+
if (resp.ok) {
|
|
896
|
+
return await resp.json();
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
catch {
|
|
900
|
+
// Classifier not running or timed out - that's fine
|
|
901
|
+
}
|
|
902
|
+
return null;
|
|
903
|
+
}
|
|
904
|
+
// Helper: Log training sample to ML classifier
|
|
905
|
+
async function logTrainingSample(data) {
|
|
906
|
+
try {
|
|
907
|
+
await fetch('http://127.0.0.1:8790/log', {
|
|
908
|
+
method: 'POST',
|
|
909
|
+
headers: { 'Content-Type': 'application/json' },
|
|
910
|
+
body: JSON.stringify(data),
|
|
911
|
+
});
|
|
912
|
+
}
|
|
913
|
+
catch {
|
|
914
|
+
// Best effort logging
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
// POST /api/inbox - Receive voice transcript from iOS app
|
|
918
|
+
if (path === '/api/inbox' && req.method === 'POST') {
|
|
919
|
+
try {
|
|
920
|
+
const body = await parseBody(req);
|
|
921
|
+
log('HTTP: /api/inbox (iOS)');
|
|
922
|
+
// Validate required field
|
|
923
|
+
const transcript = body.transcript;
|
|
924
|
+
if (!transcript) {
|
|
925
|
+
sendJson(res, 400, wrapError('Missing "transcript" field', 'MISSING_TRANSCRIPT'));
|
|
926
|
+
return;
|
|
927
|
+
}
|
|
928
|
+
// Build tags array
|
|
929
|
+
const tags = [];
|
|
930
|
+
if (body.device)
|
|
931
|
+
tags.push(`device:${body.device}`);
|
|
932
|
+
// User's explicit intent (from button tap)
|
|
933
|
+
// iOS sends as "event_type", also accept "intent" for compatibility
|
|
934
|
+
const userIntent = (body.event_type || body.intent);
|
|
935
|
+
// ML classification (optional - graceful fallback if not running)
|
|
936
|
+
const mlResult = await classifyWithML(transcript);
|
|
937
|
+
let finalIntent = userIntent;
|
|
938
|
+
let wasOverridden = false;
|
|
939
|
+
let mlConfidence = 0;
|
|
940
|
+
if (mlResult) {
|
|
941
|
+
mlConfidence = mlResult.confidence;
|
|
942
|
+
log(`HTTP: ML classified as "${mlResult.intent}" (${(mlResult.confidence * 100).toFixed(0)}%)`);
|
|
943
|
+
if (userIntent) {
|
|
944
|
+
// User provided intent - ML can override if confident and disagrees
|
|
945
|
+
if (mlResult.intent !== userIntent && mlResult.confidence > 0.75) {
|
|
946
|
+
finalIntent = mlResult.intent;
|
|
947
|
+
wasOverridden = true;
|
|
948
|
+
tags.push('ml:overridden');
|
|
949
|
+
log(`HTTP: ML overriding user intent "${userIntent}" → "${mlResult.intent}"`);
|
|
950
|
+
}
|
|
951
|
+
// Log training sample (user label = ground truth)
|
|
952
|
+
logTrainingSample({
|
|
953
|
+
transcript,
|
|
954
|
+
user_label: userIntent,
|
|
955
|
+
predicted: mlResult.intent,
|
|
956
|
+
confidence: mlResult.confidence,
|
|
957
|
+
was_overridden: wasOverridden,
|
|
958
|
+
});
|
|
959
|
+
}
|
|
960
|
+
else {
|
|
961
|
+
// No user intent - use ML prediction
|
|
962
|
+
finalIntent = mlResult.intent;
|
|
963
|
+
tags.push('ml:predicted');
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
// Mark as human-labeled if user provided intent
|
|
967
|
+
if (userIntent) {
|
|
968
|
+
tags.push('labeled:human');
|
|
969
|
+
tags.push(`user_intent:${userIntent}`);
|
|
970
|
+
}
|
|
971
|
+
// Map iOS payload to VoiceInboxAddInput
|
|
972
|
+
const voiceInput = {
|
|
973
|
+
transcript,
|
|
974
|
+
source: 'mobile_app',
|
|
975
|
+
project_id: body.project_id,
|
|
976
|
+
process_immediately: true, // Process on receipt
|
|
977
|
+
tags: tags.length > 0 ? tags : undefined,
|
|
978
|
+
// Pass final intent (may be ML-overridden)
|
|
979
|
+
explicit_intent: finalIntent,
|
|
980
|
+
};
|
|
981
|
+
const result = await voiceInboxAdd(voiceInput);
|
|
982
|
+
sendJson(res, 200, wrapSuccess({
|
|
983
|
+
inbox_id: result.inbox_id,
|
|
984
|
+
transcript: result.transcript,
|
|
985
|
+
intent: result.intent,
|
|
986
|
+
intent_confidence: result.intent_confidence,
|
|
987
|
+
inbox_status: result.status,
|
|
988
|
+
immediate_result: result.immediate_result,
|
|
989
|
+
// ML metadata
|
|
990
|
+
labeled: !!userIntent,
|
|
991
|
+
user_intent: userIntent || null,
|
|
992
|
+
ml_intent: mlResult?.intent || null,
|
|
993
|
+
ml_confidence: mlResult ? Math.round(mlResult.confidence * 100) / 100 : null,
|
|
994
|
+
was_overridden: wasOverridden,
|
|
995
|
+
}));
|
|
996
|
+
}
|
|
997
|
+
catch (error) {
|
|
998
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
999
|
+
log(`HTTP: /api/inbox error: ${message}`);
|
|
1000
|
+
sendJson(res, 400, wrapError(message, 'VOICE_INBOX_ERROR'));
|
|
1001
|
+
}
|
|
1002
|
+
return;
|
|
1003
|
+
}
|
|
1004
|
+
// ========================================================================
|
|
1005
|
+
// iOS App API Endpoints (StatusSnapshot compatible)
|
|
1006
|
+
// ========================================================================
|
|
1007
|
+
// GET /api/projects - List registered projects for iOS project picker
|
|
1008
|
+
if (path === '/api/projects' && req.method === 'GET') {
|
|
1009
|
+
try {
|
|
1010
|
+
log('HTTP: /api/projects');
|
|
1011
|
+
const projects = listProjects();
|
|
1012
|
+
sendJson(res, 200, wrapSuccess({
|
|
1013
|
+
projects: projects.map(p => ({
|
|
1014
|
+
id: p.id,
|
|
1015
|
+
name: p.name || p.id,
|
|
1016
|
+
aliases: p.aliases || [],
|
|
1017
|
+
})),
|
|
1018
|
+
}));
|
|
1019
|
+
}
|
|
1020
|
+
catch (error) {
|
|
1021
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1022
|
+
log(`HTTP: /api/projects error: ${message}`);
|
|
1023
|
+
sendJson(res, 500, wrapError(message, 'PROJECTS_ERROR'));
|
|
1024
|
+
}
|
|
1025
|
+
return;
|
|
1026
|
+
}
|
|
1027
|
+
// GET /api/status - StatusSnapshot for iOS StatusView
|
|
1028
|
+
if (path === '/api/status' && req.method === 'GET') {
|
|
1029
|
+
try {
|
|
1030
|
+
log('HTTP: /api/status');
|
|
1031
|
+
const projects = listProjects();
|
|
1032
|
+
// Check system health by listing each project's data
|
|
1033
|
+
const systemsHealth = {
|
|
1034
|
+
sentinel: { status: 'healthy', message: null },
|
|
1035
|
+
oracle: { status: 'healthy', message: null },
|
|
1036
|
+
dojo: { status: 'healthy', message: null },
|
|
1037
|
+
architect: { status: 'healthy', message: null },
|
|
1038
|
+
};
|
|
1039
|
+
// Build project summaries
|
|
1040
|
+
const projectSummaries = [];
|
|
1041
|
+
for (const project of projects) {
|
|
1042
|
+
try {
|
|
1043
|
+
// Get epic count
|
|
1044
|
+
const epicsResult = await listEpics({ projectId: project.id });
|
|
1045
|
+
const epicCount = isProjectResolutionError(epicsResult)
|
|
1046
|
+
? 0
|
|
1047
|
+
: epicsResult.epics?.length || 0;
|
|
1048
|
+
// Get open issues count
|
|
1049
|
+
const issuesResult = await listRepoIssues({ projectId: project.id, status: 'open' });
|
|
1050
|
+
const openIssueCount = isProjectResolutionError(issuesResult)
|
|
1051
|
+
? 0
|
|
1052
|
+
: issuesResult.issues?.length || 0;
|
|
1053
|
+
projectSummaries.push({
|
|
1054
|
+
project_id: project.id,
|
|
1055
|
+
name: project.name || project.id,
|
|
1056
|
+
active_epics: epicCount,
|
|
1057
|
+
open_issues: openIssueCount,
|
|
1058
|
+
last_activity: null, // Would need to scan files for timestamps
|
|
1059
|
+
});
|
|
1060
|
+
}
|
|
1061
|
+
catch {
|
|
1062
|
+
// If we can't get data for a project, still include it with zeros
|
|
1063
|
+
projectSummaries.push({
|
|
1064
|
+
project_id: project.id,
|
|
1065
|
+
name: project.name || project.id,
|
|
1066
|
+
active_epics: 0,
|
|
1067
|
+
open_issues: 0,
|
|
1068
|
+
last_activity: null,
|
|
1069
|
+
});
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
const snapshot = {
|
|
1073
|
+
snapshot_id: crypto.randomUUID(),
|
|
1074
|
+
generated_at: new Date().toISOString(),
|
|
1075
|
+
source: {
|
|
1076
|
+
generator: 'mcp-server',
|
|
1077
|
+
version: PKG.version,
|
|
1078
|
+
},
|
|
1079
|
+
systems: systemsHealth,
|
|
1080
|
+
projects: projectSummaries,
|
|
1081
|
+
builds: [],
|
|
1082
|
+
alerts: [],
|
|
1083
|
+
};
|
|
1084
|
+
sendJson(res, 200, wrapSuccess(snapshot));
|
|
1085
|
+
}
|
|
1086
|
+
catch (error) {
|
|
1087
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1088
|
+
log(`HTTP: /api/status error: ${message}`);
|
|
1089
|
+
sendJson(res, 500, wrapError(message, 'STATUS_ERROR'));
|
|
1090
|
+
}
|
|
1091
|
+
return;
|
|
1092
|
+
}
|
|
1093
|
+
// ========================================================================
|
|
1094
|
+
// Studio API Endpoints (frontend_v0.2 compatible)
|
|
1095
|
+
// ========================================================================
|
|
1096
|
+
// POST /api/generate-flux-kontext-image - Start image generation
|
|
1097
|
+
if (path === '/api/generate-flux-kontext-image' && req.method === 'POST') {
|
|
1098
|
+
try {
|
|
1099
|
+
const body = await parseBody(req);
|
|
1100
|
+
log('HTTP: /api/generate-flux-kontext-image');
|
|
1101
|
+
// Validate required fields
|
|
1102
|
+
if (!body.prompt) {
|
|
1103
|
+
sendJson(res, 400, wrapError('Missing "prompt" field', 'MISSING_PROMPT'));
|
|
1104
|
+
return;
|
|
1105
|
+
}
|
|
1106
|
+
const input = {
|
|
1107
|
+
asset_id: body.asset_id || `asset_${Date.now()}`,
|
|
1108
|
+
user_id: body.user_id || 'anonymous',
|
|
1109
|
+
prompt: body.prompt,
|
|
1110
|
+
input_image: body.input_image,
|
|
1111
|
+
aspect_ratio: body.aspect_ratio || '16:9',
|
|
1112
|
+
model: body.model || 'flux-kontext-pro',
|
|
1113
|
+
};
|
|
1114
|
+
const result = await generateImage(input);
|
|
1115
|
+
sendJson(res, 200, wrapSuccess(result));
|
|
1116
|
+
}
|
|
1117
|
+
catch (error) {
|
|
1118
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1119
|
+
log(`HTTP: /api/generate-flux-kontext-image error: ${message}`);
|
|
1120
|
+
sendJson(res, 500, wrapError(message, 'GENERATION_ERROR'));
|
|
1121
|
+
}
|
|
1122
|
+
return;
|
|
1123
|
+
}
|
|
1124
|
+
// GET /api/flux-kontext-status/:taskId - Check image generation status
|
|
1125
|
+
if (path.startsWith('/api/flux-kontext-status/') && req.method === 'GET') {
|
|
1126
|
+
try {
|
|
1127
|
+
const taskId = path.replace('/api/flux-kontext-status/', '');
|
|
1128
|
+
log(`HTTP: /api/flux-kontext-status/${taskId}`);
|
|
1129
|
+
if (!taskId) {
|
|
1130
|
+
sendJson(res, 400, wrapError('Missing task ID', 'MISSING_TASK_ID'));
|
|
1131
|
+
return;
|
|
1132
|
+
}
|
|
1133
|
+
const status = getImageStatus(taskId);
|
|
1134
|
+
if (!status) {
|
|
1135
|
+
sendJson(res, 404, wrapError('Task not found', 'TASK_NOT_FOUND'));
|
|
1136
|
+
return;
|
|
1137
|
+
}
|
|
1138
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
1139
|
+
res.end(JSON.stringify(status));
|
|
1140
|
+
}
|
|
1141
|
+
catch (error) {
|
|
1142
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1143
|
+
log(`HTTP: /api/flux-kontext-status error: ${message}`);
|
|
1144
|
+
sendJson(res, 500, wrapError(message, 'STATUS_ERROR'));
|
|
1145
|
+
}
|
|
1146
|
+
return;
|
|
1147
|
+
}
|
|
1148
|
+
// ========================================================================
|
|
1149
|
+
// Meshy 3D Generation Endpoints
|
|
1150
|
+
// ========================================================================
|
|
1151
|
+
// POST /api/meshy/generate - Start 3D generation
|
|
1152
|
+
if (path === '/api/meshy/generate' && req.method === 'POST') {
|
|
1153
|
+
try {
|
|
1154
|
+
const body = await parseBody(req);
|
|
1155
|
+
log('HTTP: /api/meshy/generate');
|
|
1156
|
+
if (!body.mode) {
|
|
1157
|
+
sendJson(res, 400, wrapError('Missing "mode" field', 'MISSING_MODE'));
|
|
1158
|
+
return;
|
|
1159
|
+
}
|
|
1160
|
+
const input = {
|
|
1161
|
+
mode: body.mode,
|
|
1162
|
+
prompt: body.prompt,
|
|
1163
|
+
image_url: body.image_url,
|
|
1164
|
+
image_urls: body.image_urls,
|
|
1165
|
+
preview_task_id: body.preview_task_id,
|
|
1166
|
+
model_input: body.model_input,
|
|
1167
|
+
parameters: body.parameters,
|
|
1168
|
+
asset_id: body.asset_id,
|
|
1169
|
+
user_id: body.user_id,
|
|
1170
|
+
};
|
|
1171
|
+
const result = await meshyGenerate(input);
|
|
1172
|
+
sendJson(res, 200, wrapSuccess(result));
|
|
1173
|
+
}
|
|
1174
|
+
catch (error) {
|
|
1175
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1176
|
+
log(`HTTP: /api/meshy/generate error: ${message}`);
|
|
1177
|
+
sendJson(res, 500, wrapError(message, 'MESHY_ERROR'));
|
|
1178
|
+
}
|
|
1179
|
+
return;
|
|
1180
|
+
}
|
|
1181
|
+
// GET /api/meshy/status/:taskId - Check 3D generation status
|
|
1182
|
+
if (path.startsWith('/api/meshy/status/') && req.method === 'GET') {
|
|
1183
|
+
try {
|
|
1184
|
+
const taskId = path.replace('/api/meshy/status/', '').split('?')[0];
|
|
1185
|
+
log(`HTTP: /api/meshy/status/${taskId}`);
|
|
1186
|
+
const status = getMeshyStatus(taskId);
|
|
1187
|
+
if (!status) {
|
|
1188
|
+
sendJson(res, 404, wrapError('Task not found', 'TASK_NOT_FOUND'));
|
|
1189
|
+
return;
|
|
1190
|
+
}
|
|
1191
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
1192
|
+
res.end(JSON.stringify(status));
|
|
1193
|
+
}
|
|
1194
|
+
catch (error) {
|
|
1195
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1196
|
+
sendJson(res, 500, wrapError(message, 'STATUS_ERROR'));
|
|
1197
|
+
}
|
|
1198
|
+
return;
|
|
1199
|
+
}
|
|
1200
|
+
// POST /api/meshy/download - Download completed model
|
|
1201
|
+
if (path === '/api/meshy/download' && req.method === 'POST') {
|
|
1202
|
+
try {
|
|
1203
|
+
const body = await parseBody(req);
|
|
1204
|
+
log('HTTP: /api/meshy/download');
|
|
1205
|
+
if (!body.task_id) {
|
|
1206
|
+
sendJson(res, 400, wrapError('Missing "task_id" field', 'MISSING_TASK_ID'));
|
|
1207
|
+
return;
|
|
1208
|
+
}
|
|
1209
|
+
const result = await meshyDownload(body.task_id, body.asset_id || `asset_${Date.now()}`, body.user_id || 'anonymous');
|
|
1210
|
+
sendJson(res, 200, wrapSuccess(result));
|
|
1211
|
+
}
|
|
1212
|
+
catch (error) {
|
|
1213
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1214
|
+
sendJson(res, 500, wrapError(message, 'DOWNLOAD_ERROR'));
|
|
1215
|
+
}
|
|
1216
|
+
return;
|
|
1217
|
+
}
|
|
1218
|
+
// ========================================================================
|
|
1219
|
+
// Tripo 3D Generation Endpoints
|
|
1220
|
+
// ========================================================================
|
|
1221
|
+
// POST /api/tripo/generate - Start Tripo 3D generation
|
|
1222
|
+
if (path === '/api/tripo/generate' && req.method === 'POST') {
|
|
1223
|
+
try {
|
|
1224
|
+
const body = await parseBody(req);
|
|
1225
|
+
log('HTTP: /api/tripo/generate');
|
|
1226
|
+
if (!body.type) {
|
|
1227
|
+
sendJson(res, 400, wrapError('Missing "type" field', 'MISSING_TYPE'));
|
|
1228
|
+
return;
|
|
1229
|
+
}
|
|
1230
|
+
const input = {
|
|
1231
|
+
type: body.type,
|
|
1232
|
+
prompt: body.prompt,
|
|
1233
|
+
image_url: body.image_url,
|
|
1234
|
+
image_urls: body.image_urls,
|
|
1235
|
+
parameters: body.parameters,
|
|
1236
|
+
asset_id: body.asset_id,
|
|
1237
|
+
user_id: body.user_id,
|
|
1238
|
+
};
|
|
1239
|
+
const result = await tripoGenerate(input);
|
|
1240
|
+
sendJson(res, 200, wrapSuccess(result));
|
|
1241
|
+
}
|
|
1242
|
+
catch (error) {
|
|
1243
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1244
|
+
log(`HTTP: /api/tripo/generate error: ${message}`);
|
|
1245
|
+
sendJson(res, 500, wrapError(message, 'TRIPO_ERROR'));
|
|
1246
|
+
}
|
|
1247
|
+
return;
|
|
1248
|
+
}
|
|
1249
|
+
// GET /api/tripo/task/:taskId - Check Tripo task status
|
|
1250
|
+
if (path.startsWith('/api/tripo/task/') && req.method === 'GET') {
|
|
1251
|
+
try {
|
|
1252
|
+
const taskId = path.replace('/api/tripo/task/', '');
|
|
1253
|
+
log(`HTTP: /api/tripo/task/${taskId}`);
|
|
1254
|
+
const status = getTripoStatus(taskId);
|
|
1255
|
+
if (!status) {
|
|
1256
|
+
sendJson(res, 404, wrapError('Task not found', 'TASK_NOT_FOUND'));
|
|
1257
|
+
return;
|
|
1258
|
+
}
|
|
1259
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
1260
|
+
res.end(JSON.stringify(status));
|
|
1261
|
+
}
|
|
1262
|
+
catch (error) {
|
|
1263
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1264
|
+
sendJson(res, 500, wrapError(message, 'STATUS_ERROR'));
|
|
1265
|
+
}
|
|
1266
|
+
return;
|
|
1267
|
+
}
|
|
1268
|
+
// POST /api/tripo/download/:taskId - Download Tripo model
|
|
1269
|
+
if (path.startsWith('/api/tripo/download/') && req.method === 'POST') {
|
|
1270
|
+
try {
|
|
1271
|
+
const taskId = path.replace('/api/tripo/download/', '');
|
|
1272
|
+
const body = await parseBody(req);
|
|
1273
|
+
log(`HTTP: /api/tripo/download/${taskId}`);
|
|
1274
|
+
const result = await tripoDownload(taskId, body.asset_id || `asset_${Date.now()}`, body.user_id || 'anonymous');
|
|
1275
|
+
sendJson(res, 200, wrapSuccess(result));
|
|
1276
|
+
}
|
|
1277
|
+
catch (error) {
|
|
1278
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1279
|
+
sendJson(res, 500, wrapError(message, 'DOWNLOAD_ERROR'));
|
|
1280
|
+
}
|
|
1281
|
+
return;
|
|
1282
|
+
}
|
|
1283
|
+
// ========================================================================
|
|
1284
|
+
// Kling Video Generation Endpoints
|
|
1285
|
+
// ========================================================================
|
|
1286
|
+
// POST /api/generate-kling-video - Image to video
|
|
1287
|
+
if (path === '/api/generate-kling-video' && req.method === 'POST') {
|
|
1288
|
+
try {
|
|
1289
|
+
const body = await parseBody(req);
|
|
1290
|
+
log('HTTP: /api/generate-kling-video');
|
|
1291
|
+
if (!body.image_url || !body.prompt) {
|
|
1292
|
+
sendJson(res, 400, wrapError('Missing "image_url" or "prompt" field', 'MISSING_FIELDS'));
|
|
1293
|
+
return;
|
|
1294
|
+
}
|
|
1295
|
+
const input = {
|
|
1296
|
+
asset_id: body.asset_id || `asset_${Date.now()}`,
|
|
1297
|
+
image_url: body.image_url,
|
|
1298
|
+
prompt: body.prompt,
|
|
1299
|
+
negative_prompt: body.negative_prompt,
|
|
1300
|
+
duration: body.duration || 5,
|
|
1301
|
+
aspect_ratio: body.aspect_ratio || '16:9',
|
|
1302
|
+
cfg_scale: body.cfg_scale,
|
|
1303
|
+
seed: body.seed,
|
|
1304
|
+
user_id: body.user_id,
|
|
1305
|
+
model: body.model,
|
|
1306
|
+
sound: body.sound,
|
|
1307
|
+
};
|
|
1308
|
+
const result = await klingGenerateVideo(input);
|
|
1309
|
+
sendJson(res, 200, wrapSuccess(result));
|
|
1310
|
+
}
|
|
1311
|
+
catch (error) {
|
|
1312
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1313
|
+
log(`HTTP: /api/generate-kling-video error: ${message}`);
|
|
1314
|
+
sendJson(res, 500, wrapError(message, 'KLING_ERROR'));
|
|
1315
|
+
}
|
|
1316
|
+
return;
|
|
1317
|
+
}
|
|
1318
|
+
// POST /api/generate-kling-text-video - Text to video
|
|
1319
|
+
if (path === '/api/generate-kling-text-video' && req.method === 'POST') {
|
|
1320
|
+
try {
|
|
1321
|
+
const body = await parseBody(req);
|
|
1322
|
+
log('HTTP: /api/generate-kling-text-video');
|
|
1323
|
+
if (!body.prompt) {
|
|
1324
|
+
sendJson(res, 400, wrapError('Missing "prompt" field', 'MISSING_PROMPT'));
|
|
1325
|
+
return;
|
|
1326
|
+
}
|
|
1327
|
+
const input = {
|
|
1328
|
+
asset_id: body.asset_id || `asset_${Date.now()}`,
|
|
1329
|
+
prompt: body.prompt,
|
|
1330
|
+
negative_prompt: body.negative_prompt,
|
|
1331
|
+
duration: body.duration || 5,
|
|
1332
|
+
aspect_ratio: body.aspect_ratio || '16:9',
|
|
1333
|
+
cfg_scale: body.cfg_scale,
|
|
1334
|
+
user_id: body.user_id,
|
|
1335
|
+
model: body.model,
|
|
1336
|
+
sound: body.sound,
|
|
1337
|
+
};
|
|
1338
|
+
const result = await klingGenerateTextVideo(input);
|
|
1339
|
+
sendJson(res, 200, wrapSuccess(result));
|
|
1340
|
+
}
|
|
1341
|
+
catch (error) {
|
|
1342
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1343
|
+
log(`HTTP: /api/generate-kling-text-video error: ${message}`);
|
|
1344
|
+
sendJson(res, 500, wrapError(message, 'KLING_ERROR'));
|
|
1345
|
+
}
|
|
1346
|
+
return;
|
|
1347
|
+
}
|
|
1348
|
+
// POST /api/generate-kling-avatar - Avatar/lip-sync video
|
|
1349
|
+
if (path === '/api/generate-kling-avatar' && req.method === 'POST') {
|
|
1350
|
+
try {
|
|
1351
|
+
const body = await parseBody(req);
|
|
1352
|
+
log('HTTP: /api/generate-kling-avatar');
|
|
1353
|
+
if (!body.image_url || !body.audio_url) {
|
|
1354
|
+
sendJson(res, 400, wrapError('Missing "image_url" or "audio_url" field', 'MISSING_FIELDS'));
|
|
1355
|
+
return;
|
|
1356
|
+
}
|
|
1357
|
+
const input = {
|
|
1358
|
+
asset_id: body.asset_id || `asset_${Date.now()}`,
|
|
1359
|
+
image_url: body.image_url,
|
|
1360
|
+
audio_url: body.audio_url,
|
|
1361
|
+
prompt: body.prompt,
|
|
1362
|
+
user_id: body.user_id,
|
|
1363
|
+
model: body.model,
|
|
1364
|
+
};
|
|
1365
|
+
const result = await klingGenerateAvatar(input);
|
|
1366
|
+
sendJson(res, 200, wrapSuccess(result));
|
|
1367
|
+
}
|
|
1368
|
+
catch (error) {
|
|
1369
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1370
|
+
log(`HTTP: /api/generate-kling-avatar error: ${message}`);
|
|
1371
|
+
sendJson(res, 500, wrapError(message, 'KLING_ERROR'));
|
|
1372
|
+
}
|
|
1373
|
+
return;
|
|
1374
|
+
}
|
|
1375
|
+
// GET /api/kling-video-status/:taskId - Check video generation status
|
|
1376
|
+
if (path.startsWith('/api/kling-video-status/') && req.method === 'GET') {
|
|
1377
|
+
try {
|
|
1378
|
+
const taskId = path.replace('/api/kling-video-status/', '');
|
|
1379
|
+
log(`HTTP: /api/kling-video-status/${taskId}`);
|
|
1380
|
+
const status = getKlingStatus(taskId);
|
|
1381
|
+
if (!status) {
|
|
1382
|
+
sendJson(res, 404, wrapError('Task not found', 'TASK_NOT_FOUND'));
|
|
1383
|
+
return;
|
|
1384
|
+
}
|
|
1385
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
1386
|
+
res.end(JSON.stringify(status));
|
|
1387
|
+
}
|
|
1388
|
+
catch (error) {
|
|
1389
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1390
|
+
sendJson(res, 500, wrapError(message, 'STATUS_ERROR'));
|
|
1391
|
+
}
|
|
1392
|
+
return;
|
|
1393
|
+
}
|
|
1394
|
+
// GET /api/studio/tasks - List all tasks (debug endpoint)
|
|
1395
|
+
if (path === '/api/studio/tasks' && req.method === 'GET') {
|
|
1396
|
+
try {
|
|
1397
|
+
log('HTTP: /api/studio/tasks');
|
|
1398
|
+
const tasks = listTasks();
|
|
1399
|
+
sendJson(res, 200, wrapSuccess({ tasks, count: tasks.length }));
|
|
1400
|
+
}
|
|
1401
|
+
catch (error) {
|
|
1402
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1403
|
+
sendJson(res, 500, wrapError(message, 'LIST_ERROR'));
|
|
1404
|
+
}
|
|
1405
|
+
return;
|
|
1406
|
+
}
|
|
1407
|
+
// ========================================================================
|
|
1408
|
+
// Full MCP Protocol Endpoint
|
|
1409
|
+
// ========================================================================
|
|
1410
|
+
// (b) MCP endpoint - supports GET, POST, DELETE via StreamableHTTPServerTransport
|
|
1411
|
+
// Handle at /mcp, /sse, /sse/ (ChatGPT uses trailing slash), and root / for compatibility
|
|
1412
|
+
if (path === '/mcp' || path === '/sse' || path === '/sse/' || (path === '/' && (req.method === 'POST' || req.method === 'DELETE'))) {
|
|
1413
|
+
try {
|
|
1414
|
+
await transport.handleRequest(req, res);
|
|
1415
|
+
}
|
|
1416
|
+
catch (error) {
|
|
1417
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1418
|
+
log(`HTTP: Error handling MCP request: ${message}`);
|
|
1419
|
+
if (!res.writableEnded) {
|
|
1420
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
1421
|
+
res.end(JSON.stringify({ error: message }));
|
|
1422
|
+
}
|
|
1423
|
+
}
|
|
1424
|
+
return;
|
|
1425
|
+
}
|
|
1426
|
+
// 404 for all other paths
|
|
1427
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
1428
|
+
res.end(JSON.stringify({ error: 'Not found' }));
|
|
1429
|
+
});
|
|
1430
|
+
httpServer.listen(port, host, () => {
|
|
1431
|
+
log(`HTTP Server listening on http://${host}:${port}`);
|
|
1432
|
+
console.log(`
|
|
1433
|
+
╔══════════════════════════════════════════════════════════════╗
|
|
1434
|
+
║ Decibel MCP Server - HTTP Mode v${PKG.version}${' '.repeat(Math.max(0, 24 - PKG.version.length))}║
|
|
1435
|
+
╠══════════════════════════════════════════════════════════════╣
|
|
1436
|
+
║ Endpoints: ║
|
|
1437
|
+
║ GET /health Health check ║
|
|
1438
|
+
║ GET /tools List tools ║
|
|
1439
|
+
║ POST /call Execute tool (generic) ║
|
|
1440
|
+
║ POST /dojo/wish Add wish ║
|
|
1441
|
+
║ POST /dojo/propose Create proposal ║
|
|
1442
|
+
║ POST /dojo/scaffold Scaffold experiment ║
|
|
1443
|
+
║ POST /dojo/run Run experiment ║
|
|
1444
|
+
║ POST /dojo/results Get results ║
|
|
1445
|
+
║ POST /dojo/artifact Read artifact file ║
|
|
1446
|
+
║ GET /dojo/list List all ║
|
|
1447
|
+
║ POST /mcp Full MCP protocol ║
|
|
1448
|
+
╠══════════════════════════════════════════════════════════════╣
|
|
1449
|
+
║ Base URL: http://${host}:${port}${' '.repeat(Math.max(0, 40 - port.toString().length - host.length))}║
|
|
1450
|
+
${authToken ? '║ Auth: Bearer token required ║' : '║ Auth: None (use --auth-token for security) ║'}
|
|
1451
|
+
╠══════════════════════════════════════════════════════════════╣
|
|
1452
|
+
║ Response format: {"status": "executed"|"error", ...} ║
|
|
1453
|
+
╚══════════════════════════════════════════════════════════════╝
|
|
1454
|
+
`);
|
|
1455
|
+
});
|
|
1456
|
+
}
|
|
1457
|
+
/**
|
|
1458
|
+
* Parse command line arguments for HTTP mode
|
|
1459
|
+
*/
|
|
1460
|
+
export function parseHttpArgs(args) {
|
|
1461
|
+
const httpMode = args.includes('--http');
|
|
1462
|
+
const portIndex = args.indexOf('--port');
|
|
1463
|
+
// Render sets PORT env var - use it if available
|
|
1464
|
+
const defaultPort = process.env.PORT ? parseInt(process.env.PORT, 10) : 8787;
|
|
1465
|
+
const port = portIndex !== -1 ? parseInt(args[portIndex + 1], 10) : defaultPort;
|
|
1466
|
+
const authIndex = args.indexOf('--auth-token');
|
|
1467
|
+
const authToken = authIndex !== -1 ? args[authIndex + 1] : undefined;
|
|
1468
|
+
const hostIndex = args.indexOf('--host');
|
|
1469
|
+
const host = hostIndex !== -1 ? args[hostIndex + 1] : '0.0.0.0';
|
|
1470
|
+
return { httpMode, port, authToken, host };
|
|
1471
|
+
}
|
|
1472
|
+
//# sourceMappingURL=httpServer.js.map
|