tinyhumansai/openhuman · error · ValidationError

Invalid ${paramName}: ${value}. Must be a positive integer.

Error message

Invalid ${paramName}: ${value}. Must be a positive integer.

What it means

validatePositiveInt() in the MCP validation layer checks that a parameter is a positive integer (e.g. message IDs). When the value is a JS number but either not an integer (fractional/Infinity/NaN) or <= 0, it throws ValidationError 'Invalid <param>: <value>. Must be a positive integer.' — the numeric-input branch of the guard.

Source

Thrown at app/src/lib/mcp/validation.ts:78

    try {
      return validateId(item, `${paramName}[${index}]`);
    } catch (error) {
      if (error instanceof ValidationError) {
        throw error;
      }
      const errorMsg = error instanceof Error ? error.message : String(error);
      throw new ValidationError(`Invalid ${paramName}[${index}]: ${errorMsg}`);
    }
  });
}

/**
 * Validate a positive integer parameter (e.g. message IDs)
 */
export function validatePositiveInt(value: unknown, paramName: string): number {
  if (typeof value === 'number') {
    if (!Number.isInteger(value) || value <= 0) {
      throw new ValidationError(`Invalid ${paramName}: ${value}. Must be a positive integer.`);
    }
    return value;
  }

  if (typeof value === 'string') {
    const intValue = Number.parseInt(value, 10);
    if (Number.isNaN(intValue) || intValue <= 0) {
      throw new ValidationError(`Invalid ${paramName}: '${value}'. Must be a positive integer.`);
    }
    return intValue;
  }

  throw new ValidationError(`Invalid ${paramName}: ${String(value)}. Must be a positive integer.`);
}

/**
 * Validate optional ID (can be undefined)
 */

View on GitHub (pinned to 7491200858)

Solutions

  1. Fix the caller to send a genuine positive integer (>= 1) for the param named in the message.
  2. Clamp/normalize computed values: Math.max(1, Math.trunc(value)) before invoking.
  3. If 0 should be meaningful, the API needs a different validator (validateOptionalId / non-negative variant) — file it upstream rather than bypassing.
  4. Add schema validation at the tool boundary (JSON Schema type integer, minimum 1) to reject early with clearer errors.

Example fix

// before
const params = { message_id: cursor ?? 0 };
await mcpCall(params);

// after
const params = { message_id: Math.max(1, Math.trunc(cursor ?? 1)) };
await mcpCall(params);
Defensive patterns

Strategy: validation

Validate before calling

function toPositiveInt(v: unknown): number | undefined {
  const n = typeof v === 'number' ? v : Number(v);
  return Number.isInteger(n) && n > 0 ? n : undefined;
}
const id = toPositiveInt(params.message_id);
if (id === undefined) throw new UserInputError('message_id must be a positive integer');

Type guard

function isPositiveInt(v: unknown): v is number {
  return typeof v === 'number' && Number.isInteger(v) && v > 0;
}

Try / catch

try {
  validatePositiveInt(value, 'message_id');
} catch (err) {
  if (err instanceof ValidationError) return badRequest(err.message);
  throw err;
}

Prevention

When it happens

Trigger: Calling an MCP tool/resource handler with params like message_id: 0, cursor: -5, limit: 2.5, or NaN (often from parseInt/parseFloat upstream or JSON with wrong types). The number branch fails before string coercion is attempted.

Common situations: Tool callers sending 0 as a default value; computed cursors going negative on empty results; float math leaking into IDs; deserialized JSON where the field was expected int but is float.

Related errors


AI-assisted analysis of tinyhumansai/openhuman@7491200858 (2026-08-17). Data as JSON: /api/errors/ca94e6433a25fb25. Report an issue: GitHub.