@wcagc/mcp
Advanced tools
| /** | ||
| * ChatGPT needs each tool to describe its own auth policy before it can surface the OAuth | ||
| * account-linking flow. The current MCP TypeScript SDK carries this Apps SDK extension through | ||
| * descriptor `_meta`; keep the value shared so all tools advertise the same hosted scope. | ||
| */ | ||
| export const OAUTH_TOOL_META = { | ||
| securitySchemes: [{ type: "oauth2", scopes: ["mcp:scan"] }], | ||
| }; |
+3
-0
@@ -41,2 +41,5 @@ function num(envVar, fallback) { | ||
| mcpServerUrl, | ||
| // OpenAI supplies this token during Plugins Directory domain verification. It stays in the | ||
| // deployment secret store; the hosted server exposes it verbatim only on the well-known path. | ||
| openAiAppsChallenge: process.env.WCAGC_OPENAI_APPS_CHALLENGE, | ||
| jwksCacheTtlSeconds: num(process.env.WCAGC_MCP_JWKS_TTL_SECONDS, 300), | ||
@@ -43,0 +46,0 @@ // ── check_pdf url fetch (wcagc-api only accepts multipart bytes — see src/pdf-fetch.ts) ── |
+7
-0
@@ -32,2 +32,9 @@ import { requireBearerAuth } from "@modelcontextprotocol/sdk/server/auth/middleware/bearerAuth.js"; | ||
| }); | ||
| app.get("/.well-known/openai-apps-challenge", (_req, res) => { | ||
| if (!config.openAiAppsChallenge) { | ||
| res.sendStatus(404); | ||
| return; | ||
| } | ||
| res.status(200).type("text/plain").send(config.openAiAppsChallenge); | ||
| }); | ||
| const issuer = config.oauthIssuer.replace(/\/+$/, ""); | ||
@@ -34,0 +41,0 @@ const oauthMetadata = { |
+1
-1
@@ -9,3 +9,3 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; | ||
| // test/version.test.ts fails the build if the two ever drift. | ||
| export const VERSION = "0.7.0"; | ||
| export const VERSION = "0.7.1"; | ||
| /** | ||
@@ -12,0 +12,0 @@ * One server, two transports (hosted Streamable HTTP + local stdio) share this — the tool |
@@ -5,2 +5,3 @@ import { z } from "zod"; | ||
| import { fetchPdf, PdfFetchError } from "../pdf-fetch.js"; | ||
| import { OAUTH_TOOL_META } from "../tool-metadata.js"; | ||
| import { toolError } from "../tool-error.js"; | ||
@@ -50,2 +51,3 @@ import { disclaimerShape, withDisclaimer } from "./common.js"; | ||
| outputSchema: pdfCheckOutputShape, | ||
| _meta: OAUTH_TOOL_META, | ||
| annotations: { | ||
@@ -57,3 +59,3 @@ title: "Check a PDF for PDF/UA-1 conformance", | ||
| idempotentHint: false, | ||
| openWorldHint: true, | ||
| openWorldHint: false, | ||
| }, | ||
@@ -92,2 +94,3 @@ }, async ({ url }, extra) => { | ||
| outputSchema: pdfCheckOutputShape, | ||
| _meta: OAUTH_TOOL_META, | ||
| annotations: { | ||
@@ -94,0 +97,0 @@ title: "Get a PDF check by id", |
+32
-9
@@ -5,2 +5,3 @@ import { z } from "zod"; | ||
| import { COVERAGE_DISCLAIMER } from "../disclaimer.js"; | ||
| import { OAUTH_TOOL_META } from "../tool-metadata.js"; | ||
| import { toolError } from "../tool-error.js"; | ||
@@ -152,10 +153,20 @@ import { disclaimerShape, failureReasonSchema, severityCountsSchema, summarizeScanLike, withDisclaimer, } from "./common.js"; | ||
| /** | ||
| * Queues real work that drives a browser against pages out on the internet, and spends quota | ||
| * doing it. Nothing is ever deleted or overwritten — a scan only appends — so destructive stays | ||
| * false, but the world it reaches into is open and the outcome is not repeatable. | ||
| * Queues private WCAGC work and spends quota. Nothing is deleted or overwritten and the tools do | ||
| * not publish or mutate public internet state, so both destructive and open-world stay false. | ||
| */ | ||
| const QUEUES_WORK = { | ||
| const QUEUES_PRIVATE_WORK = { | ||
| readOnlyHint: false, | ||
| destructiveHint: false, | ||
| idempotentHint: false, | ||
| openWorldHint: false, | ||
| }; | ||
| /** | ||
| * A saved journey can contain clicks or fills that submit a form or trigger another external, | ||
| * irreversible action. It therefore needs both warnings even though the steps are configured in | ||
| * WCAGC rather than accepted as raw arguments to this tool. | ||
| */ | ||
| const RUNS_EXTERNAL_JOURNEY = { | ||
| readOnlyHint: false, | ||
| destructiveHint: true, | ||
| idempotentHint: false, | ||
| openWorldHint: true, | ||
@@ -179,2 +190,3 @@ }; | ||
| outputSchema: { sites: z.array(siteSchema) }, | ||
| _meta: OAUTH_TOOL_META, | ||
| annotations: { title: "List registered sites", ...READ_ONLY }, | ||
@@ -203,3 +215,4 @@ }, async (_args, extra) => { | ||
| outputSchema: { run: runSchema, ...disclaimerShape }, | ||
| annotations: { title: "Start a full-site scan", ...QUEUES_WORK }, | ||
| _meta: OAUTH_TOOL_META, | ||
| annotations: { title: "Start a full-site scan", ...QUEUES_PRIVATE_WORK }, | ||
| }, async ({ siteHost }, extra) => { | ||
@@ -231,2 +244,3 @@ try { | ||
| outputSchema: { run: runSchema, ...disclaimerShape }, | ||
| _meta: OAUTH_TOOL_META, | ||
| annotations: { title: "Get a full-site scan run by id", ...READ_ONLY }, | ||
@@ -254,2 +268,3 @@ }, async ({ runId }, extra) => { | ||
| outputSchema: { violations: z.array(runViolationSchema) }, | ||
| _meta: OAUTH_TOOL_META, | ||
| annotations: { title: "Get a full-site scan run's findings", ...READ_ONLY }, | ||
@@ -279,2 +294,3 @@ }, async ({ runId }, extra) => { | ||
| outputSchema: { rootCauses: rootCausesSchema }, | ||
| _meta: OAUTH_TOOL_META, | ||
| annotations: { title: "Get a scan run's root causes", ...READ_ONLY }, | ||
@@ -304,4 +320,5 @@ }, async ({ runId }, extra) => { | ||
| "AUTHENTICATED_SCANS when the journey logs in). Steps and any login credential always come " + | ||
| "from the journey's own saved configuration — never accepted here. Queues the run and " + | ||
| "returns immediately with a runId — call get_journey_run to poll.", | ||
| "from the journey's own saved configuration — never accepted here. Saved click or fill " + | ||
| "steps can submit forms or trigger external actions, so run only a journey the user has " + | ||
| "reviewed. Queues the run and returns immediately with a runId — call get_journey_run to poll.", | ||
| inputSchema: { | ||
@@ -312,3 +329,4 @@ siteHost: z.string().describe("The registered site's normalized host, as list_sites reports it."), | ||
| outputSchema: { run: journeyRunSchema, ...disclaimerShape }, | ||
| annotations: { title: "Run a saved user journey", ...QUEUES_WORK }, | ||
| _meta: OAUTH_TOOL_META, | ||
| annotations: { title: "Run a saved user journey", ...RUNS_EXTERNAL_JOURNEY }, | ||
| }, async ({ siteHost, journeyName }, extra) => { | ||
@@ -339,2 +357,3 @@ try { | ||
| outputSchema: { run: journeyRunSchema, ...disclaimerShape }, | ||
| _meta: OAUTH_TOOL_META, | ||
| annotations: { title: "Get a journey run by id", ...READ_ONLY }, | ||
@@ -364,2 +383,3 @@ }, async ({ runId }, extra) => { | ||
| outputSchema: { trend: trendSchema, ...disclaimerShape }, | ||
| _meta: OAUTH_TOOL_META, | ||
| annotations: { title: "Get a site's violation-count trend", ...READ_ONLY }, | ||
@@ -391,2 +411,3 @@ }, async ({ siteHost, limit }, extra) => { | ||
| outputSchema: { fixes: z.array(fixSchema), ...disclaimerShape }, | ||
| _meta: OAUTH_TOOL_META, | ||
| annotations: { title: "List tracked fixes", ...READ_ONLY }, | ||
@@ -415,3 +436,4 @@ }, async ({ siteHost }, extra) => { | ||
| outputSchema: { verification: fixVerificationAcceptedSchema, pollWith: z.literal("get_fix_verification"), ...disclaimerShape }, | ||
| annotations: { title: "Verify a tracked fix", ...QUEUES_WORK }, | ||
| _meta: OAUTH_TOOL_META, | ||
| annotations: { title: "Verify a tracked fix", ...QUEUES_PRIVATE_WORK }, | ||
| }, async ({ remediationItemId }, extra) => { | ||
@@ -438,2 +460,3 @@ try { | ||
| outputSchema: { verification: fixVerificationSchema, ...disclaimerShape }, | ||
| _meta: OAUTH_TOOL_META, | ||
| annotations: { title: "Get fix verification", ...READ_ONLY }, | ||
@@ -440,0 +463,0 @@ }, async ({ fixVerificationId }, extra) => { |
| import { z } from "zod"; | ||
| import { McpApiError, apiJson } from "../api-client.js"; | ||
| import { resolveBearer } from "../bearer.js"; | ||
| import { OAUTH_TOOL_META } from "../tool-metadata.js"; | ||
| import { toolError } from "../tool-error.js"; | ||
@@ -77,2 +78,3 @@ import { disclaimerShape, failureReasonSchema, severityCountsSchema, summarizeScanLike, withDisclaimer, } from "./common.js"; | ||
| outputSchema: scanOutputShape, | ||
| _meta: OAUTH_TOOL_META, | ||
| annotations: { | ||
@@ -85,3 +87,3 @@ title: "Scan a URL", | ||
| idempotentHint: false, | ||
| openWorldHint: true, | ||
| openWorldHint: false, | ||
| }, | ||
@@ -148,2 +150,3 @@ }, async ({ url }, extra) => { | ||
| outputSchema: scanOutputShape, | ||
| _meta: OAUTH_TOOL_META, | ||
| annotations: { | ||
@@ -188,2 +191,3 @@ title: "Get a scan by id", | ||
| outputSchema: findingsOutputShape, | ||
| _meta: OAUTH_TOOL_META, | ||
| annotations: { | ||
@@ -190,0 +194,0 @@ title: "Get a scan's findings", |
+1
-1
| { | ||
| "name": "@wcagc/mcp", | ||
| "version": "0.7.0", | ||
| "version": "0.7.1", | ||
| "mcpName": "io.github.WCAG-Compliance/mcp", | ||
@@ -5,0 +5,0 @@ "description": "wcagc MCP server \u2014 a thin, stateless adapter that translates MCP tool calls into wcagc-api HTTP calls. No database, no secrets beyond WCAGC_API_BASE_URL (+ WCAGC_MCP_KEY for local stdio mode). Open-source: this package holds no business logic or credentials of its own.", |
+5
-4
@@ -89,6 +89,7 @@ # wcagc-mcp | ||
| | `WCAGC_MCP_INTROSPECT_TTL_SECONDS` | hosted | How long a verified bearer is cached before re-checking with the API (default `60`). | | ||
| | `WCAGC_OAUTH_ISSUER` | hosted | Expected OAuth issuer. | | ||
| | `WCAGC_OAUTH_JWKS_URL` | hosted | Authorization Server public JWKS URL. | | ||
| | `WCAGC_MCP_SERVER_URL` | hosted | Canonical RFC 9728 protected-resource URL. | | ||
| | `WCAGC_OAUTH_JWKS_TTL_SECONDS` | hosted | JWKS cache TTL; an unknown `kid` triggers an immediate refetch. | | ||
| | `WCAGC_MCP_OAUTH_ISSUER` | hosted | Expected OAuth issuer (defaults to `WCAGC_API_BASE_URL`). | | ||
| | `WCAGC_MCP_OAUTH_JWKS_URL` | hosted | Authorization Server public JWKS URL. | | ||
| | `WCAGC_MCP_URL` | hosted | Canonical RFC 9728 protected-resource URL (defaults to `https://mcp.wcagc.com/mcp`). | | ||
| | `WCAGC_MCP_JWKS_TTL_SECONDS` | hosted | JWKS cache TTL; an unknown `kid` triggers an immediate refetch. | | ||
| | `WCAGC_OPENAI_APPS_CHALLENGE` | hosted | OpenAI Plugins Directory domain-verification token; keep it in the deployment secret store, never in source. | | ||
@@ -95,0 +96,0 @@ ## Development |
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
79374
2.95%19
5.56%1519
3.26%108
0.93%17
6.25%