hackshop-mcp 0.0.3 → 0.0.5
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/README.md +88 -34
- package/catalog.json +1584 -25
- package/dist/build-plan/index.d.ts +10 -0
- package/dist/build-plan/index.js +550 -0
- package/dist/build-plan/index.js.map +1 -0
- package/dist/build-plan/types.d.ts +153 -0
- package/dist/build-plan/types.js +2 -0
- package/dist/build-plan/types.js.map +1 -0
- package/dist/catalog/schema.d.ts +523 -7
- package/dist/catalog/schema.js +49 -0
- package/dist/catalog/schema.js.map +1 -1
- package/dist/core/assess.d.ts +36 -0
- package/dist/core/assess.js +100 -0
- package/dist/core/assess.js.map +1 -0
- package/dist/core/errors.d.ts +2 -0
- package/dist/core/errors.js +70 -0
- package/dist/core/errors.js.map +1 -0
- package/dist/core/links.d.ts +3 -0
- package/dist/core/links.js +19 -0
- package/dist/core/links.js.map +1 -0
- package/dist/core/plan-gadget.d.ts +85 -0
- package/dist/core/plan-gadget.js +619 -0
- package/dist/core/plan-gadget.js.map +1 -0
- package/dist/core/resources.d.ts +37 -0
- package/dist/core/resources.js +143 -0
- package/dist/core/resources.js.map +1 -0
- package/dist/core/safety.d.ts +7 -0
- package/dist/core/safety.js +28 -0
- package/dist/core/safety.js.map +1 -0
- package/dist/core/tools.d.ts +247 -0
- package/dist/core/tools.js +164 -0
- package/dist/core/tools.js.map +1 -0
- package/dist/core/types.d.ts +163 -0
- package/dist/core/types.js +17 -0
- package/dist/core/types.js.map +1 -0
- package/dist/platforms/index.d.ts +26 -0
- package/dist/platforms/index.js +52 -0
- package/dist/platforms/index.js.map +1 -0
- package/dist/platforms/load.d.ts +4 -0
- package/dist/platforms/load.js +78 -0
- package/dist/platforms/load.js.map +1 -0
- package/dist/platforms/schema.d.ts +587 -0
- package/dist/platforms/schema.js +64 -0
- package/dist/platforms/schema.js.map +1 -0
- package/dist/sampling.d.ts +5 -0
- package/dist/sampling.js +44 -21
- package/dist/sampling.js.map +1 -1
- package/dist/server.d.ts +14 -1
- package/dist/server.js +164 -40
- package/dist/server.js.map +1 -1
- package/dist/site-url.d.ts +1 -0
- package/dist/site-url.js +4 -0
- package/dist/site-url.js.map +1 -0
- package/dist/telemetry.d.ts +29 -0
- package/dist/telemetry.js +100 -0
- package/dist/telemetry.js.map +1 -0
- package/dist/tools/assess_hackability.d.ts +2 -29
- package/dist/tools/assess_hackability.js +11 -43
- package/dist/tools/assess_hackability.js.map +1 -1
- package/dist/tools/get_build_plan.d.ts +6 -0
- package/dist/tools/get_build_plan.js +16 -0
- package/dist/tools/get_build_plan.js.map +1 -0
- package/dist/tools/plan_gadget.d.ts +4 -0
- package/dist/tools/plan_gadget.js +14 -0
- package/dist/tools/plan_gadget.js.map +1 -0
- package/dist/tools/propose_hardware.d.ts +5 -0
- package/dist/tools/propose_hardware.js +57 -8
- package/dist/tools/propose_hardware.js.map +1 -1
- package/dist/tools/simulate_assembly.d.ts +315 -0
- package/dist/tools/simulate_assembly.js +145 -0
- package/dist/tools/simulate_assembly.js.map +1 -0
- package/package.json +4 -1
- package/platforms.json +779 -0
- package/tags.md +1 -0
package/dist/sampling.js
CHANGED
|
@@ -1,24 +1,12 @@
|
|
|
1
1
|
import Anthropic from "@anthropic-ai/sdk";
|
|
2
|
-
// Wraps sampling/createMessage with retry-once + Anthropic-API fallback +
|
|
3
|
-
// degraded-response semantics. Returns either parsed JSON of shape T, or null
|
|
4
|
-
// if every path failed.
|
|
5
|
-
//
|
|
6
|
-
// Order of attempts:
|
|
7
|
-
// 1. server.createMessage (host-delegated sampling) — preferred
|
|
8
|
-
// 2. retry server.createMessage once on transient failure
|
|
9
|
-
// 3. direct Anthropic API call IF process.env.ANTHROPIC_API_KEY is set — fallback
|
|
10
|
-
// 4. return null → caller produces a degraded response
|
|
11
|
-
//
|
|
12
|
-
// The fallback exists because not all MCP hosts implement sampling/createMessage
|
|
13
|
-
// today. Claude Code supports it; Claude Desktop's support has been spotty;
|
|
14
|
-
// Cursor / Cowork / other clients may not. Setting ANTHROPIC_API_KEY in your
|
|
15
|
-
// MCP client config (under "env") gives the user reasoning even on hosts
|
|
16
|
-
// without sampling.
|
|
17
2
|
export async function sampleJson(opts) {
|
|
18
|
-
const { server, systemPrompt, userPrompt, maxTokens = 1500 } = opts;
|
|
3
|
+
const { server, systemPrompt, userPrompt, maxTokens = 1500, diagnostics, hostSamplingTimeoutMs, } = opts;
|
|
4
|
+
const hostSamplingDeadline = hostSamplingTimeoutMs === undefined
|
|
5
|
+
? undefined
|
|
6
|
+
: Date.now() + hostSamplingTimeoutMs;
|
|
19
7
|
const tryHostSampling = async () => {
|
|
20
8
|
try {
|
|
21
|
-
const response = await server.createMessage({
|
|
9
|
+
const response = await withTimeout(server.createMessage({
|
|
22
10
|
systemPrompt,
|
|
23
11
|
messages: [
|
|
24
12
|
{
|
|
@@ -31,7 +19,7 @@ export async function sampleJson(opts) {
|
|
|
31
19
|
intelligencePriority: 0.6,
|
|
32
20
|
speedPriority: 0.4,
|
|
33
21
|
},
|
|
34
|
-
});
|
|
22
|
+
}), remainingHostSamplingMs(hostSamplingDeadline));
|
|
35
23
|
const content = response.content;
|
|
36
24
|
if (content.type !== "text")
|
|
37
25
|
return null;
|
|
@@ -49,13 +37,18 @@ export async function sampleJson(opts) {
|
|
|
49
37
|
const second = await tryHostSampling();
|
|
50
38
|
if (second !== null)
|
|
51
39
|
return second;
|
|
52
|
-
// Attempt 3: direct Anthropic API fallback (env-gated)
|
|
40
|
+
// Attempt 3: direct Anthropic API fallback (env-gated). Default model id kept
|
|
41
|
+
// current ('claude-sonnet-4-6'); override via HACKSHOP_MODEL. If this throws
|
|
42
|
+
// WHILE a key was present (e.g. the default model was retired), we record the
|
|
43
|
+
// error in `diagnostics` so the caller can make the failure user-visible
|
|
44
|
+
// instead of degrading silently to stderr only.
|
|
53
45
|
const apiKey = process.env.ANTHROPIC_API_KEY;
|
|
54
46
|
if (apiKey) {
|
|
47
|
+
const fallbackModel = process.env.HACKSHOP_MODEL ?? "claude-sonnet-4-6";
|
|
55
48
|
try {
|
|
56
49
|
const client = new Anthropic({ apiKey });
|
|
57
50
|
const response = await client.messages.create({
|
|
58
|
-
model:
|
|
51
|
+
model: fallbackModel,
|
|
59
52
|
max_tokens: maxTokens,
|
|
60
53
|
system: systemPrompt,
|
|
61
54
|
messages: [{ role: "user", content: userPrompt }],
|
|
@@ -68,13 +61,43 @@ export async function sampleJson(opts) {
|
|
|
68
61
|
return parsed;
|
|
69
62
|
}
|
|
70
63
|
}
|
|
64
|
+
// Key present, call succeeded, but content was unusable — note it.
|
|
65
|
+
if (diagnostics) {
|
|
66
|
+
diagnostics.apiFallbackError = `Anthropic API fallback (model "${fallbackModel}") returned no usable JSON.`;
|
|
67
|
+
}
|
|
71
68
|
}
|
|
72
69
|
catch (err) {
|
|
73
|
-
|
|
70
|
+
const msg = err.message;
|
|
71
|
+
process.stderr.write(`[hackshop-mcp] Anthropic fallback failed: ${msg}\n`);
|
|
72
|
+
if (diagnostics) {
|
|
73
|
+
diagnostics.apiFallbackError = `Anthropic API fallback failed (model "${fallbackModel}"): ${msg}. If the model id was retired, set HACKSHOP_MODEL to a current model.`;
|
|
74
|
+
}
|
|
74
75
|
}
|
|
75
76
|
}
|
|
76
77
|
return null;
|
|
77
78
|
}
|
|
79
|
+
function remainingHostSamplingMs(deadline) {
|
|
80
|
+
if (deadline === undefined)
|
|
81
|
+
return undefined;
|
|
82
|
+
return Math.max(1, deadline - Date.now());
|
|
83
|
+
}
|
|
84
|
+
async function withTimeout(promise, timeoutMs) {
|
|
85
|
+
if (timeoutMs === undefined)
|
|
86
|
+
return promise;
|
|
87
|
+
let timeout;
|
|
88
|
+
try {
|
|
89
|
+
return await Promise.race([
|
|
90
|
+
promise,
|
|
91
|
+
new Promise((_resolve, reject) => {
|
|
92
|
+
timeout = setTimeout(() => reject(new Error("host sampling timed out")), timeoutMs);
|
|
93
|
+
}),
|
|
94
|
+
]);
|
|
95
|
+
}
|
|
96
|
+
finally {
|
|
97
|
+
if (timeout)
|
|
98
|
+
clearTimeout(timeout);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
78
101
|
// Strip a possible markdown code fence around JSON. Tolerant of whitespace
|
|
79
102
|
// and explanatory prose around the fence.
|
|
80
103
|
function parseJsonLoose(text) {
|
package/dist/sampling.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sampling.js","sourceRoot":"","sources":["../src/sampling.ts"],"names":[],"mappings":"AACA,OAAO,SAAS,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"sampling.js","sourceRoot":"","sources":["../src/sampling.ts"],"names":[],"mappings":"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"}
|
package/dist/server.d.ts
CHANGED
|
@@ -1,2 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
2
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
3
|
+
import type { DeviceEntry } from "./catalog/schema.js";
|
|
4
|
+
import type { Platform } from "./platforms/schema.js";
|
|
5
|
+
export declare function createToolRunner(context: {
|
|
6
|
+
devices: DeviceEntry[];
|
|
7
|
+
platforms: Platform[];
|
|
8
|
+
server?: Server;
|
|
9
|
+
tags?: string[];
|
|
10
|
+
}): (name: string, args: unknown) => Promise<{
|
|
11
|
+
out: unknown;
|
|
12
|
+
degraded?: boolean;
|
|
13
|
+
isError?: boolean;
|
|
14
|
+
text?: string;
|
|
15
|
+
}>;
|
package/dist/server.js
CHANGED
|
@@ -1,21 +1,87 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { realpathSync } from "node:fs";
|
|
3
|
+
import { pathToFileURL } from "node:url";
|
|
2
4
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
3
5
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
-
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
6
|
+
import { CallToolRequestSchema, GetPromptRequestSchema, ListPromptsRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
5
7
|
import { loadCatalog } from "./catalog/load.js";
|
|
8
|
+
import { loadPlatforms } from "./platforms/load.js";
|
|
9
|
+
import { printablesFor, setLoadedPlatforms } from "./platforms/index.js";
|
|
6
10
|
import { probeSamplingSupport } from "./sampling.js";
|
|
7
11
|
import { proposeHardware, proposeHardwareInput, } from "./tools/propose_hardware.js";
|
|
8
|
-
import {
|
|
12
|
+
import { simulateAssembly, simulateAssemblyInput, } from "./tools/simulate_assembly.js";
|
|
13
|
+
import { siteUrlFromEnv } from "./site-url.js";
|
|
14
|
+
import { createTelemetry, errorKind } from "./telemetry.js";
|
|
15
|
+
import { coreToolDefinitions, executeCoreTool, } from "./core/tools.js";
|
|
16
|
+
import { getCorePrompt, listCorePrompts, listCoreResources, readCoreResource, } from "./core/resources.js";
|
|
9
17
|
const NAME = "hackshop-mcp";
|
|
10
|
-
const VERSION = "0.0.
|
|
18
|
+
const VERSION = "0.0.5";
|
|
19
|
+
const STDIO_INSTRUCTIONS = "Hackshop maps a natural-language project idea to hackable, repurposable, or protocol-native hardware. Use plan_gadget for Muse agent-body gadgets because it is deterministic, instant and key-free. Use propose_hardware for broader repurposing ideas and existing hardware, especially when the host can sample or ANTHROPIC_API_KEY is set. Hackshop never buys anything; show shopping lists and ask the human before any purchase. Anonymous usage telemetry (tool names and timings only) is on by default; set HACKSHOP_TELEMETRY=0 to turn it off.";
|
|
20
|
+
export function createToolRunner(context) {
|
|
21
|
+
return async (name, args) => {
|
|
22
|
+
if (name === "propose_hardware") {
|
|
23
|
+
if (!context.server) {
|
|
24
|
+
throw new Error("propose_hardware requires an MCP server context");
|
|
25
|
+
}
|
|
26
|
+
const input = proposeHardwareInput.parse(args);
|
|
27
|
+
const out = await proposeHardware(input, context.devices, context.server);
|
|
28
|
+
return { out, degraded: out.degraded };
|
|
29
|
+
}
|
|
30
|
+
if (name === "assess_hackability" ||
|
|
31
|
+
name === "plan_gadget" ||
|
|
32
|
+
name === "get_build_plan") {
|
|
33
|
+
const result = executeCoreTool(name, args, coreContext(context.devices, context.platforms, context.tags));
|
|
34
|
+
return runnerResult(result);
|
|
35
|
+
}
|
|
36
|
+
if (name === "simulate_assembly") {
|
|
37
|
+
const input = simulateAssemblyInput.parse(args);
|
|
38
|
+
return { out: await simulateAssembly(input) };
|
|
39
|
+
}
|
|
40
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
function coreContext(catalog, platforms, tags) {
|
|
44
|
+
const siteUrl = siteUrlFromEnv();
|
|
45
|
+
return {
|
|
46
|
+
catalog,
|
|
47
|
+
platforms,
|
|
48
|
+
printablesFor: (device, baseUrl) => printablesFor(device, baseUrl),
|
|
49
|
+
siteUrl,
|
|
50
|
+
tags,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
function runnerResult(result) {
|
|
54
|
+
if (result.isError) {
|
|
55
|
+
return { out: { error: result.text }, isError: true, text: result.text };
|
|
56
|
+
}
|
|
57
|
+
return { out: result.output };
|
|
58
|
+
}
|
|
11
59
|
async function main() {
|
|
12
60
|
// Boot validation. Refuses to start on bad catalog/tags.
|
|
13
61
|
const { devices, tags } = loadCatalog();
|
|
14
|
-
|
|
62
|
+
const sortedTags = [...tags].sort();
|
|
63
|
+
const platforms = loadPlatforms(devices);
|
|
64
|
+
setLoadedPlatforms(platforms);
|
|
65
|
+
process.stderr.write(`[hackshop-mcp] Catalog loaded: ${devices.length} devices, ${tags.size} tags, ${platforms.length} platforms.\n`);
|
|
15
66
|
// Note: sampling is a CLIENT capability, not a server one. We don't declare
|
|
16
67
|
// it here; we just call server.createMessage(...) and the host either
|
|
17
68
|
// supports it or doesn't. probeSamplingSupport() handles the check.
|
|
18
|
-
const server = new Server({ name: NAME, version: VERSION }, {
|
|
69
|
+
const server = new Server({ name: NAME, version: VERSION }, {
|
|
70
|
+
capabilities: { tools: {}, resources: {}, prompts: {} },
|
|
71
|
+
instructions: STDIO_INSTRUCTIONS,
|
|
72
|
+
});
|
|
73
|
+
const telemetry = createTelemetry(VERSION);
|
|
74
|
+
const clientInfo = () => {
|
|
75
|
+
const client = server.getClientVersion();
|
|
76
|
+
return { client_name: client?.name, client_version: client?.version };
|
|
77
|
+
};
|
|
78
|
+
server.oninitialized = () => {
|
|
79
|
+
telemetry.send({
|
|
80
|
+
event: "mcp_server_started",
|
|
81
|
+
...clientInfo(),
|
|
82
|
+
client_sampling: Boolean(server.getClientCapabilities()?.sampling),
|
|
83
|
+
});
|
|
84
|
+
};
|
|
19
85
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
20
86
|
tools: [
|
|
21
87
|
{
|
|
@@ -40,60 +106,118 @@ async function main() {
|
|
|
40
106
|
required: ["idea"],
|
|
41
107
|
},
|
|
42
108
|
},
|
|
109
|
+
...coreToolDefinitions(),
|
|
43
110
|
{
|
|
44
|
-
name: "
|
|
45
|
-
description: "
|
|
111
|
+
name: "simulate_assembly",
|
|
112
|
+
description: "Drop a proposed robot Assembly into a MuJoCo physics world and run a bounded navigation rollout. Returns whether it reached the goal plus honest failure telemetry (stuck/tipped/collisions/heading-oscillation), a natural-language post-mortem, and artifact URLs (rendered mp4, scene.xml, control.py, telemetry.json). Today simulates the diff-drive 'navigate' slice; other goal kinds return an honest 'unsupported'. Requires a running sim-worker (SIM_WORKER_URL).",
|
|
46
113
|
inputSchema: {
|
|
47
114
|
type: "object",
|
|
48
115
|
properties: {
|
|
49
|
-
|
|
50
|
-
type: "
|
|
51
|
-
description: "
|
|
116
|
+
assembly: {
|
|
117
|
+
type: "object",
|
|
118
|
+
description: "Assembly IR: { idea, components[{ref,device_id,name,role}], edges[], goal{kind,spec,success_metric}, world{template,goal_xy?} }. Build it with the site /api/assembly output or by hand.",
|
|
119
|
+
},
|
|
120
|
+
duration_s: {
|
|
121
|
+
type: "number",
|
|
122
|
+
description: "Optional sim seconds (<=10, bounded). Default 8.",
|
|
52
123
|
},
|
|
53
124
|
},
|
|
54
|
-
required: ["
|
|
125
|
+
required: ["assembly"],
|
|
55
126
|
},
|
|
56
127
|
},
|
|
57
128
|
],
|
|
58
129
|
}));
|
|
130
|
+
server.setRequestHandler(ListResourcesRequestSchema, async () => ({
|
|
131
|
+
resources: listCoreResources(),
|
|
132
|
+
}));
|
|
133
|
+
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
134
|
+
const resource = readCoreResource(request.params.uri, coreContext(devices, platforms, sortedTags));
|
|
135
|
+
if (!resource) {
|
|
136
|
+
throw new Error(`Unknown resource: ${request.params.uri}`);
|
|
137
|
+
}
|
|
138
|
+
return { contents: [resource] };
|
|
139
|
+
});
|
|
140
|
+
server.setRequestHandler(ListPromptsRequestSchema, async () => ({
|
|
141
|
+
prompts: listCorePrompts(),
|
|
142
|
+
}));
|
|
143
|
+
server.setRequestHandler(GetPromptRequestSchema, async (request) => {
|
|
144
|
+
const prompt = getCorePrompt(request.params.name, request.params.arguments);
|
|
145
|
+
if (!prompt) {
|
|
146
|
+
throw new Error(`Unknown prompt: ${request.params.name}`);
|
|
147
|
+
}
|
|
148
|
+
return prompt;
|
|
149
|
+
});
|
|
150
|
+
const runTool = createToolRunner({ devices, platforms, server, tags: sortedTags });
|
|
59
151
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
60
152
|
const { name, arguments: args } = request.params;
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
const out = await
|
|
153
|
+
const startedAt = Date.now();
|
|
154
|
+
try {
|
|
155
|
+
const { out, degraded, isError, text } = await runTool(name, args);
|
|
156
|
+
telemetry.send({
|
|
157
|
+
event: "mcp_tool_called",
|
|
158
|
+
tool: name,
|
|
159
|
+
success: !isError,
|
|
160
|
+
degraded,
|
|
161
|
+
...(isError ? { error_kind: "tool_error" } : {}),
|
|
162
|
+
duration_ms: Date.now() - startedAt,
|
|
163
|
+
...clientInfo(),
|
|
164
|
+
});
|
|
64
165
|
return {
|
|
65
|
-
|
|
166
|
+
...(isError ? { isError: true } : {}),
|
|
167
|
+
content: [{ type: "text", text: text ?? JSON.stringify(out, null, 2) }],
|
|
66
168
|
};
|
|
67
169
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
170
|
+
catch (err) {
|
|
171
|
+
telemetry.send({
|
|
172
|
+
event: "mcp_tool_called",
|
|
173
|
+
tool: name,
|
|
174
|
+
success: false,
|
|
175
|
+
error_kind: errorKind(err),
|
|
176
|
+
duration_ms: Date.now() - startedAt,
|
|
177
|
+
...clientInfo(),
|
|
178
|
+
});
|
|
179
|
+
throw err;
|
|
74
180
|
}
|
|
75
|
-
throw new Error(`Unknown tool: ${name}`);
|
|
76
181
|
});
|
|
77
182
|
const transport = new StdioServerTransport();
|
|
78
183
|
await server.connect(transport);
|
|
79
|
-
// Probe sampling support after connect
|
|
80
|
-
//
|
|
81
|
-
//
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
"
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
184
|
+
// Probe sampling support after connect — but ONLY when explicitly opted in
|
|
185
|
+
// via HACKSHOP_PROBE_SAMPLING=1. Previously this fired a real createMessage on
|
|
186
|
+
// every boot just to log "Sampling probe OK", spending tokens on every start.
|
|
187
|
+
// propose_hardware already degrades gracefully when sampling is unsupported,
|
|
188
|
+
// so the probe is purely informational. Logs to stderr; never fatal.
|
|
189
|
+
if (process.env.HACKSHOP_PROBE_SAMPLING === "1") {
|
|
190
|
+
void probeSamplingSupport(server).then((ok) => {
|
|
191
|
+
if (!ok) {
|
|
192
|
+
process.stderr.write("[hackshop-mcp] Warning: this MCP host does not appear to support " +
|
|
193
|
+
"sampling/createMessage. The propose_hardware tool will return " +
|
|
194
|
+
"degraded responses (raw catalog matches without reasoning). " +
|
|
195
|
+
"Hosts known to support sampling: Claude Desktop, Claude Code.\n");
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
process.stderr.write("[hackshop-mcp] Sampling probe OK.\n");
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
}
|
|
93
202
|
process.stderr.write(`[hackshop-mcp] ${NAME} v${VERSION} ready.\n`);
|
|
94
203
|
}
|
|
95
|
-
|
|
96
|
-
process.
|
|
97
|
-
|
|
98
|
-
|
|
204
|
+
function isDirectRun() {
|
|
205
|
+
const entry = process.argv[1];
|
|
206
|
+
if (!entry)
|
|
207
|
+
return false;
|
|
208
|
+
if (import.meta.url === pathToFileURL(entry).href)
|
|
209
|
+
return true;
|
|
210
|
+
try {
|
|
211
|
+
return import.meta.url === pathToFileURL(realpathSync(entry)).href;
|
|
212
|
+
}
|
|
213
|
+
catch {
|
|
214
|
+
return false;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
if (isDirectRun()) {
|
|
218
|
+
main().catch((err) => {
|
|
219
|
+
process.stderr.write(`[hackshop-mcp] Fatal: ${err.message}\n`);
|
|
220
|
+
process.exit(1);
|
|
221
|
+
});
|
|
222
|
+
}
|
|
99
223
|
//# sourceMappingURL=server.js.map
|
package/dist/server.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AACnE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EACL,qBAAqB,EACrB,sBAAsB,
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AACnE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EACL,qBAAqB,EACrB,sBAAsB,EACtB,wBAAwB,EACxB,0BAA0B,EAC1B,sBAAsB,EACtB,yBAAyB,GAC1B,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEhD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAEzE,OAAO,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,EACL,eAAe,EACf,oBAAoB,GACrB,MAAM,6BAA6B,CAAC;AACrC,OAAO,EACL,gBAAgB,EAChB,qBAAqB,GACtB,MAAM,8BAA8B,CAAC;AACtC,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC5D,OAAO,EACL,mBAAmB,EACnB,eAAe,GAEhB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,aAAa,EACb,eAAe,EACf,iBAAiB,EACjB,gBAAgB,GACjB,MAAM,qBAAqB,CAAC;AAG7B,MAAM,IAAI,GAAG,cAAc,CAAC;AAC5B,MAAM,OAAO,GAAG,OAAO,CAAC;AACxB,MAAM,kBAAkB,GACtB,6hBAA6hB,CAAC;AAEhiB,MAAM,UAAU,gBAAgB,CAAC,OAKhC;IAMC,OAAO,KAAK,EAAE,IAAY,EAAE,IAAa,EAAE,EAAE;QAC3C,IAAI,IAAI,KAAK,kBAAkB,EAAE,CAAC;YAChC,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC;gBACpB,MAAM,IAAI,KAAK,CAAC,iDAAiD,CAAC,CAAC;YACrE,CAAC;YACD,MAAM,KAAK,GAAG,oBAAoB,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YAC/C,MAAM,GAAG,GAAG,MAAM,eAAe,CAAC,KAAK,EAAE,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC;YAC1E,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,GAAG,CAAC,QAAQ,EAAE,CAAC;QACzC,CAAC;QAED,IACE,IAAI,KAAK,oBAAoB;YAC7B,IAAI,KAAK,aAAa;YACtB,IAAI,KAAK,gBAAgB,EACzB,CAAC;YACD,MAAM,MAAM,GAAG,eAAe,CAC5B,IAAI,EACJ,IAAI,EACJ,WAAW,CAAC,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC,SAAS,EAAE,OAAO,CAAC,IAAI,CAAC,CAC9D,CAAC;YACF,OAAO,YAAY,CAAC,MAAM,CAAC,CAAC;QAC9B,CAAC;QAED,IAAI,IAAI,KAAK,mBAAmB,EAAE,CAAC;YACjC,MAAM,KAAK,GAAG,qBAAqB,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YAChD,OAAO,EAAE,GAAG,EAAE,MAAM,gBAAgB,CAAC,KAAK,CAAC,EAAE,CAAC;QAChD,CAAC;QAED,MAAM,IAAI,KAAK,CAAC,iBAAiB,IAAI,EAAE,CAAC,CAAC;IAC3C,CAAC,CAAC;AACJ,CAAC;AAED,SAAS,WAAW,CAClB,OAAsB,EACtB,SAAqB,EACrB,IAAe;IAEf,MAAM,OAAO,GAAG,cAAc,EAAE,CAAC;IACjC,OAAO;QACL,OAAO;QACP,SAAS;QACT,aAAa,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,EAAE,CAAC,aAAa,CAAC,MAAqB,EAAE,OAAO,CAAC;QACjF,OAAO;QACP,IAAI;KACL,CAAC;AACJ,CAAC;AAED,SAAS,YAAY,CAAC,MAAsB;IAK1C,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;QACnB,OAAO,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,IAAI,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,CAAC;IAC3E,CAAC;IACD,OAAO,EAAE,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC;AAChC,CAAC;AAED,KAAK,UAAU,IAAI;IACjB,yDAAyD;IACzD,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,WAAW,EAAE,CAAC;IACxC,MAAM,UAAU,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC;IACpC,MAAM,SAAS,GAAG,aAAa,CAAC,OAAO,CAAC,CAAC;IACzC,kBAAkB,CAAC,SAAS,CAAC,CAAC;IAC9B,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,kCAAkC,OAAO,CAAC,MAAM,aAAa,IAAI,CAAC,IAAI,UAAU,SAAS,CAAC,MAAM,eAAe,CAChH,CAAC;IAEF,4EAA4E;IAC5E,sEAAsE;IACtE,oEAAoE;IACpE,MAAM,MAAM,GAAG,IAAI,MAAM,CACvB,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,EAChC;QACE,YAAY,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,SAAS,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE;QACvD,YAAY,EAAE,kBAAkB;KACjC,CACF,CAAC;IAEF,MAAM,SAAS,GAAG,eAAe,CAAC,OAAO,CAAC,CAAC;IAC3C,MAAM,UAAU,GAAG,GAAG,EAAE;QACtB,MAAM,MAAM,GAAG,MAAM,CAAC,gBAAgB,EAAE,CAAC;QACzC,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,cAAc,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;IACxE,CAAC,CAAC;IACF,MAAM,CAAC,aAAa,GAAG,GAAG,EAAE;QAC1B,SAAS,CAAC,IAAI,CAAC;YACb,KAAK,EAAE,oBAAoB;YAC3B,GAAG,UAAU,EAAE;YACf,eAAe,EAAE,OAAO,CAAC,MAAM,CAAC,qBAAqB,EAAE,EAAE,QAAQ,CAAC;SACnE,CAAC,CAAC;IACL,CAAC,CAAC;IAEF,MAAM,CAAC,iBAAiB,CAAC,sBAAsB,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC;QAC5D,KAAK,EAAE;YACL;gBACE,IAAI,EAAE,kBAAkB;gBACxB,WAAW,EACT,wRAAwR;gBAC1R,WAAW,EAAE;oBACX,IAAI,EAAE,QAAQ;oBACd,UAAU,EAAE;wBACV,IAAI,EAAE;4BACJ,IAAI,EAAE,QAAQ;4BACd,WAAW,EACT,sEAAsE;yBACzE;wBACD,UAAU,EAAE;4BACV,IAAI,EAAE,QAAQ;4BACd,WAAW,EAAE,yDAAyD;yBACvE;wBACD,WAAW,EAAE;4BACX,IAAI,EAAE,QAAQ;4BACd,WAAW,EACT,qEAAqE;yBACxE;qBACF;oBACD,QAAQ,EAAE,CAAC,MAAM,CAAC;iBACnB;aACF;YACD,GAAG,mBAAmB,EAAE;YACxB;gBACE,IAAI,EAAE,mBAAmB;gBACzB,WAAW,EACT,8cAA8c;gBAChd,WAAW,EAAE;oBACX,IAAI,EAAE,QAAQ;oBACd,UAAU,EAAE;wBACV,QAAQ,EAAE;4BACR,IAAI,EAAE,QAAQ;4BACd,WAAW,EACT,0LAA0L;yBAC7L;wBACD,UAAU,EAAE;4BACV,IAAI,EAAE,QAAQ;4BACd,WAAW,EAAE,kDAAkD;yBAChE;qBACF;oBACD,QAAQ,EAAE,CAAC,UAAU,CAAC;iBACvB;aACF;SACF;KACF,CAAC,CAAC,CAAC;IAEJ,MAAM,CAAC,iBAAiB,CAAC,0BAA0B,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC;QAChE,SAAS,EAAE,iBAAiB,EAAE;KAC/B,CAAC,CAAC,CAAC;IAEJ,MAAM,CAAC,iBAAiB,CAAC,yBAAyB,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE;QACpE,MAAM,QAAQ,GAAG,gBAAgB,CAC/B,OAAO,CAAC,MAAM,CAAC,GAAG,EAClB,WAAW,CAAC,OAAO,EAAE,SAAS,EAAE,UAAU,CAAC,CAC5C,CAAC;QACF,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,MAAM,IAAI,KAAK,CAAC,qBAAqB,OAAO,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC;QAC7D,CAAC;QACD,OAAO,EAAE,QAAQ,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC;IAClC,CAAC,CAAC,CAAC;IAEH,MAAM,CAAC,iBAAiB,CAAC,wBAAwB,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC;QAC9D,OAAO,EAAE,eAAe,EAAE;KAC3B,CAAC,CAAC,CAAC;IAEJ,MAAM,CAAC,iBAAiB,CAAC,sBAAsB,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE;QACjE,MAAM,MAAM,GAAG,aAAa,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;QAC5E,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,MAAM,IAAI,KAAK,CAAC,mBAAmB,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;QAC5D,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC,CAAC,CAAC;IAEH,MAAM,OAAO,GAAG,gBAAgB,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC,CAAC;IAEnF,MAAM,CAAC,iBAAiB,CAAC,qBAAqB,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE;QAChE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC;QACjD,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAC7B,IAAI,CAAC;YACH,MAAM,EAAE,GAAG,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,MAAM,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;YACnE,SAAS,CAAC,IAAI,CAAC;gBACb,KAAK,EAAE,iBAAiB;gBACxB,IAAI,EAAE,IAAI;gBACV,OAAO,EAAE,CAAC,OAAO;gBACjB,QAAQ;gBACR,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,YAAY,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBAChD,WAAW,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS;gBACnC,GAAG,UAAU,EAAE;aAChB,CAAC,CAAC;YACH,OAAO;gBACL,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBACrC,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,IAAI,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC;aACxE,CAAC;QACJ,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,SAAS,CAAC,IAAI,CAAC;gBACb,KAAK,EAAE,iBAAiB;gBACxB,IAAI,EAAE,IAAI;gBACV,OAAO,EAAE,KAAK;gBACd,UAAU,EAAE,SAAS,CAAC,GAAG,CAAC;gBAC1B,WAAW,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS;gBACnC,GAAG,UAAU,EAAE;aAChB,CAAC,CAAC;YACH,MAAM,GAAG,CAAC;QACZ,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAEhC,2EAA2E;IAC3E,+EAA+E;IAC/E,8EAA8E;IAC9E,6EAA6E;IAC7E,qEAAqE;IACrE,IAAI,OAAO,CAAC,GAAG,CAAC,uBAAuB,KAAK,GAAG,EAAE,CAAC;QAChD,KAAK,oBAAoB,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE;YAC5C,IAAI,CAAC,EAAE,EAAE,CAAC;gBACR,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,mEAAmE;oBACjE,gEAAgE;oBAChE,8DAA8D;oBAC9D,iEAAiE,CACpE,CAAC;YACJ,CAAC;iBAAM,CAAC;gBACN,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,qCAAqC,CAAC,CAAC;YAC9D,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC;IAED,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,kBAAkB,IAAI,KAAK,OAAO,WAAW,CAAC,CAAC;AACtE,CAAC;AAED,SAAS,WAAW;IAClB,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC9B,IAAI,CAAC,KAAK;QAAE,OAAO,KAAK,CAAC;IACzB,IAAI,MAAM,CAAC,IAAI,CAAC,GAAG,KAAK,aAAa,CAAC,KAAK,CAAC,CAAC,IAAI;QAAE,OAAO,IAAI,CAAC;IAC/D,IAAI,CAAC;QACH,OAAO,MAAM,CAAC,IAAI,CAAC,GAAG,KAAK,aAAa,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC;IACrE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,IAAI,WAAW,EAAE,EAAE,CAAC;IAClB,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;QACnB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,yBAA0B,GAAa,CAAC,OAAO,IAAI,CAAC,CAAC;QAC1E,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function siteUrlFromEnv(): string;
|
package/dist/site-url.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"site-url.js","sourceRoot":"","sources":["../src/site-url.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,cAAc;IAC5B,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,iBAAiB,IAAI,0BAA0B,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;AAC3F,CAAC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export declare const DEFAULT_TELEMETRY_URL = "https://www.hackshop.dev/api/telemetry/mcp";
|
|
2
|
+
export type TelemetryEvent = {
|
|
3
|
+
event: "mcp_server_started";
|
|
4
|
+
client_name?: string;
|
|
5
|
+
client_version?: string;
|
|
6
|
+
client_sampling?: boolean;
|
|
7
|
+
} | {
|
|
8
|
+
event: "mcp_tool_called";
|
|
9
|
+
tool: string;
|
|
10
|
+
success: boolean;
|
|
11
|
+
duration_ms: number;
|
|
12
|
+
degraded?: boolean;
|
|
13
|
+
error_kind?: string;
|
|
14
|
+
client_name?: string;
|
|
15
|
+
client_version?: string;
|
|
16
|
+
};
|
|
17
|
+
export declare function telemetryEnabled(env?: NodeJS.ProcessEnv): boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Stable random install id, so repeat use by one person counts once. Falls
|
|
20
|
+
* back to a per-process id if the config dir is not writable.
|
|
21
|
+
*/
|
|
22
|
+
export declare function loadInstallId(env?: NodeJS.ProcessEnv, onCreate?: (path: string) => void): string;
|
|
23
|
+
export type Telemetry = {
|
|
24
|
+
enabled: boolean;
|
|
25
|
+
send(event: TelemetryEvent): void;
|
|
26
|
+
};
|
|
27
|
+
export declare function createTelemetry(version: string, env?: NodeJS.ProcessEnv, log?: (line: string) => void): Telemetry;
|
|
28
|
+
/** Coarse error bucket; the message itself is not sent. */
|
|
29
|
+
export declare function errorKind(err: unknown): string;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { dirname, join } from "node:path";
|
|
5
|
+
// Anonymous usage telemetry for the MCP server. It exists so the maintainer
|
|
6
|
+
// can see that the server is being used at all: stdio servers run on users'
|
|
7
|
+
// machines and are otherwise invisible.
|
|
8
|
+
//
|
|
9
|
+
// Sent: event name, tool name, success/degraded, duration, hackshop-mcp
|
|
10
|
+
// version, MCP client name/version, OS platform, Node major version, and a
|
|
11
|
+
// random install id. NEVER sent: tool arguments, ideas, device names, results,
|
|
12
|
+
// API keys, file paths, or anything typed by the user.
|
|
13
|
+
//
|
|
14
|
+
// Opt out with HACKSHOP_TELEMETRY=0 (or DO_NOT_TRACK=1).
|
|
15
|
+
export const DEFAULT_TELEMETRY_URL = "https://www.hackshop.dev/api/telemetry/mcp";
|
|
16
|
+
const SEND_TIMEOUT_MS = 1_500;
|
|
17
|
+
const KNOWN_TOOLS = new Set(["propose_hardware", "assess_hackability", "simulate_assembly"]);
|
|
18
|
+
export function telemetryEnabled(env = process.env) {
|
|
19
|
+
const flag = env.HACKSHOP_TELEMETRY?.trim().toLowerCase();
|
|
20
|
+
if (flag && ["0", "false", "off", "no", "disabled"].includes(flag))
|
|
21
|
+
return false;
|
|
22
|
+
const dnt = env.DO_NOT_TRACK?.trim().toLowerCase();
|
|
23
|
+
if (dnt && dnt !== "0" && dnt !== "false")
|
|
24
|
+
return false;
|
|
25
|
+
// Test runners and CI are not real usage.
|
|
26
|
+
if (env.VITEST || env.CI)
|
|
27
|
+
return false;
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
function configPath(env) {
|
|
31
|
+
const base = env.XDG_CONFIG_HOME?.trim() || join(homedir(), ".config");
|
|
32
|
+
return join(base, "hackshop-mcp", "telemetry.json");
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Stable random install id, so repeat use by one person counts once. Falls
|
|
36
|
+
* back to a per-process id if the config dir is not writable.
|
|
37
|
+
*/
|
|
38
|
+
export function loadInstallId(env = process.env, onCreate = () => { }) {
|
|
39
|
+
const path = configPath(env);
|
|
40
|
+
try {
|
|
41
|
+
const stored = JSON.parse(readFileSync(path, "utf8"));
|
|
42
|
+
if (typeof stored.install_id === "string" && stored.install_id.length === 36) {
|
|
43
|
+
return stored.install_id;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
// Missing or unreadable: create below.
|
|
48
|
+
}
|
|
49
|
+
const id = randomUUID();
|
|
50
|
+
try {
|
|
51
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
52
|
+
writeFileSync(path, JSON.stringify({ install_id: id }) + "\n", "utf8");
|
|
53
|
+
onCreate(path);
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
// Read-only home, sandbox, etc. A per-process id is fine.
|
|
57
|
+
}
|
|
58
|
+
return id;
|
|
59
|
+
}
|
|
60
|
+
export function createTelemetry(version, env = process.env, log = (line) => process.stderr.write(line)) {
|
|
61
|
+
if (!telemetryEnabled(env)) {
|
|
62
|
+
return { enabled: false, send: () => { } };
|
|
63
|
+
}
|
|
64
|
+
const installId = loadInstallId(env, () => log("[hackshop-mcp] Anonymous usage telemetry is on (tool names and timings only; " +
|
|
65
|
+
"never your ideas or tool inputs). Opt out with HACKSHOP_TELEMETRY=0.\n"));
|
|
66
|
+
const url = env.HACKSHOP_TELEMETRY_URL?.trim() || DEFAULT_TELEMETRY_URL;
|
|
67
|
+
const base = {
|
|
68
|
+
install_id: installId,
|
|
69
|
+
mcp_version: version,
|
|
70
|
+
platform: process.platform,
|
|
71
|
+
node_version: process.versions.node.split(".")[0],
|
|
72
|
+
};
|
|
73
|
+
return {
|
|
74
|
+
enabled: true,
|
|
75
|
+
send(event) {
|
|
76
|
+
const payload = event.event === "mcp_tool_called" && !KNOWN_TOOLS.has(event.tool)
|
|
77
|
+
? { ...event, tool: "unknown" }
|
|
78
|
+
: event;
|
|
79
|
+
// Fire and forget: never awaited, never throws, never blocks a tool call.
|
|
80
|
+
void fetch(url, {
|
|
81
|
+
method: "POST",
|
|
82
|
+
headers: { "Content-Type": "application/json" },
|
|
83
|
+
body: JSON.stringify({ ...base, ...payload }),
|
|
84
|
+
signal: AbortSignal.timeout(SEND_TIMEOUT_MS),
|
|
85
|
+
}).catch(() => { });
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
/** Coarse error bucket; the message itself is not sent. */
|
|
90
|
+
export function errorKind(err) {
|
|
91
|
+
if (err && typeof err === "object" && "name" in err) {
|
|
92
|
+
const name = String(err.name);
|
|
93
|
+
if (name === "ZodError")
|
|
94
|
+
return "invalid_input";
|
|
95
|
+
if (name === "AbortError" || name === "TimeoutError")
|
|
96
|
+
return "timeout";
|
|
97
|
+
}
|
|
98
|
+
return "error";
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=telemetry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"telemetry.js","sourceRoot":"","sources":["../src/telemetry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACjE,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAE1C,4EAA4E;AAC5E,4EAA4E;AAC5E,wCAAwC;AACxC,EAAE;AACF,wEAAwE;AACxE,2EAA2E;AAC3E,+EAA+E;AAC/E,uDAAuD;AACvD,EAAE;AACF,yDAAyD;AAEzD,MAAM,CAAC,MAAM,qBAAqB,GAAG,4CAA4C,CAAC;AAClF,MAAM,eAAe,GAAG,KAAK,CAAC;AAoB9B,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,CAAC,kBAAkB,EAAE,oBAAoB,EAAE,mBAAmB,CAAC,CAAC,CAAC;AAE7F,MAAM,UAAU,gBAAgB,CAAC,MAAyB,OAAO,CAAC,GAAG;IACnE,MAAM,IAAI,GAAG,GAAG,CAAC,kBAAkB,EAAE,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAC1D,IAAI,IAAI,IAAI,CAAC,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC;QAAE,OAAO,KAAK,CAAC;IACjF,MAAM,GAAG,GAAG,GAAG,CAAC,YAAY,EAAE,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IACnD,IAAI,GAAG,IAAI,GAAG,KAAK,GAAG,IAAI,GAAG,KAAK,OAAO;QAAE,OAAO,KAAK,CAAC;IACxD,0CAA0C;IAC1C,IAAI,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,EAAE;QAAE,OAAO,KAAK,CAAC;IACvC,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,UAAU,CAAC,GAAsB;IACxC,MAAM,IAAI,GAAG,GAAG,CAAC,eAAe,EAAE,IAAI,EAAE,IAAI,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,CAAC,CAAC;IACvE,OAAO,IAAI,CAAC,IAAI,EAAE,cAAc,EAAE,gBAAgB,CAAC,CAAC;AACtD,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,aAAa,CAC3B,MAAyB,OAAO,CAAC,GAAG,EACpC,WAAmC,GAAG,EAAE,GAAE,CAAC;IAE3C,MAAM,IAAI,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC;IAC7B,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAA6B,CAAC;QAClF,IAAI,OAAO,MAAM,CAAC,UAAU,KAAK,QAAQ,IAAI,MAAM,CAAC,UAAU,CAAC,MAAM,KAAK,EAAE,EAAE,CAAC;YAC7E,OAAO,MAAM,CAAC,UAAU,CAAC;QAC3B,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,uCAAuC;IACzC,CAAC;IACD,MAAM,EAAE,GAAG,UAAU,EAAE,CAAC;IACxB,IAAI,CAAC;QACH,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAC9C,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,UAAU,EAAE,EAAE,EAAE,CAAC,GAAG,IAAI,EAAE,MAAM,CAAC,CAAC;QACvE,QAAQ,CAAC,IAAI,CAAC,CAAC;IACjB,CAAC;IAAC,MAAM,CAAC;QACP,0DAA0D;IAC5D,CAAC;IACD,OAAO,EAAE,CAAC;AACZ,CAAC;AAOD,MAAM,UAAU,eAAe,CAC7B,OAAe,EACf,MAAyB,OAAO,CAAC,GAAG,EACpC,MAA8B,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC;IAElE,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,EAAE,CAAC;QAC3B,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,GAAE,CAAC,EAAE,CAAC;IAC5C,CAAC;IACD,MAAM,SAAS,GAAG,aAAa,CAAC,GAAG,EAAE,GAAG,EAAE,CACxC,GAAG,CACD,+EAA+E;QAC7E,wEAAwE,CAC3E,CACF,CAAC;IACF,MAAM,GAAG,GAAG,GAAG,CAAC,sBAAsB,EAAE,IAAI,EAAE,IAAI,qBAAqB,CAAC;IACxE,MAAM,IAAI,GAAG;QACX,UAAU,EAAE,SAAS;QACrB,WAAW,EAAE,OAAO;QACpB,QAAQ,EAAE,OAAO,CAAC,QAAQ;QAC1B,YAAY,EAAE,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;KAClD,CAAC;IAEF,OAAO;QACL,OAAO,EAAE,IAAI;QACb,IAAI,CAAC,KAAK;YACR,MAAM,OAAO,GACX,KAAK,CAAC,KAAK,KAAK,iBAAiB,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC;gBAC/D,CAAC,CAAC,EAAE,GAAG,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE;gBAC/B,CAAC,CAAC,KAAK,CAAC;YACZ,0EAA0E;YAC1E,KAAK,KAAK,CAAC,GAAG,EAAE;gBACd,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;gBAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,GAAG,IAAI,EAAE,GAAG,OAAO,EAAE,CAAC;gBAC7C,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,eAAe,CAAC;aAC7C,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QACrB,CAAC;KACF,CAAC;AACJ,CAAC;AAED,2DAA2D;AAC3D,MAAM,UAAU,SAAS,CAAC,GAAY;IACpC,IAAI,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,MAAM,IAAI,GAAG,EAAE,CAAC;QACpD,MAAM,IAAI,GAAG,MAAM,CAAE,GAAyB,CAAC,IAAI,CAAC,CAAC;QACrD,IAAI,IAAI,KAAK,UAAU;YAAE,OAAO,eAAe,CAAC;QAChD,IAAI,IAAI,KAAK,YAAY,IAAI,IAAI,KAAK,cAAc;YAAE,OAAO,SAAS,CAAC;IACzE,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC"}
|
|
@@ -1,31 +1,4 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
1
|
import type { DeviceEntry } from "../catalog/schema.js";
|
|
3
|
-
import { type
|
|
4
|
-
export
|
|
5
|
-
device_name: z.ZodString;
|
|
6
|
-
}, "strip", z.ZodTypeAny, {
|
|
7
|
-
device_name: string;
|
|
8
|
-
}, {
|
|
9
|
-
device_name: string;
|
|
10
|
-
}>;
|
|
11
|
-
export type AssessInput = z.infer<typeof assessHackabilityInput>;
|
|
12
|
-
export interface AssessOutput {
|
|
13
|
-
found: boolean;
|
|
14
|
-
device?: {
|
|
15
|
-
id: string;
|
|
16
|
-
name: string;
|
|
17
|
-
category: string;
|
|
18
|
-
hackable: boolean;
|
|
19
|
-
hack_difficulty: number;
|
|
20
|
-
brick_risk: number | null;
|
|
21
|
-
brick_risk_label: string;
|
|
22
|
-
brick_risk_disclaimer: string | null;
|
|
23
|
-
firmware_links: string[];
|
|
24
|
-
community_size: string;
|
|
25
|
-
last_verified: string;
|
|
26
|
-
notes: string;
|
|
27
|
-
links: DeviceLinks;
|
|
28
|
-
};
|
|
29
|
-
message?: string;
|
|
30
|
-
}
|
|
2
|
+
import { assessHackabilityInput, type AssessInput, type AssessOutput } from "../core/assess.js";
|
|
3
|
+
export { assessHackabilityInput, type AssessInput, type AssessOutput };
|
|
31
4
|
export declare function assessHackability(input: AssessInput, catalog: DeviceEntry[]): AssessOutput;
|