@medicine-wheel/app 0.2.7 → 0.3.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/app/api/charts/route.ts +74 -0
- package/app/api/mcp/route.ts +138 -0
- package/app/api/mmots/route.ts +64 -0
- package/package.json +29 -25
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
import * as fs from "fs";
|
|
3
|
+
import * as path from "path";
|
|
4
|
+
|
|
5
|
+
function getChartsFile(): string {
|
|
6
|
+
const dataDir = process.env.MW_DATA_DIR ?? path.join(process.cwd(), ".mw", "store");
|
|
7
|
+
return path.join(dataDir, "charts.jsonl");
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function readJsonl<T>(filePath: string): T[] {
|
|
11
|
+
if (!fs.existsSync(filePath)) return [];
|
|
12
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
13
|
+
return content
|
|
14
|
+
.split("\n")
|
|
15
|
+
.filter((line) => line.trim())
|
|
16
|
+
.map((line) => JSON.parse(line) as T);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function appendJsonl<T>(filePath: string, item: T): void {
|
|
20
|
+
const dir = path.dirname(filePath);
|
|
21
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
22
|
+
fs.appendFileSync(filePath, JSON.stringify(item) + "\n", "utf-8");
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export async function GET(request: Request) {
|
|
26
|
+
try {
|
|
27
|
+
const { searchParams } = new URL(request.url);
|
|
28
|
+
const direction = searchParams.get("direction");
|
|
29
|
+
|
|
30
|
+
let charts = readJsonl<any>(getChartsFile());
|
|
31
|
+
|
|
32
|
+
if (direction) {
|
|
33
|
+
charts = charts.filter((c) => c.direction === direction);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
charts.sort(
|
|
37
|
+
(a: any, b: any) =>
|
|
38
|
+
Date.parse(b.updated_at ?? b.created_at) -
|
|
39
|
+
Date.parse(a.updated_at ?? a.created_at)
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
return NextResponse.json(charts);
|
|
43
|
+
} catch (error: unknown) {
|
|
44
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
45
|
+
return NextResponse.json({ error: message }, { status: 500 });
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export async function POST(request: Request) {
|
|
50
|
+
try {
|
|
51
|
+
const body = await request.json();
|
|
52
|
+
|
|
53
|
+
const chart = {
|
|
54
|
+
id: body.id || crypto.randomUUID(),
|
|
55
|
+
desired_outcome: body.desired_outcome,
|
|
56
|
+
current_reality: body.current_reality,
|
|
57
|
+
direction: body.direction || "east",
|
|
58
|
+
action_steps: body.action_steps ?? [],
|
|
59
|
+
due_date: body.due_date,
|
|
60
|
+
created_at: body.created_at || new Date().toISOString(),
|
|
61
|
+
updated_at: new Date().toISOString(),
|
|
62
|
+
phase: body.phase || "current_reality",
|
|
63
|
+
ceremonies_linked: body.ceremonies_linked ?? [],
|
|
64
|
+
wilson_alignment: body.wilson_alignment,
|
|
65
|
+
cycle_id: body.cycle_id,
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
appendJsonl(getChartsFile(), chart);
|
|
69
|
+
return NextResponse.json({ success: true, chart }, { status: 201 });
|
|
70
|
+
} catch (error: unknown) {
|
|
71
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
72
|
+
return NextResponse.json({ error: message }, { status: 500 });
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* StreamableHTTP MCP endpoint
|
|
3
|
+
*
|
|
4
|
+
* Enables MCP clients to connect to the Medicine Wheel server
|
|
5
|
+
* via HTTP POST instead of stdio. Accepts JSON-RPC MCP messages
|
|
6
|
+
* and dispatches them to the same tool/resource/prompt registry
|
|
7
|
+
* that the stdio transport uses.
|
|
8
|
+
*
|
|
9
|
+
* Supported methods:
|
|
10
|
+
* - tools/list → lists all registered MCP tools
|
|
11
|
+
* - tools/call → invokes a tool by name with arguments
|
|
12
|
+
*
|
|
13
|
+
* @see https://github.com/jgwill/medicine-wheel/issues/69
|
|
14
|
+
*/
|
|
15
|
+
import { NextRequest, NextResponse } from "next/server";
|
|
16
|
+
import { allTools } from "@medicine-wheel/mcp/all-tools";
|
|
17
|
+
|
|
18
|
+
export async function POST(request: NextRequest) {
|
|
19
|
+
try {
|
|
20
|
+
const body = await request.json();
|
|
21
|
+
|
|
22
|
+
if (!body.jsonrpc || body.jsonrpc !== "2.0") {
|
|
23
|
+
return NextResponse.json(
|
|
24
|
+
{
|
|
25
|
+
jsonrpc: "2.0",
|
|
26
|
+
error: {
|
|
27
|
+
code: -32600,
|
|
28
|
+
message: "Invalid Request: expected JSON-RPC 2.0",
|
|
29
|
+
},
|
|
30
|
+
id: body.id ?? null,
|
|
31
|
+
},
|
|
32
|
+
{ status: 400 }
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const { method, params, id } = body;
|
|
37
|
+
|
|
38
|
+
// ── tools/list ──
|
|
39
|
+
if (method === "tools/list") {
|
|
40
|
+
return NextResponse.json({
|
|
41
|
+
jsonrpc: "2.0",
|
|
42
|
+
result: {
|
|
43
|
+
tools: allTools.map((t) => ({
|
|
44
|
+
name: t.name,
|
|
45
|
+
description: t.description,
|
|
46
|
+
inputSchema: t.inputSchema,
|
|
47
|
+
})),
|
|
48
|
+
},
|
|
49
|
+
id: id ?? null,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// ── tools/call ──
|
|
54
|
+
if (method === "tools/call") {
|
|
55
|
+
const toolName = params?.name;
|
|
56
|
+
const tool = allTools.find((t) => t.name === toolName);
|
|
57
|
+
if (!tool) {
|
|
58
|
+
return NextResponse.json({
|
|
59
|
+
jsonrpc: "2.0",
|
|
60
|
+
error: {
|
|
61
|
+
code: -32602,
|
|
62
|
+
message: `Unknown tool: ${toolName}`,
|
|
63
|
+
},
|
|
64
|
+
id: id ?? null,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
const result = await tool.handler(params?.arguments || {});
|
|
70
|
+
return NextResponse.json({
|
|
71
|
+
jsonrpc: "2.0",
|
|
72
|
+
result: {
|
|
73
|
+
content: [
|
|
74
|
+
{ type: "text", text: JSON.stringify(result, null, 2) },
|
|
75
|
+
],
|
|
76
|
+
},
|
|
77
|
+
id: id ?? null,
|
|
78
|
+
});
|
|
79
|
+
} catch (error) {
|
|
80
|
+
const errorMessage =
|
|
81
|
+
error instanceof Error ? error.message : String(error);
|
|
82
|
+
return NextResponse.json({
|
|
83
|
+
jsonrpc: "2.0",
|
|
84
|
+
result: {
|
|
85
|
+
content: [
|
|
86
|
+
{
|
|
87
|
+
type: "text",
|
|
88
|
+
text: JSON.stringify(
|
|
89
|
+
{
|
|
90
|
+
error: errorMessage,
|
|
91
|
+
direction:
|
|
92
|
+
"Please ensure all required parameters are provided",
|
|
93
|
+
},
|
|
94
|
+
null,
|
|
95
|
+
2
|
|
96
|
+
),
|
|
97
|
+
},
|
|
98
|
+
],
|
|
99
|
+
isError: true,
|
|
100
|
+
},
|
|
101
|
+
id: id ?? null,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// ── Unsupported method ──
|
|
107
|
+
return NextResponse.json(
|
|
108
|
+
{
|
|
109
|
+
jsonrpc: "2.0",
|
|
110
|
+
error: {
|
|
111
|
+
code: -32601,
|
|
112
|
+
message: `Method '${method}' is not supported. Available: tools/list, tools/call`,
|
|
113
|
+
},
|
|
114
|
+
id: id ?? null,
|
|
115
|
+
},
|
|
116
|
+
{ status: 200 }
|
|
117
|
+
);
|
|
118
|
+
} catch {
|
|
119
|
+
return NextResponse.json(
|
|
120
|
+
{
|
|
121
|
+
jsonrpc: "2.0",
|
|
122
|
+
error: { code: -32700, message: "Parse error" },
|
|
123
|
+
id: null,
|
|
124
|
+
},
|
|
125
|
+
{ status: 400 }
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export async function GET() {
|
|
131
|
+
return NextResponse.json({
|
|
132
|
+
status: "available",
|
|
133
|
+
transport: "StreamableHTTP",
|
|
134
|
+
description:
|
|
135
|
+
"Medicine Wheel MCP StreamableHTTP endpoint. Send JSON-RPC 2.0 POST requests to interact.",
|
|
136
|
+
capabilities: ["tools", "resources", "prompts"],
|
|
137
|
+
});
|
|
138
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
import * as fs from "fs";
|
|
3
|
+
import * as path from "path";
|
|
4
|
+
|
|
5
|
+
function getMmotsFile(): string {
|
|
6
|
+
const dataDir = process.env.MW_DATA_DIR ?? path.join(process.cwd(), ".mw", "store");
|
|
7
|
+
return path.join(dataDir, "mmots.jsonl");
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function readJsonl<T>(filePath: string): T[] {
|
|
11
|
+
if (!fs.existsSync(filePath)) return [];
|
|
12
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
13
|
+
return content
|
|
14
|
+
.split("\n")
|
|
15
|
+
.filter((line) => line.trim())
|
|
16
|
+
.map((line) => JSON.parse(line) as T);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function appendJsonl<T>(filePath: string, item: T): void {
|
|
20
|
+
const dir = path.dirname(filePath);
|
|
21
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
22
|
+
fs.appendFileSync(filePath, JSON.stringify(item) + "\n", "utf-8");
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export async function GET(request: Request) {
|
|
26
|
+
try {
|
|
27
|
+
const { searchParams } = new URL(request.url);
|
|
28
|
+
const chartId = searchParams.get("chart_id");
|
|
29
|
+
|
|
30
|
+
let mmots = readJsonl<any>(getMmotsFile());
|
|
31
|
+
|
|
32
|
+
if (chartId) {
|
|
33
|
+
mmots = mmots.filter((m) => m.chart_id === chartId);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return NextResponse.json(mmots);
|
|
37
|
+
} catch (error: unknown) {
|
|
38
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
39
|
+
return NextResponse.json({ error: message }, { status: 500 });
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export async function POST(request: Request) {
|
|
44
|
+
try {
|
|
45
|
+
const body = await request.json();
|
|
46
|
+
|
|
47
|
+
const mmot = {
|
|
48
|
+
id: body.id || crypto.randomUUID(),
|
|
49
|
+
chart_id: body.chart_id,
|
|
50
|
+
timestamp: new Date().toISOString(),
|
|
51
|
+
step1_expected: body.step1_expected,
|
|
52
|
+
step1_actual: body.step1_actual,
|
|
53
|
+
step2_analysis: body.step2_analysis,
|
|
54
|
+
step3_adjustments: body.step3_adjustments ?? [],
|
|
55
|
+
step4_feedback: body.step4_feedback,
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
appendJsonl(getMmotsFile(), mmot);
|
|
59
|
+
return NextResponse.json({ success: true, mmot }, { status: 201 });
|
|
60
|
+
} catch (error: unknown) {
|
|
61
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
62
|
+
return NextResponse.json({ error: message }, { status: 500 });
|
|
63
|
+
}
|
|
64
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@medicine-wheel/app",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Medicine Wheel — Interactive visual layer for Indigenous relational research with Four Directions, ceremonies, and narrative arcs",
|
|
5
5
|
"bin": {
|
|
6
6
|
"mw": "dist/cli/mw.js",
|
|
@@ -52,6 +52,8 @@
|
|
|
52
52
|
"build": "next build",
|
|
53
53
|
"start": "next start -p 3940",
|
|
54
54
|
"lint": "next lint",
|
|
55
|
+
"test": "vitest run",
|
|
56
|
+
"test:watch": "vitest",
|
|
55
57
|
"version:patch": "node scripts/bump-versions.mjs patch && node scripts/sync-versions.mjs && shx rm -rf node_modules src/*/node_modules package-lock.json && npm install --legacy-peer-deps",
|
|
56
58
|
"version:minor": "node scripts/bump-versions.mjs minor && node scripts/sync-versions.mjs && shx rm -rf node_modules src/*/node_modules package-lock.json && npm install --legacy-peer-deps",
|
|
57
59
|
"version:major": "node scripts/bump-versions.mjs major && node scripts/sync-versions.mjs && shx rm -rf node_modules src/*/node_modules package-lock.json && npm install --legacy-peer-deps",
|
|
@@ -72,33 +74,34 @@
|
|
|
72
74
|
"release:major": "npm run version:major && npm run publish:all && npm run release:commit"
|
|
73
75
|
},
|
|
74
76
|
"dependencies": {
|
|
77
|
+
"@medicine-wheel/ceremony-protocol": "^0.3.0",
|
|
78
|
+
"@medicine-wheel/community-review": "^0.3.0",
|
|
79
|
+
"@medicine-wheel/consent-lifecycle": "^0.3.0",
|
|
80
|
+
"@medicine-wheel/data-store": "^0.3.0",
|
|
81
|
+
"@medicine-wheel/data-store-postgres": "^0.3.0",
|
|
82
|
+
"@medicine-wheel/fire-keeper": "^0.3.0",
|
|
83
|
+
"@medicine-wheel/graph-viz": "^0.3.0",
|
|
84
|
+
"@medicine-wheel/importance-unit": "^0.3.0",
|
|
85
|
+
"@medicine-wheel/mcp": "^4.3.0",
|
|
86
|
+
"@medicine-wheel/narrative-engine": "^0.3.0",
|
|
87
|
+
"@medicine-wheel/ontology-core": "^0.3.0",
|
|
88
|
+
"@medicine-wheel/prompt-decomposition": "^0.3.0",
|
|
89
|
+
"@medicine-wheel/relational-index": "^0.3.0",
|
|
90
|
+
"@medicine-wheel/relational-query": "^0.3.0",
|
|
91
|
+
"@medicine-wheel/session-reader": "^0.3.0",
|
|
92
|
+
"@medicine-wheel/storage-provider": "^0.3.0",
|
|
93
|
+
"@medicine-wheel/transformation-tracker": "^0.3.0",
|
|
94
|
+
"@medicine-wheel/ui-components": "^0.3.0",
|
|
95
|
+
"@neondatabase/serverless": "^0.10.0",
|
|
96
|
+
"clsx": "^2.1.1",
|
|
97
|
+
"lucide-react": "^0.475.0",
|
|
75
98
|
"next": "^15.3.0",
|
|
99
|
+
"next-themes": "^0.4.6",
|
|
76
100
|
"react": "^19.0.0",
|
|
77
101
|
"react-dom": "^19.0.0",
|
|
78
|
-
"next-themes": "^0.4.6",
|
|
79
|
-
"sonner": "^1.7.0",
|
|
80
|
-
"lucide-react": "^0.475.0",
|
|
81
|
-
"clsx": "^2.1.1",
|
|
82
|
-
"tailwind-merge": "^3.0.2",
|
|
83
102
|
"recharts": "^2.15.4",
|
|
84
|
-
"
|
|
85
|
-
"
|
|
86
|
-
"@medicine-wheel/narrative-engine": "^0.2.7",
|
|
87
|
-
"@medicine-wheel/graph-viz": "^0.2.7",
|
|
88
|
-
"@medicine-wheel/relational-query": "^0.2.7",
|
|
89
|
-
"@medicine-wheel/prompt-decomposition": "^0.2.7",
|
|
90
|
-
"@medicine-wheel/ui-components": "^0.2.7",
|
|
91
|
-
"@medicine-wheel/data-store": "^0.2.7",
|
|
92
|
-
"@medicine-wheel/session-reader": "^0.2.7",
|
|
93
|
-
"@medicine-wheel/fire-keeper": "^0.2.7",
|
|
94
|
-
"@medicine-wheel/importance-unit": "^0.2.7",
|
|
95
|
-
"@medicine-wheel/relational-index": "^0.2.7",
|
|
96
|
-
"@medicine-wheel/transformation-tracker": "^0.2.7",
|
|
97
|
-
"@medicine-wheel/storage-provider": "^0.2.7",
|
|
98
|
-
"@medicine-wheel/community-review": "^0.2.7",
|
|
99
|
-
"@medicine-wheel/consent-lifecycle": "^0.2.7",
|
|
100
|
-
"@medicine-wheel/data-store-postgres": "^0.2.7",
|
|
101
|
-
"@neondatabase/serverless": "^0.10.0"
|
|
103
|
+
"sonner": "^1.7.0",
|
|
104
|
+
"tailwind-merge": "^3.0.2"
|
|
102
105
|
},
|
|
103
106
|
"devDependencies": {
|
|
104
107
|
"@tailwindcss/postcss": "^4.1.0",
|
|
@@ -107,6 +110,7 @@
|
|
|
107
110
|
"@types/react-dom": "^19.0.0",
|
|
108
111
|
"shx": "^0.4.0",
|
|
109
112
|
"tailwindcss": "^4.1.0",
|
|
110
|
-
"typescript": "^5.7.0"
|
|
113
|
+
"typescript": "^5.7.0",
|
|
114
|
+
"vitest": "^4.1.8"
|
|
111
115
|
}
|
|
112
116
|
}
|