├── .github └── workflows │ └── deno-ci-check.yml ├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── README_EN.md ├── baseline-mcp-server.ts ├── constants.ts ├── deno.json ├── deno.lock ├── glama.json ├── logo.png ├── renovate.json ├── screenshot_claude_desktop.png ├── tools ├── getNegatedBrowserBaselineStatusAsMCPContent.test.ts ├── getNegatedBrowserBaselineStatusAsMCPContent.ts ├── getWebFeatureBaselineStatusAsMCPContent.test.ts ├── getWebFeatureBaselineStatusAsMCPContent.ts ├── helpers │ ├── getFeatureStatus.test.ts │ └── getFeatureStatus.ts └── index.ts └── types.ts /.github/workflows/deno-ci-check.yml: -------------------------------------------------------------------------------- 1 | name: Deno CI Check 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - "main" 7 | paths: 8 | - "**.ts" 9 | - "deno.json" 10 | - ".github/workflows/deno-ci-check.yml" 11 | 12 | permissions: 13 | contents: read 14 | 15 | jobs: 16 | test: 17 | runs-on: ubuntu-latest 18 | 19 | steps: 20 | - name: Setup repo 21 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 22 | 23 | - name: Setup Deno 24 | uses: denoland/setup-deno@e95548e56dfa95d4e1a28d6f422fafe75c4c26fb # v2.0.3 25 | with: 26 | deno-version: v2.x 27 | 28 | - name: Verify formatting 29 | run: deno fmt --check 30 | 31 | - name: Run linter 32 | run: deno lint 33 | 34 | - name: Run tests 35 | run: deno test -A 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/.claude/settings.local.json 2 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM denoland/deno:latest 2 | 3 | # Create the application directory 4 | WORKDIR /app 5 | 6 | # Copy dependency files first for caching 7 | COPY deno.json deno.lock* ./ 8 | 9 | # Copy the rest of the application code 10 | COPY . . 11 | 12 | # Cache application dependencies 13 | RUN deno cache baseline-mcp-server.ts 14 | 15 | # Run the application 16 | CMD ["run", "--allow-net=api.webstatus.dev", "baseline-mcp-server.ts"] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) <0910yama@gmail.com> (yamanoku.net) 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. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Baseline MCP Serverロゴ 3 |

4 | 5 |

Baseline MCP Server

