{
  "components": {
    "schemas": {
      "AuthorizationServerMetadata": {
        "additionalProperties": true,
        "description": "RFC 8414 Authorization Server Metadata, passed through verbatim from Clerk. Only the members needed to start the flow are named below; the real document carries more, and Clerk is the authority on it — which is why this schema admits additional properties.",
        "properties": {
          "authorization_endpoint": {
            "description": "Where the user agent is sent to authorize.",
            "type": "string"
          },
          "code_challenge_methods_supported": {
            "description": "PKCE methods. `S256` is required; `plain` is not used.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "grant_types_supported": {
            "description": "The OAuth grant types the server implements.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "issuer": {
            "description": "The authorization server's issuer identifier.",
            "type": "string"
          },
          "registration_endpoint": {
            "description": "RFC 7591 dynamic client registration. A client needs no pre-registered id.",
            "type": "string"
          },
          "response_types_supported": {
            "description": "The OAuth response types the server implements.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "scopes_supported": {
            "description": "Every scope the authorization server understands.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "token_endpoint": {
            "description": "Where an authorization code is exchanged for a token.",
            "type": "string"
          }
        },
        "required": [
          "authorization_endpoint",
          "issuer",
          "token_endpoint"
        ],
        "type": "object"
      },
      "JsonRpcError": {
        "description": "A JSON-RPC 2.0 error the MCP server answers at the transport level, before or instead of dispatching a message. `id` is null on these, because the failure is about the request rather than about one message inside it.",
        "properties": {
          "error": {
            "description": "The JSON-RPC 2.0 error object.",
            "properties": {
              "code": {
                "description": "A JSON-RPC error code: -32700 parse error, -32600 invalid request, -32601 method not found, -32602 invalid params, -32603 internal error, or a -32000 range implementation-defined code.",
                "type": "integer"
              },
              "message": {
                "description": "A single sentence describing the failure.",
                "type": "string"
              }
            },
            "required": [
              "code",
              "message"
            ],
            "type": "object"
          },
          "id": {
            "description": "Null for a transport-level failure; otherwise the id of the request being answered.",
            "type": [
              "integer",
              "string",
              "null"
            ]
          },
          "jsonrpc": {
            "description": "Always \"2.0\".",
            "enum": [
              "2.0"
            ],
            "type": "string"
          }
        },
        "required": [
          "error",
          "id",
          "jsonrpc"
        ],
        "type": "object"
      },
      "JsonRpcRequest": {
        "additionalProperties": false,
        "description": "One JSON-RPC 2.0 request object. `tools/list` enumerates the tools and their input schemas; `tools/call` invokes one.",
        "properties": {
          "id": {
            "description": "Correlates the answer with this request. Omit it for a notification, which is answered with 202 and no body.",
            "type": [
              "integer",
              "string",
              "null"
            ]
          },
          "jsonrpc": {
            "description": "Always \"2.0\".",
            "enum": [
              "2.0"
            ],
            "type": "string"
          },
          "method": {
            "description": "The MCP method, for example \"initialize\", \"tools/list\" or \"tools/call\".",
            "type": "string"
          },
          "params": {
            "additionalProperties": true,
            "description": "The method's arguments. Its shape is the method's own — for `tools/call` it is `{ name, arguments }`, where `arguments` must match the input schema `tools/list` published for that tool.",
            "type": "object"
          }
        },
        "required": [
          "jsonrpc",
          "method"
        ],
        "type": "object"
      },
      "JsonRpcResponse": {
        "description": "One JSON-RPC 2.0 response object. Exactly one of `result` and `error` is present. A batch is answered with an array of these, which the schema does not describe for the same reason the request schema does not describe a batch.",
        "properties": {
          "error": {
            "description": "The JSON-RPC 2.0 error object.",
            "properties": {
              "code": {
                "description": "A JSON-RPC error code: -32700 parse error, -32600 invalid request, -32601 method not found, -32602 invalid params, -32603 internal error, or a -32000 range implementation-defined code.",
                "type": "integer"
              },
              "message": {
                "description": "A single sentence describing the failure.",
                "type": "string"
              }
            },
            "required": [
              "code",
              "message"
            ],
            "type": "object"
          },
          "id": {
            "description": "The id of the request being answered.",
            "type": [
              "integer",
              "string",
              "null"
            ]
          },
          "jsonrpc": {
            "description": "Always \"2.0\".",
            "enum": [
              "2.0"
            ],
            "type": "string"
          },
          "result": {
            "additionalProperties": true,
            "description": "The method's result. For `tools/call` this is a tool result, and a tool that refused — including a rate-limited one — reports it here with `isError` set rather than as an HTTP status.",
            "type": "object"
          }
        },
        "required": [
          "jsonrpc"
        ],
        "type": "object"
      },
      "ProtectedResourceMetadata": {
        "description": "RFC 9728 Protected Resource Metadata for the MCP endpoint. A client reaches it from the `resource_metadata` pointer in the 401 challenge.",
        "properties": {
          "authorization_servers": {
            "description": "Issuer identifiers of the authorization servers that can issue tokens for this resource. One entry.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "resource": {
            "description": "The canonical URL of the protected resource.",
            "type": "string"
          },
          "resource_name": {
            "description": "A human-readable name for the resource.",
            "type": "string"
          },
          "scopes_supported": {
            "description": "The scopes this resource understands.",
            "items": {
              "type": "string"
            },
            "type": "array"
          }
        },
        "required": [
          "authorization_servers",
          "resource",
          "resource_name",
          "scopes_supported"
        ],
        "type": "object"
      }
    },
    "securitySchemes": {
      "oauth2": {
        "description": "OAuth 2.1 authorization code with PKCE — `S256` only — and RFC 7591 dynamic client registration, so a client needs no pre-registered id. The URLs here are Clerk's and are reproduced for readability: the RFC 8414 document at `https://mcp.wiring.studio/.well-known/oauth-authorization-server` is authoritative, and a client should read it rather than hard-code them. It is reached from the `resource_metadata` pointer in the `WWW-Authenticate` challenge of a 401.",
        "flows": {
          "authorizationCode": {
            "authorizationUrl": "https://clerk.wiring.studio/oauth/authorize",
            "scopes": {
              "email": "Read the signed-in user's email address.",
              "openid": "Identify the signed-in user. Required: every tool call runs as that account, with exactly the access it already has.",
              "profile": "Read the signed-in user's name."
            },
            "tokenUrl": "https://clerk.wiring.studio/oauth/token"
          }
        },
        "type": "oauth2",
        "x-authorization-server-metadata": "https://mcp.wiring.studio/.well-known/oauth-authorization-server",
        "x-protected-resource-metadata": "https://mcp.wiring.studio/.well-known/oauth-protected-resource/mcp",
        "x-registration-endpoint": "https://clerk.wiring.studio/oauth/register"
      }
    }
  },
  "externalDocs": {
    "description": "API and developer resources: discover project tools and product documentation.",
    "url": "https://wiring.studio/docs/api/"
  },
  "info": {
    "contact": {
      "email": "support@wiring.studio",
      "name": "Wiring Studio support",
      "url": "https://wiring.studio/support/"
    },
    "description": "Discover Wiring Studio’s project tools and product documentation. This document describes the MCP connection and authentication endpoints, Markdown documentation, and discovery files.\n\n**Project tools are discovered through MCP.** Connect to `https://mcp.wiring.studio/mcp` and call `tools/list` for the current tools and their input schemas. There is no public REST API for project data, and individual MCP tool schemas are not duplicated here.\n\n**Read product documentation as Markdown.** Start with `/llms.txt` for an overview and links, or `/llms-full.txt` for the full documentation. Individual pages support `Accept: text/markdown` and URLs ending in `index.md`.\n\nBrowser-only support and subscription forms, and operational health checks, are outside this discovery document.",
    "license": {
      "name": "Wiring Studio Terms of Service",
      "url": "https://wiring.studio/terms-of-service/"
    },
    "termsOfService": "https://wiring.studio/terms-of-service/",
    "title": "Wiring Studio agent discovery",
    "version": "1.13.0"
  },
  "openapi": "3.1.0",
  "paths": {
    "/llms-full.txt": {
      "get": {
        "description": "Every documentation chapter in one plain-text file, for a reader that would rather take the whole corpus than crawl it. Hand-maintained alongside the chapters themselves.",
        "operationId": "getLlmsFullTxt",
        "responses": {
          "200": {
            "content": {
              "text/plain": {
                "schema": {
                  "description": "The whole documentation corpus as markdown-flavoured plain text.",
                  "type": "string"
                }
              }
            },
            "description": "The file."
          }
        },
        "summary": "Fetch the full documentation corpus",
        "tags": [
          "Discovery"
        ]
      }
    },
    "/llms.txt": {
      "get": {
        "description": "The llms.txt summary of the site: what the product is, and a linked index of every page worth reading. It doubles as the markdown twin of the home page, which is the one page with no content module behind it.",
        "operationId": "getLlmsTxt",
        "responses": {
          "200": {
            "content": {
              "text/plain": {
                "schema": {
                  "description": "The llms.txt document.",
                  "type": "string"
                }
              }
            },
            "description": "The file."
          }
        },
        "summary": "Fetch the site summary for language models",
        "tags": [
          "Discovery"
        ]
      }
    },
    "/openapi.json": {
      "get": {
        "description": "This document. Generated at build time: the changelog supplies `info.version` and the chapter registry supplies the documentation slugs.",
        "operationId": "getOpenApiDocument",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "description": "The OpenAPI 3.1 document.",
                  "type": "object"
                }
              }
            },
            "description": "The OpenAPI document."
          }
        },
        "summary": "Fetch this OpenAPI document",
        "tags": [
          "Discovery"
        ]
      }
    },
    "/robots.txt": {
      "get": {
        "description": "The crawl policy. `/api/` is disallowed there: it holds two endpoints that need a browser-minted token and nothing worth indexing.",
        "operationId": "getRobotsTxt",
        "responses": {
          "200": {
            "content": {
              "text/plain": {
                "schema": {
                  "description": "The robots.txt policy.",
                  "type": "string"
                }
              }
            },
            "description": "The file."
          }
        },
        "summary": "Fetch the robots policy",
        "tags": [
          "Discovery"
        ]
      }
    },
    "/rss.xml": {
      "get": {
        "description": "Every release, newest first, as RSS 2.0. Each item links to its anchor on `/changelog/`. Generated from the same release list the changelog page renders, so the feed and the page cannot disagree.",
        "operationId": "getRssFeed",
        "responses": {
          "200": {
            "content": {
              "application/xml": {
                "schema": {
                  "description": "An RSS 2.0 feed.",
                  "type": "string"
                }
              }
            },
            "description": "The document."
          }
        },
        "summary": "Fetch the changelog feed",
        "tags": [
          "Discovery"
        ]
      }
    },
    "/sitemap.xml": {
      "get": {
        "description": "Every page on the site, each with the date of the content behind it rather than the build date. Generated from the prerendered output, so a page that did not render is not listed.",
        "operationId": "getSitemap",
        "responses": {
          "200": {
            "content": {
              "application/xml": {
                "schema": {
                  "description": "A sitemap.org urlset.",
                  "type": "string"
                }
              }
            },
            "description": "The document."
          }
        },
        "summary": "Fetch the sitemap",
        "tags": [
          "Discovery"
        ]
      }
    },
    "/docs/index.md": {
      "get": {
        "description": "The documentation index — the `/docs/` page itself — as markdown. It is a chapter like any other and is split out only because its slug is empty, so its twin has no directory segment of its own.",
        "operationId": "getDocsIndexMarkdown",
        "responses": {
          "200": {
            "content": {
              "text/markdown": {
                "schema": {
                  "description": "The documentation index as markdown.",
                  "type": "string"
                }
              }
            },
            "description": "The index."
          }
        },
        "summary": "Fetch the documentation index as markdown",
        "tags": [
          "Documentation"
        ]
      }
    },
    "/docs/{slug}/index.md": {
      "get": {
        "description": "One documentation chapter as markdown, rendered from the same content module the HTML page renders from — there is no second copy of the prose, so the two representations cannot drift. The same document is also served at `https://wiring.studio/docs/{slug}/` under `Accept: text/markdown`; this URL exists so it can be linked and fetched without content negotiation. Every other page on the site has the same twin at `<page>index.md`, which is why only the documentation chapters are enumerated here.",
        "operationId": "getDocChapterMarkdown",
        "parameters": [
          {
            "description": "The chapter slug. There are 20, and the list is generated from the chapter registry, so it is current for the release named in `info.version`.",
            "in": "path",
            "name": "slug",
            "required": true,
            "schema": {
              "enum": [
                "getting-started",
                "desktop-app",
                "excel-import",
                "design-view",
                "wiring-view",
                "pin-configuration",
                "parts-library",
                "reusable-components",
                "cable-shielding",
                "wire-colors-and-marking",
                "design-rule-checks",
                "bom-and-cutlist",
                "pdf-export",
                "sheets-and-workspaces",
                "collaboration",
                "project-settings",
                "mcp-server",
                "api",
                "keyboard-shortcuts",
                "pricing-and-plans"
              ],
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "text/markdown": {
                "schema": {
                  "description": "The chapter as markdown.",
                  "type": "string"
                }
              }
            },
            "description": "The chapter."
          },
          "404": {
            "description": "No chapter has that slug. The body is empty and carries no `Content-Type`: `!/docs/*.md` excludes this URL from the worker's routing rules, so Workers Assets answers the miss on its own and the worker that would render the site's not-found document is never consulted. The negotiated markdown-or-HTML 404 belongs to the `/docs/{slug}/` page URL, not to its twin. A request built from the `slug` enum above does not reach here."
          }
        },
        "summary": "Fetch a documentation chapter as markdown",
        "tags": [
          "Documentation"
        ]
      }
    },
    "/.well-known/oauth-authorization-server": {
      "get": {
        "description": "The RFC 8414 Authorization Server Metadata, passed through verbatim from Clerk. Served here as well as at Clerk's own origin so a client that started from this resource can finish discovery without a second hop it has to guess at. This document is authoritative for the authorization and token endpoints; the URLs in the `oauth2` security scheme are a readable copy of it.",
        "operationId": "getAuthorizationServerMetadata",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthorizationServerMetadata"
                }
              }
            },
            "description": "The authorization server's metadata."
          },
          "500": {
            "description": "The upstream metadata document could not be fetched. Nothing the client can change."
          }
        },
        "servers": [
          {
            "description": "The hosted MCP server. A separate Worker from the website, on its own hostname.",
            "url": "https://mcp.wiring.studio"
          }
        ],
        "summary": "Fetch the authorization server metadata",
        "tags": [
          "MCP"
        ]
      }
    },
    "/mcp": {
      "post": {
        "description": "The Model Context Protocol endpoint, spoken as JSON-RPC 2.0 over Streamable HTTP. It is the only way to read or write project data, and everything it does runs as the signed-in user with exactly the access that account already has.\n\n**The tool list is not reproduced here.** Send `tools/list` and the server answers with all 59 tools and their input schemas; that answer is the authority, and a copy in this document could only go stale. `tools/call` invokes one.\n\n`Accept` must allow both `application/json` and `text/event-stream`, and `Content-Type` must be `application/json`. Neither is declared as a parameter below, because OpenAPI does not describe those three headers as parameters.\n\n**The deployment is stateless**: there is no session to resume and no standalone event stream to open, so `GET /mcp` and `DELETE /mcp` have no operation here. They are answered 405 once a token verifies; without one they get the same 401 challenge every other method gets, because the bearer gate answers before anything looks at the method. Rate limiting is metered per tool call rather than per request, so a refusal arrives as an `isError` tool result inside a 200 — never as a 429.",
        "operationId": "callMcpTool",
        "parameters": [
          {
            "description": "The MCP protocol version the client is speaking. Optional: an `initialize` body carries its own version, and on any other request the server assumes `2025-03-26` when the header is absent. It cannot fall back to a version agreed during an earlier `initialize`, because nothing here remembers one.",
            "in": "header",
            "name": "MCP-Protocol-Version",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/JsonRpcRequest"
              }
            }
          },
          "description": "One JSON-RPC 2.0 request object. A JSON array of them is also accepted as a batch, but the schema below describes the single-request form only, because describing both would need a `oneOf` this document keeps out of its schemas. `Content-Length` is required for the size check; a body declaring more than 1 MiB is refused with 413 before it is read.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JsonRpcResponse"
                }
              },
              "text/event-stream": {
                "schema": {
                  "$ref": "#/components/schemas/JsonRpcResponse"
                }
              }
            },
            "description": "The JSON-RPC answer. `text/event-stream` when the server streams it and `application/json` when it does not, which is why the client must accept both. A tool that refused still answers 200 — the refusal is inside the result."
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JsonRpcError"
                }
              },
              "text/plain": {
                "schema": {
                  "description": "The literal string `Invalid JSON`, not a JSON-RPC envelope.",
                  "type": "string"
                }
              }
            },
            "description": "The body was refused before dispatch. A body that is not JSON at all is refused by the transport, which answers the `text/plain` string below. A body that parses but is not a valid JSON-RPC message answers the JSON-RPC envelope below instead, with code `-32600` and a null `id` — a missing `method`, an invalid message inside a batch, and an empty batch all arrive this way, so a client that sends a batch has to tolerate both shapes."
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "description": "The OAuth 2.0 error response of RFC 6750 §3.",
                  "properties": {
                    "error": {
                      "description": "The OAuth error code, matching the `error` parameter of the `WWW-Authenticate` challenge.",
                      "type": "string"
                    },
                    "error_description": {
                      "description": "A human-readable explanation of the failure.",
                      "type": "string"
                    }
                  },
                  "required": [
                    "error",
                    "error_description"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "No bearer token, or one that is expired, malformed, or not issued by the configured authorization server. The `WWW-Authenticate` header is what starts the OAuth flow.",
            "headers": {
              "WWW-Authenticate": {
                "description": "A `Bearer` challenge carrying `error`, `error_description` and `resource_metadata=\"https://mcp.wiring.studio/.well-known/oauth-protected-resource/mcp\"`. There is no `scope` parameter: the server requires no scope beyond a token it can verify.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JsonRpcError"
                }
              }
            },
            "description": "The DNS-rebinding guard refused the request: the `Host` header names a hostname that is not on the server's allowlist."
          },
          "406": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JsonRpcError"
                }
              }
            },
            "description": "`Accept` did not allow both `application/json` and `text/event-stream`."
          },
          "413": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JsonRpcError"
                }
              }
            },
            "description": "The declared `Content-Length` is over 1 MiB. Refused before the body is read."
          },
          "415": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JsonRpcError"
                }
              }
            },
            "description": "`Content-Type` was not `application/json`."
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JsonRpcError"
                }
              }
            },
            "description": "The deployment is misconfigured or a dependency failed. Nothing the client can change. Two bodies reach here, and a client that parses one has to tolerate the other: the JSON-RPC object below, and — when the fault is raised inside the bearer gate rather than past it, an authorization-server outage being the realistic case — the same OAuth error object the 401 carries, because that gate answers its own failures before the MCP layer sees the request. The schema below describes the JSON-RPC shape only, because both are `application/json` and describing both would need a `oneOf` this document keeps out of its schemas."
          }
        },
        "security": [
          {
            "oauth2": [
              "openid",
              "profile",
              "email"
            ]
          }
        ],
        "servers": [
          {
            "description": "The hosted MCP server. A separate Worker from the website, on its own hostname.",
            "url": "https://mcp.wiring.studio"
          }
        ],
        "summary": "Call an MCP tool",
        "tags": [
          "MCP"
        ]
      }
    },
    "/.well-known/oauth-protected-resource/mcp": {
      "get": {
        "description": "The RFC 9728 Protected Resource Metadata for the MCP endpoint: which authorization servers can issue tokens for it, and which scopes it understands. A client is pointed here by the `resource_metadata` parameter of the 401 challenge, so it is the documented entry point into the OAuth flow.",
        "operationId": "getProtectedResourceMetadata",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProtectedResourceMetadata"
                }
              }
            },
            "description": "The resource metadata.",
            "headers": {
              "Access-Control-Allow-Origin": {
                "description": "Always `*`. Discovery has to work from a browser-based client on any origin.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "500": {
            "description": "The upstream metadata document could not be fetched. Nothing the client can change."
          }
        },
        "servers": [
          {
            "description": "The hosted MCP server. A separate Worker from the website, on its own hostname.",
            "url": "https://mcp.wiring.studio"
          }
        ],
        "summary": "Fetch the protected resource metadata",
        "tags": [
          "MCP"
        ]
      }
    }
  },
  "servers": [
    {
      "description": "The public website. Every path here except the MCP ones is served from this origin.",
      "url": "https://wiring.studio"
    }
  ],
  "tags": [
    {
      "description": "The files a crawler or an agent reads first to work out what is here: this document, the robots policy, the sitemap, the changelog feed and the two llms.txt files.",
      "name": "Discovery"
    },
    {
      "description": "The documentation corpus as markdown. Every page on the site — not only these — has a markdown twin at `<page>index.md` and answers `Accept: text/markdown`.",
      "name": "Documentation"
    },
    {
      "description": "The hosted Model Context Protocol server at https://mcp.wiring.studio. The only surface that reads or writes project data, and the only one behind OAuth.",
      "name": "MCP"
    }
  ]
}
