@mmj-map/palette 0.0.0-stage → 0.1.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 +119 -2
- package/dist/args.js +62 -0
- package/dist/cli.js +69 -0
- package/dist/derive.js +125 -0
- package/dist/mcp.js +93 -0
- package/dist/oklab.js +95 -0
- package/dist/server.js +175 -0
- package/dist/style.js +35 -0
- package/package.json +55 -3
- package/server.json +21 -0
- package/styles/modern-candy.json +950 -0
- package/styles/modern-dark.json +949 -0
- package/styles/modern-ink.json +950 -0
- package/styles/modern-light.json +950 -0
- package/styles/modern-neon.json +950 -0
- package/styles/modern-sand.json +950 -0
package/dist/server.js
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP の道具立てと、要求の割り振り。**ここは入出力を持たない**(stdio は `mcp.ts`)。
|
|
3
|
+
*
|
|
4
|
+
* **SDK を入れていない。**MCP の stdio は JSON-RPC 2.0 を改行区切りで流すだけで、
|
|
5
|
+
* ここで必要なのは `initialize` / `tools/list` / `tools/call` の 3 つ。
|
|
6
|
+
* 公式 SDK(`@modelcontextprotocol/sdk` 1.30.0・MIT)は express / hono / cors / jose /
|
|
7
|
+
* express-rate-limit まで引いてきて **99 パッケージ増える**(実測・2026-09-21)。
|
|
8
|
+
* HTTP と SSE の輸送のためのもので、**stdio 1 本には釣り合わない**(baseline §1 / §12)。
|
|
9
|
+
*
|
|
10
|
+
* 代わりに、**実際に喋れることをテストで縛る**(`test/mcp.test.ts` がプロセスを起動して
|
|
11
|
+
* 本物の JSON-RPC を流す)。仕様が動いたらそこが落ちる。
|
|
12
|
+
*/
|
|
13
|
+
import { ROLE_ORDER, deriveRoles } from "./derive.js";
|
|
14
|
+
import { buildStyle } from "./style.js";
|
|
15
|
+
/** 応えるプロトコル版。**相手の言い値をそのまま返さない**(対応していない版を認めない) */
|
|
16
|
+
export const PROTOCOL_VERSION = "2025-06-18";
|
|
17
|
+
/** 役割が何を変えるか。**名前だけ返しても、相手はどれを指定すべきか分からない** */
|
|
18
|
+
const ROLE_MEANING = {
|
|
19
|
+
background: "地図の下地",
|
|
20
|
+
earth: "陸の面",
|
|
21
|
+
landcover: "広域の土地被覆(低倍率のみ)",
|
|
22
|
+
green: "公園・森・緑地",
|
|
23
|
+
built: "学校・病院などの敷地",
|
|
24
|
+
paved: "歩行者空間・駅のホーム・桟橋",
|
|
25
|
+
water: "水域の面(海・川・湖)",
|
|
26
|
+
waterway: "水路の線(川・小川)",
|
|
27
|
+
buildings: "建物の面",
|
|
28
|
+
path: "歩道・小道",
|
|
29
|
+
"road-minor": "細街路",
|
|
30
|
+
"road-medium": "中位の道路",
|
|
31
|
+
"road-major": "幹線道路",
|
|
32
|
+
"highway-casing": "高速道路の縁取り",
|
|
33
|
+
highway: "高速道路の本体(テーマカラーが乗る場所)",
|
|
34
|
+
rail: "鉄道",
|
|
35
|
+
boundary: "行政境界",
|
|
36
|
+
station: "駅の丸の縁(テーマカラーが乗る場所)",
|
|
37
|
+
"station-fill": "駅の丸の中",
|
|
38
|
+
"label-city": "市区町村名",
|
|
39
|
+
"label-station": "駅名",
|
|
40
|
+
"label-neighbourhood": "町丁名",
|
|
41
|
+
"label-water": "水域の名前",
|
|
42
|
+
halo: "ラベルの縁取り(地色にすること)",
|
|
43
|
+
};
|
|
44
|
+
const ANCHOR_SCHEMA = {
|
|
45
|
+
land: { type: "string", description: "陸・背景の色(#rrggbb)" },
|
|
46
|
+
water: { type: "string", description: "水域の色(#rrggbb)。陸の系列から独立させる" },
|
|
47
|
+
ink: { type: "string", description: "文字の色(#rrggbb)。ラベルの一番濃い側" },
|
|
48
|
+
accent: { type: "string", description: "テーマカラー(#rrggbb)。高速道路と駅の縁に乗る" },
|
|
49
|
+
green: { type: "string", description: "公園・森の色(#rrggbb)。省くと陸から導く" },
|
|
50
|
+
overrides: {
|
|
51
|
+
type: "object",
|
|
52
|
+
description: "役割ごとに手で決めた色。ここが最後に勝つ",
|
|
53
|
+
additionalProperties: { type: "string" },
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
const ANCHOR_REQUIRED = ["land", "water", "ink", "accent"];
|
|
57
|
+
export const TOOLS = [
|
|
58
|
+
{
|
|
59
|
+
name: "mmj_list_roles",
|
|
60
|
+
description: "配色の役割を 24 個すべて返す。それぞれが地図の何を変えるかの説明つき。" +
|
|
61
|
+
"色を決める前にこれを読むと、どの役割を指定すべきか分かる。",
|
|
62
|
+
inputSchema: { type: "object", properties: {} },
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
name: "mmj_list_bases",
|
|
66
|
+
description: "土台にできるスタイルの名前を返す。土台はレイヤ構成(何をどの倍率で描くか)を決め、" +
|
|
67
|
+
"色は指し値が決める。",
|
|
68
|
+
inputSchema: { type: "object", properties: {} },
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
name: "mmj_derive_palette",
|
|
72
|
+
description: "4〜5 色の指し値から、24 役割の配色を作る。返るのは役割 → 色の JSON で、" +
|
|
73
|
+
"<mmj-map palette-url> にそのまま渡せる。手で直す前提の形。",
|
|
74
|
+
inputSchema: { type: "object", properties: ANCHOR_SCHEMA, required: ANCHOR_REQUIRED },
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
name: "mmj_build_style",
|
|
78
|
+
description: "指し値から、そのまま配れるスタイル JSON を作る。<mmj-map style-url> に渡すか、" +
|
|
79
|
+
"自分の配布物へ同梱する。生成物の印(mmj:generated)と帰属表示が入る。",
|
|
80
|
+
inputSchema: {
|
|
81
|
+
type: "object",
|
|
82
|
+
properties: {
|
|
83
|
+
...ANCHOR_SCHEMA,
|
|
84
|
+
base: { type: "string", description: "土台のスタイル名(mmj_list_bases で取れる)" },
|
|
85
|
+
name: { type: "string", description: "作るスタイルの名前" },
|
|
86
|
+
},
|
|
87
|
+
required: [...ANCHOR_REQUIRED, "base", "name"],
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
];
|
|
91
|
+
/** 道具の返り値。文字を 1 つ返すだけ */
|
|
92
|
+
function text(value) {
|
|
93
|
+
return {
|
|
94
|
+
content: [
|
|
95
|
+
{ type: "text", text: typeof value === "string" ? value : JSON.stringify(value, null, 2) },
|
|
96
|
+
],
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
/** **失敗は例外で落とさず、相手へ返す。**落とすと接続ごと切れて理由が残らない */
|
|
100
|
+
function failure(message) {
|
|
101
|
+
return { ...text(message), isError: true };
|
|
102
|
+
}
|
|
103
|
+
function readAnchors(args) {
|
|
104
|
+
const missing = ANCHOR_REQUIRED.filter((key) => typeof args?.[key] !== "string");
|
|
105
|
+
if (missing.length > 0) {
|
|
106
|
+
// 足りない指し値を勝手に作らない(baseline §11)。何が足りないかを名指しで返す
|
|
107
|
+
throw new Error(`指し値が足りません: ${missing.join(", ")}`);
|
|
108
|
+
}
|
|
109
|
+
return {
|
|
110
|
+
land: args.land,
|
|
111
|
+
water: args.water,
|
|
112
|
+
ink: args.ink,
|
|
113
|
+
accent: args.accent,
|
|
114
|
+
...(typeof args.green === "string" ? { green: args.green } : {}),
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
function callTool(name, args, deps) {
|
|
118
|
+
try {
|
|
119
|
+
if (name === "mmj_list_roles") {
|
|
120
|
+
return text(Object.fromEntries(ROLE_ORDER.map((role) => [role, ROLE_MEANING[role] ?? ""])));
|
|
121
|
+
}
|
|
122
|
+
if (name === "mmj_list_bases")
|
|
123
|
+
return text(deps.listBases());
|
|
124
|
+
if (name === "mmj_derive_palette") {
|
|
125
|
+
return text(deriveRoles(readAnchors(args), args?.overrides ?? {}));
|
|
126
|
+
}
|
|
127
|
+
if (name === "mmj_build_style") {
|
|
128
|
+
const bases = deps.listBases();
|
|
129
|
+
if (!bases.includes(args?.base)) {
|
|
130
|
+
return failure(`知らない土台です: ${args?.base}(使えるのは ${bases.join(" ")})`);
|
|
131
|
+
}
|
|
132
|
+
if (typeof args?.name !== "string" || args.name === "") {
|
|
133
|
+
return failure("name が要ります(どのスタイルを見ているのか分からなくなるため)");
|
|
134
|
+
}
|
|
135
|
+
const anchors = readAnchors(args);
|
|
136
|
+
const roles = deriveRoles(anchors, args?.overrides ?? {});
|
|
137
|
+
return text(buildStyle(deps.loadBase(args.base), roles, { name: args.name, anchors }));
|
|
138
|
+
}
|
|
139
|
+
return failure(`知らない道具です: ${name}(使えるのは ${TOOLS.map((t) => t.name).join(" ")})`);
|
|
140
|
+
}
|
|
141
|
+
catch (error) {
|
|
142
|
+
return failure(error instanceof Error ? error.message : String(error));
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* JSON-RPC の要求を 1 つ処理する。
|
|
147
|
+
* @returns 応答。**通知(id の無い要求)には `null`**(応答を返すと相手が壊れる)
|
|
148
|
+
*/
|
|
149
|
+
export function handle(request, deps) {
|
|
150
|
+
const { id, method } = request;
|
|
151
|
+
if (id === undefined || id === null)
|
|
152
|
+
return null;
|
|
153
|
+
if (method === "initialize") {
|
|
154
|
+
return {
|
|
155
|
+
jsonrpc: "2.0",
|
|
156
|
+
id,
|
|
157
|
+
result: {
|
|
158
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
159
|
+
capabilities: { tools: {} },
|
|
160
|
+
serverInfo: { name: "mmj-palette", version: deps.version },
|
|
161
|
+
},
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
if (method === "tools/list")
|
|
165
|
+
return { jsonrpc: "2.0", id, result: { tools: TOOLS } };
|
|
166
|
+
if (method === "tools/call") {
|
|
167
|
+
return {
|
|
168
|
+
jsonrpc: "2.0",
|
|
169
|
+
id,
|
|
170
|
+
result: callTool(request.params?.name, request.params?.arguments ?? {}, deps),
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
// 知らないメソッドは JSON-RPC の作法どおりに返す。**黙って無視しない**
|
|
174
|
+
return { jsonrpc: "2.0", id, error: { code: -32601, message: `Method not found: ${method}` } };
|
|
175
|
+
}
|
package/dist/style.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 土台と色から、完成したスタイルを組み立てる。**ここは純粋関数。**
|
|
3
|
+
*
|
|
4
|
+
* 26 の役割だけを渡す道(`<mmj-map palette-url>`)だけでは足りない場面がある。
|
|
5
|
+
* **配布物に自分のスタイルを同梱したい導入者**は、スタイルそのものを持つ必要がある。
|
|
6
|
+
*
|
|
7
|
+
* 出したものには **`mmj:generated: true`** を残す。
|
|
8
|
+
* 残さないと、**MMJ が手書きしている 6 枚と見分けが付かなくなる**(D-002 / D-021)。
|
|
9
|
+
*/
|
|
10
|
+
import { applyPalette } from "@mmj-map/elements/palette";
|
|
11
|
+
/**
|
|
12
|
+
* @param base 土台にするスタイル(`styles/*.json` のどれか、または自前のもの)
|
|
13
|
+
* @param roles 役割ごとの色
|
|
14
|
+
* @param options 名前と、作った元の指し値
|
|
15
|
+
* @returns 新しいスタイル。**`base` は書き換えない**
|
|
16
|
+
*/
|
|
17
|
+
export function buildStyle(base, roles, options) {
|
|
18
|
+
const result = applyPalette(base, roles);
|
|
19
|
+
if (result.applied === 0) {
|
|
20
|
+
// 色を渡したのに 1 つも当たらないスタイルを黙って出さない
|
|
21
|
+
throw new Error("渡した色がどのレイヤにも当たりませんでした。土台のレイヤ id が MMJ の 6 枚と違う可能性があります");
|
|
22
|
+
}
|
|
23
|
+
return {
|
|
24
|
+
...result.style,
|
|
25
|
+
name: options.name,
|
|
26
|
+
metadata: {
|
|
27
|
+
...base.metadata,
|
|
28
|
+
// **これが生成物である印。**手書きの 6 枚と混ざらないようにする(D-021)
|
|
29
|
+
"mmj:generated": true,
|
|
30
|
+
"mmj:base": base.name ?? null,
|
|
31
|
+
"mmj:anchors": options.anchors ?? null,
|
|
32
|
+
"mmj:status": "生成物。色は導入者のもので、MMJ は承認していない",
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,58 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mmj-map/palette",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"description": "MCP server that derives a full map palette from four anchor colours and builds a ready MapLibre style. Self-hosted maps, no API key.",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"mcp",
|
|
9
|
+
"mcp-server",
|
|
10
|
+
"maplibre",
|
|
11
|
+
"map-style",
|
|
12
|
+
"palette",
|
|
13
|
+
"color",
|
|
14
|
+
"self-hosted",
|
|
15
|
+
"no-api-key",
|
|
16
|
+
"mmj"
|
|
17
|
+
],
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "git+https://github.com/meta-taro/mmj-map.git",
|
|
21
|
+
"directory": "tools/palette"
|
|
22
|
+
},
|
|
23
|
+
"homepage": "https://meta-taro.github.io/mmj-map/",
|
|
24
|
+
"bugs": {
|
|
25
|
+
"url": "https://github.com/meta-taro/mmj-map/issues"
|
|
26
|
+
},
|
|
27
|
+
"mcpName": "io.github.meta-taro/mmj-palette",
|
|
28
|
+
"bin": {
|
|
29
|
+
"mmj-palette-mcp": "dist/mcp.js"
|
|
30
|
+
},
|
|
31
|
+
"files": [
|
|
32
|
+
"dist",
|
|
33
|
+
"styles",
|
|
34
|
+
"server.json",
|
|
35
|
+
"README.md",
|
|
36
|
+
"LICENSE"
|
|
37
|
+
],
|
|
38
|
+
"publishConfig": {
|
|
39
|
+
"access": "public"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@types/node": "^26.5.1",
|
|
43
|
+
"tsx": "^4.23.13",
|
|
44
|
+
"typescript": "^7.0.2",
|
|
45
|
+
"vitest": "^5.0.0"
|
|
46
|
+
},
|
|
47
|
+
"dependencies": {
|
|
48
|
+
"@mmj-map/elements": "0.5.0"
|
|
49
|
+
},
|
|
50
|
+
"scripts": {
|
|
51
|
+
"build": "tsc -p tsconfig.build.json",
|
|
52
|
+
"bundle-styles": "node scripts/bundle-styles.mjs",
|
|
53
|
+
"make": "tsx src/cli.ts",
|
|
54
|
+
"mcp": "tsx src/mcp.ts",
|
|
55
|
+
"test": "vitest run",
|
|
56
|
+
"typecheck": "tsc --noEmit -p tsconfig.json"
|
|
57
|
+
}
|
|
6
58
|
}
|
package/server.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
|
|
3
|
+
"name": "io.github.meta-taro/mmj-palette",
|
|
4
|
+
"description": "Build a MapLibre style from four anchor colours. Self-hosted maps, no API key.",
|
|
5
|
+
"repository": {
|
|
6
|
+
"url": "https://github.com/meta-taro/mmj-map",
|
|
7
|
+
"source": "github",
|
|
8
|
+
"subfolder": "tools/palette"
|
|
9
|
+
},
|
|
10
|
+
"version": "0.1.0",
|
|
11
|
+
"packages": [
|
|
12
|
+
{
|
|
13
|
+
"registryType": "npm",
|
|
14
|
+
"identifier": "@mmj-map/palette",
|
|
15
|
+
"version": "0.1.0",
|
|
16
|
+
"transport": {
|
|
17
|
+
"type": "stdio"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
]
|
|
21
|
+
}
|