mirror of
https://github.com/KeygraphHQ/shannon.git
synced 2026-07-05 12:47:57 +02:00
feat: migrate to use MCP tools instead of helper scripts
This commit is contained in:
@@ -0,0 +1,137 @@
|
||||
/**
|
||||
* generate_totp MCP Tool
|
||||
*
|
||||
* Generates 6-digit TOTP codes for authentication.
|
||||
* Replaces tools/generate-totp-standalone.mjs bash script.
|
||||
* Based on RFC 6238 (TOTP) and RFC 4226 (HOTP).
|
||||
*/
|
||||
|
||||
import { tool } from '@anthropic-ai/claude-agent-sdk';
|
||||
import { createHmac } from 'crypto';
|
||||
import { z } from 'zod';
|
||||
import { createToolResult } from '../types/tool-responses.js';
|
||||
import { base32Decode, validateTotpSecret } from '../validation/totp-validator.js';
|
||||
import { createCryptoError, createGenericError } from '../utils/error-formatter.js';
|
||||
|
||||
/**
|
||||
* Input schema for generate_totp tool
|
||||
*/
|
||||
export const GenerateTotpInputSchema = z.object({
|
||||
secret: z
|
||||
.string()
|
||||
.min(1)
|
||||
.regex(/^[A-Z2-7]+$/i, 'Must be base32-encoded')
|
||||
.describe('Base32-encoded TOTP secret'),
|
||||
});
|
||||
|
||||
/**
|
||||
* Generate HOTP code (RFC 4226)
|
||||
* Ported from generate-totp-standalone.mjs (lines 74-99)
|
||||
*
|
||||
* @param {string} secret - Base32-encoded secret
|
||||
* @param {number} counter - Counter value
|
||||
* @param {number} [digits=6] - Number of digits in OTP
|
||||
* @returns {string} OTP code
|
||||
*/
|
||||
function generateHOTP(secret, counter, digits = 6) {
|
||||
const key = base32Decode(secret);
|
||||
|
||||
// Convert counter to 8-byte buffer (big-endian)
|
||||
const counterBuffer = Buffer.alloc(8);
|
||||
counterBuffer.writeBigUInt64BE(BigInt(counter));
|
||||
|
||||
// Generate HMAC-SHA1
|
||||
const hmac = createHmac('sha1', key);
|
||||
hmac.update(counterBuffer);
|
||||
const hash = hmac.digest();
|
||||
|
||||
// Dynamic truncation
|
||||
const offset = hash[hash.length - 1] & 0x0f;
|
||||
const code =
|
||||
((hash[offset] & 0x7f) << 24) |
|
||||
((hash[offset + 1] & 0xff) << 16) |
|
||||
((hash[offset + 2] & 0xff) << 8) |
|
||||
(hash[offset + 3] & 0xff);
|
||||
|
||||
// Generate digits
|
||||
const otp = (code % Math.pow(10, digits)).toString().padStart(digits, '0');
|
||||
return otp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate TOTP code (RFC 6238)
|
||||
* Ported from generate-totp-standalone.mjs (lines 101-106)
|
||||
*
|
||||
* @param {string} secret - Base32-encoded secret
|
||||
* @param {number} [timeStep=30] - Time step in seconds
|
||||
* @param {number} [digits=6] - Number of digits in OTP
|
||||
* @returns {string} OTP code
|
||||
*/
|
||||
function generateTOTP(secret, timeStep = 30, digits = 6) {
|
||||
const currentTime = Math.floor(Date.now() / 1000);
|
||||
const counter = Math.floor(currentTime / timeStep);
|
||||
return generateHOTP(secret, counter, digits);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get seconds until TOTP code expires
|
||||
*
|
||||
* @param {number} [timeStep=30] - Time step in seconds
|
||||
* @returns {number} Seconds until expiration
|
||||
*/
|
||||
function getSecondsUntilExpiration(timeStep = 30) {
|
||||
const currentTime = Math.floor(Date.now() / 1000);
|
||||
return timeStep - (currentTime % timeStep);
|
||||
}
|
||||
|
||||
/**
|
||||
* generate_totp tool implementation
|
||||
*
|
||||
* @param {Object} args
|
||||
* @param {string} args.secret - Base32-encoded TOTP secret
|
||||
* @returns {Promise<Object>} Tool result
|
||||
*/
|
||||
export async function generateTotp(args) {
|
||||
try {
|
||||
const { secret } = args;
|
||||
|
||||
// Validate secret (throws on error)
|
||||
validateTotpSecret(secret);
|
||||
|
||||
// Generate TOTP code
|
||||
const totpCode = generateTOTP(secret);
|
||||
const expiresIn = getSecondsUntilExpiration();
|
||||
const timestamp = new Date().toISOString();
|
||||
|
||||
// Success response
|
||||
const successResponse = {
|
||||
status: 'success',
|
||||
message: 'TOTP code generated successfully',
|
||||
totpCode,
|
||||
timestamp,
|
||||
expiresIn,
|
||||
};
|
||||
|
||||
return createToolResult(successResponse);
|
||||
} catch (error) {
|
||||
// Check if it's a validation/crypto error
|
||||
if (error instanceof Error && (error.message.includes('base32') || error.message.includes('TOTP'))) {
|
||||
const errorResponse = createCryptoError(error.message, false);
|
||||
return createToolResult(errorResponse);
|
||||
}
|
||||
|
||||
// Generic error
|
||||
const errorResponse = createGenericError(error, false);
|
||||
return createToolResult(errorResponse);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tool definition for MCP server - created using SDK's tool() function
|
||||
*/
|
||||
export const generateTotpTool = tool(
|
||||
'generate_totp',
|
||||
'Generates 6-digit TOTP code for authentication. Secret must be base32-encoded.',
|
||||
GenerateTotpInputSchema.shape,
|
||||
generateTotp
|
||||
);
|
||||
@@ -0,0 +1,6 @@
|
||||
/**
|
||||
* MCP Tools barrel export
|
||||
*/
|
||||
|
||||
export * from './save-deliverable.js';
|
||||
export * from './generate-totp.js';
|
||||
@@ -0,0 +1,85 @@
|
||||
/**
|
||||
* save_deliverable MCP Tool
|
||||
*
|
||||
* Saves deliverable files with automatic validation.
|
||||
* Replaces tools/save_deliverable.js bash script.
|
||||
*/
|
||||
|
||||
import { tool } from '@anthropic-ai/claude-agent-sdk';
|
||||
import { z } from 'zod';
|
||||
import { DeliverableType, DELIVERABLE_FILENAMES, isQueueType } from '../types/deliverables.js';
|
||||
import { createToolResult } from '../types/tool-responses.js';
|
||||
import { validateQueueJson } from '../validation/queue-validator.js';
|
||||
import { saveDeliverableFile } from '../utils/file-operations.js';
|
||||
import { createValidationError, createGenericError } from '../utils/error-formatter.js';
|
||||
|
||||
/**
|
||||
* Input schema for save_deliverable tool
|
||||
*/
|
||||
export const SaveDeliverableInputSchema = z.object({
|
||||
deliverable_type: z.nativeEnum(DeliverableType).describe('Type of deliverable to save'),
|
||||
content: z.string().min(1).describe('File content (markdown for analysis/evidence, JSON for queues)'),
|
||||
});
|
||||
|
||||
/**
|
||||
* save_deliverable tool implementation
|
||||
*
|
||||
* @param {Object} args
|
||||
* @param {string} args.deliverable_type - Type of deliverable to save
|
||||
* @param {string} args.content - File content
|
||||
* @returns {Promise<Object>} Tool result
|
||||
*/
|
||||
export async function saveDeliverable(args) {
|
||||
try {
|
||||
const { deliverable_type, content } = args;
|
||||
|
||||
// Validate queue JSON if applicable
|
||||
if (isQueueType(deliverable_type)) {
|
||||
const queueValidation = validateQueueJson(content);
|
||||
if (!queueValidation.valid) {
|
||||
const errorResponse = createValidationError(
|
||||
queueValidation.message,
|
||||
true,
|
||||
{
|
||||
deliverableType: deliverable_type,
|
||||
expectedFormat: '{"vulnerabilities": [...]}',
|
||||
}
|
||||
);
|
||||
return createToolResult(errorResponse);
|
||||
}
|
||||
}
|
||||
|
||||
// Get filename and save file
|
||||
const filename = DELIVERABLE_FILENAMES[deliverable_type];
|
||||
const filepath = saveDeliverableFile(filename, content);
|
||||
|
||||
// Success response
|
||||
const successResponse = {
|
||||
status: 'success',
|
||||
message: `Deliverable saved successfully: ${filename}`,
|
||||
filepath,
|
||||
deliverableType: deliverable_type,
|
||||
validated: isQueueType(deliverable_type),
|
||||
};
|
||||
|
||||
return createToolResult(successResponse);
|
||||
} catch (error) {
|
||||
const errorResponse = createGenericError(
|
||||
error,
|
||||
false,
|
||||
{ deliverableType: args.deliverable_type }
|
||||
);
|
||||
|
||||
return createToolResult(errorResponse);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tool definition for MCP server - created using SDK's tool() function
|
||||
*/
|
||||
export const saveDeliverableTool = tool(
|
||||
'save_deliverable',
|
||||
'Saves deliverable files with automatic validation. Queue files must have {"vulnerabilities": [...]} structure.',
|
||||
SaveDeliverableInputSchema.shape,
|
||||
saveDeliverable
|
||||
);
|
||||
Reference in New Issue
Block a user