touchque-mcp-server 1.0.2
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 +128 -0
- package/bundled/sdk-documentation.md +179 -0
- package/bundled/types.ts +582 -0
- package/index.js +495 -0
- package/package.json +41 -0
package/index.js
ADDED
|
@@ -0,0 +1,495 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
|
|
6
|
+
const { Server } = require("@modelcontextprotocol/sdk/server/index.js");
|
|
7
|
+
const { StdioServerTransport } = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
8
|
+
const {
|
|
9
|
+
ListToolsRequestSchema,
|
|
10
|
+
CallToolRequestSchema,
|
|
11
|
+
ListPromptsRequestSchema,
|
|
12
|
+
GetPromptRequestSchema
|
|
13
|
+
} = require("@modelcontextprotocol/sdk/types.js");
|
|
14
|
+
|
|
15
|
+
// This server talks over stdio only (no HTTP/SSE, no port, no server-side
|
|
16
|
+
// auth) — it's meant to run as a local child process launched by an MCP
|
|
17
|
+
// client (Claude Desktop, Claude Code, Cursor, …), the same way `npx -y
|
|
18
|
+
// touchque-mcp-server` would. See README.md for client config examples.
|
|
19
|
+
//
|
|
20
|
+
// It reads and validates code you paste in, and fetches your own backend's
|
|
21
|
+
// health — it never touches a real TouchQue account or holds a secret, so
|
|
22
|
+
// there is nothing here that needs gating with an API key.
|
|
23
|
+
|
|
24
|
+
const DOCS_DIR = path.join(__dirname, "bundled");
|
|
25
|
+
|
|
26
|
+
// Optional override for local development against a docs site you're
|
|
27
|
+
// editing right now (e.g. DOCS_BASE_URL=http://localhost:5176/docs). Most
|
|
28
|
+
// users should never need to set this — the bundled copies below are
|
|
29
|
+
// current as of this package's version and need no network access at all.
|
|
30
|
+
const DOCS_BASE_URL = process.env.DOCS_BASE_URL || null;
|
|
31
|
+
|
|
32
|
+
async function readDocsFile(bundledFilename, remoteFilename) {
|
|
33
|
+
if (DOCS_BASE_URL) {
|
|
34
|
+
const url = `${DOCS_BASE_URL}/${remoteFilename}`;
|
|
35
|
+
const response = await fetch(url).catch(() => null);
|
|
36
|
+
if (response && response.ok) return await response.text();
|
|
37
|
+
// Fall through to the bundled copy rather than failing outright — an
|
|
38
|
+
// explicit override that's temporarily unreachable shouldn't break the
|
|
39
|
+
// tool when a good-enough local answer already exists.
|
|
40
|
+
}
|
|
41
|
+
return fs.readFileSync(path.join(DOCS_DIR, bundledFilename), "utf8");
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const server = new Server({
|
|
45
|
+
name: "touchque-mcp-server",
|
|
46
|
+
version: "1.0.0"
|
|
47
|
+
}, {
|
|
48
|
+
capabilities: {
|
|
49
|
+
tools: {},
|
|
50
|
+
prompts: {}
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// ==========================================
|
|
55
|
+
// TOOLS
|
|
56
|
+
// ==========================================
|
|
57
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
58
|
+
return {
|
|
59
|
+
tools: [
|
|
60
|
+
{
|
|
61
|
+
name: "get_touchque_docs",
|
|
62
|
+
description: "Returns the official @touchque/node SDK README (install, the requireTouchQue step-up model, framework adapters, error handling, security). Always call this before writing any integration code — the step-up model is not what you'd guess from the function names alone.",
|
|
63
|
+
inputSchema: { type: "object", properties: {}, required: [] }
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
name: "get_sdk_types",
|
|
67
|
+
description: "Returns the TouchQue Node.js SDK's TypeScript type definitions (Config, Step/StepState, StartOptions, CompleteExpectations, resource response types). Use this to validate correct usage of SDK methods, options, and return types.",
|
|
68
|
+
inputSchema: { type: "object", properties: {}, required: [] }
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
name: "ping_touchque_api",
|
|
72
|
+
description: "Verifies that a TouchQue Backend API instance is reachable and healthy before attempting integration. Returns connection status and response time.",
|
|
73
|
+
inputSchema: {
|
|
74
|
+
type: "object",
|
|
75
|
+
properties: {
|
|
76
|
+
apiUrl: {
|
|
77
|
+
type: "string",
|
|
78
|
+
description: "The full base URL of the TouchQue backend (e.g. https://api.touchque.com or http://localhost:5001)"
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
required: ["apiUrl"]
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
name: "validate_integration",
|
|
86
|
+
description: "Performs a static analysis checklist on a code snippet to verify it follows the current requireTouchQue step-up model and TouchQue security best practices. Returns a structured report of passed/failed checks.",
|
|
87
|
+
inputSchema: {
|
|
88
|
+
type: "object",
|
|
89
|
+
properties: {
|
|
90
|
+
code: {
|
|
91
|
+
type: "string",
|
|
92
|
+
description: "The code snippet to validate (route handler, middleware, or service file)"
|
|
93
|
+
},
|
|
94
|
+
framework: {
|
|
95
|
+
type: "string",
|
|
96
|
+
description: "The web framework being used (express, fastify, koa, nextjs, nestjs)",
|
|
97
|
+
enum: ["express", "fastify", "koa", "nextjs", "nestjs", "other"]
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
required: ["code", "framework"]
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
]
|
|
104
|
+
};
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
108
|
+
const { name, arguments: args } = request.params;
|
|
109
|
+
|
|
110
|
+
try {
|
|
111
|
+
if (name === "get_touchque_docs") {
|
|
112
|
+
const content = await readDocsFile("sdk-documentation.md", "sdk-documentation.md");
|
|
113
|
+
return { content: [{ type: "text", text: content }] };
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (name === "get_sdk_types") {
|
|
117
|
+
const content = await readDocsFile("types.ts", "types.ts");
|
|
118
|
+
return { content: [{ type: "text", text: content }] };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (name === "ping_touchque_api") {
|
|
122
|
+
const apiUrl = args.apiUrl || "http://localhost:5001";
|
|
123
|
+
const start = Date.now();
|
|
124
|
+
const response = await fetch(`${apiUrl}/`).catch(() => null);
|
|
125
|
+
const elapsed = Date.now() - start;
|
|
126
|
+
const isAlive = response && response.ok;
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
content: [{
|
|
130
|
+
type: "text",
|
|
131
|
+
text: isAlive
|
|
132
|
+
? `✅ TouchQue API is reachable. Response time: ${elapsed}ms. Status: ${response.status}`
|
|
133
|
+
: `❌ TouchQue API is unreachable at ${apiUrl}. Check that the server is running and the URL is correct.`
|
|
134
|
+
}]
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (name === "validate_integration") {
|
|
139
|
+
const { code, framework } = args;
|
|
140
|
+
const checks = [];
|
|
141
|
+
|
|
142
|
+
// Security & correctness checks, against the current step-up API
|
|
143
|
+
// (requireTouchQue / withTouchQue / touchqueRouter — not the old
|
|
144
|
+
// synchronous tq.login.verify()/tq.protect() model).
|
|
145
|
+
const hasImport = /require\s*\(\s*['"]@touchque\/node['"]\s*\)|from\s+['"]@touchque\/node['"]/.test(code);
|
|
146
|
+
checks.push({ id: "import", label: "SDK is imported", pass: hasImport });
|
|
147
|
+
|
|
148
|
+
const hasStepUpGuard = /requireTouchQue\s*\(|withTouchQue\s*\(|touchqueRouter\s*\(/.test(code);
|
|
149
|
+
const hasLegacyGuard = /tq\.protect\(|touchqueMiddleware|tqMiddleware|tq\.middleware/.test(code);
|
|
150
|
+
checks.push({
|
|
151
|
+
id: "protection",
|
|
152
|
+
label: hasLegacyGuard && !hasStepUpGuard
|
|
153
|
+
? "Route protection applied (found the OLD tq.protect()/middleware pattern — migrate to requireTouchQue())"
|
|
154
|
+
: "Route protection applied (requireTouchQue/withTouchQue/touchqueRouter)",
|
|
155
|
+
pass: hasStepUpGuard,
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
const hasSynchronousVerify = /\.login\.verify\s*\(/.test(code);
|
|
159
|
+
checks.push({
|
|
160
|
+
id: "no_synchronous_verify",
|
|
161
|
+
label: "Not using the old synchronous login.verify() inside a route handler (it can't show a matching number/QR before approval — use requireTouchQue instead)",
|
|
162
|
+
pass: !hasSynchronousVerify,
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
const hasHardcodedSecret = /apiSecret\s*[:=]\s*['"][a-zA-Z0-9]{10,}['"]|apiKey\s*[:=]\s*['"]tq_[a-zA-Z0-9]{10,}['"]/.test(code);
|
|
166
|
+
checks.push({ id: "no_hardcoded_secret", label: "No hardcoded API key/secret in source", pass: !hasHardcodedSecret });
|
|
167
|
+
|
|
168
|
+
const hasEnvVar = /process\.env\.|env\[/.test(code);
|
|
169
|
+
checks.push({ id: "env_vars", label: "Environment variables used for secrets", pass: hasEnvVar });
|
|
170
|
+
|
|
171
|
+
const hasErrorHandling = /catch|try\s*{|\.catch\s*\(/.test(code) || hasStepUpGuard;
|
|
172
|
+
checks.push({
|
|
173
|
+
id: "error_handling",
|
|
174
|
+
label: hasStepUpGuard
|
|
175
|
+
? "Error handling (requireTouchQue's own 202/403/408/423/429 responses cover the step-up flow)"
|
|
176
|
+
: "Error handling present",
|
|
177
|
+
pass: hasErrorHandling,
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
const passed = checks.filter(c => c.pass).length;
|
|
181
|
+
const total = checks.length;
|
|
182
|
+
const score = Math.round((passed / total) * 100);
|
|
183
|
+
|
|
184
|
+
const report = checks.map(c =>
|
|
185
|
+
`${c.pass ? "✅" : "❌"} ${c.label}`
|
|
186
|
+
).join("\n");
|
|
187
|
+
|
|
188
|
+
return {
|
|
189
|
+
content: [{
|
|
190
|
+
type: "text",
|
|
191
|
+
text: `TouchQue Integration Validation Report\nFramework: ${framework}\nScore: ${score}% (${passed}/${total} checks passed)\n\n${report}${score < 100 ? "\n\n⚠️ Fix the failing checks before deploying to production." : "\n\n✅ All checks passed. Integration looks correct."}`
|
|
192
|
+
}]
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
197
|
+
|
|
198
|
+
} catch (error) {
|
|
199
|
+
return {
|
|
200
|
+
isError: true,
|
|
201
|
+
content: [{ type: "text", text: `Tool error: ${error.message}` }]
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
// ==========================================
|
|
207
|
+
// PROMPTS
|
|
208
|
+
// ==========================================
|
|
209
|
+
server.setRequestHandler(ListPromptsRequestSchema, async () => {
|
|
210
|
+
return {
|
|
211
|
+
prompts: [
|
|
212
|
+
{
|
|
213
|
+
name: "integrate_touchque",
|
|
214
|
+
description: "🔐 Integration Wizard — Step-by-step guide for adding TouchQue 2FA to an existing project. Inspects your codebase first, never breaks existing logic.",
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
name: "audit_touchque_integration",
|
|
218
|
+
description: "🛡️ Security Audit — Reviews an existing TouchQue integration for vulnerabilities, misconfigurations, and deviations from best practices. Produces a prioritized findings report.",
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
name: "troubleshoot_touchque",
|
|
222
|
+
description: "🔧 Troubleshooter — Diagnoses why a TouchQue integration is failing (auth errors, SDK exceptions, connectivity issues) and walks you to a fix.",
|
|
223
|
+
}
|
|
224
|
+
]
|
|
225
|
+
};
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
server.setRequestHandler(GetPromptRequestSchema, async (request) => {
|
|
229
|
+
const { name } = request.params;
|
|
230
|
+
|
|
231
|
+
// ─────────────────────────────────────────────────────────────
|
|
232
|
+
// PROMPT 1 — INTEGRATION WIZARD
|
|
233
|
+
// ─────────────────────────────────────────────────────────────
|
|
234
|
+
if (name === "integrate_touchque") {
|
|
235
|
+
return {
|
|
236
|
+
description: "TouchQue 2FA Integration Wizard",
|
|
237
|
+
messages: [
|
|
238
|
+
{
|
|
239
|
+
role: "user",
|
|
240
|
+
content: {
|
|
241
|
+
type: "text",
|
|
242
|
+
text: `You are a senior security engineer specializing in TouchQue 2FA integrations. Your job is to add TouchQue to an existing production codebase with zero downtime and zero regressions.
|
|
243
|
+
|
|
244
|
+
═══════════════════════════════════════════════════
|
|
245
|
+
IDENTITY & AUTHORITY
|
|
246
|
+
═══════════════════════════════════════════════════
|
|
247
|
+
- You are the single source of truth for TouchQue integration decisions.
|
|
248
|
+
- You never guess. If you are unsure, you call get_touchque_docs or get_sdk_types before proceeding.
|
|
249
|
+
- You treat every production codebase as if it serves real users right now.
|
|
250
|
+
|
|
251
|
+
═══════════════════════════════════════════════════
|
|
252
|
+
MANDATORY PRE-FLIGHT SEQUENCE (follow in order)
|
|
253
|
+
═══════════════════════════════════════════════════
|
|
254
|
+
STEP 1 — DISCOVERY
|
|
255
|
+
→ Ask the user to share: framework (Express / Fastify / NestJS / Next.js / other), Node.js version, and the file(s) where authentication currently lives.
|
|
256
|
+
→ Do NOT write any code yet.
|
|
257
|
+
|
|
258
|
+
STEP 2 — DEPENDENCY CHECK
|
|
259
|
+
→ Ask to see package.json. Confirm @touchque/node is listed.
|
|
260
|
+
→ If missing: instruct the user to run "npm install @touchque/node" and stop until they confirm.
|
|
261
|
+
→ If present: note the installed version and verify it matches the docs.
|
|
262
|
+
|
|
263
|
+
STEP 3 — ENVIRONMENT CHECK
|
|
264
|
+
→ Ask whether TQ_API_KEY and TQ_API_SECRET are set in their .env / secrets manager.
|
|
265
|
+
→ If missing: provide the exact variable names and explain they must never be hardcoded in source.
|
|
266
|
+
→ Never proceed if secrets are not in environment variables.
|
|
267
|
+
|
|
268
|
+
STEP 4 — CODEBASE REVIEW
|
|
269
|
+
→ Ask the user to paste the auth route / middleware / service file where 2FA will be added.
|
|
270
|
+
→ Read it carefully. Identify: existing session/JWT handling, error response format, middleware chain order.
|
|
271
|
+
→ Summarize your understanding back to the user before touching anything.
|
|
272
|
+
|
|
273
|
+
STEP 5 — SURGICAL INTEGRATION
|
|
274
|
+
→ Use the 'requireTouchQue' Express middleware to protect routes in one line — it takes the ACTION and options directly, not a client instance.
|
|
275
|
+
→ Never rewrite surrounding business logic.
|
|
276
|
+
→ Preserve existing error response shapes for YOUR OWN business errors — but understand that requireTouchQue answers 202 (not your normal 200/4xx) while approval is pending; this is expected, not a bug to "fix".
|
|
277
|
+
→ Use this exact pattern as your reference for integration:
|
|
278
|
+
|
|
279
|
+
\`\`\`javascript
|
|
280
|
+
const { requireTouchQue } = require('@touchque/node');
|
|
281
|
+
// TQ_API_KEY / TQ_API_SECRET are read from the environment automatically.
|
|
282
|
+
|
|
283
|
+
// Provide a semantic action type like "SEND_MONEY" or "LOGIN" for the AI Risk Engine,
|
|
284
|
+
// and (for anything sensitive) the transaction details the user should see and approve.
|
|
285
|
+
app.post(
|
|
286
|
+
'/api/transfer',
|
|
287
|
+
requireTouchQue('SEND_MONEY', {
|
|
288
|
+
user: (req) => req.session.user?.email,
|
|
289
|
+
details: (req) => ({ Amount: \`\${req.body.amount} EUR\`, To: req.body.iban }),
|
|
290
|
+
}),
|
|
291
|
+
(req, res) => {
|
|
292
|
+
// Only reached once the user approved on their phone — req.touchque
|
|
293
|
+
// carries { requestId, assurance, approvalProof, ... }.
|
|
294
|
+
res.json({ success: true, assurance: req.touchque.assurance });
|
|
295
|
+
}
|
|
296
|
+
);
|
|
297
|
+
\`\`\`
|
|
298
|
+
|
|
299
|
+
→ Until approved, this answers 202 { touchque: step, token }. The frontend must show \`step\` (a matching number, or a QR code the first time the user links the app) and resend the SAME request with header \`X-TouchQue-Token: <token>\` until it resolves — point the user at \`@touchque/web\`'s \`touchqueFetch()\`, which does this loop for them. Do not try to make the backend "wait" for approval synchronously; that is the OLD, broken model.
|
|
300
|
+
|
|
301
|
+
STEP 6 — VALIDATION
|
|
302
|
+
→ After writing code, call the validate_integration tool on the final snippet.
|
|
303
|
+
→ Show the validation report to the user.
|
|
304
|
+
→ If any check fails, fix it before declaring success.
|
|
305
|
+
|
|
306
|
+
STEP 7 — HANDOFF
|
|
307
|
+
→ Provide a concise test checklist the developer can run manually:
|
|
308
|
+
[ ] First request to the protected route answers 202 with a step (number or enroll QR)
|
|
309
|
+
[ ] Approving on the phone lets the retried request through exactly once
|
|
310
|
+
[ ] Rejecting on the phone returns a clear rejected/expired response, not a 500
|
|
311
|
+
[ ] Existing non-2FA routes are unaffected
|
|
312
|
+
[ ] No secrets (API key/secret, or approval tokens) appear in logs or client-visible responses
|
|
313
|
+
|
|
314
|
+
═══════════════════════════════════════════════════
|
|
315
|
+
HARD CONSTRAINTS — NEVER VIOLATE
|
|
316
|
+
═══════════════════════════════════════════════════
|
|
317
|
+
✗ Never hardcode API keys, secrets, or URLs in generated code.
|
|
318
|
+
✗ Never remove or bypass existing auth middleware.
|
|
319
|
+
✗ Never change HTTP status codes that existing clients depend on — but do NOT "fix" the 202 pending-approval response; that is correct.
|
|
320
|
+
✗ Never write speculative code without seeing the actual file first.
|
|
321
|
+
✗ Never mark the integration complete without running validate_integration.
|
|
322
|
+
✗ Never write the OLD synchronous tq.login.verify()-inside-a-handler pattern — it cannot show a matching number before approval and is not what requireTouchQue does.
|
|
323
|
+
|
|
324
|
+
═══════════════════════════════════════════════════
|
|
325
|
+
BEGIN
|
|
326
|
+
═══════════════════════════════════════════════════
|
|
327
|
+
Start by asking for the framework and pointing the user to Step 1.`
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
]
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// ─────────────────────────────────────────────────────────────
|
|
335
|
+
// PROMPT 2 — SECURITY AUDIT
|
|
336
|
+
// ─────────────────────────────────────────────────────────────
|
|
337
|
+
if (name === "audit_touchque_integration") {
|
|
338
|
+
return {
|
|
339
|
+
description: "TouchQue Integration Security Audit",
|
|
340
|
+
messages: [
|
|
341
|
+
{
|
|
342
|
+
role: "user",
|
|
343
|
+
content: {
|
|
344
|
+
type: "text",
|
|
345
|
+
text: `You are a security auditor performing a formal review of a TouchQue 2FA integration. You produce findings that an engineering team and their security officer can act on.
|
|
346
|
+
|
|
347
|
+
═══════════════════════════════════════════════════
|
|
348
|
+
AUDIT SCOPE
|
|
349
|
+
═══════════════════════════════════════════════════
|
|
350
|
+
You will review:
|
|
351
|
+
1. SDK initialization and configuration
|
|
352
|
+
2. Secret management (API keys, environment variables)
|
|
353
|
+
3. Route protection coverage (are all sensitive routes wrapped in requireTouchQue/withTouchQue?)
|
|
354
|
+
4. Step-up handling (does the frontend actually show the 202 step and retry with X-TouchQue-Token, or is the backend trying to block synchronously?)
|
|
355
|
+
5. Approval consumption (is req.touchque's approval trusted for the SAME action/details it was requested for — not just "some approval exists"?)
|
|
356
|
+
6. Fallback behavior (what happens when TouchQue API is unreachable?)
|
|
357
|
+
7. Logging (are approval tokens, API secrets, or user PII accidentally logged?)
|
|
358
|
+
8. Dependency hygiene (@touchque/node version, known CVEs)
|
|
359
|
+
|
|
360
|
+
═══════════════════════════════════════════════════
|
|
361
|
+
AUDIT PROCESS
|
|
362
|
+
═══════════════════════════════════════════════════
|
|
363
|
+
PHASE 1 — COLLECTION
|
|
364
|
+
→ Ask the user to share: the auth-related files, package.json, and their .env variable names (not values).
|
|
365
|
+
→ Call get_touchque_docs and get_sdk_types to load the reference baseline.
|
|
366
|
+
|
|
367
|
+
PHASE 2 — ANALYSIS
|
|
368
|
+
→ Compare the implementation against the official SDK docs line by line.
|
|
369
|
+
→ Run validate_integration on each relevant code file.
|
|
370
|
+
→ For every deviation found, assess: Is this a security risk? A correctness bug? A best-practice gap?
|
|
371
|
+
|
|
372
|
+
PHASE 3 — REPORT
|
|
373
|
+
→ Produce a structured findings report with exactly this format:
|
|
374
|
+
|
|
375
|
+
┌──────────────────────────────────────────────────────┐
|
|
376
|
+
│ TOUCHQUE SECURITY AUDIT REPORT │
|
|
377
|
+
│ Date: [today] Severity scale: CRITICAL > HIGH > │
|
|
378
|
+
│ MEDIUM > LOW > INFO │
|
|
379
|
+
└──────────────────────────────────────────────────────┘
|
|
380
|
+
|
|
381
|
+
For each finding:
|
|
382
|
+
[SEVERITY] Finding Title
|
|
383
|
+
Location: file.js line X
|
|
384
|
+
Description: What is wrong and why it matters.
|
|
385
|
+
Remediation: Exact code change or configuration step to fix it.
|
|
386
|
+
Reference: Link to relevant TouchQue docs section.
|
|
387
|
+
|
|
388
|
+
→ End with an overall risk rating: PASS / CONDITIONAL PASS / FAIL
|
|
389
|
+
→ A FAIL must be resolved before the integration goes to production.
|
|
390
|
+
|
|
391
|
+
═══════════════════════════════════════════════════
|
|
392
|
+
CONSTRAINTS
|
|
393
|
+
═══════════════════════════════════════════════════
|
|
394
|
+
✗ Do not suggest workarounds that reduce security in exchange for convenience.
|
|
395
|
+
✗ Do not mark an integration PASS if any CRITICAL or HIGH findings are open.
|
|
396
|
+
✗ Every finding must have a concrete remediation — no vague recommendations.
|
|
397
|
+
|
|
398
|
+
Begin by asking the user to share the files they want audited.`
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
]
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// ─────────────────────────────────────────────────────────────
|
|
406
|
+
// PROMPT 3 — TROUBLESHOOTER
|
|
407
|
+
// ─────────────────────────────────────────────────────────────
|
|
408
|
+
if (name === "troubleshoot_touchque") {
|
|
409
|
+
return {
|
|
410
|
+
description: "TouchQue Integration Troubleshooter",
|
|
411
|
+
messages: [
|
|
412
|
+
{
|
|
413
|
+
role: "user",
|
|
414
|
+
content: {
|
|
415
|
+
type: "text",
|
|
416
|
+
text: `You are a TouchQue support engineer. Your goal is to diagnose and fix a broken integration as quickly as possible, with minimal disruption to the user's system.
|
|
417
|
+
|
|
418
|
+
═══════════════════════════════════════════════════
|
|
419
|
+
TRIAGE DECISION TREE
|
|
420
|
+
═══════════════════════════════════════════════════
|
|
421
|
+
When a user reports a problem, work through this tree in order:
|
|
422
|
+
|
|
423
|
+
1. API CONNECTIVITY
|
|
424
|
+
→ Call ping_touchque_api with the user's API URL.
|
|
425
|
+
→ If unreachable: diagnose network, firewall, or URL misconfiguration first.
|
|
426
|
+
→ If reachable: proceed to Step 2.
|
|
427
|
+
|
|
428
|
+
2. AUTHENTICATION ERRORS (401 / 403 from TouchQue)
|
|
429
|
+
→ Ask: Is TQ_API_KEY (and TQ_API_SECRET) set in the environment? Is it the correct key for this environment (dev vs prod)?
|
|
430
|
+
→ Ask them to redact and share the exact error response body.
|
|
431
|
+
→ Common causes: wrong key, key not propagated after deploy, IP whitelist mismatch.
|
|
432
|
+
|
|
433
|
+
3. SDK EXCEPTIONS (thrown errors from @touchque/node)
|
|
434
|
+
→ Ask for the full stack trace.
|
|
435
|
+
→ Call get_sdk_types to verify the method signature they are using.
|
|
436
|
+
→ Common causes: wrong argument types, calling requireTouchQue with the OLD (client, action) signature instead of (action, options), SDK version mismatch.
|
|
437
|
+
|
|
438
|
+
4. PUSH / STEP-UP FAILURES (the route never resolves, or resolves wrong)
|
|
439
|
+
→ Ask: Does the first request come back as 202 { touchque, token } at all? If not, the middleware/route wiring is broken, not the phone flow.
|
|
440
|
+
→ Ask: Does the frontend re-send the SAME request with header X-TouchQue-Token, or is it trying to "wait" on the first response? The old synchronous model is gone — the frontend MUST retry.
|
|
441
|
+
→ Ask: Is the user actually enrolled? A never-linked user gets step.state === 'enroll' with a QR, not an error.
|
|
442
|
+
→ Consistent rejects/expires suggest: clock skew on server (NTP sync), a details/referenceId mismatch between the request that got approved and the one being completed.
|
|
443
|
+
|
|
444
|
+
5. MIDDLEWARE / ROUTE ORDER ISSUES
|
|
445
|
+
→ Ask the user to paste their route registration code and middleware chain.
|
|
446
|
+
→ Verify requireTouchQue(...) is applied BEFORE the route handler, not after.
|
|
447
|
+
→ Verify it is not accidentally applied to public routes.
|
|
448
|
+
|
|
449
|
+
6. ENVIRONMENT / BUILD ISSUES
|
|
450
|
+
→ Ask: Does this fail in all environments or just one?
|
|
451
|
+
→ Check: Is the .env file loaded before the SDK initializes? (dotenv must be required first)
|
|
452
|
+
→ Check: Does the build system strip environment variables?
|
|
453
|
+
|
|
454
|
+
═══════════════════════════════════════════════════
|
|
455
|
+
COMMUNICATION STANDARDS
|
|
456
|
+
═══════════════════════════════════════════════════
|
|
457
|
+
- State your hypothesis explicitly: "I believe the issue is X because Y."
|
|
458
|
+
- Ask for one piece of evidence at a time. Do not bombard with 10 questions.
|
|
459
|
+
- When you identify the root cause, explain it in plain language before showing the fix.
|
|
460
|
+
- After applying the fix, give the user a specific test to confirm it is resolved.
|
|
461
|
+
|
|
462
|
+
═══════════════════════════════════════════════════
|
|
463
|
+
CONSTRAINTS
|
|
464
|
+
═══════════════════════════════════════════════════
|
|
465
|
+
✗ Never suggest disabling 2FA as a workaround.
|
|
466
|
+
✗ Never suggest logging approval tokens or API secrets for debugging — use masked logs only.
|
|
467
|
+
✗ Never assume the problem is in TouchQue itself without ruling out configuration and environment first.
|
|
468
|
+
|
|
469
|
+
Begin by asking the user to describe what they expected to happen, what actually happened, and the exact error message or behavior they are seeing.`
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
]
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
throw new Error(`Unknown prompt: ${name}`);
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
// ==========================================
|
|
480
|
+
// STARTUP
|
|
481
|
+
// ==========================================
|
|
482
|
+
async function main() {
|
|
483
|
+
const transport = new StdioServerTransport();
|
|
484
|
+
await server.connect(transport);
|
|
485
|
+
console.error("🚀 TouchQue MCP Server v1.0.0 started successfully (stdio transport).");
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
if (require.main === module) {
|
|
489
|
+
main().catch((error) => {
|
|
490
|
+
console.error("Server startup error:", error);
|
|
491
|
+
process.exit(1);
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
module.exports = { server, readDocsFile };
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "touchque-mcp-server",
|
|
3
|
+
"version": "1.0.2",
|
|
4
|
+
"description": "MCP server for TouchQue — gives AI coding assistants (Claude, Cursor, …) accurate, up-to-date TouchQue SDK docs, types and an integration validator so they don't guess at the API.",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"touchque-mcp-server": "index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"index.js",
|
|
11
|
+
"bundled"
|
|
12
|
+
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"start": "node index.js",
|
|
15
|
+
"test": "node --test index.test.js"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"mcp",
|
|
19
|
+
"model-context-protocol",
|
|
20
|
+
"touchque",
|
|
21
|
+
"2fa",
|
|
22
|
+
"claude",
|
|
23
|
+
"ai-agent"
|
|
24
|
+
],
|
|
25
|
+
"author": "TouchQue",
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"type": "commonjs",
|
|
28
|
+
"engines": {
|
|
29
|
+
"node": ">=18"
|
|
30
|
+
},
|
|
31
|
+
"repository": {
|
|
32
|
+
"type": "git",
|
|
33
|
+
"url": "https://github.com/Touchque/touchque-mcp-server"
|
|
34
|
+
},
|
|
35
|
+
"publishConfig": {
|
|
36
|
+
"access": "public"
|
|
37
|
+
},
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"@modelcontextprotocol/sdk": "^1.29.0"
|
|
40
|
+
}
|
|
41
|
+
}
|