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.
@@ -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
+ }