mirror of
https://github.com/KeygraphHQ/shannon.git
synced 2026-02-12 17:22:50 +00:00
feat: typescript migration (#40)
* chore: initialize TypeScript configuration and build setup - Add tsconfig.json for root and mcp-server with strict type checking - Install typescript and @types/node as devDependencies - Add npm build script for TypeScript compilation - Update main entrypoint to compiled dist/shannon.js - Update Dockerfile to build TypeScript before running - Configure output directory and module resolution for Node.js * refactor: migrate codebase from JavaScript to TypeScript - Convert all 37 JavaScript files to TypeScript (.js -> .ts) - Add type definitions in src/types/ for agents, config, errors, session - Update mcp-server with proper TypeScript types - Move entry point from shannon.mjs to src/shannon.ts - Update tsconfig.json with rootDir: "./src" for cleaner dist output - Update Dockerfile to build TypeScript before runtime - Update package.json paths to use compiled dist/shannon.js No runtime behavior changes - pure type safety migration. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * docs: update CLI references from ./shannon.mjs to shannon - Update help text in src/cli/ui.ts - Update usage examples in src/cli/command-handler.ts - Update setup message in src/shannon.ts - Update CLAUDE.md documentation with TypeScript file structure - Replace all ./shannon.mjs references with shannon command 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * chore: remove unnecessary eslint-disable comments ESLint is not configured in this project, making these comments redundant. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
35
mcp-server/package-lock.json
generated
35
mcp-server/package-lock.json
generated
@@ -10,6 +10,10 @@
|
||||
"dependencies": {
|
||||
"@anthropic-ai/claude-agent-sdk": "^0.1.0",
|
||||
"zod": "^3.22.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^25.0.3",
|
||||
"typescript": "^5.9.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@anthropic-ai/claude-agent-sdk": {
|
||||
@@ -241,6 +245,37 @@
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "25.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.3.tgz",
|
||||
"integrity": "sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~7.16.0"
|
||||
}
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.9.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "7.16.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
|
||||
"integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/zod": {
|
||||
"version": "3.25.76",
|
||||
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
|
||||
|
||||
@@ -2,12 +2,17 @@
|
||||
"name": "@shannon/mcp-server",
|
||||
"version": "1.0.0",
|
||||
"type": "module",
|
||||
"main": "./src/index.js",
|
||||
"main": "./dist/index.js",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"clean": "rm -rf dist"
|
||||
},
|
||||
"dependencies": {
|
||||
"@anthropic-ai/claude-agent-sdk": "^0.1.0",
|
||||
"zod": "^3.22.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^25.0.3",
|
||||
"typescript": "^5.9.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,13 +17,14 @@ import { createSdkMcpServer } from '@anthropic-ai/claude-agent-sdk';
|
||||
import { saveDeliverableTool } from './tools/save-deliverable.js';
|
||||
import { generateTotpTool } from './tools/generate-totp.js';
|
||||
|
||||
declare global {
|
||||
var __SHANNON_TARGET_DIR: string | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create Shannon Helper MCP Server with target directory context
|
||||
*
|
||||
* @param {string} targetDir - The target repository directory where deliverables should be saved
|
||||
* @returns {Object} MCP server instance
|
||||
*/
|
||||
export function createShannonHelperServer(targetDir) {
|
||||
export function createShannonHelperServer(targetDir: string): ReturnType<typeof createSdkMcpServer> {
|
||||
// Store target directory for tool access
|
||||
global.__SHANNON_TARGET_DIR = targetDir;
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
import { tool } from '@anthropic-ai/claude-agent-sdk';
|
||||
import { createHmac } from 'crypto';
|
||||
import { z } from 'zod';
|
||||
import { createToolResult } from '../types/tool-responses.js';
|
||||
import { createToolResult, type ToolResult, type GenerateTotpResponse } from '../types/tool-responses.js';
|
||||
import { base32Decode, validateTotpSecret } from '../validation/totp-validator.js';
|
||||
import { createCryptoError, createGenericError } from '../utils/error-formatter.js';
|
||||
|
||||
@@ -30,16 +30,13 @@ export const GenerateTotpInputSchema = z.object({
|
||||
.describe('Base32-encoded TOTP secret'),
|
||||
});
|
||||
|
||||
export type GenerateTotpInput = z.infer<typeof GenerateTotpInputSchema>;
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
function generateHOTP(secret: string, counter: number, digits: number = 6): string {
|
||||
const key = base32Decode(secret);
|
||||
|
||||
// Convert counter to 8-byte buffer (big-endian)
|
||||
@@ -52,12 +49,12 @@ function generateHOTP(secret, counter, digits = 6) {
|
||||
const hash = hmac.digest();
|
||||
|
||||
// Dynamic truncation
|
||||
const offset = hash[hash.length - 1] & 0x0f;
|
||||
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);
|
||||
((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');
|
||||
@@ -67,13 +64,8 @@ function generateHOTP(secret, counter, digits = 6) {
|
||||
/**
|
||||
* 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) {
|
||||
function generateTOTP(secret: string, timeStep: number = 30, digits: number = 6): string {
|
||||
const currentTime = Math.floor(Date.now() / 1000);
|
||||
const counter = Math.floor(currentTime / timeStep);
|
||||
return generateHOTP(secret, counter, digits);
|
||||
@@ -81,23 +73,16 @@ function generateTOTP(secret, timeStep = 30, digits = 6) {
|
||||
|
||||
/**
|
||||
* Get seconds until TOTP code expires
|
||||
*
|
||||
* @param {number} [timeStep=30] - Time step in seconds
|
||||
* @returns {number} Seconds until expiration
|
||||
*/
|
||||
function getSecondsUntilExpiration(timeStep = 30) {
|
||||
function getSecondsUntilExpiration(timeStep: number = 30): number {
|
||||
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) {
|
||||
export async function generateTotp(args: GenerateTotpInput): Promise<ToolResult> {
|
||||
try {
|
||||
const { secret } = args;
|
||||
|
||||
@@ -110,7 +95,7 @@ export async function generateTotp(args) {
|
||||
const timestamp = new Date().toISOString();
|
||||
|
||||
// Success response
|
||||
const successResponse = {
|
||||
const successResponse: GenerateTotpResponse = {
|
||||
status: 'success',
|
||||
message: 'TOTP code generated successfully',
|
||||
totpCode,
|
||||
@@ -14,7 +14,7 @@
|
||||
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 { createToolResult, type ToolResult, type SaveDeliverableResponse } 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';
|
||||
@@ -27,15 +27,12 @@ export const SaveDeliverableInputSchema = z.object({
|
||||
content: z.string().min(1).describe('File content (markdown for analysis/evidence, JSON for queues)'),
|
||||
});
|
||||
|
||||
export type SaveDeliverableInput = z.infer<typeof SaveDeliverableInputSchema>;
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
export async function saveDeliverable(args: SaveDeliverableInput): Promise<ToolResult> {
|
||||
try {
|
||||
const { deliverable_type, content } = args;
|
||||
|
||||
@@ -44,7 +41,7 @@ export async function saveDeliverable(args) {
|
||||
const queueValidation = validateQueueJson(content);
|
||||
if (!queueValidation.valid) {
|
||||
const errorResponse = createValidationError(
|
||||
queueValidation.message,
|
||||
queueValidation.message ?? 'Invalid queue JSON',
|
||||
true,
|
||||
{
|
||||
deliverableType: deliverable_type,
|
||||
@@ -60,7 +57,7 @@ export async function saveDeliverable(args) {
|
||||
const filepath = saveDeliverableFile(filename, content);
|
||||
|
||||
// Success response
|
||||
const successResponse = {
|
||||
const successResponse: SaveDeliverableResponse = {
|
||||
status: 'success',
|
||||
message: `Deliverable saved successfully: ${filename}`,
|
||||
filepath,
|
||||
@@ -11,63 +11,42 @@
|
||||
* Must match the exact mappings from tools/save_deliverable.js.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} DeliverableType
|
||||
* @property {string} CODE_ANALYSIS
|
||||
* @property {string} RECON
|
||||
* @property {string} INJECTION_ANALYSIS
|
||||
* @property {string} INJECTION_QUEUE
|
||||
* @property {string} XSS_ANALYSIS
|
||||
* @property {string} XSS_QUEUE
|
||||
* @property {string} AUTH_ANALYSIS
|
||||
* @property {string} AUTH_QUEUE
|
||||
* @property {string} AUTHZ_ANALYSIS
|
||||
* @property {string} AUTHZ_QUEUE
|
||||
* @property {string} SSRF_ANALYSIS
|
||||
* @property {string} SSRF_QUEUE
|
||||
* @property {string} INJECTION_EVIDENCE
|
||||
* @property {string} XSS_EVIDENCE
|
||||
* @property {string} AUTH_EVIDENCE
|
||||
* @property {string} AUTHZ_EVIDENCE
|
||||
* @property {string} SSRF_EVIDENCE
|
||||
*/
|
||||
|
||||
export const DeliverableType = {
|
||||
export enum DeliverableType {
|
||||
// Pre-recon agent
|
||||
CODE_ANALYSIS: 'CODE_ANALYSIS',
|
||||
CODE_ANALYSIS = 'CODE_ANALYSIS',
|
||||
|
||||
// Recon agent
|
||||
RECON: 'RECON',
|
||||
RECON = 'RECON',
|
||||
|
||||
// Vulnerability analysis agents
|
||||
INJECTION_ANALYSIS: 'INJECTION_ANALYSIS',
|
||||
INJECTION_QUEUE: 'INJECTION_QUEUE',
|
||||
INJECTION_ANALYSIS = 'INJECTION_ANALYSIS',
|
||||
INJECTION_QUEUE = 'INJECTION_QUEUE',
|
||||
|
||||
XSS_ANALYSIS: 'XSS_ANALYSIS',
|
||||
XSS_QUEUE: 'XSS_QUEUE',
|
||||
XSS_ANALYSIS = 'XSS_ANALYSIS',
|
||||
XSS_QUEUE = 'XSS_QUEUE',
|
||||
|
||||
AUTH_ANALYSIS: 'AUTH_ANALYSIS',
|
||||
AUTH_QUEUE: 'AUTH_QUEUE',
|
||||
AUTH_ANALYSIS = 'AUTH_ANALYSIS',
|
||||
AUTH_QUEUE = 'AUTH_QUEUE',
|
||||
|
||||
AUTHZ_ANALYSIS: 'AUTHZ_ANALYSIS',
|
||||
AUTHZ_QUEUE: 'AUTHZ_QUEUE',
|
||||
AUTHZ_ANALYSIS = 'AUTHZ_ANALYSIS',
|
||||
AUTHZ_QUEUE = 'AUTHZ_QUEUE',
|
||||
|
||||
SSRF_ANALYSIS: 'SSRF_ANALYSIS',
|
||||
SSRF_QUEUE: 'SSRF_QUEUE',
|
||||
SSRF_ANALYSIS = 'SSRF_ANALYSIS',
|
||||
SSRF_QUEUE = 'SSRF_QUEUE',
|
||||
|
||||
// Exploitation agents
|
||||
INJECTION_EVIDENCE: 'INJECTION_EVIDENCE',
|
||||
XSS_EVIDENCE: 'XSS_EVIDENCE',
|
||||
AUTH_EVIDENCE: 'AUTH_EVIDENCE',
|
||||
AUTHZ_EVIDENCE: 'AUTHZ_EVIDENCE',
|
||||
SSRF_EVIDENCE: 'SSRF_EVIDENCE',
|
||||
};
|
||||
INJECTION_EVIDENCE = 'INJECTION_EVIDENCE',
|
||||
XSS_EVIDENCE = 'XSS_EVIDENCE',
|
||||
AUTH_EVIDENCE = 'AUTH_EVIDENCE',
|
||||
AUTHZ_EVIDENCE = 'AUTHZ_EVIDENCE',
|
||||
SSRF_EVIDENCE = 'SSRF_EVIDENCE',
|
||||
}
|
||||
|
||||
/**
|
||||
* Hard-coded filename mappings from agent prompts
|
||||
* Must match tools/save_deliverable.js exactly
|
||||
*/
|
||||
export const DELIVERABLE_FILENAMES = {
|
||||
export const DELIVERABLE_FILENAMES: Record<DeliverableType, string> = {
|
||||
[DeliverableType.CODE_ANALYSIS]: 'code_analysis_deliverable.md',
|
||||
[DeliverableType.RECON]: 'recon_deliverable.md',
|
||||
[DeliverableType.INJECTION_ANALYSIS]: 'injection_analysis_deliverable.md',
|
||||
@@ -90,7 +69,7 @@ export const DELIVERABLE_FILENAMES = {
|
||||
/**
|
||||
* Queue types that require JSON validation
|
||||
*/
|
||||
export const QUEUE_TYPES = [
|
||||
export const QUEUE_TYPES: DeliverableType[] = [
|
||||
DeliverableType.INJECTION_QUEUE,
|
||||
DeliverableType.XSS_QUEUE,
|
||||
DeliverableType.AUTH_QUEUE,
|
||||
@@ -100,14 +79,18 @@ export const QUEUE_TYPES = [
|
||||
|
||||
/**
|
||||
* Type guard to check if a deliverable type is a queue
|
||||
* @param {string} type - Deliverable type to check
|
||||
* @returns {boolean} True if the type is a queue type
|
||||
*/
|
||||
export function isQueueType(type) {
|
||||
return QUEUE_TYPES.includes(type);
|
||||
export function isQueueType(type: string): boolean {
|
||||
return QUEUE_TYPES.includes(type as DeliverableType);
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {Object} VulnerabilityQueue
|
||||
* @property {Array<Object>} vulnerabilities - Array of vulnerability objects
|
||||
* Vulnerability queue structure
|
||||
*/
|
||||
export interface VulnerabilityQueue {
|
||||
vulnerabilities: VulnerabilityItem[];
|
||||
}
|
||||
|
||||
export interface VulnerabilityItem {
|
||||
[key: string]: unknown;
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
// Copyright (C) 2025 Keygraph, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License version 3
|
||||
// as published by the Free Software Foundation.
|
||||
|
||||
/**
|
||||
* Tool Response Type Definitions
|
||||
*
|
||||
* Defines structured response formats for MCP tools to ensure
|
||||
* consistent error handling and success reporting.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} ErrorResponse
|
||||
* @property {'error'} status
|
||||
* @property {string} message
|
||||
* @property {string} errorType - ValidationError, FileSystemError, CryptoError, etc.
|
||||
* @property {boolean} retryable
|
||||
* @property {Record<string, unknown>} [context]
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} SuccessResponse
|
||||
* @property {'success'} status
|
||||
* @property {string} message
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} SaveDeliverableResponse
|
||||
* @property {'success'} status
|
||||
* @property {string} message
|
||||
* @property {string} filepath
|
||||
* @property {string} deliverableType
|
||||
* @property {boolean} validated - true if queue JSON was validated
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} GenerateTotpResponse
|
||||
* @property {'success'} status
|
||||
* @property {string} message
|
||||
* @property {string} totpCode
|
||||
* @property {string} timestamp
|
||||
* @property {number} expiresIn - seconds until expiration
|
||||
*/
|
||||
|
||||
/**
|
||||
* Helper to create tool result from response
|
||||
* MCP tools should return this format
|
||||
*
|
||||
* @param {ErrorResponse | SaveDeliverableResponse | GenerateTotpResponse} response
|
||||
* @returns {{ content: Array<{ type: string; text: string }>; isError: boolean }}
|
||||
*/
|
||||
export function createToolResult(response) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: JSON.stringify(response, null, 2),
|
||||
},
|
||||
],
|
||||
isError: response.status === 'error',
|
||||
};
|
||||
}
|
||||
73
mcp-server/src/types/tool-responses.ts
Normal file
73
mcp-server/src/types/tool-responses.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
// Copyright (C) 2025 Keygraph, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License version 3
|
||||
// as published by the Free Software Foundation.
|
||||
|
||||
/**
|
||||
* Tool Response Type Definitions
|
||||
*
|
||||
* Defines structured response formats for MCP tools to ensure
|
||||
* consistent error handling and success reporting.
|
||||
*/
|
||||
|
||||
export interface ErrorResponse {
|
||||
status: 'error';
|
||||
message: string;
|
||||
errorType: string; // ValidationError, FileSystemError, CryptoError, etc.
|
||||
retryable: boolean;
|
||||
context?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface SuccessResponse {
|
||||
status: 'success';
|
||||
message: string;
|
||||
}
|
||||
|
||||
export interface SaveDeliverableResponse {
|
||||
status: 'success';
|
||||
message: string;
|
||||
filepath: string;
|
||||
deliverableType: string;
|
||||
validated: boolean; // true if queue JSON was validated
|
||||
}
|
||||
|
||||
export interface GenerateTotpResponse {
|
||||
status: 'success';
|
||||
message: string;
|
||||
totpCode: string;
|
||||
timestamp: string;
|
||||
expiresIn: number; // seconds until expiration
|
||||
}
|
||||
|
||||
export type ToolResponse =
|
||||
| ErrorResponse
|
||||
| SuccessResponse
|
||||
| SaveDeliverableResponse
|
||||
| GenerateTotpResponse;
|
||||
|
||||
export interface ToolResultContent {
|
||||
type: string;
|
||||
text: string;
|
||||
}
|
||||
|
||||
export interface ToolResult {
|
||||
content: ToolResultContent[];
|
||||
isError: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to create tool result from response
|
||||
* MCP tools should return this format
|
||||
*/
|
||||
export function createToolResult(response: ToolResponse): ToolResult {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: JSON.stringify(response, null, 2),
|
||||
},
|
||||
],
|
||||
isError: response.status === 'error',
|
||||
};
|
||||
}
|
||||
@@ -10,60 +10,50 @@
|
||||
* Helper functions for creating structured error responses.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} ErrorResponse
|
||||
* @property {'error'} status
|
||||
* @property {string} message
|
||||
* @property {string} errorType
|
||||
* @property {boolean} retryable
|
||||
* @property {Record<string, unknown>} [context]
|
||||
*/
|
||||
import type { ErrorResponse } from '../types/tool-responses.js';
|
||||
|
||||
/**
|
||||
* Create a validation error response
|
||||
*
|
||||
* @param {string} message
|
||||
* @param {boolean} [retryable=true]
|
||||
* @param {Record<string, unknown>} [context]
|
||||
* @returns {ErrorResponse}
|
||||
*/
|
||||
export function createValidationError(message, retryable = true, context) {
|
||||
export function createValidationError(
|
||||
message: string,
|
||||
retryable: boolean = true,
|
||||
context?: Record<string, unknown>
|
||||
): ErrorResponse {
|
||||
return {
|
||||
status: 'error',
|
||||
message,
|
||||
errorType: 'ValidationError',
|
||||
retryable,
|
||||
context,
|
||||
...(context !== undefined && { context }),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a crypto error response
|
||||
*
|
||||
* @param {string} message
|
||||
* @param {boolean} [retryable=false]
|
||||
* @param {Record<string, unknown>} [context]
|
||||
* @returns {ErrorResponse}
|
||||
*/
|
||||
export function createCryptoError(message, retryable = false, context) {
|
||||
export function createCryptoError(
|
||||
message: string,
|
||||
retryable: boolean = false,
|
||||
context?: Record<string, unknown>
|
||||
): ErrorResponse {
|
||||
return {
|
||||
status: 'error',
|
||||
message,
|
||||
errorType: 'CryptoError',
|
||||
retryable,
|
||||
context,
|
||||
...(context !== undefined && { context }),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a generic error response
|
||||
*
|
||||
* @param {unknown} error
|
||||
* @param {boolean} [retryable=false]
|
||||
* @param {Record<string, unknown>} [context]
|
||||
* @returns {ErrorResponse}
|
||||
*/
|
||||
export function createGenericError(error, retryable = false, context) {
|
||||
export function createGenericError(
|
||||
error: unknown,
|
||||
retryable: boolean = false,
|
||||
context?: Record<string, unknown>
|
||||
): ErrorResponse {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
const errorType = error instanceof Error ? error.constructor.name : 'UnknownError';
|
||||
|
||||
@@ -72,6 +62,6 @@ export function createGenericError(error, retryable = false, context) {
|
||||
message,
|
||||
errorType,
|
||||
retryable,
|
||||
context,
|
||||
...(context !== undefined && { context }),
|
||||
};
|
||||
}
|
||||
@@ -14,14 +14,14 @@
|
||||
import { writeFileSync, mkdirSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
|
||||
declare global {
|
||||
var __SHANNON_TARGET_DIR: string | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save deliverable file to deliverables/ directory
|
||||
*
|
||||
* @param {string} filename - Name of the file to save
|
||||
* @param {string} content - Content to write to the file
|
||||
* @returns {string} Full path to the saved file
|
||||
*/
|
||||
export function saveDeliverableFile(filename, content) {
|
||||
export function saveDeliverableFile(filename: string, content: string): string {
|
||||
// Use target directory from global context (set by createShannonHelperServer)
|
||||
const targetDir = global.__SHANNON_TARGET_DIR || process.cwd();
|
||||
const deliverablesDir = join(targetDir, 'deliverables');
|
||||
@@ -30,7 +30,7 @@ export function saveDeliverableFile(filename, content) {
|
||||
// Ensure deliverables directory exists
|
||||
try {
|
||||
mkdirSync(deliverablesDir, { recursive: true });
|
||||
} catch (error) {
|
||||
} catch {
|
||||
// Directory might already exist, ignore
|
||||
}
|
||||
|
||||
@@ -11,33 +11,41 @@
|
||||
* Ported from tools/save_deliverable.js (lines 56-75).
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} ValidationResult
|
||||
* @property {boolean} valid
|
||||
* @property {string} [message]
|
||||
* @property {Object} [data]
|
||||
*/
|
||||
import type { VulnerabilityQueue } from '../types/deliverables.js';
|
||||
|
||||
export interface ValidationResult {
|
||||
valid: boolean;
|
||||
message?: string;
|
||||
data?: VulnerabilityQueue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate JSON structure for queue files
|
||||
* Queue files must have a 'vulnerabilities' array
|
||||
*
|
||||
* @param {string} content - JSON string to validate
|
||||
* @returns {ValidationResult} ValidationResult with valid flag, optional error message, and parsed data
|
||||
*/
|
||||
export function validateQueueJson(content) {
|
||||
export function validateQueueJson(content: string): ValidationResult {
|
||||
try {
|
||||
const parsed = JSON.parse(content);
|
||||
const parsed = JSON.parse(content) as unknown;
|
||||
|
||||
// Type guard for the parsed result
|
||||
if (typeof parsed !== 'object' || parsed === null) {
|
||||
return {
|
||||
valid: false,
|
||||
message: `Invalid queue structure: Expected an object. Got: ${typeof parsed}`,
|
||||
};
|
||||
}
|
||||
|
||||
const obj = parsed as Record<string, unknown>;
|
||||
|
||||
// Queue files must have a 'vulnerabilities' array
|
||||
if (!parsed.vulnerabilities) {
|
||||
if (!('vulnerabilities' in obj)) {
|
||||
return {
|
||||
valid: false,
|
||||
message: `Invalid queue structure: Missing 'vulnerabilities' property. Expected: {"vulnerabilities": [...]}`,
|
||||
};
|
||||
}
|
||||
|
||||
if (!Array.isArray(parsed.vulnerabilities)) {
|
||||
if (!Array.isArray(obj.vulnerabilities)) {
|
||||
return {
|
||||
valid: false,
|
||||
message: `Invalid queue structure: 'vulnerabilities' must be an array. Expected: {"vulnerabilities": [...]}`,
|
||||
@@ -46,7 +54,7 @@ export function validateQueueJson(content) {
|
||||
|
||||
return {
|
||||
valid: true,
|
||||
data: parsed,
|
||||
data: parsed as VulnerabilityQueue,
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
@@ -14,11 +14,8 @@
|
||||
/**
|
||||
* Base32 decode function
|
||||
* Ported from generate-totp-standalone.mjs
|
||||
*
|
||||
* @param {string} encoded - Base32 encoded string
|
||||
* @returns {Buffer} Buffer containing decoded bytes
|
||||
*/
|
||||
export function base32Decode(encoded) {
|
||||
export function base32Decode(encoded: string): Buffer {
|
||||
const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
|
||||
const cleanInput = encoded.toUpperCase().replace(/[^A-Z2-7]/g, '');
|
||||
|
||||
@@ -26,7 +23,7 @@ export function base32Decode(encoded) {
|
||||
return Buffer.alloc(0);
|
||||
}
|
||||
|
||||
const output = [];
|
||||
const output: number[] = [];
|
||||
let bits = 0;
|
||||
let value = 0;
|
||||
|
||||
@@ -52,10 +49,9 @@ export function base32Decode(encoded) {
|
||||
* Validate TOTP secret
|
||||
* Must be base32-encoded string
|
||||
*
|
||||
* @param {string} secret - Secret to validate
|
||||
* @returns {boolean} true if valid, throws Error if invalid
|
||||
* @returns true if valid, throws Error if invalid
|
||||
*/
|
||||
export function validateTotpSecret(secret) {
|
||||
export function validateTotpSecret(secret: string): boolean {
|
||||
if (!secret || secret.length === 0) {
|
||||
throw new Error('TOTP secret cannot be empty');
|
||||
}
|
||||
50
mcp-server/tsconfig.json
Normal file
50
mcp-server/tsconfig.json
Normal file
@@ -0,0 +1,50 @@
|
||||
{
|
||||
// Visit https://aka.ms/tsconfig to read more about this file
|
||||
"compilerOptions": {
|
||||
// File Layout
|
||||
"rootDir": "./src",
|
||||
"outDir": "./dist",
|
||||
|
||||
// Environment Settings
|
||||
// See also https://aka.ms/tsconfig/module
|
||||
"module": "nodenext",
|
||||
"moduleResolution": "nodenext",
|
||||
|
||||
"target": "es2022",
|
||||
"lib": ["es2022"],
|
||||
|
||||
"types": ["node"],
|
||||
// For nodejs:
|
||||
// "lib": ["esnext"],
|
||||
// "types": ["node"],
|
||||
// and npm install -D @types/node
|
||||
|
||||
"resolveJsonModule": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noEmitOnError": true,
|
||||
|
||||
// Other Outputs
|
||||
"sourceMap": true,
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
|
||||
// Stricter Typechecking Options
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"exactOptionalPropertyTypes": true,
|
||||
|
||||
// Style Options
|
||||
// "noImplicitReturns": true,
|
||||
// "noImplicitOverride": true,
|
||||
// "noUnusedLocals": true,
|
||||
// "noUnusedParameters": true,
|
||||
// "noFallthroughCasesInSwitch": true,
|
||||
// "noPropertyAccessFromIndexSignature": true,
|
||||
|
||||
// Recommended Options
|
||||
"strict": true,
|
||||
"noUncheckedSideEffectImports": true,
|
||||
"skipLibCheck": true,
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
Reference in New Issue
Block a user