isagentready-mcp 0.2.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 +21 -0
- package/README.md +280 -0
- package/dist/cache.d.ts +10 -0
- package/dist/cache.js +36 -0
- package/dist/client.d.ts +27 -0
- package/dist/client.js +79 -0
- package/dist/format.d.ts +5 -0
- package/dist/format.js +37 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +28 -0
- package/dist/server.d.ts +5 -0
- package/dist/server.js +41 -0
- package/dist/tool-result.d.ts +14 -0
- package/dist/tool-result.js +61 -0
- package/dist/tools/rankings.d.ts +3 -0
- package/dist/tools/rankings.js +52 -0
- package/dist/tools/scans.d.ts +3 -0
- package/dist/tools/scans.js +67 -0
- package/dist/types.d.ts +49 -0
- package/dist/types.js +1 -0
- package/dist/update-checker.d.ts +2 -0
- package/dist/update-checker.js +26 -0
- package/package.json +62 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Bart Waardenburg
|
|
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,280 @@
|
|
|
1
|
+
# isagentready-mcp
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/isagentready-mcp)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
[](https://nodejs.org/)
|
|
6
|
+
[](https://github.com/bartwaardenburg/isagentready-mcp/actions/workflows/ci.yml)
|
|
7
|
+
[](https://bartwaardenburg.github.io/isagentready-mcp/)
|
|
8
|
+
[](https://modelcontextprotocol.io)
|
|
9
|
+
|
|
10
|
+
A [Model Context Protocol](https://modelcontextprotocol.io) (MCP) server for the [IsAgentReady](https://isagentready.com) API. Scan any website for AI agent readiness and get detailed reports with scores, letter grades, and actionable recommendations across 5 categories: Discovery, Structured Data, Semantics, Agent Protocols, and Security.
|
|
11
|
+
|
|
12
|
+
## Features
|
|
13
|
+
|
|
14
|
+
- **3 MCP tools** for scanning, retrieving results, and browsing rankings
|
|
15
|
+
- **No API key required** — public API, zero configuration needed
|
|
16
|
+
- **Built-in caching** with configurable TTL and automatic invalidation on scans
|
|
17
|
+
- **Retry logic** with exponential backoff and `Retry-After` header support
|
|
18
|
+
- **Structured content** returned alongside human-readable text
|
|
19
|
+
- **Toolset filtering** to expose only the tool categories you need
|
|
20
|
+
- **Actionable error messages** with context-aware recovery suggestions
|
|
21
|
+
- **Docker support** for containerized deployment
|
|
22
|
+
- **97 unit tests** across 7 test files
|
|
23
|
+
|
|
24
|
+
## Supported Clients
|
|
25
|
+
|
|
26
|
+
This MCP server works with any client that supports the Model Context Protocol, including:
|
|
27
|
+
|
|
28
|
+
| Client | Easiest install |
|
|
29
|
+
|---|---|
|
|
30
|
+
| [Claude Code](https://docs.anthropic.com/en/docs/claude-code) | One-liner: `claude mcp add` |
|
|
31
|
+
| [Codex CLI](https://github.com/openai/codex) (OpenAI) | One-liner: `codex mcp add` |
|
|
32
|
+
| [Gemini CLI](https://github.com/google-gemini/gemini-cli) (Google) | One-liner: `gemini mcp add` |
|
|
33
|
+
| [VS Code](https://code.visualstudio.com/) (Copilot) | Command Palette: `MCP: Add Server` |
|
|
34
|
+
| [Claude Desktop](https://claude.ai/download) | JSON config file |
|
|
35
|
+
| [Cursor](https://cursor.com) | JSON config file |
|
|
36
|
+
| [Windsurf](https://codeium.com/windsurf) | JSON config file |
|
|
37
|
+
| [Cline](https://github.com/cline/cline) | UI settings |
|
|
38
|
+
| [Zed](https://zed.dev) | JSON settings file |
|
|
39
|
+
|
|
40
|
+
## Installation
|
|
41
|
+
|
|
42
|
+
### Claude Code
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
claude mcp add isagentready-mcp -- npx -y isagentready-mcp
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Codex CLI (OpenAI)
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
codex mcp add isagentready-mcp -- npx -y isagentready-mcp
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Gemini CLI (Google)
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
gemini mcp add isagentready-mcp -- npx -y isagentready-mcp
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### VS Code (Copilot)
|
|
61
|
+
|
|
62
|
+
Open the Command Palette (`Cmd+Shift+P` / `Ctrl+Shift+P`) > `MCP: Add Server` > select **Command (stdio)**.
|
|
63
|
+
|
|
64
|
+
Or add to `.vscode/mcp.json` in your project directory:
|
|
65
|
+
|
|
66
|
+
```json
|
|
67
|
+
{
|
|
68
|
+
"servers": {
|
|
69
|
+
"isagentready-mcp": {
|
|
70
|
+
"type": "stdio",
|
|
71
|
+
"command": "npx",
|
|
72
|
+
"args": ["-y", "isagentready-mcp"]
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Claude Desktop / Cursor / Windsurf / Cline
|
|
79
|
+
|
|
80
|
+
These clients share the same JSON format. Add the config below to the appropriate file:
|
|
81
|
+
|
|
82
|
+
| Client | Config file |
|
|
83
|
+
|---|---|
|
|
84
|
+
| Claude Desktop (macOS) | `~/Library/Application Support/Claude/claude_desktop_config.json` |
|
|
85
|
+
| Claude Desktop (Windows) | `%APPDATA%\Claude\claude_desktop_config.json` |
|
|
86
|
+
| Cursor (project) | `.cursor/mcp.json` |
|
|
87
|
+
| Cursor (global) | `~/.cursor/mcp.json` |
|
|
88
|
+
| Windsurf | `~/.codeium/windsurf/mcp_config.json` |
|
|
89
|
+
| Cline | Settings > MCP Servers > Edit |
|
|
90
|
+
|
|
91
|
+
```json
|
|
92
|
+
{
|
|
93
|
+
"mcpServers": {
|
|
94
|
+
"isagentready-mcp": {
|
|
95
|
+
"command": "npx",
|
|
96
|
+
"args": ["-y", "isagentready-mcp"]
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Zed
|
|
103
|
+
|
|
104
|
+
Add to your Zed settings (`~/.zed/settings.json` on macOS, `~/.config/zed/settings.json` on Linux):
|
|
105
|
+
|
|
106
|
+
```json
|
|
107
|
+
{
|
|
108
|
+
"context_servers": {
|
|
109
|
+
"isagentready-mcp": {
|
|
110
|
+
"command": "npx",
|
|
111
|
+
"args": ["-y", "isagentready-mcp"]
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Docker
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
docker run -i --rm ghcr.io/bartwaardenburg/isagentready-mcp
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Codex CLI (TOML config alternative)
|
|
124
|
+
|
|
125
|
+
If you prefer editing `~/.codex/config.toml` directly:
|
|
126
|
+
|
|
127
|
+
```toml
|
|
128
|
+
[mcp_servers.isagentready-mcp]
|
|
129
|
+
command = "npx"
|
|
130
|
+
args = ["-y", "isagentready-mcp"]
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Other MCP Clients
|
|
134
|
+
|
|
135
|
+
For any MCP-compatible client, use this server configuration:
|
|
136
|
+
|
|
137
|
+
- **Command:** `npx`
|
|
138
|
+
- **Args:** `["-y", "isagentready-mcp"]`
|
|
139
|
+
- **No environment variables required**
|
|
140
|
+
|
|
141
|
+
## Configuration
|
|
142
|
+
|
|
143
|
+
All configuration is optional via environment variables:
|
|
144
|
+
|
|
145
|
+
| Variable | Description | Default |
|
|
146
|
+
|---|---|---|
|
|
147
|
+
| `ISAGENTREADY_CACHE_TTL` | Response cache lifetime in seconds. Set to `0` to disable caching. | `120` |
|
|
148
|
+
| `ISAGENTREADY_MAX_RETRIES` | Maximum retry attempts for rate-limited (429) requests with exponential backoff. | `3` |
|
|
149
|
+
| `ISAGENTREADY_BASE_URL` | API base URL. | `https://isagentready.com` |
|
|
150
|
+
| `ISAGENTREADY_TOOLSETS` | Comma-separated list of tool categories to enable (see [Toolset Filtering](#toolset-filtering)). | All toolsets |
|
|
151
|
+
|
|
152
|
+
### Example with configuration
|
|
153
|
+
|
|
154
|
+
```json
|
|
155
|
+
{
|
|
156
|
+
"mcpServers": {
|
|
157
|
+
"isagentready-mcp": {
|
|
158
|
+
"command": "npx",
|
|
159
|
+
"args": ["-y", "isagentready-mcp"],
|
|
160
|
+
"env": {
|
|
161
|
+
"ISAGENTREADY_CACHE_TTL": "300",
|
|
162
|
+
"ISAGENTREADY_MAX_RETRIES": "5"
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## Available Tools
|
|
170
|
+
|
|
171
|
+
### Scans
|
|
172
|
+
|
|
173
|
+
| Tool | Description |
|
|
174
|
+
|---|---|
|
|
175
|
+
| `scan_website` | Trigger a new scan of a website for AI agent readiness. Returns cached results if scanned within the last hour. |
|
|
176
|
+
| `get_scan_results` | Get the latest scan results for a domain with scores, grades, and recommendations. |
|
|
177
|
+
|
|
178
|
+
### Rankings
|
|
179
|
+
|
|
180
|
+
| Tool | Description |
|
|
181
|
+
|---|---|
|
|
182
|
+
| `get_rankings` | Browse paginated AI readiness rankings with filtering by grade range, search, and sorting. |
|
|
183
|
+
|
|
184
|
+
## Toolset Filtering
|
|
185
|
+
|
|
186
|
+
Reduce context window usage by enabling only the tool categories you need. Set the `ISAGENTREADY_TOOLSETS` environment variable to a comma-separated list:
|
|
187
|
+
|
|
188
|
+
```bash
|
|
189
|
+
ISAGENTREADY_TOOLSETS=scans
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
| Toolset | Tools included |
|
|
193
|
+
|---|---|
|
|
194
|
+
| `scans` | `scan_website`, `get_scan_results` |
|
|
195
|
+
| `rankings` | `get_rankings` |
|
|
196
|
+
|
|
197
|
+
When not set, all toolsets are enabled. Invalid names are ignored; if all names are invalid, all toolsets are enabled as a fallback.
|
|
198
|
+
|
|
199
|
+
## Example Usage
|
|
200
|
+
|
|
201
|
+
Once connected, you can interact with the IsAgentReady API using natural language:
|
|
202
|
+
|
|
203
|
+
- "Scan example.com for AI agent readiness"
|
|
204
|
+
- "Check if the scan results for example.com are ready"
|
|
205
|
+
- "Show me the top AI-ready websites"
|
|
206
|
+
- "Search the rankings for sites in the high grade range"
|
|
207
|
+
- "How does my site compare to others?"
|
|
208
|
+
|
|
209
|
+
### Scan + fix workflow
|
|
210
|
+
|
|
211
|
+
Combine with the [isagentready-skills](https://github.com/bartwaardenburg/isagentready-skills) agent skills for a full scan, fix, and re-verify workflow:
|
|
212
|
+
|
|
213
|
+
```
|
|
214
|
+
> Scan example.com for AI agent readiness
|
|
215
|
+
|
|
216
|
+
Scan enqueued for example.com
|
|
217
|
+
Status: pending
|
|
218
|
+
|
|
219
|
+
> Check the results for example.com
|
|
220
|
+
|
|
221
|
+
Domain: example.com
|
|
222
|
+
Grade: B (72/100)
|
|
223
|
+
Status: completed
|
|
224
|
+
|
|
225
|
+
Discovery — 15/20 (75%, weight: 30%)
|
|
226
|
+
[PASS] 1.1 robots.txt (5/5)
|
|
227
|
+
[FAIL] 1.2 sitemap.xml (0/5)
|
|
228
|
+
Recommendation: Add a sitemap.xml file...
|
|
229
|
+
|
|
230
|
+
> Fix the failing checkpoints
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
## Development
|
|
234
|
+
|
|
235
|
+
```bash
|
|
236
|
+
# Install dependencies
|
|
237
|
+
pnpm install
|
|
238
|
+
|
|
239
|
+
# Run in development mode
|
|
240
|
+
pnpm dev
|
|
241
|
+
|
|
242
|
+
# Build for production
|
|
243
|
+
pnpm build
|
|
244
|
+
|
|
245
|
+
# Run tests
|
|
246
|
+
pnpm test
|
|
247
|
+
|
|
248
|
+
# Type check
|
|
249
|
+
pnpm typecheck
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
### Project Structure
|
|
253
|
+
|
|
254
|
+
```
|
|
255
|
+
src/
|
|
256
|
+
index.ts # Entry point (stdio transport)
|
|
257
|
+
server.ts # MCP server setup, toolset filtering
|
|
258
|
+
client.ts # IsAgentReady API HTTP client with caching and retry
|
|
259
|
+
cache.ts # TTL-based in-memory response cache
|
|
260
|
+
types.ts # TypeScript interfaces
|
|
261
|
+
format.ts # Output formatting helpers
|
|
262
|
+
tool-result.ts # Error formatting with recovery suggestions
|
|
263
|
+
update-checker.ts # NPM update notifications
|
|
264
|
+
tools/
|
|
265
|
+
scans.ts # scan_website, get_scan_results
|
|
266
|
+
rankings.ts # get_rankings
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
## Requirements
|
|
270
|
+
|
|
271
|
+
- Node.js >= 20
|
|
272
|
+
|
|
273
|
+
## Related
|
|
274
|
+
|
|
275
|
+
- [isagentready-skills](https://github.com/bartwaardenburg/isagentready-skills) — Agent skills for fixing AI readiness issues identified by scans
|
|
276
|
+
- [IsAgentReady.com](https://isagentready.com) — The web scanner
|
|
277
|
+
|
|
278
|
+
## License
|
|
279
|
+
|
|
280
|
+
MIT — see [LICENSE](LICENSE) for details.
|
package/dist/cache.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare class TtlCache {
|
|
2
|
+
private readonly store;
|
|
3
|
+
private readonly defaultTtlMs;
|
|
4
|
+
constructor(defaultTtlMs?: number);
|
|
5
|
+
get<T>(key: string): T | undefined;
|
|
6
|
+
set<T>(key: string, value: T, ttlMs?: number): void;
|
|
7
|
+
invalidate(pattern: string): void;
|
|
8
|
+
clear(): void;
|
|
9
|
+
get size(): number;
|
|
10
|
+
}
|
package/dist/cache.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export class TtlCache {
|
|
2
|
+
store = new Map();
|
|
3
|
+
defaultTtlMs;
|
|
4
|
+
constructor(defaultTtlMs = 120_000) {
|
|
5
|
+
this.defaultTtlMs = defaultTtlMs;
|
|
6
|
+
}
|
|
7
|
+
get(key) {
|
|
8
|
+
const entry = this.store.get(key);
|
|
9
|
+
if (!entry)
|
|
10
|
+
return undefined;
|
|
11
|
+
if (Date.now() > entry.expiresAt) {
|
|
12
|
+
this.store.delete(key);
|
|
13
|
+
return undefined;
|
|
14
|
+
}
|
|
15
|
+
return entry.value;
|
|
16
|
+
}
|
|
17
|
+
set(key, value, ttlMs) {
|
|
18
|
+
this.store.set(key, {
|
|
19
|
+
value,
|
|
20
|
+
expiresAt: Date.now() + (ttlMs ?? this.defaultTtlMs),
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
invalidate(pattern) {
|
|
24
|
+
for (const key of this.store.keys()) {
|
|
25
|
+
if (key.includes(pattern)) {
|
|
26
|
+
this.store.delete(key);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
clear() {
|
|
31
|
+
this.store.clear();
|
|
32
|
+
}
|
|
33
|
+
get size() {
|
|
34
|
+
return this.store.size;
|
|
35
|
+
}
|
|
36
|
+
}
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { ScanResult, ScanPending, ScanEnqueued, RankingsResponse } from "./types.js";
|
|
2
|
+
export declare class IsAgentReadyApiError extends Error {
|
|
3
|
+
readonly status: number;
|
|
4
|
+
readonly details?: unknown | undefined;
|
|
5
|
+
constructor(message: string, status: number, details?: unknown | undefined);
|
|
6
|
+
}
|
|
7
|
+
interface ClientOptions {
|
|
8
|
+
maxRetries?: number;
|
|
9
|
+
cacheTtlMs?: number;
|
|
10
|
+
}
|
|
11
|
+
export declare class IsAgentReadyClient {
|
|
12
|
+
private readonly baseUrl;
|
|
13
|
+
private readonly cache;
|
|
14
|
+
private readonly maxRetries;
|
|
15
|
+
constructor(baseUrl?: string, options?: ClientOptions);
|
|
16
|
+
private request;
|
|
17
|
+
getScanResults(domain: string): Promise<ScanResult | ScanPending>;
|
|
18
|
+
createScan(url: string): Promise<ScanEnqueued | ScanResult>;
|
|
19
|
+
getRankings(params?: {
|
|
20
|
+
page?: number;
|
|
21
|
+
per_page?: number;
|
|
22
|
+
grade_range?: "high" | "mid" | "low";
|
|
23
|
+
search?: string;
|
|
24
|
+
sort?: "score_desc" | "score_asc" | "domain" | "newest";
|
|
25
|
+
}): Promise<RankingsResponse>;
|
|
26
|
+
}
|
|
27
|
+
export {};
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { TtlCache } from "./cache.js";
|
|
2
|
+
export class IsAgentReadyApiError extends Error {
|
|
3
|
+
status;
|
|
4
|
+
details;
|
|
5
|
+
constructor(message, status, details) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.status = status;
|
|
8
|
+
this.details = details;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
12
|
+
export class IsAgentReadyClient {
|
|
13
|
+
baseUrl;
|
|
14
|
+
cache;
|
|
15
|
+
maxRetries;
|
|
16
|
+
constructor(baseUrl = "https://isagentready.com", options = {}) {
|
|
17
|
+
this.baseUrl = baseUrl.replace(/\/$/, "");
|
|
18
|
+
this.cache = new TtlCache(options.cacheTtlMs ?? 120_000);
|
|
19
|
+
this.maxRetries = options.maxRetries ?? 3;
|
|
20
|
+
}
|
|
21
|
+
async request(method, path, body, cacheKey) {
|
|
22
|
+
if (cacheKey) {
|
|
23
|
+
const cached = this.cache.get(cacheKey);
|
|
24
|
+
if (cached !== undefined)
|
|
25
|
+
return cached;
|
|
26
|
+
}
|
|
27
|
+
for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
|
|
28
|
+
const url = `${this.baseUrl}${path}`;
|
|
29
|
+
const headers = new Headers({ "Content-Type": "application/json" });
|
|
30
|
+
const init = { method, headers };
|
|
31
|
+
if (body) {
|
|
32
|
+
init.body = JSON.stringify(body);
|
|
33
|
+
}
|
|
34
|
+
const response = await fetch(url, init);
|
|
35
|
+
if (response.status === 429 && attempt < this.maxRetries) {
|
|
36
|
+
const retryAfter = response.headers.get("retry-after");
|
|
37
|
+
const delayMs = retryAfter ? parseInt(retryAfter, 10) * 1000 : Math.pow(2, attempt) * 1000;
|
|
38
|
+
await sleep(delayMs);
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
if (!response.ok) {
|
|
42
|
+
const errorBody = await response.json().catch(() => null);
|
|
43
|
+
throw new IsAgentReadyApiError(errorBody?.error ?? `HTTP ${response.status}`, response.status, errorBody);
|
|
44
|
+
}
|
|
45
|
+
const data = (await response.json());
|
|
46
|
+
if (cacheKey) {
|
|
47
|
+
this.cache.set(cacheKey, data);
|
|
48
|
+
}
|
|
49
|
+
return data;
|
|
50
|
+
}
|
|
51
|
+
throw new Error("Request failed after retries");
|
|
52
|
+
}
|
|
53
|
+
async getScanResults(domain) {
|
|
54
|
+
const encoded = encodeURIComponent(domain);
|
|
55
|
+
return this.request("GET", `/api/v1/scan/${encoded}`, undefined, `scan:${domain}`);
|
|
56
|
+
}
|
|
57
|
+
async createScan(url) {
|
|
58
|
+
this.cache.invalidate("scan:");
|
|
59
|
+
this.cache.invalidate("rankings:");
|
|
60
|
+
return this.request("POST", "/api/v1/scan", { url });
|
|
61
|
+
}
|
|
62
|
+
async getRankings(params = {}) {
|
|
63
|
+
const searchParams = new URLSearchParams();
|
|
64
|
+
if (params.page !== undefined)
|
|
65
|
+
searchParams.set("page", String(params.page));
|
|
66
|
+
if (params.per_page !== undefined)
|
|
67
|
+
searchParams.set("per_page", String(params.per_page));
|
|
68
|
+
if (params.grade_range)
|
|
69
|
+
searchParams.set("grade_range", params.grade_range);
|
|
70
|
+
if (params.search)
|
|
71
|
+
searchParams.set("search", params.search);
|
|
72
|
+
if (params.sort)
|
|
73
|
+
searchParams.set("sort", params.sort);
|
|
74
|
+
const query = searchParams.toString();
|
|
75
|
+
const path = `/api/v1/rankings${query ? `?${query}` : ""}`;
|
|
76
|
+
const cacheKey = `rankings:${query}`;
|
|
77
|
+
return this.request("GET", path, undefined, cacheKey);
|
|
78
|
+
}
|
|
79
|
+
}
|
package/dist/format.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { ScanResult, Category, Checkpoint } from "./types.js";
|
|
2
|
+
export declare const formatCheckpoint: (cp: Checkpoint) => string;
|
|
3
|
+
export declare const formatCategory: (cat: Category) => string;
|
|
4
|
+
export declare const formatScanResult: (scan: ScanResult) => string;
|
|
5
|
+
export declare const formatScanSummary: (scan: ScanResult) => string;
|
package/dist/format.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
const statusIcon = {
|
|
2
|
+
pass: "[PASS]",
|
|
3
|
+
partial: "[PARTIAL]",
|
|
4
|
+
fail: "[FAIL]",
|
|
5
|
+
skip: "[SKIP]",
|
|
6
|
+
};
|
|
7
|
+
export const formatCheckpoint = (cp) => {
|
|
8
|
+
const lines = [
|
|
9
|
+
` ${statusIcon[cp.status] ?? cp.status} ${cp.id} ${cp.name} (${cp.score}/${cp.max_score})`,
|
|
10
|
+
];
|
|
11
|
+
if (cp.details)
|
|
12
|
+
lines.push(` Details: ${cp.details}`);
|
|
13
|
+
if (cp.status !== "pass" && cp.recommendation)
|
|
14
|
+
lines.push(` Recommendation: ${cp.recommendation}`);
|
|
15
|
+
return lines.join("\n");
|
|
16
|
+
};
|
|
17
|
+
export const formatCategory = (cat) => {
|
|
18
|
+
const pct = cat.max_score > 0 ? Math.round((cat.score / cat.max_score) * 100) : 0;
|
|
19
|
+
const lines = [
|
|
20
|
+
`${cat.label} — ${cat.score}/${cat.max_score} (${pct}%, weight: ${cat.weight}%)`,
|
|
21
|
+
...cat.checkpoints.map(formatCheckpoint),
|
|
22
|
+
];
|
|
23
|
+
return lines.join("\n");
|
|
24
|
+
};
|
|
25
|
+
export const formatScanResult = (scan) => {
|
|
26
|
+
const lines = [
|
|
27
|
+
`Domain: ${scan.domain}`,
|
|
28
|
+
`Grade: ${scan.letter_grade} (${scan.overall_score}/100)`,
|
|
29
|
+
`Status: ${scan.status}`,
|
|
30
|
+
scan.scan_duration_ms ? `Scan duration: ${scan.scan_duration_ms}ms` : "",
|
|
31
|
+
scan.completed_at ? `Completed: ${scan.completed_at}` : "",
|
|
32
|
+
"",
|
|
33
|
+
...(scan.categories ?? []).map(formatCategory),
|
|
34
|
+
];
|
|
35
|
+
return lines.filter(Boolean).join("\n");
|
|
36
|
+
};
|
|
37
|
+
export const formatScanSummary = (scan) => `${scan.domain} — Grade: ${scan.letter_grade} (${scan.overall_score}/100)`;
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { IsAgentReadyClient } from "./client.js";
|
|
5
|
+
import { createServer, parseToolsets } from "./server.js";
|
|
6
|
+
import { checkForUpdate } from "./update-checker.js";
|
|
7
|
+
const require = createRequire(import.meta.url);
|
|
8
|
+
const { name, version } = require("../package.json");
|
|
9
|
+
const cacheTtl = process.env.ISAGENTREADY_CACHE_TTL !== undefined
|
|
10
|
+
? parseInt(process.env.ISAGENTREADY_CACHE_TTL, 10) * 1000
|
|
11
|
+
: undefined;
|
|
12
|
+
const maxRetries = process.env.ISAGENTREADY_MAX_RETRIES !== undefined
|
|
13
|
+
? parseInt(process.env.ISAGENTREADY_MAX_RETRIES, 10)
|
|
14
|
+
: 3;
|
|
15
|
+
const baseUrl = process.env.ISAGENTREADY_BASE_URL ?? "https://isagentready.com";
|
|
16
|
+
const client = new IsAgentReadyClient(baseUrl, { maxRetries, cacheTtlMs: cacheTtl });
|
|
17
|
+
const toolsets = parseToolsets(process.env.ISAGENTREADY_TOOLSETS);
|
|
18
|
+
const server = createServer(client, toolsets);
|
|
19
|
+
const main = async () => {
|
|
20
|
+
const transport = new StdioServerTransport();
|
|
21
|
+
await server.connect(transport);
|
|
22
|
+
// Fire-and-forget — don't block server startup
|
|
23
|
+
void checkForUpdate(name, version);
|
|
24
|
+
};
|
|
25
|
+
main().catch((error) => {
|
|
26
|
+
console.error("IsAgentReady MCP server failed:", error);
|
|
27
|
+
process.exit(1);
|
|
28
|
+
});
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import type { IsAgentReadyClient } from "./client.js";
|
|
3
|
+
export type Toolset = "scans" | "rankings";
|
|
4
|
+
export declare const parseToolsets: (env?: string) => Set<Toolset>;
|
|
5
|
+
export declare const createServer: (client: IsAgentReadyClient, toolsets?: Set<Toolset>) => McpServer;
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { registerScanTools } from "./tools/scans.js";
|
|
4
|
+
import { registerRankingTools } from "./tools/rankings.js";
|
|
5
|
+
const require = createRequire(import.meta.url);
|
|
6
|
+
const { version } = require("../package.json");
|
|
7
|
+
const ALL_TOOLSETS = ["scans", "rankings"];
|
|
8
|
+
export const parseToolsets = (env) => {
|
|
9
|
+
if (!env)
|
|
10
|
+
return new Set(ALL_TOOLSETS);
|
|
11
|
+
const requested = env.split(",").map((s) => s.trim().toLowerCase());
|
|
12
|
+
const valid = new Set();
|
|
13
|
+
for (const name of requested) {
|
|
14
|
+
if (ALL_TOOLSETS.includes(name)) {
|
|
15
|
+
valid.add(name);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return valid.size > 0 ? valid : new Set(ALL_TOOLSETS);
|
|
19
|
+
};
|
|
20
|
+
const toolsetRegistry = {
|
|
21
|
+
scans: [registerScanTools],
|
|
22
|
+
rankings: [registerRankingTools],
|
|
23
|
+
};
|
|
24
|
+
export const createServer = (client, toolsets) => {
|
|
25
|
+
const server = new McpServer({
|
|
26
|
+
name: "isagentready-mcp",
|
|
27
|
+
version,
|
|
28
|
+
});
|
|
29
|
+
const enabled = toolsets ?? new Set(ALL_TOOLSETS);
|
|
30
|
+
const registered = new Set();
|
|
31
|
+
for (const toolset of enabled) {
|
|
32
|
+
const registerers = toolsetRegistry[toolset];
|
|
33
|
+
for (const register of registerers) {
|
|
34
|
+
if (!registered.has(register)) {
|
|
35
|
+
registered.add(register);
|
|
36
|
+
register(server, client);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return server;
|
|
41
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export declare const toTextResult: (text: string, structuredContent?: Record<string, unknown>) => {
|
|
2
|
+
structuredContent?: Record<string, unknown> | undefined;
|
|
3
|
+
content: {
|
|
4
|
+
type: "text";
|
|
5
|
+
text: string;
|
|
6
|
+
}[];
|
|
7
|
+
};
|
|
8
|
+
export declare const toErrorResult: (error: unknown) => {
|
|
9
|
+
content: {
|
|
10
|
+
type: "text";
|
|
11
|
+
text: string;
|
|
12
|
+
}[];
|
|
13
|
+
isError: boolean;
|
|
14
|
+
};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { IsAgentReadyApiError } from "./client.js";
|
|
2
|
+
export const toTextResult = (text, structuredContent) => ({
|
|
3
|
+
content: [{ type: "text", text }],
|
|
4
|
+
...(structuredContent ? { structuredContent } : {}),
|
|
5
|
+
});
|
|
6
|
+
const getRecoverySuggestion = (status, message, details) => {
|
|
7
|
+
if (status === 429) {
|
|
8
|
+
return "Rate limit exceeded. Wait a moment and retry. If scanning, note that recently scanned domains have a 1-hour cooldown.";
|
|
9
|
+
}
|
|
10
|
+
if (status === 404) {
|
|
11
|
+
return "No scan found for this domain. Use scan_website to trigger a new scan first.";
|
|
12
|
+
}
|
|
13
|
+
if (status === 400) {
|
|
14
|
+
const detailStr = typeof details === "string" ? details : JSON.stringify(details ?? "");
|
|
15
|
+
const lower = detailStr.toLowerCase();
|
|
16
|
+
if (lower.includes("url")) {
|
|
17
|
+
return "Invalid or missing URL. Ensure the URL starts with http:// or https://.";
|
|
18
|
+
}
|
|
19
|
+
return "Missing required parameter. Ensure the URL or domain is provided.";
|
|
20
|
+
}
|
|
21
|
+
if (status === 409) {
|
|
22
|
+
return "Conflict — a scan for this domain is already in progress. Wait for it to complete, then use get_scan_results to retrieve the results.";
|
|
23
|
+
}
|
|
24
|
+
if (status === 422) {
|
|
25
|
+
return "Invalid URL. The URL must start with http:// or https:// and be a valid website address.";
|
|
26
|
+
}
|
|
27
|
+
if (status >= 500) {
|
|
28
|
+
return "IsAgentReady API server error. This is a temporary issue. Wait a moment and retry.";
|
|
29
|
+
}
|
|
30
|
+
return null;
|
|
31
|
+
};
|
|
32
|
+
export const toErrorResult = (error) => {
|
|
33
|
+
if (error instanceof IsAgentReadyApiError) {
|
|
34
|
+
const suggestion = getRecoverySuggestion(error.status, error.message, error.details);
|
|
35
|
+
return {
|
|
36
|
+
content: [
|
|
37
|
+
{
|
|
38
|
+
type: "text",
|
|
39
|
+
text: [
|
|
40
|
+
`IsAgentReady API error: ${error.message}`,
|
|
41
|
+
`Status: ${error.status}`,
|
|
42
|
+
error.details ? `Details: ${JSON.stringify(error.details, null, 2)}` : "",
|
|
43
|
+
suggestion ? `\nRecovery: ${suggestion}` : "",
|
|
44
|
+
]
|
|
45
|
+
.filter(Boolean)
|
|
46
|
+
.join("\n"),
|
|
47
|
+
},
|
|
48
|
+
],
|
|
49
|
+
isError: true,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
content: [
|
|
54
|
+
{
|
|
55
|
+
type: "text",
|
|
56
|
+
text: error instanceof Error ? error.message : String(error),
|
|
57
|
+
},
|
|
58
|
+
],
|
|
59
|
+
isError: true,
|
|
60
|
+
};
|
|
61
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import * as z from "zod/v4";
|
|
2
|
+
import { formatScanSummary } from "../format.js";
|
|
3
|
+
import { toTextResult, toErrorResult } from "../tool-result.js";
|
|
4
|
+
export const registerRankingTools = (server, client) => {
|
|
5
|
+
server.registerTool("get_rankings", {
|
|
6
|
+
title: "Get AI Readiness Rankings",
|
|
7
|
+
description: "Get a paginated, sorted list of website AI readiness scores. Supports filtering by grade range (high/mid/low), search by domain name, and sorting by score, domain, or newest.",
|
|
8
|
+
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: true },
|
|
9
|
+
inputSchema: z.object({
|
|
10
|
+
page: z.number().int().min(1).default(1).describe("Page number (default: 1)"),
|
|
11
|
+
per_page: z
|
|
12
|
+
.number()
|
|
13
|
+
.int()
|
|
14
|
+
.min(1)
|
|
15
|
+
.max(100)
|
|
16
|
+
.default(25)
|
|
17
|
+
.describe("Results per page (1-100, default: 25)"),
|
|
18
|
+
grade_range: z
|
|
19
|
+
.enum(["high", "mid", "low"])
|
|
20
|
+
.optional()
|
|
21
|
+
.describe("Filter by grade range: high (A/A+), mid (B/C), low (D/F)"),
|
|
22
|
+
search: z.string().optional().describe("Search by domain name"),
|
|
23
|
+
sort: z
|
|
24
|
+
.enum(["score_desc", "score_asc", "domain", "newest"])
|
|
25
|
+
.default("score_desc")
|
|
26
|
+
.describe("Sort order (default: score_desc)"),
|
|
27
|
+
}),
|
|
28
|
+
}, async ({ page, per_page, grade_range, search, sort }) => {
|
|
29
|
+
try {
|
|
30
|
+
const result = await client.getRankings({ page, per_page, grade_range, search, sort });
|
|
31
|
+
const lines = [
|
|
32
|
+
`AI Readiness Rankings — Page ${result.page}/${result.total_pages} (${result.total} total)`,
|
|
33
|
+
"",
|
|
34
|
+
...result.entries.map((entry, i) => `${(result.page - 1) * result.per_page + i + 1}. ${formatScanSummary(entry)}`),
|
|
35
|
+
];
|
|
36
|
+
if (result.entries.length === 0) {
|
|
37
|
+
lines.push("No results found.");
|
|
38
|
+
}
|
|
39
|
+
return toTextResult(lines.join("\n"), {
|
|
40
|
+
type: "rankings",
|
|
41
|
+
page: result.page,
|
|
42
|
+
per_page: result.per_page,
|
|
43
|
+
total: result.total,
|
|
44
|
+
total_pages: result.total_pages,
|
|
45
|
+
entries: result.entries,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
return toErrorResult(error);
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import * as z from "zod/v4";
|
|
2
|
+
import { formatScanResult } from "../format.js";
|
|
3
|
+
import { toTextResult, toErrorResult } from "../tool-result.js";
|
|
4
|
+
const isScanCompleted = (result) => result.status === "completed" || result.status === "failed";
|
|
5
|
+
export const registerScanTools = (server, client) => {
|
|
6
|
+
server.registerTool("scan_website", {
|
|
7
|
+
title: "Scan Website for AI Agent Readiness",
|
|
8
|
+
description: "Trigger a new scan of a website for AI agent readiness. The scan runs asynchronously and typically takes 15-30 seconds. If a recent scan exists (within 1 hour), returns cached results. Use get_scan_results to poll for completed results.",
|
|
9
|
+
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true },
|
|
10
|
+
inputSchema: z.object({
|
|
11
|
+
url: z
|
|
12
|
+
.string()
|
|
13
|
+
.min(1)
|
|
14
|
+
.describe("The full URL to scan (must be http:// or https://)"),
|
|
15
|
+
}),
|
|
16
|
+
}, async ({ url }) => {
|
|
17
|
+
try {
|
|
18
|
+
const result = await client.createScan(url);
|
|
19
|
+
if (isScanCompleted(result)) {
|
|
20
|
+
return toTextResult(`Recent scan found (cached):\n\n${formatScanResult(result)}`, { type: "cached_result", ...result });
|
|
21
|
+
}
|
|
22
|
+
return toTextResult([
|
|
23
|
+
`Scan enqueued for ${result.domain}`,
|
|
24
|
+
`Status: ${result.status}`,
|
|
25
|
+
`ID: ${result.id}`,
|
|
26
|
+
"message" in result ? `Message: ${result.message}` : "",
|
|
27
|
+
"",
|
|
28
|
+
`Poll with get_scan_results for domain "${result.domain}" to check when results are ready.`,
|
|
29
|
+
]
|
|
30
|
+
.filter(Boolean)
|
|
31
|
+
.join("\n"), { type: "enqueued", id: result.id, domain: result.domain, status: result.status });
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
return toErrorResult(error);
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
server.registerTool("get_scan_results", {
|
|
38
|
+
title: "Get Scan Results",
|
|
39
|
+
description: "Get the latest scan results for a domain, including overall score, letter grade, per-category breakdowns, and individual checkpoint results with actionable recommendations. Returns a pending status if the scan is still in progress.",
|
|
40
|
+
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: true },
|
|
41
|
+
inputSchema: z.object({
|
|
42
|
+
domain: z
|
|
43
|
+
.string()
|
|
44
|
+
.min(1)
|
|
45
|
+
.describe("The domain to get scan results for (e.g., example.com)"),
|
|
46
|
+
}),
|
|
47
|
+
}, async ({ domain }) => {
|
|
48
|
+
try {
|
|
49
|
+
const result = await client.getScanResults(domain);
|
|
50
|
+
if (!isScanCompleted(result)) {
|
|
51
|
+
const message = "message" in result ? String(result.message) : "";
|
|
52
|
+
return toTextResult([
|
|
53
|
+
`Scan for ${result.domain} is ${result.status}.`,
|
|
54
|
+
message,
|
|
55
|
+
"",
|
|
56
|
+
"Poll again in a few seconds to check for completion.",
|
|
57
|
+
]
|
|
58
|
+
.filter(Boolean)
|
|
59
|
+
.join("\n"), { type: "pending", id: result.id, domain: result.domain, status: result.status });
|
|
60
|
+
}
|
|
61
|
+
return toTextResult(formatScanResult(result), { type: "completed", ...result });
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
return toErrorResult(error);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
};
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
export interface Checkpoint {
|
|
2
|
+
id: string;
|
|
3
|
+
name: string;
|
|
4
|
+
status: "pass" | "partial" | "fail" | "skip";
|
|
5
|
+
score: number;
|
|
6
|
+
max_score: number;
|
|
7
|
+
details: string;
|
|
8
|
+
recommendation: string;
|
|
9
|
+
why: string;
|
|
10
|
+
code_example?: string;
|
|
11
|
+
}
|
|
12
|
+
export interface Category {
|
|
13
|
+
category: "discovery" | "structured_data" | "semantics" | "agent_protocols" | "security";
|
|
14
|
+
label: string;
|
|
15
|
+
score: number;
|
|
16
|
+
max_score: number;
|
|
17
|
+
weight: number;
|
|
18
|
+
checkpoints: Checkpoint[];
|
|
19
|
+
}
|
|
20
|
+
export interface ScanResult {
|
|
21
|
+
id: string;
|
|
22
|
+
domain: string;
|
|
23
|
+
status: "completed" | "failed";
|
|
24
|
+
overall_score: number;
|
|
25
|
+
letter_grade: "F" | "D" | "C" | "B" | "A" | "A+";
|
|
26
|
+
scan_duration_ms: number;
|
|
27
|
+
completed_at: string;
|
|
28
|
+
categories: Category[];
|
|
29
|
+
}
|
|
30
|
+
export interface ScanPending {
|
|
31
|
+
id: string;
|
|
32
|
+
domain: string;
|
|
33
|
+
status: "pending" | "running";
|
|
34
|
+
message: string;
|
|
35
|
+
}
|
|
36
|
+
export interface ScanEnqueued {
|
|
37
|
+
id: string;
|
|
38
|
+
domain: string;
|
|
39
|
+
status: "pending" | "running";
|
|
40
|
+
poll_url: string;
|
|
41
|
+
message: string;
|
|
42
|
+
}
|
|
43
|
+
export interface RankingsResponse {
|
|
44
|
+
entries: ScanResult[];
|
|
45
|
+
total: number;
|
|
46
|
+
page: number;
|
|
47
|
+
per_page: number;
|
|
48
|
+
total_pages: number;
|
|
49
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export const isNewerVersion = (latest, current) => {
|
|
2
|
+
const l = latest.split(".").map(Number);
|
|
3
|
+
const c = current.split(".").map(Number);
|
|
4
|
+
for (let i = 0; i < 3; i++) {
|
|
5
|
+
if ((l[i] ?? 0) > (c[i] ?? 0))
|
|
6
|
+
return true;
|
|
7
|
+
if ((l[i] ?? 0) < (c[i] ?? 0))
|
|
8
|
+
return false;
|
|
9
|
+
}
|
|
10
|
+
return false;
|
|
11
|
+
};
|
|
12
|
+
export const checkForUpdate = async (packageName, currentVersion) => {
|
|
13
|
+
try {
|
|
14
|
+
const response = await fetch(`https://registry.npmjs.org/${packageName}/latest`);
|
|
15
|
+
if (!response.ok)
|
|
16
|
+
return;
|
|
17
|
+
const data = (await response.json());
|
|
18
|
+
if (isNewerVersion(data.version, currentVersion)) {
|
|
19
|
+
process.stderr.write(`\n Update available: ${currentVersion} → ${data.version}\n` +
|
|
20
|
+
` Run: npm install -g ${packageName}@latest\n\n`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
// Best-effort — silently ignore network errors
|
|
25
|
+
}
|
|
26
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "isagentready-mcp",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"packageManager": "pnpm@10.28.1",
|
|
5
|
+
"description": "MCP server for scanning websites for AI agent readiness via the IsAgentReady API",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"isagentready-mcp": "dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist"
|
|
12
|
+
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"dev": "tsx src/index.ts",
|
|
15
|
+
"start": "node --enable-source-maps dist/index.js",
|
|
16
|
+
"build": "tsc -p tsconfig.json",
|
|
17
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
18
|
+
"test": "vitest run",
|
|
19
|
+
"prepublishOnly": "pnpm build",
|
|
20
|
+
"release": "GITHUB_TOKEN=$(gh auth token) release-it"
|
|
21
|
+
},
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "git+https://github.com/bartwaardenburg/isagentready-mcp.git"
|
|
25
|
+
},
|
|
26
|
+
"keywords": [
|
|
27
|
+
"mcp",
|
|
28
|
+
"mcp-server",
|
|
29
|
+
"isagentready",
|
|
30
|
+
"ai-readiness",
|
|
31
|
+
"website-scanner",
|
|
32
|
+
"agent-readiness",
|
|
33
|
+
"model-context-protocol",
|
|
34
|
+
"automation",
|
|
35
|
+
"ai",
|
|
36
|
+
"claude",
|
|
37
|
+
"llm"
|
|
38
|
+
],
|
|
39
|
+
"author": "Bart Waardenburg",
|
|
40
|
+
"license": "MIT",
|
|
41
|
+
"mcpName": "io.github.BartWaardenburg/isagentready-mcp",
|
|
42
|
+
"bugs": {
|
|
43
|
+
"url": "https://github.com/bartwaardenburg/isagentready-mcp/issues"
|
|
44
|
+
},
|
|
45
|
+
"homepage": "https://github.com/bartwaardenburg/isagentready-mcp#readme",
|
|
46
|
+
"dependencies": {
|
|
47
|
+
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
48
|
+
"zod": "^4.3.6"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"@release-it/conventional-changelog": "^10.0.5",
|
|
52
|
+
"@types/node": "^20.19.30",
|
|
53
|
+
"@vitest/coverage-v8": "^4.0.18",
|
|
54
|
+
"release-it": "^19.2.4",
|
|
55
|
+
"tsx": "^4.21.0",
|
|
56
|
+
"typescript": "^5.9.2",
|
|
57
|
+
"vitest": "^4.0.18"
|
|
58
|
+
},
|
|
59
|
+
"engines": {
|
|
60
|
+
"node": ">=20"
|
|
61
|
+
}
|
|
62
|
+
}
|