@ontosdk/mcp 1.0.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Onto
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,166 @@
1
+ # Onto MCP Server
2
+
3
+ The official Onto Model Context Protocol server. Add clean web content reading and AI-readability scoring to Claude Code, Cursor, Cline, Zed, or any MCP-compatible AI client.
4
+
5
+ ## What this does
6
+
7
+ Onto's MCP server exposes three tools to any AI agent:
8
+
9
+ - **`read_url`** — Read any URL and get back clean, agent-ready Markdown (typically 10× smaller than raw HTML)
10
+ - **`score_url`** — Get the AIO (AI-readability) score for any URL with a breakdown of what helps and what hurts AI consumption
11
+ - **`read_and_score`** — Both at once: clean content plus quality assessment so the agent knows how much to trust the source
12
+
13
+ This is the official MCP wrapper for the [Onto Read API](https://api.buildonto.dev).
14
+
15
+ ## Why use this?
16
+
17
+ When AI agents read websites today, they parse hundreds of KB of React noise to find a few KB of actual content. This burns tokens and causes hallucinations.
18
+
19
+ Onto strips the noise server-side, returns the agent-ready format, and reports a confidence score for the source. One tool call, one accurate answer.
20
+
21
+ ## Quick start
22
+
23
+ ### 1. Get an Onto API key
24
+
25
+ Sign up at [app.buildonto.dev](https://app.buildonto.dev) and create an API key at **Read → Keys**.
26
+
27
+ Free tier: 1,000 requests / month. No credit card.
28
+
29
+ ### 2. Install in Claude Code
30
+
31
+ Add to your Claude Code MCP config:
32
+
33
+ ```json
34
+ {
35
+ "mcpServers": {
36
+ "onto": {
37
+ "command": "npx",
38
+ "args": ["-y", "@ontosdk/mcp"],
39
+ "env": {
40
+ "ONTO_API_KEY": "onto_sk_live_your_key_here"
41
+ }
42
+ }
43
+ }
44
+ }
45
+ ```
46
+
47
+ Restart Claude Code. The `read_url`, `score_url`, and `read_and_score` tools will appear in the available tools list.
48
+
49
+ ### 3. Install in Cursor
50
+
51
+ Add to Cursor's MCP configuration (Settings → Features → MCP):
52
+
53
+ ```json
54
+ {
55
+ "mcpServers": {
56
+ "onto": {
57
+ "command": "npx",
58
+ "args": ["-y", "@ontosdk/mcp"],
59
+ "env": {
60
+ "ONTO_API_KEY": "onto_sk_live_your_key_here"
61
+ }
62
+ }
63
+ }
64
+ }
65
+ ```
66
+
67
+ See [`examples/`](./examples/) for Cline, Zed, and Continue configs.
68
+
69
+ ### 4. Use it
70
+
71
+ In Claude Code or Cursor, try:
72
+
73
+ > Read https://stripe.com/pricing using Onto and summarize the pricing tiers.
74
+
75
+ The agent calls `read_url`, gets clean Markdown, and returns an accurate summary without parsing hundreds of KB of layout HTML.
76
+
77
+ ## Tools
78
+
79
+ ### `read_url`
80
+
81
+ Returns clean Markdown for a URL with metadata about the extraction (sizes, reduction %, cache state).
82
+
83
+ **Input:**
84
+
85
+ | Field | Type | Required | Description |
86
+ |---|---|---|---|
87
+ | `url` | string | yes | Publicly accessible HTTP(S) URL |
88
+ | `fresh` | boolean | no | If true, bypass cache (default: false) |
89
+
90
+ ### `score_url`
91
+
92
+ Returns the AIO (AI-readability) score for a URL — 0-100 with a letter grade, hallucination risk, and a structured list of penalties / benefits / recommendations.
93
+
94
+ **Input:**
95
+
96
+ | Field | Type | Required | Description |
97
+ |---|---|---|---|
98
+ | `url` | string | yes | URL to score |
99
+
100
+ ### `read_and_score`
101
+
102
+ Returns clean Markdown plus the AIO score in one call. Recommended default for agentic workflows.
103
+
104
+ **Input:** same as `read_url`.
105
+
106
+ ## Pricing
107
+
108
+ | Tier | Monthly requests | Price |
109
+ |---|---|---|
110
+ | Free | 1,000 | $0 |
111
+ | Starter | 10,000 | $9 |
112
+ | Growth | 100,000 | $49 |
113
+ | Scale | 500,000 | $250 |
114
+ | Enterprise | Custom | Contact sales |
115
+
116
+ Manage your subscription at [app.buildonto.dev/read/billing](https://app.buildonto.dev/read/billing). Credit packs ($5–$200) are available for overflow once you're on a paid tier.
117
+
118
+ ## Configuration
119
+
120
+ Environment variables:
121
+
122
+ - `ONTO_API_KEY` (required) — Your Onto API key from [app.buildonto.dev/read/keys](https://app.buildonto.dev/read/keys)
123
+ - `ONTO_API_BASE` (optional) — Override the API base URL (default: `https://api.buildonto.dev`)
124
+
125
+ ## Troubleshooting
126
+
127
+ ### "Invalid Onto API key"
128
+
129
+ Verify your key at [app.buildonto.dev/read/keys](https://app.buildonto.dev/read/keys). If you recently rotated keys, your MCP config may have a stale value.
130
+
131
+ ### "Monthly quota exceeded"
132
+
133
+ You've used your monthly allotment. Upgrade at [app.buildonto.dev/read/billing](https://app.buildonto.dev/read/billing) or wait for the monthly reset. Paid tiers can also top up with credit packs.
134
+
135
+ ### Tool doesn't appear in Claude Code / Cursor
136
+
137
+ 1. Verify the config file is valid JSON
138
+ 2. Restart the MCP host (Claude Code, Cursor, etc.)
139
+ 3. Check the host's MCP logs for connection errors
140
+ 4. Make sure `npx` is on your PATH
141
+
142
+ ### "Request timed out"
143
+
144
+ The target site may be slow or unreachable. Onto's request timeout is 15 seconds. Retry, or try a different URL.
145
+
146
+ ## Links
147
+
148
+ - Onto homepage: [buildonto.dev](https://buildonto.dev)
149
+ - API documentation: [docs.buildonto.dev](https://docs.buildonto.dev)
150
+ - Dashboard: [app.buildonto.dev](https://app.buildonto.dev)
151
+ - GitHub issues: [github.com/ravixalgorithm/onto-mcp/issues](https://github.com/ravixalgorithm/onto-mcp/issues)
152
+ - Contact: [founder@buildonto.dev](mailto:founder@buildonto.dev)
153
+
154
+ ## About Onto
155
+
156
+ Onto is the compatibility layer for the agent web. Three products on one engine:
157
+
158
+ - **Read** (this MCP server + API) — AI developers read any URL cleanly
159
+ - **Serve** ([Next.js SDK](https://www.npmjs.com/package/@ontosdk/next)) — Site owners serve clean Markdown to AI crawlers
160
+ - **Act** (coming Q3 2026) — Agents act on websites through semantic intent
161
+
162
+ Built for AI agents reading the web. Built so they read it correctly.
163
+
164
+ ## License
165
+
166
+ MIT
@@ -0,0 +1,6 @@
1
+ export { readUrl, readUrlInputSchema, type ReadUrlInput } from './tools/read.js';
2
+ export { scoreUrl, scoreUrlInputSchema, type ScoreUrlInput } from './tools/score.js';
3
+ export { readAndScore, readAndScoreInputSchema, type ReadAndScoreInput, } from './tools/read-and-score.js';
4
+ export { callOntoApi, OntoApiError } from './lib/api-client.js';
5
+ export type { ReadResponse, ScoreResponse, ReadAndScoreResponse, Recommendation, } from './lib/types.js';
6
+ export { version } from './lib/version.js';
package/dist/index.js ADDED
@@ -0,0 +1,9 @@
1
+ /* Programmatic exports for embedding the Onto tools in custom MCP setups.
2
+ * Most users should run the bin via `npx @ontosdk/mcp`; this file is for
3
+ * power users wiring tools into their own Server instance. */
4
+ export { readUrl, readUrlInputSchema } from './tools/read.js';
5
+ export { scoreUrl, scoreUrlInputSchema } from './tools/score.js';
6
+ export { readAndScore, readAndScoreInputSchema, } from './tools/read-and-score.js';
7
+ export { callOntoApi, OntoApiError } from './lib/api-client.js';
8
+ export { version } from './lib/version.js';
9
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;8DAE8D;AAE9D,OAAO,EAAE,OAAO,EAAE,kBAAkB,EAAqB,MAAM,iBAAiB,CAAC;AACjF,OAAO,EAAE,QAAQ,EAAE,mBAAmB,EAAsB,MAAM,kBAAkB,CAAC;AACrF,OAAO,EACL,YAAY,EACZ,uBAAuB,GAExB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAOhE,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC"}
@@ -0,0 +1,11 @@
1
+ export declare class OntoApiError extends Error {
2
+ readonly status: number;
3
+ readonly code?: string | undefined;
4
+ constructor(message: string, status: number, code?: string | undefined);
5
+ }
6
+ interface CallOptions {
7
+ body: unknown;
8
+ signal?: AbortSignal;
9
+ }
10
+ export declare function callOntoApi<T>(endpoint: string, options: CallOptions): Promise<T>;
11
+ export {};
@@ -0,0 +1,88 @@
1
+ /* Thin HTTP wrapper around the Onto Read API. Reads ONTO_API_KEY at call time
2
+ * (not module-load) so server.ts can fail with a clean error message first. */
3
+ import { version as PACKAGE_VERSION } from './version.js';
4
+ const DEFAULT_BASE = 'https://api.buildonto.dev';
5
+ const REQUEST_TIMEOUT_MS = 15_000;
6
+ export class OntoApiError extends Error {
7
+ status;
8
+ code;
9
+ constructor(message, status, code) {
10
+ super(message);
11
+ this.status = status;
12
+ this.code = code;
13
+ this.name = 'OntoApiError';
14
+ }
15
+ }
16
+ export async function callOntoApi(endpoint, options) {
17
+ const apiKey = process.env.ONTO_API_KEY;
18
+ if (!apiKey) {
19
+ throw new OntoApiError('ONTO_API_KEY environment variable is not set. Get a key at https://app.buildonto.dev/read/keys', 0, 'NO_API_KEY');
20
+ }
21
+ const base = process.env.ONTO_API_BASE ?? DEFAULT_BASE;
22
+ const url = `${base}${endpoint}`;
23
+ const timeout = AbortSignal.timeout(REQUEST_TIMEOUT_MS);
24
+ const signal = options.signal
25
+ ? AbortSignal.any([options.signal, timeout])
26
+ : timeout;
27
+ let response;
28
+ try {
29
+ response = await fetch(url, {
30
+ method: 'POST',
31
+ headers: {
32
+ Authorization: `Bearer ${apiKey}`,
33
+ 'Content-Type': 'application/json',
34
+ 'User-Agent': `@ontosdk/mcp/${PACKAGE_VERSION}`,
35
+ },
36
+ body: JSON.stringify(options.body),
37
+ signal,
38
+ });
39
+ }
40
+ catch (err) {
41
+ if (err instanceof DOMException && err.name === 'TimeoutError') {
42
+ throw new OntoApiError(`Onto API request timed out after ${REQUEST_TIMEOUT_MS / 1000}s. The target site may be slow or unreachable.`, 0, 'TIMEOUT');
43
+ }
44
+ throw new OntoApiError(`Failed to reach Onto API at ${base}: ${err.message}`, 0, 'NETWORK_ERROR');
45
+ }
46
+ const rawBody = await response.text();
47
+ if (!response.ok) {
48
+ let parsed = {};
49
+ try {
50
+ parsed = JSON.parse(rawBody);
51
+ }
52
+ catch {
53
+ // Body wasn't JSON; fall through with status-code-only error
54
+ }
55
+ const message = humanizeError(response.status, parsed);
56
+ throw new OntoApiError(message, response.status, parsed.error);
57
+ }
58
+ try {
59
+ return JSON.parse(rawBody);
60
+ }
61
+ catch (err) {
62
+ throw new OntoApiError(`Onto API returned invalid JSON: ${err.message}`, response.status, 'INVALID_RESPONSE');
63
+ }
64
+ }
65
+ function humanizeError(status, body) {
66
+ if (status === 401) {
67
+ return 'Invalid Onto API key. Verify your key at https://app.buildonto.dev/read/keys';
68
+ }
69
+ if (status === 402) {
70
+ return (body.message ??
71
+ 'Monthly plan quota exceeded and credit balance is empty. Top up credits at https://app.buildonto.dev/read/billing');
72
+ }
73
+ if (status === 403) {
74
+ if (body.error === 'ROBOTS_BLOCKED') {
75
+ return body.message ?? 'The target site blocks AI crawlers via robots.txt.';
76
+ }
77
+ return body.message ?? 'Forbidden.';
78
+ }
79
+ if (status === 429) {
80
+ return (body.message ??
81
+ 'Onto API rate limit exceeded. Upgrade your tier at https://app.buildonto.dev/read/billing or wait for the monthly reset.');
82
+ }
83
+ if (status >= 500) {
84
+ return body.message ?? `Onto API server error (${status}). Try again in a moment.`;
85
+ }
86
+ return body.message ?? `Onto API returned ${status}.`;
87
+ }
88
+ //# sourceMappingURL=api-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api-client.js","sourceRoot":"","sources":["../../src/lib/api-client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
2
+ export declare function formatToolError(error: unknown): CallToolResult;
@@ -0,0 +1,24 @@
1
+ /* Format errors as MCP tool responses. We don't throw McpError for
2
+ * tool-call failures — the AI host shows tool errors to the user as
3
+ * unhelpful internal-error messages. Returning isError: true with a
4
+ * text body lets the model see what went wrong and recover. */
5
+ import { OntoApiError } from './api-client.js';
6
+ export function formatToolError(error) {
7
+ if (error instanceof OntoApiError) {
8
+ return {
9
+ content: [{ type: 'text', text: error.message }],
10
+ isError: true,
11
+ };
12
+ }
13
+ const message = error instanceof Error ? error.message : String(error);
14
+ return {
15
+ content: [
16
+ {
17
+ type: 'text',
18
+ text: `Onto MCP error: ${message}\n\nTroubleshooting:\n- Verify ONTO_API_KEY is set and valid (https://app.buildonto.dev/read/keys)\n- Check the target URL is publicly accessible\n- Check your monthly quota at https://app.buildonto.dev/read/usage`,
19
+ },
20
+ ],
21
+ isError: true,
22
+ };
23
+ }
24
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/lib/errors.ts"],"names":[],"mappings":"AAAA;;;+DAG+D;AAG/D,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAE/C,MAAM,UAAU,eAAe,CAAC,KAAc;IAC5C,IAAI,KAAK,YAAY,YAAY,EAAE,CAAC;QAClC,OAAO;YACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC;YAChD,OAAO,EAAE,IAAI;SACd,CAAC;IACJ,CAAC;IAED,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACvE,OAAO;QACL,OAAO,EAAE;YACP;gBACE,IAAI,EAAE,MAAM;gBACZ,IAAI,EAAE,mBAAmB,OAAO,uNAAuN;aACxP;SACF;QACD,OAAO,EAAE,IAAI;KACd,CAAC;AACJ,CAAC"}
@@ -0,0 +1,76 @@
1
+ export interface ReadResponse {
2
+ status: 'success';
3
+ url: string;
4
+ markdown: string;
5
+ metadata: {
6
+ title: string;
7
+ description: string;
8
+ language?: string;
9
+ };
10
+ stats: {
11
+ raw_html_size_kb: number;
12
+ markdown_size_kb: number;
13
+ reduction_percent: number;
14
+ extraction_time_ms: number;
15
+ };
16
+ cache: {
17
+ hit: boolean;
18
+ ttl_seconds: number;
19
+ };
20
+ }
21
+ export interface Recommendation {
22
+ title?: string;
23
+ description?: string;
24
+ priority?: 'High' | 'Medium' | 'Low' | string;
25
+ [key: string]: unknown;
26
+ }
27
+ export interface ScoreResponse {
28
+ status: 'success';
29
+ url: string;
30
+ aio_score: number;
31
+ grade: string;
32
+ hallucination_risk: 'low' | 'medium' | 'high';
33
+ insights: Record<string, boolean>;
34
+ penalties: string[];
35
+ benefits: string[];
36
+ recommendations: Recommendation[];
37
+ stats: {
38
+ raw_size: string;
39
+ efficiency: string;
40
+ extraction_time_ms: number;
41
+ };
42
+ bot_preview?: unknown;
43
+ }
44
+ export interface ReadAndScoreResponse {
45
+ status: 'success';
46
+ url: string;
47
+ markdown: string;
48
+ metadata: {
49
+ title: string;
50
+ description: string;
51
+ language?: string;
52
+ };
53
+ aio_score: number;
54
+ grade: string;
55
+ hallucination_risk: 'low' | 'medium' | 'high';
56
+ insights: Record<string, boolean>;
57
+ penalties: string[];
58
+ benefits: string[];
59
+ recommendations: Recommendation[];
60
+ stats: {
61
+ raw_html_size_kb: number;
62
+ markdown_size_kb: number;
63
+ reduction_percent: number;
64
+ extraction_time_ms: number;
65
+ };
66
+ cache: {
67
+ hit: boolean;
68
+ ttl_seconds: number;
69
+ };
70
+ }
71
+ export interface ApiErrorBody {
72
+ status: 'error';
73
+ error: string;
74
+ message: string;
75
+ details?: Record<string, unknown>;
76
+ }
@@ -0,0 +1,4 @@
1
+ /* Response shapes from the Onto Read API. These mirror onto-api/app/v1/*.
2
+ * If the API shape changes, update here. */
3
+ export {};
4
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/lib/types.ts"],"names":[],"mappings":"AAAA;4CAC4C"}
@@ -0,0 +1 @@
1
+ export declare const version = "1.0.0";
@@ -0,0 +1,2 @@
1
+ export const version = '1.0.0';
2
+ //# sourceMappingURL=version.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"version.js","sourceRoot":"","sources":["../../src/lib/version.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,OAAO,GAAG,OAAO,CAAC"}
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/server.js ADDED
@@ -0,0 +1,125 @@
1
+ #!/usr/bin/env node
2
+ /* Onto MCP Server — exposes the Onto Read API as Model Context Protocol tools.
3
+ *
4
+ * Tools: read_url, score_url, read_and_score.
5
+ * Reads ONTO_API_KEY from env. Defaults base URL to https://api.buildonto.dev.
6
+ *
7
+ * Install in Claude Code:
8
+ * "mcpServers": {
9
+ * "onto": {
10
+ * "command": "npx",
11
+ * "args": ["-y", "@ontosdk/mcp"],
12
+ * "env": { "ONTO_API_KEY": "onto_sk_live_..." }
13
+ * }
14
+ * }
15
+ */
16
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
17
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
18
+ import { CallToolRequestSchema, ListToolsRequestSchema, ErrorCode, McpError, } from '@modelcontextprotocol/sdk/types.js';
19
+ import { readUrl, readUrlInputSchema } from './tools/read.js';
20
+ import { scoreUrl, scoreUrlInputSchema } from './tools/score.js';
21
+ import { readAndScore, readAndScoreInputSchema } from './tools/read-and-score.js';
22
+ import { version } from './lib/version.js';
23
+ if (!process.env.ONTO_API_KEY) {
24
+ console.error('[onto-mcp] ONTO_API_KEY environment variable is required.');
25
+ console.error('[onto-mcp] Create a key at https://app.buildonto.dev/read/keys');
26
+ process.exit(1);
27
+ }
28
+ const server = new Server({ name: 'onto', version }, { capabilities: { tools: {} } });
29
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
30
+ tools: [
31
+ {
32
+ name: 'read_url',
33
+ description: 'Read any URL and return clean, agent-ready Markdown. Strips HTML noise, preserves semantic content, and returns content optimized for AI consumption. Use this when you need to extract content from a website for an AI agent to process.',
34
+ inputSchema: {
35
+ type: 'object',
36
+ properties: {
37
+ url: {
38
+ type: 'string',
39
+ description: 'The URL to read. Must be a publicly accessible HTTP or HTTPS URL.',
40
+ },
41
+ fresh: {
42
+ type: 'boolean',
43
+ description: 'If true, bypass cache and fetch fresh content. Default false.',
44
+ default: false,
45
+ },
46
+ },
47
+ required: ['url'],
48
+ },
49
+ },
50
+ {
51
+ name: 'score_url',
52
+ description: 'Get the AIO (AI-readability) score for any URL. Returns a 0-100 score plus a list of penalties, benefits, and recommendations describing why the source is or is not well-suited for AI consumption. Use this to evaluate source quality before relying on it.',
53
+ inputSchema: {
54
+ type: 'object',
55
+ properties: {
56
+ url: {
57
+ type: 'string',
58
+ description: 'The URL to score.',
59
+ },
60
+ },
61
+ required: ['url'],
62
+ },
63
+ },
64
+ {
65
+ name: 'read_and_score',
66
+ description: 'Read any URL and return both clean Markdown AND the AIO accuracy score in one call. The recommended default for most AI workflows — gives both content and quality assessment together, so the AI agent can decide how much to trust the content.',
67
+ inputSchema: {
68
+ type: 'object',
69
+ properties: {
70
+ url: {
71
+ type: 'string',
72
+ description: 'The URL to read and score.',
73
+ },
74
+ fresh: {
75
+ type: 'boolean',
76
+ description: 'If true, bypass cache and fetch fresh content. Default false.',
77
+ default: false,
78
+ },
79
+ },
80
+ required: ['url'],
81
+ },
82
+ },
83
+ ],
84
+ }));
85
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
86
+ const { name, arguments: args } = request.params;
87
+ try {
88
+ switch (name) {
89
+ case 'read_url': {
90
+ const validated = readUrlInputSchema.parse(args ?? {});
91
+ return await readUrl(validated);
92
+ }
93
+ case 'score_url': {
94
+ const validated = scoreUrlInputSchema.parse(args ?? {});
95
+ return await scoreUrl(validated);
96
+ }
97
+ case 'read_and_score': {
98
+ const validated = readAndScoreInputSchema.parse(args ?? {});
99
+ return await readAndScore(validated);
100
+ }
101
+ default:
102
+ throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`);
103
+ }
104
+ }
105
+ catch (error) {
106
+ if (error instanceof McpError)
107
+ throw error;
108
+ const message = error instanceof Error ? error.message : String(error);
109
+ return {
110
+ content: [{ type: 'text', text: `Tool '${name}' failed: ${message}` }],
111
+ isError: true,
112
+ };
113
+ }
114
+ });
115
+ async function main() {
116
+ const transport = new StdioServerTransport();
117
+ await server.connect(transport);
118
+ // stderr (not stdout) — stdout is reserved for MCP protocol frames
119
+ console.error(`[onto-mcp] v${version} listening on stdio`);
120
+ }
121
+ main().catch((err) => {
122
+ console.error('[onto-mcp] fatal:', err);
123
+ process.exit(1);
124
+ });
125
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ import { z } from 'zod';
2
+ import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
3
+ export declare const readAndScoreInputSchema: z.ZodObject<{
4
+ url: z.ZodString;
5
+ fresh: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
6
+ }, "strip", z.ZodTypeAny, {
7
+ url: string;
8
+ fresh: boolean;
9
+ }, {
10
+ url: string;
11
+ fresh?: boolean | undefined;
12
+ }>;
13
+ export type ReadAndScoreInput = z.infer<typeof readAndScoreInputSchema>;
14
+ export declare function readAndScore(input: ReadAndScoreInput): Promise<CallToolResult>;
@@ -0,0 +1,43 @@
1
+ import { z } from 'zod';
2
+ import { callOntoApi } from '../lib/api-client.js';
3
+ import { formatToolError } from '../lib/errors.js';
4
+ export const readAndScoreInputSchema = z.object({
5
+ url: z.string().url(),
6
+ fresh: z.boolean().optional().default(false),
7
+ });
8
+ export async function readAndScore(input) {
9
+ try {
10
+ const result = await callOntoApi('/v1/read-and-score', {
11
+ body: { url: input.url, fresh: input.fresh },
12
+ });
13
+ const trustHint = trustLine(result.aio_score, result.hallucination_risk);
14
+ const summaryLines = [
15
+ `**Source quality assessment (from Onto):**`,
16
+ `- AIO Score: ${result.aio_score}/100 (${result.grade})`,
17
+ `- Hallucination risk: ${result.hallucination_risk}`,
18
+ `- Reduction: ${result.stats.reduction_percent}% (${result.stats.raw_html_size_kb} KB → ${result.stats.markdown_size_kb} KB)`,
19
+ `- Cache: ${result.cache.hit ? 'HIT' : 'MISS'}`,
20
+ '',
21
+ trustHint,
22
+ ];
23
+ return {
24
+ content: [
25
+ { type: 'text', text: result.markdown },
26
+ { type: 'text', text: `\n\n---\n\n${summaryLines.join('\n')}` },
27
+ ],
28
+ };
29
+ }
30
+ catch (error) {
31
+ return formatToolError(error);
32
+ }
33
+ }
34
+ function trustLine(score, risk) {
35
+ if (risk === 'high' || score < 40) {
36
+ return 'Trust signal: low — this source is poorly structured for AI consumption. Verify any facts before relying on them.';
37
+ }
38
+ if (risk === 'medium' || score < 70) {
39
+ return 'Trust signal: medium — source is partially AI-readable. Cross-check critical claims.';
40
+ }
41
+ return 'Trust signal: high — source is well-structured for AI consumption.';
42
+ }
43
+ //# sourceMappingURL=read-and-score.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"read-and-score.js","sourceRoot":"","sources":["../../src/tools/read-and-score.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACnD,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAGnD,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC9C,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE;IACrB,KAAK,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC;CAC7C,CAAC,CAAC;AAIH,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,KAAwB;IACzD,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,WAAW,CAAuB,oBAAoB,EAAE;YAC3E,IAAI,EAAE,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE;SAC7C,CAAC,CAAC;QAEH,MAAM,SAAS,GAAG,SAAS,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,kBAAkB,CAAC,CAAC;QACzE,MAAM,YAAY,GAAG;YACnB,4CAA4C;YAC5C,gBAAgB,MAAM,CAAC,SAAS,SAAS,MAAM,CAAC,KAAK,GAAG;YACxD,yBAAyB,MAAM,CAAC,kBAAkB,EAAE;YACpD,gBAAgB,MAAM,CAAC,KAAK,CAAC,iBAAiB,MAAM,MAAM,CAAC,KAAK,CAAC,gBAAgB,SAAS,MAAM,CAAC,KAAK,CAAC,gBAAgB,MAAM;YAC7H,YAAY,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE;YAC/C,EAAE;YACF,SAAS;SACV,CAAC;QAEF,OAAO;YACL,OAAO,EAAE;gBACP,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,MAAM,CAAC,QAAQ,EAAE;gBAChD,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,cAAc,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE;aACzE;SACF,CAAC;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,eAAe,CAAC,KAAK,CAAC,CAAC;IAChC,CAAC;AACH,CAAC;AAED,SAAS,SAAS,CAAC,KAAa,EAAE,IAA+B;IAC/D,IAAI,IAAI,KAAK,MAAM,IAAI,KAAK,GAAG,EAAE,EAAE,CAAC;QAClC,OAAO,mHAAmH,CAAC;IAC7H,CAAC;IACD,IAAI,IAAI,KAAK,QAAQ,IAAI,KAAK,GAAG,EAAE,EAAE,CAAC;QACpC,OAAO,sFAAsF,CAAC;IAChG,CAAC;IACD,OAAO,oEAAoE,CAAC;AAC9E,CAAC"}
@@ -0,0 +1,14 @@
1
+ import { z } from 'zod';
2
+ import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
3
+ export declare const readUrlInputSchema: z.ZodObject<{
4
+ url: z.ZodString;
5
+ fresh: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
6
+ }, "strip", z.ZodTypeAny, {
7
+ url: string;
8
+ fresh: boolean;
9
+ }, {
10
+ url: string;
11
+ fresh?: boolean | undefined;
12
+ }>;
13
+ export type ReadUrlInput = z.infer<typeof readUrlInputSchema>;
14
+ export declare function readUrl(input: ReadUrlInput): Promise<CallToolResult>;
@@ -0,0 +1,36 @@
1
+ import { z } from 'zod';
2
+ import { callOntoApi } from '../lib/api-client.js';
3
+ import { formatToolError } from '../lib/errors.js';
4
+ export const readUrlInputSchema = z.object({
5
+ url: z.string().url(),
6
+ fresh: z.boolean().optional().default(false),
7
+ });
8
+ export async function readUrl(input) {
9
+ try {
10
+ const result = await callOntoApi('/v1/read', {
11
+ body: { url: input.url, fresh: input.fresh },
12
+ });
13
+ const metaLines = [
14
+ `- URL: ${result.url}`,
15
+ `- Title: ${result.metadata.title || '(none)'}`,
16
+ `- Original size: ${result.stats.raw_html_size_kb} KB`,
17
+ `- Cleaned size: ${result.stats.markdown_size_kb} KB`,
18
+ `- Reduction: ${result.stats.reduction_percent}%`,
19
+ `- Extraction time: ${result.stats.extraction_time_ms} ms`,
20
+ `- Cache: ${result.cache.hit ? 'HIT' : 'MISS'}`,
21
+ ].join('\n');
22
+ return {
23
+ content: [
24
+ { type: 'text', text: result.markdown },
25
+ {
26
+ type: 'text',
27
+ text: `\n\n---\n\n**Source metadata (from Onto):**\n${metaLines}`,
28
+ },
29
+ ],
30
+ };
31
+ }
32
+ catch (error) {
33
+ return formatToolError(error);
34
+ }
35
+ }
36
+ //# sourceMappingURL=read.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"read.js","sourceRoot":"","sources":["../../src/tools/read.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACnD,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAGnD,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,CAAC,MAAM,CAAC;IACzC,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE;IACrB,KAAK,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC;CAC7C,CAAC,CAAC;AAIH,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC,KAAmB;IAC/C,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,WAAW,CAAe,UAAU,EAAE;YACzD,IAAI,EAAE,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE;SAC7C,CAAC,CAAC;QAEH,MAAM,SAAS,GAAG;YAChB,UAAU,MAAM,CAAC,GAAG,EAAE;YACtB,YAAY,MAAM,CAAC,QAAQ,CAAC,KAAK,IAAI,QAAQ,EAAE;YAC/C,oBAAoB,MAAM,CAAC,KAAK,CAAC,gBAAgB,KAAK;YACtD,mBAAmB,MAAM,CAAC,KAAK,CAAC,gBAAgB,KAAK;YACrD,gBAAgB,MAAM,CAAC,KAAK,CAAC,iBAAiB,GAAG;YACjD,sBAAsB,MAAM,CAAC,KAAK,CAAC,kBAAkB,KAAK;YAC1D,YAAY,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE;SAChD,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAEb,OAAO;YACL,OAAO,EAAE;gBACP,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,MAAM,CAAC,QAAQ,EAAE;gBAChD;oBACE,IAAI,EAAE,MAAe;oBACrB,IAAI,EAAE,gDAAgD,SAAS,EAAE;iBAClE;aACF;SACF,CAAC;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,eAAe,CAAC,KAAK,CAAC,CAAC;IAChC,CAAC;AACH,CAAC"}
@@ -0,0 +1,13 @@
1
+ import { z } from 'zod';
2
+ import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
3
+ import type { ScoreResponse } from '../lib/types.js';
4
+ export declare const scoreUrlInputSchema: z.ZodObject<{
5
+ url: z.ZodString;
6
+ }, "strip", z.ZodTypeAny, {
7
+ url: string;
8
+ }, {
9
+ url: string;
10
+ }>;
11
+ export type ScoreUrlInput = z.infer<typeof scoreUrlInputSchema>;
12
+ export declare function scoreUrl(input: ScoreUrlInput): Promise<CallToolResult>;
13
+ export declare function formatScoreSummary(result: ScoreResponse): string;
@@ -0,0 +1,71 @@
1
+ import { z } from 'zod';
2
+ import { callOntoApi } from '../lib/api-client.js';
3
+ import { formatToolError } from '../lib/errors.js';
4
+ export const scoreUrlInputSchema = z.object({
5
+ url: z.string().url(),
6
+ });
7
+ export async function scoreUrl(input) {
8
+ try {
9
+ const result = await callOntoApi('/v1/score', {
10
+ body: { url: input.url },
11
+ });
12
+ return {
13
+ content: [{ type: 'text', text: formatScoreSummary(result) }],
14
+ };
15
+ }
16
+ catch (error) {
17
+ return formatToolError(error);
18
+ }
19
+ }
20
+ export function formatScoreSummary(result) {
21
+ const lines = [
22
+ `**AIO Score:** ${result.aio_score}/100 (${result.grade})`,
23
+ `**Hallucination risk:** ${result.hallucination_risk}`,
24
+ `**URL:** ${result.url}`,
25
+ '',
26
+ ];
27
+ if (result.benefits.length > 0) {
28
+ lines.push('**What works well:**');
29
+ for (const item of result.benefits)
30
+ lines.push(`- ${item}`);
31
+ lines.push('');
32
+ }
33
+ if (result.penalties.length > 0) {
34
+ lines.push('**What hurts AI readability:**');
35
+ for (const item of result.penalties)
36
+ lines.push(`- ${item}`);
37
+ lines.push('');
38
+ }
39
+ const insightEntries = Object.entries(result.insights ?? {});
40
+ if (insightEntries.length > 0) {
41
+ lines.push('**Insights:**');
42
+ for (const [key, value] of insightEntries) {
43
+ lines.push(`- ${key}: ${value ? 'yes' : 'no'}`);
44
+ }
45
+ lines.push('');
46
+ }
47
+ if (result.recommendations.length > 0) {
48
+ lines.push('**Recommendations:**');
49
+ for (const rec of result.recommendations) {
50
+ lines.push(describeRecommendation(rec));
51
+ }
52
+ lines.push('');
53
+ }
54
+ lines.push('**Stats:**');
55
+ lines.push(`- Raw size: ${result.stats.raw_size}`);
56
+ lines.push(`- Efficiency: ${result.stats.efficiency}`);
57
+ lines.push(`- Extraction time: ${result.stats.extraction_time_ms} ms`);
58
+ return lines.join('\n');
59
+ }
60
+ function describeRecommendation(rec) {
61
+ if (typeof rec === 'string')
62
+ return `- ${rec}`;
63
+ if (rec.title) {
64
+ const head = rec.priority ? `**${rec.title}** _(priority: ${rec.priority})_` : `**${rec.title}**`;
65
+ return rec.description ? `- ${head} — ${rec.description}` : `- ${head}`;
66
+ }
67
+ if (rec.description)
68
+ return `- ${rec.description}`;
69
+ return `- ${JSON.stringify(rec)}`;
70
+ }
71
+ //# sourceMappingURL=score.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"score.js","sourceRoot":"","sources":["../../src/tools/score.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,48 @@
1
+ # Onto MCP in Claude Code
2
+
3
+ ## Config location
4
+
5
+ | OS | Path |
6
+ |---|---|
7
+ | macOS | `~/Library/Application Support/Claude/claude_desktop_config.json` |
8
+ | Windows | `%APPDATA%\Claude\claude_desktop_config.json` |
9
+ | Linux | `~/.config/Claude/claude_desktop_config.json` |
10
+
11
+ Claude Code CLI users: the same `mcpServers` block can be added via `claude mcp add` or by editing `~/.claude/settings.json` and nesting under `mcpServers`.
12
+
13
+ ## Config block
14
+
15
+ Add to the `mcpServers` object (create the key if it doesn't exist):
16
+
17
+ ```json
18
+ {
19
+ "mcpServers": {
20
+ "onto": {
21
+ "command": "npx",
22
+ "args": ["-y", "@ontosdk/mcp"],
23
+ "env": {
24
+ "ONTO_API_KEY": "onto_sk_live_your_key_here"
25
+ }
26
+ }
27
+ }
28
+ }
29
+ ```
30
+
31
+ ## Verify
32
+
33
+ 1. Restart Claude Code (fully quit and reopen — `Cmd/Ctrl+Q`, not just close window)
34
+ 2. Open a new chat
35
+ 3. Type: `What tools do I have available?`
36
+ 4. You should see `read_url`, `score_url`, and `read_and_score` in the list
37
+
38
+ ## Try it
39
+
40
+ > Read https://news.ycombinator.com using Onto and tell me the top three stories.
41
+
42
+ > Score https://example.com — is it well-structured for AI consumption?
43
+
44
+ > Read https://stripe.com/pricing using Onto and summarize each plan.
45
+
46
+ ## Where to get an API key
47
+
48
+ [app.buildonto.dev/read/keys](https://app.buildonto.dev/read/keys) — free tier includes 1,000 requests/month, no credit card.
@@ -0,0 +1,33 @@
1
+ # Onto MCP in Cline (VS Code)
2
+
3
+ ## Setup
4
+
5
+ 1. Install the [Cline](https://marketplace.visualstudio.com/items?itemName=saoudrizwan.claude-dev) extension in VS Code
6
+ 2. Open the Cline panel → click the MCP icon → "Edit MCP Settings"
7
+ 3. Add the Onto block to `cline_mcp_settings.json`
8
+
9
+ ## Config block
10
+
11
+ ```json
12
+ {
13
+ "mcpServers": {
14
+ "onto": {
15
+ "command": "npx",
16
+ "args": ["-y", "@ontosdk/mcp"],
17
+ "env": {
18
+ "ONTO_API_KEY": "onto_sk_live_your_key_here"
19
+ },
20
+ "disabled": false,
21
+ "autoApprove": []
22
+ }
23
+ }
24
+ }
25
+ ```
26
+
27
+ ## Verify
28
+
29
+ The Cline MCP panel should show "onto" with three connected tools: `read_url`, `score_url`, `read_and_score`.
30
+
31
+ ## API key
32
+
33
+ Get one at [app.buildonto.dev/read/keys](https://app.buildonto.dev/read/keys).
@@ -0,0 +1,44 @@
1
+ # Onto MCP in Cursor
2
+
3
+ ## Config location
4
+
5
+ Cursor → Settings → Features → **Model Context Protocol** → "Add server"
6
+
7
+ Or edit the config file directly:
8
+
9
+ | OS | Path |
10
+ |---|---|
11
+ | macOS | `~/.cursor/mcp.json` |
12
+ | Windows | `%USERPROFILE%\.cursor\mcp.json` |
13
+ | Linux | `~/.cursor/mcp.json` |
14
+
15
+ ## Config block
16
+
17
+ ```json
18
+ {
19
+ "mcpServers": {
20
+ "onto": {
21
+ "command": "npx",
22
+ "args": ["-y", "@ontosdk/mcp"],
23
+ "env": {
24
+ "ONTO_API_KEY": "onto_sk_live_your_key_here"
25
+ }
26
+ }
27
+ }
28
+ }
29
+ ```
30
+
31
+ ## Verify
32
+
33
+ 1. Restart Cursor
34
+ 2. Open the Composer or Chat
35
+ 3. Ask: "What MCP tools are available?"
36
+ 4. `read_url`, `score_url`, `read_and_score` should be listed
37
+
38
+ ## Try it
39
+
40
+ > Use Onto to read https://docs.cursor.com and summarize the @-symbol reference.
41
+
42
+ ## API key
43
+
44
+ Get one at [app.buildonto.dev/read/keys](https://app.buildonto.dev/read/keys).
@@ -0,0 +1,41 @@
1
+ # Onto MCP in Zed
2
+
3
+ ## Config location
4
+
5
+ Zed → Settings → Open `settings.json`
6
+
7
+ Or directly:
8
+
9
+ | OS | Path |
10
+ |---|---|
11
+ | macOS | `~/.config/zed/settings.json` |
12
+ | Linux | `~/.config/zed/settings.json` |
13
+ | Windows | `%APPDATA%\Zed\settings.json` |
14
+
15
+ ## Config block
16
+
17
+ Add (or merge with) the `context_servers` key:
18
+
19
+ ```json
20
+ {
21
+ "context_servers": {
22
+ "onto": {
23
+ "command": {
24
+ "path": "npx",
25
+ "args": ["-y", "@ontosdk/mcp"],
26
+ "env": {
27
+ "ONTO_API_KEY": "onto_sk_live_your_key_here"
28
+ }
29
+ }
30
+ }
31
+ }
32
+ }
33
+ ```
34
+
35
+ ## Verify
36
+
37
+ Restart Zed. Open Assistant and check the context server status indicator — "onto" should appear as connected.
38
+
39
+ ## API key
40
+
41
+ Get one at [app.buildonto.dev/read/keys](https://app.buildonto.dev/read/keys).
package/package.json ADDED
@@ -0,0 +1,62 @@
1
+ {
2
+ "name": "@ontosdk/mcp",
3
+ "version": "1.0.0",
4
+ "description": "Official Onto MCP server — clean Markdown and AIO scoring for any URL, available as MCP tools for Claude Code, Cursor, and any MCP client.",
5
+ "type": "module",
6
+ "main": "./dist/server.js",
7
+ "bin": {
8
+ "onto-mcp": "./dist/server.js"
9
+ },
10
+ "files": [
11
+ "dist/**/*",
12
+ "examples/**/*",
13
+ "README.md",
14
+ "LICENSE"
15
+ ],
16
+ "scripts": {
17
+ "build": "tsc",
18
+ "watch": "tsc --watch",
19
+ "start": "node dist/server.js",
20
+ "prepublishOnly": "npm run build"
21
+ },
22
+ "dependencies": {
23
+ "@modelcontextprotocol/sdk": "^1.0.0",
24
+ "zod": "^3.23.0"
25
+ },
26
+ "devDependencies": {
27
+ "@types/node": "^20.0.0",
28
+ "typescript": "^5.3.0"
29
+ },
30
+ "engines": {
31
+ "node": ">=20.0.0"
32
+ },
33
+ "keywords": [
34
+ "mcp",
35
+ "model-context-protocol",
36
+ "ai-agents",
37
+ "claude",
38
+ "cursor",
39
+ "onto",
40
+ "markdown",
41
+ "web-scraping",
42
+ "agent-tools",
43
+ "aio-score"
44
+ ],
45
+ "license": "MIT",
46
+ "repository": {
47
+ "type": "git",
48
+ "url": "git+https://github.com/ravixalgorithm/onto-mcp.git"
49
+ },
50
+ "homepage": "https://buildonto.dev",
51
+ "bugs": {
52
+ "url": "https://github.com/ravixalgorithm/onto-mcp/issues"
53
+ },
54
+ "author": {
55
+ "name": "Onto",
56
+ "email": "founder@buildonto.dev",
57
+ "url": "https://buildonto.dev"
58
+ },
59
+ "publishConfig": {
60
+ "access": "public"
61
+ }
62
+ }