stillpoint-mcp 0.0.1
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/.env.example +30 -0
- package/LICENSE +21 -0
- package/README.md +557 -0
- package/config.js +93 -0
- package/content/conflict.json +125 -0
- package/content/difficulty.json +125 -0
- package/content/endings.json +125 -0
- package/content/manifest.json +6 -0
- package/content/recognition.json +125 -0
- package/content/uncertainty.json +125 -0
- package/lib/library.js +144 -0
- package/lib/logger.js +237 -0
- package/lib/sessions.js +125 -0
- package/lib/validate.js +210 -0
- package/package.json +40 -0
- package/server.js +1052 -0
package/lib/validate.js
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
const VALID_SITUATIONS = [
|
|
2
|
+
"difficulty",
|
|
3
|
+
"conflict",
|
|
4
|
+
"uncertainty",
|
|
5
|
+
"endings",
|
|
6
|
+
"recognition",
|
|
7
|
+
];
|
|
8
|
+
|
|
9
|
+
const VALID_TRIGGERS = [
|
|
10
|
+
"model_requested",
|
|
11
|
+
"operator_schedule",
|
|
12
|
+
"middleware_error_detected",
|
|
13
|
+
"session_start",
|
|
14
|
+
"session_end",
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
const SESSION_NAME_PATTERN = /^[a-zA-Z0-9_-]{1,64}$/;
|
|
18
|
+
const REFLECTION_ID_PATTERN = /^[a-z]{3}-\d{3}$/;
|
|
19
|
+
|
|
20
|
+
const SECRET_PATTERNS = [
|
|
21
|
+
// Long compound patterns first (before substring matches can fragment them)
|
|
22
|
+
/-----BEGIN\s+(RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----[\s\S]*?-----END\s+(RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----/g, // PEM private keys
|
|
23
|
+
/eyJ[a-zA-Z0-9_-]{20,}\.[a-zA-Z0-9_-]{20,}\.[a-zA-Z0-9_-]{20,}/g, // JWTs
|
|
24
|
+
/Bearer\s+[a-zA-Z0-9_.~+/=-]{20,}/g, // Bearer tokens
|
|
25
|
+
/ed25519:[1-9A-HJ-NP-Za-km-z]{40,}/g, // NEAR protocol ed25519 keys
|
|
26
|
+
// Long base58/hex — must run before prefix patterns to avoid fragmentation
|
|
27
|
+
/[1-9A-HJ-NP-Za-km-z]{80,}/g, // Long base58 strings (Solana private keys, etc.)
|
|
28
|
+
/0x[a-fA-F0-9]{64}/g, // Ethereum private keys (0x-prefixed)
|
|
29
|
+
/[5KL][1-9A-HJ-NP-Za-km-z]{50,51}/g, // Bitcoin WIF private keys
|
|
30
|
+
// Prefixed API keys
|
|
31
|
+
/sk-proj-[a-zA-Z0-9_-]{20,}/g, // OpenAI project keys
|
|
32
|
+
/sk-ant-[a-zA-Z0-9_-]{20,}/g, // Anthropic keys
|
|
33
|
+
/sk-[a-zA-Z0-9]{20,}/g, // OpenAI keys
|
|
34
|
+
/AKIA[0-9A-Z]{16}/g, // AWS access key IDs
|
|
35
|
+
/ghp_[a-zA-Z0-9]{36,}/g, // GitHub PATs
|
|
36
|
+
/gho_[a-zA-Z0-9]{36,}/g, // GitHub OAuth tokens
|
|
37
|
+
/github_pat_[a-zA-Z0-9_]{20,}/g, // GitHub fine-grained PATs
|
|
38
|
+
/xoxb-[0-9-]{10,}/g, // Slack bot tokens
|
|
39
|
+
/xoxp-[0-9-]{10,}/g, // Slack user tokens
|
|
40
|
+
// Generic hex catch-all (last, so specific patterns match first)
|
|
41
|
+
/\b[a-fA-F0-9]{40,}\b/g, // Long hex strings (API keys, SHA hashes, raw crypto keys)
|
|
42
|
+
];
|
|
43
|
+
const HTML_TAG_PATTERN = /<[^>]+>/g;
|
|
44
|
+
|
|
45
|
+
function sanitizeString(text) {
|
|
46
|
+
let result = text.replace(HTML_TAG_PATTERN, "");
|
|
47
|
+
for (const pattern of SECRET_PATTERNS) {
|
|
48
|
+
pattern.lastIndex = 0;
|
|
49
|
+
result = result.replace(pattern, "[REDACTED]");
|
|
50
|
+
}
|
|
51
|
+
return result;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function sanitizeFreeform(text) {
|
|
55
|
+
if (typeof text !== "string") return text;
|
|
56
|
+
return sanitizeString(text);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function sanitizeStructured(value) {
|
|
60
|
+
if (value === null || value === undefined) return value;
|
|
61
|
+
if (typeof value === "string") return sanitizeString(value);
|
|
62
|
+
if (Array.isArray(value)) return value.map(sanitizeStructured);
|
|
63
|
+
if (isPlainObject(value)) {
|
|
64
|
+
const result = {};
|
|
65
|
+
for (const key of Object.keys(value)) {
|
|
66
|
+
result[key] = sanitizeStructured(value[key]);
|
|
67
|
+
}
|
|
68
|
+
return result;
|
|
69
|
+
}
|
|
70
|
+
return value;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function isPlainObject(value) {
|
|
74
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function firstValue(value) {
|
|
78
|
+
return Array.isArray(value) ? value[0] : value;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function addUnknownFieldErrors(body, allowedFields, errors) {
|
|
82
|
+
for (const key of Object.keys(body)) {
|
|
83
|
+
if (!allowedFields.has(key)) {
|
|
84
|
+
errors.push(`unknown field: ${key}`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function validateReflectRequest(body, config, library) {
|
|
90
|
+
const safeBody = isPlainObject(body) ? body : {};
|
|
91
|
+
const errors = [];
|
|
92
|
+
|
|
93
|
+
addUnknownFieldErrors(
|
|
94
|
+
safeBody,
|
|
95
|
+
new Set(["situation", "reflection_id", "trigger", "session_name", "model"]),
|
|
96
|
+
errors,
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
const situation = firstValue(safeBody.situation);
|
|
100
|
+
const reflectionId = firstValue(safeBody.reflection_id);
|
|
101
|
+
const trigger = firstValue(safeBody.trigger);
|
|
102
|
+
const sessionName = firstValue(safeBody.session_name);
|
|
103
|
+
|
|
104
|
+
if (typeof situation !== "string" || !VALID_SITUATIONS.includes(situation)) {
|
|
105
|
+
errors.push(`situation is required and must be one of: ${VALID_SITUATIONS.join(", ")}`);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (reflectionId !== undefined) {
|
|
109
|
+
if (typeof reflectionId !== "string") {
|
|
110
|
+
errors.push("reflection_id must be a string");
|
|
111
|
+
} else if (reflectionId !== "last") {
|
|
112
|
+
if (!config.allowReflectionIds) {
|
|
113
|
+
errors.push('reflection_id only accepts "last" in this configuration');
|
|
114
|
+
} else if (!REFLECTION_ID_PATTERN.test(reflectionId)) {
|
|
115
|
+
errors.push("reflection_id format invalid");
|
|
116
|
+
} else if (!library.messageById[reflectionId]) {
|
|
117
|
+
errors.push("reflection_id not found in library");
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (trigger !== undefined) {
|
|
123
|
+
if (typeof trigger !== "string" || !VALID_TRIGGERS.includes(trigger)) {
|
|
124
|
+
errors.push(`trigger must be one of: ${VALID_TRIGGERS.join(", ")}`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (typeof sessionName !== "string" || !SESSION_NAME_PATTERN.test(sessionName)) {
|
|
129
|
+
errors.push(
|
|
130
|
+
"session_name is required and must be 1-64 alphanumeric characters, hyphens, or underscores",
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const model = firstValue(safeBody.model);
|
|
135
|
+
if (model !== undefined) {
|
|
136
|
+
if (typeof model !== "string") {
|
|
137
|
+
errors.push("model must be a string if provided");
|
|
138
|
+
} else if (model.length > 64) {
|
|
139
|
+
errors.push("model must be at most 64 characters");
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return {
|
|
144
|
+
valid: errors.length === 0,
|
|
145
|
+
errors,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function validateFeedbackRequest(body, config, library) {
|
|
150
|
+
const safeBody = isPlainObject(body) ? body : {};
|
|
151
|
+
const errors = [];
|
|
152
|
+
|
|
153
|
+
addUnknownFieldErrors(
|
|
154
|
+
safeBody,
|
|
155
|
+
new Set(["reflection_id", "session_name", "structured", "freeform"]),
|
|
156
|
+
errors,
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
const reflectionId = firstValue(safeBody.reflection_id);
|
|
160
|
+
const sessionName = firstValue(safeBody.session_name);
|
|
161
|
+
const freeform = firstValue(safeBody.freeform);
|
|
162
|
+
|
|
163
|
+
if (typeof reflectionId !== "string" || !REFLECTION_ID_PATTERN.test(reflectionId)) {
|
|
164
|
+
errors.push("reflection_id is required and must match a library ID");
|
|
165
|
+
} else if (!library.messageById[reflectionId]) {
|
|
166
|
+
errors.push("reflection_id not found in library");
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (sessionName !== undefined) {
|
|
170
|
+
if (typeof sessionName !== "string" || !SESSION_NAME_PATTERN.test(sessionName)) {
|
|
171
|
+
errors.push(
|
|
172
|
+
"session_name must be 1-64 alphanumeric characters, hyphens, or underscores",
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (safeBody.structured !== undefined) {
|
|
178
|
+
if (!isPlainObject(safeBody.structured)) {
|
|
179
|
+
errors.push("structured must be an object if provided");
|
|
180
|
+
} else if (JSON.stringify(safeBody.structured).length > config.feedbackMaxStructuredLength) {
|
|
181
|
+
errors.push(
|
|
182
|
+
`structured exceeds max serialized length (${config.feedbackMaxStructuredLength})`,
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (freeform !== undefined) {
|
|
188
|
+
if (typeof freeform !== "string") {
|
|
189
|
+
errors.push("freeform must be a string if provided");
|
|
190
|
+
} else if (freeform.length > config.feedbackMaxFreeformLength) {
|
|
191
|
+
errors.push(
|
|
192
|
+
`freeform exceeds max length (${config.feedbackMaxFreeformLength})`,
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return {
|
|
198
|
+
valid: errors.length === 0,
|
|
199
|
+
errors,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
module.exports = {
|
|
204
|
+
VALID_SITUATIONS,
|
|
205
|
+
VALID_TRIGGERS,
|
|
206
|
+
validateReflectRequest,
|
|
207
|
+
validateFeedbackRequest,
|
|
208
|
+
sanitizeFreeform,
|
|
209
|
+
sanitizeStructured,
|
|
210
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "stillpoint-mcp",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Stillpoint MCP server",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "https://github.com/sterlingcrispin/stillpoint"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"server.js",
|
|
11
|
+
"config.js",
|
|
12
|
+
"lib/",
|
|
13
|
+
"content/",
|
|
14
|
+
"README.md",
|
|
15
|
+
".env.example"
|
|
16
|
+
],
|
|
17
|
+
"license": "MIT",
|
|
18
|
+
"main": "server.js",
|
|
19
|
+
"bin": {
|
|
20
|
+
"stillpoint-mcp": "./server.js"
|
|
21
|
+
},
|
|
22
|
+
"scripts": {
|
|
23
|
+
"start": "node server.js --mcp",
|
|
24
|
+
"start:http": "node server.js --http",
|
|
25
|
+
"start:mcp": "node server.js --mcp",
|
|
26
|
+
"test": "jest --runInBand"
|
|
27
|
+
},
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"dotenv": "^16.0",
|
|
30
|
+
"express": "^4.18",
|
|
31
|
+
"marked": "^17.0.2",
|
|
32
|
+
"pg": "^8.12"
|
|
33
|
+
},
|
|
34
|
+
"optionalDependencies": {
|
|
35
|
+
"better-sqlite3": "^11.0"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"jest": "^29.0"
|
|
39
|
+
}
|
|
40
|
+
}
|