scan_ai
Detect AI-generated content using Originality.ai to verify originality before publishing. Provides overall AI percentage and sentence-by-sentence breakdown with confidence scores.
Instructions
Detect AI-generated content using Originality.ai. Returns an overall AI vs. Original percentage and a sentence-by-sentence breakdown with confidence scores. Use this after writing content to check AI detection scores before publishing. Costs ~1 credit per 100 words.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| content | Yes | Plain text content to scan. Strip HTML/markdown for best accuracy. Minimum ~50 words for reliable results. | |
| title | No | Label for the scan (for reference in stored results). | AI Detection Scan |
Implementation Reference
- The handle_scan_ai function is the main handler that executes the scan_ai tool logic. It strips markdown from input content, validates minimum content length (~50 words), calls the OriginalityClient.scan() API with check_ai=True, and returns formatted AI detection results as TextContent.
async def handle_scan_ai( arguments: dict[str, Any], client: OriginalityClient, ) -> list[TextContent]: """AI detection scan only.""" content = _strip_markdown(arguments.get("content", "")) if not content or len(content.split()) < 20: return [TextContent(type="text", text="Content too short — need at least ~50 words for reliable AI detection.")] result = await client.scan( content=content, title=arguments.get("title", "AI Detection Scan"), check_ai=True, ai_model_version=arguments.get("ai_model", "lite-102"), ) return [TextContent(type="text", text=_format_ai_result(result))] - Defines the scan_ai Tool with name, description, and inputSchema. The schema specifies 'content' (required string) and 'title' (optional string with default 'AI Detection Scan') as parameters for the AI detection scan.
Tool( name="scan_ai", description=( "Detect AI-generated content using Originality.ai. Returns an overall " "AI vs. Original percentage and a sentence-by-sentence breakdown with " "confidence scores. Use this after writing content to check AI detection " "scores before publishing. Costs ~1 credit per 100 words." ), inputSchema={ "type": "object", "properties": { "content": { "type": "string", "description": "Plain text content to scan. Strip HTML/markdown for best accuracy. Minimum ~50 words for reliable results.", }, "title": { "type": "string", "default": "AI Detection Scan", "description": "Label for the scan (for reference in stored results).", }, }, "required": ["content"], }, ), - armavita_originality_ai_mcp/server.py:53-54 (registration)Registers the scan_ai tool by mapping the tool name 'scan_ai' to its handler function handle_scan_ai in the TOOL_HANDLERS dictionary. This routing enables the MCP server to dispatch tool calls to the correct handler.
TOOL_HANDLERS = { "scan_ai": handle_scan_ai, - The _strip_markdown helper function strips markdown formatting (bold, italic, headings, links, code blocks, lists, etc.) from input text to provide clean plaintext to the Originality.ai API for more accurate AI detection scoring.
def _strip_markdown(text: str) -> str: """Strip markdown formatting to send clean plaintext to the API. Originality.ai docs: 'For the most accurate scoring provide plain text.' """ # Remove bold/italic markers text = re.sub(r'\*{1,3}(.+?)\*{1,3}', r'\1', text) text = re.sub(r'_{1,3}(.+?)_{1,3}', r'\1', text) # Remove headings (## Heading -> Heading) text = re.sub(r'^#{1,6}\s+', '', text, flags=re.MULTILINE) # Remove markdown links [text](url) -> text text = re.sub(r'\[([^\]]+)\]\([^)]+\)', r'\1', text) # Remove inline code backticks text = re.sub(r'`([^`]+)`', r'\1', text) # Remove blockquote markers text = re.sub(r'^>\s+', '', text, flags=re.MULTILINE) # Remove horizontal rules text = re.sub(r'^---+$', '', text, flags=re.MULTILINE) # Remove list markers (- item, * item, 1. item) text = re.sub(r'^[\-\*]\s+', '', text, flags=re.MULTILINE) text = re.sub(r'^\d+\.\s+', '', text, flags=re.MULTILINE) # Remove image syntax  text = re.sub(r'!\[([^\]]*)\]\([^)]+\)', r'\1', text) # Collapse multiple blank lines text = re.sub(r'\n{3,}', '\n\n', text) return text.strip() - The _format_ai_result helper function formats AI detection results into a human-readable markdown table with AI/Original percentages, model used, credits consumed, a verdict (high AI/mixed/mostly original), and flagged sentence breakdown.
def _format_ai_result(data: dict) -> str: """Format AI detection results.""" results = data.get("results", data) ai = results.get("ai", {}) credits_used = _safe_get(results, "credits", "used", default="?") ai_pct, original_pct = _extract_ai_probs(results) model = ai.get("aiModel", "unknown") lines = [ "## AI Detection Results", "", f"| Metric | Score |", f"|--------|-------|", f"| AI Content | **{ai_pct:.0%}** |", f"| Original Content | **{original_pct:.0%}** |", f"| Model Used | {model} |", f"| Credits Used | {credits_used} |", "", ] # Verdict if ai_pct >= 0.75: lines.append("**Verdict:** High AI probability — significant revision recommended before publishing.") elif ai_pct >= 0.5: lines.append("**Verdict:** Mixed signals — some sections flagged as AI. Review flagged sentences below.") elif ai_pct >= 0.25: lines.append("**Verdict:** Mostly original with minor AI signals. Light revision may help.") else: lines.append("**Verdict:** Content reads as original.") # Sentence-level breakdown (top flagged sentences) blocks = ai.get("blocks", []) flagged = [b for b in blocks if b.get("result", {}).get("fake", 0) > 0.5] if flagged: lines.append("") lines.append(f"### Flagged Sentences ({len(flagged)} of {len(blocks)} total)") lines.append("") for block in flagged: text = block.get("text", "")[:120] fake_score = block.get("result", {}).get("fake", 0) lines.append(f"- **{fake_score:.0%} AI** — \"{text}{'...' if len(block.get('text', '')) > 120 else ''}\"") # Scan ID for retrieval scan_id = results.get("id", "") if scan_id: lines.extend(["", f"*Scan ID: {scan_id}*"]) return "\n".join(lines)