6 | 7 | [日本語版](./README.md) | [English Version](./README_EN.md) 8 | 9 | Web Platform APIのサポート状況を提供するModel Context Protocolサーバーです。 10 | 11 | [![JSR Version](https://jsr.io/badges/@yamanoku/baseline-mcp-server)](https://jsr.io/@yamanoku/baseline-mcp-server) 12 | 13 | ## 概要 14 | 15 | このサーバーは、[Web Platform Dashboard](https://webstatus.dev/)のAPIを使用して、WebのAPI機能のBaselineステータス(サポート状況)を取得できるMCPサーバーを実装しています。クエリに基づいてWeb機能の情報を取得し、その結果をMCPクライアントに返します。 16 | 17 | ![Claude Desktop上でdetails要素にまつわるBaseline情報を質問してMCPサーバーを経由してその結果が反映されている。内容は
要素、相互排他的な
要素、::details-content疑似要素のそれぞれをリストアップしてBaselineの情報を伝えている。](./screenshot_claude_desktop.png) 18 | 19 | ## 機能 20 | 21 | - Web Platform DashboardのAPIを使用した機能検索 22 | - 機能のBaselineステータス(`widely`、`newly`、`limited`、`no_data`)の提供 23 | - ブラウザ実装状況(バージョンと実装日)の提供 24 | - 機能の使用状況データの提供 25 | - 特定のブラウザを除外した機能検索(`chrome`, `edge`, `firefox`, `safari`) 26 | - MCPを介した各種AIモデルとの連携 27 | 28 | ## Baselineステータスについて 29 | 30 | Baselineステータスは、Web機能のブラウザサポート状況を示します: 31 | 32 | - **widely**: 33 | 広くサポートされているWeb標準機能。ほとんどのブラウザで安全に使用できます。 34 | - **newly**: 35 | 新しく標準化されたWeb機能。主要なブラウザでサポートされ始めていますが、まだ普及途上です。 36 | - **limited**: 37 | 限定的にサポートされているWeb機能。一部のブラウザでは使用できないか、フラグが必要な場合があります。 38 | - **no_data**: 39 | 現時点ではBaselineに含まれていないWeb機能。ブラウザのサポート状況を個別に確認する必要があります。 40 | 41 | Baselineについての詳細については「[Baseline (互換性) - MDN Web Docs 用語集](https://developer.mozilla.org/ja/docs/Glossary/Baseline/Compatibility)」を参照してください。 42 | 43 | ## MCPクライアントでの設定 44 | 45 | - サーバーを起動するにあたり、Denoの使用を推奨します 46 | - パーミッションとして`api.webstatus.dev`のみのアクセスを許可してください 47 | - [`@yamanoku/baseline-mcp-server`](https://jsr.io/@yamanoku/baseline-mcp-server)を指定するか、お手元のローカル環境にbaseline-mcp-server.tsを設置して読み取るように設定してください 48 | 49 | ### Claude Desktop 50 | 51 | Claude 52 | DesktopのMCPクライアントで使用するには、以下のように`cline_mcp_settings.json`に設定を追加します。 53 | 54 | ```json 55 | { 56 | "mcpServers": { 57 | "baseline-mcp-server": { 58 | "command": "deno", 59 | "args": [ 60 | "run", 61 | "--allow-net=api.webstatus.dev", 62 | "jsr:@yamanoku/baseline-mcp-server" 63 | ] 64 | } 65 | } 66 | } 67 | ``` 68 | 69 | ### Visual Studio Code 70 | 71 | Visual Studio 72 | CodeのMCPクライアントで使用するには、以下のように`settings.json`に設定を追加します。 73 | 74 | ```json 75 | { 76 | "mcp": { 77 | "servers": { 78 | "baseline-mcp-server": { 79 | "command": "deno", 80 | "args": [ 81 | "run", 82 | "--allow-net=api.webstatus.dev", 83 | "jsr:@yamanoku/baseline-mcp-server" 84 | ] 85 | } 86 | } 87 | } 88 | } 89 | ``` 90 | 91 | ## Dockerによる起動 92 | 93 | 最初にDockerイメージをビルドします。 94 | 95 | ```shell 96 | docker build -t baseline-mcp-server . 97 | ``` 98 | 99 | MCPクライアントの設定でDockerコンテナを実行するようにします。 100 | 101 | ```json 102 | { 103 | "mcpServers": { 104 | "baseline-mcp-server": { 105 | "command": "docker", 106 | "args": [ 107 | "run", 108 | "-i", 109 | "baseline-mcp-server:latest" 110 | ] 111 | } 112 | } 113 | } 114 | ``` 115 | 116 | ## 謝辞 117 | 118 | このOSSはGPT-4o Image Generationによってロゴを製作、Claude 3.7 119 | Sonnetによって実装、ドキュメントのサンプルを提案いただきました。感謝申し上げます。 120 | 121 | ## ライセンス 122 | 123 | [MIT License](./LICENSE) 124 | -------------------------------------------------------------------------------- /README_EN.md: -------------------------------------------------------------------------------- 1 |

2 | Baseline MCP Server Logo 3 |

4 | 5 |

Baseline MCP Server

6 | 7 | [English Version](./README_EN.md) | [日本語版](./README.md) 8 | 9 | A Model Context Protocol server that provides Web Platform API support status. 10 | 11 | [![JSR Version](https://jsr.io/badges/@yamanoku/baseline-mcp-server)](https://jsr.io/@yamanoku/baseline-mcp-server) 12 | 13 | ## Overview 14 | 15 | This server implements an MCP server that can retrieve Baseline status (support 16 | status) of Web API features using the 17 | [Web Platform Dashboard](https://webstatus.dev/) API. It fetches information 18 | about web features based on queries and returns the results to MCP clients. 19 | 20 | ![Claude Desktop showing Baseline information about details elements via the MCP server. The content lists baseline information for
element, mutually exclusive
elements, and ::details-content pseudo-element.](./screenshot_claude_desktop.png) 21 | 22 | ## Features 23 | 24 | - Feature search using Web Platform Dashboard API 25 | - Providing Baseline status (`widely`, `newly`, `limited`, `no_data`) for 26 | features 27 | - Browser implementation status (version and release date) 28 | - Feature usage data 29 | - Feature search excluding specific browsers (`chrome`, `edge`, `firefox`, 30 | `safari`) 31 | - Integration with various AI models via MCP 32 | 33 | ## About Baseline Status 34 | 35 | Baseline status indicates browser support for web features: 36 | 37 | - **widely**: Widely supported web standard features. Safe to use in most 38 | browsers. 39 | - **newly**: Newly standardized web features. Beginning to be supported in major 40 | browsers but still in the process of adoption. 41 | - **limited**: Web features with limited support. May not be available in some 42 | browsers or may require flags. 43 | - **no_data**: Web features not currently included in Baseline. Browser support 44 | status needs to be checked individually. 45 | 46 | For more details about Baseline, refer to 47 | "[Baseline (Compatibility) - MDN Web Docs Glossary](https://developer.mozilla.org/en-US/docs/Glossary/Baseline/Compatibility)". 48 | 49 | ## MCP Client Configuration 50 | 51 | - Deno is recommended for running the server 52 | - Please only allow access to `api.webstatus.dev` as a permission 53 | - Specify 54 | [`@yamanoku/baseline-mcp-server`](https://jsr.io/@yamanoku/baseline-mcp-server) 55 | or set up your local environment to read baseline-mcp-server.ts 56 | 57 | ### Claude Desktop 58 | 59 | To use with Claude Desktop's MCP client, add the following configuration to 60 | `cline_mcp_settings.json`: 61 | 62 | ```json 63 | { 64 | "mcpServers": { 65 | "baseline-mcp-server": { 66 | "command": "deno", 67 | "args": [ 68 | "run", 69 | "--allow-net=api.webstatus.dev", 70 | "jsr:@yamanoku/baseline-mcp-server" 71 | ] 72 | } 73 | } 74 | } 75 | ``` 76 | 77 | ### Visual Studio Code 78 | 79 | To use with Visual Studio Code's MCP client, add the following configuration to 80 | `settings.json`: 81 | 82 | ```json 83 | { 84 | "mcp": { 85 | "servers": { 86 | "baseline-mcp-server": { 87 | "command": "deno", 88 | "args": [ 89 | "run", 90 | "--allow-net=api.webstatus.dev", 91 | "jsr:@yamanoku/baseline-mcp-server" 92 | ] 93 | } 94 | } 95 | } 96 | } 97 | ``` 98 | 99 | ## Running with Docker 100 | 101 | First, build the Docker image: 102 | 103 | ```shell 104 | docker build -t baseline-mcp-server . 105 | ``` 106 | 107 | Configure your MCP client to run the Docker container: 108 | 109 | ```json 110 | { 111 | "mcpServers": { 112 | "baseline-mcp-server": { 113 | "command": "docker", 114 | "args": [ 115 | "run", 116 | "-i", 117 | "baseline-mcp-server:latest" 118 | ] 119 | } 120 | } 121 | } 122 | ``` 123 | 124 | ## Acknowledgements 125 | 126 | The logo for this OSS was created by GPT-4o Image Generation, and the 127 | implementation and documentation samples were suggested by Claude 3.7 Sonnet. We 128 | express our gratitude. 129 | 130 | ## License 131 | 132 | [MIT License](./LICENSE) 133 | -------------------------------------------------------------------------------- /baseline-mcp-server.ts: -------------------------------------------------------------------------------- 1 | // baseline-mcp-server.ts 2 | import { McpServer } from "npm:@modelcontextprotocol/sdk@^1.11.1/server/mcp.js"; 3 | import { StdioServerTransport } from "npm:@modelcontextprotocol/sdk@^1.11.1/server/stdio.js"; 4 | import { z } from "npm:zod@^3.24.4"; 5 | import { 6 | getNegatedBrowserBaselineStatusAsMCPContent, 7 | getWebFeatureBaselineStatusAsMCPContent, 8 | } from "./tools/index.ts"; 9 | import { BROWSERS, type Browsers } from "./types.ts"; 10 | import DenoJSON from "./deno.json" with { type: "json" }; 11 | 12 | // MCPサーバーの初期化 13 | const server = new McpServer({ 14 | name: "Baseline MCP Server", 15 | version: DenoJSON.version, 16 | capabilities: { 17 | tools: {}, 18 | }, 19 | }); 20 | 21 | // 特定の機能のBaselineステータスを取得 22 | server.tool( 23 | "get_web_feature_baseline_status", 24 | "クエリを指定し、Web Platform Dashboardからfeatureの結果を取得します", 25 | { 26 | query: z.string().array().describe("調べたい機能の名前"), 27 | }, 28 | async ({ query }: { query: string | string[] }) => { 29 | return await getWebFeatureBaselineStatusAsMCPContent(query); 30 | }, 31 | ); 32 | 33 | // 特定のブラウザを除外した機能を検索 34 | server.tool( 35 | "get_negated_browser_baseline_status", 36 | "特定のブラウザを除外して、Web Platform Dashboardからfeatureの結果を取得します", 37 | { 38 | query: z.enum(BROWSERS).describe( 39 | "除外したいブラウザの名前(chrome, edge, firefox, safari)", 40 | ), 41 | }, 42 | async ({ query }: { query: Browsers }) => { 43 | return await getNegatedBrowserBaselineStatusAsMCPContent(query); 44 | }, 45 | ); 46 | 47 | // 起動 48 | async function setMCPServer() { 49 | const transport = new StdioServerTransport(); 50 | await server.connect(transport); 51 | console.error("Baseline MCP Server running on stdio"); 52 | } 53 | 54 | setMCPServer().catch((error) => { 55 | console.error("Fatal error in setMCPServer():", error); 56 | Deno.exit(1); 57 | }); 58 | -------------------------------------------------------------------------------- /constants.ts: -------------------------------------------------------------------------------- 1 | export const APIURL = "https://api.webstatus.dev/v1/features"; 2 | -------------------------------------------------------------------------------- /deno.json: -------------------------------------------------------------------------------- 1 | { 2 | "$id": "https://jsr.io/schema/config-file.v1.json", 3 | "$schema": "http://json-schema.org/draft-07/schema", 4 | "name": "@yamanoku/baseline-mcp-server", 5 | "description": "Model Context Protocol server that provides support status for Web Platform APIs", 6 | "version": "0.5.1", 7 | "license": "MIT", 8 | "exports": "./baseline-mcp-server.ts", 9 | "tasks": { 10 | "dev": "deno run --watch baseline-mcp-server.ts", 11 | "test": "deno test --allow-net=api.webstatus.dev", 12 | "lint": "deno lint", 13 | "fmt": "deno fmt" 14 | }, 15 | "imports": { 16 | "@modelcontextprotocol/sdk": "npm:@modelcontextprotocol/sdk@^1.11.1", 17 | "zod": "npm:zod@^3.24.4" 18 | }, 19 | "publish": { 20 | "include": [ 21 | "baseline-mcp-server.ts", 22 | "constants.ts", 23 | "types.ts", 24 | "tools/**/*.ts", 25 | "screenshot_claude_desktop.png", 26 | "README.md", 27 | "LICENSE" 28 | ], 29 | "exclude": [ 30 | "tools/**/*.test.ts" 31 | ] 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /deno.lock: -------------------------------------------------------------------------------- 1 | { 2 | "version": "5", 3 | "specifiers": { 4 | "npm:@modelcontextprotocol/sdk@^1.11.1": "1.11.1_express@5.1.0_zod@3.24.4", 5 | "npm:@types/node@*": "22.12.0", 6 | "npm:zod@^3.24.4": "3.24.4" 7 | }, 8 | "npm": { 9 | "@modelcontextprotocol/sdk@1.11.1_express@5.1.0_zod@3.24.4": { 10 | "integrity": "sha512-9LfmxKTb1v+vUS1/emSk1f5ePmTLkb9Le9AxOB5T0XM59EUumwcS45z05h7aiZx3GI0Bl7mjb3FMEglYj+acuQ==", 11 | "dependencies": [ 12 | "content-type", 13 | "cors", 14 | "cross-spawn", 15 | "eventsource", 16 | "express", 17 | "express-rate-limit", 18 | "pkce-challenge", 19 | "raw-body", 20 | "zod", 21 | "zod-to-json-schema" 22 | ] 23 | }, 24 | "@types/node@22.12.0": { 25 | "integrity": "sha512-Fll2FZ1riMjNmlmJOdAyY5pUbkftXslB5DgEzlIuNaiWhXd00FhWxVC/r4yV/4wBb9JfImTu+jiSvXTkJ7F/gA==", 26 | "dependencies": [ 27 | "undici-types" 28 | ] 29 | }, 30 | "accepts@2.0.0": { 31 | "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", 32 | "dependencies": [ 33 | "mime-types", 34 | "negotiator" 35 | ] 36 | }, 37 | "body-parser@2.2.0": { 38 | "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", 39 | "dependencies": [ 40 | "bytes", 41 | "content-type", 42 | "debug", 43 | "http-errors", 44 | "iconv-lite", 45 | "on-finished", 46 | "qs", 47 | "raw-body", 48 | "type-is" 49 | ] 50 | }, 51 | "bytes@3.1.2": { 52 | "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" 53 | }, 54 | "call-bind-apply-helpers@1.0.2": { 55 | "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", 56 | "dependencies": [ 57 | "es-errors", 58 | "function-bind" 59 | ] 60 | }, 61 | "call-bound@1.0.4": { 62 | "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", 63 | "dependencies": [ 64 | "call-bind-apply-helpers", 65 | "get-intrinsic" 66 | ] 67 | }, 68 | "content-disposition@1.0.0": { 69 | "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", 70 | "dependencies": [ 71 | "safe-buffer" 72 | ] 73 | }, 74 | "content-type@1.0.5": { 75 | "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==" 76 | }, 77 | "cookie-signature@1.2.2": { 78 | "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==" 79 | }, 80 | "cookie@0.7.2": { 81 | "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==" 82 | }, 83 | "cors@2.8.5": { 84 | "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", 85 | "dependencies": [ 86 | "object-assign", 87 | "vary" 88 | ] 89 | }, 90 | "cross-spawn@7.0.6": { 91 | "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", 92 | "dependencies": [ 93 | "path-key", 94 | "shebang-command", 95 | "which" 96 | ] 97 | }, 98 | "debug@4.4.0": { 99 | "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", 100 | "dependencies": [ 101 | "ms" 102 | ] 103 | }, 104 | "depd@2.0.0": { 105 | "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" 106 | }, 107 | "dunder-proto@1.0.1": { 108 | "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", 109 | "dependencies": [ 110 | "call-bind-apply-helpers", 111 | "es-errors", 112 | "gopd" 113 | ] 114 | }, 115 | "ee-first@1.1.1": { 116 | "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" 117 | }, 118 | "encodeurl@2.0.0": { 119 | "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==" 120 | }, 121 | "es-define-property@1.0.1": { 122 | "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==" 123 | }, 124 | "es-errors@1.3.0": { 125 | "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==" 126 | }, 127 | "es-object-atoms@1.1.1": { 128 | "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", 129 | "dependencies": [ 130 | "es-errors" 131 | ] 132 | }, 133 | "escape-html@1.0.3": { 134 | "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" 135 | }, 136 | "etag@1.8.1": { 137 | "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==" 138 | }, 139 | "eventsource-parser@3.0.1": { 140 | "integrity": "sha512-VARTJ9CYeuQYb0pZEPbzi740OWFgpHe7AYJ2WFZVnUDUQp5Dk2yJUgF36YsZ81cOyxT0QxmXD2EQpapAouzWVA==" 141 | }, 142 | "eventsource@3.0.7": { 143 | "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", 144 | "dependencies": [ 145 | "eventsource-parser" 146 | ] 147 | }, 148 | "express-rate-limit@7.5.0_express@5.1.0": { 149 | "integrity": "sha512-eB5zbQh5h+VenMPM3fh+nw1YExi5nMr6HUCR62ELSP11huvxm/Uir1H1QEyTkk5QX6A58pX6NmaTMceKZ0Eodg==", 150 | "dependencies": [ 151 | "express" 152 | ] 153 | }, 154 | "express@5.1.0": { 155 | "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", 156 | "dependencies": [ 157 | "accepts", 158 | "body-parser", 159 | "content-disposition", 160 | "content-type", 161 | "cookie", 162 | "cookie-signature", 163 | "debug", 164 | "encodeurl", 165 | "escape-html", 166 | "etag", 167 | "finalhandler", 168 | "fresh", 169 | "http-errors", 170 | "merge-descriptors", 171 | "mime-types", 172 | "on-finished", 173 | "once", 174 | "parseurl", 175 | "proxy-addr", 176 | "qs", 177 | "range-parser", 178 | "router", 179 | "send", 180 | "serve-static", 181 | "statuses", 182 | "type-is", 183 | "vary" 184 | ] 185 | }, 186 | "finalhandler@2.1.0": { 187 | "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", 188 | "dependencies": [ 189 | "debug", 190 | "encodeurl", 191 | "escape-html", 192 | "on-finished", 193 | "parseurl", 194 | "statuses" 195 | ] 196 | }, 197 | "forwarded@0.2.0": { 198 | "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==" 199 | }, 200 | "fresh@2.0.0": { 201 | "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==" 202 | }, 203 | "function-bind@1.1.2": { 204 | "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" 205 | }, 206 | "get-intrinsic@1.3.0": { 207 | "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", 208 | "dependencies": [ 209 | "call-bind-apply-helpers", 210 | "es-define-property", 211 | "es-errors", 212 | "es-object-atoms", 213 | "function-bind", 214 | "get-proto", 215 | "gopd", 216 | "has-symbols", 217 | "hasown", 218 | "math-intrinsics" 219 | ] 220 | }, 221 | "get-proto@1.0.1": { 222 | "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", 223 | "dependencies": [ 224 | "dunder-proto", 225 | "es-object-atoms" 226 | ] 227 | }, 228 | "gopd@1.2.0": { 229 | "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==" 230 | }, 231 | "has-symbols@1.1.0": { 232 | "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==" 233 | }, 234 | "hasown@2.0.2": { 235 | "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", 236 | "dependencies": [ 237 | "function-bind" 238 | ] 239 | }, 240 | "http-errors@2.0.0": { 241 | "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", 242 | "dependencies": [ 243 | "depd", 244 | "inherits", 245 | "setprototypeof", 246 | "statuses", 247 | "toidentifier" 248 | ] 249 | }, 250 | "iconv-lite@0.6.3": { 251 | "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", 252 | "dependencies": [ 253 | "safer-buffer" 254 | ] 255 | }, 256 | "inherits@2.0.4": { 257 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 258 | }, 259 | "ipaddr.js@1.9.1": { 260 | "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" 261 | }, 262 | "is-promise@4.0.0": { 263 | "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==" 264 | }, 265 | "isexe@2.0.0": { 266 | "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" 267 | }, 268 | "math-intrinsics@1.1.0": { 269 | "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==" 270 | }, 271 | "media-typer@1.1.0": { 272 | "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==" 273 | }, 274 | "merge-descriptors@2.0.0": { 275 | "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==" 276 | }, 277 | "mime-db@1.54.0": { 278 | "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==" 279 | }, 280 | "mime-types@3.0.1": { 281 | "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", 282 | "dependencies": [ 283 | "mime-db" 284 | ] 285 | }, 286 | "ms@2.1.3": { 287 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" 288 | }, 289 | "negotiator@1.0.0": { 290 | "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==" 291 | }, 292 | "object-assign@4.1.1": { 293 | "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==" 294 | }, 295 | "object-inspect@1.13.4": { 296 | "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==" 297 | }, 298 | "on-finished@2.4.1": { 299 | "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", 300 | "dependencies": [ 301 | "ee-first" 302 | ] 303 | }, 304 | "once@1.4.0": { 305 | "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", 306 | "dependencies": [ 307 | "wrappy" 308 | ] 309 | }, 310 | "parseurl@1.3.3": { 311 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" 312 | }, 313 | "path-key@3.1.1": { 314 | "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==" 315 | }, 316 | "path-to-regexp@8.2.0": { 317 | "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==" 318 | }, 319 | "pkce-challenge@5.0.0": { 320 | "integrity": "sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ==" 321 | }, 322 | "proxy-addr@2.0.7": { 323 | "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", 324 | "dependencies": [ 325 | "forwarded", 326 | "ipaddr.js" 327 | ] 328 | }, 329 | "qs@6.14.0": { 330 | "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", 331 | "dependencies": [ 332 | "side-channel" 333 | ] 334 | }, 335 | "range-parser@1.2.1": { 336 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" 337 | }, 338 | "raw-body@3.0.0": { 339 | "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", 340 | "dependencies": [ 341 | "bytes", 342 | "http-errors", 343 | "iconv-lite", 344 | "unpipe" 345 | ] 346 | }, 347 | "router@2.2.0": { 348 | "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", 349 | "dependencies": [ 350 | "debug", 351 | "depd", 352 | "is-promise", 353 | "parseurl", 354 | "path-to-regexp" 355 | ] 356 | }, 357 | "safe-buffer@5.2.1": { 358 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" 359 | }, 360 | "safer-buffer@2.1.2": { 361 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 362 | }, 363 | "send@1.2.0": { 364 | "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", 365 | "dependencies": [ 366 | "debug", 367 | "encodeurl", 368 | "escape-html", 369 | "etag", 370 | "fresh", 371 | "http-errors", 372 | "mime-types", 373 | "ms", 374 | "on-finished", 375 | "range-parser", 376 | "statuses" 377 | ] 378 | }, 379 | "serve-static@2.2.0": { 380 | "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", 381 | "dependencies": [ 382 | "encodeurl", 383 | "escape-html", 384 | "parseurl", 385 | "send" 386 | ] 387 | }, 388 | "setprototypeof@1.2.0": { 389 | "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" 390 | }, 391 | "shebang-command@2.0.0": { 392 | "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", 393 | "dependencies": [ 394 | "shebang-regex" 395 | ] 396 | }, 397 | "shebang-regex@3.0.0": { 398 | "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==" 399 | }, 400 | "side-channel-list@1.0.0": { 401 | "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", 402 | "dependencies": [ 403 | "es-errors", 404 | "object-inspect" 405 | ] 406 | }, 407 | "side-channel-map@1.0.1": { 408 | "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", 409 | "dependencies": [ 410 | "call-bound", 411 | "es-errors", 412 | "get-intrinsic", 413 | "object-inspect" 414 | ] 415 | }, 416 | "side-channel-weakmap@1.0.2": { 417 | "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", 418 | "dependencies": [ 419 | "call-bound", 420 | "es-errors", 421 | "get-intrinsic", 422 | "object-inspect", 423 | "side-channel-map" 424 | ] 425 | }, 426 | "side-channel@1.1.0": { 427 | "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", 428 | "dependencies": [ 429 | "es-errors", 430 | "object-inspect", 431 | "side-channel-list", 432 | "side-channel-map", 433 | "side-channel-weakmap" 434 | ] 435 | }, 436 | "statuses@2.0.1": { 437 | "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" 438 | }, 439 | "toidentifier@1.0.1": { 440 | "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" 441 | }, 442 | "type-is@2.0.1": { 443 | "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", 444 | "dependencies": [ 445 | "content-type", 446 | "media-typer", 447 | "mime-types" 448 | ] 449 | }, 450 | "undici-types@6.20.0": { 451 | "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==" 452 | }, 453 | "unpipe@1.0.0": { 454 | "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==" 455 | }, 456 | "vary@1.1.2": { 457 | "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==" 458 | }, 459 | "which@2.0.2": { 460 | "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", 461 | "dependencies": [ 462 | "isexe" 463 | ], 464 | "bin": true 465 | }, 466 | "wrappy@1.0.2": { 467 | "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" 468 | }, 469 | "zod-to-json-schema@3.24.5_zod@3.24.4": { 470 | "integrity": "sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g==", 471 | "dependencies": [ 472 | "zod" 473 | ] 474 | }, 475 | "zod@3.24.4": { 476 | "integrity": "sha512-OdqJE9UDRPwWsrHjLN2F8bPxvwJBK22EHLWtanu0LSYr5YqzsaaW3RMgmjwr8Rypg5k+meEJdSPXJZXE/yqOMg==" 477 | } 478 | }, 479 | "redirects": { 480 | "https://deno.land/std/assert/mod.ts": "https://deno.land/std@0.224.0/assert/mod.ts", 481 | "https://deno.land/std/testing/mock.ts": "https://deno.land/std@0.224.0/testing/mock.ts" 482 | }, 483 | "remote": { 484 | "https://deno.land/std@0.224.0/assert/_constants.ts": "a271e8ef5a573f1df8e822a6eb9d09df064ad66a4390f21b3e31f820a38e0975", 485 | "https://deno.land/std@0.224.0/assert/assert.ts": "09d30564c09de846855b7b071e62b5974b001bb72a4b797958fe0660e7849834", 486 | "https://deno.land/std@0.224.0/assert/assert_almost_equals.ts": "9e416114322012c9a21fa68e187637ce2d7df25bcbdbfd957cd639e65d3cf293", 487 | "https://deno.land/std@0.224.0/assert/assert_array_includes.ts": "14c5094471bc8e4a7895fc6aa5a184300d8a1879606574cb1cd715ef36a4a3c7", 488 | "https://deno.land/std@0.224.0/assert/assert_equals.ts": "3bbca947d85b9d374a108687b1a8ba3785a7850436b5a8930d81f34a32cb8c74", 489 | "https://deno.land/std@0.224.0/assert/assert_exists.ts": "43420cf7f956748ae6ed1230646567b3593cb7a36c5a5327269279c870c5ddfd", 490 | "https://deno.land/std@0.224.0/assert/assert_false.ts": "3e9be8e33275db00d952e9acb0cd29481a44fa0a4af6d37239ff58d79e8edeff", 491 | "https://deno.land/std@0.224.0/assert/assert_greater.ts": "5e57b201fd51b64ced36c828e3dfd773412c1a6120c1a5a99066c9b261974e46", 492 | "https://deno.land/std@0.224.0/assert/assert_greater_or_equal.ts": "9870030f997a08361b6f63400273c2fb1856f5db86c0c3852aab2a002e425c5b", 493 | "https://deno.land/std@0.224.0/assert/assert_instance_of.ts": "e22343c1fdcacfaea8f37784ad782683ec1cf599ae9b1b618954e9c22f376f2c", 494 | "https://deno.land/std@0.224.0/assert/assert_is_error.ts": "f856b3bc978a7aa6a601f3fec6603491ab6255118afa6baa84b04426dd3cc491", 495 | "https://deno.land/std@0.224.0/assert/assert_less.ts": "60b61e13a1982865a72726a5fa86c24fad7eb27c3c08b13883fb68882b307f68", 496 | "https://deno.land/std@0.224.0/assert/assert_less_or_equal.ts": "d2c84e17faba4afe085e6c9123a63395accf4f9e00150db899c46e67420e0ec3", 497 | "https://deno.land/std@0.224.0/assert/assert_match.ts": "ace1710dd3b2811c391946954234b5da910c5665aed817943d086d4d4871a8b7", 498 | "https://deno.land/std@0.224.0/assert/assert_not_equals.ts": "78d45dd46133d76ce624b2c6c09392f6110f0df9b73f911d20208a68dee2ef29", 499 | "https://deno.land/std@0.224.0/assert/assert_not_instance_of.ts": "3434a669b4d20cdcc5359779301a0588f941ffdc2ad68803c31eabdb4890cf7a", 500 | "https://deno.land/std@0.224.0/assert/assert_not_match.ts": "df30417240aa2d35b1ea44df7e541991348a063d9ee823430e0b58079a72242a", 501 | "https://deno.land/std@0.224.0/assert/assert_not_strict_equals.ts": "37f73880bd672709373d6dc2c5f148691119bed161f3020fff3548a0496f71b8", 502 | "https://deno.land/std@0.224.0/assert/assert_object_match.ts": "411450fd194fdaabc0089ae68f916b545a49d7b7e6d0026e84a54c9e7eed2693", 503 | "https://deno.land/std@0.224.0/assert/assert_rejects.ts": "4bee1d6d565a5b623146a14668da8f9eb1f026a4f338bbf92b37e43e0aa53c31", 504 | "https://deno.land/std@0.224.0/assert/assert_strict_equals.ts": "b4f45f0fd2e54d9029171876bd0b42dd9ed0efd8f853ab92a3f50127acfa54f5", 505 | "https://deno.land/std@0.224.0/assert/assert_string_includes.ts": "496b9ecad84deab72c8718735373feb6cdaa071eb91a98206f6f3cb4285e71b8", 506 | "https://deno.land/std@0.224.0/assert/assert_throws.ts": "c6508b2879d465898dab2798009299867e67c570d7d34c90a2d235e4553906eb", 507 | "https://deno.land/std@0.224.0/assert/assertion_error.ts": "ba8752bd27ebc51f723702fac2f54d3e94447598f54264a6653d6413738a8917", 508 | "https://deno.land/std@0.224.0/assert/equal.ts": "bddf07bb5fc718e10bb72d5dc2c36c1ce5a8bdd3b647069b6319e07af181ac47", 509 | "https://deno.land/std@0.224.0/assert/fail.ts": "0eba674ffb47dff083f02ced76d5130460bff1a9a68c6514ebe0cdea4abadb68", 510 | "https://deno.land/std@0.224.0/assert/mod.ts": "48b8cb8a619ea0b7958ad7ee9376500fe902284bb36f0e32c598c3dc34cbd6f3", 511 | "https://deno.land/std@0.224.0/assert/unimplemented.ts": "8c55a5793e9147b4f1ef68cd66496b7d5ba7a9e7ca30c6da070c1a58da723d73", 512 | "https://deno.land/std@0.224.0/assert/unreachable.ts": "5ae3dbf63ef988615b93eb08d395dda771c96546565f9e521ed86f6510c29e19", 513 | "https://deno.land/std@0.224.0/fmt/colors.ts": "508563c0659dd7198ba4bbf87e97f654af3c34eb56ba790260f252ad8012e1c5", 514 | "https://deno.land/std@0.224.0/internal/diff.ts": "6234a4b493ebe65dc67a18a0eb97ef683626a1166a1906232ce186ae9f65f4e6", 515 | "https://deno.land/std@0.224.0/internal/format.ts": "0a98ee226fd3d43450245b1844b47003419d34d210fa989900861c79820d21c2", 516 | "https://deno.land/std@0.224.0/internal/mod.ts": "534125398c8e7426183e12dc255bb635d94e06d0f93c60a297723abe69d3b22e", 517 | "https://deno.land/std@0.224.0/testing/mock.ts": "a963181c2860b6ba3eb60e08b62c164d33cf5da7cd445893499b2efda20074db" 518 | }, 519 | "workspace": { 520 | "dependencies": [ 521 | "npm:@modelcontextprotocol/sdk@^1.11.1", 522 | "npm:zod@^3.24.4" 523 | ] 524 | } 525 | } 526 | -------------------------------------------------------------------------------- /glama.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://glama.ai/mcp/schemas/server.json", 3 | "maintainers": [ 4 | "yamanoku" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yamanoku/baseline-mcp-server/7b69466332470b79f6582469e4a582231b0dd7c4/logo.png -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base", 4 | "github>Omochice/renovate-config:deno", 5 | "helpers:pinGitHubActionDigests" 6 | ], 7 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 8 | "labels": ["dependencies"], 9 | "dependencyDashboard": false, 10 | "automerge": true, 11 | "major": { 12 | "automerge": false 13 | }, 14 | "assignees": [ 15 | "yamanoku" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /screenshot_claude_desktop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yamanoku/baseline-mcp-server/7b69466332470b79f6582469e4a582231b0dd7c4/screenshot_claude_desktop.png -------------------------------------------------------------------------------- /tools/getNegatedBrowserBaselineStatusAsMCPContent.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | assertEquals, 3 | assertExists, 4 | } from "https://deno.land/std@0.224.0/assert/mod.ts"; 5 | import { getNegatedBrowserBaselineStatusAsMCPContent } from "./getNegatedBrowserBaselineStatusAsMCPContent.ts"; 6 | import type { Browsers } from "../types.ts"; 7 | 8 | Deno.test({ 9 | name: "getNegatedBrowserBaselineStatusAsMCPContent - Valid Browser Query", 10 | fn: async () => { 11 | const query = "chrome"; 12 | const result = await getNegatedBrowserBaselineStatusAsMCPContent(query); 13 | assertExists(result); 14 | const text = result.content[0].text; 15 | assertEquals(text.includes("## chrome以外で使用可能な機能"), true); 16 | const hasKnownFeature = text.includes("Ambient light sensor") || 17 | text.includes("Barcode detector"); 18 | assertEquals(hasKnownFeature, true); 19 | }, 20 | }); 21 | 22 | Deno.test({ 23 | name: "getNegatedBrowserBaselineStatusAsMCPContent - Invalid Browser Query", 24 | fn: async () => { 25 | const query = "invalid-browser" as Browsers; 26 | const result = await getNegatedBrowserBaselineStatusAsMCPContent(query); 27 | assertExists(result); 28 | assertEquals( 29 | result.content[0].text.includes("情報が見つかりませんでした") || 30 | result.content[0].text.includes("は見つかりませんでした"), 31 | true, 32 | "Response should indicate that no information was found", 33 | ); 34 | }, 35 | }); 36 | -------------------------------------------------------------------------------- /tools/getNegatedBrowserBaselineStatusAsMCPContent.ts: -------------------------------------------------------------------------------- 1 | import { getFeatureStatus } from "./helpers/getFeatureStatus.ts"; 2 | import type { TextContent } from "@modelcontextprotocol/sdk/types.js"; 3 | import type { Browsers } from "../types.ts"; 4 | 5 | /** 6 | * 特定のブラウザを除外して、利用可能になった機能のBaselineステータスに関する構造化された結果を返します。 7 | * 8 | * この関数はWeb機能に関連するクエリ文字列とブラウザ名を受け取り、 9 | * 指定されたブラウザを除外したBaselineステータスを検索します。 10 | * 11 | * @param excludedBrowserName - 除外対象にするブラウザ名 12 | * 13 | * @returns Promise<{ content: TextContent[] }> 14 | * - 機能が見つかった場合: 指定されたブラウザを除外した機能のBaselineステータスに関するフォーマットされた情報を返します 15 | * - 機能が見つからない場合: 情報が見つからなかったことを示すメッセージを返します 16 | * - 例外エラーが発生した場合: エラーメッセージを返します 17 | * 18 | * @throws コンソールにエラーをログ出力することはありますが、常にレスポンスオブジェクトを返し、呼び出し元には例外をスローしません 19 | */ 20 | export const getNegatedBrowserBaselineStatusAsMCPContent = async ( 21 | excludedBrowserName: Browsers, 22 | ): Promise<{ content: TextContent[] }> => { 23 | try { 24 | // -available_on:${query}の形式でAPIに渡す 25 | const apiQuery = `-available_on:${excludedBrowserName}`; 26 | const webFeatures = await getFeatureStatus(apiQuery); 27 | 28 | if (webFeatures === undefined || webFeatures.length === 0) { 29 | return { 30 | content: [ 31 | { 32 | type: "text", 33 | text: 34 | `「${excludedBrowserName}」を除外した利用可能な機能は見つかりませんでした。別のブラウザ名で試してみてください。`, 35 | }, 36 | ], 37 | }; 38 | } 39 | 40 | // 機能名のリストを作成 41 | const featureNames = webFeatures.map((feature) => feature.name).join(", "); 42 | 43 | const createFormattedResponse = ( 44 | featureNames: string, 45 | ) => { 46 | return [ 47 | `\n## ${excludedBrowserName}以外で使用可能な機能\n${featureNames}`, 48 | ] 49 | .join("\n") 50 | .trim(); 51 | }; 52 | 53 | const formattedResponse = createFormattedResponse( 54 | featureNames, 55 | ); 56 | 57 | return { 58 | content: [ 59 | { 60 | type: "text", 61 | text: formattedResponse, 62 | }, 63 | ], 64 | }; 65 | } catch (error) { 66 | console.error("Error processing event:", error); 67 | return { 68 | content: [ 69 | { 70 | type: "text", 71 | text: 72 | "情報取得中にエラーが発生しました。しばらく経ってから再試行してください。", 73 | }, 74 | ], 75 | }; 76 | } 77 | }; 78 | -------------------------------------------------------------------------------- /tools/getWebFeatureBaselineStatusAsMCPContent.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | assertEquals, 3 | assertExists, 4 | } from "https://deno.land/std@0.224.0/assert/mod.ts"; 5 | import { getWebFeatureBaselineStatusAsMCPContent } from "./getWebFeatureBaselineStatusAsMCPContent.ts"; 6 | 7 | Deno.test({ 8 | name: "getWebFeatureBaselineStatusAsMCPContent - Valid Query", 9 | fn: async () => { 10 | const query = ""; 11 | const result = await getWebFeatureBaselineStatusAsMCPContent(query); 12 | assertExists(result); 13 | 14 | // コンテンツの特定の部分をチェック(APIの値は変動するため、完全一致ではなく部分チェックに変更) 15 | const text = result.content[0].text; 16 | 17 | // 基本構造のチェック 18 | const hasFeatureHeading = text.includes("## 機能"); 19 | assertEquals(hasFeatureHeading, true); 20 | 21 | // 機能名と説明のチェック 22 | const hasFeatureDescription = text.includes( 23 | ": 広くサポートされているWeb標準機能です", 24 | ); 25 | assertEquals(hasFeatureDescription, true); 26 | 27 | // サポート状況のチェック 28 | const hasSupportStatus = text.includes("## サポート状況"); 29 | assertEquals(hasSupportStatus, true); 30 | 31 | // ブラウザサポート状況のチェック 32 | const hasBrowserSupport = text.includes("## ブラウザのサポート状況"); 33 | assertEquals(hasBrowserSupport, true); 34 | 35 | // 主要ブラウザのチェック 36 | const hasMajorBrowsers = text.includes("chrome:") && 37 | text.includes("firefox:") && 38 | text.includes("safari:"); 39 | assertEquals(hasMajorBrowsers, true); 40 | 41 | // 使用状況のチェック 42 | const hasUsageInfo = text.includes("## 機能の使用状況"); 43 | assertEquals(hasUsageInfo, true); 44 | }, 45 | }); 46 | 47 | Deno.test({ 48 | name: "getWebFeatureBaselineStatusAsMCPContent - Multiple Valid Queries", 49 | fn: async () => { 50 | const queries = ["dialog", "grid"]; 51 | const result = await getWebFeatureBaselineStatusAsMCPContent(queries); 52 | assertExists(result); 53 | assertExists(result.content); 54 | assertExists(result.content[0]); 55 | assertExists(result.content[0].text); 56 | 57 | // 結果のテキストに dialog または grid のいずれかが含まれていることを確認 58 | const text = result.content[0].text; 59 | const containsFeature = text.includes("dialog") || text.includes("grid"); 60 | assertEquals(containsFeature, true); 61 | 62 | // 複数の機能に関する結果が返されていることを確認 63 | const containsFeatureList = text.includes("## 具体的な機能"); 64 | assertEquals(containsFeatureList, true); 65 | }, 66 | }); 67 | 68 | Deno.test({ 69 | name: "getWebFeatureBaselineStatusAsMCPContent - Invalid Query", 70 | fn: async () => { 71 | const query = "invalid-element"; 72 | const result = await getWebFeatureBaselineStatusAsMCPContent(query); 73 | assertExists(result); 74 | assertEquals( 75 | result.content[0].text, 76 | "「invalid-element」に関する情報は見つかりませんでした。別の機能名で試してみてください。", 77 | ); 78 | }, 79 | }); 80 | 81 | Deno.test({ 82 | name: "getWebFeatureBaselineStatusAsMCPContent - Multiple Invalid Queries", 83 | fn: async () => { 84 | const queries = ["invalid-element-1", "invalid-element-2"]; 85 | const result = await getWebFeatureBaselineStatusAsMCPContent(queries); 86 | assertExists(result); 87 | assertEquals( 88 | result.content[0].text, 89 | '「invalid-element-1", "invalid-element-2」に関する情報は見つかりませんでした。別の機能名で試してみてください。', 90 | ); 91 | }, 92 | }); 93 | 94 | Deno.test({ 95 | name: 96 | "getWebFeatureBaselineStatusAsMCPContent - Deduplicates baseline statuses", 97 | fn: async () => { 98 | const query = "grid"; 99 | const result = await getWebFeatureBaselineStatusAsMCPContent(query); 100 | assertExists(result); 101 | const text = result.content[0].text; 102 | const widelyCount = (text.match(/### Widely available/g) || []).length; 103 | // API から複数の Grid 関連機能が返るが、Widely カテゴリは 1 度だけ表示される 104 | assertEquals(widelyCount, 1); 105 | }, 106 | }); 107 | -------------------------------------------------------------------------------- /tools/getWebFeatureBaselineStatusAsMCPContent.ts: -------------------------------------------------------------------------------- 1 | import { getFeatureStatus } from "./helpers/getFeatureStatus.ts"; 2 | import type { TextContent } from "@modelcontextprotocol/sdk/types.js"; 3 | import type { BaselineStatus } from "../types.ts"; 4 | 5 | /** 6 | * Web機能APIへのクエリを投稿し、Baselineステータスに関する構造化された結果を返します。 7 | * 8 | * この関数はWeb機能に関連するクエリ文字列(または複数のクエリ文字列)を受け取り、そのBaselineステータスを検索し、 9 | * ブラウザ間での機能のサポートレベルに関するフォーマットされた情報を返します。 10 | * 11 | * @param query - Baselineステータスを照会するWeb機能名、または機能名の配列 12 | * 複数の機能名が配列として提供された場合、API検索では「+OR+」でつなげられます 13 | * 14 | * @returns Promise<{ content: TextContent[] }> 15 | * - 機能が見つかった場合: 機能のBaselineステータスに関するフォーマットされた情報を返します 16 | * - 機能が見つからない場合: 情報が見つからなかったことを示すメッセージを返します 17 | * - 例外エラーが発生した場合: エラーメッセージを返します 18 | * 19 | * @throws コンソールにエラーをログ出力することはありますが、常にレスポンスオブジェクトを返し、呼び出し元には例外をスローしません 20 | */ 21 | export const getWebFeatureBaselineStatusAsMCPContent = async ( 22 | query: string | string[], 23 | ): Promise<{ content: TextContent[] }> => { 24 | try { 25 | const webFeatures = await getFeatureStatus(query); 26 | 27 | if (webFeatures === undefined || webFeatures.length === 0) { 28 | const queryDisplay = Array.isArray(query) ? query.join('", "') : query; 29 | return { 30 | content: [ 31 | { 32 | type: "text", 33 | text: 34 | `「${queryDisplay}」に関する情報は見つかりませんでした。別の機能名で試してみてください。`, 35 | }, 36 | ], 37 | }; 38 | } 39 | 40 | // Baselineカテゴリのリストを作成 41 | // Web Status API では基本的に重複しないが、防御的に status 単位で 42 | // 重複を排除して最初に出現したデータだけを保持する 43 | const seenStatuses = new Set(); 44 | const baselineCategories = webFeatures.reduce<{ 45 | status: BaselineStatus; 46 | high_date?: string; 47 | low_date?: string; 48 | }[]>((acc, feature) => { 49 | if (!seenStatuses.has(feature.baseline.status)) { 50 | seenStatuses.add(feature.baseline.status); 51 | acc.push(feature.baseline); 52 | } 53 | return acc; 54 | }, []); 55 | 56 | // BrowserImplementationsDataを取得 57 | const browserImplementationsData = webFeatures.map( 58 | (feature) => feature.browser_implementations, 59 | ); 60 | 61 | // Usage情報を取得 62 | const usageInfo = webFeatures.map((feature) => feature.usage); 63 | 64 | const baselineCategoryDescriptions = { 65 | widely: 66 | "広くサポートされているWeb標準機能です。ほとんどのブラウザで安全に使用できます。", 67 | newly: 68 | "新しく標準化されたWeb機能です。主要なブラウザでサポートされ始めていますが、まだ普及途上です。", 69 | limited: 70 | "限定的にサポートされているWeb機能です。一部のブラウザでは使用できないか、フラグが必要な場合があります。", 71 | no_data: 72 | "現時点ではBaselineに含まれていないWeb機能です。ブラウザのサポート状況を個別に確認する必要があります。", 73 | } as const satisfies { 74 | [key in BaselineStatus]: string; 75 | }; 76 | 77 | const baselineSupportDate = (category: { 78 | low_date?: string; 79 | high_date?: string; 80 | }) => { 81 | const lowDate = category.low_date 82 | ? `### Newly available\n${category.low_date}\n` 83 | : null; 84 | const highDate = category.high_date 85 | ? `### Widely available\n${category.high_date}\n` 86 | : null; 87 | if (!lowDate && !highDate) { 88 | return ""; 89 | } 90 | return `${lowDate}${highDate}`; 91 | }; 92 | 93 | // 複数のクエリがある場合は、最初のクエリだけを表示 94 | const displayQuery = Array.isArray(query) ? query[0] : query; 95 | 96 | const formattedCategoryDescriptions = baselineCategories 97 | .filter((category) => baselineCategoryDescriptions[category.status]) 98 | .map( 99 | (category) => 100 | `## 機能\n- ${displayQuery}: ${ 101 | baselineCategoryDescriptions[category.status] 102 | }\n## サポート状況\n${baselineSupportDate(category)}`, 103 | ) 104 | .join("\n"); 105 | 106 | const featureStatusList = webFeatures 107 | .map((feature) => `${feature.name}: ${feature.baseline.status}`) 108 | .join("\n- "); 109 | 110 | const browserSupportList = browserImplementationsData 111 | .map((browserData) => { 112 | const browserSupport = Object.entries(browserData).map( 113 | ([browser, data]) => { 114 | const version = data?.version || "N/A"; 115 | const date = data?.date || "N/A"; 116 | return `${browser}: ${version} (${date})`; 117 | }, 118 | ); 119 | return `${browserSupport.join(", ")}`; 120 | }) 121 | .join("\n- "); 122 | 123 | const featureUsageList = usageInfo 124 | .map((usage) => { 125 | return Object.values(usage) 126 | .map((data) => (data?.daily ? data.daily * 100 : "N/A")) 127 | .join(", "); 128 | }) 129 | .join("\n- "); 130 | 131 | const createFormattedResponse = ( 132 | categories: typeof formattedCategoryDescriptions, 133 | browserList: typeof browserSupportList, 134 | usageList: typeof featureUsageList, 135 | featureList: typeof featureStatusList | null, 136 | ) => { 137 | return [ 138 | categories, 139 | `## ブラウザのサポート状況\n- ${browserList}\n`, 140 | `## 機能の使用状況\n- ${usageList}\n`, 141 | featureList ? `## 具体的な機能\n- ${featureList}` : "", 142 | ] 143 | .join("\n") 144 | .trim(); 145 | }; 146 | 147 | const formattedResponse = createFormattedResponse( 148 | formattedCategoryDescriptions, 149 | browserSupportList, 150 | featureUsageList, 151 | webFeatures.length > 1 ? featureStatusList : null, 152 | ); 153 | 154 | return { 155 | content: [ 156 | { 157 | type: "text", 158 | text: formattedResponse, 159 | }, 160 | ], 161 | }; 162 | } catch (error) { 163 | console.error("Error processing event:", error); 164 | return { 165 | content: [ 166 | { 167 | type: "text", 168 | text: 169 | "情報取得中にエラーが発生しました。しばらく経ってから再試行してください。", 170 | }, 171 | ], 172 | }; 173 | } 174 | }; 175 | -------------------------------------------------------------------------------- /tools/helpers/getFeatureStatus.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | assertEquals, 3 | assertExists, 4 | } from "https://deno.land/std@0.224.0/assert/mod.ts"; 5 | import { getFeatureStatus } from "./getFeatureStatus.ts"; 6 | 7 | Deno.test({ 8 | name: "getFeatureStatus - Valid Query", 9 | fn: async () => { 10 | const query = "dialog"; 11 | const result = await getFeatureStatus(query); 12 | assertExists(result); 13 | assertEquals(result.length, 2); 14 | }, 15 | }); 16 | 17 | Deno.test({ 18 | name: "getFeatureStatus - Multiple Valid Queries", 19 | fn: async () => { 20 | const queries = ["dialog", "grid"]; 21 | const result = await getFeatureStatus(queries); 22 | assertExists(result); 23 | // 実行環境によって変わる可能性があるので、具体的な数値は硬直にしない 24 | // APIが実際に「dialog OR grid」で検索を行えているかを確認 25 | assertExists(result.length); 26 | }, 27 | }); 28 | 29 | Deno.test({ 30 | name: "getFeatureStatus - Invalid Query", 31 | fn: async () => { 32 | const query = "invalid-query"; 33 | const result = await getFeatureStatus(query); 34 | assertEquals(result, []); 35 | }, 36 | }); 37 | 38 | Deno.test({ 39 | name: "getFeatureStatus - Empty Query", 40 | fn: async () => { 41 | const query = ""; 42 | const result = await getFeatureStatus(query); 43 | assertEquals(result, undefined); 44 | }, 45 | }); 46 | 47 | Deno.test({ 48 | name: "getFeatureStatus - Empty Array Query", 49 | fn: async () => { 50 | const query: string[] = []; 51 | const result = await getFeatureStatus(query); 52 | assertEquals(result, undefined); 53 | }, 54 | }); 55 | -------------------------------------------------------------------------------- /tools/helpers/getFeatureStatus.ts: -------------------------------------------------------------------------------- 1 | import type { WebFeature, WebFeatureResponse } from "../../types.ts"; 2 | import { APIURL } from "../../constants.ts"; 3 | 4 | /** 5 | * クエリ文字列に基づいてWeb Status APIから機能のBaseline情報を取得します。 6 | * 7 | * @param query - 特定のWeb機能を検索するための検索クエリ文字列、または検索クエリ文字列の配列 8 | * @returns WebFeatureオブジェクトの配列またはリクエストが失敗した場合はundefinedで解決するPromise 9 | * @throws エラーはコンソールに記録されますが、呼び出し元にはスローされません 10 | * 11 | * @remarks 12 | * - この関数はフェッチリクエストに5000msのタイムアウトを使用します 13 | * - クエリパラメータはAPI URLに追加される前にURLエンコードされます 14 | * - レスポンスがOKでない場合、エラーをスローする前にレスポンスボディがキャンセルされます 15 | * - 複数のクエリが配列として提供された場合、それらは「+OR+」でつなげられます(例:query1+OR+query2) 16 | */ 17 | export const getFeatureStatus = async ( 18 | query: string | string[], 19 | ): Promise => { 20 | try { 21 | // 配列の場合、ORで連結 22 | const queryString = Array.isArray(query) 23 | ? query.map((q) => encodeURIComponent(q)).join("+OR+") 24 | : encodeURIComponent(query); 25 | 26 | // 空のクエリの場合は早期リターン 27 | if (!queryString) return undefined; 28 | 29 | const url = `${APIURL}?q=${queryString}`; 30 | 31 | const controller = new AbortController(); 32 | const timeoutId = setTimeout(() => controller.abort(), 5000); 33 | 34 | const response = await fetch(url, { 35 | signal: controller.signal, 36 | }); 37 | 38 | clearTimeout(timeoutId); 39 | 40 | if (!response.ok) { 41 | await response.body?.cancel(); 42 | throw new Error(`API request failed with status ${response.status}`); 43 | } 44 | 45 | const responseData: WebFeatureResponse = await response.json(); 46 | return responseData.data; 47 | } catch (error) { 48 | console.error(error); 49 | } 50 | }; 51 | -------------------------------------------------------------------------------- /tools/index.ts: -------------------------------------------------------------------------------- 1 | import { getWebFeatureBaselineStatusAsMCPContent } from "./getWebFeatureBaselineStatusAsMCPContent.ts"; 2 | import { getNegatedBrowserBaselineStatusAsMCPContent } from "./getNegatedBrowserBaselineStatusAsMCPContent.ts"; 3 | 4 | export { 5 | getNegatedBrowserBaselineStatusAsMCPContent, 6 | getWebFeatureBaselineStatusAsMCPContent, 7 | }; 8 | -------------------------------------------------------------------------------- /types.ts: -------------------------------------------------------------------------------- 1 | export type BaselineStatus = "widely" | "limited" | "newly" | "no_data"; 2 | 3 | export const BROWSERS = ["chrome", "edge", "firefox", "safari"] as const; 4 | export type Browsers = typeof BROWSERS[number]; 5 | 6 | type BrowserImplementationsData = { 7 | date: string; 8 | status: "available"; 9 | version: string; 10 | }; 11 | 12 | export type WebFeature = { 13 | baseline: { 14 | status: BaselineStatus; 15 | high_date?: string; 16 | low_date?: string; 17 | }; 18 | browser_implementations: { 19 | [key in Browsers]?: BrowserImplementationsData; 20 | }; 21 | usage: { 22 | chrome: { 23 | daily?: number; 24 | }; 25 | }; 26 | name: string; 27 | }; 28 | 29 | export type WebFeatureResponse = { 30 | data: WebFeature[]; 31 | }; 32 | --------------------------------------------------------------------------------