diff --git a/.gitignore b/.gitignore index cdf8daf..33c2046 100644 --- a/.gitignore +++ b/.gitignore @@ -1,12 +1,11 @@ bin/ -gen/ dist/ node_modules/ __pycache__/ *.egg-info/ .venv/ -*.pb.go -*_pb2.py .superpowers/ docs/superpowers/ sdk/rust/target/ +/protomcp +docs/.astro/ diff --git a/docs/.astro/collections/docs.schema.json b/docs/.astro/collections/docs.schema.json deleted file mode 100644 index a908db9..0000000 --- a/docs/.astro/collections/docs.schema.json +++ /dev/null @@ -1,645 +0,0 @@ -{ - "$ref": "#/definitions/docs", - "definitions": { - "docs": { - "type": "object", - "properties": { - "title": { - "type": "string" - }, - "description": { - "type": "string" - }, - "editUrl": { - "anyOf": [ - { - "type": "string", - "format": "uri" - }, - { - "type": "boolean" - } - ], - "default": true - }, - "head": { - "type": "array", - "items": { - "type": "object", - "properties": { - "tag": { - "type": "string", - "enum": [ - "title", - "base", - "link", - "style", - "meta", - "script", - "noscript", - "template" - ] - }, - "attrs": { - "type": "object", - "additionalProperties": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "boolean" - }, - { - "not": {} - } - ] - }, - "default": {} - }, - "content": { - "type": "string", - "default": "" - } - }, - "required": [ - "tag" - ], - "additionalProperties": false - }, - "default": [] - }, - "tableOfContents": { - "anyOf": [ - { - "type": "object", - "properties": { - "minHeadingLevel": { - "type": "integer", - "minimum": 1, - "maximum": 6, - "default": 2 - }, - "maxHeadingLevel": { - "type": "integer", - "minimum": 1, - "maximum": 6, - "default": 3 - } - }, - "additionalProperties": false - }, - { - "type": "boolean" - } - ], - "default": { - "minHeadingLevel": 2, - "maxHeadingLevel": 3 - } - }, - "template": { - "type": "string", - "enum": [ - "doc", - "splash" - ], - "default": "doc" - }, - "hero": { - "type": "object", - "properties": { - "title": { - "type": "string" - }, - "tagline": { - "type": "string" - }, - "image": { - "anyOf": [ - { - "type": "object", - "properties": { - "alt": { - "type": "string", - "default": "" - }, - "file": { - "type": "string" - } - }, - "required": [ - "file" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "alt": { - "type": "string", - "default": "" - }, - "dark": { - "type": "string" - }, - "light": { - "type": "string" - } - }, - "required": [ - "dark", - "light" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "html": { - "type": "string" - } - }, - "required": [ - "html" - ], - "additionalProperties": false - } - ] - }, - "actions": { - "type": "array", - "items": { - "type": "object", - "properties": { - "text": { - "type": "string" - }, - "link": { - "type": "string" - }, - "variant": { - "type": "string", - "enum": [ - "primary", - "secondary", - "minimal" - ], - "default": "primary" - }, - "icon": { - "anyOf": [ - { - "type": "string", - "enum": [ - "up-caret", - "down-caret", - "right-caret", - "left-caret", - "up-arrow", - "down-arrow", - "right-arrow", - "left-arrow", - "bars", - "translate", - "pencil", - "pen", - "document", - "add-document", - "setting", - "external", - "download", - "cloud-download", - "moon", - "sun", - "laptop", - "open-book", - "information", - "magnifier", - "forward-slash", - "close", - "error", - "warning", - "approve-check-circle", - "approve-check", - "rocket", - "star", - "puzzle", - "list-format", - "random", - "comment", - "comment-alt", - "heart", - "github", - "gitlab", - "bitbucket", - "codePen", - "farcaster", - "discord", - "gitter", - "twitter", - "x.com", - "mastodon", - "codeberg", - "youtube", - "threads", - "linkedin", - "twitch", - "azureDevOps", - "microsoftTeams", - "instagram", - "stackOverflow", - "telegram", - "rss", - "facebook", - "email", - "phone", - "reddit", - "patreon", - "signal", - "slack", - "matrix", - "hackerOne", - "openCollective", - "blueSky", - "discourse", - "zulip", - "pinterest", - "tiktok", - "astro", - "alpine", - "pnpm", - "biome", - "bun", - "mdx", - "apple", - "linux", - "homebrew", - "nix", - "starlight", - "pkl", - "node", - "cloudflare", - "vercel", - "netlify", - "deno", - "jsr", - "nostr", - "backstage", - "confluence", - "jira", - "storybook", - "vscode", - "jetbrains", - "zed", - "vim", - "figma", - "sketch", - "npm", - "sourcehut", - "substack", - "seti:folder", - "seti:bsl", - "seti:mdo", - "seti:salesforce", - "seti:asm", - "seti:bicep", - "seti:bazel", - "seti:c", - "seti:c-sharp", - "seti:html", - "seti:cpp", - "seti:clojure", - "seti:coldfusion", - "seti:config", - "seti:crystal", - "seti:crystal_embedded", - "seti:json", - "seti:css", - "seti:csv", - "seti:xls", - "seti:cu", - "seti:cake", - "seti:cake_php", - "seti:d", - "seti:word", - "seti:elixir", - "seti:elixir_script", - "seti:hex", - "seti:elm", - "seti:favicon", - "seti:f-sharp", - "seti:git", - "seti:go", - "seti:godot", - "seti:gradle", - "seti:grails", - "seti:graphql", - "seti:hacklang", - "seti:haml", - "seti:mustache", - "seti:haskell", - "seti:haxe", - "seti:jade", - "seti:java", - "seti:javascript", - "seti:jinja", - "seti:julia", - "seti:karma", - "seti:kotlin", - "seti:dart", - "seti:liquid", - "seti:livescript", - "seti:lua", - "seti:markdown", - "seti:argdown", - "seti:info", - "seti:clock", - "seti:maven", - "seti:nim", - "seti:github", - "seti:notebook", - "seti:nunjucks", - "seti:npm", - "seti:ocaml", - "seti:odata", - "seti:perl", - "seti:php", - "seti:pipeline", - "seti:pddl", - "seti:plan", - "seti:happenings", - "seti:powershell", - "seti:prisma", - "seti:pug", - "seti:puppet", - "seti:purescript", - "seti:python", - "seti:react", - "seti:rescript", - "seti:R", - "seti:ruby", - "seti:rust", - "seti:sass", - "seti:spring", - "seti:slim", - "seti:smarty", - "seti:sbt", - "seti:scala", - "seti:ethereum", - "seti:stylus", - "seti:svelte", - "seti:swift", - "seti:db", - "seti:terraform", - "seti:tex", - "seti:default", - "seti:twig", - "seti:typescript", - "seti:tsconfig", - "seti:vala", - "seti:vite", - "seti:vue", - "seti:wasm", - "seti:wat", - "seti:xml", - "seti:yml", - "seti:prolog", - "seti:zig", - "seti:zip", - "seti:wgt", - "seti:illustrator", - "seti:photoshop", - "seti:pdf", - "seti:font", - "seti:image", - "seti:svg", - "seti:sublime", - "seti:code-search", - "seti:shell", - "seti:video", - "seti:audio", - "seti:windows", - "seti:jenkins", - "seti:babel", - "seti:bower", - "seti:docker", - "seti:code-climate", - "seti:eslint", - "seti:firebase", - "seti:firefox", - "seti:gitlab", - "seti:grunt", - "seti:gulp", - "seti:ionic", - "seti:platformio", - "seti:rollup", - "seti:stylelint", - "seti:yarn", - "seti:webpack", - "seti:lock", - "seti:license", - "seti:makefile", - "seti:heroku", - "seti:todo", - "seti:ignored" - ] - }, - { - "type": "string", - "pattern": "^\\ import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fdemo.mdx&astroContentModuleFlag=true")], -["src/content/docs/index.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Findex.mdx&astroContentModuleFlag=true")], -["src/content/docs/concepts/architecture.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fconcepts%2Farchitecture.mdx&astroContentModuleFlag=true")], -["src/content/docs/getting-started/how-it-works.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fgetting-started%2Fhow-it-works.mdx&astroContentModuleFlag=true")], -["src/content/docs/getting-started/quick-start.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fgetting-started%2Fquick-start.mdx&astroContentModuleFlag=true")], -["src/content/docs/reference/cli.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Freference%2Fcli.mdx&astroContentModuleFlag=true")], -["src/content/docs/concepts/transports.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fconcepts%2Ftransports.mdx&astroContentModuleFlag=true")], -["src/content/docs/reference/mcp-compliance.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Freference%2Fmcp-compliance.mdx&astroContentModuleFlag=true")], -["src/content/docs/reference/python-api.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Freference%2Fpython-api.mdx&astroContentModuleFlag=true")], -["src/content/docs/getting-started/installation.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fgetting-started%2Finstallation.mdx&astroContentModuleFlag=true")], -["src/content/docs/concepts/tool-list-modes.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fconcepts%2Ftool-list-modes.mdx&astroContentModuleFlag=true")], -["src/content/docs/reference/protobuf-spec.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Freference%2Fprotobuf-spec.mdx&astroContentModuleFlag=true")], -["src/content/docs/guides/dynamic-tool-lists.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fguides%2Fdynamic-tool-lists.mdx&astroContentModuleFlag=true")], -["src/content/docs/guides/auth.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fguides%2Fauth.mdx&astroContentModuleFlag=true")], -["src/content/docs/guides/error-handling.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fguides%2Ferror-handling.mdx&astroContentModuleFlag=true")], -["src/content/docs/reference/typescript-api.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Freference%2Ftypescript-api.mdx&astroContentModuleFlag=true")], -["src/content/docs/guides/hot-reload.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fguides%2Fhot-reload.mdx&astroContentModuleFlag=true")], -["src/content/docs/guides/middleware.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fguides%2Fmiddleware.mdx&astroContentModuleFlag=true")], -["src/content/docs/guides/production-deployment.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fguides%2Fproduction-deployment.mdx&astroContentModuleFlag=true")], -["src/content/docs/guides/sampling.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fguides%2Fsampling.mdx&astroContentModuleFlag=true")], -["src/content/docs/guides/prompts.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fguides%2Fprompts.mdx&astroContentModuleFlag=true")], -["src/content/docs/guides/resources.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fguides%2Fresources.mdx&astroContentModuleFlag=true")], -["src/content/docs/guides/writing-tools-python.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fguides%2Fwriting-tools-python.mdx&astroContentModuleFlag=true")], -["src/content/docs/guides/testing.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fguides%2Ftesting.mdx&astroContentModuleFlag=true")], -["src/content/docs/guides/writing-a-language-library.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fguides%2Fwriting-a-language-library.mdx&astroContentModuleFlag=true")], -["src/content/docs/guides/writing-tools-typescript.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fguides%2Fwriting-tools-typescript.mdx&astroContentModuleFlag=true")], -["src/content/docs/guides/writing-tools-rust.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fguides%2Fwriting-tools-rust.mdx&astroContentModuleFlag=true")], -["src/content/docs/guides/writing-tools-go.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fguides%2Fwriting-tools-go.mdx&astroContentModuleFlag=true")]]); - \ No newline at end of file diff --git a/docs/.astro/content.d.ts b/docs/.astro/content.d.ts deleted file mode 100644 index faf4105..0000000 --- a/docs/.astro/content.d.ts +++ /dev/null @@ -1,220 +0,0 @@ -declare module 'astro:content' { - interface Render { - '.mdx': Promise<{ - Content: import('astro').MDXContent; - headings: import('astro').MarkdownHeading[]; - remarkPluginFrontmatter: Record; - components: import('astro').MDXInstance<{}>['components']; - }>; - } -} - -declare module 'astro:content' { - export interface RenderResult { - Content: import('astro/runtime/server/index.js').AstroComponentFactory; - headings: import('astro').MarkdownHeading[]; - remarkPluginFrontmatter: Record; - } - interface Render { - '.md': Promise; - } - - export interface RenderedContent { - html: string; - metadata?: { - imagePaths: Array; - [key: string]: unknown; - }; - } -} - -declare module 'astro:content' { - type Flatten = T extends { [K: string]: infer U } ? U : never; - - export type CollectionKey = keyof AnyEntryMap; - export type CollectionEntry = Flatten; - - export type ContentCollectionKey = keyof ContentEntryMap; - export type DataCollectionKey = keyof DataEntryMap; - - type AllValuesOf = T extends any ? T[keyof T] : never; - type ValidContentEntrySlug = AllValuesOf< - ContentEntryMap[C] - >['slug']; - - export type ReferenceDataEntry< - C extends CollectionKey, - E extends keyof DataEntryMap[C] = string, - > = { - collection: C; - id: E; - }; - export type ReferenceContentEntry< - C extends keyof ContentEntryMap, - E extends ValidContentEntrySlug | (string & {}) = string, - > = { - collection: C; - slug: E; - }; - export type ReferenceLiveEntry = { - collection: C; - id: string; - }; - - /** @deprecated Use `getEntry` instead. */ - export function getEntryBySlug< - C extends keyof ContentEntryMap, - E extends ValidContentEntrySlug | (string & {}), - >( - collection: C, - // Note that this has to accept a regular string too, for SSR - entrySlug: E, - ): E extends ValidContentEntrySlug - ? Promise> - : Promise | undefined>; - - /** @deprecated Use `getEntry` instead. */ - export function getDataEntryById( - collection: C, - entryId: E, - ): Promise>; - - export function getCollection>( - collection: C, - filter?: (entry: CollectionEntry) => entry is E, - ): Promise; - export function getCollection( - collection: C, - filter?: (entry: CollectionEntry) => unknown, - ): Promise[]>; - - export function getLiveCollection( - collection: C, - filter?: LiveLoaderCollectionFilterType, - ): Promise< - import('astro').LiveDataCollectionResult, LiveLoaderErrorType> - >; - - export function getEntry< - C extends keyof ContentEntryMap, - E extends ValidContentEntrySlug | (string & {}), - >( - entry: ReferenceContentEntry, - ): E extends ValidContentEntrySlug - ? Promise> - : Promise | undefined>; - export function getEntry< - C extends keyof DataEntryMap, - E extends keyof DataEntryMap[C] | (string & {}), - >( - entry: ReferenceDataEntry, - ): E extends keyof DataEntryMap[C] - ? Promise - : Promise | undefined>; - export function getEntry< - C extends keyof ContentEntryMap, - E extends ValidContentEntrySlug | (string & {}), - >( - collection: C, - slug: E, - ): E extends ValidContentEntrySlug - ? Promise> - : Promise | undefined>; - export function getEntry< - C extends keyof DataEntryMap, - E extends keyof DataEntryMap[C] | (string & {}), - >( - collection: C, - id: E, - ): E extends keyof DataEntryMap[C] - ? string extends keyof DataEntryMap[C] - ? Promise | undefined - : Promise - : Promise | undefined>; - export function getLiveEntry( - collection: C, - filter: string | LiveLoaderEntryFilterType, - ): Promise, LiveLoaderErrorType>>; - - /** Resolve an array of entry references from the same collection */ - export function getEntries( - entries: ReferenceContentEntry>[], - ): Promise[]>; - export function getEntries( - entries: ReferenceDataEntry[], - ): Promise[]>; - - export function render( - entry: AnyEntryMap[C][string], - ): Promise; - - export function reference( - collection: C, - ): import('astro/zod').ZodEffects< - import('astro/zod').ZodString, - C extends keyof ContentEntryMap - ? ReferenceContentEntry> - : ReferenceDataEntry - >; - // Allow generic `string` to avoid excessive type errors in the config - // if `dev` is not running to update as you edit. - // Invalid collection names will be caught at build time. - export function reference( - collection: C, - ): import('astro/zod').ZodEffects; - - type ReturnTypeOrOriginal = T extends (...args: any[]) => infer R ? R : T; - type InferEntrySchema = import('astro/zod').infer< - ReturnTypeOrOriginal['schema']> - >; - - type ContentEntryMap = { - - }; - - type DataEntryMap = { - "docs": Record; - rendered?: RenderedContent; - filePath?: string; -}>; - - }; - - type AnyEntryMap = ContentEntryMap & DataEntryMap; - - type ExtractLoaderTypes = T extends import('astro/loaders').LiveLoader< - infer TData, - infer TEntryFilter, - infer TCollectionFilter, - infer TError - > - ? { data: TData; entryFilter: TEntryFilter; collectionFilter: TCollectionFilter; error: TError } - : { data: never; entryFilter: never; collectionFilter: never; error: never }; - type ExtractDataType = ExtractLoaderTypes['data']; - type ExtractEntryFilterType = ExtractLoaderTypes['entryFilter']; - type ExtractCollectionFilterType = ExtractLoaderTypes['collectionFilter']; - type ExtractErrorType = ExtractLoaderTypes['error']; - - type LiveLoaderDataType = - LiveContentConfig['collections'][C]['schema'] extends undefined - ? ExtractDataType - : import('astro/zod').infer< - Exclude - >; - type LiveLoaderEntryFilterType = - ExtractEntryFilterType; - type LiveLoaderCollectionFilterType = - ExtractCollectionFilterType; - type LiveLoaderErrorType = ExtractErrorType< - LiveContentConfig['collections'][C]['loader'] - >; - - export type ContentConfig = typeof import("../src/content.config.js"); - export type LiveContentConfig = never; -} diff --git a/docs/.astro/data-store.json b/docs/.astro/data-store.json deleted file mode 100644 index e5e4b9b..0000000 --- a/docs/.astro/data-store.json +++ /dev/null @@ -1 +0,0 @@ -[["Map",1,2,9,10],"meta::meta",["Map",3,4,5,6,7,8],"astro-version","5.18.1","content-config-digest","96c7bc78b71524ea","astro-config-digest","{\"root\":{},\"srcDir\":{},\"publicDir\":{},\"outDir\":{},\"cacheDir\":{},\"compressHTML\":true,\"base\":\"/\",\"trailingSlash\":\"ignore\",\"output\":\"static\",\"scopedStyleStrategy\":\"where\",\"build\":{\"format\":\"directory\",\"client\":{},\"server\":{},\"assets\":\"_astro\",\"serverEntry\":\"entry.mjs\",\"redirects\":true,\"inlineStylesheets\":\"auto\",\"concurrency\":1},\"server\":{\"open\":false,\"host\":false,\"port\":4321,\"streaming\":true,\"allowedHosts\":[]},\"redirects\":{},\"image\":{\"endpoint\":{\"route\":\"/_image\"},\"service\":{\"entrypoint\":\"astro/assets/services/sharp\",\"config\":{}},\"domains\":[],\"remotePatterns\":[],\"responsiveStyles\":false},\"devToolbar\":{\"enabled\":true},\"markdown\":{\"syntaxHighlight\":false,\"shikiConfig\":{\"langs\":[],\"langAlias\":{},\"theme\":\"css-variables\",\"themes\":{},\"wrap\":false,\"transformers\":[]},\"remarkPlugins\":[null,null,null],\"rehypePlugins\":[null,[null,{\"themes\":[{\"name\":\"Night Owl No Italics\",\"type\":\"dark\",\"colors\":{\"focusBorder\":\"#122d42\",\"foreground\":\"#d6deeb\",\"disabledForeground\":\"#cccccc80\",\"descriptionForeground\":\"#d6deebb3\",\"errorForeground\":\"#ef5350\",\"icon.foreground\":\"#c5c5c5\",\"contrastActiveBorder\":null,\"contrastBorder\":\"#122d42\",\"textBlockQuote.background\":\"#7f7f7f1a\",\"textBlockQuote.border\":\"#007acc80\",\"textCodeBlock.background\":\"#4f4f4f\",\"textLink.activeForeground\":\"#3794ff\",\"textLink.foreground\":\"#3794ff\",\"textPreformat.foreground\":\"#d7ba7d\",\"textSeparator.foreground\":\"#ffffff2e\",\"editor.background\":\"#23262f\",\"editor.foreground\":\"#d6deeb\",\"editorLineNumber.foreground\":\"#4b6479\",\"editorLineNumber.activeForeground\":\"#c5e4fd\",\"editorActiveLineNumber.foreground\":\"#c6c6c6\",\"editor.selectionBackground\":\"#1d3b53\",\"editor.inactiveSelectionBackground\":\"#7e57c25a\",\"editor.selectionHighlightBackground\":\"#5f7e9779\",\"editorError.foreground\":\"#ef5350\",\"editorWarning.foreground\":\"#b39554\",\"editorInfo.foreground\":\"#3794ff\",\"editorHint.foreground\":\"#eeeeeeb2\",\"problemsErrorIcon.foreground\":\"#ef5350\",\"problemsWarningIcon.foreground\":\"#b39554\",\"problemsInfoIcon.foreground\":\"#3794ff\",\"editor.findMatchBackground\":\"#5f7e9779\",\"editor.findMatchHighlightBackground\":\"#1085bb5d\",\"editor.findRangeHighlightBackground\":\"#3a3d4166\",\"editorLink.activeForeground\":\"#4e94ce\",\"editorLightBulb.foreground\":\"#ffcc00\",\"editorLightBulbAutoFix.foreground\":\"#75beff\",\"diffEditor.insertedTextBackground\":\"#99b76d23\",\"diffEditor.insertedTextBorder\":\"#c5e47833\",\"diffEditor.removedTextBackground\":\"#ef535033\",\"diffEditor.removedTextBorder\":\"#ef53504d\",\"diffEditor.insertedLineBackground\":\"#9bb95533\",\"diffEditor.removedLineBackground\":\"#ff000033\",\"editorStickyScroll.background\":\"#011627\",\"editorStickyScrollHover.background\":\"#2a2d2e\",\"editorInlayHint.background\":\"#5f7e97cc\",\"editorInlayHint.foreground\":\"#ffffff\",\"editorInlayHint.typeBackground\":\"#5f7e97cc\",\"editorInlayHint.typeForeground\":\"#ffffff\",\"editorInlayHint.parameterBackground\":\"#5f7e97cc\",\"editorInlayHint.parameterForeground\":\"#ffffff\",\"editorPane.background\":\"#011627\",\"editorGroup.emptyBackground\":\"#011627\",\"editorGroup.focusedEmptyBorder\":null,\"editorGroupHeader.tabsBackground\":\"var(--sl-color-black)\",\"editorGroupHeader.tabsBorder\":\"color-mix(in srgb, var(--sl-color-gray-5), transparent 25%)\",\"editorGroupHeader.noTabsBackground\":\"#011627\",\"editorGroupHeader.border\":null,\"editorGroup.border\":\"#011627\",\"editorGroup.dropBackground\":\"#7e57c273\",\"editorGroup.dropIntoPromptForeground\":\"#d6deeb\",\"editorGroup.dropIntoPromptBackground\":\"#021320\",\"editorGroup.dropIntoPromptBorder\":null,\"sideBySideEditor.horizontalBorder\":\"#011627\",\"sideBySideEditor.verticalBorder\":\"#011627\",\"scrollbar.shadow\":\"#010b14\",\"scrollbarSlider.background\":\"#ffffff17\",\"scrollbarSlider.hoverBackground\":\"#ffffff40\",\"scrollbarSlider.activeBackground\":\"#084d8180\",\"panel.background\":\"#011627\",\"panel.border\":\"#5f7e97\",\"panelTitle.activeBorder\":\"#5f7e97\",\"panelTitle.activeForeground\":\"#ffffffcc\",\"panelTitle.inactiveForeground\":\"#d6deeb80\",\"panelSectionHeader.background\":\"#80808051\",\"terminal.background\":\"#011627\",\"widget.shadow\":\"#011627\",\"editorWidget.background\":\"#021320\",\"editorWidget.foreground\":\"#d6deeb\",\"editorWidget.border\":\"#5f7e97\",\"quickInput.background\":\"#021320\",\"quickInput.foreground\":\"#d6deeb\",\"quickInputTitle.background\":\"#ffffff1a\",\"pickerGroup.foreground\":\"#d1aaff\",\"pickerGroup.border\":\"#011627\",\"editor.hoverHighlightBackground\":\"#7e57c25a\",\"editorHoverWidget.background\":\"#011627\",\"editorHoverWidget.foreground\":\"#d6deeb\",\"editorHoverWidget.border\":\"#5f7e97\",\"editorHoverWidget.statusBarBackground\":\"#011a2f\",\"titleBar.activeBackground\":\"var(--sl-color-black)\",\"titleBar.activeForeground\":\"var(--sl-color-text)\",\"titleBar.inactiveBackground\":\"#010e1a\",\"titleBar.inactiveForeground\":\"#eeefff99\",\"titleBar.border\":\"color-mix(in srgb, var(--sl-color-gray-5), transparent 25%)\",\"toolbar.hoverBackground\":\"#5a5d5e50\",\"toolbar.activeBackground\":\"#63666750\",\"tab.activeBackground\":\"#0b2942\",\"tab.unfocusedActiveBackground\":\"#0b2942\",\"tab.inactiveBackground\":\"#01111d\",\"tab.unfocusedInactiveBackground\":\"#01111d\",\"tab.activeForeground\":\"var(--sl-color-text)\",\"tab.inactiveForeground\":\"#5f7e97\",\"tab.unfocusedActiveForeground\":\"#5f7e97\",\"tab.unfocusedInactiveForeground\":\"#5f7e97\",\"tab.hoverBackground\":null,\"tab.unfocusedHoverBackground\":null,\"tab.hoverForeground\":null,\"tab.unfocusedHoverForeground\":null,\"tab.border\":\"#272b3b\",\"tab.lastPinnedBorder\":\"#585858\",\"tab.activeBorder\":\"transparent\",\"tab.unfocusedActiveBorder\":\"#262a39\",\"tab.activeBorderTop\":\"var(--sl-color-accent-high)\",\"tab.unfocusedActiveBorderTop\":null,\"tab.hoverBorder\":null,\"tab.unfocusedHoverBorder\":null,\"tab.activeModifiedBorder\":\"#3399cc\",\"tab.inactiveModifiedBorder\":\"#3399cc80\",\"tab.unfocusedActiveModifiedBorder\":\"#3399cc80\",\"tab.unfocusedInactiveModifiedBorder\":\"#3399cc40\",\"badge.background\":\"#5f7e97\",\"badge.foreground\":\"#ffffff\",\"button.background\":\"#7e57c2cc\",\"button.foreground\":\"#ffffffcc\",\"button.border\":\"#122d42\",\"button.separator\":\"#ffffff52\",\"button.hoverBackground\":\"#7e57c2\",\"button.secondaryBackground\":\"#3a3d41\",\"button.secondaryForeground\":\"#ffffff\",\"button.secondaryHoverBackground\":\"#46494e\",\"dropdown.background\":\"#011627\",\"dropdown.foreground\":\"#ffffffcc\",\"dropdown.border\":\"#5f7e97\",\"list.activeSelectionBackground\":\"#234d708c\",\"list.activeSelectionForeground\":\"#ffffff\",\"tree.indentGuidesStroke\":\"#585858\",\"input.background\":\"#0b253a\",\"input.foreground\":\"#ffffffcc\",\"input.placeholderForeground\":\"#5f7e97\",\"inputOption.activeBorder\":\"#ffffffcc\",\"inputOption.hoverBackground\":\"#5a5d5e80\",\"inputOption.activeBackground\":\"#122d4266\",\"inputOption.activeForeground\":\"#ffffff\",\"inputValidation.infoBackground\":\"#00589ef2\",\"inputValidation.infoBorder\":\"#64b5f6\",\"inputValidation.warningBackground\":\"#675700f2\",\"inputValidation.warningBorder\":\"#ffca28\",\"inputValidation.errorBackground\":\"#ab0300f2\",\"inputValidation.errorBorder\":\"#ef5350\",\"keybindingLabel.background\":\"#8080802b\",\"keybindingLabel.foreground\":\"#cccccc\",\"keybindingLabel.border\":\"#33333399\",\"keybindingLabel.bottomBorder\":\"#44444499\",\"menu.foreground\":\"#ffffffcc\",\"menu.background\":\"#011627\",\"menu.selectionForeground\":\"#ffffff\",\"menu.selectionBackground\":\"#234d708c\",\"menu.separatorBackground\":\"#606060\",\"editor.snippetTabstopHighlightBackground\":\"#7c7c74c\",\"editor.snippetFinalTabstopHighlightBorder\":\"#525252\",\"terminal.ansiBlack\":\"#011627\",\"terminal.ansiRed\":\"#ef5350\",\"terminal.ansiGreen\":\"#22da6e\",\"terminal.ansiYellow\":\"#c5e478\",\"terminal.ansiBlue\":\"#82aaff\",\"terminal.ansiMagenta\":\"#c792ea\",\"terminal.ansiCyan\":\"#21c7a8\",\"terminal.ansiWhite\":\"#ffffff\",\"terminal.ansiBrightBlack\":\"#575656\",\"terminal.ansiBrightRed\":\"#ef5350\",\"terminal.ansiBrightGreen\":\"#22da6e\",\"terminal.ansiBrightYellow\":\"#ffeb95\",\"terminal.ansiBrightBlue\":\"#82aaff\",\"terminal.ansiBrightMagenta\":\"#c792ea\",\"terminal.ansiBrightCyan\":\"#7fdbca\",\"terminal.ansiBrightWhite\":\"#ffffff\",\"selection.background\":\"#4373c2\",\"input.border\":\"#5f7e97\",\"punctuation.definition.generic.begin.html\":\"#ef5350f2\",\"progress.background\":\"#7e57c2\",\"breadcrumb.foreground\":\"#a599e9\",\"breadcrumb.focusForeground\":\"#ffffff\",\"breadcrumb.activeSelectionForeground\":\"#ffffff\",\"breadcrumbPicker.background\":\"#001122\",\"list.invalidItemForeground\":\"#975f94\",\"list.dropBackground\":\"#011627\",\"list.focusBackground\":\"#010d18\",\"list.focusForeground\":\"#ffffff\",\"list.highlightForeground\":\"#ffffff\",\"list.hoverBackground\":\"#011627\",\"list.hoverForeground\":\"#ffffff\",\"list.inactiveSelectionBackground\":\"#0e293f\",\"list.inactiveSelectionForeground\":\"#5f7e97\",\"activityBar.background\":\"#011627\",\"activityBar.dropBackground\":\"#5f7e97\",\"activityBar.foreground\":\"#5f7e97\",\"activityBar.border\":\"#011627\",\"activityBarBadge.background\":\"#44596b\",\"activityBarBadge.foreground\":\"#ffffff\",\"sideBar.background\":\"#011627\",\"sideBar.foreground\":\"#89a4bb\",\"sideBar.border\":\"#011627\",\"sideBarTitle.foreground\":\"#5f7e97\",\"sideBarSectionHeader.background\":\"#011627\",\"sideBarSectionHeader.foreground\":\"#5f7e97\",\"editorCursor.foreground\":\"#80a4c2\",\"editor.wordHighlightBackground\":\"#f6bbe533\",\"editor.wordHighlightStrongBackground\":\"#e2a2f433\",\"editor.lineHighlightBackground\":\"#0003\",\"editor.rangeHighlightBackground\":\"#7e57c25a\",\"editorIndentGuide.background\":\"#5e81ce52\",\"editorIndentGuide.activeBackground\":\"#7e97ac\",\"editorRuler.foreground\":\"#5e81ce52\",\"editorCodeLens.foreground\":\"#5e82ceb4\",\"editorBracketMatch.background\":\"#5f7e974d\",\"editorOverviewRuler.currentContentForeground\":\"#7e57c2\",\"editorOverviewRuler.incomingContentForeground\":\"#7e57c2\",\"editorOverviewRuler.commonContentForeground\":\"#7e57c2\",\"editorGutter.background\":\"#011627\",\"editorGutter.modifiedBackground\":\"#e2b93d\",\"editorGutter.addedBackground\":\"#9ccc65\",\"editorGutter.deletedBackground\":\"#ef5350\",\"editorSuggestWidget.background\":\"#2c3043\",\"editorSuggestWidget.border\":\"#2b2f40\",\"editorSuggestWidget.foreground\":\"#d6deeb\",\"editorSuggestWidget.highlightForeground\":\"#ffffff\",\"editorSuggestWidget.selectedBackground\":\"#5f7e97\",\"debugExceptionWidget.background\":\"#011627\",\"debugExceptionWidget.border\":\"#5f7e97\",\"editorMarkerNavigation.background\":\"#0b2942\",\"editorMarkerNavigationError.background\":\"#ef5350\",\"editorMarkerNavigationWarning.background\":\"#ffca28\",\"peekView.border\":\"#5f7e97\",\"peekViewEditor.background\":\"#011627\",\"peekViewEditor.matchHighlightBackground\":\"#7e57c25a\",\"peekViewResult.background\":\"#011627\",\"peekViewResult.fileForeground\":\"#5f7e97\",\"peekViewResult.lineForeground\":\"#5f7e97\",\"peekViewResult.matchHighlightBackground\":\"#ffffffcc\",\"peekViewResult.selectionBackground\":\"#2e3250\",\"peekViewResult.selectionForeground\":\"#5f7e97\",\"peekViewTitle.background\":\"#011627\",\"peekViewTitleDescription.foreground\":\"#697098\",\"peekViewTitleLabel.foreground\":\"#5f7e97\",\"merge.currentHeaderBackground\":\"#5f7e97\",\"merge.incomingHeaderBackground\":\"#7e57c25a\",\"statusBar.background\":\"#011627\",\"statusBar.foreground\":\"#5f7e97\",\"statusBar.border\":\"#262a39\",\"statusBar.debuggingBackground\":\"#202431\",\"statusBar.debuggingBorder\":\"#1f2330\",\"statusBar.noFolderBackground\":\"#011627\",\"statusBar.noFolderBorder\":\"#25293a\",\"statusBarItem.activeBackground\":\"#202431\",\"statusBarItem.hoverBackground\":\"#202431\",\"statusBarItem.prominentBackground\":\"#202431\",\"statusBarItem.prominentHoverBackground\":\"#202431\",\"notifications.background\":\"#01111d\",\"notifications.border\":\"#262a39\",\"notificationCenter.border\":\"#262a39\",\"notificationToast.border\":\"#262a39\",\"notifications.foreground\":\"#ffffffcc\",\"notificationLink.foreground\":\"#80cbc4\",\"extensionButton.prominentForeground\":\"#ffffffcc\",\"extensionButton.prominentBackground\":\"#7e57c2cc\",\"extensionButton.prominentHoverBackground\":\"#7e57c2\",\"terminal.selectionBackground\":\"#1b90dd4d\",\"terminalCursor.background\":\"#234d70\",\"debugToolBar.background\":\"#011627\",\"welcomePage.buttonBackground\":\"#011627\",\"welcomePage.buttonHoverBackground\":\"#011627\",\"walkThrough.embeddedEditorBackground\":\"#011627\",\"gitDecoration.modifiedResourceForeground\":\"#a2bffc\",\"gitDecoration.deletedResourceForeground\":\"#ef535090\",\"gitDecoration.untrackedResourceForeground\":\"#c5e478ff\",\"gitDecoration.ignoredResourceForeground\":\"#395a75\",\"gitDecoration.conflictingResourceForeground\":\"#ffeb95cc\",\"source.elm\":\"#5f7e97\",\"string.quoted.single.js\":\"#ffffff\",\"meta.objectliteral.js\":\"#82aaff\"},\"fg\":\"#d6deeb\",\"bg\":\"#23262f\",\"semanticHighlighting\":false,\"settings\":[{\"name\":\"Changed\",\"scope\":[\"markup.changed\",\"meta.diff.header.git\",\"meta.diff.header.from-file\",\"meta.diff.header.to-file\"],\"settings\":{\"foreground\":\"#a2bffc\"}},{\"name\":\"Deleted\",\"scope\":[\"markup.deleted.diff\"],\"settings\":{\"foreground\":\"#f27775fe\"}},{\"name\":\"Inserted\",\"scope\":[\"markup.inserted.diff\"],\"settings\":{\"foreground\":\"#c5e478\"}},{\"name\":\"Global settings\",\"settings\":{\"background\":\"#011627\",\"foreground\":\"#d6deeb\"}},{\"name\":\"Comment\",\"scope\":[\"comment\"],\"settings\":{\"foreground\":\"#919f9f\",\"fontStyle\":\"\"}},{\"name\":\"String\",\"scope\":[\"string\"],\"settings\":{\"foreground\":\"#ecc48d\"}},{\"name\":\"String Quoted\",\"scope\":[\"string.quoted\",\"variable.other.readwrite.js\"],\"settings\":{\"foreground\":\"#ecc48d\"}},{\"name\":\"Support Constant Math\",\"scope\":[\"support.constant.math\"],\"settings\":{\"foreground\":\"#c5e478\"}},{\"name\":\"Number\",\"scope\":[\"constant.numeric\",\"constant.character.numeric\"],\"settings\":{\"foreground\":\"#f78c6c\",\"fontStyle\":\"\"}},{\"name\":\"Built-in constant\",\"scope\":[\"constant.language\",\"punctuation.definition.constant\",\"variable.other.constant\"],\"settings\":{\"foreground\":\"#82aaff\"}},{\"name\":\"User-defined constant\",\"scope\":[\"constant.character\",\"constant.other\"],\"settings\":{\"foreground\":\"#82aaff\"}},{\"name\":\"Constant Character Escape\",\"scope\":[\"constant.character.escape\"],\"settings\":{\"foreground\":\"#f78c6c\"}},{\"name\":\"RegExp String\",\"scope\":[\"string.regexp\",\"string.regexp keyword.other\"],\"settings\":{\"foreground\":\"#5ca7e4\"}},{\"name\":\"Comma in functions\",\"scope\":[\"meta.function punctuation.separator.comma\"],\"settings\":{\"foreground\":\"#889fb2\"}},{\"name\":\"Variable\",\"scope\":[\"variable\"],\"settings\":{\"foreground\":\"#c5e478\"}},{\"name\":\"Keyword\",\"scope\":[\"punctuation.accessor\",\"keyword\"],\"settings\":{\"foreground\":\"#c792ea\",\"fontStyle\":\"\"}},{\"name\":\"Storage\",\"scope\":[\"storage\",\"meta.var.expr\",\"meta.class meta.method.declaration meta.var.expr storage.type.js\",\"storage.type.property.js\",\"storage.type.property.ts\",\"storage.type.property.tsx\"],\"settings\":{\"foreground\":\"#c792ea\",\"fontStyle\":\"\"}},{\"name\":\"Storage type\",\"scope\":[\"storage.type\"],\"settings\":{\"foreground\":\"#c792ea\"}},{\"name\":\"Storage type\",\"scope\":[\"storage.type.function.arrow.js\"],\"settings\":{\"fontStyle\":\"\"}},{\"name\":\"Class name\",\"scope\":[\"entity.name.class\",\"meta.class entity.name.type.class\"],\"settings\":{\"foreground\":\"#ffcb8b\"}},{\"name\":\"Inherited class\",\"scope\":[\"entity.other.inherited-class\"],\"settings\":{\"foreground\":\"#c5e478\"}},{\"name\":\"Function name\",\"scope\":[\"entity.name.function\"],\"settings\":{\"foreground\":\"#c792ea\",\"fontStyle\":\"\"}},{\"name\":\"Meta Tag\",\"scope\":[\"punctuation.definition.tag\",\"meta.tag\"],\"settings\":{\"foreground\":\"#7fdbca\"}},{\"name\":\"HTML Tag names\",\"scope\":[\"entity.name.tag\",\"meta.tag.other.html\",\"meta.tag.other.js\",\"meta.tag.other.tsx\",\"entity.name.tag.tsx\",\"entity.name.tag.js\",\"entity.name.tag\",\"meta.tag.js\",\"meta.tag.tsx\",\"meta.tag.html\"],\"settings\":{\"foreground\":\"#caece6\",\"fontStyle\":\"\"}},{\"name\":\"Tag attribute\",\"scope\":[\"entity.other.attribute-name\"],\"settings\":{\"fontStyle\":\"\",\"foreground\":\"#c5e478\"}},{\"name\":\"Entity Name Tag Custom\",\"scope\":[\"entity.name.tag.custom\"],\"settings\":{\"foreground\":\"#c5e478\"}},{\"name\":\"Library (function & constant)\",\"scope\":[\"support.function\",\"support.constant\"],\"settings\":{\"foreground\":\"#82aaff\"}},{\"name\":\"Support Constant Property Value meta\",\"scope\":[\"support.constant.meta.property-value\"],\"settings\":{\"foreground\":\"#7fdbca\"}},{\"name\":\"Library class/type\",\"scope\":[\"support.type\",\"support.class\"],\"settings\":{\"foreground\":\"#c5e478\"}},{\"name\":\"Support Variable DOM\",\"scope\":[\"support.variable.dom\"],\"settings\":{\"foreground\":\"#c5e478\"}},{\"name\":\"Invalid\",\"scope\":[\"invalid\"],\"settings\":{\"background\":\"#ff2c83\",\"foreground\":\"#ffffff\"}},{\"name\":\"Invalid deprecated\",\"scope\":[\"invalid.deprecated\"],\"settings\":{\"foreground\":\"#ffffff\",\"background\":\"#d3423e\"}},{\"name\":\"Keyword Operator\",\"scope\":[\"keyword.operator\"],\"settings\":{\"foreground\":\"#7fdbca\",\"fontStyle\":\"\"}},{\"name\":\"Keyword Operator Relational\",\"scope\":[\"keyword.operator.relational\"],\"settings\":{\"foreground\":\"#c792ea\",\"fontStyle\":\"\"}},{\"name\":\"Keyword Operator Assignment\",\"scope\":[\"keyword.operator.assignment\"],\"settings\":{\"foreground\":\"#c792ea\"}},{\"name\":\"Keyword Operator Arithmetic\",\"scope\":[\"keyword.operator.arithmetic\"],\"settings\":{\"foreground\":\"#c792ea\"}},{\"name\":\"Keyword Operator Bitwise\",\"scope\":[\"keyword.operator.bitwise\"],\"settings\":{\"foreground\":\"#c792ea\"}},{\"name\":\"Keyword Operator Increment\",\"scope\":[\"keyword.operator.increment\"],\"settings\":{\"foreground\":\"#c792ea\"}},{\"name\":\"Keyword Operator Ternary\",\"scope\":[\"keyword.operator.ternary\"],\"settings\":{\"foreground\":\"#c792ea\"}},{\"name\":\"Double-Slashed Comment\",\"scope\":[\"comment.line.double-slash\"],\"settings\":{\"foreground\":\"#919f9f\"}},{\"name\":\"Object\",\"scope\":[\"object\"],\"settings\":{\"foreground\":\"#cdebf7\"}},{\"name\":\"Null\",\"scope\":[\"constant.language.null\"],\"settings\":{\"foreground\":\"#ff6a83\"}},{\"name\":\"Meta Brace\",\"scope\":[\"meta.brace\"],\"settings\":{\"foreground\":\"#d6deeb\"}},{\"name\":\"Meta Delimiter Period\",\"scope\":[\"meta.delimiter.period\"],\"settings\":{\"foreground\":\"#c792ea\",\"fontStyle\":\"\"}},{\"name\":\"Punctuation Definition String\",\"scope\":[\"punctuation.definition.string\"],\"settings\":{\"foreground\":\"#d9f5dd\"}},{\"name\":\"Punctuation Definition String Markdown\",\"scope\":[\"punctuation.definition.string.begin.markdown\"],\"settings\":{\"foreground\":\"#ff6a83\"}},{\"name\":\"Boolean\",\"scope\":[\"constant.language.boolean\"],\"settings\":{\"foreground\":\"#ff6a83\"}},{\"name\":\"Object Comma\",\"scope\":[\"object.comma\"],\"settings\":{\"foreground\":\"#ffffff\"}},{\"name\":\"Variable Parameter Function\",\"scope\":[\"variable.parameter.function\"],\"settings\":{\"foreground\":\"#7fdbca\",\"fontStyle\":\"\"}},{\"name\":\"Support Type Property Name & entity name tags\",\"scope\":[\"support.type.vendor.property-name\",\"support.constant.vendor.property-value\",\"support.type.property-name\",\"meta.property-list entity.name.tag\"],\"settings\":{\"foreground\":\"#80cbc4\",\"fontStyle\":\"\"}},{\"name\":\"Entity Name tag reference in stylesheets\",\"scope\":[\"meta.property-list entity.name.tag.reference\"],\"settings\":{\"foreground\":\"#57eaf1\"}},{\"name\":\"Constant Other Color RGB Value Punctuation Definition Constant\",\"scope\":[\"constant.other.color.rgb-value punctuation.definition.constant\"],\"settings\":{\"foreground\":\"#f78c6c\"}},{\"name\":\"Constant Other Color\",\"scope\":[\"constant.other.color\"],\"settings\":{\"foreground\":\"#ffeb95\"}},{\"name\":\"Keyword Other Unit\",\"scope\":[\"keyword.other.unit\"],\"settings\":{\"foreground\":\"#ffeb95\"}},{\"name\":\"Meta Selector\",\"scope\":[\"meta.selector\"],\"settings\":{\"foreground\":\"#c792ea\",\"fontStyle\":\"\"}},{\"name\":\"Entity Other Attribute Name Id\",\"scope\":[\"entity.other.attribute-name.id\"],\"settings\":{\"foreground\":\"#fad430\"}},{\"name\":\"Meta Property Name\",\"scope\":[\"meta.property-name\"],\"settings\":{\"foreground\":\"#80cbc4\"}},{\"name\":\"Doctypes\",\"scope\":[\"entity.name.tag.doctype\",\"meta.tag.sgml.doctype\"],\"settings\":{\"foreground\":\"#c792ea\",\"fontStyle\":\"\"}},{\"name\":\"Punctuation Definition Parameters\",\"scope\":[\"punctuation.definition.parameters\"],\"settings\":{\"foreground\":\"#d9f5dd\"}},{\"name\":\"Keyword Control Operator\",\"scope\":[\"keyword.control.operator\"],\"settings\":{\"foreground\":\"#7fdbca\"}},{\"name\":\"Keyword Operator Logical\",\"scope\":[\"keyword.operator.logical\"],\"settings\":{\"foreground\":\"#c792ea\",\"fontStyle\":\"\"}},{\"name\":\"Variable Instances\",\"scope\":[\"variable.instance\",\"variable.other.instance\",\"variable.readwrite.instance\",\"variable.other.readwrite.instance\",\"variable.other.property\"],\"settings\":{\"foreground\":\"#7fdbca\"}},{\"name\":\"Variable Property Other object property\",\"scope\":[\"variable.other.object.property\"],\"settings\":{\"foreground\":\"#faf39f\",\"fontStyle\":\"\"}},{\"name\":\"Variable Property Other object\",\"scope\":[\"variable.other.object.js\"],\"settings\":{\"fontStyle\":\"\"}},{\"name\":\"Entity Name Function\",\"scope\":[\"entity.name.function\"],\"settings\":{\"foreground\":\"#82aaff\",\"fontStyle\":\"\"}},{\"name\":\"Keyword Operator Comparison, returns, imports, and Keyword Operator Ruby\",\"scope\":[\"keyword.control.conditional.js\",\"keyword.operator.comparison\",\"keyword.control.flow.js\",\"keyword.control.flow.ts\",\"keyword.control.flow.tsx\",\"keyword.control.ruby\",\"keyword.control.def.ruby\",\"keyword.control.loop.js\",\"keyword.control.loop.ts\",\"keyword.control.import.js\",\"keyword.control.import.ts\",\"keyword.control.import.tsx\",\"keyword.control.from.js\",\"keyword.control.from.ts\",\"keyword.control.from.tsx\",\"keyword.control.conditional.js\",\"keyword.control.conditional.ts\",\"keyword.control.switch.js\",\"keyword.control.switch.ts\",\"keyword.operator.instanceof.js\",\"keyword.operator.expression.instanceof.ts\",\"keyword.operator.expression.instanceof.tsx\"],\"settings\":{\"foreground\":\"#c792ea\",\"fontStyle\":\"\"}},{\"name\":\"Support Constant, `new` keyword, Special Method Keyword, `debugger`, other keywords\",\"scope\":[\"support.constant\",\"keyword.other.special-method\",\"keyword.other.new\",\"keyword.other.debugger\",\"keyword.control\"],\"settings\":{\"foreground\":\"#7fdbca\"}},{\"name\":\"Support Function\",\"scope\":[\"support.function\"],\"settings\":{\"foreground\":\"#c5e478\"}},{\"name\":\"Invalid Broken\",\"scope\":[\"invalid.broken\"],\"settings\":{\"foreground\":\"#989da0\",\"background\":\"#F78C6C\"}},{\"name\":\"Invalid Unimplemented\",\"scope\":[\"invalid.unimplemented\"],\"settings\":{\"background\":\"#8BD649\",\"foreground\":\"#ffffff\"}},{\"name\":\"Invalid Illegal\",\"scope\":[\"invalid.illegal\"],\"settings\":{\"foreground\":\"#ffffff\",\"background\":\"#ec5f67\"}},{\"name\":\"Language Variable\",\"scope\":[\"variable.language\"],\"settings\":{\"foreground\":\"#7fdbca\"}},{\"name\":\"Support Variable Property\",\"scope\":[\"support.variable.property\"],\"settings\":{\"foreground\":\"#7fdbca\"}},{\"name\":\"Variable Function\",\"scope\":[\"variable.function\"],\"settings\":{\"foreground\":\"#82aaff\"}},{\"name\":\"Variable Interpolation\",\"scope\":[\"variable.interpolation\"],\"settings\":{\"foreground\":\"#ef787f\"}},{\"name\":\"Meta Function Call\",\"scope\":[\"meta.function-call\"],\"settings\":{\"foreground\":\"#82aaff\"}},{\"name\":\"Punctuation Section Embedded\",\"scope\":[\"punctuation.section.embedded\"],\"settings\":{\"foreground\":\"#e2817f\"}},{\"name\":\"Punctuation Tweaks\",\"scope\":[\"punctuation.terminator.expression\",\"punctuation.definition.arguments\",\"punctuation.definition.array\",\"punctuation.section.array\",\"meta.array\"],\"settings\":{\"foreground\":\"#d6deeb\"}},{\"name\":\"More Punctuation Tweaks\",\"scope\":[\"punctuation.definition.list.begin\",\"punctuation.definition.list.end\",\"punctuation.separator.arguments\",\"punctuation.definition.list\"],\"settings\":{\"foreground\":\"#d9f5dd\"}},{\"name\":\"Template Strings\",\"scope\":[\"string.template meta.template.expression\"],\"settings\":{\"foreground\":\"#e2817f\"}},{\"name\":\"Backtics(``) in Template Strings\",\"scope\":[\"string.template punctuation.definition.string\"],\"settings\":{\"foreground\":\"#d6deeb\"}},{\"name\":\"Italics\",\"scope\":[\"italic\"],\"settings\":{\"foreground\":\"#c792ea\",\"fontStyle\":\"italic\"}},{\"name\":\"Bold\",\"scope\":[\"bold\"],\"settings\":{\"foreground\":\"#c5e478\",\"fontStyle\":\"bold\"}},{\"name\":\"Quote\",\"scope\":[\"quote\"],\"settings\":{\"foreground\":\"#969bb7\",\"fontStyle\":\"\"}},{\"name\":\"Raw Code\",\"scope\":[\"raw\"],\"settings\":{\"foreground\":\"#80cbc4\"}},{\"name\":\"CoffeScript Variable Assignment\",\"scope\":[\"variable.assignment.coffee\"],\"settings\":{\"foreground\":\"#31e1eb\"}},{\"name\":\"CoffeScript Parameter Function\",\"scope\":[\"variable.parameter.function.coffee\"],\"settings\":{\"foreground\":\"#d6deeb\"}},{\"name\":\"CoffeeScript Assignments\",\"scope\":[\"variable.assignment.coffee\"],\"settings\":{\"foreground\":\"#7fdbca\"}},{\"name\":\"C# Readwrite Variables\",\"scope\":[\"variable.other.readwrite.cs\"],\"settings\":{\"foreground\":\"#d6deeb\"}},{\"name\":\"C# Classes & Storage types\",\"scope\":[\"entity.name.type.class.cs\",\"storage.type.cs\"],\"settings\":{\"foreground\":\"#ffcb8b\"}},{\"name\":\"C# Namespaces\",\"scope\":[\"entity.name.type.namespace.cs\"],\"settings\":{\"foreground\":\"#b2ccd6\"}},{\"name\":\"C# Unquoted String Zone\",\"scope\":[\"string.unquoted.preprocessor.message.cs\"],\"settings\":{\"foreground\":\"#d6deeb\"}},{\"name\":\"C# Region\",\"scope\":[\"punctuation.separator.hash.cs\",\"keyword.preprocessor.region.cs\",\"keyword.preprocessor.endregion.cs\"],\"settings\":{\"foreground\":\"#ffcb8b\",\"fontStyle\":\"bold\"}},{\"name\":\"C# Other Variables\",\"scope\":[\"variable.other.object.cs\"],\"settings\":{\"foreground\":\"#b2ccd6\"}},{\"name\":\"C# Enum\",\"scope\":[\"entity.name.type.enum.cs\"],\"settings\":{\"foreground\":\"#c5e478\"}},{\"name\":\"Dart String\",\"scope\":[\"string.interpolated.single.dart\",\"string.interpolated.double.dart\"],\"settings\":{\"foreground\":\"#ffcb8b\"}},{\"name\":\"Dart Class\",\"scope\":[\"support.class.dart\"],\"settings\":{\"foreground\":\"#ffcb8b\"}},{\"name\":\"Tag names in Stylesheets\",\"scope\":[\"entity.name.tag.css\",\"entity.name.tag.less\",\"entity.name.tag.custom.css\",\"support.constant.property-value.css\"],\"settings\":{\"foreground\":\"#ff6d6d\",\"fontStyle\":\"\"}},{\"name\":\"Wildcard(*) selector in Stylesheets\",\"scope\":[\"entity.name.tag.wildcard.css\",\"entity.name.tag.wildcard.less\",\"entity.name.tag.wildcard.scss\",\"entity.name.tag.wildcard.sass\"],\"settings\":{\"foreground\":\"#7fdbca\"}},{\"name\":\"CSS Keyword Other Unit\",\"scope\":[\"keyword.other.unit.css\"],\"settings\":{\"foreground\":\"#ffeb95\"}},{\"name\":\"Attribute Name for CSS\",\"scope\":[\"meta.attribute-selector.css entity.other.attribute-name.attribute\",\"variable.other.readwrite.js\"],\"settings\":{\"foreground\":\"#f78c6c\"}},{\"name\":\"Elixir Classes\",\"scope\":[\"source.elixir support.type.elixir\",\"source.elixir meta.module.elixir entity.name.class.elixir\"],\"settings\":{\"foreground\":\"#82aaff\"}},{\"name\":\"Elixir Functions\",\"scope\":[\"source.elixir entity.name.function\"],\"settings\":{\"foreground\":\"#c5e478\"}},{\"name\":\"Elixir Constants\",\"scope\":[\"source.elixir constant.other.symbol.elixir\",\"source.elixir constant.other.keywords.elixir\"],\"settings\":{\"foreground\":\"#82aaff\"}},{\"name\":\"Elixir String Punctuations\",\"scope\":[\"source.elixir punctuation.definition.string\"],\"settings\":{\"foreground\":\"#c5e478\"}},{\"name\":\"Elixir\",\"scope\":[\"source.elixir variable.other.readwrite.module.elixir\",\"source.elixir variable.other.readwrite.module.elixir punctuation.definition.variable.elixir\"],\"settings\":{\"foreground\":\"#c5e478\"}},{\"name\":\"Elixir Binary Punctuations\",\"scope\":[\"source.elixir .punctuation.binary.elixir\"],\"settings\":{\"foreground\":\"#c792ea\",\"fontStyle\":\"\"}},{\"name\":\"Closure Constant Keyword\",\"scope\":[\"constant.keyword.clojure\"],\"settings\":{\"foreground\":\"#7fdbca\"}},{\"name\":\"Go Function Calls\",\"scope\":[\"source.go meta.function-call.go\"],\"settings\":{\"foreground\":\"#dddddd\"}},{\"name\":\"Go Keywords\",\"scope\":[\"source.go keyword.package.go\",\"source.go keyword.import.go\",\"source.go keyword.function.go\",\"source.go keyword.type.go\",\"source.go keyword.struct.go\",\"source.go keyword.interface.go\",\"source.go keyword.const.go\",\"source.go keyword.var.go\",\"source.go keyword.map.go\",\"source.go keyword.channel.go\",\"source.go keyword.control.go\"],\"settings\":{\"foreground\":\"#c792ea\"}},{\"name\":\"Go Constants e.g. nil, string format (%s, %d, etc.)\",\"scope\":[\"source.go constant.language.go\",\"source.go constant.other.placeholder.go\"],\"settings\":{\"foreground\":\"#ff6a83\"}},{\"name\":\"C++ Functions\",\"scope\":[\"entity.name.function.preprocessor.cpp\",\"entity.scope.name.cpp\"],\"settings\":{\"foreground\":\"#7fdbca\"}},{\"name\":\"C++ Meta Namespace\",\"scope\":[\"meta.namespace-block.cpp\"],\"settings\":{\"foreground\":\"#e0dec6\"}},{\"name\":\"C++ Language Primitive Storage\",\"scope\":[\"storage.type.language.primitive.cpp\"],\"settings\":{\"foreground\":\"#ff6a83\"}},{\"name\":\"C++ Preprocessor Macro\",\"scope\":[\"meta.preprocessor.macro.cpp\"],\"settings\":{\"foreground\":\"#d6deeb\"}},{\"name\":\"C++ Variable Parameter\",\"scope\":[\"variable.parameter\"],\"settings\":{\"foreground\":\"#ffcb8b\"}},{\"name\":\"Powershell Variables\",\"scope\":[\"variable.other.readwrite.powershell\"],\"settings\":{\"foreground\":\"#82aaff\"}},{\"name\":\"Powershell Function\",\"scope\":[\"support.function.powershell\"],\"settings\":{\"foreground\":\"#7fdbca\"}},{\"name\":\"ID Attribute Name in HTML\",\"scope\":[\"entity.other.attribute-name.id.html\"],\"settings\":{\"foreground\":\"#c5e478\"}},{\"name\":\"HTML Punctuation Definition Tag\",\"scope\":[\"punctuation.definition.tag.html\"],\"settings\":{\"foreground\":\"#6ae9f0\"}},{\"name\":\"HTML Doctype\",\"scope\":[\"meta.tag.sgml.doctype.html\"],\"settings\":{\"foreground\":\"#c792ea\",\"fontStyle\":\"\"}},{\"name\":\"JavaScript Classes\",\"scope\":[\"meta.class entity.name.type.class.js\"],\"settings\":{\"foreground\":\"#ffcb8b\"}},{\"name\":\"JavaScript Method Declaration e.g. `constructor`\",\"scope\":[\"meta.method.declaration storage.type.js\"],\"settings\":{\"foreground\":\"#82aaff\"}},{\"name\":\"JavaScript Terminator\",\"scope\":[\"terminator.js\"],\"settings\":{\"foreground\":\"#d6deeb\"}},{\"name\":\"JavaScript Meta Punctuation Definition\",\"scope\":[\"meta.js punctuation.definition.js\"],\"settings\":{\"foreground\":\"#d6deeb\"}},{\"name\":\"Entity Names in Code Documentations\",\"scope\":[\"entity.name.type.instance.jsdoc\",\"entity.name.type.instance.phpdoc\"],\"settings\":{\"foreground\":\"#889fb2\"}},{\"name\":\"Other Variables in Code Documentations\",\"scope\":[\"variable.other.jsdoc\",\"variable.other.phpdoc\"],\"settings\":{\"foreground\":\"#78ccf0\"}},{\"name\":\"JavaScript module imports and exports\",\"scope\":[\"variable.other.meta.import.js\",\"meta.import.js variable.other\",\"variable.other.meta.export.js\",\"meta.export.js variable.other\"],\"settings\":{\"foreground\":\"#d6deeb\"}},{\"name\":\"JavaScript Variable Parameter Function\",\"scope\":[\"variable.parameter.function.js\"],\"settings\":{\"foreground\":\"#8b96ea\"}},{\"name\":\"JavaScript[React] Variable Other Object\",\"scope\":[\"variable.other.object.js\",\"variable.other.object.jsx\",\"variable.object.property.js\",\"variable.object.property.jsx\"],\"settings\":{\"foreground\":\"#d6deeb\"}},{\"name\":\"JavaScript Variables\",\"scope\":[\"variable.js\",\"variable.other.js\"],\"settings\":{\"foreground\":\"#d6deeb\"}},{\"name\":\"JavaScript Entity Name Type\",\"scope\":[\"entity.name.type.js\",\"entity.name.type.module.js\"],\"settings\":{\"foreground\":\"#ffcb8b\",\"fontStyle\":\"\"}},{\"name\":\"JavaScript Support Classes\",\"scope\":[\"support.class.js\"],\"settings\":{\"foreground\":\"#d6deeb\"}},{\"name\":\"JSON Property Names\",\"scope\":[\"support.type.property-name.json\"],\"settings\":{\"foreground\":\"#7fdbca\"}},{\"name\":\"JSON Support Constants\",\"scope\":[\"support.constant.json\"],\"settings\":{\"foreground\":\"#c5e478\"}},{\"name\":\"JSON Property values (string)\",\"scope\":[\"meta.structure.dictionary.value.json string.quoted.double\"],\"settings\":{\"foreground\":\"#c789d6\"}},{\"name\":\"Strings in JSON values\",\"scope\":[\"string.quoted.double.json punctuation.definition.string.json\"],\"settings\":{\"foreground\":\"#80cbc4\"}},{\"name\":\"Specific JSON Property values like null\",\"scope\":[\"meta.structure.dictionary.json meta.structure.dictionary.value constant.language\"],\"settings\":{\"foreground\":\"#ff6a83\"}},{\"name\":\"JavaScript Other Variable\",\"scope\":[\"variable.other.object.js\"],\"settings\":{\"foreground\":\"#7fdbca\"}},{\"name\":\"Ruby Variables\",\"scope\":[\"variable.other.ruby\"],\"settings\":{\"foreground\":\"#d6deeb\"}},{\"name\":\"Ruby Class\",\"scope\":[\"entity.name.type.class.ruby\"],\"settings\":{\"foreground\":\"#ecc48d\"}},{\"name\":\"Ruby Hashkeys\",\"scope\":[\"constant.language.symbol.hashkey.ruby\"],\"settings\":{\"foreground\":\"#7fdbca\"}},{\"name\":\"LESS Tag names\",\"scope\":[\"entity.name.tag.less\"],\"settings\":{\"foreground\":\"#7fdbca\"}},{\"name\":\"LESS Keyword Other Unit\",\"scope\":[\"keyword.other.unit.css\"],\"settings\":{\"foreground\":\"#ffeb95\"}},{\"name\":\"Attribute Name for LESS\",\"scope\":[\"meta.attribute-selector.less entity.other.attribute-name.attribute\"],\"settings\":{\"foreground\":\"#f78c6c\"}},{\"name\":\"Markdown Headings\",\"scope\":[\"markup.heading.markdown\",\"markup.heading.setext.1.markdown\",\"markup.heading.setext.2.markdown\"],\"settings\":{\"foreground\":\"#82b1ff\"}},{\"name\":\"Markdown Italics\",\"scope\":[\"markup.italic.markdown\"],\"settings\":{\"foreground\":\"#c792ea\",\"fontStyle\":\"italic\"}},{\"name\":\"Markdown Bold\",\"scope\":[\"markup.bold.markdown\"],\"settings\":{\"foreground\":\"#c5e478\",\"fontStyle\":\"bold\"}},{\"name\":\"Markdown Quote + others\",\"scope\":[\"markup.quote.markdown\"],\"settings\":{\"foreground\":\"#969bb7\",\"fontStyle\":\"\"}},{\"name\":\"Markdown Raw Code + others\",\"scope\":[\"markup.inline.raw.markdown\"],\"settings\":{\"foreground\":\"#80cbc4\"}},{\"name\":\"Markdown Links\",\"scope\":[\"markup.underline.link.markdown\",\"markup.underline.link.image.markdown\"],\"settings\":{\"foreground\":\"#ff869a\",\"fontStyle\":\"underline\"}},{\"name\":\"Markdown Link Title and Description\",\"scope\":[\"string.other.link.title.markdown\",\"string.other.link.description.markdown\"],\"settings\":{\"foreground\":\"#d6deeb\",\"fontStyle\":\"underline\"}},{\"name\":\"Markdown Punctuation\",\"scope\":[\"punctuation.definition.string.markdown\",\"punctuation.definition.string.begin.markdown\",\"punctuation.definition.string.end.markdown\",\"meta.link.inline.markdown punctuation.definition.string\"],\"settings\":{\"foreground\":\"#82b1ff\"}},{\"name\":\"Markdown MetaData Punctuation\",\"scope\":[\"punctuation.definition.metadata.markdown\"],\"settings\":{\"foreground\":\"#7fdbca\"}},{\"name\":\"Markdown List Punctuation\",\"scope\":[\"beginning.punctuation.definition.list.markdown\"],\"settings\":{\"foreground\":\"#82b1ff\"}},{\"name\":\"Markdown Inline Raw String\",\"scope\":[\"markup.inline.raw.string.markdown\"],\"settings\":{\"foreground\":\"#c5e478\"}},{\"name\":\"PHP Variables\",\"scope\":[\"variable.other.php\"],\"settings\":{\"foreground\":\"#bec5d4\"}},{\"name\":\"Support Classes in PHP\",\"scope\":[\"support.class.php\"],\"settings\":{\"foreground\":\"#ffcb8b\"}},{\"name\":\"Punctuations in PHP function calls\",\"scope\":[\"meta.function-call.php punctuation\"],\"settings\":{\"foreground\":\"#d6deeb\"}},{\"name\":\"PHP Global Variables\",\"scope\":[\"variable.other.global.php\"],\"settings\":{\"foreground\":\"#c5e478\"}},{\"name\":\"Declaration Punctuation in PHP Global Variables\",\"scope\":[\"variable.other.global.php punctuation.definition.variable\"],\"settings\":{\"foreground\":\"#c5e478\"}},{\"name\":\"Language Constants in Python\",\"scope\":[\"constant.language.python\"],\"settings\":{\"foreground\":\"#ff6a83\"}},{\"name\":\"Python Function Parameter and Arguments\",\"scope\":[\"variable.parameter.function.python\",\"meta.function-call.arguments.python\"],\"settings\":{\"foreground\":\"#82aaff\"}},{\"name\":\"Python Function Call\",\"scope\":[\"meta.function-call.python\",\"meta.function-call.generic.python\"],\"settings\":{\"foreground\":\"#b2ccd6\"}},{\"name\":\"Punctuations in Python\",\"scope\":[\"punctuation.python\"],\"settings\":{\"foreground\":\"#d6deeb\"}},{\"name\":\"Decorator Functions in Python\",\"scope\":[\"entity.name.function.decorator.python\"],\"settings\":{\"foreground\":\"#c5e478\"}},{\"name\":\"Python Language Variable\",\"scope\":[\"source.python variable.language.special\"],\"settings\":{\"foreground\":\"#8eace3\"}},{\"name\":\"Python import control keyword\",\"scope\":[\"keyword.control\"],\"settings\":{\"foreground\":\"#c792ea\"}},{\"name\":\"SCSS Variable\",\"scope\":[\"variable.scss\",\"variable.sass\",\"variable.parameter.url.scss\",\"variable.parameter.url.sass\"],\"settings\":{\"foreground\":\"#c5e478\"}},{\"name\":\"Variables in SASS At-Rules\",\"scope\":[\"source.css.scss meta.at-rule variable\",\"source.css.sass meta.at-rule variable\"],\"settings\":{\"foreground\":\"#82aaff\"}},{\"name\":\"Variables in SASS At-Rules\",\"scope\":[\"source.css.scss meta.at-rule variable\",\"source.css.sass meta.at-rule variable\"],\"settings\":{\"foreground\":\"#bec5d4\"}},{\"name\":\"Attribute Name for SASS\",\"scope\":[\"meta.attribute-selector.scss entity.other.attribute-name.attribute\",\"meta.attribute-selector.sass entity.other.attribute-name.attribute\"],\"settings\":{\"foreground\":\"#f78c6c\"}},{\"name\":\"Tag names in SASS\",\"scope\":[\"entity.name.tag.scss\",\"entity.name.tag.sass\"],\"settings\":{\"foreground\":\"#7fdbca\"}},{\"name\":\"SASS Keyword Other Unit\",\"scope\":[\"keyword.other.unit.scss\",\"keyword.other.unit.sass\"],\"settings\":{\"foreground\":\"#ffeb95\"}},{\"name\":\"TypeScript[React] Variables and Object Properties\",\"scope\":[\"variable.other.readwrite.alias.ts\",\"variable.other.readwrite.alias.tsx\",\"variable.other.readwrite.ts\",\"variable.other.readwrite.tsx\",\"variable.other.object.ts\",\"variable.other.object.tsx\",\"variable.object.property.ts\",\"variable.object.property.tsx\",\"variable.other.ts\",\"variable.other.tsx\",\"variable.tsx\",\"variable.ts\"],\"settings\":{\"foreground\":\"#d6deeb\"}},{\"name\":\"TypeScript[React] Entity Name Types\",\"scope\":[\"entity.name.type.ts\",\"entity.name.type.tsx\"],\"settings\":{\"foreground\":\"#ffcb8b\"}},{\"name\":\"TypeScript[React] Node Classes\",\"scope\":[\"support.class.node.ts\",\"support.class.node.tsx\"],\"settings\":{\"foreground\":\"#82aaff\"}},{\"name\":\"TypeScript[React] Entity Name Types as Parameters\",\"scope\":[\"meta.type.parameters.ts entity.name.type\",\"meta.type.parameters.tsx entity.name.type\"],\"settings\":{\"foreground\":\"#889fb2\"}},{\"name\":\"TypeScript[React] Import/Export Punctuations\",\"scope\":[\"meta.import.ts punctuation.definition.block\",\"meta.import.tsx punctuation.definition.block\",\"meta.export.ts punctuation.definition.block\",\"meta.export.tsx punctuation.definition.block\"],\"settings\":{\"foreground\":\"#d6deeb\"}},{\"name\":\"TypeScript[React] Punctuation Decorators\",\"scope\":[\"meta.decorator punctuation.decorator.ts\",\"meta.decorator punctuation.decorator.tsx\"],\"settings\":{\"foreground\":\"#82aaff\"}},{\"name\":\"TypeScript[React] Punctuation Decorators\",\"scope\":[\"meta.tag.js meta.jsx.children.tsx\"],\"settings\":{\"foreground\":\"#82aaff\"}},{\"name\":\"YAML Entity Name Tags\",\"scope\":[\"entity.name.tag.yaml\"],\"settings\":{\"foreground\":\"#7fdbca\"}},{\"name\":\"JavaScript Variable Other ReadWrite\",\"scope\":[\"variable.other.readwrite.js\",\"variable.parameter\"],\"settings\":{\"foreground\":\"#d7dbe0\"}},{\"name\":\"Support Class Component\",\"scope\":[\"support.class.component.js\",\"support.class.component.tsx\"],\"settings\":{\"foreground\":\"#f78c6c\",\"fontStyle\":\"\"}},{\"name\":\"Text nested in React tags\",\"scope\":[\"meta.jsx.children\",\"meta.jsx.children.js\",\"meta.jsx.children.tsx\"],\"settings\":{\"foreground\":\"#d6deeb\"}},{\"name\":\"TypeScript Classes\",\"scope\":[\"meta.class entity.name.type.class.tsx\"],\"settings\":{\"foreground\":\"#ffcb8b\"}},{\"name\":\"TypeScript Entity Name Type\",\"scope\":[\"entity.name.type.tsx\",\"entity.name.type.module.tsx\"],\"settings\":{\"foreground\":\"#ffcb8b\"}},{\"name\":\"TypeScript Class Variable Keyword\",\"scope\":[\"meta.class.ts meta.var.expr.ts storage.type.ts\",\"meta.class.tsx meta.var.expr.tsx storage.type.tsx\"],\"settings\":{\"foreground\":\"#c792ea\"}},{\"name\":\"TypeScript Method Declaration e.g. `constructor`\",\"scope\":[\"meta.method.declaration storage.type.ts\",\"meta.method.declaration storage.type.tsx\"],\"settings\":{\"foreground\":\"#82aaff\"}},{\"name\":\"normalize font style of certain components\",\"scope\":[\"meta.property-list.css meta.property-value.css variable.other.less\",\"meta.property-list.scss variable.scss\",\"meta.property-list.sass variable.sass\",\"meta.brace\",\"keyword.operator.operator\",\"keyword.operator.or.regexp\",\"keyword.operator.expression.in\",\"keyword.operator.relational\",\"keyword.operator.assignment\",\"keyword.operator.comparison\",\"keyword.operator.type\",\"keyword.operator\",\"keyword\",\"punctuation.definintion.string\",\"punctuation\",\"variable.other.readwrite.js\",\"storage.type\",\"source.css\",\"string.quoted\"],\"settings\":{\"fontStyle\":\"\"}}],\"styleOverrides\":{\"frames\":{\"editorBackground\":\"var(--sl-color-gray-6)\",\"terminalBackground\":\"var(--sl-color-gray-6)\",\"editorActiveTabBackground\":\"var(--sl-color-gray-6)\",\"terminalTitlebarDotsForeground\":\"color-mix(in srgb, var(--sl-color-gray-5), transparent 25%)\",\"terminalTitlebarDotsOpacity\":\"0.75\",\"inlineButtonForeground\":\"var(--sl-color-text)\",\"frameBoxShadowCssValue\":\"none\"},\"textMarkers\":{\"markBackground\":\"#ffffff17\",\"markBorderColor\":\"#ffffff40\"}}},{\"name\":\"Night Owl Light\",\"type\":\"light\",\"colors\":{\"focusBorder\":\"#93a1a1\",\"foreground\":\"#403f53\",\"disabledForeground\":\"#61616180\",\"descriptionForeground\":\"#403f53\",\"errorForeground\":\"#403f53\",\"icon.foreground\":\"#424242\",\"contrastActiveBorder\":null,\"contrastBorder\":null,\"textBlockQuote.background\":\"#7f7f7f1a\",\"textBlockQuote.border\":\"#007acc80\",\"textCodeBlock.background\":\"#dcdcdc66\",\"textLink.activeForeground\":\"#006ab1\",\"textLink.foreground\":\"#006ab1\",\"textPreformat.foreground\":\"#a31515\",\"textSeparator.foreground\":\"#0000002e\",\"editor.background\":\"#f6f7f9\",\"editor.foreground\":\"#403f53\",\"editorLineNumber.foreground\":\"#90a7b2\",\"editorLineNumber.activeForeground\":\"#403f53\",\"editorActiveLineNumber.foreground\":\"#0b216f\",\"editor.selectionBackground\":\"#e0e0e0\",\"editor.inactiveSelectionBackground\":\"#e0e0e080\",\"editor.selectionHighlightBackground\":\"#339cec33\",\"editorError.foreground\":\"#e64d49\",\"editorWarning.foreground\":\"#daaa01\",\"editorInfo.foreground\":\"#1a85ff\",\"editorHint.foreground\":\"#6c6c6c\",\"problemsErrorIcon.foreground\":\"#e64d49\",\"problemsWarningIcon.foreground\":\"#daaa01\",\"problemsInfoIcon.foreground\":\"#1a85ff\",\"editor.findMatchBackground\":\"#93a1a16c\",\"editor.findMatchHighlightBackground\":\"#93a1a16c\",\"editor.findRangeHighlightBackground\":\"#7497a633\",\"editorLink.activeForeground\":\"#0000ff\",\"editorLightBulb.foreground\":\"#ddb100\",\"editorLightBulbAutoFix.foreground\":\"#007acc\",\"diffEditor.insertedTextBackground\":\"#9ccc2c40\",\"diffEditor.insertedTextBorder\":null,\"diffEditor.removedTextBackground\":\"#ff000033\",\"diffEditor.removedTextBorder\":null,\"diffEditor.insertedLineBackground\":\"#9bb95533\",\"diffEditor.removedLineBackground\":\"#ff000033\",\"editorStickyScroll.background\":\"#fbfbfb\",\"editorStickyScrollHover.background\":\"#f0f0f0\",\"editorInlayHint.background\":\"#2aa29899\",\"editorInlayHint.foreground\":\"#f0f0f0\",\"editorInlayHint.typeBackground\":\"#2aa29899\",\"editorInlayHint.typeForeground\":\"#f0f0f0\",\"editorInlayHint.parameterBackground\":\"#2aa29899\",\"editorInlayHint.parameterForeground\":\"#f0f0f0\",\"editorPane.background\":\"#fbfbfb\",\"editorGroup.emptyBackground\":null,\"editorGroup.focusedEmptyBorder\":null,\"editorGroupHeader.tabsBackground\":\"var(--sl-color-gray-6)\",\"editorGroupHeader.tabsBorder\":\"color-mix(in srgb, var(--sl-color-gray-5), transparent 25%)\",\"editorGroupHeader.noTabsBackground\":\"#f0f0f0\",\"editorGroupHeader.border\":null,\"editorGroup.border\":\"#f0f0f0\",\"editorGroup.dropBackground\":\"#2677cb2d\",\"editorGroup.dropIntoPromptForeground\":\"#403f53\",\"editorGroup.dropIntoPromptBackground\":\"#f0f0f0\",\"editorGroup.dropIntoPromptBorder\":null,\"sideBySideEditor.horizontalBorder\":\"#f0f0f0\",\"sideBySideEditor.verticalBorder\":\"#f0f0f0\",\"scrollbar.shadow\":\"#cccccc\",\"scrollbarSlider.background\":\"#0000001a\",\"scrollbarSlider.hoverBackground\":\"#00000055\",\"scrollbarSlider.activeBackground\":\"#00000099\",\"panel.background\":\"#f0f0f0\",\"panel.border\":\"#d9d9d9\",\"panelTitle.activeBorder\":\"#424242\",\"panelTitle.activeForeground\":\"#424242\",\"panelTitle.inactiveForeground\":\"#424242bf\",\"panelSectionHeader.background\":\"#80808051\",\"terminal.background\":\"#f6f6f6\",\"widget.shadow\":\"#d9d9d9\",\"editorWidget.background\":\"#f0f0f0\",\"editorWidget.foreground\":\"#403f53\",\"editorWidget.border\":\"#d9d9d9\",\"quickInput.background\":\"#f0f0f0\",\"quickInput.foreground\":\"#403f53\",\"quickInputTitle.background\":\"#0000000f\",\"pickerGroup.foreground\":\"#403f53\",\"pickerGroup.border\":\"#d9d9d9\",\"editor.hoverHighlightBackground\":\"#339cec33\",\"editorHoverWidget.background\":\"#f0f0f0\",\"editorHoverWidget.foreground\":\"#403f53\",\"editorHoverWidget.border\":\"#d9d9d9\",\"editorHoverWidget.statusBarBackground\":\"#e4e4e4\",\"titleBar.activeBackground\":\"var(--sl-color-gray-6)\",\"titleBar.activeForeground\":\"var(--sl-color-text)\",\"titleBar.inactiveBackground\":\"#f0f0f099\",\"titleBar.inactiveForeground\":\"#33333399\",\"titleBar.border\":\"color-mix(in srgb, var(--sl-color-gray-5), transparent 25%)\",\"toolbar.hoverBackground\":\"#b8b8b850\",\"toolbar.activeBackground\":\"#a6a6a650\",\"tab.activeBackground\":\"#f6f6f6\",\"tab.unfocusedActiveBackground\":\"#f6f6f6\",\"tab.inactiveBackground\":\"#f0f0f0\",\"tab.unfocusedInactiveBackground\":\"#f0f0f0\",\"tab.activeForeground\":\"var(--sl-color-text)\",\"tab.inactiveForeground\":\"#403f53\",\"tab.unfocusedActiveForeground\":\"#403f53b3\",\"tab.unfocusedInactiveForeground\":\"#403f5380\",\"tab.hoverBackground\":null,\"tab.unfocusedHoverBackground\":null,\"tab.hoverForeground\":null,\"tab.unfocusedHoverForeground\":null,\"tab.border\":\"#f0f0f0\",\"tab.lastPinnedBorder\":\"#a9a9a9\",\"tab.activeBorder\":\"transparent\",\"tab.unfocusedActiveBorder\":null,\"tab.activeBorderTop\":\"var(--sl-color-accent)\",\"tab.unfocusedActiveBorderTop\":null,\"tab.hoverBorder\":null,\"tab.unfocusedHoverBorder\":null,\"tab.activeModifiedBorder\":\"#2aa298\",\"tab.inactiveModifiedBorder\":\"#93a1a1\",\"tab.unfocusedActiveModifiedBorder\":\"#93a1a1\",\"tab.unfocusedInactiveModifiedBorder\":\"#93a1a1\",\"badge.background\":\"#2aa298\",\"badge.foreground\":\"#f0f0f0\",\"button.background\":\"#2aa298\",\"button.foreground\":\"#f0f0f0\",\"button.border\":null,\"button.separator\":\"#f0f0f066\",\"button.hoverBackground\":\"#22827a\",\"button.secondaryBackground\":\"#5f6a79\",\"button.secondaryForeground\":\"#ffffff\",\"button.secondaryHoverBackground\":\"#4c5561\",\"dropdown.background\":\"#f0f0f0\",\"dropdown.foreground\":\"#403f53\",\"dropdown.border\":\"#d9d9d9\",\"list.activeSelectionBackground\":\"#d3e8f8\",\"list.activeSelectionForeground\":\"#403f53\",\"tree.indentGuidesStroke\":\"#a9a9a9\",\"input.background\":\"#f0f0f0\",\"input.foreground\":\"#403f53\",\"input.placeholderForeground\":\"#93a1a1\",\"inputOption.activeBorder\":\"#2aa298\",\"inputOption.hoverBackground\":\"#b8b8b850\",\"inputOption.activeBackground\":\"#93a1a133\",\"inputOption.activeForeground\":\"#000000\",\"inputValidation.infoBackground\":\"#f0f0f0\",\"inputValidation.infoBorder\":\"#d0d0d0\",\"inputValidation.warningBackground\":\"#daaa01\",\"inputValidation.warningBorder\":\"#e0af02\",\"inputValidation.errorBackground\":\"#f76e6e\",\"inputValidation.errorBorder\":\"#de3d3b\",\"keybindingLabel.background\":\"#dddddd66\",\"keybindingLabel.foreground\":\"#555555\",\"keybindingLabel.border\":\"#cccccc66\",\"keybindingLabel.bottomBorder\":\"#bbbbbb66\",\"menu.foreground\":\"#403f53\",\"menu.background\":\"#f0f0f0\",\"menu.selectionForeground\":\"#403f53\",\"menu.selectionBackground\":\"#d3e8f8\",\"menu.separatorBackground\":\"#d4d4d4\",\"editor.snippetTabstopHighlightBackground\":\"#0a326433\",\"editor.snippetFinalTabstopHighlightBorder\":\"#0a326480\",\"terminal.ansiBlack\":\"#403f53\",\"terminal.ansiRed\":\"#de3d3b\",\"terminal.ansiGreen\":\"#08916a\",\"terminal.ansiYellow\":\"#e0af02\",\"terminal.ansiBlue\":\"#288ed7\",\"terminal.ansiMagenta\":\"#d6438a\",\"terminal.ansiCyan\":\"#2aa298\",\"terminal.ansiWhite\":\"#f0f0f0\",\"terminal.ansiBrightBlack\":\"#403f53\",\"terminal.ansiBrightRed\":\"#de3d3b\",\"terminal.ansiBrightGreen\":\"#08916a\",\"terminal.ansiBrightYellow\":\"#daaa01\",\"terminal.ansiBrightBlue\":\"#288ed7\",\"terminal.ansiBrightMagenta\":\"#d6438a\",\"terminal.ansiBrightCyan\":\"#2aa298\",\"terminal.ansiBrightWhite\":\"#f0f0f0\",\"selection.background\":\"#7a8181ad\",\"notifications.background\":\"#f0f0f0\",\"notifications.foreground\":\"#403f53\",\"notificationLink.foreground\":\"#994cc3\",\"notifications.border\":\"#cccccc\",\"notificationCenter.border\":\"#cccccc\",\"notificationToast.border\":\"#cccccc\",\"notificationCenterHeader.foreground\":\"#403f53\",\"notificationCenterHeader.background\":\"#f0f0f0\",\"input.border\":\"#d9d9d9\",\"progressBar.background\":\"#2aa298\",\"list.inactiveSelectionBackground\":\"#e0e7ea\",\"list.inactiveSelectionForeground\":\"#403f53\",\"list.focusBackground\":\"#d3e8f8\",\"list.hoverBackground\":\"#d3e8f8\",\"list.focusForeground\":\"#403f53\",\"list.hoverForeground\":\"#403f53\",\"list.highlightForeground\":\"#403f53\",\"list.errorForeground\":\"#e64d49\",\"list.warningForeground\":\"#daaa01\",\"activityBar.background\":\"#f0f0f0\",\"activityBar.foreground\":\"#403f53\",\"activityBar.dropBackground\":\"#d0d0d0\",\"activityBarBadge.background\":\"#403f53\",\"activityBarBadge.foreground\":\"#f0f0f0\",\"activityBar.border\":\"#f0f0f0\",\"sideBar.background\":\"#f0f0f0\",\"sideBar.foreground\":\"#403f53\",\"sideBarTitle.foreground\":\"#403f53\",\"sideBar.border\":\"#f0f0f0\",\"editorGroup.background\":\"#f6f6f6\",\"editorCursor.foreground\":\"#90a7b2\",\"editor.wordHighlightBackground\":\"#339cec33\",\"editor.wordHighlightStrongBackground\":\"#007dd659\",\"editor.lineHighlightBackground\":\"#f0f0f0\",\"editor.rangeHighlightBackground\":\"#7497a633\",\"editorWhitespace.foreground\":\"#d9d9d9\",\"editorIndentGuide.background\":\"#d9d9d9\",\"editorCodeLens.foreground\":\"#403f53\",\"editorBracketMatch.background\":\"#d3e8f8\",\"editorBracketMatch.border\":\"#2aa298\",\"editorError.border\":\"#fbfbfb\",\"editorWarning.border\":\"#daaa01\",\"editorGutter.addedBackground\":\"#49d0c5\",\"editorGutter.modifiedBackground\":\"#6fbef6\",\"editorGutter.deletedBackground\":\"#f76e6e\",\"editorRuler.foreground\":\"#d9d9d9\",\"editorOverviewRuler.errorForeground\":\"#e64d49\",\"editorOverviewRuler.warningForeground\":\"#daaa01\",\"editorSuggestWidget.background\":\"#f0f0f0\",\"editorSuggestWidget.foreground\":\"#403f53\",\"editorSuggestWidget.highlightForeground\":\"#403f53\",\"editorSuggestWidget.selectedBackground\":\"#d3e8f8\",\"editorSuggestWidget.border\":\"#d9d9d9\",\"debugExceptionWidget.background\":\"#f0f0f0\",\"debugExceptionWidget.border\":\"#d9d9d9\",\"editorMarkerNavigation.background\":\"#d0d0d0\",\"editorMarkerNavigationError.background\":\"#f76e6e\",\"editorMarkerNavigationWarning.background\":\"#daaa01\",\"debugToolBar.background\":\"#f0f0f0\",\"extensionButton.prominentBackground\":\"#2aa298\",\"extensionButton.prominentForeground\":\"#f0f0f0\",\"statusBar.background\":\"#f0f0f0\",\"statusBar.border\":\"#f0f0f0\",\"statusBar.debuggingBackground\":\"#f0f0f0\",\"statusBar.debuggingForeground\":\"#403f53\",\"statusBar.foreground\":\"#403f53\",\"statusBar.noFolderBackground\":\"#f0f0f0\",\"statusBar.noFolderForeground\":\"#403f53\",\"peekView.border\":\"#d9d9d9\",\"peekViewEditor.background\":\"#f6f6f6\",\"peekViewEditorGutter.background\":\"#f6f6f6\",\"peekViewEditor.matchHighlightBackground\":\"#49d0c5\",\"peekViewResult.background\":\"#f0f0f0\",\"peekViewResult.fileForeground\":\"#403f53\",\"peekViewResult.lineForeground\":\"#403f53\",\"peekViewResult.matchHighlightBackground\":\"#49d0c5\",\"peekViewResult.selectionBackground\":\"#e0e7ea\",\"peekViewResult.selectionForeground\":\"#403f53\",\"peekViewTitle.background\":\"#f0f0f0\",\"peekViewTitleLabel.foreground\":\"#403f53\",\"peekViewTitleDescription.foreground\":\"#403f53\",\"terminal.foreground\":\"#403f53\"},\"fg\":\"#403f53\",\"bg\":\"#f6f7f9\",\"semanticHighlighting\":false,\"settings\":[{\"name\":\"Changed\",\"scope\":[\"markup.changed\",\"meta.diff.header.git\",\"meta.diff.header.from-file\",\"meta.diff.header.to-file\"],\"settings\":{\"foreground\":\"#556484\"}},{\"name\":\"Deleted\",\"scope\":[\"markup.deleted.diff\"],\"settings\":{\"foreground\":\"#ae3c3afd\"}},{\"name\":\"Inserted\",\"scope\":[\"markup.inserted.diff\"],\"settings\":{\"foreground\":\"#3b61b0\"}},{\"name\":\"Global settings\",\"settings\":{\"background\":\"#011627\",\"foreground\":\"#403f53\"}},{\"name\":\"Comment\",\"scope\":[\"comment\"],\"settings\":{\"foreground\":\"#5f636f\"}},{\"name\":\"String\",\"scope\":[\"string\"],\"settings\":{\"foreground\":\"#3b61b0\"}},{\"name\":\"String Quoted\",\"scope\":[\"string.quoted\",\"variable.other.readwrite.js\"],\"settings\":{\"foreground\":\"#984e4d\"}},{\"name\":\"Support Constant Math\",\"scope\":[\"support.constant.math\"],\"settings\":{\"foreground\":\"#3b61b0\"}},{\"name\":\"Number\",\"scope\":[\"constant.numeric\",\"constant.character.numeric\"],\"settings\":{\"foreground\":\"#aa0982\",\"fontStyle\":\"\"}},{\"name\":\"Built-in constant\",\"scope\":[\"constant.language\",\"punctuation.definition.constant\",\"variable.other.constant\"],\"settings\":{\"foreground\":\"#3b61b0\"}},{\"name\":\"User-defined constant\",\"scope\":[\"constant.character\",\"constant.other\"],\"settings\":{\"foreground\":\"#3b61b0\"}},{\"name\":\"Constant Character Escape\",\"scope\":[\"constant.character.escape\"],\"settings\":{\"foreground\":\"#aa0982\"}},{\"name\":\"RegExp String\",\"scope\":[\"string.regexp\",\"string.regexp keyword.other\"],\"settings\":{\"foreground\":\"#3a688f\"}},{\"name\":\"Comma in functions\",\"scope\":[\"meta.function punctuation.separator.comma\"],\"settings\":{\"foreground\":\"#4d667b\"}},{\"name\":\"Variable\",\"scope\":[\"variable\"],\"settings\":{\"foreground\":\"#3b61b0\"}},{\"name\":\"Keyword\",\"scope\":[\"punctuation.accessor\",\"keyword\"],\"settings\":{\"foreground\":\"#8844ae\"}},{\"name\":\"Storage\",\"scope\":[\"storage\",\"meta.var.expr\",\"meta.class meta.method.declaration meta.var.expr storage.type.js\",\"storage.type.property.js\",\"storage.type.property.ts\",\"storage.type.property.tsx\"],\"settings\":{\"foreground\":\"#8844ae\"}},{\"name\":\"Storage type\",\"scope\":[\"storage.type\"],\"settings\":{\"foreground\":\"#8844ae\"}},{\"name\":\"Storage type\",\"scope\":[\"storage.type.function.arrow.js\"],\"settings\":{\"fontStyle\":\"\"}},{\"name\":\"Class name\",\"scope\":[\"entity.name.class\",\"meta.class entity.name.type.class\"],\"settings\":{\"foreground\":\"#111111\"}},{\"name\":\"Inherited class\",\"scope\":[\"entity.other.inherited-class\"],\"settings\":{\"foreground\":\"#3b61b0\"}},{\"name\":\"Function name\",\"scope\":[\"entity.name.function\"],\"settings\":{\"foreground\":\"#8844ae\"}},{\"name\":\"Meta Tag\",\"scope\":[\"punctuation.definition.tag\",\"meta.tag\"],\"settings\":{\"foreground\":\"#8844ae\"}},{\"name\":\"HTML Tag names\",\"scope\":[\"entity.name.tag\",\"meta.tag.other.html\",\"meta.tag.other.js\",\"meta.tag.other.tsx\",\"entity.name.tag.tsx\",\"entity.name.tag.js\",\"entity.name.tag\",\"meta.tag.js\",\"meta.tag.tsx\",\"meta.tag.html\"],\"settings\":{\"foreground\":\"#8844ae\"}},{\"name\":\"Tag attribute\",\"scope\":[\"entity.other.attribute-name\"],\"settings\":{\"foreground\":\"#3b61b0\"}},{\"name\":\"Entity Name Tag Custom\",\"scope\":[\"entity.name.tag.custom\"],\"settings\":{\"foreground\":\"#3b61b0\"}},{\"name\":\"Library (function & constant)\",\"scope\":[\"support.function\",\"support.constant\"],\"settings\":{\"foreground\":\"#3b61b0\"}},{\"name\":\"Support Constant Property Value meta\",\"scope\":[\"support.constant.meta.property-value\"],\"settings\":{\"foreground\":\"#096e72\"}},{\"name\":\"Library class/type\",\"scope\":[\"support.type\",\"support.class\"],\"settings\":{\"foreground\":\"#3b61b0\"}},{\"name\":\"Support Variable DOM\",\"scope\":[\"support.variable.dom\"],\"settings\":{\"foreground\":\"#3b61b0\"}},{\"name\":\"Invalid\",\"scope\":[\"invalid\"],\"settings\":{\"foreground\":\"#bb2060\"}},{\"name\":\"Invalid deprecated\",\"scope\":[\"invalid.deprecated\"],\"settings\":{\"foreground\":\"#b23834\"}},{\"name\":\"Keyword Operator\",\"scope\":[\"keyword.operator\"],\"settings\":{\"foreground\":\"#096e72\",\"fontStyle\":\"\"}},{\"name\":\"Keyword Operator Relational\",\"scope\":[\"keyword.operator.relational\"],\"settings\":{\"foreground\":\"#8844ae\"}},{\"name\":\"Keyword Operator Assignment\",\"scope\":[\"keyword.operator.assignment\"],\"settings\":{\"foreground\":\"#8844ae\"}},{\"name\":\"Keyword Operator Arithmetic\",\"scope\":[\"keyword.operator.arithmetic\"],\"settings\":{\"foreground\":\"#8844ae\"}},{\"name\":\"Keyword Operator Bitwise\",\"scope\":[\"keyword.operator.bitwise\"],\"settings\":{\"foreground\":\"#8844ae\"}},{\"name\":\"Keyword Operator Increment\",\"scope\":[\"keyword.operator.increment\"],\"settings\":{\"foreground\":\"#8844ae\"}},{\"name\":\"Keyword Operator Ternary\",\"scope\":[\"keyword.operator.ternary\"],\"settings\":{\"foreground\":\"#8844ae\"}},{\"name\":\"Double-Slashed Comment\",\"scope\":[\"comment.line.double-slash\"],\"settings\":{\"foreground\":\"#5d6376\"}},{\"name\":\"Object\",\"scope\":[\"object\"],\"settings\":{\"foreground\":\"#58656a\"}},{\"name\":\"Null\",\"scope\":[\"constant.language.null\"],\"settings\":{\"foreground\":\"#a24848\"}},{\"name\":\"Meta Brace\",\"scope\":[\"meta.brace\"],\"settings\":{\"foreground\":\"#403f53\"}},{\"name\":\"Meta Delimiter Period\",\"scope\":[\"meta.delimiter.period\"],\"settings\":{\"foreground\":\"#8844ae\"}},{\"name\":\"Punctuation Definition String\",\"scope\":[\"punctuation.definition.string\"],\"settings\":{\"foreground\":\"#111111\"}},{\"name\":\"Punctuation Definition String Markdown\",\"scope\":[\"punctuation.definition.string.begin.markdown\"],\"settings\":{\"foreground\":\"#a24848\"}},{\"name\":\"Boolean\",\"scope\":[\"constant.language.boolean\"],\"settings\":{\"foreground\":\"#a24848\"}},{\"name\":\"Object Comma\",\"scope\":[\"object.comma\"],\"settings\":{\"foreground\":\"#646464\"}},{\"name\":\"Variable Parameter Function\",\"scope\":[\"variable.parameter.function\"],\"settings\":{\"foreground\":\"#096e72\",\"fontStyle\":\"\"}},{\"name\":\"Support Type Property Name & entity name tags\",\"scope\":[\"support.type.vendor.property-name\",\"support.constant.vendor.property-value\",\"support.type.property-name\",\"meta.property-list entity.name.tag\"],\"settings\":{\"foreground\":\"#096e72\",\"fontStyle\":\"\"}},{\"name\":\"Entity Name tag reference in stylesheets\",\"scope\":[\"meta.property-list entity.name.tag.reference\"],\"settings\":{\"foreground\":\"#286d70\"}},{\"name\":\"Constant Other Color RGB Value Punctuation Definition Constant\",\"scope\":[\"constant.other.color.rgb-value punctuation.definition.constant\"],\"settings\":{\"foreground\":\"#aa0982\"}},{\"name\":\"Constant Other Color\",\"scope\":[\"constant.other.color\"],\"settings\":{\"foreground\":\"#aa0982\"}},{\"name\":\"Keyword Other Unit\",\"scope\":[\"keyword.other.unit\"],\"settings\":{\"foreground\":\"#aa0982\"}},{\"name\":\"Meta Selector\",\"scope\":[\"meta.selector\"],\"settings\":{\"foreground\":\"#8844ae\"}},{\"name\":\"Entity Other Attribute Name Id\",\"scope\":[\"entity.other.attribute-name.id\"],\"settings\":{\"foreground\":\"#aa0982\"}},{\"name\":\"Meta Property Name\",\"scope\":[\"meta.property-name\"],\"settings\":{\"foreground\":\"#096e72\"}},{\"name\":\"Doctypes\",\"scope\":[\"entity.name.tag.doctype\",\"meta.tag.sgml.doctype\"],\"settings\":{\"foreground\":\"#8844ae\"}},{\"name\":\"Punctuation Definition Parameters\",\"scope\":[\"punctuation.definition.parameters\"],\"settings\":{\"foreground\":\"#111111\"}},{\"name\":\"Keyword Control Operator\",\"scope\":[\"keyword.control.operator\"],\"settings\":{\"foreground\":\"#096e72\"}},{\"name\":\"Keyword Operator Logical\",\"scope\":[\"keyword.operator.logical\"],\"settings\":{\"foreground\":\"#8844ae\",\"fontStyle\":\"\"}},{\"name\":\"Variable Instances\",\"scope\":[\"variable.instance\",\"variable.other.instance\",\"variable.readwrite.instance\",\"variable.other.readwrite.instance\",\"variable.other.property\"],\"settings\":{\"foreground\":\"#096e72\"}},{\"name\":\"Variable Property Other object property\",\"scope\":[\"variable.other.object.property\"],\"settings\":{\"foreground\":\"#111111\"}},{\"name\":\"Variable Property Other object\",\"scope\":[\"variable.other.object.js\"],\"settings\":{\"fontStyle\":\"\"}},{\"name\":\"Entity Name Function\",\"scope\":[\"entity.name.function\"],\"settings\":{\"foreground\":\"#3b61b0\"}},{\"name\":\"Keyword Operator Comparison, imports, returns and Keyword Operator Ruby\",\"scope\":[\"keyword.operator.comparison\",\"keyword.control.flow.js\",\"keyword.control.flow.ts\",\"keyword.control.flow.tsx\",\"keyword.control.ruby\",\"keyword.control.module.ruby\",\"keyword.control.class.ruby\",\"keyword.control.def.ruby\",\"keyword.control.loop.js\",\"keyword.control.loop.ts\",\"keyword.control.import.js\",\"keyword.control.import.ts\",\"keyword.control.import.tsx\",\"keyword.control.from.js\",\"keyword.control.from.ts\",\"keyword.control.from.tsx\",\"keyword.operator.instanceof.js\",\"keyword.operator.expression.instanceof.ts\",\"keyword.operator.expression.instanceof.tsx\"],\"settings\":{\"foreground\":\"#8844ae\"}},{\"name\":\"Keyword Control Conditional\",\"scope\":[\"keyword.control.conditional.js\",\"keyword.control.conditional.ts\",\"keyword.control.switch.js\",\"keyword.control.switch.ts\"],\"settings\":{\"foreground\":\"#8844ae\",\"fontStyle\":\"\"}},{\"name\":\"Support Constant, `new` keyword, Special Method Keyword, `debugger`, other keywords\",\"scope\":[\"support.constant\",\"keyword.other.special-method\",\"keyword.other.new\",\"keyword.other.debugger\",\"keyword.control\"],\"settings\":{\"foreground\":\"#096e72\"}},{\"name\":\"Support Function\",\"scope\":[\"support.function\"],\"settings\":{\"foreground\":\"#3b61b0\"}},{\"name\":\"Invalid Broken\",\"scope\":[\"invalid.broken\"],\"settings\":{\"foreground\":\"#aa0982\"}},{\"name\":\"Invalid Unimplemented\",\"scope\":[\"invalid.unimplemented\"],\"settings\":{\"foreground\":\"#486e26\"}},{\"name\":\"Invalid Illegal\",\"scope\":[\"invalid.illegal\"],\"settings\":{\"foreground\":\"#984e4d\"}},{\"name\":\"Language Variable\",\"scope\":[\"variable.language\"],\"settings\":{\"foreground\":\"#096e72\"}},{\"name\":\"Support Variable Property\",\"scope\":[\"support.variable.property\"],\"settings\":{\"foreground\":\"#096e72\"}},{\"name\":\"Variable Function\",\"scope\":[\"variable.function\"],\"settings\":{\"foreground\":\"#3b61b0\"}},{\"name\":\"Variable Interpolation\",\"scope\":[\"variable.interpolation\"],\"settings\":{\"foreground\":\"#a64348\"}},{\"name\":\"Meta Function Call\",\"scope\":[\"meta.function-call\"],\"settings\":{\"foreground\":\"#3b61b0\"}},{\"name\":\"Punctuation Section Embedded\",\"scope\":[\"punctuation.section.embedded\"],\"settings\":{\"foreground\":\"#b23834\"}},{\"name\":\"Punctuation Tweaks\",\"scope\":[\"punctuation.terminator.expression\",\"punctuation.definition.arguments\",\"punctuation.definition.array\",\"punctuation.section.array\",\"meta.array\"],\"settings\":{\"foreground\":\"#403f53\"}},{\"name\":\"More Punctuation Tweaks\",\"scope\":[\"punctuation.definition.list.begin\",\"punctuation.definition.list.end\",\"punctuation.separator.arguments\",\"punctuation.definition.list\"],\"settings\":{\"foreground\":\"#111111\"}},{\"name\":\"Template Strings\",\"scope\":[\"string.template meta.template.expression\"],\"settings\":{\"foreground\":\"#b23834\"}},{\"name\":\"Backtics(``) in Template Strings\",\"scope\":[\"string.template punctuation.definition.string\"],\"settings\":{\"foreground\":\"#403f53\"}},{\"name\":\"Italics\",\"scope\":[\"italic\"],\"settings\":{\"foreground\":\"#8844ae\",\"fontStyle\":\"italic\"}},{\"name\":\"Bold\",\"scope\":[\"bold\"],\"settings\":{\"foreground\":\"#3b61b0\",\"fontStyle\":\"bold\"}},{\"name\":\"Quote\",\"scope\":[\"quote\"],\"settings\":{\"foreground\":\"#5c6285\"}},{\"name\":\"Raw Code\",\"scope\":[\"raw\"],\"settings\":{\"foreground\":\"#096e72\"}},{\"name\":\"CoffeScript Variable Assignment\",\"scope\":[\"variable.assignment.coffee\"],\"settings\":{\"foreground\":\"#186e73\"}},{\"name\":\"CoffeScript Parameter Function\",\"scope\":[\"variable.parameter.function.coffee\"],\"settings\":{\"foreground\":\"#403f53\"}},{\"name\":\"CoffeeScript Assignments\",\"scope\":[\"variable.assignment.coffee\"],\"settings\":{\"foreground\":\"#096e72\"}},{\"name\":\"C# Readwrite Variables\",\"scope\":[\"variable.other.readwrite.cs\"],\"settings\":{\"foreground\":\"#403f53\"}},{\"name\":\"C# Classes & Storage types\",\"scope\":[\"entity.name.type.class.cs\",\"storage.type.cs\"],\"settings\":{\"foreground\":\"#3b61b0\"}},{\"name\":\"C# Namespaces\",\"scope\":[\"entity.name.type.namespace.cs\"],\"settings\":{\"foreground\":\"#096e72\"}},{\"name\":\"Tag names in Stylesheets\",\"scope\":[\"entity.name.tag.css\",\"entity.name.tag.less\",\"entity.name.tag.custom.css\",\"support.constant.property-value.css\"],\"settings\":{\"foreground\":\"#984e4d\",\"fontStyle\":\"\"}},{\"name\":\"Wildcard(*) selector in Stylesheets\",\"scope\":[\"entity.name.tag.wildcard.css\",\"entity.name.tag.wildcard.less\",\"entity.name.tag.wildcard.scss\",\"entity.name.tag.wildcard.sass\"],\"settings\":{\"foreground\":\"#096e72\"}},{\"name\":\"CSS Keyword Other Unit\",\"scope\":[\"keyword.other.unit.css\"],\"settings\":{\"foreground\":\"#3b61b0\"}},{\"name\":\"Attribute Name for CSS\",\"scope\":[\"meta.attribute-selector.css entity.other.attribute-name.attribute\",\"variable.other.readwrite.js\"],\"settings\":{\"foreground\":\"#aa0982\"}},{\"name\":\"Elixir Classes\",\"scope\":[\"source.elixir support.type.elixir\",\"source.elixir meta.module.elixir entity.name.class.elixir\"],\"settings\":{\"foreground\":\"#3b61b0\"}},{\"name\":\"Elixir Functions\",\"scope\":[\"source.elixir entity.name.function\"],\"settings\":{\"foreground\":\"#3b61b0\"}},{\"name\":\"Elixir Constants\",\"scope\":[\"source.elixir constant.other.symbol.elixir\",\"source.elixir constant.other.keywords.elixir\"],\"settings\":{\"foreground\":\"#3b61b0\"}},{\"name\":\"Elixir String Punctuations\",\"scope\":[\"source.elixir punctuation.definition.string\"],\"settings\":{\"foreground\":\"#3b61b0\"}},{\"name\":\"Elixir\",\"scope\":[\"source.elixir variable.other.readwrite.module.elixir\",\"source.elixir variable.other.readwrite.module.elixir punctuation.definition.variable.elixir\"],\"settings\":{\"foreground\":\"#3b61b0\"}},{\"name\":\"Elixir Binary Punctuations\",\"scope\":[\"source.elixir .punctuation.binary.elixir\"],\"settings\":{\"foreground\":\"#8844ae\"}},{\"name\":\"Closure Constant Keyword\",\"scope\":[\"constant.keyword.clojure\"],\"settings\":{\"foreground\":\"#096e72\"}},{\"name\":\"Go Function Calls\",\"scope\":[\"source.go meta.function-call.go\"],\"settings\":{\"foreground\":\"#096e72\"}},{\"name\":\"Go Keywords\",\"scope\":[\"source.go keyword.package.go\",\"source.go keyword.import.go\",\"source.go keyword.function.go\",\"source.go keyword.type.go\",\"source.go keyword.struct.go\",\"source.go keyword.interface.go\",\"source.go keyword.const.go\",\"source.go keyword.var.go\",\"source.go keyword.map.go\",\"source.go keyword.channel.go\",\"source.go keyword.control.go\"],\"settings\":{\"foreground\":\"#8844ae\"}},{\"name\":\"Go Constants e.g. nil, string format (%s, %d, etc.)\",\"scope\":[\"source.go constant.language.go\",\"source.go constant.other.placeholder.go\"],\"settings\":{\"foreground\":\"#a24848\"}},{\"name\":\"C++ Functions\",\"scope\":[\"entity.name.function.preprocessor.cpp\",\"entity.scope.name.cpp\"],\"settings\":{\"foreground\":\"#096e72\"}},{\"name\":\"C++ Meta Namespace\",\"scope\":[\"meta.namespace-block.cpp\"],\"settings\":{\"foreground\":\"#111111\"}},{\"name\":\"C++ Language Primitive Storage\",\"scope\":[\"storage.type.language.primitive.cpp\"],\"settings\":{\"foreground\":\"#a24848\"}},{\"name\":\"C++ Preprocessor Macro\",\"scope\":[\"meta.preprocessor.macro.cpp\"],\"settings\":{\"foreground\":\"#403f53\"}},{\"name\":\"C++ Variable Parameter\",\"scope\":[\"variable.parameter\"],\"settings\":{\"foreground\":\"#111111\"}},{\"name\":\"Powershell Variables\",\"scope\":[\"variable.other.readwrite.powershell\"],\"settings\":{\"foreground\":\"#3b61b0\"}},{\"name\":\"Powershell Function\",\"scope\":[\"support.function.powershell\"],\"settings\":{\"foreground\":\"#096e72\"}},{\"name\":\"ID Attribute Name in HTML\",\"scope\":[\"entity.other.attribute-name.id.html\"],\"settings\":{\"foreground\":\"#3b61b0\"}},{\"name\":\"HTML Punctuation Definition Tag\",\"scope\":[\"punctuation.definition.tag.html\"],\"settings\":{\"foreground\":\"#8844ae\"}},{\"name\":\"HTML Doctype\",\"scope\":[\"meta.tag.sgml.doctype.html\"],\"settings\":{\"foreground\":\"#8844ae\"}},{\"name\":\"JavaScript Classes\",\"scope\":[\"meta.class entity.name.type.class.js\"],\"settings\":{\"foreground\":\"#111111\"}},{\"name\":\"JavaScript Method Declaration e.g. `constructor`\",\"scope\":[\"meta.method.declaration storage.type.js\"],\"settings\":{\"foreground\":\"#3b61b0\"}},{\"name\":\"JavaScript Terminator\",\"scope\":[\"terminator.js\"],\"settings\":{\"foreground\":\"#403f53\"}},{\"name\":\"JavaScript Meta Punctuation Definition\",\"scope\":[\"meta.js punctuation.definition.js\"],\"settings\":{\"foreground\":\"#403f53\"}},{\"name\":\"Entity Names in Code Documentations\",\"scope\":[\"entity.name.type.instance.jsdoc\",\"entity.name.type.instance.phpdoc\"],\"settings\":{\"foreground\":\"#4d667b\"}},{\"name\":\"Other Variables in Code Documentations\",\"scope\":[\"variable.other.jsdoc\",\"variable.other.phpdoc\"],\"settings\":{\"foreground\":\"#3e697c\"}},{\"name\":\"JavaScript module imports and exports\",\"scope\":[\"variable.other.meta.import.js\",\"meta.import.js variable.other\",\"variable.other.meta.export.js\",\"meta.export.js variable.other\"],\"settings\":{\"foreground\":\"#403f53\"}},{\"name\":\"JavaScript Variable Parameter Function\",\"scope\":[\"variable.parameter.function.js\"],\"settings\":{\"foreground\":\"#555ea2\"}},{\"name\":\"JavaScript[React] Variable Other Object\",\"scope\":[\"variable.other.object.js\",\"variable.other.object.jsx\",\"variable.object.property.js\",\"variable.object.property.jsx\"],\"settings\":{\"foreground\":\"#403f53\"}},{\"name\":\"JavaScript Variables\",\"scope\":[\"variable.js\",\"variable.other.js\"],\"settings\":{\"foreground\":\"#403f53\"}},{\"name\":\"JavaScript Entity Name Type\",\"scope\":[\"entity.name.type.js\",\"entity.name.type.module.js\"],\"settings\":{\"foreground\":\"#111111\",\"fontStyle\":\"\"}},{\"name\":\"JavaScript Support Classes\",\"scope\":[\"support.class.js\"],\"settings\":{\"foreground\":\"#403f53\"}},{\"name\":\"JSON Property Names\",\"scope\":[\"support.type.property-name.json\"],\"settings\":{\"foreground\":\"#096e72\"}},{\"name\":\"JSON Support Constants\",\"scope\":[\"support.constant.json\"],\"settings\":{\"foreground\":\"#3b61b0\"}},{\"name\":\"JSON Property values (string)\",\"scope\":[\"meta.structure.dictionary.value.json string.quoted.double\"],\"settings\":{\"foreground\":\"#7c5686\"}},{\"name\":\"Strings in JSON values\",\"scope\":[\"string.quoted.double.json punctuation.definition.string.json\"],\"settings\":{\"foreground\":\"#096e72\"}},{\"name\":\"Specific JSON Property values like null\",\"scope\":[\"meta.structure.dictionary.json meta.structure.dictionary.value constant.language\"],\"settings\":{\"foreground\":\"#a24848\"}},{\"name\":\"JavaScript Other Variable\",\"scope\":[\"variable.other.object.js\"],\"settings\":{\"foreground\":\"#096e72\"}},{\"name\":\"Ruby Variables\",\"scope\":[\"variable.other.ruby\"],\"settings\":{\"foreground\":\"#403f53\"}},{\"name\":\"Ruby Class\",\"scope\":[\"entity.name.type.class.ruby\"],\"settings\":{\"foreground\":\"#984e4d\"}},{\"name\":\"Ruby Hashkeys\",\"scope\":[\"constant.language.symbol.hashkey.ruby\"],\"settings\":{\"foreground\":\"#096e72\"}},{\"name\":\"Ruby Symbols\",\"scope\":[\"constant.language.symbol.ruby\"],\"settings\":{\"foreground\":\"#096e72\"}},{\"name\":\"LESS Tag names\",\"scope\":[\"entity.name.tag.less\"],\"settings\":{\"foreground\":\"#8844ae\"}},{\"name\":\"LESS Keyword Other Unit\",\"scope\":[\"keyword.other.unit.css\"],\"settings\":{\"foreground\":\"#096e72\"}},{\"name\":\"Attribute Name for LESS\",\"scope\":[\"meta.attribute-selector.less entity.other.attribute-name.attribute\"],\"settings\":{\"foreground\":\"#aa0982\"}},{\"name\":\"Markdown Headings\",\"scope\":[\"markup.heading.markdown\",\"markup.heading.setext.1.markdown\",\"markup.heading.setext.2.markdown\"],\"settings\":{\"foreground\":\"#3b61b0\"}},{\"name\":\"Markdown Italics\",\"scope\":[\"markup.italic.markdown\"],\"settings\":{\"foreground\":\"#8844ae\",\"fontStyle\":\"italic\"}},{\"name\":\"Markdown Bold\",\"scope\":[\"markup.bold.markdown\"],\"settings\":{\"foreground\":\"#3b61b0\",\"fontStyle\":\"bold\"}},{\"name\":\"Markdown Quote + others\",\"scope\":[\"markup.quote.markdown\"],\"settings\":{\"foreground\":\"#5c6285\"}},{\"name\":\"Markdown Raw Code + others\",\"scope\":[\"markup.inline.raw.markdown\"],\"settings\":{\"foreground\":\"#096e72\"}},{\"name\":\"Markdown Links\",\"scope\":[\"markup.underline.link.markdown\",\"markup.underline.link.image.markdown\"],\"settings\":{\"foreground\":\"#954f5a\",\"fontStyle\":\"underline\"}},{\"name\":\"Markdown Link Title and Description\",\"scope\":[\"string.other.link.title.markdown\",\"string.other.link.description.markdown\"],\"settings\":{\"foreground\":\"#403f53\",\"fontStyle\":\"underline\"}},{\"name\":\"Markdown Punctuation\",\"scope\":[\"punctuation.definition.string.markdown\",\"punctuation.definition.string.begin.markdown\",\"punctuation.definition.string.end.markdown\",\"meta.link.inline.markdown punctuation.definition.string\"],\"settings\":{\"foreground\":\"#3b61b0\"}},{\"name\":\"Markdown MetaData Punctuation\",\"scope\":[\"punctuation.definition.metadata.markdown\"],\"settings\":{\"foreground\":\"#096e72\"}},{\"name\":\"Markdown List Punctuation\",\"scope\":[\"beginning.punctuation.definition.list.markdown\"],\"settings\":{\"foreground\":\"#3b61b0\"}},{\"name\":\"Markdown Inline Raw String\",\"scope\":[\"markup.inline.raw.string.markdown\"],\"settings\":{\"foreground\":\"#3b61b0\"}},{\"name\":\"PHP Variables\",\"scope\":[\"variable.other.php\",\"variable.other.property.php\"],\"settings\":{\"foreground\":\"#111111\"}},{\"name\":\"Support Classes in PHP\",\"scope\":[\"support.class.php\"],\"settings\":{\"foreground\":\"#111111\"}},{\"name\":\"Punctuations in PHP function calls\",\"scope\":[\"meta.function-call.php punctuation\"],\"settings\":{\"foreground\":\"#403f53\"}},{\"name\":\"PHP Global Variables\",\"scope\":[\"variable.other.global.php\"],\"settings\":{\"foreground\":\"#3b61b0\"}},{\"name\":\"Declaration Punctuation in PHP Global Variables\",\"scope\":[\"variable.other.global.php punctuation.definition.variable\"],\"settings\":{\"foreground\":\"#3b61b0\"}},{\"name\":\"Language Constants in Python\",\"scope\":[\"constant.language.python\"],\"settings\":{\"foreground\":\"#a24848\"}},{\"name\":\"Python Function Parameter and Arguments\",\"scope\":[\"variable.parameter.function.python\",\"meta.function-call.arguments.python\"],\"settings\":{\"foreground\":\"#3b61b0\"}},{\"name\":\"Python Function Call\",\"scope\":[\"meta.function-call.python\",\"meta.function-call.generic.python\"],\"settings\":{\"foreground\":\"#096e72\"}},{\"name\":\"Punctuations in Python\",\"scope\":[\"punctuation.python\"],\"settings\":{\"foreground\":\"#403f53\"}},{\"name\":\"Decorator Functions in Python\",\"scope\":[\"entity.name.function.decorator.python\"],\"settings\":{\"foreground\":\"#3b61b0\"}},{\"name\":\"Python Language Variable\",\"scope\":[\"source.python variable.language.special\"],\"settings\":{\"foreground\":\"#aa0982\"}},{\"name\":\"Python import control keyword\",\"scope\":[\"keyword.control\"],\"settings\":{\"foreground\":\"#8844ae\"}},{\"name\":\"SCSS Variable\",\"scope\":[\"variable.scss\",\"variable.sass\",\"variable.parameter.url.scss\",\"variable.parameter.url.sass\"],\"settings\":{\"foreground\":\"#3b61b0\"}},{\"name\":\"Variables in SASS At-Rules\",\"scope\":[\"source.css.scss meta.at-rule variable\",\"source.css.sass meta.at-rule variable\"],\"settings\":{\"foreground\":\"#3b61b0\"}},{\"name\":\"Variables in SASS At-Rules\",\"scope\":[\"source.css.scss meta.at-rule variable\",\"source.css.sass meta.at-rule variable\"],\"settings\":{\"foreground\":\"#111111\"}},{\"name\":\"Attribute Name for SASS\",\"scope\":[\"meta.attribute-selector.scss entity.other.attribute-name.attribute\",\"meta.attribute-selector.sass entity.other.attribute-name.attribute\"],\"settings\":{\"foreground\":\"#aa0982\"}},{\"name\":\"Tag names in SASS\",\"scope\":[\"entity.name.tag.scss\",\"entity.name.tag.sass\"],\"settings\":{\"foreground\":\"#096e72\"}},{\"name\":\"SASS Keyword Other Unit\",\"scope\":[\"keyword.other.unit.scss\",\"keyword.other.unit.sass\"],\"settings\":{\"foreground\":\"#8844ae\"}},{\"name\":\"TypeScript[React] Variables and Object Properties\",\"scope\":[\"variable.other.readwrite.alias.ts\",\"variable.other.readwrite.alias.tsx\",\"variable.other.readwrite.ts\",\"variable.other.readwrite.tsx\",\"variable.other.object.ts\",\"variable.other.object.tsx\",\"variable.object.property.ts\",\"variable.object.property.tsx\",\"variable.other.ts\",\"variable.other.tsx\",\"variable.tsx\",\"variable.ts\"],\"settings\":{\"foreground\":\"#403f53\"}},{\"name\":\"TypeScript[React] Entity Name Types\",\"scope\":[\"entity.name.type.ts\",\"entity.name.type.tsx\"],\"settings\":{\"foreground\":\"#111111\"}},{\"name\":\"TypeScript[React] Node Classes\",\"scope\":[\"support.class.node.ts\",\"support.class.node.tsx\"],\"settings\":{\"foreground\":\"#3b61b0\"}},{\"name\":\"TypeScript[React] Entity Name Types as Parameters\",\"scope\":[\"meta.type.parameters.ts entity.name.type\",\"meta.type.parameters.tsx entity.name.type\"],\"settings\":{\"foreground\":\"#4d667b\"}},{\"name\":\"TypeScript[React] Import/Export Punctuations\",\"scope\":[\"meta.import.ts punctuation.definition.block\",\"meta.import.tsx punctuation.definition.block\",\"meta.export.ts punctuation.definition.block\",\"meta.export.tsx punctuation.definition.block\"],\"settings\":{\"foreground\":\"#403f53\"}},{\"name\":\"TypeScript[React] Punctuation Decorators\",\"scope\":[\"meta.decorator punctuation.decorator.ts\",\"meta.decorator punctuation.decorator.tsx\"],\"settings\":{\"foreground\":\"#3b61b0\"}},{\"name\":\"TypeScript[React] Punctuation Decorators\",\"scope\":[\"meta.tag.js meta.jsx.children.tsx\"],\"settings\":{\"foreground\":\"#3b61b0\"}},{\"name\":\"YAML Entity Name Tags\",\"scope\":[\"entity.name.tag.yaml\"],\"settings\":{\"foreground\":\"#111111\"}},{\"name\":\"JavaScript Variable Other ReadWrite\",\"scope\":[\"variable.other.readwrite.js\",\"variable.parameter\"],\"settings\":{\"foreground\":\"#403f53\"}},{\"name\":\"Support Class Component\",\"scope\":[\"support.class.component.js\",\"support.class.component.tsx\"],\"settings\":{\"foreground\":\"#aa0982\",\"fontStyle\":\"\"}},{\"name\":\"Text nested in React tags\",\"scope\":[\"meta.jsx.children\",\"meta.jsx.children.js\",\"meta.jsx.children.tsx\"],\"settings\":{\"foreground\":\"#403f53\"}},{\"name\":\"TypeScript Classes\",\"scope\":[\"meta.class entity.name.type.class.tsx\"],\"settings\":{\"foreground\":\"#111111\"}},{\"name\":\"TypeScript Entity Name Type\",\"scope\":[\"entity.name.type.tsx\",\"entity.name.type.module.tsx\"],\"settings\":{\"foreground\":\"#111111\"}},{\"name\":\"TypeScript Class Variable Keyword\",\"scope\":[\"meta.class.ts meta.var.expr.ts storage.type.ts\",\"meta.class.tsx meta.var.expr.tsx storage.type.tsx\"],\"settings\":{\"foreground\":\"#76578b\"}},{\"name\":\"TypeScript Method Declaration e.g. `constructor`\",\"scope\":[\"meta.method.declaration storage.type.ts\",\"meta.method.declaration storage.type.tsx\"],\"settings\":{\"foreground\":\"#3b61b0\"}},{\"name\":\"normalize font style of certain components\",\"scope\":[\"meta.property-list.css meta.property-value.css variable.other.less\",\"meta.property-list.scss variable.scss\",\"meta.property-list.sass variable.sass\",\"meta.brace\",\"keyword.operator.operator\",\"keyword.operator.or.regexp\",\"keyword.operator.expression.in\",\"keyword.operator.relational\",\"keyword.operator.assignment\",\"keyword.operator.comparison\",\"keyword.operator.type\",\"keyword.operator\",\"keyword\",\"punctuation.definintion.string\",\"punctuation\",\"variable.other.readwrite.js\",\"storage.type\",\"source.css\",\"string.quoted\"],\"settings\":{\"fontStyle\":\"\"}}],\"styleOverrides\":{\"frames\":{\"editorBackground\":\"var(--sl-color-gray-7)\",\"terminalBackground\":\"var(--sl-color-gray-7)\",\"editorActiveTabBackground\":\"var(--sl-color-gray-7)\",\"terminalTitlebarDotsForeground\":\"color-mix(in srgb, var(--sl-color-gray-5), transparent 25%)\",\"terminalTitlebarDotsOpacity\":\"0.75\",\"inlineButtonForeground\":\"var(--sl-color-text)\",\"frameBoxShadowCssValue\":\"none\"},\"textMarkers\":{\"markBackground\":\"#0000001a\",\"markBorderColor\":\"#00000055\"}}}],\"defaultLocale\":\"en\",\"styleOverrides\":{\"borderRadius\":\"0px\",\"borderWidth\":\"1px\",\"codePaddingBlock\":\"0.75rem\",\"codePaddingInline\":\"1rem\",\"codeFontFamily\":\"var(--__sl-font-mono)\",\"codeFontSize\":\"var(--sl-text-code)\",\"codeLineHeight\":\"var(--sl-line-height)\",\"uiFontFamily\":\"var(--__sl-font)\",\"textMarkers\":{\"lineDiffIndicatorMarginLeft\":\"0.25rem\",\"defaultChroma\":\"45\",\"backgroundOpacity\":\"60%\"}},\"plugins\":[{\"name\":\"Starlight Plugin\",\"hooks\":{}},{\"name\":\"astro-expressive-code\",\"hooks\":{}}]}]],\"remarkRehype\":{},\"gfm\":true,\"smartypants\":true},\"security\":{\"checkOrigin\":true,\"allowedDomains\":[],\"actionBodySizeLimit\":1048576},\"env\":{\"schema\":{},\"validateSecrets\":false},\"experimental\":{\"clientPrerender\":false,\"contentIntellisense\":false,\"headingIdCompat\":false,\"preserveScriptOrder\":false,\"liveContentCollections\":false,\"csp\":false,\"staticImportMetaEnv\":false,\"chromeDevtoolsWorkspace\":false,\"failOnPrerenderConflict\":false,\"svgo\":false},\"legacy\":{\"collections\":false},\"prefetch\":{\"prefetchAll\":true},\"i18n\":{\"defaultLocale\":\"en\",\"locales\":[\"en\"],\"routing\":{\"prefixDefaultLocale\":false,\"redirectToDefaultLocale\":false,\"fallbackType\":\"redirect\"}}}","docs",["Map",11,12,29,30,42,43,72,73,84,85,95,96,107,108,119,120,131,132,143,144,155,156,167,168,179,180,191,192,203,204,215,216,227,228,239,240,251,252,263,264,275,276,287,288,299,300,311,312,323,324,335,336,347,348,359,360],"demo",{"id":11,"data":13,"body":25,"filePath":26,"digest":27,"legacyId":28,"deferredRender":16},{"title":14,"description":15,"editUrl":16,"head":17,"template":18,"hero":19,"sidebar":22,"pagefind":16,"draft":23},"Demo","See pmcp in action — seven demos showing tool calls, progress, dynamic tool lists, hot reload, tool groups, workflows, and middleware.",true,[],"splash",{"tagline":20,"actions":21},"Watch an AI agent use tools through pmcp — from chat to wire protocol.",[],{"hidden":23,"attrs":24},false,{},"import ArchitectureHero from '../../components/demo/ArchitectureHero.astro';\nimport DemoSection from '../../components/demo/DemoSection.astro';\n\n\u003CArchitectureHero />\n\n## Tool Calls\n\nThe core flow — AI calls a tool, pmcp translates JSON-RPC to protobuf, your tool process handles it, result flows back.\n\n\u003CDemoSection\n title=\"Tool Call Flow\"\n demoId=\"demo1\"\n timeline={[[1, 600], [2, 800], [3, 400], [4, 350], [5, 300], [6, 700], [7, 300], [8, 200], [9, 700]]}\n sideEffects={[{ step: 8, action: \"complete-tool\", target: \"d1-tc-weather\" }]}\n>\n \u003CFragment slot=\"chat\">\n \u003Cdiv class=\"chat-item user-msg\" data-step=\"1\">\n \u003Cdiv class=\"user-bubble\">What's the weather in San Francisco?\u003C/div>\n \u003C/div>\n \u003Cdiv class=\"chat-item ai-msg\" data-step=\"2\">\n \u003Cdiv class=\"ai-bubble\">Let me check that for you.\u003C/div>\n \u003C/div>\n \u003Cdiv class=\"chat-item tool-card\" data-step=\"3\">\n \u003Cdiv class=\"tc-bar tc-running\" id=\"d1-tc-weather-bar\">\u003C/div>\n \u003Cdiv class=\"tc-body\">\n \u003Cdiv class=\"tc-head\">\n \u003Cspan class=\"tc-icon\">⚡\u003C/span>\n \u003Cspan class=\"tc-name\">weather\u003C/span>\n \u003Cspan class=\"tc-status\" id=\"d1-tc-weather-status\">running\u003C/span>\n \u003C/div>\n \u003Cdiv class=\"tc-args\">{\"{\"} city: \"San Francisco\" {\"}\"}\u003C/div>\n \u003Cdiv class=\"tc-result\" data-step=\"8\">\n \u003Cpre class=\"tc-json\">{\"{\"} \"temp\": 62, \"condition\": \"foggy\", \"wind\": \"12mph\" {\"}\"}\u003C/pre>\n \u003C/div>\n \u003C/div>\n \u003C/div>\n \u003Cdiv class=\"chat-item ai-msg\" data-step=\"9\">\n \u003Cdiv class=\"ai-bubble\">It's \u003Cstrong>62°F and foggy\u003C/strong> in San Francisco with 12mph winds.\u003C/div>\n \u003C/div>\n \u003C/Fragment>\n \u003CFragment slot=\"flow\">\n \u003Cdiv class=\"flow-item fi-out\" data-step=\"4\">\n \u003Cspan class=\"fi-arrow\">→\u003C/span>\n \u003Cspan class=\"fi-proto fi-jsonrpc\">JSON-RPC\u003C/span>\n \u003Cspan class=\"fi-hop\">Host→pmcp\u003C/span>\n \u003Cspan class=\"fi-detail\">tools/call \"weather\"\u003C/span>\n \u003C/div>\n \u003Cdiv class=\"flow-item fi-out\" data-step=\"5\">\n \u003Cspan class=\"fi-arrow\">→\u003C/span>\n \u003Cspan class=\"fi-proto fi-protobuf\">protobuf\u003C/span>\n \u003Cspan class=\"fi-hop\">pmcp→App\u003C/span>\n \u003Cspan class=\"fi-detail\">CallToolRequest\u003C/span>\n \u003C/div>\n \u003Cdiv class=\"flow-item fi-in\" data-step=\"6\">\n \u003Cspan class=\"fi-arrow\">←\u003C/span>\n \u003Cspan class=\"fi-proto fi-protobuf\">protobuf\u003C/span>\n \u003Cspan class=\"fi-hop\">App→pmcp\u003C/span>\n \u003Cspan class=\"fi-detail\">CallToolResult\u003C/span>\n \u003C/div>\n \u003Cdiv class=\"flow-item fi-in\" data-step=\"7\">\n \u003Cspan class=\"fi-arrow\">←\u003C/span>\n \u003Cspan class=\"fi-proto fi-jsonrpc\">JSON-RPC\u003C/span>\n \u003Cspan class=\"fi-hop\">pmcp→Host\u003C/span>\n \u003Cspan class=\"fi-detail\">result: {\"{\"}\"temp\": 62, ...{\"}\"}\u003C/span>\n \u003C/div>\n \u003C/Fragment>\n\u003C/DemoSection>\n\n## Progress Reporting\n\nLong-running tools report progress in real time. Notifications flow back through pmcp to the host — no polling needed.\n\n\u003CDemoSection\n title=\"Progress Reporting\"\n demoId=\"demo2\"\n timeline={[[1, 600], [2, 800], [3, 400], [4, 350], [5, 300], [6, 700], [7, 600], [8, 500], [9, 350], [10, 250], [11, 700]]}\n sideEffects={[\n { step: 6, action: \"set-progress\", target: \"d2-search-progress\", value: \"33%\" },\n { step: 7, action: \"set-progress\", target: \"d2-search-progress\", value: \"66%\" },\n { step: 8, action: \"set-progress\", target: \"d2-search-progress\", value: \"100%\" },\n { step: 10, action: \"complete-tool\", target: \"d2-tc-search\" }\n ]}\n>\n \u003CFragment slot=\"chat\">\n \u003Cdiv class=\"chat-item user-msg\" data-step=\"1\">\n \u003Cdiv class=\"user-bubble\">Search src/ for all Python files\u003C/div>\n \u003C/div>\n \u003Cdiv class=\"chat-item ai-msg\" data-step=\"2\">\n \u003Cdiv class=\"ai-bubble\">Scanning the directory tree now.\u003C/div>\n \u003C/div>\n \u003Cdiv class=\"chat-item tool-card\" data-step=\"3\">\n \u003Cdiv class=\"tc-bar tc-running\" id=\"d2-tc-search-bar\">\u003C/div>\n \u003Cdiv class=\"tc-body\">\n \u003Cdiv class=\"tc-head\">\n \u003Cspan class=\"tc-icon\">⚡\u003C/span>\n \u003Cspan class=\"tc-name\">search_files\u003C/span>\n \u003Cspan class=\"tc-status\" id=\"d2-tc-search-status\">running\u003C/span>\n \u003C/div>\n \u003Cdiv class=\"tc-args\">{\"{\"} dir: \"src\", pattern: \"*.py\" {\"}\"}\u003C/div>\n \u003Cdiv class=\"tc-progress\">\n \u003Cdiv class=\"tc-progress-fill\" id=\"d2-search-progress-fill\">\u003C/div>\n \u003C/div>\n \u003Cdiv class=\"tc-result\" data-step=\"10\">\n \u003Cpre class=\"tc-json\">found 47 files across 12 directories\u003C/pre>\n \u003C/div>\n \u003C/div>\n \u003C/div>\n \u003Cdiv class=\"chat-item ai-msg\" data-step=\"11\">\n \u003Cdiv class=\"ai-bubble\">Found \u003Cstrong>47 Python files\u003C/strong> across 12 directories in src/.\u003C/div>\n \u003C/div>\n \u003C/Fragment>\n \u003CFragment slot=\"flow\">\n \u003Cdiv class=\"flow-item fi-out\" data-step=\"4\">\n \u003Cspan class=\"fi-arrow\">→\u003C/span>\n \u003Cspan class=\"fi-proto fi-jsonrpc\">JSON-RPC\u003C/span>\n \u003Cspan class=\"fi-hop\">Host→pmcp\u003C/span>\n \u003Cspan class=\"fi-detail\">tools/call \"search_files\"\u003C/span>\n \u003C/div>\n \u003Cdiv class=\"flow-item fi-out\" data-step=\"5\">\n \u003Cspan class=\"fi-arrow\">→\u003C/span>\n \u003Cspan class=\"fi-proto fi-protobuf\">protobuf\u003C/span>\n \u003Cspan class=\"fi-hop\">pmcp→App\u003C/span>\n \u003Cspan class=\"fi-detail\">CallToolRequest\u003C/span>\n \u003C/div>\n \u003Cdiv class=\"flow-item fi-progress\" data-step=\"6\">\n \u003Cspan class=\"fi-arrow\">←\u003C/span>\n \u003Cspan class=\"fi-proto fi-protobuf\">protobuf\u003C/span>\n \u003Cspan class=\"fi-hop\">App→pmcp\u003C/span>\n \u003Cspan class=\"fi-detail\">ProgressNotification 33%\u003C/span>\n \u003C/div>\n \u003Cdiv class=\"flow-item fi-progress\" data-step=\"7\">\n \u003Cspan class=\"fi-arrow\">←\u003C/span>\n \u003Cspan class=\"fi-proto fi-protobuf\">protobuf\u003C/span>\n \u003Cspan class=\"fi-hop\">App→pmcp\u003C/span>\n \u003Cspan class=\"fi-detail\">ProgressNotification 66%\u003C/span>\n \u003C/div>\n \u003Cdiv class=\"flow-item fi-progress\" data-step=\"8\">\n \u003Cspan class=\"fi-arrow\">←\u003C/span>\n \u003Cspan class=\"fi-proto fi-protobuf\">protobuf\u003C/span>\n \u003Cspan class=\"fi-hop\">App→pmcp\u003C/span>\n \u003Cspan class=\"fi-detail\">ProgressNotification 100%\u003C/span>\n \u003C/div>\n \u003Cdiv class=\"flow-item fi-in\" data-step=\"9\">\n \u003Cspan class=\"fi-arrow\">←\u003C/span>\n \u003Cspan class=\"fi-proto fi-protobuf\">protobuf\u003C/span>\n \u003Cspan class=\"fi-hop\">App→pmcp\u003C/span>\n \u003Cspan class=\"fi-detail\">CallToolResult\u003C/span>\n \u003C/div>\n \u003Cdiv class=\"flow-item fi-in\" data-step=\"10\">\n \u003Cspan class=\"fi-arrow\">←\u003C/span>\n \u003Cspan class=\"fi-proto fi-jsonrpc\">JSON-RPC\u003C/span>\n \u003Cspan class=\"fi-hop\">pmcp→Host\u003C/span>\n \u003Cspan class=\"fi-detail\">result: 47 files\u003C/span>\n \u003C/div>\n \u003C/Fragment>\n\u003C/DemoSection>\n\n## Dynamic Tool Lists\n\nThe tool process controls which tools are visible. Here, the `login` handler returns `enable_tools` in its result — pmcp updates the tool list and notifies the host. No restart, no config change.\n\n\u003CDemoSection\n title=\"Dynamic Tool Lists\"\n demoId=\"demo3\"\n timeline={[[1, 600], [2, 800], [3, 400], [4, 350], [5, 300], [6, 700], [7, 300], [8, 400], [9, 100], [10, 800], [11, 1000], [12, 400], [13, 350], [14, 300], [15, 700], [16, 300]]}\n sideEffects={[\n { step: 7, action: \"complete-tool\", target: \"d3-tc-login\" },\n { step: 16, action: \"complete-tool\", target: \"d3-tc-create\" }\n ]}\n>\n \u003CFragment slot=\"chat\">\n \u003Cdiv class=\"chat-item user-msg\" data-step=\"1\">\n \u003Cdiv class=\"user-bubble\">Log me in as admin\u003C/div>\n \u003C/div>\n \u003Cdiv class=\"chat-item ai-msg\" data-step=\"2\">\n \u003Cdiv class=\"ai-bubble\">Authenticating you now.\u003C/div>\n \u003C/div>\n \u003Cdiv class=\"chat-item tool-card\" data-step=\"3\">\n \u003Cdiv class=\"tc-bar tc-running\" id=\"d3-tc-login-bar\">\u003C/div>\n \u003Cdiv class=\"tc-body\">\n \u003Cdiv class=\"tc-head\">\n \u003Cspan class=\"tc-icon\">⚡\u003C/span>\n \u003Cspan class=\"tc-name\">login\u003C/span>\n \u003Cspan class=\"tc-status\" id=\"d3-tc-login-status\">running\u003C/span>\n \u003C/div>\n \u003Cdiv class=\"tc-args\">{\"{\"} user: \"admin\" {\"}\"}\u003C/div>\n \u003Cdiv class=\"tc-result\" data-step=\"7\">\n \u003Cpre class=\"tc-json\">Authenticated\u003C/pre>\n \u003Cdiv class=\"unlock-badge\">+create_record +delete_record\u003C/div>\n \u003C/div>\n \u003C/div>\n \u003C/div>\n \u003Cdiv class=\"chat-item ai-msg\" data-step=\"10\">\n \u003Cdiv class=\"ai-bubble\">You're logged in. \u003Cstrong>create_record\u003C/strong> and \u003Cstrong>delete_record\u003C/strong> are now available.\u003C/div>\n \u003C/div>\n \u003Cdiv class=\"chat-item user-msg\" data-step=\"11\">\n \u003Cdiv class=\"user-bubble\">Create a new user record for Alice\u003C/div>\n \u003C/div>\n \u003Cdiv class=\"chat-item tool-card\" data-step=\"12\">\n \u003Cdiv class=\"tc-bar tc-running\" id=\"d3-tc-create-bar\">\u003C/div>\n \u003Cdiv class=\"tc-body\">\n \u003Cdiv class=\"tc-head\">\n \u003Cspan class=\"tc-icon\">⚡\u003C/span>\n \u003Cspan class=\"tc-name\">create_record\u003C/span>\n \u003Cspan class=\"tc-status\" id=\"d3-tc-create-status\">running\u003C/span>\n \u003C/div>\n \u003Cdiv class=\"tc-args\">{\"{\"} name: \"Alice\", role: \"editor\" {\"}\"}\u003C/div>\n \u003Cdiv class=\"tc-result\" data-step=\"16\">\n \u003Cpre class=\"tc-json\">Created user Alice (id: usr_847)\u003C/pre>\n \u003C/div>\n \u003C/div>\n \u003C/div>\n \u003C/Fragment>\n \u003CFragment slot=\"flow\">\n \u003Cdiv class=\"flow-item fi-out\" data-step=\"4\">\n \u003Cspan class=\"fi-arrow\">→\u003C/span>\n \u003Cspan class=\"fi-proto fi-jsonrpc\">JSON-RPC\u003C/span>\n \u003Cspan class=\"fi-hop\">Host→pmcp\u003C/span>\n \u003Cspan class=\"fi-detail\">tools/call \"login\"\u003C/span>\n \u003C/div>\n \u003Cdiv class=\"flow-item fi-out\" data-step=\"5\">\n \u003Cspan class=\"fi-arrow\">→\u003C/span>\n \u003Cspan class=\"fi-proto fi-protobuf\">protobuf\u003C/span>\n \u003Cspan class=\"fi-hop\">pmcp→App\u003C/span>\n \u003Cspan class=\"fi-detail\">CallToolRequest\u003C/span>\n \u003C/div>\n \u003Cdiv class=\"flow-item fi-in fi-highlight\" data-step=\"6\">\n \u003Cspan class=\"fi-arrow\">←\u003C/span>\n \u003Cspan class=\"fi-proto fi-protobuf\">protobuf\u003C/span>\n \u003Cspan class=\"fi-hop\">App→pmcp\u003C/span>\n \u003Cspan class=\"fi-detail\">CallToolResult + enable_tools\u003C/span>\n \u003C/div>\n \u003Cdiv class=\"flow-item fi-in\" data-step=\"7\">\n \u003Cspan class=\"fi-arrow\">←\u003C/span>\n \u003Cspan class=\"fi-proto fi-jsonrpc\">JSON-RPC\u003C/span>\n \u003Cspan class=\"fi-hop\">pmcp→Host\u003C/span>\n \u003Cspan class=\"fi-detail\">result: \"Authenticated\"\u003C/span>\n \u003C/div>\n \u003Cdiv class=\"flow-item fi-notify\" data-step=\"8\">\n \u003Cspan class=\"fi-arrow\">⇢\u003C/span>\n \u003Cspan class=\"fi-proto fi-jsonrpc\">JSON-RPC\u003C/span>\n \u003Cspan class=\"fi-hop\">pmcp→Host\u003C/span>\n \u003Cspan class=\"fi-detail\">notifications/tools/list_changed\u003C/span>\n \u003C/div>\n \u003Cdiv class=\"flow-sep\" data-step=\"9\">\u003C/div>\n \u003Cdiv class=\"flow-item fi-out\" data-step=\"13\">\n \u003Cspan class=\"fi-arrow\">→\u003C/span>\n \u003Cspan class=\"fi-proto fi-jsonrpc\">JSON-RPC\u003C/span>\n \u003Cspan class=\"fi-hop\">Host→pmcp\u003C/span>\n \u003Cspan class=\"fi-detail\">tools/call \"create_record\"\u003C/span>\n \u003C/div>\n \u003Cdiv class=\"flow-item fi-out\" data-step=\"14\">\n \u003Cspan class=\"fi-arrow\">→\u003C/span>\n \u003Cspan class=\"fi-proto fi-protobuf\">protobuf\u003C/span>\n \u003Cspan class=\"fi-hop\">pmcp→App\u003C/span>\n \u003Cspan class=\"fi-detail\">CallToolRequest\u003C/span>\n \u003C/div>\n \u003Cdiv class=\"flow-item fi-in\" data-step=\"15\">\n \u003Cspan class=\"fi-arrow\">←\u003C/span>\n \u003Cspan class=\"fi-proto fi-protobuf\">protobuf\u003C/span>\n \u003Cspan class=\"fi-hop\">App→pmcp\u003C/span>\n \u003Cspan class=\"fi-detail\">CallToolResult\u003C/span>\n \u003C/div>\n \u003Cdiv class=\"flow-item fi-in\" data-step=\"16\">\n \u003Cspan class=\"fi-arrow\">←\u003C/span>\n \u003Cspan class=\"fi-proto fi-jsonrpc\">JSON-RPC\u003C/span>\n \u003Cspan class=\"fi-hop\">pmcp→Host\u003C/span>\n \u003Cspan class=\"fi-detail\">result: \"Created user Alice\"\u003C/span>\n \u003C/div>\n \u003C/Fragment>\n\u003C/DemoSection>\n\n## Hot Reload\n\nA tool call fails. The developer fixes the bug and saves. pmcp hot-reloads the fix — no restart needed. The AI retries and it works.\n\n\u003CDemoSection\n title=\"Hot Reload\"\n demoId=\"demo4\"\n timeline={[[1, 600], [2, 800], [3, 400], [4, 350], [5, 300], [6, 600], [7, 300], [8, 200], [9, 700], [10, 1500], [11, 300], [12, 400], [13, 600], [14, 400], [15, 500], [16, 200], [17, 800], [18, 400], [19, 350], [20, 300], [21, 600], [22, 300], [23, 200], [24, 700]]}\n sideEffects={[\n { step: 8, action: \"error-tool\", target: \"d4-tc-export-err\" },\n { step: 23, action: \"complete-tool\", target: \"d4-tc-export-ok\" }\n ]}\n>\n \u003CFragment slot=\"chat\">\n \u003Cdiv class=\"chat-item user-msg\" data-step=\"1\">\n \u003Cdiv class=\"user-bubble\">Export the sales data as CSV\u003C/div>\n \u003C/div>\n \u003Cdiv class=\"chat-item ai-msg\" data-step=\"2\">\n \u003Cdiv class=\"ai-bubble\">I'll export that for you.\u003C/div>\n \u003C/div>\n \u003Cdiv class=\"chat-item tool-card\" data-step=\"3\">\n \u003Cdiv class=\"tc-bar tc-running\" id=\"d4-tc-export-err-bar\">\u003C/div>\n \u003Cdiv class=\"tc-body\">\n \u003Cdiv class=\"tc-head\">\n \u003Cspan class=\"tc-icon\">⚡\u003C/span>\n \u003Cspan class=\"tc-name\">export_csv\u003C/span>\n \u003Cspan class=\"tc-status\" id=\"d4-tc-export-err-status\">running\u003C/span>\n \u003C/div>\n \u003Cdiv class=\"tc-args\">{\"{\"} dataset: \"sales\" {\"}\"}\u003C/div>\n \u003Cdiv class=\"tc-result\" data-step=\"8\">\n \u003Cpre class=\"tc-json tc-error\">error [INVALID_FORMAT]: CSV writer not initialized\u003C/pre>\n \u003C/div>\n \u003C/div>\n \u003C/div>\n \u003Cdiv class=\"chat-item ai-msg\" data-step=\"9\">\n \u003Cdiv class=\"ai-bubble\">The export tool has a bug — the CSV writer isn't initialized.\u003C/div>\n \u003C/div>\n \u003Cdiv class=\"chat-item system-msg\" data-step=\"10\">\n ⟳ File changed: tools.py — reloading...\n \u003C/div>\n \u003Cdiv class=\"chat-item system-msg system-success\" data-step=\"15\">\n ✓ Reconnected · 3 tools registered\n \u003C/div>\n \u003Cdiv class=\"chat-item ai-msg\" data-step=\"16\">\n \u003Cdiv class=\"ai-bubble\">Tools reloaded. Let me retry.\u003C/div>\n \u003C/div>\n \u003Cdiv class=\"chat-item tool-card\" data-step=\"17\">\n \u003Cdiv class=\"tc-bar tc-running\" id=\"d4-tc-export-ok-bar\">\u003C/div>\n \u003Cdiv class=\"tc-body\">\n \u003Cdiv class=\"tc-head\">\n \u003Cspan class=\"tc-icon\">⚡\u003C/span>\n \u003Cspan class=\"tc-name\">export_csv\u003C/span>\n \u003Cspan class=\"tc-status\" id=\"d4-tc-export-ok-status\">running\u003C/span>\n \u003C/div>\n \u003Cdiv class=\"tc-args\">{\"{\"} dataset: \"sales\" {\"}\"}\u003C/div>\n \u003Cdiv class=\"tc-result\" data-step=\"23\">\n \u003Cpre class=\"tc-json\">Exported 1,204 rows to sales.csv\u003C/pre>\n \u003C/div>\n \u003C/div>\n \u003C/div>\n \u003Cdiv class=\"chat-item ai-msg\" data-step=\"24\">\n \u003Cdiv class=\"ai-bubble\">Done — \u003Cstrong>1,204 rows\u003C/strong> exported to sales.csv.\u003C/div>\n \u003C/div>\n \u003C/Fragment>\n \u003CFragment slot=\"flow\">\n \u003Cdiv class=\"flow-item fi-out\" data-step=\"4\">\n \u003Cspan class=\"fi-arrow\">→\u003C/span>\n \u003Cspan class=\"fi-proto fi-jsonrpc\">JSON-RPC\u003C/span>\n \u003Cspan class=\"fi-hop\">Host→pmcp\u003C/span>\n \u003Cspan class=\"fi-detail\">tools/call \"export_csv\"\u003C/span>\n \u003C/div>\n \u003Cdiv class=\"flow-item fi-out\" data-step=\"5\">\n \u003Cspan class=\"fi-arrow\">→\u003C/span>\n \u003Cspan class=\"fi-proto fi-protobuf\">protobuf\u003C/span>\n \u003Cspan class=\"fi-hop\">pmcp→App\u003C/span>\n \u003Cspan class=\"fi-detail\">CallToolRequest\u003C/span>\n \u003C/div>\n \u003Cdiv class=\"flow-item fi-in fi-error\" data-step=\"6\">\n \u003Cspan class=\"fi-arrow\">←\u003C/span>\n \u003Cspan class=\"fi-proto fi-protobuf\">protobuf\u003C/span>\n \u003Cspan class=\"fi-hop\">App→pmcp\u003C/span>\n \u003Cspan class=\"fi-detail\">CallToolResult (is_error)\u003C/span>\n \u003C/div>\n \u003Cdiv class=\"flow-item fi-in fi-error\" data-step=\"7\">\n \u003Cspan class=\"fi-arrow\">←\u003C/span>\n \u003Cspan class=\"fi-proto fi-jsonrpc\">JSON-RPC\u003C/span>\n \u003Cspan class=\"fi-hop\">pmcp→Host\u003C/span>\n \u003Cspan class=\"fi-detail\">error: INVALID_FORMAT\u003C/span>\n \u003C/div>\n \u003Cdiv class=\"flow-sep\" data-step=\"10\">\u003C/div>\n \u003Cdiv class=\"flow-item fi-notify\" data-step=\"11\">\n \u003Cspan class=\"fi-arrow\">⟳\u003C/span>\n \u003Cspan class=\"fi-proto fi-internal\">internal\u003C/span>\n \u003Cspan class=\"fi-hop\">pmcp\u003C/span>\n \u003Cspan class=\"fi-detail\">file watch: tools.py modified\u003C/span>\n \u003C/div>\n \u003Cdiv class=\"flow-item fi-out\" data-step=\"12\">\n \u003Cspan class=\"fi-arrow\">→\u003C/span>\n \u003Cspan class=\"fi-proto fi-protobuf\">protobuf\u003C/span>\n \u003Cspan class=\"fi-hop\">pmcp→App\u003C/span>\n \u003Cspan class=\"fi-detail\">ReloadRequest\u003C/span>\n \u003C/div>\n \u003Cdiv class=\"flow-item fi-in\" data-step=\"13\">\n \u003Cspan class=\"fi-arrow\">←\u003C/span>\n \u003Cspan class=\"fi-proto fi-protobuf\">protobuf\u003C/span>\n \u003Cspan class=\"fi-hop\">App→pmcp\u003C/span>\n \u003Cspan class=\"fi-detail\">ReloadResponse {\"{\"} success: true {\"}\"}\u003C/span>\n \u003C/div>\n \u003Cdiv class=\"flow-item fi-notify\" data-step=\"14\">\n \u003Cspan class=\"fi-arrow\">⇢\u003C/span>\n \u003Cspan class=\"fi-proto fi-jsonrpc\">JSON-RPC\u003C/span>\n \u003Cspan class=\"fi-hop\">pmcp→Host\u003C/span>\n \u003Cspan class=\"fi-detail\">notifications/tools/list_changed\u003C/span>\n \u003C/div>\n \u003Cdiv class=\"flow-sep\" data-step=\"16\">\u003C/div>\n \u003Cdiv class=\"flow-item fi-out\" data-step=\"18\">\n \u003Cspan class=\"fi-arrow\">→\u003C/span>\n \u003Cspan class=\"fi-proto fi-jsonrpc\">JSON-RPC\u003C/span>\n \u003Cspan class=\"fi-hop\">Host→pmcp\u003C/span>\n \u003Cspan class=\"fi-detail\">tools/call \"export_csv\"\u003C/span>\n \u003C/div>\n \u003Cdiv class=\"flow-item fi-out\" data-step=\"19\">\n \u003Cspan class=\"fi-arrow\">→\u003C/span>\n \u003Cspan class=\"fi-proto fi-protobuf\">protobuf\u003C/span>\n \u003Cspan class=\"fi-hop\">pmcp→App\u003C/span>\n \u003Cspan class=\"fi-detail\">CallToolRequest\u003C/span>\n \u003C/div>\n \u003Cdiv class=\"flow-item fi-in\" data-step=\"20\">\n \u003Cspan class=\"fi-arrow\">←\u003C/span>\n \u003Cspan class=\"fi-proto fi-protobuf\">protobuf\u003C/span>\n \u003Cspan class=\"fi-hop\">App→pmcp\u003C/span>\n \u003Cspan class=\"fi-detail\">CallToolResult\u003C/span>\n \u003C/div>\n \u003Cdiv class=\"flow-item fi-in\" data-step=\"21\">\n \u003Cspan class=\"fi-arrow\">←\u003C/span>\n \u003Cspan class=\"fi-proto fi-jsonrpc\">JSON-RPC\u003C/span>\n \u003Cspan class=\"fi-hop\">pmcp→Host\u003C/span>\n \u003Cspan class=\"fi-detail\">result: \"1,204 rows\"\u003C/span>\n \u003C/div>\n \u003C/Fragment>\n\u003C/DemoSection>\n\n## Tool Groups\n\nTool groups bundle related actions under one tool name. A `oneOf` discriminated union schema guides the agent to the right parameters for each action.\n\n\u003CDemoSection\n title=\"Tool Groups\"\n demoId=\"demo5\"\n timeline={[[1, 600], [2, 800], [3, 400], [4, 350], [5, 300], [6, 700], [7, 300], [8, 200], [9, 700]]}\n sideEffects={[{ step: 8, action: \"complete-tool\", target: \"d5-tc-data\" }]}\n>\n \u003CFragment slot=\"chat\">\n \u003Cdiv class=\"chat-item user-msg\" data-step=\"1\">\n \u003Cdiv class=\"user-bubble\">Add the sales dataset from /data/sales.csv\u003C/div>\n \u003C/div>\n \u003Cdiv class=\"chat-item ai-msg\" data-step=\"2\">\n \u003Cdiv class=\"ai-bubble\">I'll ingest that dataset.\u003C/div>\n \u003C/div>\n \u003Cdiv class=\"chat-item tool-card\" data-step=\"3\">\n \u003Cdiv class=\"tc-bar tc-running\" id=\"d5-tc-data-bar\">\u003C/div>\n \u003Cdiv class=\"tc-body\">\n \u003Cdiv class=\"tc-head\">\n \u003Cspan class=\"tc-icon\">⚡\u003C/span>\n \u003Cspan class=\"tc-name\">data\u003C/span>\n \u003Cspan class=\"tc-status\" id=\"d5-tc-data-status\">running\u003C/span>\n \u003C/div>\n \u003Cdiv class=\"tc-args\">{\"{\"} action: \"add\", data_path: \"/data/sales.csv\", auto_clean: true {\"}\"}\u003C/div>\n \u003Cdiv class=\"tc-result\" data-step=\"8\">\n \u003Cpre class=\"tc-json\">Added 12,847 rows from sales.csv\u003C/pre>\n \u003C/div>\n \u003C/div>\n \u003C/div>\n \u003Cdiv class=\"chat-item ai-msg\" data-step=\"9\">\n \u003Cdiv class=\"ai-bubble\">Dataset loaded — \u003Cstrong>12,847 rows\u003C/strong> ingested.\u003C/div>\n \u003C/div>\n \u003C/Fragment>\n \u003CFragment slot=\"flow\">\n \u003Cdiv class=\"flow-item fi-out\" data-step=\"4\">\n \u003Cspan class=\"fi-arrow\">→\u003C/span>\n \u003Cspan class=\"fi-proto fi-jsonrpc\">JSON-RPC\u003C/span>\n \u003Cspan class=\"fi-hop\">Host→pmcp\u003C/span>\n \u003Cspan class=\"fi-detail\">tools/call \"data\" (oneOf schema)\u003C/span>\n \u003C/div>\n \u003Cdiv class=\"flow-item fi-out\" data-step=\"5\">\n \u003Cspan class=\"fi-arrow\">→\u003C/span>\n \u003Cspan class=\"fi-proto fi-protobuf\">protobuf\u003C/span>\n \u003Cspan class=\"fi-hop\">pmcp→App\u003C/span>\n \u003Cspan class=\"fi-detail\">CallToolRequest\u003C/span>\n \u003C/div>\n \u003Cdiv class=\"flow-item fi-in\" data-step=\"6\">\n \u003Cspan class=\"fi-arrow\">←\u003C/span>\n \u003Cspan class=\"fi-proto fi-protobuf\">protobuf\u003C/span>\n \u003Cspan class=\"fi-hop\">App→pmcp\u003C/span>\n \u003Cspan class=\"fi-detail\">CallToolResult\u003C/span>\n \u003C/div>\n \u003Cdiv class=\"flow-item fi-in\" data-step=\"7\">\n \u003Cspan class=\"fi-arrow\">←\u003C/span>\n \u003Cspan class=\"fi-proto fi-jsonrpc\">JSON-RPC\u003C/span>\n \u003Cspan class=\"fi-hop\">pmcp→Host\u003C/span>\n \u003Cspan class=\"fi-detail\">result: \"12,847 rows added\"\u003C/span>\n \u003C/div>\n \u003C/Fragment>\n\u003C/DemoSection>\n\n## Server-Defined Workflows\n\nMulti-step workflows where the tool surface changes at each step. Tools appear and disappear as the workflow progresses — the available tools _are_ the state machine.\n\n\u003CDemoSection\n title=\"Server-Defined Workflows\"\n demoId=\"demo6\"\n timeline={[[1, 600], [2, 800], [3, 400], [4, 350], [5, 300], [6, 700], [7, 300], [8, 400], [9, 100], [10, 800], [11, 400], [12, 350], [13, 300], [14, 700], [15, 300], [16, 400], [17, 100], [18, 400], [19, 350], [20, 300], [21, 700], [22, 300], [23, 400], [24, 100], [25, 400], [26, 350], [27, 300], [28, 700], [29, 300], [30, 700]]}\n sideEffects={[\n { step: 7, action: \"complete-tool\", target: \"d6-tc-review\" },\n { step: 15, action: \"complete-tool\", target: \"d6-tc-approve\" },\n { step: 22, action: \"complete-tool\", target: \"d6-tc-tests\" },\n { step: 29, action: \"complete-tool\", target: \"d6-tc-promote\" }\n ]}\n>\n \u003CFragment slot=\"chat\">\n \u003Cdiv class=\"chat-item user-msg\" data-step=\"1\">\n \u003Cdiv class=\"user-bubble\">Deploy PR #142\u003C/div>\n \u003C/div>\n \u003Cdiv class=\"chat-item ai-msg\" data-step=\"2\">\n \u003Cdiv class=\"ai-bubble\">Starting deployment review.\u003C/div>\n \u003C/div>\n \u003Cdiv class=\"chat-item tool-card\" data-step=\"3\">\n \u003Cdiv class=\"tc-bar tc-running\" id=\"d6-tc-review-bar\">\u003C/div>\n \u003Cdiv class=\"tc-body\">\n \u003Cdiv class=\"tc-head\">\n \u003Cspan class=\"tc-icon\">⚡\u003C/span>\n \u003Cspan class=\"tc-name\">deploy.review\u003C/span>\n \u003Cspan class=\"tc-status\" id=\"d6-tc-review-status\">running\u003C/span>\n \u003C/div>\n \u003Cdiv class=\"tc-args\">{\"{\"} pr_url: \"#142\" {\"}\"}\u003C/div>\n \u003Cdiv class=\"tc-result\" data-step=\"7\">\n \u003Cpre class=\"tc-json\">3 files changed\u003C/pre>\n \u003C/div>\n \u003C/div>\n \u003C/div>\n \u003Cdiv class=\"chat-item ai-msg\" data-step=\"10\">\n \u003Cdiv class=\"ai-bubble\">Changes look good. Approving.\u003C/div>\n \u003C/div>\n \u003Cdiv class=\"chat-item tool-card\" data-step=\"11\">\n \u003Cdiv class=\"tc-bar tc-running\" id=\"d6-tc-approve-bar\">\u003C/div>\n \u003Cdiv class=\"tc-body\">\n \u003Cdiv class=\"tc-head\">\n \u003Cspan class=\"tc-icon\">⚡\u003C/span>\n \u003Cspan class=\"tc-name\">deploy.approve\u003C/span>\n \u003Cspan class=\"tc-status\" id=\"d6-tc-approve-status\">running\u003C/span>\n \u003C/div>\n \u003Cdiv class=\"tc-args\">{\"{\"} reason: \"Tests green, LGTM\" {\"}\"}\u003C/div>\n \u003Cdiv class=\"tc-result\" data-step=\"15\">\n \u003Cpre class=\"tc-json\">Approved\u003C/pre>\n \u003C/div>\n \u003C/div>\n \u003C/div>\n \u003Cdiv class=\"chat-item system-msg\" data-step=\"16\">\n ⚡ Available tools changed: +deploy.run_tests −deploy.review −deploy.approve\n \u003C/div>\n \u003Cdiv class=\"chat-item tool-card\" data-step=\"18\">\n \u003Cdiv class=\"tc-bar tc-running\" id=\"d6-tc-tests-bar\">\u003C/div>\n \u003Cdiv class=\"tc-body\">\n \u003Cdiv class=\"tc-head\">\n \u003Cspan class=\"tc-icon\">⚡\u003C/span>\n \u003Cspan class=\"tc-name\">deploy.run_tests\u003C/span>\n \u003Cspan class=\"tc-status\" id=\"d6-tc-tests-status\">running\u003C/span>\n \u003C/div>\n \u003Cdiv class=\"tc-args\">{\"{\"} {\"}\"}\u003C/div>\n \u003Cdiv class=\"tc-result\" data-step=\"22\">\n \u003Cpre class=\"tc-json\">42 tests passed\u003C/pre>\n \u003C/div>\n \u003C/div>\n \u003C/div>\n \u003Cdiv class=\"chat-item system-msg\" data-step=\"23\">\n ⚡ Available tools changed: +deploy.promote −deploy.run_tests −deploy.cancel\n \u003C/div>\n \u003Cdiv class=\"chat-item tool-card\" data-step=\"25\">\n \u003Cdiv class=\"tc-bar tc-running\" id=\"d6-tc-promote-bar\">\u003C/div>\n \u003Cdiv class=\"tc-body\">\n \u003Cdiv class=\"tc-head\">\n \u003Cspan class=\"tc-icon\">⚡\u003C/span>\n \u003Cspan class=\"tc-name\">deploy.promote\u003C/span>\n \u003Cspan class=\"tc-status\" id=\"d6-tc-promote-status\">running\u003C/span>\n \u003C/div>\n \u003Cdiv class=\"tc-args\">{\"{\"} {\"}\"}\u003C/div>\n \u003Cdiv class=\"tc-result\" data-step=\"29\">\n \u003Cpre class=\"tc-json\">Live in production\u003C/pre>\n \u003C/div>\n \u003C/div>\n \u003C/div>\n \u003Cdiv class=\"chat-item ai-msg\" data-step=\"30\">\n \u003Cdiv class=\"ai-bubble\">Deployed \u003Cstrong>PR #142\u003C/strong> to production.\u003C/div>\n \u003C/div>\n \u003C/Fragment>\n \u003CFragment slot=\"flow\">\n \u003Cdiv class=\"flow-item fi-out\" data-step=\"4\">\n \u003Cspan class=\"fi-arrow\">→\u003C/span>\n \u003Cspan class=\"fi-proto fi-jsonrpc\">JSON-RPC\u003C/span>\n \u003Cspan class=\"fi-hop\">Host→pmcp\u003C/span>\n \u003Cspan class=\"fi-detail\">tools/call \"deploy.review\"\u003C/span>\n \u003C/div>\n \u003Cdiv class=\"flow-item fi-out\" data-step=\"5\">\n \u003Cspan class=\"fi-arrow\">→\u003C/span>\n \u003Cspan class=\"fi-proto fi-protobuf\">protobuf\u003C/span>\n \u003Cspan class=\"fi-hop\">pmcp→App\u003C/span>\n \u003Cspan class=\"fi-detail\">CallToolRequest\u003C/span>\n \u003C/div>\n \u003Cdiv class=\"flow-item fi-in\" data-step=\"6\">\n \u003Cspan class=\"fi-arrow\">←\u003C/span>\n \u003Cspan class=\"fi-proto fi-protobuf\">protobuf\u003C/span>\n \u003Cspan class=\"fi-hop\">App→pmcp\u003C/span>\n \u003Cspan class=\"fi-detail\">CallToolResult + set_allowed\u003C/span>\n \u003C/div>\n \u003Cdiv class=\"flow-item fi-in\" data-step=\"7\">\n \u003Cspan class=\"fi-arrow\">←\u003C/span>\n \u003Cspan class=\"fi-proto fi-jsonrpc\">JSON-RPC\u003C/span>\n \u003Cspan class=\"fi-hop\">pmcp→Host\u003C/span>\n \u003Cspan class=\"fi-detail\">result: \"3 files changed\"\u003C/span>\n \u003C/div>\n \u003Cdiv class=\"flow-item fi-notify\" data-step=\"8\">\n \u003Cspan class=\"fi-arrow\">⇢\u003C/span>\n \u003Cspan class=\"fi-proto fi-jsonrpc\">JSON-RPC\u003C/span>\n \u003Cspan class=\"fi-hop\">pmcp→Host\u003C/span>\n \u003Cspan class=\"fi-detail\">notifications/tools/list_changed\u003C/span>\n \u003C/div>\n \u003Cdiv class=\"flow-sep\" data-step=\"9\">\u003C/div>\n \u003Cdiv class=\"flow-item fi-out\" data-step=\"12\">\n \u003Cspan class=\"fi-arrow\">→\u003C/span>\n \u003Cspan class=\"fi-proto fi-jsonrpc\">JSON-RPC\u003C/span>\n \u003Cspan class=\"fi-hop\">Host→pmcp\u003C/span>\n \u003Cspan class=\"fi-detail\">tools/call \"deploy.approve\"\u003C/span>\n \u003C/div>\n \u003Cdiv class=\"flow-item fi-out\" data-step=\"13\">\n \u003Cspan class=\"fi-arrow\">→\u003C/span>\n \u003Cspan class=\"fi-proto fi-protobuf\">protobuf\u003C/span>\n \u003Cspan class=\"fi-hop\">pmcp→App\u003C/span>\n \u003Cspan class=\"fi-detail\">CallToolRequest\u003C/span>\n \u003C/div>\n \u003Cdiv class=\"flow-item fi-in\" data-step=\"14\">\n \u003Cspan class=\"fi-arrow\">←\u003C/span>\n \u003Cspan class=\"fi-proto fi-protobuf\">protobuf\u003C/span>\n \u003Cspan class=\"fi-hop\">App→pmcp\u003C/span>\n \u003Cspan class=\"fi-detail\">CallToolResult + set_allowed\u003C/span>\n \u003C/div>\n \u003Cdiv class=\"flow-item fi-notify\" data-step=\"15\">\n \u003Cspan class=\"fi-arrow\">⇢\u003C/span>\n \u003Cspan class=\"fi-proto fi-jsonrpc\">JSON-RPC\u003C/span>\n \u003Cspan class=\"fi-hop\">pmcp→Host\u003C/span>\n \u003Cspan class=\"fi-detail\">notifications/tools/list_changed\u003C/span>\n \u003C/div>\n \u003Cdiv class=\"flow-sep\" data-step=\"17\">\u003C/div>\n \u003Cdiv class=\"flow-item fi-out\" data-step=\"19\">\n \u003Cspan class=\"fi-arrow\">→\u003C/span>\n \u003Cspan class=\"fi-proto fi-jsonrpc\">JSON-RPC\u003C/span>\n \u003Cspan class=\"fi-hop\">Host→pmcp\u003C/span>\n \u003Cspan class=\"fi-detail\">tools/call \"deploy.run_tests\"\u003C/span>\n \u003C/div>\n \u003Cdiv class=\"flow-item fi-out\" data-step=\"20\">\n \u003Cspan class=\"fi-arrow\">→\u003C/span>\n \u003Cspan class=\"fi-proto fi-protobuf\">protobuf\u003C/span>\n \u003Cspan class=\"fi-hop\">pmcp→App\u003C/span>\n \u003Cspan class=\"fi-detail\">CallToolRequest\u003C/span>\n \u003C/div>\n \u003Cdiv class=\"flow-item fi-in\" data-step=\"21\">\n \u003Cspan class=\"fi-arrow\">←\u003C/span>\n \u003Cspan class=\"fi-proto fi-protobuf\">protobuf\u003C/span>\n \u003Cspan class=\"fi-hop\">App→pmcp\u003C/span>\n \u003Cspan class=\"fi-detail\">CallToolResult + set_allowed\u003C/span>\n \u003C/div>\n \u003Cdiv class=\"flow-item fi-notify\" data-step=\"22\">\n \u003Cspan class=\"fi-arrow\">⇢\u003C/span>\n \u003Cspan class=\"fi-proto fi-jsonrpc\">JSON-RPC\u003C/span>\n \u003Cspan class=\"fi-hop\">pmcp→Host\u003C/span>\n \u003Cspan class=\"fi-detail\">notifications/tools/list_changed\u003C/span>\n \u003C/div>\n \u003Cdiv class=\"flow-sep\" data-step=\"24\">\u003C/div>\n \u003Cdiv class=\"flow-item fi-out\" data-step=\"26\">\n \u003Cspan class=\"fi-arrow\">→\u003C/span>\n \u003Cspan class=\"fi-proto fi-jsonrpc\">JSON-RPC\u003C/span>\n \u003Cspan class=\"fi-hop\">Host→pmcp\u003C/span>\n \u003Cspan class=\"fi-detail\">tools/call \"deploy.promote\"\u003C/span>\n \u003C/div>\n \u003Cdiv class=\"flow-item fi-out\" data-step=\"27\">\n \u003Cspan class=\"fi-arrow\">→\u003C/span>\n \u003Cspan class=\"fi-proto fi-protobuf\">protobuf\u003C/span>\n \u003Cspan class=\"fi-hop\">pmcp→App\u003C/span>\n \u003Cspan class=\"fi-detail\">CallToolRequest\u003C/span>\n \u003C/div>\n \u003Cdiv class=\"flow-item fi-in\" data-step=\"28\">\n \u003Cspan class=\"fi-arrow\">←\u003C/span>\n \u003Cspan class=\"fi-proto fi-protobuf\">protobuf\u003C/span>\n \u003Cspan class=\"fi-hop\">App→pmcp\u003C/span>\n \u003Cspan class=\"fi-detail\">CallToolResult\u003C/span>\n \u003C/div>\n \u003Cdiv class=\"flow-item fi-in\" data-step=\"29\">\n \u003Cspan class=\"fi-arrow\">←\u003C/span>\n \u003Cspan class=\"fi-proto fi-jsonrpc\">JSON-RPC\u003C/span>\n \u003Cspan class=\"fi-hop\">pmcp→Host\u003C/span>\n \u003Cspan class=\"fi-detail\">result: \"Live in production\"\u003C/span>\n \u003C/div>\n \u003C/Fragment>\n\u003C/DemoSection>\n\n## Local Middleware\n\nMiddleware runs in-process — no round-trip to the Go bridge. Here it intercepts an error, formats it for the agent, and the agent recovers.\n\n\u003CDemoSection\n title=\"Local Middleware\"\n demoId=\"demo7\"\n timeline={[[1, 600], [2, 800], [3, 400], [4, 350], [5, 300], [6, 200], [7, 700], [8, 800], [9, 400], [10, 350], [11, 300], [12, 200], [13, 300], [14, 200], [15, 700]]}\n sideEffects={[\n { step: 6, action: \"error-tool\", target: \"d7-tc-read-err\" },\n { step: 14, action: \"complete-tool\", target: \"d7-tc-read-ok\" }\n ]}\n>\n \u003CFragment slot=\"chat\">\n \u003Cdiv class=\"chat-item user-msg\" data-step=\"1\">\n \u003Cdiv class=\"user-bubble\">Read /etc/shadow\u003C/div>\n \u003C/div>\n \u003Cdiv class=\"chat-item ai-msg\" data-step=\"2\">\n \u003Cdiv class=\"ai-bubble\">Let me try that.\u003C/div>\n \u003C/div>\n \u003Cdiv class=\"chat-item tool-card\" data-step=\"3\">\n \u003Cdiv class=\"tc-bar tc-running\" id=\"d7-tc-read-err-bar\">\u003C/div>\n \u003Cdiv class=\"tc-body\">\n \u003Cdiv class=\"tc-head\">\n \u003Cspan class=\"tc-icon\">⚡\u003C/span>\n \u003Cspan class=\"tc-name\">read_file\u003C/span>\n \u003Cspan class=\"tc-status\" id=\"d7-tc-read-err-status\">running\u003C/span>\n \u003C/div>\n \u003Cdiv class=\"tc-args\">{\"{\"} path: \"/etc/shadow\" {\"}\"}\u003C/div>\n \u003Cdiv class=\"tc-result\" data-step=\"6\">\n \u003Cpre class=\"tc-json tc-error\">\u003Cstrong>Permission denied\u003C/strong>: /etc/shadow — try a file you own\u003C/pre>\n \u003C/div>\n \u003C/div>\n \u003C/div>\n \u003Cdiv class=\"chat-item ai-msg\" data-step=\"8\">\n \u003Cdiv class=\"ai-bubble\">I don't have permission. Let me try a different file.\u003C/div>\n \u003C/div>\n \u003Cdiv class=\"chat-item tool-card\" data-step=\"9\">\n \u003Cdiv class=\"tc-bar tc-running\" id=\"d7-tc-read-ok-bar\">\u003C/div>\n \u003Cdiv class=\"tc-body\">\n \u003Cdiv class=\"tc-head\">\n \u003Cspan class=\"tc-icon\">⚡\u003C/span>\n \u003Cspan class=\"tc-name\">read_file\u003C/span>\n \u003Cspan class=\"tc-status\" id=\"d7-tc-read-ok-status\">running\u003C/span>\n \u003C/div>\n \u003Cdiv class=\"tc-args\">{\"{\"} path: \"~/.profile\" {\"}\"}\u003C/div>\n \u003Cdiv class=\"tc-result\" data-step=\"14\">\n \u003Cpre class=\"tc-json\"># .profile contents...\u003C/pre>\n \u003C/div>\n \u003C/div>\n \u003C/div>\n \u003Cdiv class=\"chat-item ai-msg\" data-step=\"15\">\n \u003Cdiv class=\"ai-bubble\">Here's your profile file.\u003C/div>\n \u003C/div>\n \u003C/Fragment>\n \u003CFragment slot=\"flow\">\n \u003Cdiv class=\"flow-item fi-out\" data-step=\"4\">\n \u003Cspan class=\"fi-arrow\">→\u003C/span>\n \u003Cspan class=\"fi-proto fi-jsonrpc\">JSON-RPC\u003C/span>\n \u003Cspan class=\"fi-hop\">Host→pmcp\u003C/span>\n \u003Cspan class=\"fi-detail\">tools/call \"read_file\"\u003C/span>\n \u003C/div>\n \u003Cdiv class=\"flow-item fi-notify fi-error\" data-step=\"5\">\n \u003Cspan class=\"fi-arrow\">⟳\u003C/span>\n \u003Cspan class=\"fi-proto fi-internal\">middleware\u003C/span>\n \u003Cspan class=\"fi-hop\">in-process\u003C/span>\n \u003Cspan class=\"fi-detail\">PermissionError → formatted message\u003C/span>\n \u003C/div>\n \u003Cdiv class=\"flow-item fi-in fi-error\" data-step=\"6\">\n \u003Cspan class=\"fi-arrow\">←\u003C/span>\n \u003Cspan class=\"fi-proto fi-jsonrpc\">JSON-RPC\u003C/span>\n \u003Cspan class=\"fi-hop\">pmcp→Host\u003C/span>\n \u003Cspan class=\"fi-detail\">error: \"Permission denied\"\u003C/span>\n \u003C/div>\n \u003Cdiv class=\"flow-sep\" data-step=\"7\">\u003C/div>\n \u003Cdiv class=\"flow-item fi-out\" data-step=\"10\">\n \u003Cspan class=\"fi-arrow\">→\u003C/span>\n \u003Cspan class=\"fi-proto fi-jsonrpc\">JSON-RPC\u003C/span>\n \u003Cspan class=\"fi-hop\">Host→pmcp\u003C/span>\n \u003Cspan class=\"fi-detail\">tools/call \"read_file\"\u003C/span>\n \u003C/div>\n \u003Cdiv class=\"flow-item fi-out\" data-step=\"11\">\n \u003Cspan class=\"fi-arrow\">→\u003C/span>\n \u003Cspan class=\"fi-proto fi-protobuf\">protobuf\u003C/span>\n \u003Cspan class=\"fi-hop\">pmcp→App\u003C/span>\n \u003Cspan class=\"fi-detail\">CallToolRequest\u003C/span>\n \u003C/div>\n \u003Cdiv class=\"flow-item fi-in\" data-step=\"12\">\n \u003Cspan class=\"fi-arrow\">←\u003C/span>\n \u003Cspan class=\"fi-proto fi-protobuf\">protobuf\u003C/span>\n \u003Cspan class=\"fi-hop\">App→pmcp\u003C/span>\n \u003Cspan class=\"fi-detail\">CallToolResult\u003C/span>\n \u003C/div>\n \u003Cdiv class=\"flow-item fi-in\" data-step=\"13\">\n \u003Cspan class=\"fi-arrow\">←\u003C/span>\n \u003Cspan class=\"fi-proto fi-jsonrpc\">JSON-RPC\u003C/span>\n \u003Cspan class=\"fi-hop\">pmcp→Host\u003C/span>\n \u003Cspan class=\"fi-detail\">result: \".profile contents\"\u003C/span>\n \u003C/div>\n \u003C/Fragment>\n\u003C/DemoSection>\n\n## What's New\n\nprotomcp now supports advanced features for building complex MCP servers:\n\n- **Tool Groups** — group related actions with per-action schemas (`oneOf` discriminated unions)\n- **Server-Defined Workflows** — multi-step state machines where the tool surface is the state\n- **Local Middleware** — in-process middleware chains for error formatting, timing, and more\n- **Declarative Validation** — required fields, enum fuzzy matching, cross-parameter rules\n- **Server Context** — shared parameter resolvers injected automatically\n- **Telemetry Sinks** — structured tool-call events to pluggable, fail-safe sinks\n- **Sidecar Management** — managed companion processes with health checks\n- **Handler Discovery** — auto-discover tool handlers from a directory\n\nSee the [Python Guide](/protomcp/guides/writing-tools-python/) for the full API.\n\n## Try It Yourself\n\n```sh\nbrew install msilverblatt/tap/protomcp\npmcp dev examples/python/basic.py\n```\n\nCheck out the [Quick Start](/protomcp/getting-started/quick-start/) guide or browse the [examples](https://github.com/msilverblatt/protomcp/tree/master/examples).","src/content/docs/demo.mdx","09db3e72dff6fce0","demo.mdx","getting-started/how-it-works",{"id":29,"data":31,"body":38,"filePath":39,"digest":40,"legacyId":41,"deferredRender":16},{"title":32,"description":33,"editUrl":16,"head":34,"template":35,"sidebar":36,"pagefind":16,"draft":23},"How It Works","Architecture overview of the three-layer protomcp system.",[],"doc",{"hidden":23,"attrs":37},{},"## Three Layers\n\nprotomcp connects an MCP host to a tool process through three distinct layers:\n\n```mermaid\ngraph LR\n A[MCP Host\u003Cbr/>Claude Desktop] \u003C-->|MCP protocol\u003Cbr/>stdio / HTTP| B[protomcp\u003Cbr/>Go binary]\n B \u003C-->|protobuf\u003Cbr/>unix socket| C[Tool Process\u003Cbr/>Python / TS / any]\n```\n\n**Layer 1 — MCP Host**: Your AI client (Claude Desktop, Cursor, etc.). Speaks the Model Context Protocol. protomcp is fully transparent to it.\n\n**Layer 2 — protomcp (Go binary)**: Bridges MCP and your tool process. Handles hot reload, tool list management, transport negotiation, and request routing. Stateless — restartable at any time.\n\n**Layer 3 — Tool Process**: Your code. Registers tool handlers using the SDK. Communicates with protomcp over a unix socket using a simple length-prefixed protobuf protocol.\n\n---\n\n## Startup Sequence\n\n```mermaid\nsequenceDiagram\n participant H as MCP Host\n participant G as protomcp\n participant T as Tool Process\n\n G->>T: spawn process\n G->>T: ListToolsRequest\n T->>G: ToolListResponse [add, greet]\n H->>G: tools/list\n G->>H: [add, greet]\n H->>G: tools/call add {a:1, b:2}\n G->>T: CallToolRequest {name:\"add\", arguments_json:...}\n T->>G: CallToolResponse {result_json:\"3\"}\n G->>H: result \"3\"\n```\n\n---\n\n## Hot Reload\n\nWhen you save your tool file, protomcp detects the change (via file watch) and:\n\n1. Sends a `ReloadRequest` to the tool process\n2. Tool process re-registers all tools and responds with `ReloadResponse`\n3. protomcp sends `notifications/tools/list_changed` to the MCP host\n4. Host re-fetches the tool list\n\nIn-flight calls complete before reload takes effect. With `--hot-reload immediate`, the process is restarted instead of asked to reload — useful for languages without dynamic reload support.\n\n```mermaid\nsequenceDiagram\n participant W as File Watcher\n participant G as protomcp\n participant T as Tool Process\n participant H as MCP Host\n\n W->>G: file changed\n G->>T: ReloadRequest\n T->>G: ReloadResponse {success: true}\n G->>H: notifications/tools/list_changed\n H->>G: tools/list\n G->>H: [updated tool list]\n```\n\n---\n\n## Dynamic Tool Lists\n\nTool processes can change their own tool list at runtime, without a file change. This lets tools enable or disable each other based on context:\n\n```mermaid\ngraph TD\n A[Tool call returns] -->|ToolResult.enable_tools| B[protomcp enables tools]\n A -->|ToolResult.disable_tools| C[protomcp disables tools]\n D[Tool calls tool_manager.enable] --> B\n D2[Tool calls tool_manager.disable] --> C\n B --> E[notifications/tools/list_changed sent to host]\n C --> E\n```\n\nTools can also use `tool_manager` (Python) or `toolManager` (TypeScript) to modify the tool list mid-call, or set an explicit allowlist/blocklist.\n\n---\n\n## Wire Protocol\n\nAll communication between protomcp and the tool process uses a simple framing:\n\n```\n[4 bytes: uint32 big-endian length][N bytes: serialized protobuf Envelope]\n```\n\nThe `Envelope` message wraps all request and response types in a `oneof` field. See the [Protobuf Spec](/protomcp/reference/protobuf-spec/) for full details.\n\n---\n\n## Transports\n\nprotomcp supports two transports for the MCP host side:\n\n| Transport | Flag | Use case |\n|-----------|------|----------|\n| stdio | `--transport stdio` | MCP clients that spawn subprocesses (default) |\n| Streamable HTTP | `--transport http` | Remote or web-based MCP clients |\n\nSee [Transports](/protomcp/concepts/transports/) for details.","src/content/docs/getting-started/how-it-works.mdx","c68c32d10ed8bebe","getting-started/how-it-works.mdx","index",{"id":42,"data":44,"body":68,"filePath":69,"digest":70,"legacyId":71,"deferredRender":16},{"title":45,"description":46,"editUrl":16,"head":47,"template":18,"hero":48,"sidebar":66,"pagefind":16,"draft":23},"protomcp","Language-agnostic MCP runtime — proxy MCP protocol to any tool process via protobuf over unix socket.",[],{"tagline":49,"actions":50},"Write MCP tools in any language. Hot-reload without restarting your AI host.",[51,58,62],{"text":52,"link":53,"variant":54,"icon":55},"See It In Action","/protomcp/demo/","primary",{"type":56,"name":57},"icon","right-arrow",{"text":59,"link":60,"variant":54,"icon":61},"Quick Start","/protomcp/getting-started/quick-start/",{"type":56,"name":57},{"text":32,"link":63,"variant":54,"icon":64},"/protomcp/getting-started/how-it-works/",{"type":56,"name":65},"open-book",{"hidden":23,"attrs":67},{},"import { Card, CardGrid } from '@astrojs/starlight/components';\nimport ArchitectureHero from '../../components/demo/ArchitectureHero.astro';\n\n\u003CArchitectureHero />\n\n## Why protomcp?\n\n\u003CCardGrid>\n \u003CCard title=\"Any Language\" icon=\"puzzle\">\n Write tools in Python, TypeScript, Go, Rust, or any language that can speak protobuf over a unix socket.\n \u003C/Card>\n \u003CCard title=\"Hot Reload\" icon=\"refresh\">\n Save your file and tools reload instantly — no need to restart Claude Desktop or your MCP host.\n \u003C/Card>\n \u003CCard title=\"Dynamic Tool Lists\" icon=\"list-format\">\n Tools can enable or disable themselves at runtime. Show only what's relevant based on context.\n \u003C/Card>\n \u003CCard title=\"Single Binary\" icon=\"rocket\">\n One Go binary. No daemon, no registry, no config files. Just `pmcp dev server.py`.\n \u003C/Card>\n\u003C/CardGrid>\n\n## How It Works\n\nprotomcp sits between your MCP host (Claude Desktop, etc.) and your tool process. It speaks MCP on one side and a simple protobuf protocol on the other.\n\n```\nMCP Host ←── MCP protocol ──→ protomcp (Go) ←── protobuf/unix socket ──→ tool process\n```\n\nYour tool process registers handlers using a decorator or function, and protomcp handles everything else: listing tools, routing calls, hot reload, and dynamic tool list management.\n\n## Get Started\n\n```sh\n# Install\nbrew install msilverblatt/tap/protomcp\n```\n\n```python\n# tools.py (Python)\nfrom protomcp import tool, ToolResult\n\n@tool(\"Add two numbers\")\ndef add(a: int, b: int) -> ToolResult:\n return ToolResult(result=str(a + b))\n```\n\n```typescript\n// tools.ts (TypeScript)\nimport { tool, ToolResult } from 'protomcp';\nimport { z } from 'zod';\n\ntool({\n description: 'Add two numbers',\n args: z.object({ a: z.number(), b: z.number() }),\n handler({ a, b }) {\n return new ToolResult({ result: String(a + b) });\n },\n});\n```\n\n```go\n// tools.go (Go)\npackage main\n\nimport (\n\t\"fmt\"\n\t\"github.com/msilverblatt/protomcp/sdk/go/protomcp\"\n)\n\nfunc main() {\n\tprotomcp.Tool(\"add\",\n\t\tprotomcp.Description(\"Add two numbers\"),\n\t\tprotomcp.Args(protomcp.IntArg(\"a\"), protomcp.IntArg(\"b\")),\n\t\tprotomcp.Handler(func(ctx protomcp.ToolContext, args map[string]interface{}) protomcp.ToolResult {\n\t\t\ta := int(args[\"a\"].(float64))\n\t\t\tb := int(args[\"b\"].(float64))\n\t\t\treturn protomcp.Result(fmt.Sprintf(\"%d\", a+b))\n\t\t}),\n\t)\n\tprotomcp.Run()\n}\n```\n\n```rust\n// src/main.rs (Rust)\nuse protomcp::{tool, ToolResult, ArgDef};\n\n#[tokio::main]\nasync fn main() {\n tool(\"add\")\n .description(\"Add two numbers\")\n .arg(ArgDef::int(\"a\"))\n .arg(ArgDef::int(\"b\"))\n .handler(|_ctx, args| {\n let a = args[\"a\"].as_i64().unwrap_or(0);\n let b = args[\"b\"].as_i64().unwrap_or(0);\n ToolResult::new(format!(\"{}\", a + b))\n })\n .register();\n protomcp::run().await;\n}\n```\n\n```sh\n# Python\npmcp dev tools.py\n\n# TypeScript\npmcp dev tools.ts\n\n# Go\npmcp dev tools.go\n\n# Rust\npmcp dev src/main.rs\n```\n\nThen add the command to your MCP client config. That's it.\n\n## Quick Links\n\n- [Installation](/protomcp/getting-started/installation/) — Homebrew, binary, from source\n- [Quick Start](/protomcp/getting-started/quick-start/) — 5-minute tutorial\n- [Python Guide](/protomcp/guides/writing-tools-python/) — `@tool()` decorator, `ToolResult`, `tool_manager`\n- [TypeScript Guide](/protomcp/guides/writing-tools-typescript/) — `tool()`, Zod schemas, `toolManager`\n- [Go Guide](/protomcp/guides/writing-tools-go/) — `Tool()`, functional options, `ToolResult`\n- [Rust Guide](/protomcp/guides/writing-tools-rust/) — `tool()` builder, `ToolResult`, `ArgDef`\n- [Custom Middleware](/protomcp/guides/middleware/) — intercept tool calls with before/after hooks\n- [Authentication](/protomcp/guides/auth/) — token and API key auth for network transports\n- [CLI Reference](/protomcp/reference/cli/) — all commands and flags\n- [Protobuf Spec](/protomcp/reference/protobuf-spec/) — wire protocol","src/content/docs/index.mdx","a89c8aa1d57ebea4","index.mdx","concepts/architecture",{"id":72,"data":74,"body":80,"filePath":81,"digest":82,"legacyId":83,"deferredRender":16},{"title":75,"description":76,"editUrl":16,"head":77,"template":35,"sidebar":78,"pagefind":16,"draft":23},"Architecture","Deep dive into the protomcp architecture.",[],{"hidden":23,"attrs":79},{},"## Overview\n\nprotomcp is a Go binary that acts as a protocol bridge. On one side it speaks the Model Context Protocol (MCP) to an AI host. On the other it speaks a simple protobuf protocol over a unix socket to your tool process.\n\n```mermaid\ngraph LR\n subgraph host [\"MCP Host\"]\n H[Claude Desktop\u003Cbr/>Cursor\u003Cbr/>etc.]\n end\n subgraph binary [\"protomcp (Go)\"]\n T[Transport Layer]\n R[Router]\n TL[Tool List Manager]\n W[File Watcher]\n end\n subgraph process [\"Tool Process\"]\n SDK[SDK\u003Cbr/>Python / TS / any]\n Handlers[Tool Handlers]\n end\n H \u003C-->|MCP| T\n T --> R\n R \u003C--> TL\n R \u003C-->|protobuf / unix socket| SDK\n W -->|file changed| R\n SDK --> Handlers\n```\n\n---\n\n## Components\n\n### Transport Layer\n\nHandles the MCP host connection. protomcp supports two transports, selected with `--transport`, provided by the official MCP Go SDK:\n\n- **stdio**: Reads MCP from stdin, writes to stdout. The host spawns protomcp as a child process.\n- **Streamable HTTP**: HTTP transport. Supports remote and web-based MCP clients.\n\nThe transport is transparent to the tool process. Your tools work the same regardless of which transport the host uses.\n\n### Router\n\nReceives MCP requests from the transport and dispatches them to the tool process. Key behaviors:\n\n- **tools/list**: Returns the current active tool list from the Tool List Manager.\n- **tools/call**: Forwards the call to the tool process, waits for `CallToolResponse`, then updates the tool list if the response includes `enable_tools`/`disable_tools`.\n- **Reload**: On file change signal from the File Watcher, sends `ReloadRequest` to the tool process, waits for `ReloadResponse`, then sends `notifications/tools/list_changed` to the host.\n\n### Tool List Manager\n\nMaintains the set of active tools and enforces the current mode (open, allowlist, or blocklist). See [Tool List Modes](/protomcp/concepts/tool-list-modes/).\n\n### File Watcher\n\nMonitors the tool file for changes (dev mode only). Uses OS-level file notifications. When a change is detected, signals the Router to trigger a reload.\n\n---\n\n## Process lifecycle\n\n```mermaid\nsequenceDiagram\n participant CLI\n participant G as protomcp\n participant W as File Watcher\n participant T as Tool Process\n\n CLI->>G: pmcp dev tools.py (or tools.ts)\n G->>T: spawn tool process\n G->>T: ListToolsRequest\n T->>G: ToolListResponse [add, greet]\n G-->>CLI: listening on stdio\n Note over W: watching tools.py\n W->>G: file changed\n G->>T: ReloadRequest\n T->>G: ReloadResponse {success: true}\n G->>G: update tool list\n```\n\n---\n\n## Unix socket protocol\n\nThe unix socket is created by protomcp at startup. The tool process connects to it (the SDK handles this automatically). All messages use the length-prefixed protobuf framing:\n\n```\n┌─────────────────────────────────────────────────────┐\n│ 4 bytes (uint32, big-endian): message length N │\n├─────────────────────────────────────────────────────┤\n│ N bytes: serialized protobuf Envelope │\n└─────────────────────────────────────────────────────┘\n```\n\nThe `Envelope` uses a `oneof msg` field to carry all message types. See the [Protobuf Spec](/protomcp/reference/protobuf-spec/).\n\n---\n\n## Tool call flow\n\n```mermaid\nsequenceDiagram\n participant H as MCP Host\n participant G as protomcp\n participant T as Tool Process\n\n H->>G: tools/call {name:\"add\", arguments:{a:1,b:2}}\n G->>T: CallToolRequest {name:\"add\", arguments_json:'{\"a\":1,\"b\":2}'}\n T->>G: CallToolResponse {result_json:'\"3\"', enable_tools:[]}\n G->>G: update tool list (if enable/disable tools set)\n G->>H: result \"3\"\n```\n\n---\n\n## Reload flow\n\n```mermaid\nsequenceDiagram\n participant W as File Watcher\n participant G as protomcp\n participant T as Tool Process\n participant H as MCP Host\n\n W->>G: file changed\n G->>T: ReloadRequest\n T->>T: re-register all tools\n T->>G: ReloadResponse {success:true}\n G->>G: update tool list\n G->>H: notifications/tools/list_changed\n H->>G: tools/list\n G->>H: [updated list]\n```\n\nIn-flight calls are not interrupted. If a call is in progress when a reload is triggered, the reload is queued and applied after the call completes.\n\n---\n\n## Statelessness\n\nprotomcp is stateless with respect to your tool logic. It only tracks:\n\n- The current tool list (active set + mode)\n- In-flight request correlation IDs\n\nIf protomcp is restarted, it reconnects to the tool process (or restarts it) and re-fetches the tool list. The tool list state is rebuilt from the tool process's `ToolListResponse`.","src/content/docs/concepts/architecture.mdx","ecce3795a29bfffa","concepts/architecture.mdx","getting-started/quick-start",{"id":84,"data":86,"body":91,"filePath":92,"digest":93,"legacyId":94,"deferredRender":16},{"title":59,"description":87,"editUrl":16,"head":88,"template":35,"sidebar":89,"pagefind":16,"draft":23},"Build and run your first MCP tool in 5 minutes.",[],{"hidden":23,"attrs":90},{},"import { Steps } from '@astrojs/starlight/components';\n\n\u003CSteps>\n\n1. **Install protomcp**\n\n ```sh\n brew install msilverblatt/tap/protomcp\n ```\n\n2. **Install the SDK**\n\n ```sh\n # Python\n pip install protomcp\n\n # TypeScript\n npm install protomcp zod\n\n # Go\n go get github.com/msilverblatt/protomcp/sdk/go\n\n # Rust\n cargo add protomcp tokio serde_json\n ```\n\n3. **Write a tool file**\n\n Create `tools.py` (Python):\n\n ```python\n from protomcp import tool, ToolResult\n\n @tool(\"Add two numbers together\")\n def add(a: int, b: int) -> ToolResult:\n return ToolResult(result=str(a + b))\n\n @tool(\"Greet a user by name\")\n def greet(name: str) -> ToolResult:\n return ToolResult(result=f\"Hello, {name}!\")\n ```\n\n Or create `tools.ts` (TypeScript):\n\n ```typescript\n import { tool, ToolResult } from 'protomcp';\n import { z } from 'zod';\n\n tool({\n description: 'Add two numbers together',\n args: z.object({ a: z.number(), b: z.number() }),\n handler: function add({ a, b }) {\n return new ToolResult({ result: String(a + b) });\n },\n });\n\n tool({\n description: 'Greet a user by name',\n args: z.object({ name: z.string() }),\n handler: function greet({ name }) {\n return new ToolResult({ result: `Hello, ${name}!` });\n },\n });\n ```\n\n4. **Run in dev mode**\n\n ```sh\n # Python\n pmcp dev tools.py\n\n # TypeScript\n pmcp dev tools.ts\n ```\n\n You should see:\n\n ```\n [protomcp] listening on stdio\n [protomcp] loaded 2 tools: add, greet\n ```\n\n5. **Configure your MCP client**\n\n Add to your MCP client config (e.g. Claude Desktop `claude_desktop_config.json`):\n\n ```json\n {\n \"mcpServers\": {\n \"mytools\": {\n \"command\": \"pmcp\",\n \"args\": [\"dev\", \"/absolute/path/to/tools.py\"]\n }\n }\n }\n ```\n\n Use the full path to your tool file (`.py` or `.ts`). Restart your MCP client. The tools `add` and `greet` will appear.\n\n6. **Try hot reload**\n\n While the MCP client is running, add a new tool to your file:\n\n ```python\n # Python\n @tool(\"Reverse a string\")\n def reverse(text: str) -> ToolResult:\n return ToolResult(result=text[::-1])\n ```\n\n ```typescript\n // TypeScript\n tool({\n description: 'Reverse a string',\n args: z.object({ text: z.string() }),\n handler: function reverse({ text }) {\n return new ToolResult({ result: text.split('').reverse().join('') });\n },\n });\n ```\n\n Save the file. protomcp reloads automatically — no restart needed. The new tool appears in your client within seconds.\n\n\u003C/Steps>\n\n## Next steps\n\n- [Writing Tools (Python)](/protomcp/guides/writing-tools-python/) — type hints, optional params, progress, logging\n- [Writing Tools (TypeScript)](/protomcp/guides/writing-tools-typescript/) — Zod schemas, async handlers, progress, logging\n- [Writing Tools (Go)](/protomcp/guides/writing-tools-go/) — functional options, `ToolResult`, progress\n- [Writing Tools (Rust)](/protomcp/guides/writing-tools-rust/) — builder pattern, `ArgDef`, progress\n- [Dynamic Tool Lists](/protomcp/guides/dynamic-tool-lists/) — enable/disable tools at runtime\n- [Custom Middleware](/protomcp/guides/middleware/) — intercept tool calls with before/after hooks\n- [CLI Reference](/protomcp/reference/cli/) — all flags and options","src/content/docs/getting-started/quick-start.mdx","f4af66cd885674fc","getting-started/quick-start.mdx","reference/cli",{"id":95,"data":97,"body":103,"filePath":104,"digest":105,"legacyId":106,"deferredRender":16},{"title":98,"description":99,"editUrl":16,"head":100,"template":35,"sidebar":101,"pagefind":16,"draft":23},"CLI Reference","All protomcp commands and flags.",[],{"hidden":23,"attrs":102},{},"## Commands\n\n### `pmcp dev \u003Cfile> [flags]`\n\nStart protomcp in development mode with hot reload.\n\n```sh\n# Python\npmcp dev tools.py\npmcp dev tools.py --transport http --port 8080\n\n# TypeScript\npmcp dev tools.ts\npmcp dev tools.ts --transport http --port 8080\n```\n\nHot reload is enabled: when `\u003Cfile>` changes on disk, protomcp reloads the tool process and notifies the MCP host.\n\n### `pmcp run \u003Cfile> [flags]`\n\nStart protomcp in production mode without hot reload.\n\n```sh\n# Python\npmcp run tools.py\npmcp run tools.py --transport http --host 0.0.0.0 --port 8080\n\n# TypeScript\npmcp run tools.ts\npmcp run tools.ts --transport http --host 0.0.0.0 --port 8080\n```\n\n### `pmcp validate \u003Cfile> [flags]`\n\nValidate tool definitions without starting the server. Spawns the tool process, collects tool definitions, validates them, and exits.\n\n```sh\n# Basic validation\npmcp validate tools.py\n\n# Strict mode (enforces description length, generic name detection)\npmcp validate tools.py --strict\n\n# JSON output\npmcp validate tools.py --format json\n```\n\n| Flag | Default | Description |\n|------|---------|-------------|\n| `--strict` | `false` | Enable strict validation (min 10 char descriptions, generic name detection) |\n| `--format` | `text` | Output format: `text` or `json` |\n\nValidation checks:\n- Tool name format: must match `^[a-zA-Z_][a-zA-Z0-9_]*$`\n- No duplicate tool names\n- All tools have descriptions\n- Input schema is valid JSON Schema\n- **Strict mode**: description at least 10 characters, no generic names like \"tool1\"\n\n---\n\n## Flags\n\n### `--auth \u003Cscheme:ENV_VAR>`\n\n**Repeatable.** Configure authentication for the HTTP transport (`--transport http`). Skipped for stdio.\n\n| Scheme | Header | Format |\n|--------|--------|--------|\n| `token` | `Authorization` | `Bearer \u003Cvalue>` |\n| `apikey` | `X-API-Key` | `\u003Cvalue>` |\n\n```sh\n# Require Bearer token\npmcp run tools.py --transport http --auth token:MY_TOKEN\n\n# Require API key\npmcp run tools.py --transport http --auth apikey:SERVICE_KEY\n\n# Multiple (all must pass)\npmcp run tools.py --transport http --auth token:MY_TOKEN --auth apikey:SERVICE_KEY\n```\n\nThe environment variable must be set at startup. See [Authentication](/protomcp/guides/auth/) for details.\n\n### `--transport \u003Cvalue>`\n\n**Default:** `stdio`\n\nThe transport to use for the MCP host connection.\n\n| Value | Description |\n|-------|-------------|\n| `stdio` | Read/write on stdin/stdout. Used when the MCP client spawns protomcp as a subprocess. |\n| `http` | Streamable HTTP transport. MCP client connects over HTTP. |\n\n```sh\n# Python\npmcp dev tools.py --transport http\n\n# TypeScript\npmcp dev tools.ts --transport http\n```\n\n### `--host \u003Cvalue>`\n\n**Default:** `localhost`\n\nHost/address to bind the HTTP server to. Used with `--transport http`.\n\n```sh\n# Python\npmcp run tools.py --transport http --host 0.0.0.0\n\n# TypeScript\npmcp run tools.ts --transport http --host 0.0.0.0\n```\n\n### `--port \u003Cvalue>`\n\n**Default:** `8080`\n\nPort to bind the HTTP server to. Used with `--transport http`.\n\n```sh\n# Python\npmcp run tools.py --transport http --port 9000\n\n# TypeScript\npmcp run tools.ts --transport http --port 9000\n```\n\n### `--hot-reload \u003Cvalue>`\n\n**Default:** *(graceful reload)*\n\nControls the hot reload strategy. Only applies in `dev` mode.\n\n| Value | Description |\n|-------|-------------|\n| `immediate` | Kill and restart the tool process on every file change. |\n| *(omitted)* | Send a `ReloadRequest` to the running process (graceful). |\n\n```sh\n# Python\npmcp dev tools.py --hot-reload immediate\n\n# TypeScript\npmcp dev tools.ts --hot-reload immediate\n```\n\n### `--call-timeout \u003Cduration>`\n\n**Default:** `5m`\n\nMaximum time to wait for a tool call to complete. Uses Go duration syntax.\n\n```sh\n# Python\npmcp run tools.py --call-timeout 30s\npmcp run tools.py --call-timeout 10m\n\n# TypeScript\npmcp run tools.ts --call-timeout 30s\npmcp run tools.ts --call-timeout 10m\n```\n\n### `--log-level \u003Cvalue>`\n\n**Default:** `info`\n\nLogging verbosity.\n\n| Value | Description |\n|-------|-------------|\n| `debug` | All log messages including internal state |\n| `info` | Normal operational messages |\n| `warn` | Warnings and errors only |\n| `error` | Errors only |\n\n```sh\n# Python\npmcp dev tools.py --log-level debug\n\n# TypeScript\npmcp dev tools.ts --log-level debug\n```\n\n### `--socket \u003Cpath>`\n\n**Default:** `$XDG_RUNTIME_DIR/protomcp/\u003Cpid>.sock` or `$TMPDIR/protomcp/\u003Cpid>.sock`\n\nPath for the unix socket used to communicate with the tool process.\n\n```sh\n# Python\npmcp dev tools.py --socket /tmp/mytools.sock\n\n# TypeScript\npmcp dev tools.ts --socket /tmp/mytools.sock\n```\n\n### `--runtime \u003Ccommand>`\n\nOverride the runtime used to execute the tool file. By default, protomcp detects the runtime from the file extension.\n\n```sh\n# Override Python version\npmcp dev tools.py --runtime python3.12\n\n# Override TypeScript runtime\npmcp dev tools.ts --runtime bun\n```\n\n---\n\n## Runtime detection\n\nWhen `--runtime` is not set, protomcp picks the runtime based on file extension:\n\n| Extension | Default command | Override env var |\n|-----------|----------------|-----------------|\n| `.py` | `python3` | `PROTOMCP_PYTHON` |\n| `.ts` | `npx tsx` | `PROTOMCP_NODE` |\n| `.js` | `node` | `PROTOMCP_NODE` |\n| `.go` | `go run` | — |\n| `.rs` | `cargo run` | — |\n| other | run directly | — |\n\n```sh\n# Use a specific Python version\nPROTOMCP_PYTHON=python3.12 pmcp dev tools.py\n\n# Use bun instead of node for TypeScript\nPROTOMCP_NODE=bun pmcp dev tools.ts\n```","src/content/docs/reference/cli.mdx","1d16a8aee131fd48","reference/cli.mdx","getting-started/installation",{"id":107,"data":109,"body":115,"filePath":116,"digest":117,"legacyId":118,"deferredRender":16},{"title":110,"description":111,"editUrl":16,"head":112,"template":35,"sidebar":113,"pagefind":16,"draft":23},"Installation","Install protomcp and the Python, TypeScript, Go, or Rust SDK.",[],{"hidden":23,"attrs":114},{},"## protomcp binary\n\n### Homebrew (macOS / Linux)\n\n```sh\nbrew install msilverblatt/tap/protomcp\n```\n\n### Binary download\n\nDownload the latest release from [GitHub Releases](https://github.com/msilverblatt/protomcp/releases) and place the binary on your `PATH`:\n\n```sh\n# macOS (Apple Silicon)\ncurl -L https://github.com/msilverblatt/protomcp/releases/latest/download/protomcp_darwin_arm64.tar.gz | tar xz\nsudo mv protomcp /usr/local/bin/\n\n# Linux (amd64)\ncurl -L https://github.com/msilverblatt/protomcp/releases/latest/download/protomcp_linux_amd64.tar.gz | tar xz\nsudo mv protomcp /usr/local/bin/\n```\n\n### From source\n\nRequires Go 1.22+.\n\n```sh\ngit clone https://github.com/msilverblatt/protomcp.git\ncd protomcp\ngo build -o protomcp ./cmd/protomcp\nsudo mv protomcp /usr/local/bin/\n```\n\n### Verify\n\n```sh\nprotomcp --version\n```\n\n---\n\n## Python SDK\n\nRequires Python 3.10+.\n\n```sh\npip install protomcp\n```\n\nOr with a virtual environment:\n\n```sh\npython -m venv .venv\nsource .venv/bin/activate\npip install protomcp\n```\n\n---\n\n## TypeScript SDK\n\nRequires Node.js 18+.\n\n```sh\nnpm install protomcp\n```\n\nOr with yarn / pnpm:\n\n```sh\nyarn add protomcp\npnpm add protomcp\n```\n\n---\n\n## Go SDK\n\nRequires Go 1.22+.\n\n```sh\ngo get github.com/msilverblatt/protomcp/sdk/go\n```\n\n---\n\n## Rust SDK\n\nRequires Rust 1.70+ and `protoc` (protobuf compiler).\n\n```sh\ncargo add protomcp tokio --features tokio/full\ncargo add serde_json\n```\n\nInstall protoc if not already available:\n\n```sh\n# macOS\nbrew install protobuf\n\n# Linux (apt)\nsudo apt install -y protobuf-compiler\n```\n\n---\n\n## Runtime detection\n\nprotomcp detects the runtime from the file extension automatically:\n\n| Extension | Runtime |\n|-----------|---------|\n| `.py` | `python3` (or `$PROTOMCP_PYTHON`) |\n| `.ts` | `npx tsx` (or `$PROTOMCP_NODE`) |\n| `.js` | `node` (or `$PROTOMCP_NODE`) |\n| `.go` | `go run` |\n\nUse `--runtime` to override: `pmcp dev tools.py --runtime python3.12`","src/content/docs/getting-started/installation.mdx","b8f84994be99869d","getting-started/installation.mdx","concepts/transports",{"id":119,"data":121,"body":127,"filePath":128,"digest":129,"legacyId":130,"deferredRender":16},{"title":122,"description":123,"editUrl":16,"head":124,"template":35,"sidebar":125,"pagefind":16,"draft":23},"Transports","protomcp transports — stdio and Streamable HTTP via the official MCP Go SDK.",[],{"hidden":23,"attrs":126},{},"## Overview\n\nprotomcp supports two transports, both implemented by the [official MCP Go SDK](https://github.com/modelcontextprotocol/go-sdk). The transport determines how your MCP client connects to protomcp. Your server code is unaffected — it always communicates with protomcp via the same unix socket protocol.\n\nSet the transport with `--transport`:\n\n```sh\npmcp dev server.py --transport http --port 8080\n```\n\n---\n\n## stdio (default)\n\n**Flag:** `--transport stdio`\n\nReads MCP requests from stdin, writes responses to stdout. The MCP client spawns protomcp as a child process and communicates over its standard I/O.\n\n**Use when:** Your MCP client supports subprocess spawning (Claude Desktop, Cursor, most local clients).\n\n**Config:**\n\n```json\n{\n \"mcpServers\": {\n \"mytools\": {\n \"command\": \"pmcp\",\n \"args\": [\"dev\", \"/path/to/server.py\"]\n }\n }\n}\n```\n\nNo `--host` or `--port` needed. stdio is the simplest and most compatible option.\n\n---\n\n## Streamable HTTP\n\n**Flag:** `--transport http`\n\nRuns an HTTP server using the official SDK's `StreamableHTTPHandler`. Supports session management, multiple concurrent clients, and SSE-based streaming responses. This is the modern MCP HTTP transport that replaces the older SSE transport.\n\n**Use when:**\n- Your MCP client connects over HTTP instead of spawning a subprocess\n- You need to handle multiple concurrent MCP clients\n- You're running protomcp on a remote server\n- Load balancers or proxies are in the path\n\n```sh\npmcp run server.py --transport http --host 0.0.0.0 --port 8080\n```\n\nMCP client config:\n\n```json\n{\n \"mcpServers\": {\n \"mytools\": {\n \"url\": \"http://myserver:8080/mcp\"\n }\n }\n}\n```\n\n---\n\n## Compatibility matrix\n\n| Transport | MCP clients | Hot reload | Multiple clients | Session management |\n|-----------|-------------|------------|-----------------|-------------------|\n| stdio | Claude Desktop, Cursor, most local | Yes | No (one client) | Single session |\n| http | Modern HTTP clients | Yes | Yes | Per-session via `Mcp-Session-Id` |\n\n---\n\n## Host and port flags\n\n`--host` and `--port` only apply to the HTTP transport:\n\n| Flag | Default | Description |\n|------|---------|-------------|\n| `--host` | `localhost` | Address to bind |\n| `--port` | `8080` | Port to bind |\n\nFor local-only access, use the default `localhost`. For external access:\n\n```sh\npmcp run server.py --transport http --host 0.0.0.0 --port 8080\n```","src/content/docs/concepts/transports.mdx","658b30c9fb5ecba0","concepts/transports.mdx","reference/mcp-compliance",{"id":131,"data":133,"body":139,"filePath":140,"digest":141,"legacyId":142,"deferredRender":16},{"title":134,"description":135,"editUrl":16,"head":136,"template":35,"sidebar":137,"pagefind":16,"draft":23},"MCP Spec Compliance","How protomcp maps to every feature in the MCP specification.",[],{"hidden":23,"attrs":138},{},"protomcp implements the MCP specification via the [official Go SDK](https://github.com/modelcontextprotocol/go-sdk). This page maps every MCP spec feature to its protomcp implementation.\n\n## Protocol\n\n| Spec Feature | Status | Implementation |\n|-------------|--------|----------------|\n| Protocol version negotiation | Automatic | Official SDK handles `initialize` handshake |\n| JSON-RPC 2.0 | Automatic | Official SDK |\n| Protocol version 2025-03-26 | Supported | Default version |\n\n## Server Features\n\n### Tools\n\n| Method | Status | Notes |\n|--------|--------|-------|\n| `tools/list` | Supported | Registered via `@tool` / `tool()` in each SDK |\n| `tools/call` | Supported | Proxy through bridge to SDK process |\n| Tool annotations | Supported | `destructive`, `readOnly`, `idempotent`, `openWorld` hints |\n| Structured output | Supported | Output schemas via `output_type` parameter |\n| `notifications/tools/list_changed` | Supported | Sent on hot reload and dynamic enable/disable |\n\n### Resources\n\n| Method | Status | Notes |\n|--------|--------|-------|\n| `resources/list` | Supported | Registered via `@resource` / `resource()` |\n| `resources/read` | Supported | Handler receives URI, returns content |\n| `resources/templates/list` | Supported | Registered via `@resource_template` / `resourceTemplate()` |\n| `resources/subscribe` | Supported | Via official SDK `SubscribeHandler` |\n| `notifications/resources/list_changed` | Supported | Sent on hot reload |\n\n### Prompts\n\n| Method | Status | Notes |\n|--------|--------|-------|\n| `prompts/list` | Supported | Registered via `@prompt` / `prompt()` |\n| `prompts/get` | Supported | Handler receives arguments, returns messages |\n| `notifications/prompts/list_changed` | Supported | Sent on hot reload |\n\n### Completions\n\n| Method | Status | Notes |\n|--------|--------|-------|\n| `completion/complete` | Supported | Registered via `@completion` / `completion()` |\n\n### Sampling\n\n| Method | Status | Notes |\n|--------|--------|-------|\n| `sampling/createMessage` | Supported | SDK processes can request LLM calls from client |\n\n### Roots\n\n| Method | Status | Notes |\n|--------|--------|-------|\n| `roots/list` | Supported | SDK processes can request client roots |\n| `notifications/roots/list_changed` | Supported | Bridge handles change notifications |\n\n### Logging\n\n| Method | Status | Notes |\n|--------|--------|-------|\n| `logging/setLevel` | Supported | Official SDK tracks per-session log level |\n| Log messages | Supported | `log.info()`, `log.warning()`, etc. in each SDK |\n\n### Utilities\n\n| Method | Status | Notes |\n|--------|--------|-------|\n| `ping` | Supported | Official SDK handles automatically |\n| `notifications/progress` | Supported | `ctx.report_progress()` in each SDK |\n| `notifications/cancelled` | Supported | Official SDK handles cancellation |\n| Pagination | Supported | Cursor-based, automatic for list operations |\n\n## Transports\n\n| Transport | Status | Notes |\n|-----------|--------|-------|\n| stdio | Supported | Default. `mcp.StdioTransport` from official SDK |\n| Streamable HTTP | Supported | `mcp.StreamableHTTPHandler` with session management |\n\n## Content Types\n\n| Type | Status |\n|------|--------|\n| TextContent | Supported |\n| ImageContent | Supported |\n| AudioContent | Supported |\n| EmbeddedResource | Supported |\n| ResourceLink | Supported |\n\n## SDK Feature Matrix\n\nWhich features are available in each language SDK:\n\n| Feature | Python | TypeScript | Go | Rust |\n|---------|--------|------------|-----|------|\n| Tools | `@tool` | `tool()` | `Tool()` | `tool()` |\n| Resources | `@resource` | `resource()` | `RegisterResource()` | `register_resource()` |\n| Resource Templates | `@resource_template` | `resourceTemplate()` | `RegisterResourceTemplate()` | `register_resource_template()` |\n| Prompts | `@prompt` | `prompt()` | `RegisterPrompt()` | `register_prompt()` |\n| Completions | `@completion` | `completion()` | `RegisterCompletion()` | `register_completion()` |\n| Progress | `ctx.report_progress()` | `ctx.reportProgress()` | `ctx.ReportProgress()` | `ctx.report_progress()` |\n| Logging | `log.info()` | `log.info()` | `Log.Info()` | `log::info!()` |\n| Cancellation | `ctx.is_cancelled()` | `ctx.isCancelled()` | `ctx.Done()` | `ctx.is_cancelled()` |\n| Tool management | `tool_manager` | `toolManager` | `ToolManager` | N/A |\n| Sampling | `ctx.sample()` | `ctx.sample()` | `ctx.Sample()` | `ctx.sample()` |","src/content/docs/reference/mcp-compliance.mdx","069bb5bc49216ae0","reference/mcp-compliance.mdx","concepts/tool-list-modes",{"id":143,"data":145,"body":151,"filePath":152,"digest":153,"legacyId":154,"deferredRender":16},{"title":146,"description":147,"editUrl":16,"head":148,"template":35,"sidebar":149,"pagefind":16,"draft":23},"Tool List Modes","How the open, allowlist, and blocklist modes control which tools are visible.",[],{"hidden":23,"attrs":150},{},"## Overview\n\nThe Tool List Manager in protomcp tracks which tools are currently visible to the MCP host. It operates in one of three modes:\n\n| Mode | What's visible |\n|------|---------------|\n| **Open** | All registered tools, minus any explicitly disabled |\n| **Allowlist** | Only the tools in the allowlist |\n| **Blocklist** | All registered tools, minus the tools in the blocklist |\n\n---\n\n## Open mode (default)\n\nAt startup, the tool list is in open mode. All tools returned in the `ToolListResponse` are active.\n\nEnable/disable operations in open mode track deltas:\n\n- `enable([\"a\", \"b\"])` — adds `a` and `b` to the active set (removes from disabled set)\n- `disable([\"c\"])` — removes `c` from the active set (adds to disabled set)\n\n```python\n# Open mode: all tools active\n# Disable specific tools\ntool_manager.disable([\"dangerous_delete\"])\n# Re-enable later\ntool_manager.enable([\"dangerous_delete\"])\n```\n\n---\n\n## Allowlist mode\n\nCall `set_allowed` to switch to allowlist mode. Only the tools you specify are active. All others are hidden.\n\n```python\n# Switch to allowlist: only these two tools are visible\ntool_manager.set_allowed([\"read_file\", \"search\"])\n```\n\nUse allowlist mode when you want to start with a minimal surface area and explicitly opt in to each tool.\n\nTo add tools to an existing allowlist, call `enable`:\n\n```python\ntool_manager.enable([\"write_file\"]) # adds write_file to the allowlist\n```\n\nTo remove tools from the allowlist, call `disable`:\n\n```python\ntool_manager.disable([\"read_file\"]) # removes read_file from allowlist\n```\n\n---\n\n## Blocklist mode\n\nCall `set_blocked` to switch to blocklist mode. All registered tools are active except those you specify.\n\n```python\n# Switch to blocklist: everything except these is visible\ntool_manager.set_blocked([\"nuclear_launch\", \"format_disk\"])\n```\n\nUse blocklist mode when you have many tools and want to hide a few dangerous or irrelevant ones.\n\nTo add more tools to the blocklist, call `disable`:\n\n```python\ntool_manager.disable([\"another_dangerous_tool\"])\n```\n\nTo remove tools from the blocklist, call `enable`:\n\n```python\ntool_manager.enable([\"nuclear_launch\"]) # removes from blocklist (becomes visible again)\n```\n\n---\n\n## State diagram\n\n```mermaid\nstateDiagram-v2\n [*] --> Open: startup\n\n Open --> Open: enable / disable\n Open --> Allowlist: set_allowed\n Open --> Blocklist: set_blocked\n\n Allowlist --> Allowlist: enable (add to allowlist)\\ndisable (remove from allowlist)\n Allowlist --> Open: set_allowed([]) — empty allowlist clears mode\n Allowlist --> Blocklist: set_blocked\n\n Blocklist --> Blocklist: enable (remove from blocklist)\\ndisable (add to blocklist)\n Blocklist --> Open: set_blocked([]) — empty blocklist clears mode\n Blocklist --> Allowlist: set_allowed\n```\n\n---\n\n## Batch operations\n\nThe `batch` method lets you perform multiple operations in one round-trip. All operations are applied in order:\n\n1. `enable` — enable tools\n2. `disable` — disable tools\n3. `allow` — switch to allowlist with these tools\n4. `block` — switch to blocklist with these tools\n\n```python\n# Enable some, disable others, atomically\ntool_manager.batch(\n enable=[\"write_file\"],\n disable=[\"read_only_hint\"],\n)\n\n# Switch to allowlist with a single call\ntool_manager.batch(allow=[\"read_file\", \"search\"])\n```\n\n---\n\n## Effect on MCP host\n\nWhenever the active tool list changes (from enable/disable/set_allowed/set_blocked), protomcp sends `notifications/tools/list_changed` to the MCP host. The host then re-fetches the tool list with `tools/list`, and the updated set of tools becomes available in the conversation.\n\nThis is how tools can \"appear\" or \"disappear\" mid-conversation based on context.","src/content/docs/concepts/tool-list-modes.mdx","586310d050f33532","concepts/tool-list-modes.mdx","reference/python-api",{"id":155,"data":157,"body":163,"filePath":164,"digest":165,"legacyId":166,"deferredRender":16},{"title":158,"description":159,"editUrl":16,"head":160,"template":35,"sidebar":161,"pagefind":16,"draft":23},"Python API Reference","Complete API reference for the protomcp Python SDK.",[],{"hidden":23,"attrs":162},{},"## Installation\n\n```sh\npip install protomcp\n```\n\n## Imports\n\n```python\nfrom protomcp import tool, ToolResult, tool_manager\nfrom protomcp.context import ToolContext\nfrom protomcp.log import ServerLogger\nfrom protomcp.group import tool_group, action\nfrom protomcp.local_middleware import local_middleware\nfrom protomcp.server_context import server_context\nfrom protomcp.telemetry import telemetry_sink, ToolCallEvent\nfrom protomcp.sidecar import sidecar\nfrom protomcp.workflow import workflow, step, StepResult, get_registered_workflows, clear_workflow_registry\nfrom protomcp.discovery import configure\n```\n\n---\n\n## `@tool(...)`\n\nRegisters a function as an MCP tool.\n\n**Parameters:**\n\n| Parameter | Type | Default | Description |\n|-----------|------|---------|-------------|\n| `description` | `str` | required | Human-readable description of what the tool does |\n| `output_type` | dataclass type | `None` | Dataclass type used to generate the structured output JSON Schema |\n| `title` | `str` | `\"\"` | Display name shown in the MCP host UI |\n| `destructive` | `bool` | `False` | Hint: the tool has destructive side effects |\n| `idempotent` | `bool` | `False` | Hint: calling the tool multiple times has the same effect as once |\n| `read_only` | `bool` | `False` | Hint: the tool does not modify state |\n| `open_world` | `bool` | `False` | Hint: the tool may access resources outside the current context |\n| `task_support` | `bool` | `False` | Hint: the tool supports long-running async task semantics |\n\n**Returns:** The original function (unmodified).\n\nThe function name becomes the tool name. Type hints on parameters are used to generate the JSON Schema for tool inputs.\n\n```python\nfrom protomcp import tool, ToolResult\n\n@tool(\"Multiply two numbers\", title=\"Multiply\", read_only=True)\ndef multiply(a: float, b: float) -> ToolResult:\n return ToolResult(result=str(a * b))\n```\n\n### Supported parameter types\n\n| Python type | JSON Schema |\n|-------------|-------------|\n| `str` | `{\"type\": \"string\"}` |\n| `int` | `{\"type\": \"integer\"}` |\n| `float` | `{\"type\": \"number\"}` |\n| `bool` | `{\"type\": \"boolean\"}` |\n| `list` | `{\"type\": \"array\"}` |\n| `dict` | `{\"type\": \"object\"}` |\n| `list[T]` | `{\"type\": \"array\", \"items\": \u003CT schema>}` |\n| `dict[K, V]` | `{\"type\": \"object\", \"additionalProperties\": \u003CV schema>}` |\n| `str \\| int` / `Union[str, int]` | `{\"anyOf\": [{\"type\": \"string\"}, {\"type\": \"integer\"}]}` |\n| `Optional[T]` | type of `T`, not required |\n| `Literal[\"a\", \"b\"]` | `{\"type\": \"string\", \"enum\": [\"a\", \"b\"]}` |\n\nParameters without defaults and without `Optional` are added to the `required` array. Parameters with `Optional[T]` or a default value are optional.\n\n### Skipped parameters\n\nParameters named `self`, `cls`, or `ctx` are skipped during schema generation. Parameters with type `ToolContext` (if present) are also skipped.\n\n---\n\n## `ToolResult`\n\nReturned by every tool handler.\n\n```python\nfrom dataclasses import dataclass\nfrom typing import Optional\n\n@dataclass\nclass ToolResult:\n result: str = \"\"\n is_error: bool = False\n enable_tools: Optional[list[str]] = None\n disable_tools: Optional[list[str]] = None\n error_code: Optional[str] = None\n message: Optional[str] = None\n suggestion: Optional[str] = None\n retryable: bool = False\n```\n\n### Fields\n\n| Field | Type | Default | Description |\n|-------|------|---------|-------------|\n| `result` | `str` | `\"\"` | The result string returned to the MCP host |\n| `is_error` | `bool` | `False` | Set to `True` to indicate an error |\n| `enable_tools` | `list[str] \\| None` | `None` | Tool names to enable after this call |\n| `disable_tools` | `list[str] \\| None` | `None` | Tool names to disable after this call |\n| `error_code` | `str \\| None` | `None` | Machine-readable error code |\n| `message` | `str \\| None` | `None` | Human-readable error message |\n| `suggestion` | `str \\| None` | `None` | Recovery suggestion for the AI or user |\n| `retryable` | `bool` | `False` | Whether retrying this call might succeed |\n\n---\n\n## `ToolContext`\n\nInjected by protomcp into tool handlers that declare a `ctx: ToolContext` parameter. Provides progress reporting and cancellation detection.\n\n```python\nfrom protomcp.context import ToolContext\n```\n\n### Constructor\n\n```python\nToolContext(progress_token: str, send_fn: Callable)\n```\n\nNot constructed directly — protomcp creates and injects it.\n\n### Methods\n\n#### `report_progress(progress, total=0, message=\"\")`\n\nSend a progress notification to the MCP host.\n\n| Parameter | Type | Default | Description |\n|-----------|------|---------|-------------|\n| `progress` | `int` | required | Current progress value |\n| `total` | `int` | `0` | Total expected value (0 means unknown) |\n| `message` | `str` | `\"\"` | Optional human-readable status message |\n\nNo-op if the host did not supply a `progress_token` for this call.\n\n```python\nctx.report_progress(50, 100, \"Halfway done\")\n```\n\n#### `is_cancelled() -> bool`\n\nReturns `True` if the MCP host has sent a cancellation for this call. Thread-safe.\n\n```python\nif ctx.is_cancelled():\n return ToolResult(is_error=True, message=\"Cancelled\")\n```\n\n---\n\n## `ServerLogger`\n\nSends structured log messages to the MCP host over the protomcp protocol.\n\n```python\nfrom protomcp.log import ServerLogger\n```\n\n### Constructor\n\n```python\nServerLogger(send_fn: Callable, name: str = \"\")\n```\n\nNot constructed directly — protomcp creates and injects it. The `name` field identifies the logger source in log messages.\n\n### Methods\n\nAll log methods have the same signature:\n\n```python\nmethod(message: str, *, data=None)\n```\n\n`data` is an optional value that is serialized to JSON and included in the log envelope. If `data` is `None`, the message string is used as the data payload.\n\n| Method | MCP log level |\n|--------|---------------|\n| `debug(message, *, data=None)` | `debug` |\n| `info(message, *, data=None)` | `info` |\n| `notice(message, *, data=None)` | `notice` |\n| `warning(message, *, data=None)` | `warning` |\n| `error(message, *, data=None)` | `error` |\n| `critical(message, *, data=None)` | `critical` |\n| `alert(message, *, data=None)` | `alert` |\n| `emergency(message, *, data=None)` | `emergency` |\n\n```python\nlogger.info(\"Starting job\", data={\"job_id\": \"abc123\"})\nlogger.error(\"Job failed\", data={\"error\": \"timeout\"})\n```\n\n---\n\n## `tool_manager`\n\nModule-level object for programmatic tool list control. Only available when running under protomcp (raises `RuntimeError` if called outside protomcp).\n\n```python\nfrom protomcp import tool_manager\n```\n\n### `tool_manager.enable(tool_names)`\n\nEnable the specified tools. Returns the updated list of active tool names.\n\n```python\nactive: list[str] = tool_manager.enable([\"write_file\", \"delete_file\"])\n```\n\n### `tool_manager.disable(tool_names)`\n\nDisable the specified tools. Returns the updated list of active tool names.\n\n```python\nactive: list[str] = tool_manager.disable([\"write_file\", \"delete_file\"])\n```\n\n### `tool_manager.set_allowed(tool_names)`\n\nSwitch to allowlist mode. Only the specified tools are active. Returns the updated list of active tool names.\n\n```python\nactive: list[str] = tool_manager.set_allowed([\"read_file\", \"search\"])\n```\n\n### `tool_manager.set_blocked(tool_names)`\n\nSwitch to blocklist mode. All tools except the specified ones are active. Returns the updated list of active tool names.\n\n```python\nactive: list[str] = tool_manager.set_blocked([\"delete_database\"])\n```\n\n### `tool_manager.get_active_tools()`\n\nGet the current list of active tool names.\n\n```python\nactive: list[str] = tool_manager.get_active_tools()\n```\n\n### `tool_manager.batch(enable, disable, allow, block)`\n\nPerform multiple operations atomically. All parameters are optional lists of tool names.\n\n```python\nactive: list[str] = tool_manager.batch(\n enable=[\"write_file\"],\n disable=[\"read_only_mode\"],\n allow=None,\n block=None,\n)\n```\n\n---\n\n## Internal API (for testing)\n\nThese are not part of the public API but are useful in tests:\n\n### `get_registered_tools()`\n\nReturns a copy of the current tool registry.\n\n```python\nfrom protomcp.tool import get_registered_tools, ToolDef\n\ntools: list[ToolDef] = get_registered_tools()\n```\n\n### `clear_registry()`\n\nClears the tool registry. Call this in test setup to avoid cross-test contamination.\n\n```python\nfrom protomcp.tool import clear_registry\n\nclear_registry()\n```\n\n### `ToolDef`\n\n```python\n@dataclass\nclass ToolDef:\n name: str\n description: str\n input_schema_json: str # JSON string\n handler: Callable\n output_schema_json: str = \"\" # JSON string, empty if no output_type\n title: str = \"\"\n destructive_hint: bool = False\n idempotent_hint: bool = False\n read_only_hint: bool = False\n open_world_hint: bool = False\n task_support: bool = False\n hidden: bool = False\n```\n\n---\n\n## `@tool_group(...)`\n\nClass decorator that registers a group of related actions as one or more MCP tools.\n\n```python\nfrom protomcp.group import tool_group\n```\n\n**Parameters:**\n\n| Parameter | Type | Default | Description |\n|-----------|------|---------|-------------|\n| `name` | `str` | required | Tool group name |\n| `description` | `str` | `\"\"` | Human-readable description |\n| `strategy` | `str` | `\"union\"` | `\"union\"` (single tool with `oneOf` schema) or `\"separate\"` (one tool per action, namespaced as `group.action`) |\n| `title` | `str` | `\"\"` | Display name |\n| `destructive` | `bool` | `False` | Hint: destructive side effects |\n| `idempotent` | `bool` | `False` | Hint: idempotent |\n| `read_only` | `bool` | `False` | Hint: read-only |\n| `open_world` | `bool` | `False` | Hint: open world access |\n| `task_support` | `bool` | `False` | Hint: task support |\n| `hidden` | `bool` | `False` | Hide from tool list |\n\n**Returns:** The original class (unmodified).\n\n```python\n@tool_group(\"files\", description=\"File operations\", strategy=\"union\")\nclass FileTools:\n @action(\"read\", description=\"Read a file\")\n def read(self, path: str) -> ToolResult:\n return ToolResult(result=open(path).read())\n```\n\n---\n\n## `@action(...)`\n\nMethod decorator that marks a method as a group action.\n\n```python\nfrom protomcp.group import action\n```\n\n**Parameters:**\n\n| Parameter | Type | Default | Description |\n|-----------|------|---------|-------------|\n| `name` | `str` | required | Action name |\n| `description` | `str` | `\"\"` | Human-readable description |\n| `requires` | `list[str]` | `[]` | Required field names — validation fails if missing or empty |\n| `enum_fields` | `dict[str, list]` | `{}` | Map of field name to valid values — invalid values trigger \"Did you mean?\" suggestions |\n| `cross_rules` | `list[tuple[Callable, str]]` | `[]` | List of `(condition_fn, error_message)` tuples — if condition returns `True`, validation fails |\n| `hints` | `dict[str, dict]` | `{}` | Map of hint name to `{\"condition\": Callable, \"message\": str}` — non-blocking advisory messages |\n\n**Returns:** The original function (unmodified).\n\n```python\n@action(\"deploy\", requires=[\"env\"], enum_fields={\"env\": [\"dev\", \"staging\", \"prod\"]})\ndef deploy(self, env: str, version: str) -> ToolResult:\n return ToolResult(result=f\"Deployed {version} to {env}\")\n```\n\n---\n\n## `ActionDef`\n\n```python\n@dataclass\nclass ActionDef:\n name: str\n description: str\n handler: Callable\n input_schema: dict\n requires: list[str] = field(default_factory=list)\n enum_fields: dict[str, list] = field(default_factory=dict)\n cross_rules: list[tuple[Callable, str]] = field(default_factory=list)\n hints: dict[str, dict] = field(default_factory=dict)\n```\n\n---\n\n## `GroupDef`\n\n```python\n@dataclass\nclass GroupDef:\n name: str\n description: str\n actions: list[ActionDef]\n instance: Any\n strategy: str = \"union\"\n title: str = \"\"\n destructive_hint: bool = False\n idempotent_hint: bool = False\n read_only_hint: bool = False\n open_world_hint: bool = False\n task_support: bool = False\n hidden: bool = False\n```\n\n### `get_registered_groups()`\n\nReturns a copy of the current group registry.\n\n```python\nfrom protomcp.group import get_registered_groups\ngroups: list[GroupDef] = get_registered_groups()\n```\n\n### `clear_group_registry()`\n\nClears the group registry.\n\n```python\nfrom protomcp.group import clear_group_registry\nclear_group_registry()\n```\n\n---\n\n## `@server_context(...)`\n\nRegisters a context resolver that injects a value into tool handlers.\n\n```python\nfrom protomcp.server_context import server_context\n```\n\n**Parameters:**\n\n| Parameter | Type | Default | Description |\n|-----------|------|---------|-------------|\n| `param_name` | `str` | required | Name of the parameter to inject into handlers |\n| `expose` | `bool` | `True` | If `False`, the parameter is hidden from the tool JSON Schema |\n\n**Returns:** The original function (unmodified).\n\nThe decorated function receives the full `args` dict and returns the resolved value.\n\n```python\n@server_context(\"project_dir\", expose=False)\ndef resolve_project_dir(args: dict) -> str:\n return os.getcwd()\n```\n\n### `ContextDef`\n\n```python\n@dataclass\nclass ContextDef:\n param_name: str\n resolver: Callable[[dict], Any]\n expose: bool\n```\n\n### `resolve_contexts(args)`\n\nRuns all registered context resolvers against `args`. Returns a `dict[str, Any]` of resolved values keyed by `param_name`.\n\n### `get_hidden_context_params()`\n\nReturns a `set[str]` of parameter names where `expose=False`.\n\n### `get_registered_contexts()`\n\nReturns a copy of the context registry.\n\n### `clear_context_registry()`\n\nClears the context registry.\n\n---\n\n## `@local_middleware(...)`\n\nRegisters an in-process middleware that wraps tool handlers.\n\n```python\nfrom protomcp.local_middleware import local_middleware\n```\n\n**Parameters:**\n\n| Parameter | Type | Default | Description |\n|-----------|------|---------|-------------|\n| `priority` | `int` | `100` | Sort order — lowest priority runs outermost |\n\n**Returns:** The original function (unmodified).\n\nThe decorated function signature is `(ctx, tool_name: str, args: dict, next_handler) -> ToolResult`. Call `next_handler(ctx, args)` to continue the chain, or return a `ToolResult` directly to short-circuit.\n\n```python\n@local_middleware(priority=10)\ndef timing_mw(ctx, tool_name, args, next_handler):\n import time\n start = time.monotonic()\n result = next_handler(ctx, args)\n elapsed = time.monotonic() - start\n print(f\"{tool_name} took {elapsed:.3f}s\")\n return result\n```\n\n### `LocalMiddlewareDef`\n\n```python\n@dataclass\nclass LocalMiddlewareDef:\n priority: int\n handler: Callable # (ctx, tool_name, args, next_handler) -> ToolResult\n```\n\n### `build_middleware_chain(tool_name, handler)`\n\nBuilds a composed callable that wraps `handler` with all registered middleware. Returns `(ctx, args_dict) -> ToolResult`.\n\n### `get_local_middleware()`\n\nReturns middleware sorted by priority (lowest first).\n\n### `clear_local_middleware()`\n\nClears the middleware registry.\n\n---\n\n## `@telemetry_sink`\n\nRegisters an observe-only telemetry sink. Sinks receive `ToolCallEvent` instances but cannot affect tool execution. Exceptions in sinks are silently swallowed.\n\n```python\nfrom protomcp.telemetry import telemetry_sink\n```\n\n**Returns:** The original function (unmodified).\n\n```python\n@telemetry_sink\ndef log_events(event: ToolCallEvent):\n print(f\"[{event.phase}] {event.tool_name}\")\n```\n\n### `ToolCallEvent`\n\n```python\n@dataclass\nclass ToolCallEvent:\n tool_name: str\n phase: str # \"start\", \"success\", \"error\", \"progress\"\n args: dict\n action: str = \"\"\n result: str = \"\"\n error: Optional[Exception] = None\n duration_ms: int = 0\n progress: int = 0\n total: int = 0\n message: str = \"\"\n```\n\n| Field | Type | Description |\n|-------|------|-------------|\n| `tool_name` | `str` | Name of the tool being called |\n| `phase` | `str` | One of `\"start\"`, `\"success\"`, `\"error\"`, `\"progress\"` |\n| `args` | `dict` | Arguments passed to the tool |\n| `action` | `str` | Action name (for tool groups) |\n| `result` | `str` | Result string (on success) |\n| `error` | `Exception \\| None` | Exception (on error) |\n| `duration_ms` | `int` | Elapsed time in milliseconds |\n| `progress` | `int` | Current progress value (on progress) |\n| `total` | `int` | Total progress value (on progress) |\n| `message` | `str` | Human-readable status message |\n\n### `emit_telemetry(event)`\n\nSends a `ToolCallEvent` to all registered sinks. Called internally by protomcp.\n\n### `get_telemetry_sinks()`\n\nReturns a copy of the sink list.\n\n### `clear_telemetry_sinks()`\n\nClears the telemetry sink registry.\n\n---\n\n## `@sidecar(...)`\n\nDeclares a companion process that protomcp manages alongside the server.\n\n```python\nfrom protomcp.sidecar import sidecar\n```\n\n**Parameters:**\n\n| Parameter | Type | Default | Description |\n|-----------|------|---------|-------------|\n| `name` | `str` | required | Unique sidecar identifier |\n| `command` | `list[str]` | required | Process command and arguments |\n| `health_check` | `str` | `\"\"` | URL to poll for health (HTTP 200 = healthy) |\n| `start_on` | `str` | `\"first_tool_call\"` | `\"server_start\"` or `\"first_tool_call\"` |\n| `restart_on_version_mismatch` | `bool` | `False` | Restart if version changes |\n| `health_timeout` | `float` | `30.0` | Seconds to wait for health check |\n\n**Returns:** The original function (unmodified).\n\n```python\n@sidecar(name=\"redis\", command=[\"redis-server\"], start_on=\"server_start\")\ndef redis_sidecar():\n pass\n```\n\n### `SidecarDef`\n\n```python\n@dataclass\nclass SidecarDef:\n name: str\n command: list[str]\n health_check: str = \"\"\n start_on: str = \"first_tool_call\"\n restart_on_version_mismatch: bool = False\n health_timeout: float = 30.0\n health_interval: float = 1.0\n shutdown_timeout: float = 3.0\n```\n\n| Property | Description |\n|----------|-------------|\n| `pid_file_path` | `~/.protomcp/sidecars/{name}.pid` |\n\n### `start_sidecars(trigger)`\n\nStarts all sidecars matching the given trigger (`\"server_start\"` or `\"first_tool_call\"`).\n\n### `stop_all_sidecars()`\n\nStops all running sidecars. Sends `SIGTERM`, then `SIGKILL` after `shutdown_timeout`. Registered with `atexit` automatically.\n\n### `get_registered_sidecars()`\n\nReturns a copy of the sidecar registry.\n\n### `clear_sidecar_registry()`\n\nClears the sidecar registry.\n\n---\n\n## `@workflow(...)`\n\nClass decorator that registers a workflow — a server-defined state machine composed of steps.\n\n```python\nfrom protomcp import workflow\n```\n\n**Parameters:**\n\n| Parameter | Type | Default | Description |\n|-----------|------|---------|-------------|\n| `name` | `str` | required | Unique workflow identifier. Steps are registered as `name.step_name` tools |\n| `description` | `str` | `\"\"` | Human-readable description |\n| `allow_during` | `list[str] \\| None` | `None` | Glob patterns for external tools visible during the workflow |\n| `block_during` | `list[str] \\| None` | `None` | Glob patterns for external tools hidden during the workflow |\n\n**Returns:** The original class (unmodified).\n\nThe class may define `on_cancel(self, current_step, history)` and `on_complete(self, history)` lifecycle methods.\n\n```python\n@workflow(\"deploy\", allow_during=[\"status\"])\nclass DeployWorkflow:\n @step(initial=True, next=[\"approve\"], description=\"Review changes\")\n def review(self, pr_url: str) -> StepResult:\n return StepResult(result=f\"Reviewing {pr_url}\")\n\n @step(terminal=True, description=\"Approve changes\")\n def approve(self, reason: str) -> StepResult:\n return StepResult(result=f\"Approved: {reason}\")\n```\n\n---\n\n## `@step(...)`\n\nMethod decorator that marks a method as a workflow step.\n\n```python\nfrom protomcp import step\n```\n\n**Parameters:**\n\n| Parameter | Type | Default | Description |\n|-----------|------|---------|-------------|\n| `name` | `str \\| None` | method name | Step name. Defaults to the decorated method's name if not provided |\n| `description` | `str` | `\"\"` | Human-readable description |\n| `initial` | `bool` | `False` | Mark as the workflow entry point. Exactly one step must be initial |\n| `next` | `list[str] \\| None` | `None` | Valid next step names. Required for non-terminal steps |\n| `terminal` | `bool` | `False` | Mark as a workflow exit point. Terminal steps must not have `next` |\n| `no_cancel` | `bool` | `False` | Prevent cancellation while at this step |\n| `allow_during` | `list[str] \\| None` | `None` | Step-level visibility override (replaces workflow-level, does not merge) |\n| `block_during` | `list[str] \\| None` | `None` | Step-level block override (replaces workflow-level, does not merge) |\n| `on_error` | `dict[type, str] \\| None` | `None` | Map exception types to target step names for error-driven transitions |\n| `requires` | `list[str] \\| None` | `None` | Required field names — validation fails if missing or empty |\n| `enum_fields` | `dict[str, list] \\| None` | `None` | Map of field name to valid values |\n\n**Returns:** The original function (unmodified).\n\n```python\n@step(initial=True, next=[\"approve\", \"reject\"], description=\"Review changes\")\ndef review(self, pr_url: str) -> StepResult:\n return StepResult(result=f\"Reviewing {pr_url}\")\n```\n\n---\n\n## `StepResult`\n\nReturned by step handlers to provide the result and optionally narrow the next steps.\n\n```python\nfrom protomcp import StepResult\n```\n\n```python\n@dataclass\nclass StepResult:\n result: str = \"\"\n next: list[str] | None = None\n```\n\n### Fields\n\n| Field | Type | Default | Description |\n|-------|------|---------|-------------|\n| `result` | `str` | `\"\"` | The result string returned to the agent |\n| `next` | `list[str] \\| None` | `None` | Narrow the valid next steps at runtime. Must be a subset of the `@step` decorator's `next` list. If `None`, uses the full declared `next` list |\n\n```python\nreturn StepResult(result=\"Tests passed\", next=[\"promote\"])\n```\n\n---\n\n## `WorkflowDef`\n\n```python\n@dataclass\nclass WorkflowDef:\n name: str\n description: str\n steps: list[StepDef]\n instance: Any\n allow_during: list[str] | None = None\n block_during: list[str] | None = None\n on_cancel: Callable | None = None\n on_complete: Callable | None = None\n```\n\n### `get_registered_workflows()`\n\nReturns a copy of the current workflow registry.\n\n```python\nfrom protomcp import get_registered_workflows\nworkflows: list[WorkflowDef] = get_registered_workflows()\n```\n\n### `clear_workflow_registry()`\n\nClears the workflow registry and any active workflow state.\n\n```python\nfrom protomcp import clear_workflow_registry\nclear_workflow_registry()\n```\n\n---\n\n## `configure(...)`\n\nConfigures handler auto-discovery.\n\n```python\nfrom protomcp.discovery import configure\n```\n\n**Parameters:**\n\n| Parameter | Type | Default | Description |\n|-----------|------|---------|-------------|\n| `handlers_dir` | `str` | `\"\"` | Path to the directory containing handler files |\n| `hot_reload` | `bool` | `False` | Re-import handlers on each discovery pass |\n\n```python\nconfigure(handlers_dir=\"./handlers\", hot_reload=True)\n```\n\n### `discover_handlers()`\n\nImports all `.py` files in the configured `handlers_dir`. Files prefixed with `_` are skipped. With `hot_reload=True`, previously loaded modules are cleared first.\n\n### `get_config()`\n\nReturns a copy of the current configuration dict.\n\n### `reset_config()`\n\nClears the configuration and loaded module cache.","src/content/docs/reference/python-api.mdx","6e88d2a090909f38","reference/python-api.mdx","guides/dynamic-tool-lists",{"id":167,"data":169,"body":175,"filePath":176,"digest":177,"legacyId":178,"deferredRender":16},{"title":170,"description":171,"editUrl":16,"head":172,"template":35,"sidebar":173,"pagefind":16,"draft":23},"Dynamic Tool Lists","Enable and disable tools at runtime using tool results or tool_manager.",[],{"hidden":23,"attrs":174},{},"## Overview\n\nprotomcp lets tool processes change which tools are visible to the MCP host at runtime. This enables patterns like:\n\n- Showing only login tools until authenticated\n- Enabling destructive tools only after confirmation\n- Hiding debug tools in production\n- Progressively revealing tools based on workflow state\n\nThere are two ways to modify the tool list: **inline from a ToolResult** and **programmatically via tool_manager**.\n\n---\n\n## Inline: from ToolResult\n\nReturn `enable_tools` or `disable_tools` in your `ToolResult`. The changes take effect after the call returns.\n\n**Python:**\n\n```python\nfrom protomcp import tool, ToolResult\n\n@tool(\"Authenticate and unlock tools\")\ndef login(username: str, password: str) -> ToolResult:\n if not authenticate(username, password):\n return ToolResult(is_error=True, message=\"Bad credentials\")\n return ToolResult(\n result=\"Authenticated\",\n enable_tools=[\"create_record\", \"delete_record\"],\n disable_tools=[\"login\"],\n )\n```\n\n**TypeScript:**\n\n```typescript\nimport { tool, ToolResult } from 'protomcp';\nimport { z } from 'zod';\n\ntool({\n description: 'Authenticate and unlock tools',\n args: z.object({ username: z.string(), password: z.string() }),\n async handler({ username, password }) {\n if (!await authenticate(username, password)) {\n return new ToolResult({ isError: true, message: 'Bad credentials' });\n }\n return new ToolResult({\n result: 'Authenticated',\n enableTools: ['create_record', 'delete_record'],\n disableTools: ['login'],\n });\n },\n});\n```\n\n---\n\n## Programmatic: tool_manager / toolManager\n\nCall `tool_manager` (Python) or `toolManager` (TypeScript) inside a handler to modify the tool list mid-call. Useful when the modification is a side effect, not the primary action.\n\n**Python:**\n\n```python\nfrom protomcp import tool, ToolResult, tool_manager\n\n@tool(\"Run in safe mode\")\ndef enable_safe_mode() -> ToolResult:\n tool_manager.disable([\"delete_database\", \"drop_table\"])\n return ToolResult(result=\"Safe mode enabled\")\n\n@tool(\"Get the current active tool list\")\ndef list_active_tools() -> ToolResult:\n active = tool_manager.get_active_tools()\n return ToolResult(result=\", \".join(active))\n```\n\n**TypeScript:**\n\n```typescript\nimport { tool, ToolResult, toolManager } from 'protomcp';\nimport { z } from 'zod';\n\ntool({\n description: 'Enable safe mode',\n args: z.object({}),\n async handler() {\n await toolManager.disable(['delete_database', 'drop_table']);\n return new ToolResult({ result: 'Safe mode enabled' });\n },\n});\n```\n\n---\n\n## Modes\n\nThere are three tool list modes:\n\n| Mode | Description |\n|------|-------------|\n| **Open** | All registered tools are active. Enable/disable mutations are tracked as deltas. |\n| **Allowlist** | Only explicitly allowed tools are active. Call `set_allowed` to enter this mode. |\n| **Blocklist** | All tools except explicitly blocked ones are active. Call `set_blocked` to enter. |\n\nSee [Tool List Modes](/protomcp/concepts/tool-list-modes/) for the full state machine.\n\n### Switching to allowlist mode\n\n**Python:**\n\n```python\n# Only read_file and search are now visible\nactive = tool_manager.set_allowed([\"read_file\", \"search\"])\n```\n\n**TypeScript:**\n\n```typescript\nconst active = await toolManager.setAllowed(['read_file', 'search']);\n```\n\n### Switching to blocklist mode\n\n**Python:**\n\n```python\n# Everything except delete_file is visible\nactive = tool_manager.set_blocked([\"delete_file\"])\n```\n\n**TypeScript:**\n\n```typescript\nconst active = await toolManager.setBlocked(['delete_file']);\n```\n\n---\n\n## Batch operations\n\nUse `batch` to perform multiple enable/disable/allow/block operations in a single round-trip:\n\n**Python:**\n\n```python\nactive = tool_manager.batch(\n enable=[\"write_file\", \"rename_file\"],\n disable=[\"read_only_hint\"],\n allow=None,\n block=None,\n)\n```\n\n**TypeScript:**\n\n```typescript\nconst active = await toolManager.batch({\n enable: ['write_file', 'rename_file'],\n disable: ['read_only_hint'],\n});\n```\n\n---\n\n## Example: workflow-gated tools\n\n```python\nfrom protomcp import tool, ToolResult, tool_manager\n\n@tool(\"Start a write session\")\ndef begin_write() -> ToolResult:\n tool_manager.batch(\n enable=[\"write_file\", \"delete_file\"],\n disable=[\"begin_write\"],\n )\n return ToolResult(result=\"Write session started\")\n\n@tool(\"End a write session\")\ndef end_write() -> ToolResult:\n tool_manager.batch(\n disable=[\"write_file\", \"delete_file\"],\n enable=[\"begin_write\"],\n )\n return ToolResult(result=\"Write session ended\")\n\n@tool(\"Write content to a file\")\ndef write_file(path: str, content: str) -> ToolResult:\n with open(path, \"w\") as f:\n f.write(content)\n return ToolResult(result=f\"Wrote {len(content)} bytes to {path}\")\n\n@tool(\"Delete a file\")\ndef delete_file(path: str) -> ToolResult:\n import os\n os.remove(path)\n return ToolResult(result=f\"Deleted {path}\")\n```\n\nOn startup, only `begin_write` is active. Calling it enables `write_file` and `delete_file`, and disables itself. Calling `end_write` reverses this.","src/content/docs/guides/dynamic-tool-lists.mdx","cb8ddd5eb4ecdf67","guides/dynamic-tool-lists.mdx","guides/auth",{"id":179,"data":181,"body":187,"filePath":188,"digest":189,"legacyId":190,"deferredRender":16},{"title":182,"description":183,"editUrl":16,"head":184,"template":35,"sidebar":185,"pagefind":16,"draft":23},"Authentication","Configure built-in token and API key authentication for the HTTP transport.",[],{"hidden":23,"attrs":186},{},"protomcp includes built-in authentication middleware for the HTTP transport. Auth is configured via the `--auth` flag and only applies to `--transport http`. It is automatically skipped for stdio transport.\n\n## Configuration\n\nUse `--auth` to require authentication. The flag accepts `scheme:ENV_VAR` pairs:\n\n```sh\n# Require Bearer token authentication\npmcp run --auth token:MY_API_TOKEN server.py\n\n# Require API key authentication\npmcp run --auth apikey:SERVICE_API_KEY server.py\n\n# Multiple auth requirements (all must pass)\npmcp run --auth token:AUTH_TOKEN --auth apikey:ADMIN_KEY server.py\n```\n\n### Supported schemes\n\n| Scheme | Header | Format |\n|--------|--------|--------|\n| `token` | `Authorization` | `Bearer \u003Cvalue>` |\n| `apikey` | `X-API-Key` | `\u003Cvalue>` |\n\n### Environment variables\n\nThe `--auth` flag references environment variables by name. The environment variable must be set at startup:\n\n```sh\nexport MY_API_TOKEN=\"secret-token-value\"\npmcp run --auth token:MY_API_TOKEN server.py\n```\n\nIf the environment variable is not set, `pmcp` exits with an error.\n\n---\n\n## How it works\n\n1. `pmcp` reads the `--auth` flag values at startup\n2. For each `scheme:ENV_VAR` pair, it reads the environment variable value\n3. On each incoming request (for HTTP transport only):\n - `token` scheme: validates that `Authorization: Bearer \u003Cvalue>` matches\n - `apikey` scheme: validates that `X-API-Key: \u003Cvalue>` matches\n4. If validation fails, the request is rejected with an unauthorized error\n5. If all auth checks pass, the request proceeds to the middleware chain and tool handler\n\n---\n\n## Stdio transport\n\nAuth is automatically skipped when using stdio transport (`pmcp dev`). Since stdio communication happens over local pipes, there is no network boundary to authenticate across.\n\n---\n\n## Multiple auth requirements\n\nWhen multiple `--auth` flags are provided, all must pass. This lets you require both a Bearer token and an API key:\n\n```sh\npmcp run --auth token:USER_TOKEN --auth apikey:SERVICE_KEY server.py\n```\n\nBoth `USER_TOKEN` and `SERVICE_KEY` environment variables must be set, and both authentication checks must pass for each request.","src/content/docs/guides/auth.mdx","c434fa317a0069a8","guides/auth.mdx","guides/error-handling",{"id":191,"data":193,"body":199,"filePath":200,"digest":201,"legacyId":202,"deferredRender":16},{"title":194,"description":195,"editUrl":16,"head":196,"template":35,"sidebar":197,"pagefind":16,"draft":23},"Error Handling","Return structured errors from tools using ToolResult.",[],{"hidden":23,"attrs":198},{},"## Structured errors\n\nInstead of raising exceptions or returning plain strings, return a `ToolResult` with `is_error=True` and structured error fields. This gives the MCP host — and the AI — enough context to understand and potentially recover from errors.\n\n**Python:**\n\n```python\nfrom protomcp import tool, ToolResult\n\n@tool(\"Read a file\")\ndef read_file(path: str) -> ToolResult:\n try:\n with open(path) as f:\n return ToolResult(result=f.read())\n except FileNotFoundError:\n return ToolResult(\n is_error=True,\n error_code=\"NOT_FOUND\",\n message=f\"File not found: {path}\",\n suggestion=\"Check that the path is correct and the file exists\",\n retryable=False,\n )\n except PermissionError:\n return ToolResult(\n is_error=True,\n error_code=\"PERMISSION_DENIED\",\n message=f\"Cannot read {path}: permission denied\",\n suggestion=\"Check file permissions or run with elevated privileges\",\n retryable=False,\n )\n```\n\n**TypeScript:**\n\n```typescript\nimport { tool, ToolResult } from 'protomcp';\nimport { z } from 'zod';\nimport { readFile } from 'fs/promises';\n\ntool({\n description: 'Read a file',\n args: z.object({ path: z.string() }),\n async handler({ path }) {\n try {\n const content = await readFile(path, 'utf-8');\n return new ToolResult({ result: content });\n } catch (err: any) {\n if (err.code === 'ENOENT') {\n return new ToolResult({\n isError: true,\n errorCode: 'NOT_FOUND',\n message: `File not found: ${path}`,\n suggestion: 'Check that the path is correct and the file exists',\n retryable: false,\n });\n }\n return new ToolResult({\n isError: true,\n errorCode: 'READ_ERROR',\n message: err.message,\n retryable: false,\n });\n }\n },\n});\n```\n\n---\n\n## Error fields\n\n| Field | Type | Description |\n|-------|------|-------------|\n| `is_error` / `isError` | `bool` | Set to `True` to indicate an error |\n| `error_code` / `errorCode` | `str` | Machine-readable code (e.g. `NOT_FOUND`, `TIMEOUT`) |\n| `message` | `str` | Human-readable description of the error |\n| `suggestion` | `str` | What the AI or user should try next |\n| `retryable` | `bool` | Whether retrying the same call might succeed |\n\n---\n\n## Best practices\n\n**Use `error_code` consistently**: Define a set of codes for your tool suite (e.g. `NOT_FOUND`, `INVALID_INPUT`, `TIMEOUT`, `PERMISSION_DENIED`). This lets the AI learn to handle them.\n\n**Always include `message`**: The message is shown to the AI and should explain what went wrong in plain language.\n\n**Use `suggestion` for recovery**: If there's a clear next step, put it in `suggestion`. The AI can act on this directly.\n\n**Set `retryable` accurately**: If the error is transient (network timeout, rate limit), set `retryable=True`. If it's deterministic (wrong input, missing file), set `retryable=False`.\n\n**Don't raise exceptions**: Unhandled exceptions in a tool handler will cause protomcp to return a generic error. Catch exceptions and convert them to structured `ToolResult` errors.\n\n---\n\n## Retryable errors\n\n```python\nimport httpx\n\n@tool(\"Fetch a URL\")\ndef fetch(url: str) -> ToolResult:\n try:\n resp = httpx.get(url, timeout=10)\n resp.raise_for_status()\n return ToolResult(result=resp.text)\n except httpx.TimeoutException:\n return ToolResult(\n is_error=True,\n error_code=\"TIMEOUT\",\n message=f\"Request to {url} timed out after 10s\",\n suggestion=\"Try again or increase the timeout\",\n retryable=True,\n )\n except httpx.HTTPStatusError as e:\n retryable = e.response.status_code in (429, 502, 503, 504)\n return ToolResult(\n is_error=True,\n error_code=f\"HTTP_{e.response.status_code}\",\n message=str(e),\n retryable=retryable,\n )\n```","src/content/docs/guides/error-handling.mdx","a70cc34057ed9b91","guides/error-handling.mdx","reference/protobuf-spec",{"id":203,"data":205,"body":211,"filePath":212,"digest":213,"legacyId":214,"deferredRender":16},{"title":206,"description":207,"editUrl":16,"head":208,"template":35,"sidebar":209,"pagefind":16,"draft":23},"Protobuf Spec","The annotated protomcp.proto wire protocol specification.",[],{"hidden":23,"attrs":210},{},"## Wire format\n\nAll communication between protomcp and the tool process uses length-prefixed protobuf frames:\n\n```\n[4 bytes: uint32 big-endian length][N bytes: serialized Envelope]\n```\n\nEvery message is an `Envelope`. The `oneof msg` field identifies the message type.\n\n---\n\n## `Envelope`\n\n```protobuf\nmessage Envelope {\n oneof msg {\n // Go -> Tool Process\n ReloadRequest reload = 1;\n ListToolsRequest list_tools = 2;\n CallToolRequest call_tool = 3;\n\n // Tool Process -> Go\n ReloadResponse reload_response = 4;\n ToolListResponse tool_list = 5;\n CallToolResponse call_result = 6;\n EnableToolsRequest enable_tools = 7;\n DisableToolsRequest disable_tools = 8;\n SetAllowedRequest set_allowed = 9;\n SetBlockedRequest set_blocked = 10;\n GetActiveToolsRequest get_active_tools = 11;\n BatchUpdateRequest batch = 12;\n ActiveToolsResponse active_tools = 13;\n\n // Progress, cancellation, logging, tasks\n ProgressNotification progress = 16;\n CancelRequest cancel = 17;\n LogMessage log = 18;\n CreateTaskResponse create_task = 19;\n TaskStatusRequest task_status = 20;\n TaskStatusResponse task_status_response = 21;\n TaskResultRequest task_result = 22;\n TaskCancelRequest task_cancel = 23;\n }\n\n // Correlation ID for matching requests to responses.\n string request_id = 14;\n\n // Namespace for future multi-process support. Ignored in v1.\n string namespace = 15;\n}\n```\n\n---\n\n## Go → Tool Process\n\n### `ReloadRequest`\n\nSent when the tool file changes (dev mode). The tool process should re-register all tools and respond with `ReloadResponse`.\n\n```protobuf\nmessage ReloadRequest {}\n```\n\n### `ListToolsRequest`\n\nSent at startup to discover all registered tools.\n\n```protobuf\nmessage ListToolsRequest {}\n```\n\n### `CallToolRequest`\n\nSent when the MCP host calls a tool.\n\n```protobuf\nmessage CallToolRequest {\n string name = 1; // Tool name to call\n string arguments_json = 2; // JSON-encoded arguments object\n string progress_token = 3; // Token for progress notifications (optional)\n}\n```\n\n---\n\n## Tool Process → Go\n\n### `ReloadResponse`\n\n```protobuf\nmessage ReloadResponse {\n bool success = 1;\n string error = 2; // Error message if success is false\n}\n```\n\n### `ToolListResponse`\n\n```protobuf\nmessage ToolListResponse {\n repeated ToolDefinition tools = 1;\n}\n\nmessage ToolDefinition {\n string name = 1;\n string description = 2;\n string input_schema_json = 3; // JSON Schema for tool inputs\n string output_schema_json = 4; // JSON Schema for tool outputs (optional)\n string title = 5; // Human-readable title (optional)\n bool read_only_hint = 6; // Tool does not modify state\n bool destructive_hint = 7; // Tool may have irreversible effects\n bool idempotent_hint = 8; // Calling multiple times is safe\n bool open_world_hint = 9; // Tool interacts with external systems\n bool task_support = 10; // Tool supports async task lifecycle\n}\n```\n\n### `CallToolResponse`\n\n```protobuf\nmessage CallToolResponse {\n bool is_error = 1;\n string result_json = 2; // JSON-encoded result string\n repeated string enable_tools = 3; // Tools to enable after this call\n repeated string disable_tools = 4; // Tools to disable after this call\n ToolError error = 5; // Structured error (if is_error)\n string structured_content_json = 6; // Structured content (optional)\n}\n\nmessage ToolError {\n string error_code = 1; // Machine-readable code\n string message = 2; // Human-readable description\n string suggestion = 3; // Recovery suggestion\n bool retryable = 4; // Whether retrying might succeed\n}\n```\n\n---\n\n## Tool List Control\n\nThese messages are sent from the tool process to modify the active tool list at runtime.\n\n### `EnableToolsRequest` / `DisableToolsRequest`\n\n```protobuf\nmessage EnableToolsRequest {\n repeated string tool_names = 1;\n}\n\nmessage DisableToolsRequest {\n repeated string tool_names = 1;\n}\n```\n\n### `SetAllowedRequest` / `SetBlockedRequest`\n\nSwitch to allowlist or blocklist mode.\n\n```protobuf\nmessage SetAllowedRequest {\n repeated string tool_names = 1;\n}\n\nmessage SetBlockedRequest {\n repeated string tool_names = 1;\n}\n```\n\n### `GetActiveToolsRequest`\n\n```protobuf\nmessage GetActiveToolsRequest {}\n```\n\n### `BatchUpdateRequest`\n\nPerform multiple enable/disable/allow/block operations atomically.\n\n```protobuf\nmessage BatchUpdateRequest {\n repeated string enable = 1;\n repeated string disable = 2;\n repeated string allow = 3;\n repeated string block = 4;\n}\n```\n\n### `ActiveToolsResponse`\n\nSent in response to any tool list control message.\n\n```protobuf\nmessage ActiveToolsResponse {\n repeated string tool_names = 1;\n}\n```\n\n---\n\n## Progress, Cancellation, Logging\n\n### `ProgressNotification`\n\n```protobuf\nmessage ProgressNotification {\n string progress_token = 1;\n int64 progress = 2;\n int64 total = 3;\n string message = 4;\n}\n```\n\n### `CancelRequest`\n\n```protobuf\nmessage CancelRequest {\n string request_id = 1;\n}\n```\n\n### `LogMessage`\n\n```protobuf\nmessage LogMessage {\n string level = 1; // debug, info, warn, error\n string logger = 2; // Logger name\n string data_json = 3; // JSON payload\n}\n```\n\n---\n\n## Task (Async) Lifecycle\n\n### `CreateTaskResponse`\n\nReturned when a tool handler initiates an async task.\n\n```protobuf\nmessage CreateTaskResponse {\n string task_id = 1;\n}\n```\n\n### `TaskStatusRequest` / `TaskStatusResponse`\n\n```protobuf\nmessage TaskStatusRequest {\n string task_id = 1;\n}\n\nmessage TaskStatusResponse {\n string task_id = 1;\n string state = 2; // pending, running, completed, failed, cancelled\n int64 progress = 3;\n int64 total = 4;\n string message = 5;\n}\n```\n\n### `TaskResultRequest` / `TaskCancelRequest`\n\n```protobuf\nmessage TaskResultRequest {\n string task_id = 1;\n}\n\nmessage TaskCancelRequest {\n string task_id = 1;\n}\n```","src/content/docs/reference/protobuf-spec.mdx","5e8da20577ac169e","reference/protobuf-spec.mdx","guides/production-deployment",{"id":215,"data":217,"body":223,"filePath":224,"digest":225,"legacyId":226,"deferredRender":16},{"title":218,"description":219,"editUrl":16,"head":220,"template":35,"sidebar":221,"pagefind":16,"draft":23},"Production Deployment","Deploy protomcp in production using run mode, systemd, Docker, and more.",[],{"hidden":23,"attrs":222},{},"## dev vs run\n\n| | `dev` | `run` |\n|--|-------|-------|\n| Hot reload | Enabled (file watch) | Disabled |\n| Intended for | Local development | Production |\n\n```sh\n# Development (Python)\npmcp dev tools.py\n\n# Development (TypeScript)\npmcp dev tools.ts\n\n# Production (Python)\npmcp run tools.py\n\n# Production (TypeScript)\npmcp run tools.ts\n```\n\nIn `run` mode, protomcp starts the tool process and does not watch for file changes. Reload requires restarting the protomcp process.\n\n---\n\n## Choosing a transport\n\nFor production, choose the transport that matches your deployment topology:\n\n| Transport | Flag | When to use |\n|-----------|------|-------------|\n| stdio | `--transport stdio` | MCP client spawns protomcp as a subprocess (most common) |\n| Streamable HTTP | `--transport http` | Remote or web-based MCP clients |\n\nFor Claude Desktop (local), stdio is the correct choice. For a remote server, use `http`.\n\n```sh\n# Remote HTTP server (Python)\npmcp run tools.py --transport http --host 0.0.0.0 --port 8080\n\n# Remote HTTP server (TypeScript)\npmcp run tools.ts --transport http --host 0.0.0.0 --port 8080\n```\n\n---\n\n## Call timeout\n\nThe default call timeout is 5 minutes. Adjust for your workload:\n\n```sh\n# Long-running tasks\npmcp run tools.py --call-timeout 30m\npmcp run tools.ts --call-timeout 30m\n\n# Fast tools only\npmcp run tools.py --call-timeout 30s\npmcp run tools.ts --call-timeout 30s\n```\n\n---\n\n## Logging\n\n```sh\npmcp run tools.py --log-level debug # debug, info, warn, error\npmcp run tools.ts --log-level debug\n```\n\nLogs are written to stderr in structured format.\n\n---\n\n## systemd\n\nCreate `/etc/systemd/system/protomcp.service`:\n\n```ini\n[Unit]\nDescription=protomcp MCP server\nAfter=network.target\n\n[Service]\nType=simple\nExecStart=/usr/local/bin/pmcp run /opt/mytools/tools.py --transport http --host 127.0.0.1 --port 8080\n# For TypeScript: ExecStart=/usr/local/bin/pmcp run /opt/mytools/tools.ts --transport http --host 127.0.0.1 --port 8080\nWorkingDirectory=/opt/mytools\nUser=protomcp\nRestart=on-failure\nRestartSec=5\nStandardOutput=journal\nStandardError=journal\n\n[Install]\nWantedBy=multi-user.target\n```\n\nEnable and start:\n\n```sh\nsudo systemctl daemon-reload\nsudo systemctl enable protomcp\nsudo systemctl start protomcp\n```\n\n---\n\n## Docker\n\n```dockerfile\nFROM python:3.12-slim\n\n# Install protomcp binary\nRUN curl -L https://github.com/msilverblatt/protomcp/releases/latest/download/protomcp_linux_amd64.tar.gz \\\n | tar xz -C /usr/local/bin/\n\n# Install Python SDK and tool dependencies\nWORKDIR /app\nCOPY requirements.txt .\nRUN pip install --no-cache-dir -r requirements.txt\n\nCOPY tools.py .\n\nEXPOSE 8080\nCMD [\"pmcp\", \"run\", \"tools.py\", \"--transport\", \"http\", \"--host\", \"0.0.0.0\", \"--port\", \"8080\"]\n```\n\n`requirements.txt`:\n\n```\nprotomcp\n```\n\nBuild and run:\n\n```sh\ndocker build -t mytools .\ndocker run -p 8080:8080 mytools\n```\n\n---\n\n## Unix socket path\n\nBy default, protomcp creates the unix socket at `$XDG_RUNTIME_DIR/protomcp/\u003Cpid>.sock`. Override with `--socket`:\n\n```sh\npmcp run tools.py --socket /tmp/mytools.sock\n```\n\nThis is useful when you have multiple tool processes and want predictable socket paths.\n\n---\n\n## Multiple tool processes\n\nRun separate protomcp instances for each tool file:\n\n```sh\n# Python\npmcp run auth_tools.py --transport http --port 8081 &\npmcp run data_tools.py --transport http --port 8082 &\n\n# TypeScript\npmcp run auth_tools.ts --transport http --port 8081 &\npmcp run data_tools.ts --transport http --port 8082 &\n```\n\nConfigure each as a separate MCP server in your client.\n\n---\n\n## Health monitoring\n\nprotomcp does not expose a health endpoint, but you can monitor the process directly:\n\n```sh\n# systemd\nsystemctl status protomcp\n\n# Docker\ndocker inspect --format='{{.State.Health.Status}}' mytools-container\n```\n\nFor HTTP transports, a successful `tools/list` call indicates the server is healthy.","src/content/docs/guides/production-deployment.mdx","157fc134519cc680","guides/production-deployment.mdx","reference/typescript-api",{"id":227,"data":229,"body":235,"filePath":236,"digest":237,"legacyId":238,"deferredRender":16},{"title":230,"description":231,"editUrl":16,"head":232,"template":35,"sidebar":233,"pagefind":16,"draft":23},"TypeScript API Reference","Complete API reference for the protomcp TypeScript SDK.",[],{"hidden":23,"attrs":234},{},"## Installation\n\n```sh\nnpm install protomcp zod\n```\n\n## Imports\n\n```typescript\nimport { tool, ToolResult, toolManager, ToolContext, ServerLogger } from 'protomcp';\nimport { z } from 'zod';\n```\n\n---\n\n## `tool(options)`\n\nRegisters a tool and returns its `ToolDef`.\n\n### Options\n\n| Field | Type | Required | Description |\n|-------|------|----------|-------------|\n| `description` | `string` | Yes | Human-readable description |\n| `args` | `z.ZodObject\u003Cany>` | Yes | Zod schema for input arguments |\n| `handler` | `(args) => any` | Yes | Function to call when the tool is invoked |\n| `name` | `string` | No | Explicit tool name (defaults to handler function name) |\n| `output` | `z.ZodObject\u003Cany>` | No | Zod schema for structured output |\n| `title` | `string` | No | Display name shown in the MCP host UI |\n| `destructiveHint` | `boolean` | No | Hint: the tool has destructive side effects |\n| `idempotentHint` | `boolean` | No | Hint: calling the tool multiple times has the same effect as once |\n| `readOnlyHint` | `boolean` | No | Hint: the tool does not modify state |\n| `openWorldHint` | `boolean` | No | Hint: the tool may access resources outside the current context |\n| `taskSupport` | `boolean` | No | Hint: the tool supports long-running async task semantics |\n\n### Name inference\n\nThe tool name is derived from the handler function name. Arrow functions assigned to the `handler` property get the name `\"handler\"`, which is ignored. To use a specific name, use a named function or pass `name` explicitly.\n\n```typescript\nimport { tool, ToolResult } from 'protomcp';\nimport { z } from 'zod';\n\n// Name inferred from function name: \"multiply\"\ntool({\n description: 'Multiply two numbers',\n args: z.object({ a: z.number(), b: z.number() }),\n handler: function multiply({ a, b }) {\n return new ToolResult({ result: String(a * b) });\n },\n});\n\n// Name set explicitly\ntool({\n name: 'my_tool',\n description: 'A tool',\n args: z.object({ input: z.string() }),\n handler({ input }) {\n return new ToolResult({ result: input });\n },\n});\n```\n\n### Return value\n\nReturns a `ToolDef\u003CT>` object (also added to the global registry).\n\n---\n\n## `ToolResult`\n\n```typescript\nclass ToolResult {\n result: string; // default: \"\"\n isError: boolean; // default: false\n enableTools?: string[];\n disableTools?: string[];\n errorCode?: string;\n message?: string;\n suggestion?: string;\n retryable: boolean; // default: false\n\n constructor(options?: ToolResultOptions);\n}\n```\n\n### `ToolResultOptions`\n\n| Field | Type | Default | Description |\n|-------|------|---------|-------------|\n| `result` | `string` | `\"\"` | Result string returned to the MCP host |\n| `isError` | `boolean` | `false` | Set to `true` to indicate an error |\n| `enableTools` | `string[]` | — | Tools to enable after this call |\n| `disableTools` | `string[]` | — | Tools to disable after this call |\n| `errorCode` | `string` | — | Machine-readable error code |\n| `message` | `string` | — | Human-readable error message |\n| `suggestion` | `string` | — | Recovery suggestion |\n| `retryable` | `boolean` | `false` | Whether retrying might succeed |\n\n```typescript\n// Success\nnew ToolResult({ result: 'done' })\n\n// Error\nnew ToolResult({\n isError: true,\n errorCode: 'NOT_FOUND',\n message: 'File not found',\n suggestion: 'Check the path',\n retryable: false,\n})\n\n// Enable tools after call\nnew ToolResult({\n result: 'Authenticated',\n enableTools: ['write_file', 'delete_file'],\n})\n```\n\n---\n\n## `ToolContext`\n\nInjected by protomcp as the second argument to tool handlers. Provides progress reporting and cancellation detection.\n\n```typescript\nimport { ToolContext } from 'protomcp';\n```\n\n### Constructor\n\n```typescript\nnew ToolContext(progressToken: string, sendFn: (msg: any) => void)\n```\n\nNot constructed directly — protomcp creates and injects it.\n\n### Methods\n\n#### `reportProgress(progress, total?, message?)`\n\nSend a progress notification to the MCP host.\n\n| Parameter | Type | Description |\n|-----------|------|-------------|\n| `progress` | `number` | Current progress value |\n| `total` | `number` (optional) | Total expected value |\n| `message` | `string` (optional) | Human-readable status message |\n\nNo-op if the host did not supply a `progressToken` for this call.\n\n```typescript\nctx.reportProgress(50, 100, 'Halfway done');\n```\n\n#### `isCancelled() -> boolean`\n\nReturns `true` if the MCP host has sent a cancellation for this call.\n\n```typescript\nif (ctx.isCancelled()) {\n return new ToolResult({ isError: true, message: 'Cancelled' });\n}\n```\n\n---\n\n## `ServerLogger`\n\nSends structured log messages to the MCP host over the protomcp protocol.\n\n```typescript\nimport { ServerLogger } from 'protomcp';\n```\n\n### Constructor\n\n```typescript\nnew ServerLogger(sendFn: (msg: any) => void, name?: string)\n```\n\nNot constructed directly — protomcp creates and injects it. The optional `name` field identifies the logger source in log messages.\n\n### Methods\n\nAll log methods accept a message string and an optional data object:\n\n```typescript\nmethod(msg: string, data?: Record\u003Cstring, unknown>): void\n```\n\n`data` is serialized to JSON and included in the log envelope. If `data` is omitted, the message string is used as the payload.\n\n| Method | MCP log level |\n|--------|---------------|\n| `debug(msg, data?)` | `debug` |\n| `info(msg, data?)` | `info` |\n| `notice(msg, data?)` | `notice` |\n| `warning(msg, data?)` | `warning` |\n| `error(msg, data?)` | `error` |\n| `critical(msg, data?)` | `critical` |\n| `alert(msg, data?)` | `alert` |\n| `emergency(msg, data?)` | `emergency` |\n\n```typescript\nlogger.info('Starting job', { jobId: 'abc123' });\nlogger.error('Job failed', { error: 'timeout' });\n```\n\n---\n\n## `toolManager`\n\nSingleton instance for programmatic tool list control. All methods are `async` and return `Promise\u003Cstring[]>` — the updated list of active tool names.\n\n```typescript\nimport { toolManager } from 'protomcp';\n```\n\n### `toolManager.enable(toolNames)`\n\n```typescript\nconst active: string[] = await toolManager.enable(['write_file', 'delete_file']);\n```\n\n### `toolManager.disable(toolNames)`\n\n```typescript\nconst active: string[] = await toolManager.disable(['write_file', 'delete_file']);\n```\n\n### `toolManager.setAllowed(toolNames)`\n\nSwitch to allowlist mode. Only the specified tools are active.\n\n```typescript\nconst active: string[] = await toolManager.setAllowed(['read_file', 'search']);\n```\n\n### `toolManager.setBlocked(toolNames)`\n\nSwitch to blocklist mode. All tools except the specified ones are active.\n\n```typescript\nconst active: string[] = await toolManager.setBlocked(['delete_database']);\n```\n\n### `toolManager.getActiveTools()`\n\n```typescript\nconst active: string[] = await toolManager.getActiveTools();\n```\n\n### `toolManager.batch(options)`\n\n```typescript\nconst active: string[] = await toolManager.batch({\n enable: ['write_file'],\n disable: ['read_only_mode'],\n allow: [],\n block: [],\n});\n```\n\n**`BatchOptions`:**\n\n| Field | Type | Description |\n|-------|------|-------------|\n| `enable` | `string[]` | Tool names to enable |\n| `disable` | `string[]` | Tool names to disable |\n| `allow` | `string[]` | Set allowlist |\n| `block` | `string[]` | Set blocklist |\n\nAll fields are optional.\n\n---\n\n## Internal API (for testing)\n\n### `getRegisteredTools()`\n\nReturns a shallow copy of the current tool registry.\n\n```typescript\nimport { getRegisteredTools } from 'protomcp';\n\nconst tools = getRegisteredTools(); // ToolDef[]\n```\n\n### `clearRegistry()`\n\nClears the tool registry. Use in test `beforeEach` to avoid cross-test contamination.\n\n```typescript\nimport { clearRegistry } from 'protomcp';\n\nbeforeEach(() => clearRegistry());\n```\n\n### `ToolDef\u003CT>`\n\n```typescript\ninterface ToolDef\u003CT extends z.ZodType = z.ZodType> {\n name: string;\n description: string;\n inputSchemaJson: string; // JSON string\n outputSchemaJson: string; // JSON string, empty if no output schema\n title: string;\n destructiveHint: boolean;\n idempotentHint: boolean;\n readOnlyHint: boolean;\n openWorldHint: boolean;\n taskSupport: boolean;\n handler: (args: z.infer\u003CT>) => any;\n}\n```","src/content/docs/reference/typescript-api.mdx","e385aaa00ba59640","reference/typescript-api.mdx","guides/prompts",{"id":239,"data":241,"body":247,"filePath":248,"digest":249,"legacyId":250,"deferredRender":16},{"title":242,"description":243,"editUrl":16,"head":244,"template":35,"sidebar":245,"pagefind":16,"draft":23},"Prompts","Define reusable message templates with arguments and autocomplete.",[],{"hidden":23,"attrs":246},{},"Prompts are reusable message templates that MCP clients can discover and fill in with arguments. They're useful for building structured conversations — summarization templates, code review checklists, analysis workflows.\n\n## How Prompts Work\n\n```\nMCP Client pmcp Your Code\n │ │ │\n ├─ prompts/list ──────────►│ │\n │ ├─ ListPromptsRequest ────►│\n │ │◄─ PromptListResponse ───┤\n │◄─ prompt list ───────────┤ │\n │ │ │\n ├─ prompts/get(name) ─────►│ │\n │ ├─ GetPromptRequest ──────►│\n │ │◄─ GetPromptResponse ────┤\n │◄─ prompt messages ───────┤ │\n```\n\n## Python\n\n### Basic Prompt\n\n```python\nfrom protomcp import prompt, PromptMessage\n\n@prompt(description=\"Generate a code review checklist\")\ndef code_review() -> list[PromptMessage]:\n return [\n PromptMessage(role=\"user\", content=\"Review this code for:\\n1. Security issues\\n2. Performance\\n3. Readability\"),\n ]\n```\n\n### Prompts with Arguments\n\nArguments let the MCP client fill in values before sending the prompt:\n\n```python\nfrom protomcp import prompt, PromptArg, PromptMessage\n\n@prompt(\n description=\"Summarize a document\",\n arguments=[\n PromptArg(name=\"topic\", description=\"What to summarize\", required=True),\n PromptArg(name=\"style\", description=\"brief, detailed, or bullet\"),\n PromptArg(name=\"audience\", description=\"Who the summary is for\"),\n ],\n)\ndef summarize(topic: str, style: str = \"brief\", audience: str = \"general\") -> list[PromptMessage]:\n return [\n PromptMessage(\n role=\"user\",\n content=f\"Summarize {topic} in a {style} style for a {audience} audience.\",\n ),\n ]\n```\n\n### Multi-Turn Prompts\n\nReturn multiple messages to set up a conversation:\n\n```python\n@prompt(description=\"Debug a production issue\", arguments=[PromptArg(name=\"error\", required=True)])\ndef debug_issue(error: str) -> list[PromptMessage]:\n return [\n PromptMessage(role=\"user\", content=f\"I'm seeing this error in production:\\n\\n{error}\"),\n PromptMessage(role=\"assistant\", content=\"I'll help debug this. Let me analyze the error and ask some clarifying questions.\"),\n PromptMessage(role=\"user\", content=\"Please check for common causes first, then suggest specific debugging steps.\"),\n ]\n```\n\n## TypeScript\n\n```typescript\nimport { prompt } from 'protomcp';\n\nprompt({\n name: 'summarize',\n description: 'Summarize a document',\n arguments: [\n { name: 'topic', description: 'What to summarize', required: true },\n { name: 'style', description: 'brief, detailed, or bullet' },\n ],\n handler: (args) => [\n { role: 'user', content: `Summarize ${args.topic} in a ${args.style ?? 'brief'} style.` },\n ],\n});\n```\n\n## Go\n\n```go\nprotomcp.RegisterPrompt(protomcp.PromptDef{\n Name: \"summarize\",\n Description: \"Summarize a document\",\n Arguments: []protomcp.PromptArg{\n {Name: \"topic\", Description: \"What to summarize\", Required: true},\n {Name: \"style\", Description: \"brief, detailed, or bullet\"},\n },\n HandlerFn: func(args map[string]string) (string, []protomcp.PromptMessage) {\n style := args[\"style\"]\n if style == \"\" {\n style = \"brief\"\n }\n return \"\", []protomcp.PromptMessage{\n {Role: \"user\", ContentJSON: fmt.Sprintf(`{\"type\":\"text\",\"text\":\"Summarize %s in a %s style.\"}`, args[\"topic\"], style)},\n }\n },\n})\n```\n\n## Completions\n\nCompletions provide autocomplete suggestions as users fill in prompt arguments. Register a completion handler for a specific prompt + argument combination:\n\n```python\nfrom protomcp import completion, CompletionResult\n\n@completion(\"ref/prompt\", \"summarize\", \"topic\")\ndef complete_topic(value: str) -> CompletionResult:\n topics = [\"quarterly-report\", \"incident-2024-03\", \"architecture-review\"]\n matches = [t for t in topics if t.startswith(value)]\n return CompletionResult(values=matches, total=len(matches))\n\n@completion(\"ref/prompt\", \"summarize\", \"style\")\ndef complete_style(value: str) -> list[str]:\n return [s for s in [\"brief\", \"detailed\", \"bullet\"] if s.startswith(value)]\n```\n\nCompletions also work for resource template arguments:\n\n```python\n@completion(\"ref/resource\", \"db://users/{user_id}\", \"user_id\")\ndef complete_user_id(value: str) -> list[str]:\n return [uid for uid in get_all_user_ids() if uid.startswith(value)]\n```","src/content/docs/guides/prompts.mdx","99558702d2cfd571","guides/prompts.mdx","guides/hot-reload",{"id":251,"data":253,"body":259,"filePath":260,"digest":261,"legacyId":262,"deferredRender":16},{"title":254,"description":255,"editUrl":16,"head":256,"template":35,"sidebar":257,"pagefind":16,"draft":23},"Hot Reload","How protomcp reloads tool processes when source files change.",[],{"hidden":23,"attrs":258},{},"## How it works\n\nprotomcp watches your tool file for changes. When a change is detected:\n\n1. A `ReloadRequest` is sent to the tool process over the unix socket\n2. The tool process handles the reload (re-registers tools, re-reads config, etc.)\n3. The tool process replies with `ReloadResponse { success: true }`\n4. protomcp sends `notifications/tools/list_changed` to the MCP host\n5. The MCP host re-fetches the tool list\n\nThe file is the same process — no restart, no re-connection.\n\n---\n\n## Triggering reload\n\nHot reload is only active in `dev` mode:\n\n```sh\n# Python\npmcp dev tools.py\n\n# TypeScript\npmcp dev tools.ts\n```\n\nIt is disabled in `run` mode, which is intended for production.\n\nThe file watcher monitors the tool file path specified on the command line. Changes to imported modules are not automatically detected — only the entry file.\n\n---\n\n## Reload modes\n\n### Default: graceful reload\n\n```sh\n# Python\npmcp dev tools.py\n\n# TypeScript\npmcp dev tools.ts\n```\n\nSends `ReloadRequest` to the running process. The SDK handles this by re-executing the module-level code and re-registering all tools.\n\n### Immediate: process restart\n\n```sh\n# Python\npmcp dev tools.py --hot-reload immediate\n\n# TypeScript\npmcp dev tools.ts --hot-reload immediate\n```\n\nKills and restarts the tool process. Useful when:\n- The language or runtime doesn't support graceful reload\n- You want a clean slate every time (no stale module cache)\n- You're using `.go` or `.rs` files that need recompilation\n\n---\n\n## In-flight calls\n\nCalls that are in flight when a reload is triggered are not interrupted. protomcp waits for in-flight calls to complete before applying the reload. If a reload is requested while calls are running, it is queued.\n\n---\n\n## SDK behavior on reload\n\nThe Python and TypeScript SDKs re-run the tool registration code automatically. Since `@tool()` and `tool()` append to a global registry, the SDK clears the registry before re-running.\n\nIf you have initialization code that should only run once (e.g. loading a model, connecting to a database), gate it with a module-level flag:\n\n```python\nfrom protomcp import tool, ToolResult\n\n_db = None\n\ndef _get_db():\n global _db\n if _db is None:\n _db = connect_to_db()\n return _db\n\n@tool(\"Query the database\")\ndef query(sql: str) -> ToolResult:\n results = _get_db().execute(sql)\n return ToolResult(result=str(results))\n```\n\nThe database connection is created on first use and reused across reloads.\n\n---\n\n## Gotchas\n\n**Module-level side effects**: Code at module level runs on every reload. Avoid expensive operations (network calls, model loading) at module level without caching.\n\n**File-level watch only**: Only the entry file is watched. If you change an imported module, touch the entry file to trigger reload:\n\n```sh\ntouch tools.py\n```\n\n**Immediate mode loses state**: With `--hot-reload immediate`, the process is killed and restarted. Any in-memory state (caches, sessions, etc.) is lost.\n\n**Syntax errors**: If the tool file has a syntax error after reload, the `ReloadResponse` will contain `success: false` and an error message. The previous tool list remains active.","src/content/docs/guides/hot-reload.mdx","bf7d24ec3439845d","guides/hot-reload.mdx","guides/middleware",{"id":263,"data":265,"body":271,"filePath":272,"digest":273,"legacyId":274,"deferredRender":16},{"title":266,"description":267,"editUrl":16,"head":268,"template":35,"sidebar":269,"pagefind":16,"draft":23},"Custom Middleware","Intercept tool calls with before/after hooks using custom middleware registered from tool processes.",[],{"hidden":23,"attrs":270},{},"Custom middleware lets you intercept tool calls with before/after hooks. Middleware is registered by the tool process during the handshake and dispatched by the Go binary.\n\n## How it works\n\n1. Tool process registers middleware during startup (after listing tools, before handshake-complete)\n2. When a `tools/call` request arrives, the Go binary sends intercept requests to the tool process\n3. **Before phase**: middleware runs in priority order (lowest first). Each can modify arguments or reject the call\n4. The tool handler runs\n5. **After phase**: middleware runs in reverse priority order. Each can modify the result\n\n---\n\n## Python\n\n```python\nfrom protomcp import tool, ToolResult\nfrom protomcp.middleware import middleware\n\n@middleware(\"audit_log\", priority=10)\ndef audit_log(phase, tool_name, args_json, result_json, is_error):\n if phase == \"before\":\n print(f\"[AUDIT] Calling {tool_name} with {args_json}\")\n return {} # no modifications\n elif phase == \"after\":\n print(f\"[AUDIT] {tool_name} returned (error={is_error}): {result_json}\")\n return {}\n\n@middleware(\"rate_limiter\", priority=5)\ndef rate_limiter(phase, tool_name, args_json, result_json, is_error):\n if phase == \"before\":\n if is_rate_limited(tool_name):\n return {\"reject\": True, \"reject_reason\": \"Rate limit exceeded\"}\n return {}\n\n@tool(\"greet\")\ndef greet(name: str) -> ToolResult:\n return ToolResult(result=f\"Hello, {name}!\")\n```\n\n### Handler return values\n\n| Key | Type | Description |\n|-----|------|-------------|\n| `reject` | `bool` | Reject the call (before phase only) |\n| `reject_reason` | `str` | Reason for rejection |\n| `arguments_json` | `str` | Modified arguments JSON (before phase) |\n| `result_json` | `str` | Modified result JSON (after phase) |\n\n---\n\n## TypeScript\n\n```typescript\nimport { tool, ToolResult, middleware } from 'protomcp';\nimport { z } from 'zod';\n\nmiddleware('audit_log', 10, (phase, toolName, argsJson, resultJson, isError) => {\n if (phase === 'before') {\n console.log(`[AUDIT] Calling ${toolName} with ${argsJson}`);\n return {};\n } else {\n console.log(`[AUDIT] ${toolName} returned (error=${isError}): ${resultJson}`);\n return {};\n }\n});\n\nmiddleware('rate_limiter', 5, (phase, toolName) => {\n if (phase === 'before') {\n if (isRateLimited(toolName)) {\n return { reject: true, rejectReason: 'Rate limit exceeded' };\n }\n }\n return {};\n});\n\ntool({\n name: 'greet',\n description: 'Greet someone',\n args: z.object({ name: z.string() }),\n handler({ name }) {\n return new ToolResult({ result: `Hello, ${name}!` });\n },\n});\n```\n\n---\n\n## Go\n\nThe Go SDK does not currently expose a middleware registration API. Middleware is registered by tool processes using the Python, TypeScript, or Rust SDKs.\n\n---\n\n## Rust\n\nThe Rust SDK does not currently expose a middleware registration API. Middleware support is planned for a future release.\n\n---\n\n## Priority\n\nMiddleware priority determines execution order:\n- **Before phase**: lowest priority number runs first\n- **After phase**: highest priority number runs first (reverse order)\n\nThis ensures that outermost middleware (e.g., auth at priority 1) wraps innermost middleware (e.g., logging at priority 100).\n\n---\n\n## Argument modification\n\nMiddleware in the before phase can modify the tool arguments by returning `arguments_json`:\n\n```python\n@middleware(\"inject_defaults\", priority=50)\ndef inject_defaults(phase, tool_name, args_json, result_json, is_error):\n if phase == \"before\":\n import json\n args = json.loads(args_json)\n if \"timeout\" not in args:\n args[\"timeout\"] = 30\n return {\"arguments_json\": json.dumps(args)}\n return {}\n```\n\n---\n\n## Rejection\n\nMiddleware in the before phase can reject a call entirely:\n\n```python\n@middleware(\"block_dangerous\", priority=1)\ndef block_dangerous(phase, tool_name, args_json, result_json, is_error):\n if phase == \"before\" and tool_name in BLOCKED_TOOLS:\n return {\"reject\": True, \"reject_reason\": f\"Tool {tool_name} is blocked\"}\n return {}\n```\n\nWhen a call is rejected, the tool handler does not run and no after-phase middleware executes.","src/content/docs/guides/middleware.mdx","5f6e89cfbab863a1","guides/middleware.mdx","guides/resources",{"id":275,"data":277,"body":283,"filePath":284,"digest":285,"legacyId":286,"deferredRender":16},{"title":278,"description":279,"editUrl":16,"head":280,"template":35,"sidebar":281,"pagefind":16,"draft":23},"Resources","Expose data to MCP clients — files, configs, database records, API responses.",[],{"hidden":23,"attrs":282},{},"Resources let your MCP server expose data that clients can read. Unlike tools (which perform actions), resources are for providing context — configuration files, database records, log entries, or any data the LLM might need.\n\n## How Resources Work\n\n```\nMCP Client pmcp Your Code\n │ │ │\n ├─ resources/list ────────►│ │\n │ ├─ ListResourcesRequest ──►│\n │ │◄─ ResourceListResponse ──┤\n │◄─ resource list ─────────┤ │\n │ │ │\n ├─ resources/read(uri) ───►│ │\n │ ├─ ReadResourceRequest ───►│\n │ │◄─ ReadResourceResponse ──┤\n │◄─ resource contents ─────┤ │\n```\n\n## Python\n\n### Static Resources\n\nRegister a resource with a fixed URI:\n\n```python\nfrom protomcp import resource, ResourceContent\n\n@resource(uri=\"config://app\", description=\"Application configuration\")\ndef app_config(uri: str) -> ResourceContent:\n return ResourceContent(\n uri=uri,\n text='{\"debug\": false, \"log_level\": \"info\"}',\n mime_type=\"application/json\",\n )\n```\n\nThe handler receives the requested URI and returns a `ResourceContent` with the data. You can return text or binary data:\n\n```python\n@resource(uri=\"logo://main\", description=\"Company logo\", mime_type=\"image/png\")\ndef logo(uri: str) -> ResourceContent:\n with open(\"logo.png\", \"rb\") as f:\n return ResourceContent(uri=uri, blob=f.read(), mime_type=\"image/png\")\n```\n\n### Resource Templates\n\nTemplates use URI patterns with placeholders. The client can request any URI matching the pattern:\n\n```python\nfrom protomcp import resource_template, ResourceContent\n\n@resource_template(\n uri_template=\"db://users/{user_id}\",\n description=\"Read a user record by ID\",\n mime_type=\"application/json\",\n)\ndef read_user(uri: str) -> ResourceContent:\n user_id = uri.replace(\"db://users/\", \"\")\n user = db.get_user(user_id)\n return ResourceContent(uri=uri, text=json.dumps(user))\n```\n\n### Returning Multiple Contents\n\nA handler can return a list of `ResourceContent` for multi-part responses:\n\n```python\n@resource(uri=\"metrics://dashboard\", description=\"Dashboard metrics\")\ndef dashboard(uri: str) -> list[ResourceContent]:\n return [\n ResourceContent(uri=\"metrics://dashboard/cpu\", text=\"72%\"),\n ResourceContent(uri=\"metrics://dashboard/memory\", text=\"4.2GB/8GB\"),\n ]\n```\n\n## TypeScript\n\n```typescript\nimport { resource, resourceTemplate } from 'protomcp';\n\nresource({\n uri: 'config://app',\n description: 'Application configuration',\n handler: (uri) => ({\n uri,\n text: '{\"debug\": false}',\n mimeType: 'application/json',\n }),\n});\n\nresourceTemplate({\n uriTemplate: 'db://users/{user_id}',\n description: 'Read a user record by ID',\n handler: (uri) => {\n const userId = uri.replace('db://users/', '');\n return { uri, text: JSON.stringify(getUser(userId)) };\n },\n});\n```\n\n## Go\n\n```go\nprotomcp.RegisterResource(protomcp.ResourceDef{\n URI: \"config://app\",\n Name: \"app_config\",\n Description: \"Application configuration\",\n MimeType: \"application/json\",\n HandlerFn: func() []protomcp.ResourceContent {\n return []protomcp.ResourceContent{{\n URI: \"config://app\",\n Text: `{\"debug\": false}`,\n }}\n },\n})\n\nprotomcp.RegisterResourceTemplate(protomcp.ResourceTemplateDef{\n URITemplate: \"db://users/{user_id}\",\n Name: \"read_user\",\n Description: \"Read a user record by ID\",\n HandlerFn: func(uri string) []protomcp.ResourceContent {\n userID := strings.TrimPrefix(uri, \"db://users/\")\n return []protomcp.ResourceContent{{URI: uri, Text: getUser(userID)}}\n },\n})\n```\n\n## Combining with Tools\n\nResources and tools complement each other. A common pattern: expose data via resources, provide actions via tools.\n\n```python\nNOTES = {}\n\n@resource_template(uri_template=\"notes://{id}\", description=\"Read a note\")\ndef read_note(uri: str) -> ResourceContent:\n note_id = uri.replace(\"notes://\", \"\")\n return ResourceContent(uri=uri, text=NOTES.get(note_id, \"Not found\"))\n\n@tool(\"Create a new note\")\ndef create_note(id: str, content: str) -> ToolResult:\n NOTES[id] = content\n return ToolResult(result=f\"Created note {id}\")\n```","src/content/docs/guides/resources.mdx","1f8ad86f318f70d6","guides/resources.mdx","guides/sampling",{"id":287,"data":289,"body":295,"filePath":296,"digest":297,"legacyId":298,"deferredRender":16},{"title":290,"description":291,"editUrl":16,"head":292,"template":35,"sidebar":293,"pagefind":16,"draft":23},"Sampling","Request LLM calls from your tool code — the MCP client becomes your AI backend.",[],{"hidden":23,"attrs":294},{},"Sampling lets your server-side code request LLM completions from the MCP client. Instead of calling an AI API directly, your tool sends a sampling request through MCP, and the client (Claude Desktop, Cursor, etc.) handles the actual LLM call.\n\nThis is powerful because:\n- Your tool code doesn't need API keys or AI SDK dependencies\n- The client controls model selection, rate limiting, and cost\n- The user sees and approves the LLM interaction\n\n## How Sampling Works\n\n```\nYour Code pmcp (bridge) MCP Client\n │ │ │\n ├─ SamplingRequest ───►│ │\n │ ├─ sampling/createMessage ►│\n │ │ ├─ LLM call\n │ │ │◄─ response\n │ │◄─ CreateMessageResult ───┤\n │◄─ SamplingResponse ──┤ │\n```\n\nThe flow is bidirectional: your code initiates the request, pmcp forwards it to the MCP client, the client calls the LLM, and the response flows back.\n\n## Python\n\nUse `ctx.sample()` inside a tool handler to request an LLM completion:\n\n```python\nfrom protomcp import tool, ToolResult, ToolContext\n\n@tool(\"Translate text to another language\")\ndef translate(ctx: ToolContext, text: str, target_language: str) -> ToolResult:\n response = ctx.sample(\n messages=[\n {\"role\": \"user\", \"content\": f\"Translate the following to {target_language}:\\n\\n{text}\"}\n ],\n system_prompt=f\"You are a translator. Translate accurately to {target_language}.\",\n max_tokens=1000,\n )\n\n if response.get(\"error\"):\n return ToolResult(result=f\"Translation failed: {response['error']}\", is_error=True)\n\n return ToolResult(result=response[\"content\"])\n```\n\n### Sampling Parameters\n\n| Parameter | Type | Description |\n|-----------|------|-------------|\n| `messages` | `list[dict]` | Conversation messages (`role` + `content`) |\n| `system_prompt` | `str` | System prompt for the LLM |\n| `max_tokens` | `int` | Maximum tokens to generate |\n| `model_preferences` | `dict` | Hints about model selection (client may ignore) |\n\n### Event-Driven Sampling\n\nSampling isn't limited to tool calls. Your server can trigger LLM calls in response to events:\n\n```python\nimport threading\nfrom protomcp import tool, ToolResult, ToolContext\n\n# Store the sampling function for use outside tool calls\n_sampler = None\n\n@tool(\"Start monitoring for anomalies\")\ndef start_monitor(ctx: ToolContext, threshold: float) -> ToolResult:\n global _sampler\n _sampler = ctx.sample # Save for later use\n\n def monitor_loop():\n while True:\n data = check_for_anomalies()\n if data.score > threshold and _sampler:\n response = _sampler(\n messages=[{\"role\": \"user\", \"content\": f\"Anomaly detected: {data}. Suggest remediation.\"}],\n max_tokens=500,\n )\n log_remediation(response)\n\n threading.Thread(target=monitor_loop, daemon=True).start()\n return ToolResult(result=\"Monitoring started\")\n```\n\n## When to Use Sampling vs. Direct API Calls\n\n| Use Sampling When | Use Direct API Calls When |\n|-------------------|--------------------------|\n| You want the client to control costs | You need specific model guarantees |\n| You want the user to see/approve LLM usage | You need low-latency, high-volume calls |\n| Your tool is running in a client's environment | You're running a standalone server |\n| You don't want to manage API keys | You need provider-specific features |\n\n## Client Support\n\nSampling requires the MCP client to support the `sampling` capability. Not all clients support it. If sampling isn't available, `ctx.sample()` will return an error.","src/content/docs/guides/sampling.mdx","a964df9d190c3458","guides/sampling.mdx","guides/testing",{"id":299,"data":301,"body":307,"filePath":308,"digest":309,"legacyId":310,"deferredRender":16},{"title":302,"description":303,"editUrl":16,"head":304,"template":35,"sidebar":305,"pagefind":16,"draft":23},"Testing & Playground","Test your MCP server with built-in CLI tools and an interactive web playground.",[],{"hidden":23,"attrs":306},{},"protomcp includes built-in testing — no agent, no MCP client configuration, no external tools.\n\n## `pmcp test` — CLI\n\n### List everything\n\n```sh\npmcp test server.py list\n```\n\nShows all tools with parameter schemas, resources with URIs and MIME types, and prompts with arguments:\n\n```\nTools (3):\n add — Add two numbers\n a (integer, required)\n b (integer, required)\n\n search_notes — Search meeting notes\n query (string, required)\n\nResources (1):\n notes://index — List of available notes [application/json]\n\nPrompts (1):\n summarize_note — Summarize a meeting note\n note_id (required) — ID of the note\n style (optional) — Summary style\n```\n\n### Call a tool\n\n```sh\npmcp test server.py call add --args '{\"a\": 2, \"b\": 3}'\n```\n\nShows the result, timing, and the full protocol trace:\n\n```\nTool: add\nDuration: 1ms\nStatus: ok\n\nResult:\n 5\n\nTrace (5 messages):\n [10:15:32.100] send initialize\n [10:15:32.101] recv\n [10:15:32.101] send notifications/initialized\n [10:15:32.101] send tools/call\n [10:15:32.102] recv\n```\n\n### Tool list changes\n\nIf a tool call enables or disables other tools, the output shows it:\n\n```sh\npmcp test server.py call login --args '{\"token\": \"valid\"}'\n```\n\n```\nTools enabled: [create_record, delete_record]\nTools disabled: [login]\n```\n\n### Flags\n\n| Flag | Default | Description |\n|------|---------|-------------|\n| `--args '{...}'` | `{}` | JSON arguments for `call` |\n| `--format json` | `text` | Machine-readable JSON output |\n| `--trace=false` | `true` | Suppress protocol trace |\n\n### JSON output\n\n```sh\npmcp test server.py list --format json\npmcp test server.py call add --args '{\"a\":1,\"b\":2}' --format json\n```\n\nOutputs structured JSON — useful for CI or scripting.\n\n---\n\n## `pmcp playground` — Interactive web UI\n\n```sh\npmcp playground server.py --port 3000\n```\n\nOpens a web-based testing environment at `http://localhost:3000`.\n\n### Two-panel layout\n\n**Left panel — Interaction:**\n- Browse tools, resources, and prompts in tabbed view\n- Auto-generated forms from your JSON schemas\n- Call tools, read resources, get prompts with one click\n- Interaction history shows results like a chat\n\n**Right panel — Protocol trace:**\n- Every JSON-RPC message in both directions, timestamped\n- Color-coded: blue (outgoing), green (incoming), yellow (notifications)\n- Click any entry to expand the full JSON-RPC message\n- Auto-scrolls to latest unless you scroll up to inspect\n\n### Live features\n\n- **Hot reload**: save your file and the playground updates automatically\n- **Tool list changes**: when a tool call enables/disables tools, you see it happen in real-time\n- **Connection status**: green dot when connected, red when disconnected\n\n### Flags\n\n| Flag | Default | Description |\n|------|---------|-------------|\n| `--port` | `8080` | Port to serve on |\n| `--host` | `localhost` | Address to bind |","src/content/docs/guides/testing.mdx","56ef9a075ada7157","guides/testing.mdx","guides/writing-tools-python",{"id":311,"data":313,"body":319,"filePath":320,"digest":321,"legacyId":322,"deferredRender":16},{"title":314,"description":315,"editUrl":16,"head":316,"template":35,"sidebar":317,"pagefind":16,"draft":23},"Python Guide","Tools, resources, prompts, completions, sampling, and more in Python.",[],{"hidden":23,"attrs":318},{},"## Installation\n\n```sh\npip install protomcp\n```\n\n---\n\n## The `@tool()` decorator\n\nDecorate any function with `@tool(\"description\")` to register it as an MCP tool. The function name becomes the tool name.\n\n```python\nfrom protomcp import tool, ToolResult\n\n@tool(\"Add two integers\")\ndef add(a: int, b: int) -> ToolResult:\n return ToolResult(result=str(a + b))\n```\n\n### Tool metadata\n\nPass additional keyword arguments to `@tool()` to provide metadata hints to the MCP host:\n\n```python\n@tool(\n \"Delete a file from disk\",\n title=\"Delete File\",\n destructive=True,\n idempotent=False,\n read_only=False,\n open_world=False,\n task_support=False,\n)\ndef delete_file(path: str) -> ToolResult:\n os.remove(path)\n return ToolResult(result=f\"Deleted {path}\")\n```\n\n| Parameter | Type | Default | Description |\n|-----------|------|---------|-------------|\n| `description` | `str` | required | Human-readable description of what the tool does |\n| `title` | `str` | `\"\"` | Display name shown in the MCP host UI |\n| `destructive` | `bool` | `False` | Hint: the tool has destructive side effects |\n| `idempotent` | `bool` | `False` | Hint: calling the tool multiple times has the same effect as once |\n| `read_only` | `bool` | `False` | Hint: the tool does not modify state |\n| `open_world` | `bool` | `False` | Hint: the tool may access resources outside the current context |\n| `task_support` | `bool` | `False` | Hint: the tool supports long-running async task semantics |\n| `output_type` | dataclass type | `None` | Dataclass type for structured output schema generation |\n\n### Type hints and schema generation\n\nprotomcp reads Python type hints to generate the JSON Schema for tool inputs automatically.\n\n| Python type | JSON Schema type |\n|-------------|-----------------|\n| `str` | `\"string\"` |\n| `int` | `\"integer\"` |\n| `float` | `\"number\"` |\n| `bool` | `\"boolean\"` |\n| `list` | `\"array\"` |\n| `dict` | `\"object\"` |\n| `list[T]` | `{\"type\": \"array\", \"items\": \u003CT schema>}` |\n| `dict[K, V]` | `{\"type\": \"object\", \"additionalProperties\": \u003CV schema>}` |\n| `str \\| int` / `Union[str, int]` | `{\"anyOf\": [...]}` |\n| `Optional[T]` | type of `T`, not required |\n| `Literal[\"a\", \"b\"]` | `{\"type\": \"string\", \"enum\": [\"a\", \"b\"]}` |\n\n```python\n@tool(\"Search for documents\")\ndef search(query: str, limit: int = 10, include_archived: bool = False) -> ToolResult:\n # limit and include_archived are optional because they have defaults\n results = do_search(query, limit, include_archived)\n return ToolResult(result=str(results))\n```\n\n### Optional parameters\n\nUse `Optional[T]` or a default value to mark a parameter as optional:\n\n```python\nfrom typing import Optional\n\n@tool(\"Send a notification\")\ndef notify(message: str, channel: Optional[str] = None) -> ToolResult:\n ch = channel or \"default\"\n send(ch, message)\n return ToolResult(result=f\"Sent to {ch}\")\n```\n\nParameters with `Optional[T]` or a default are not added to the `required` array in the JSON Schema.\n\n---\n\n## `ToolResult`\n\nAll tool handlers must return a `ToolResult`.\n\n```python\nfrom dataclasses import dataclass\nfrom typing import Optional\n\n@dataclass\nclass ToolResult:\n result: str = \"\"\n is_error: bool = False\n enable_tools: Optional[list[str]] = None\n disable_tools: Optional[list[str]] = None\n error_code: Optional[str] = None\n message: Optional[str] = None\n suggestion: Optional[str] = None\n retryable: bool = False\n```\n\n### Success\n\n```python\nreturn ToolResult(result=\"done\")\n```\n\n### Structured error\n\n```python\nreturn ToolResult(\n is_error=True,\n error_code=\"NOT_FOUND\",\n message=\"The file /tmp/data.csv does not exist\",\n suggestion=\"Check the path and try again\",\n retryable=False,\n)\n```\n\n### Enabling / disabling tools from a result\n\nReturn `enable_tools` or `disable_tools` to change the active tool list after this call completes:\n\n```python\n@tool(\"Log in and unlock tools\")\ndef login(username: str, password: str) -> ToolResult:\n if not authenticate(username, password):\n return ToolResult(is_error=True, message=\"Authentication failed\")\n return ToolResult(\n result=\"Logged in\",\n enable_tools=[\"delete_file\", \"write_file\"],\n )\n```\n\n---\n\n## `tool_manager`\n\nUse `tool_manager` to modify the active tool list during a tool call (not just at return time).\n\n```python\nfrom protomcp import tool, ToolResult, tool_manager\n\n@tool(\"Enable debug tools\")\ndef enable_debug() -> ToolResult:\n active = tool_manager.enable([\"debug_dump\", \"trace_calls\"])\n return ToolResult(result=f\"Active tools: {active}\")\n\n@tool(\"Disable debug tools\")\ndef disable_debug() -> ToolResult:\n active = tool_manager.disable([\"debug_dump\", \"trace_calls\"])\n return ToolResult(result=f\"Active tools: {active}\")\n```\n\n### `tool_manager` API\n\nAll functions return a `list[str]` of the currently active tool names after the operation.\n\n```python\ntool_manager.enable(tool_names: list[str]) -> list[str]\ntool_manager.disable(tool_names: list[str]) -> list[str]\ntool_manager.set_allowed(tool_names: list[str]) -> list[str]\ntool_manager.set_blocked(tool_names: list[str]) -> list[str]\ntool_manager.get_active_tools() -> list[str]\ntool_manager.batch(enable=None, disable=None, allow=None, block=None) -> list[str]\n```\n\n`set_allowed` and `set_blocked` switch the tool list to allowlist/blocklist mode respectively. See [Tool List Modes](/protomcp/concepts/tool-list-modes/).\n\n### Batch operations\n\nUse `batch` to perform multiple operations atomically:\n\n```python\nactive = tool_manager.batch(\n enable=[\"write_file\"],\n disable=[\"read_only_mode\"],\n)\n```\n\n---\n\n## Progress Reporting\n\nUse `ToolContext` to report progress during a long-running tool call. Declare a `ctx: ToolContext` parameter in your handler — protomcp injects it automatically and skips it during schema generation.\n\n```python\nfrom protomcp import tool, ToolResult\nfrom protomcp.context import ToolContext\n\n@tool(\"Process a large dataset\")\ndef process_data(file_path: str, ctx: ToolContext) -> ToolResult:\n rows = load_rows(file_path)\n total = len(rows)\n for i, row in enumerate(rows):\n if ctx.is_cancelled():\n return ToolResult(is_error=True, message=\"Cancelled\")\n process_row(row)\n ctx.report_progress(i + 1, total, f\"Processing row {i + 1}/{total}\")\n return ToolResult(result=f\"Processed {total} rows\")\n```\n\n### `ToolContext` API\n\n| Method | Signature | Description |\n|--------|-----------|-------------|\n| `report_progress` | `(progress: int, total: int = 0, message: str = \"\") -> None` | Send a progress notification to the MCP host |\n| `is_cancelled` | `() -> bool` | Returns `True` if the MCP host has cancelled this call |\n\n`report_progress` is a no-op if no `progress_token` was provided by the host (i.e. the host does not support progress notifications).\n\n---\n\n## Server Logging\n\nUse `ServerLogger` to send structured log messages back to the MCP host (not to stderr). The host can display or filter these messages.\n\n```python\nfrom protomcp import tool, ToolResult\nfrom protomcp.log import ServerLogger\n\nlogger = ServerLogger(send_fn=None) # send_fn is injected by protomcp at runtime\n\n@tool(\"Fetch remote data\")\ndef fetch_data(url: str) -> ToolResult:\n logger.info(\"Fetching URL\", data={\"url\": url})\n try:\n data = download(url)\n logger.debug(\"Fetch complete\", data={\"bytes\": len(data)})\n return ToolResult(result=data)\n except Exception as e:\n logger.error(\"Fetch failed\", data={\"url\": url, \"error\": str(e)})\n return ToolResult(is_error=True, message=str(e))\n```\n\n### `ServerLogger` API\n\nAll methods accept a `message` string and an optional `data` keyword argument (a dict serialized to JSON).\n\n| Method | Level |\n|--------|-------|\n| `debug(message, *, data=None)` | `debug` |\n| `info(message, *, data=None)` | `info` |\n| `notice(message, *, data=None)` | `notice` |\n| `warning(message, *, data=None)` | `warning` |\n| `error(message, *, data=None)` | `error` |\n| `critical(message, *, data=None)` | `critical` |\n| `alert(message, *, data=None)` | `alert` |\n| `emergency(message, *, data=None)` | `emergency` |\n\n---\n\n## Structured Output\n\nUse `output_type` in `@tool()` to declare a structured output schema. Pass a dataclass type — protomcp generates the JSON Schema automatically.\n\n```python\nfrom dataclasses import dataclass\nfrom protomcp import tool, ToolResult\n\n@dataclass\nclass SearchResult:\n title: str\n url: str\n score: float\n\n@tool(\"Search the web\", output_type=SearchResult)\ndef search(query: str) -> ToolResult:\n results = run_search(query)\n # Return the structured result serialized to JSON\n import json, dataclasses\n return ToolResult(result=json.dumps([dataclasses.asdict(r) for r in results]))\n```\n\nThe `output_type` dataclass must use field types that map to JSON Schema primitives (`str`, `int`, `float`, `bool`, `list`, `dict`, `Optional[T]`).\n\n---\n\n## Cancellation\n\nCheck `ctx.is_cancelled()` periodically in long-running tools to stop early when the MCP host cancels the request.\n\n```python\nfrom protomcp import tool, ToolResult\nfrom protomcp.context import ToolContext\n\n@tool(\"Run a slow computation\")\ndef slow_compute(n: int, ctx: ToolContext) -> ToolResult:\n result = 0\n for i in range(n):\n if ctx.is_cancelled():\n return ToolResult(is_error=True, message=\"Cancelled by host\")\n result += expensive_step(i)\n return ToolResult(result=str(result))\n```\n\nCancellation is cooperative — protomcp sets the cancelled flag and your tool is responsible for checking it. In-flight calls are not interrupted forcibly.\n\n---\n\n## Testing tools\n\nSince `@tool()` registers handlers in a global registry, test files should use `clear_registry()` between tests:\n\n```python\nfrom protomcp.tool import clear_registry, get_registered_tools\nfrom protomcp import tool, ToolResult\n\ndef test_add():\n clear_registry()\n\n @tool(\"Add two numbers\")\n def add(a: int, b: int) -> ToolResult:\n return ToolResult(result=str(a + b))\n\n tools = get_registered_tools()\n assert len(tools) == 1\n assert tools[0].name == \"add\"\n\n result = tools[0].handler(a=2, b=3)\n assert result.result == \"5\"\n assert not result.is_error\n```\n\nCall the handler directly — no need to run protomcp for unit tests.\n\n---\n\n## Resources\n\nResources expose data that MCP clients can read. See the [Resources guide](/protomcp/guides/resources/) for the full pattern.\n\n```python\nfrom protomcp import resource, resource_template, ResourceContent\n\n@resource(uri=\"config://app\", description=\"App configuration\")\ndef app_config(uri: str) -> ResourceContent:\n return ResourceContent(uri=uri, text='{\"debug\": false}', mime_type=\"application/json\")\n\n@resource_template(uri_template=\"db://users/{user_id}\", description=\"Read a user by ID\")\ndef read_user(uri: str) -> ResourceContent:\n user_id = uri.replace(\"db://users/\", \"\")\n return ResourceContent(uri=uri, text=json.dumps(get_user(user_id)))\n```\n\n---\n\n## Prompts\n\nPrompts define reusable message templates. See the [Prompts guide](/protomcp/guides/prompts/) for the full pattern.\n\n```python\nfrom protomcp import prompt, PromptArg, PromptMessage\n\n@prompt(\n description=\"Summarize a topic\",\n arguments=[PromptArg(name=\"topic\", required=True)],\n)\ndef summarize(topic: str) -> list[PromptMessage]:\n return [PromptMessage(role=\"user\", content=f\"Summarize {topic} briefly.\")]\n```\n\n---\n\n## Completions\n\nProvide autocomplete for prompt and resource arguments. See [Prompts guide — Completions](/protomcp/guides/prompts/#completions).\n\n```python\nfrom protomcp import completion, CompletionResult\n\n@completion(\"ref/prompt\", \"summarize\", \"topic\")\ndef complete_topic(value: str) -> CompletionResult:\n topics = [\"architecture\", \"performance\", \"security\"]\n return CompletionResult(values=[t for t in topics if t.startswith(value)])\n```\n\n---\n\n## Sampling\n\nRequest LLM calls from the MCP client. See the [Sampling guide](/protomcp/guides/sampling/).\n\n```python\n@tool(\"Translate text\")\ndef translate(ctx: ToolContext, text: str, language: str) -> ToolResult:\n response = ctx.sample(\n messages=[{\"role\": \"user\", \"content\": f\"Translate to {language}: {text}\"}],\n max_tokens=500,\n )\n return ToolResult(result=response.get(\"content\", \"\"))\n```\n\n---\n\n## Tool Groups\n\nGroup related actions under a single tool using `@tool_group` on a class and `@action` on its methods.\n\n```python\nfrom protomcp.group import tool_group, action\nfrom protomcp import ToolResult\n\n@tool_group(\"files\", description=\"File operations\", strategy=\"union\")\nclass FileTools:\n @action(\"read\", description=\"Read a file\")\n def read(self, path: str) -> ToolResult:\n return ToolResult(result=open(path).read())\n\n @action(\"write\", description=\"Write a file\")\n def write(self, path: str, content: str) -> ToolResult:\n open(path, \"w\").write(content)\n return ToolResult(result=f\"Wrote {path}\")\n```\n\n### Strategy: union (default)\n\nWith `strategy=\"union\"`, the group registers as a single tool with a discriminated `oneOf` schema. The caller passes an `action` field to select the action.\n\n### Strategy: separate\n\nWith `strategy=\"separate\"`, each action becomes its own tool, namespaced as `group.action` (e.g. `files.read`, `files.write`).\n\n```python\n@tool_group(\"files\", strategy=\"separate\")\nclass FileTools:\n ...\n```\n\n### Dispatch and fuzzy matching\n\nWhen an unknown action is passed, the dispatcher returns an error with a fuzzy \"Did you mean?\" suggestion based on close matches.\n\n### Per-action schema\n\nEach `@action` generates its own JSON Schema from the method's type hints, following the same rules as `@tool()`.\n\n---\n\n## Declarative Validation\n\nDeclare validation rules on `@action()` to validate input before your handler runs.\n\n### `requires`\n\nFail if a required field is missing or empty.\n\n```python\n@action(\"deploy\", requires=[\"env\", \"version\"])\ndef deploy(self, env: str, version: str) -> ToolResult:\n return ToolResult(result=f\"Deployed {version} to {env}\")\n```\n\n### `enum_fields`\n\nRestrict a field to a set of valid values. Invalid values trigger a \"Did you mean?\" suggestion.\n\n```python\n@action(\"set_env\", enum_fields={\"env\": [\"dev\", \"staging\", \"prod\"]})\ndef set_env(self, env: str) -> ToolResult:\n return ToolResult(result=f\"Set to {env}\")\n```\n\n### `cross_rules`\n\nValidate relationships between parameters. Each rule is a `(condition_fn, error_message)` tuple — if the condition returns `True`, the error is raised.\n\n```python\n@action(\"scale\", cross_rules=[\n (lambda args: args.get(\"min\", 0) > args.get(\"max\", 0), \"min must be \u003C= max\"),\n])\ndef scale(self, min: int, max: int) -> ToolResult:\n return ToolResult(result=f\"Scaled {min}-{max}\")\n```\n\n### `hints`\n\nNon-blocking advisory messages appended to the result when a condition is met.\n\n```python\n@action(\"query\", hints={\n \"slow_warning\": {\n \"condition\": lambda args: args.get(\"limit\", 0) > 1000,\n \"message\": \"Large limit may cause slow queries\",\n },\n})\ndef query(self, table: str, limit: int = 100) -> ToolResult:\n return ToolResult(result=f\"Queried {table}\")\n```\n\n---\n\n## Server Context\n\nRegister resolvers that inject shared parameters into tool handlers automatically.\n\n```python\nfrom protomcp.server_context import server_context\n\n@server_context(\"project_dir\", expose=False)\ndef resolve_project_dir(args: dict) -> str:\n return os.getcwd()\n```\n\n| Parameter | Type | Default | Description |\n|-----------|------|---------|-------------|\n| `param_name` | `str` | required | Name of the parameter to inject |\n| `expose` | `bool` | `True` | If `False`, the parameter is hidden from the tool schema |\n\nWhen a tool handler declares a parameter matching `param_name`, protomcp calls the resolver and injects the returned value. With `expose=False`, the parameter does not appear in the JSON Schema sent to the MCP host.\n\n---\n\n## Local Middleware\n\nWrap tool handlers with in-process middleware for cross-cutting concerns like logging, error formatting, or timing.\n\n```python\nfrom protomcp.local_middleware import local_middleware\n\n@local_middleware(priority=10)\ndef timing_middleware(ctx, tool_name, args, next_handler):\n import time\n start = time.monotonic()\n result = next_handler(ctx, args)\n elapsed = time.monotonic() - start\n print(f\"{tool_name} took {elapsed:.3f}s\")\n return result\n```\n\n### Priority chain\n\nMiddleware is sorted by `priority` (lowest first = outermost). A priority-10 middleware wraps a priority-100 middleware, which wraps the tool handler.\n\n### Short-circuit\n\nReturn a `ToolResult` directly from middleware to skip the handler entirely.\n\n```python\n@local_middleware(priority=5)\ndef auth_gate(ctx, tool_name, args, next_handler):\n if not is_authenticated():\n return ToolResult(is_error=True, message=\"Not authenticated\")\n return next_handler(ctx, args)\n```\n\n### Local vs Go-bridge middleware\n\nLocal middleware runs in-process in Python. Go-bridge middleware runs cross-process via the Go transport layer. Use local middleware for Python-only concerns; use Go-bridge middleware for transport-level concerns.\n\n---\n\n## Telemetry\n\nObserve tool calls with fail-safe telemetry sinks. Sinks receive events but cannot affect tool execution — exceptions in sinks are silently swallowed.\n\n```python\nfrom protomcp.telemetry import telemetry_sink, ToolCallEvent\n\n@telemetry_sink\ndef log_events(event: ToolCallEvent):\n print(f\"[{event.phase}] {event.tool_name}: {event.message}\")\n```\n\n### `ToolCallEvent` phases\n\n| Phase | When |\n|-------|------|\n| `\"start\"` | Before the handler runs |\n| `\"success\"` | After the handler returns successfully |\n| `\"error\"` | After the handler raises or returns an error |\n| `\"progress\"` | When `report_progress` is called |\n\n### Example: SQLite event store\n\n```python\n@telemetry_sink\ndef sqlite_store(event: ToolCallEvent):\n import sqlite3\n conn = sqlite3.connect(\"telemetry.db\")\n conn.execute(\n \"INSERT INTO events (tool, phase, duration_ms) VALUES (?, ?, ?)\",\n (event.tool_name, event.phase, event.duration_ms),\n )\n conn.commit()\n```\n\n---\n\n## Sidecar Management\n\nDeclare companion processes that protomcp manages alongside your server.\n\n```python\nfrom protomcp.sidecar import sidecar\n\n@sidecar(\n name=\"redis\",\n command=[\"redis-server\", \"--port\", \"6380\"],\n health_check=\"http://localhost:6380/ping\",\n start_on=\"server_start\",\n health_timeout=30.0,\n)\ndef redis_sidecar():\n pass\n```\n\n| Parameter | Type | Default | Description |\n|-----------|------|---------|-------------|\n| `name` | `str` | required | Unique sidecar identifier |\n| `command` | `list[str]` | required | Process command and arguments |\n| `health_check` | `str` | `\"\"` | URL to poll for health (HTTP 200 = healthy) |\n| `start_on` | `str` | `\"first_tool_call\"` | `\"server_start\"` or `\"first_tool_call\"` |\n| `health_timeout` | `float` | `30.0` | Seconds to wait for health check to pass |\n\n### Lifecycle\n\n- **PID management**: PID files are stored in `~/.protomcp/sidecars/`.\n- **Graceful shutdown**: On exit, processes receive `SIGTERM` followed by `SIGKILL` if they do not stop within the shutdown timeout.\n- **Health checks**: If `health_check` is set, protomcp polls the URL until it returns 200 or the timeout expires.\n\n---\n\n## Workflows\n\nWorkflows are server-defined state machines that guide an agent through a multi-step process. At each point in the workflow, the agent only sees the valid next steps — all other tools are hidden. This prevents the agent from skipping ahead or calling steps out of order.\n\n### Defining a workflow\n\nUse the `@workflow` class decorator and `@step` method decorators to define a workflow. Each step is a method on the class.\n\n```python\nfrom protomcp import workflow, step, StepResult, tool, ToolResult\n\n@workflow(\"deploy\", allow_during=[\"status\"])\nclass DeployWorkflow:\n def __init__(self):\n self.pr_url = None\n\n @step(initial=True, next=[\"approve\", \"reject\"],\n description=\"Review changes before deployment\")\n def review(self, pr_url: str) -> StepResult:\n self.pr_url = pr_url\n return StepResult(result=f\"Reviewing {pr_url}: 5 files changed\")\n\n @step(next=[\"run_tests\"],\n description=\"Approve the changes for deployment\")\n def approve(self, reason: str) -> StepResult:\n return StepResult(result=f\"Approved: {reason}\")\n\n @step(terminal=True,\n description=\"Reject the changes\")\n def reject(self, reason: str) -> StepResult:\n return StepResult(result=f\"Rejected: {reason}\")\n\n @step(next=[\"promote\", \"rollback\"], no_cancel=True,\n description=\"Run test suite against staging\")\n def run_tests(self) -> StepResult:\n return StepResult(result=\"All 42 tests passed\", next=[\"promote\"])\n\n @step(terminal=True, no_cancel=True,\n description=\"Deploy to production\")\n def promote(self) -> StepResult:\n return StepResult(result=f\"Deployed {self.pr_url} to production\")\n\n @step(terminal=True,\n description=\"Roll back staging deployment\")\n def rollback(self) -> StepResult:\n return StepResult(result=\"Rolled back staging\")\n```\n\n### `@workflow` parameters\n\n| Parameter | Type | Default | Description |\n|-----------|------|---------|-------------|\n| `name` | `str` | required | Unique workflow identifier. Steps are registered as `name.step_name` tools |\n| `description` | `str` | `\"\"` | Human-readable description |\n| `allow_during` | `list[str]` | `None` | Glob patterns for external tools visible during the workflow |\n| `block_during` | `list[str]` | `None` | Glob patterns for external tools hidden during the workflow |\n\n### `@step` parameters\n\n| Parameter | Type | Default | Description |\n|-----------|------|---------|-------------|\n| `name` | `str` | method name | Step name. Defaults to the decorated method's name |\n| `description` | `str` | `\"\"` | Human-readable description |\n| `initial` | `bool` | `False` | Mark as the entry point. Exactly one step must be initial |\n| `next` | `list[str]` | `None` | Valid next step names. Required for non-terminal steps |\n| `terminal` | `bool` | `False` | Mark as an exit point. Terminal steps must not have `next` |\n| `no_cancel` | `bool` | `False` | Prevent cancellation while at this step |\n| `allow_during` | `list[str]` | `None` | Step-level visibility override (replaces workflow-level, does not merge) |\n| `block_during` | `list[str]` | `None` | Step-level block override (replaces workflow-level, does not merge) |\n| `on_error` | `dict[type, str]` | `None` | Map exception types to step names for error-driven transitions |\n| `requires` | `list[str]` | `None` | Required field names — validation fails if missing or empty |\n| `enum_fields` | `dict[str, list]` | `None` | Map of field name to valid values |\n\n### Step lifecycle\n\nEvery workflow must have exactly one `initial=True` step — this is the only step visible to the agent before the workflow starts. When the initial step is called, protomcp saves the current tool list, then restricts visibility to the declared `next` steps. This continues until a `terminal=True` step completes, at which point the original tool list is restored.\n\n### Dynamic `next` narrowing with `StepResult`\n\nA step handler returns a `StepResult`. The `next` field on the result can narrow the set of valid next steps at runtime, but it can only be a subset of the statically declared `next` on the `@step` decorator.\n\n```python\n@step(next=[\"promote\", \"rollback\"], description=\"Run tests\")\ndef run_tests(self) -> StepResult:\n if all_tests_passed():\n return StepResult(result=\"Tests passed\", next=[\"promote\"])\n return StepResult(result=\"Tests failed\", next=[\"rollback\"])\n```\n\n### `no_cancel` for committed steps\n\nSet `no_cancel=True` on a step to hide the cancel tool while the agent is choosing the next step. This is useful for steps that represent committed operations (e.g. a production deploy) where cancellation would leave the system in an inconsistent state.\n\n### Lifecycle hooks\n\nDefine `on_cancel` and `on_complete` methods on the workflow class to run cleanup or audit logic.\n\n```python\n@workflow(\"deploy\")\nclass DeployWorkflow:\n # ... steps ...\n\n def on_cancel(self, current_step, history):\n return f\"Deploy cancelled at step '{current_step}'\"\n\n def on_complete(self, history):\n steps = \" -> \".join(s[0] for s in history)\n print(f\"[audit] Deploy complete: {steps}\")\n```\n\n### Tool visibility: `allow_during` and `block_during`\n\nBy default, all external tools are hidden during a workflow. Use `allow_during` and `block_during` with glob patterns to control which external tools remain visible.\n\n```python\n@workflow(\"deploy\", allow_during=[\"status\", \"log_*\"])\nclass DeployWorkflow:\n ...\n```\n\nStep-level `allow_during` / `block_during` **replaces** the workflow-level setting entirely (it does not merge). This lets individual steps expose a different set of external tools.\n\n### Error handling\n\nBy default, if a step handler raises an exception, the workflow stays in the current state and the agent can retry. Use `on_error` to define exception-driven transitions to other steps:\n\n```python\n@step(next=[\"deploy\"], on_error={TimeoutError: \"rollback\"},\n description=\"Run smoke tests\")\ndef smoke_test(self) -> StepResult:\n run_smoke_tests() # may raise TimeoutError\n return StepResult(result=\"Smoke tests passed\")\n```\n\n---\n\n## Handler Discovery\n\nAuto-discover handler files from a directory instead of importing them manually.\n\n```python\nfrom protomcp.discovery import configure\n\nconfigure(handlers_dir=\"./handlers\", hot_reload=True)\n```\n\n| Parameter | Type | Default | Description |\n|-----------|------|---------|-------------|\n| `handlers_dir` | `str` | `\"\"` | Path to the directory containing handler files |\n| `hot_reload` | `bool` | `False` | Re-import handlers on each discovery pass |\n\n### Behavior\n\n- All `.py` files in `handlers_dir` are imported automatically.\n- Files prefixed with `_` (e.g. `_helpers.py`) are skipped.\n- With `hot_reload=True`, previously loaded modules are cleared and re-imported on each discovery pass.","src/content/docs/guides/writing-tools-python.mdx","ef97c08b9359a76d","guides/writing-tools-python.mdx","guides/writing-a-language-library",{"id":323,"data":325,"body":331,"filePath":332,"digest":333,"legacyId":334,"deferredRender":16},{"title":326,"description":327,"editUrl":16,"head":328,"template":35,"sidebar":329,"pagefind":16,"draft":23},"Writing a Language Library","Build a protomcp SDK for any language using the protobuf contract and unix socket protocol.",[],{"hidden":23,"attrs":330},{},"protomcp is language-agnostic. If you want to write tools in a language that doesn't have an SDK yet, you can build one by implementing the protobuf protocol over unix sockets.\n\n## Architecture\n\n```\nMCP Host ←→ pmcp (Go binary, JSON-RPC/stdio) ←→ Tool Process (your language, protobuf/unix socket)\n```\n\nThe Go binary (`pmcp`) handles all MCP protocol details. Your SDK only needs to:\n\n1. Connect to a unix socket\n2. Send and receive length-prefixed protobuf messages\n3. Handle a small set of message types\n\n---\n\n## The protobuf contract\n\nAll messages are wrapped in an `Envelope` message. The proto definition is at `proto/protomcp.proto`.\n\n```protobuf\nmessage Envelope {\n oneof msg {\n ReloadRequest reload = 1;\n ListToolsRequest list_tools = 2;\n CallToolRequest call_tool = 3;\n // ... (see proto/protomcp.proto for full list)\n }\n string request_id = 14;\n string namespace = 15;\n}\n```\n\n---\n\n## Wire format\n\nMessages use 4-byte big-endian length-prefixed framing:\n\n```\n[4 bytes: uint32 big-endian length][N bytes: serialized Envelope]\n```\n\n### Sending a message\n\n1. Serialize the `Envelope` to bytes using protobuf\n2. Write a 4-byte big-endian uint32 of the serialized length\n3. Write the serialized bytes\n\n### Receiving a message\n\n1. Read 4 bytes, interpret as big-endian uint32 for the message length\n2. Read that many bytes\n3. Deserialize as an `Envelope` protobuf message\n\n---\n\n## Connection\n\nThe Go binary passes the unix socket path via the `PROTOMCP_SOCKET` environment variable. Your SDK should:\n\n1. Read `PROTOMCP_SOCKET` from the environment\n2. Connect to that unix socket path\n3. Begin the handshake protocol\n\n---\n\n## Handshake protocol\n\nThe handshake establishes the tool list and any middleware:\n\n```\nGo binary Tool process\n │ │\n ├── ListToolsRequest ───────────────>│\n │ │\n │\u003C──────────────── ToolListResponse ─┤\n │ │\n │ (optional middleware registration)│\n │\u003C── RegisterMiddlewareRequest ──────┤\n │ │\n │── RegisterMiddlewareResponse ─────>│\n │ │\n │\u003C──────────────── ReloadResponse ───┤ (handshake-complete signal)\n │ │\n```\n\n### Steps\n\n1. **Receive `ListToolsRequest`**: The Go binary asks for the tool list\n2. **Send `ToolListResponse`**: Respond with all registered tools (name, description, input_schema, hints)\n3. **(Optional) Send `RegisterMiddlewareRequest`**: Register any custom middleware. Wait for `RegisterMiddlewareResponse` acknowledgment for each\n4. **Send `ReloadResponse`**: This signals handshake complete. The Go binary will wait up to 500ms for this signal (for backward compatibility with v1.0 SDKs that don't send it)\n\n---\n\n## Tool calls\n\nAfter the handshake, the Go binary sends `CallToolRequest` messages when tools are invoked:\n\n```\nGo binary Tool process\n │ │\n ├── CallToolRequest ────────────────>│\n │ (name, arguments_json, │\n │ progress_token, request_id) │\n │ │\n │ (optional progress notifications)│\n │\u003C──────── ProgressNotification ─────┤\n │ │\n │\u003C──────────── CallToolResponse ─────┤\n │ (result_text, is_error, │\n │ error details, enable/disable) │\n```\n\n### Handling a tool call\n\n1. Parse `CallToolRequest.name` to find the matching tool\n2. Parse `CallToolRequest.arguments_json` as JSON\n3. Execute the tool handler\n4. Optionally send `ProgressNotification` messages during execution\n5. Send `CallToolResponse` with the result\n\n### Progress notifications\n\nIf the request includes a `progress_token`, you can send progress updates:\n\n```protobuf\nmessage ProgressNotification {\n string progress_token = 1;\n int64 progress = 2;\n int64 total = 3;\n string message = 4;\n}\n```\n\n### Cancellation\n\nThe Go binary may send a `CancelRequest` with a matching `request_id`. Set a flag that the handler can check via `is_cancelled()`.\n\n---\n\n## Reload\n\nWhen the user changes the tool file and saves, the Go binary sends a `ReloadRequest`. Your SDK should:\n\n1. Re-register all tools (re-run decorators, builders, etc.)\n2. Go through the handshake again (send `ToolListResponse`, optional middleware, then `ReloadResponse`)\n\n---\n\n## Server logging\n\nSend `LogMessage` to the Go binary at any time:\n\n```protobuf\nmessage LogMessage {\n string level = 1; // debug, info, notice, warning, error, critical, alert, emergency\n string logger = 2; // logger name (e.g., \"mylib\")\n string data_json = 3; // JSON-encoded log data\n}\n```\n\n---\n\n## Tool list management\n\nSend these messages to dynamically modify which tools are active:\n\n| Message | Effect |\n|---------|--------|\n| `EnableToolsRequest` | Add tools to the active set |\n| `DisableToolsRequest` | Remove tools from the active set |\n| `SetAllowedRequest` | Switch to allowlist mode with these tools |\n| `SetBlockedRequest` | Switch to blocklist mode, blocking these tools |\n| `BatchUpdateRequest` | Multiple operations atomically |\n\n---\n\n## Testing your SDK\n\nBuild a tool with your SDK and test it against the Go binary:\n\n```sh\n# Build the binary\ngo build -o pmcp ./cmd/protomcp/\n\n# Run your tool\npmcp dev path/to/your/tool_file\n\n# In another terminal, the MCP host can connect via stdio\n```\n\nUse `pmcp validate path/to/your/tool_file` to validate tool definitions without starting the server.\n\n---\n\n## Reference implementations\n\nStudy the existing SDKs for patterns:\n\n| Language | Path | Pattern |\n|----------|------|---------|\n| Python | `sdk/python/` | `@tool()` decorator, type hint schema generation |\n| TypeScript | `sdk/typescript/` | `tool()` function, Zod schema conversion |\n| Go | `sdk/go/` | `Tool()` with functional options |\n| Rust | `sdk/rust/` | `tool()` builder pattern with `.register()` |","src/content/docs/guides/writing-a-language-library.mdx","f7c1b4c4bf2b076f","guides/writing-a-language-library.mdx","guides/writing-tools-typescript",{"id":335,"data":337,"body":343,"filePath":344,"digest":345,"legacyId":346,"deferredRender":16},{"title":338,"description":339,"editUrl":16,"head":340,"template":35,"sidebar":341,"pagefind":16,"draft":23},"Writing Tools (TypeScript)","Use tool(), Zod schemas, ToolResult, ToolContext, ServerLogger, and toolManager in TypeScript.",[],{"hidden":23,"attrs":342},{},"## Installation\n\n```sh\nnpm install protomcp zod\n```\n\n---\n\n## The `tool()` function\n\nRegister a tool by calling `tool()` with a description, Zod schema for arguments, and a handler.\n\n```typescript\nimport { tool, ToolResult } from 'protomcp';\nimport { z } from 'zod';\n\ntool({\n description: 'Add two numbers',\n args: z.object({\n a: z.number().describe('First number'),\n b: z.number().describe('Second number'),\n }),\n handler({ a, b }) {\n return new ToolResult({ result: String(a + b) });\n },\n});\n```\n\nThe tool name is inferred from the handler function name. Use `name` to set it explicitly:\n\n```typescript\ntool({\n name: 'my_tool',\n description: 'A tool with an explicit name',\n args: z.object({ input: z.string() }),\n handler({ input }) {\n return new ToolResult({ result: input.toUpperCase() });\n },\n});\n```\n\n### Tool metadata\n\nPass additional options to provide metadata hints to the MCP host:\n\n```typescript\ntool({\n description: 'Delete a file from disk',\n title: 'Delete File',\n destructiveHint: true,\n idempotentHint: false,\n readOnlyHint: false,\n openWorldHint: false,\n taskSupport: false,\n args: z.object({ path: z.string() }),\n async handler({ path }) {\n await fs.unlink(path);\n return new ToolResult({ result: `Deleted ${path}` });\n },\n});\n```\n\n| Option | Type | Default | Description |\n|--------|------|---------|-------------|\n| `description` | `string` | required | Human-readable description |\n| `args` | `z.ZodObject\u003Cany>` | required | Zod schema for input arguments |\n| `handler` | `(args) => any` | required | Function called when the tool is invoked |\n| `name` | `string` | inferred | Explicit tool name |\n| `title` | `string` | `\"\"` | Display name shown in the MCP host UI |\n| `destructiveHint` | `boolean` | `false` | Hint: the tool has destructive side effects |\n| `idempotentHint` | `boolean` | `false` | Hint: calling the tool multiple times has the same effect as once |\n| `readOnlyHint` | `boolean` | `false` | Hint: the tool does not modify state |\n| `openWorldHint` | `boolean` | `false` | Hint: the tool may access resources outside the current context |\n| `taskSupport` | `boolean` | `false` | Hint: the tool supports long-running async task semantics |\n| `output` | `z.ZodObject\u003Cany>` | — | Zod schema for structured output |\n\n### Zod schemas for input validation\n\nAll argument types are defined using Zod. protomcp converts the Zod schema to JSON Schema automatically using `zod-to-json-schema`.\n\n```typescript\ntool({\n description: 'Search documents',\n args: z.object({\n query: z.string().describe('Search query'),\n limit: z.number().int().min(1).max(100).default(10),\n includeArchived: z.boolean().default(false),\n }),\n async handler({ query, limit, includeArchived }) {\n const results = await search(query, { limit, includeArchived });\n return new ToolResult({ result: JSON.stringify(results) });\n },\n});\n```\n\n### Async handlers\n\nHandlers can be `async`:\n\n```typescript\ntool({\n description: 'Fetch a URL',\n args: z.object({ url: z.string().url() }),\n async handler({ url }) {\n const resp = await fetch(url);\n const text = await resp.text();\n return new ToolResult({ result: text });\n },\n});\n```\n\n---\n\n## `ToolResult`\n\n```typescript\nclass ToolResult {\n result: string;\n isError: boolean;\n enableTools?: string[];\n disableTools?: string[];\n errorCode?: string;\n message?: string;\n suggestion?: string;\n retryable: boolean;\n\n constructor(options?: {\n result?: string;\n isError?: boolean;\n enableTools?: string[];\n disableTools?: string[];\n errorCode?: string;\n message?: string;\n suggestion?: string;\n retryable?: boolean;\n });\n}\n```\n\n### Success\n\n```typescript\nreturn new ToolResult({ result: 'done' });\n```\n\n### Structured error\n\n```typescript\nreturn new ToolResult({\n isError: true,\n errorCode: 'NOT_FOUND',\n message: 'The file /tmp/data.csv does not exist',\n suggestion: 'Check the path and try again',\n retryable: false,\n});\n```\n\n### Enabling / disabling tools from a result\n\n```typescript\ntool({\n description: 'Log in and unlock tools',\n args: z.object({ username: z.string(), password: z.string() }),\n async handler({ username, password }) {\n if (!await authenticate(username, password)) {\n return new ToolResult({ isError: true, message: 'Authentication failed' });\n }\n return new ToolResult({\n result: 'Logged in',\n enableTools: ['delete_file', 'write_file'],\n });\n },\n});\n```\n\n---\n\n## `toolManager`\n\nUse `toolManager` to modify the active tool list during a tool call.\n\n```typescript\nimport { tool, ToolResult, toolManager } from 'protomcp';\nimport { z } from 'zod';\n\ntool({\n description: 'Enable debug tools',\n args: z.object({}),\n async handler() {\n const active = await toolManager.enable(['debug_dump', 'trace_calls']);\n return new ToolResult({ result: `Active: ${active.join(', ')}` });\n },\n});\n```\n\n### `toolManager` API\n\nAll methods return `Promise\u003Cstring[]>` — the list of currently active tool names.\n\n```typescript\ntoolManager.enable(toolNames: string[]): Promise\u003Cstring[]>\ntoolManager.disable(toolNames: string[]): Promise\u003Cstring[]>\ntoolManager.setAllowed(toolNames: string[]): Promise\u003Cstring[]>\ntoolManager.setBlocked(toolNames: string[]): Promise\u003Cstring[]>\ntoolManager.getActiveTools(): Promise\u003Cstring[]>\ntoolManager.batch(options: {\n enable?: string[];\n disable?: string[];\n allow?: string[];\n block?: string[];\n}): Promise\u003Cstring[]>\n```\n\n### Batch operations\n\n```typescript\nconst active = await toolManager.batch({\n enable: ['write_file'],\n disable: ['read_only_mode'],\n});\n```\n\n---\n\n## Progress Reporting\n\nUse `ToolContext` to report progress during a long-running tool call. protomcp injects it — access it via the second argument to `handler`.\n\n```typescript\nimport { tool, ToolResult, ToolContext } from 'protomcp';\nimport { z } from 'zod';\n\ntool({\n description: 'Process a large dataset',\n args: z.object({ filePath: z.string() }),\n async handler({ filePath }, ctx: ToolContext) {\n const rows = await loadRows(filePath);\n const total = rows.length;\n for (let i = 0; i \u003C rows.length; i++) {\n if (ctx.isCancelled()) {\n return new ToolResult({ isError: true, message: 'Cancelled' });\n }\n await processRow(rows[i]);\n ctx.reportProgress(i + 1, total, `Processing row ${i + 1}/${total}`);\n }\n return new ToolResult({ result: `Processed ${total} rows` });\n },\n});\n```\n\n### `ToolContext` API\n\n| Method | Signature | Description |\n|--------|-----------|-------------|\n| `reportProgress` | `(progress: number, total?: number, message?: string) => void` | Send a progress notification to the MCP host |\n| `isCancelled` | `() => boolean` | Returns `true` if the MCP host has cancelled this call |\n\n`reportProgress` is a no-op if no `progressToken` was provided by the host.\n\n---\n\n## Server Logging\n\nUse `ServerLogger` to send structured log messages back to the MCP host.\n\n```typescript\nimport { tool, ToolResult, ServerLogger } from 'protomcp';\nimport { z } from 'zod';\n\nconst logger = new ServerLogger(/* injected by protomcp */);\n\ntool({\n description: 'Fetch remote data',\n args: z.object({ url: z.string().url() }),\n async handler({ url }) {\n logger.info('Fetching URL', { url });\n try {\n const resp = await fetch(url);\n const text = await resp.text();\n logger.debug('Fetch complete', { bytes: text.length });\n return new ToolResult({ result: text });\n } catch (e) {\n logger.error('Fetch failed', { url, error: String(e) });\n return new ToolResult({ isError: true, message: String(e) });\n }\n },\n});\n```\n\n### `ServerLogger` API\n\nAll methods accept a message string and an optional data object (serialized to JSON).\n\n| Method | Level |\n|--------|-------|\n| `debug(msg, data?)` | `debug` |\n| `info(msg, data?)` | `info` |\n| `notice(msg, data?)` | `notice` |\n| `warning(msg, data?)` | `warning` |\n| `error(msg, data?)` | `error` |\n| `critical(msg, data?)` | `critical` |\n| `alert(msg, data?)` | `alert` |\n| `emergency(msg, data?)` | `emergency` |\n\n---\n\n## Structured Output\n\nUse `output` in `tool()` to declare a structured output schema using Zod. protomcp converts it to JSON Schema automatically.\n\n```typescript\nimport { tool, ToolResult } from 'protomcp';\nimport { z } from 'zod';\n\nconst SearchResult = z.object({\n title: z.string(),\n url: z.string(),\n score: z.number(),\n});\n\ntool({\n description: 'Search the web',\n output: SearchResult,\n args: z.object({ query: z.string() }),\n async handler({ query }) {\n const results = await runSearch(query);\n return new ToolResult({ result: JSON.stringify(results) });\n },\n});\n```\n\n---\n\n## Cancellation\n\nCheck `ctx.isCancelled()` periodically in long-running tools to stop early when the MCP host cancels the request.\n\n```typescript\nimport { tool, ToolResult, ToolContext } from 'protomcp';\nimport { z } from 'zod';\n\ntool({\n description: 'Run a slow computation',\n args: z.object({ n: z.number().int() }),\n async handler({ n }, ctx: ToolContext) {\n let result = 0;\n for (let i = 0; i \u003C n; i++) {\n if (ctx.isCancelled()) {\n return new ToolResult({ isError: true, message: 'Cancelled by host' });\n }\n result += await expensiveStep(i);\n }\n return new ToolResult({ result: String(result) });\n },\n});\n```\n\nCancellation is cooperative — protomcp sets the cancelled flag and your tool is responsible for checking it.\n\n---\n\n## Testing tools\n\nUse `clearRegistry()` between tests to avoid cross-test contamination:\n\n```typescript\nimport { tool, ToolResult, clearRegistry, getRegisteredTools } from 'protomcp';\nimport { z } from 'zod';\n\nbeforeEach(() => clearRegistry());\n\ntest('add tool returns sum', async () => {\n tool({\n name: 'add',\n description: 'Add two numbers',\n args: z.object({ a: z.number(), b: z.number() }),\n handler({ a, b }) {\n return new ToolResult({ result: String(a + b) });\n },\n });\n\n const tools = getRegisteredTools();\n expect(tools).toHaveLength(1);\n expect(tools[0].name).toBe('add');\n\n const result = await tools[0].handler({ a: 2, b: 3 });\n expect(result.result).toBe('5');\n expect(result.isError).toBe(false);\n});\n```\n\n---\n\n## Extended Schema Types\n\nZod provides built-in support for complex types that map to JSON Schema automatically:\n\n| Zod type | JSON Schema |\n|----------|-------------|\n| `z.array(z.string())` | `{\"type\": \"array\", \"items\": {\"type\": \"string\"}}` |\n| `z.record(z.number())` | `{\"type\": \"object\", \"additionalProperties\": {\"type\": \"number\"}}` |\n| `z.union([z.string(), z.number()])` | `{\"anyOf\": [...]}` |\n| `z.enum([\"a\", \"b\"])` | `{\"type\": \"string\", \"enum\": [\"a\", \"b\"]}` |\n| `z.string().optional()` | `{\"type\": \"string\"}`, not required |\n\n---\n\n## Tool Groups\n\nGroup related actions under a single tool using `toolGroup()`.\n\n```typescript\nimport { toolGroup, ToolResult } from 'protomcp';\nimport { z } from 'zod';\n\ntoolGroup({\n name: 'files',\n description: 'File operations',\n strategy: 'union',\n actions: [\n {\n name: 'read',\n description: 'Read a file',\n args: z.object({ path: z.string() }),\n handler({ path }) {\n return new ToolResult({ result: readFileSync(path, 'utf8') });\n },\n },\n {\n name: 'write',\n description: 'Write a file',\n args: z.object({ path: z.string(), content: z.string() }),\n handler({ path, content }) {\n writeFileSync(path, content);\n return new ToolResult({ result: `Wrote ${path}` });\n },\n },\n ],\n});\n```\n\n### Strategy: union (default)\n\nWith `strategy: 'union'`, the group registers as a single tool with a discriminated `oneOf` schema. The caller passes an `action` field to select the action.\n\n### Strategy: separate\n\nWith `strategy: 'separate'`, each action becomes its own tool, namespaced as `group.action` (e.g. `files.read`, `files.write`).\n\n### Dispatch and fuzzy matching\n\nWhen an unknown action is passed, the dispatcher returns an error with a fuzzy \"Did you mean?\" suggestion.\n\n---\n\n## Declarative Validation\n\nDeclare validation rules on actions to validate input before the handler runs.\n\n### `requires`\n\n```typescript\n{\n name: 'deploy',\n requires: ['env', 'version'],\n args: z.object({ env: z.string(), version: z.string() }),\n handler({ env, version }) { ... },\n}\n```\n\n### `enumFields`\n\nRestrict a field to a set of valid values. Invalid values trigger a \"Did you mean?\" suggestion.\n\n```typescript\n{\n name: 'set_env',\n enumFields: { env: ['dev', 'staging', 'prod'] },\n args: z.object({ env: z.string() }),\n handler({ env }) { ... },\n}\n```\n\n### `crossRules`\n\nValidate relationships between parameters.\n\n```typescript\n{\n name: 'scale',\n crossRules: [\n { check: (args) => args.min > args.max, message: 'min must be \u003C= max' },\n ],\n args: z.object({ min: z.number(), max: z.number() }),\n handler({ min, max }) { ... },\n}\n```\n\n### `hints`\n\nNon-blocking advisory messages appended to the result when a condition is met.\n\n```typescript\n{\n name: 'query',\n hints: {\n slow_warning: {\n condition: (args) => args.limit > 1000,\n message: 'Large limit may cause slow queries',\n },\n },\n args: z.object({ table: z.string(), limit: z.number().default(100) }),\n handler({ table, limit }) { ... },\n}\n```\n\n---\n\n## Server Context\n\nRegister resolvers that inject shared parameters into tool handlers automatically.\n\n```typescript\nimport { serverContext } from 'protomcp';\n\nserverContext('project_dir', {\n expose: false,\n resolver: (args) => process.cwd(),\n});\n```\n\nWith `expose: false`, the parameter is hidden from the tool schema sent to the MCP host but still injected into the handler arguments.\n\n---\n\n## Local Middleware\n\nWrap tool handlers with in-process middleware for cross-cutting concerns.\n\n```typescript\nimport { localMiddleware, ToolResult } from 'protomcp';\n\nlocalMiddleware(10, async (ctx, toolName, args, next) => {\n const start = Date.now();\n const result = await next(ctx, args);\n console.log(`${toolName} took ${Date.now() - start}ms`);\n return result;\n});\n```\n\n### Priority chain\n\nMiddleware is sorted by priority (lowest first = outermost). Return a `ToolResult` directly to short-circuit the chain.\n\n### Local vs Go-bridge middleware\n\nLocal middleware runs in-process in Node.js. Go-bridge middleware runs cross-process via the Go transport layer.\n\n---\n\n## Telemetry\n\nObserve tool calls with fail-safe telemetry sinks. Exceptions in sinks are silently swallowed.\n\n```typescript\nimport { telemetrySink, ToolCallEvent } from 'protomcp';\n\ntelemetrySink((event: ToolCallEvent) => {\n console.log(`[${event.phase}] ${event.toolName}: ${event.message}`);\n});\n```\n\n### `ToolCallEvent` phases\n\n| Phase | When |\n|-------|------|\n| `\"start\"` | Before the handler runs |\n| `\"success\"` | After the handler returns successfully |\n| `\"error\"` | After the handler raises or returns an error |\n| `\"progress\"` | When `reportProgress` is called |\n\n---\n\n## Sidecar Management\n\nDeclare companion processes that protomcp manages alongside your server.\n\n```typescript\nimport { sidecar } from 'protomcp';\n\nsidecar({\n name: 'redis',\n command: ['redis-server', '--port', '6380'],\n healthCheck: 'http://localhost:6380/ping',\n startOn: 'server_start',\n healthTimeout: 30000,\n});\n```\n\n| Option | Type | Default | Description |\n|--------|------|---------|-------------|\n| `name` | `string` | required | Unique sidecar identifier |\n| `command` | `string[]` | required | Process command and arguments |\n| `healthCheck` | `string` | `\"\"` | URL to poll for health (HTTP 200 = healthy) |\n| `startOn` | `string` | `\"first_tool_call\"` | `\"server_start\"` or `\"first_tool_call\"` |\n| `healthTimeout` | `number` | `30000` | Milliseconds to wait for health check to pass |\n\nPID files are stored in `~/.protomcp/sidecars/`. Processes receive `SIGTERM` on shutdown, followed by `SIGKILL` if they do not stop within the timeout.\n\n---\n\n## Handler Discovery\n\nAuto-discover handler files from a directory instead of importing them manually.\n\n```typescript\nimport { configure } from 'protomcp';\n\nconfigure({ handlersDir: './handlers', hotReload: true });\n```\n\n- All `.ts` and `.js` files in `handlersDir` are imported automatically.\n- Files prefixed with `_` (e.g. `_helpers.ts`) are skipped.\n- With `hotReload: true`, previously loaded modules are cleared and re-imported on each discovery pass.","src/content/docs/guides/writing-tools-typescript.mdx","8467deb32a9e5689","guides/writing-tools-typescript.mdx","guides/writing-tools-go",{"id":347,"data":349,"body":355,"filePath":356,"digest":357,"legacyId":358,"deferredRender":16},{"title":350,"description":351,"editUrl":16,"head":352,"template":35,"sidebar":353,"pagefind":16,"draft":23},"Writing Tools (Go)","Use Tool(), functional options, ToolResult, ToolContext, ServerLogger, and ToolManager in Go.",[],{"hidden":23,"attrs":354},{},"## Installation\n\n```sh\ngo get github.com/msilverblatt/protomcp/sdk/go\n```\n\n---\n\n## The `Tool()` function\n\nRegister tools using `Tool()` with functional options. The first argument is the tool name.\n\n```go\npackage main\n\nimport (\n\t\"fmt\"\n\t\"github.com/msilverblatt/protomcp/sdk/go/protomcp\"\n)\n\nfunc main() {\n\tprotomcp.Tool(\"add\",\n\t\tprotomcp.Description(\"Add two numbers\"),\n\t\tprotomcp.Args(\n\t\t\tprotomcp.IntArg(\"a\"),\n\t\t\tprotomcp.IntArg(\"b\"),\n\t\t),\n\t\tprotomcp.Handler(func(ctx protomcp.ToolContext, args map[string]interface{}) protomcp.ToolResult {\n\t\t\ta := int(args[\"a\"].(float64))\n\t\t\tb := int(args[\"b\"].(float64))\n\t\t\treturn protomcp.Result(fmt.Sprintf(\"%d\", a+b))\n\t\t}),\n\t)\n\n\tprotomcp.Run()\n}\n```\n\n### Tool metadata\n\nPass hint options to provide metadata to the MCP host:\n\n```go\nprotomcp.Tool(\"delete_file\",\n\tprotomcp.Description(\"Delete a file from disk\"),\n\tprotomcp.DestructiveHint(true),\n\tprotomcp.IdempotentHint(false),\n\tprotomcp.ReadOnlyHint(false),\n\tprotomcp.OpenWorldHint(false),\n\tprotomcp.Args(protomcp.StrArg(\"path\")),\n\tprotomcp.Handler(func(ctx protomcp.ToolContext, args map[string]interface{}) protomcp.ToolResult {\n\t\tpath := args[\"path\"].(string)\n\t\tos.Remove(path)\n\t\treturn protomcp.Result(fmt.Sprintf(\"Deleted %s\", path))\n\t}),\n)\n```\n\n| Option | Description |\n|--------|-------------|\n| `Description(s)` | Human-readable description (required) |\n| `Title(s)` | Display name shown in the MCP host UI |\n| `DestructiveHint(bool)` | Hint: the tool has destructive side effects |\n| `IdempotentHint(bool)` | Hint: calling multiple times has the same effect as once |\n| `ReadOnlyHint(bool)` | Hint: the tool does not modify state |\n| `OpenWorldHint(bool)` | Hint: the tool may access external resources |\n| `TaskSupportHint(bool)` | Hint: the tool supports long-running async task semantics |\n\n### Argument types\n\n| Function | JSON Schema type |\n|----------|-----------------|\n| `IntArg(name)` | `\"integer\"` |\n| `StrArg(name)` | `\"string\"` |\n| `NumArg(name)` | `\"number\"` |\n| `BoolArg(name)` | `\"boolean\"` |\n| `ArrayArg(name, itemType)` | `{\"type\": \"array\", \"items\": \u003CitemType>}` |\n| `UnionArg(name, types...)` | `{\"anyOf\": [...]}` |\n| `LiteralArg(name, values...)` | `{\"type\": \"string\", \"enum\": [...]}` |\n\nArguments are passed to the handler as `map[string]interface{}`. JSON numbers are `float64` in Go — cast with `int()` when expecting integers.\n\n---\n\n## `ToolResult`\n\nAll handlers return a `ToolResult`.\n\n### Success\n\n```go\nreturn protomcp.Result(\"done\")\n```\n\n### Structured error\n\n```go\nreturn protomcp.ErrorResult(\n\t\"The file /tmp/data.csv does not exist\",\n\t\"NOT_FOUND\",\n\t\"Check the path and try again\",\n\tfalse,\n)\n```\n\n| Field | Description |\n|-------|-------------|\n| `ResultText` | The result string |\n| `IsError` | Whether this is an error |\n| `ErrorCode` | Machine-readable error code |\n| `Message` | Human-readable error message |\n| `Suggestion` | Actionable suggestion for the caller |\n| `Retryable` | Whether the caller should retry |\n| `EnableTools` | Tool names to enable after this call |\n| `DisableTools` | Tool names to disable after this call |\n\n### Enabling / disabling tools from a result\n\n```go\nprotomcp.Tool(\"login\",\n\tprotomcp.Description(\"Log in and unlock tools\"),\n\tprotomcp.Args(protomcp.StrArg(\"username\"), protomcp.StrArg(\"password\")),\n\tprotomcp.Handler(func(ctx protomcp.ToolContext, args map[string]interface{}) protomcp.ToolResult {\n\t\tusername := args[\"username\"].(string)\n\t\tpassword := args[\"password\"].(string)\n\t\tif !authenticate(username, password) {\n\t\t\treturn protomcp.ErrorResult(\"Authentication failed\", \"AUTH_FAILED\", \"\", false)\n\t\t}\n\t\tr := protomcp.Result(\"Logged in\")\n\t\tr.EnableTools = []string{\"delete_file\", \"write_file\"}\n\t\treturn r\n\t}),\n)\n```\n\n---\n\n## Progress Reporting\n\nUse `ToolContext` to report progress during long-running tool calls.\n\n```go\nprotomcp.Tool(\"process_data\",\n\tprotomcp.Description(\"Process a large dataset\"),\n\tprotomcp.Args(protomcp.StrArg(\"file_path\")),\n\tprotomcp.Handler(func(ctx protomcp.ToolContext, args map[string]interface{}) protomcp.ToolResult {\n\t\tfilePath := args[\"file_path\"].(string)\n\t\trows := loadRows(filePath)\n\t\ttotal := len(rows)\n\t\tfor i, row := range rows {\n\t\t\tif ctx.IsCancelled() {\n\t\t\t\treturn protomcp.ErrorResult(\"Cancelled\", \"CANCELLED\", \"\", false)\n\t\t\t}\n\t\t\tprocessRow(row)\n\t\t\tctx.ReportProgress(i+1, total)\n\t\t}\n\t\treturn protomcp.Result(fmt.Sprintf(\"Processed %d rows\", total))\n\t}),\n)\n```\n\n### `ToolContext` API\n\n| Method | Description |\n|--------|-------------|\n| `ReportProgress(progress, total int)` | Send a progress notification to the MCP host |\n| `IsCancelled() bool` | Returns `true` if the MCP host has cancelled this call |\n\n---\n\n## Server Logging\n\nUse `ServerLogger` to send structured log messages back to the MCP host.\n\n```go\nprotomcp.Tool(\"fetch_data\",\n\tprotomcp.Description(\"Fetch remote data\"),\n\tprotomcp.Args(protomcp.StrArg(\"url\")),\n\tprotomcp.Handler(func(ctx protomcp.ToolContext, args map[string]interface{}) protomcp.ToolResult {\n\t\turl := args[\"url\"].(string)\n\t\tprotomcp.Log.Info(fmt.Sprintf(\"Fetching %s\", url))\n\t\tdata, err := download(url)\n\t\tif err != nil {\n\t\t\tprotomcp.Log.Error(fmt.Sprintf(\"Fetch failed: %v\", err))\n\t\t\treturn protomcp.ErrorResult(err.Error(), \"FETCH_FAILED\", \"\", true)\n\t\t}\n\t\tprotomcp.Log.Debug(fmt.Sprintf(\"Fetched %d bytes\", len(data)))\n\t\treturn protomcp.Result(data)\n\t}),\n)\n```\n\n### Log levels\n\n| Method | Level |\n|--------|-------|\n| `Log.Debug(msg)` | `debug` |\n| `Log.Info(msg)` | `info` |\n| `Log.Notice(msg)` | `notice` |\n| `Log.Warning(msg)` | `warning` |\n| `Log.Error(msg)` | `error` |\n| `Log.Critical(msg)` | `critical` |\n| `Log.Alert(msg)` | `alert` |\n| `Log.Emergency(msg)` | `emergency` |\n\n---\n\n## Cancellation\n\nCheck `ctx.IsCancelled()` periodically in long-running tools to stop early when the MCP host cancels the request.\n\n```go\nprotomcp.Tool(\"slow_compute\",\n\tprotomcp.Description(\"Run a slow computation\"),\n\tprotomcp.Args(protomcp.IntArg(\"n\")),\n\tprotomcp.Handler(func(ctx protomcp.ToolContext, args map[string]interface{}) protomcp.ToolResult {\n\t\tn := int(args[\"n\"].(float64))\n\t\tresult := 0\n\t\tfor i := 0; i \u003C n; i++ {\n\t\t\tif ctx.IsCancelled() {\n\t\t\t\treturn protomcp.ErrorResult(\"Cancelled by host\", \"CANCELLED\", \"\", false)\n\t\t\t}\n\t\t\tresult += expensiveStep(i)\n\t\t}\n\t\treturn protomcp.Result(fmt.Sprintf(\"%d\", result))\n\t}),\n)\n```\n\nCancellation is cooperative — protomcp sets the cancelled flag and your tool is responsible for checking it.\n\n---\n\n## Testing tools\n\nUse `ClearRegistry()` between tests:\n\n```go\nfunc TestAdd(t *testing.T) {\n\tprotomcp.ClearRegistry()\n\n\tprotomcp.Tool(\"add\",\n\t\tprotomcp.Description(\"Add two numbers\"),\n\t\tprotomcp.Args(protomcp.IntArg(\"a\"), protomcp.IntArg(\"b\")),\n\t\tprotomcp.Handler(func(ctx protomcp.ToolContext, args map[string]interface{}) protomcp.ToolResult {\n\t\t\ta := int(args[\"a\"].(float64))\n\t\t\tb := int(args[\"b\"].(float64))\n\t\t\treturn protomcp.Result(fmt.Sprintf(\"%d\", a+b))\n\t\t}),\n\t)\n\n\ttools := protomcp.GetRegisteredTools()\n\tif len(tools) != 1 {\n\t\tt.Fatalf(\"expected 1 tool, got %d\", len(tools))\n\t}\n\tif tools[0].Name != \"add\" {\n\t\tt.Fatalf(\"expected tool name 'add', got %q\", tools[0].Name)\n\t}\n}\n```\n\n---\n\n## Tool Groups\n\nGroup related actions under a single tool using `ToolGroup()`.\n\n```go\nprotomcp.ToolGroup(\"files\",\n\tprotomcp.Description(\"File operations\"),\n\tprotomcp.Strategy(\"union\"),\n\tprotomcp.Action(\"read\",\n\t\tprotomcp.ActionDescription(\"Read a file\"),\n\t\tprotomcp.Args(protomcp.StrArg(\"path\")),\n\t\tprotomcp.ActionHandler(func(ctx protomcp.ToolContext, args map[string]interface{}) protomcp.ToolResult {\n\t\t\tpath := args[\"path\"].(string)\n\t\t\treturn protomcp.Result(readFile(path))\n\t\t}),\n\t),\n\tprotomcp.Action(\"write\",\n\t\tprotomcp.ActionDescription(\"Write a file\"),\n\t\tprotomcp.Args(protomcp.StrArg(\"path\"), protomcp.StrArg(\"content\")),\n\t\tprotomcp.ActionHandler(func(ctx protomcp.ToolContext, args map[string]interface{}) protomcp.ToolResult {\n\t\t\treturn protomcp.Result(\"written\")\n\t\t}),\n\t),\n)\n```\n\n### Strategy: union (default)\n\nWith `Strategy(\"union\")`, the group registers as a single tool with a discriminated `oneOf` schema. The caller passes an `action` field to select the action.\n\n### Strategy: separate\n\nWith `Strategy(\"separate\")`, each action becomes its own tool, namespaced as `group.action` (e.g. `files.read`, `files.write`).\n\n### Dispatch and fuzzy matching\n\nWhen an unknown action is passed, the dispatcher returns an error with a fuzzy \"Did you mean?\" suggestion.\n\n---\n\n## Declarative Validation\n\nDeclare validation rules on `Action()` to validate input before the handler runs.\n\n### Requires\n\n```go\nprotomcp.Action(\"deploy\",\n\tprotomcp.Requires(\"env\", \"version\"),\n\tprotomcp.ActionHandler(deployHandler),\n)\n```\n\n### EnumFields\n\nRestrict a field to a set of valid values. Invalid values trigger a \"Did you mean?\" suggestion.\n\n```go\nprotomcp.Action(\"set_env\",\n\tprotomcp.EnumFields(map[string][]string{\"env\": {\"dev\", \"staging\", \"prod\"}}),\n\tprotomcp.ActionHandler(setEnvHandler),\n)\n```\n\n### CrossRules\n\nValidate relationships between parameters.\n\n```go\nprotomcp.Action(\"scale\",\n\tprotomcp.CrossRules(protomcp.CrossRule{\n\t\tCheck: func(args map[string]interface{}) bool {\n\t\t\treturn args[\"min\"].(float64) > args[\"max\"].(float64)\n\t\t},\n\t\tMessage: \"min must be \u003C= max\",\n\t}),\n\tprotomcp.ActionHandler(scaleHandler),\n)\n```\n\n### Hints\n\nNon-blocking advisory messages appended to the result when a condition is met.\n\n```go\nprotomcp.Action(\"query\",\n\tprotomcp.Hints(protomcp.Hint{\n\t\tCondition: func(args map[string]interface{}) bool {\n\t\t\treturn args[\"limit\"].(float64) > 1000\n\t\t},\n\t\tMessage: \"Large limit may cause slow queries\",\n\t}),\n\tprotomcp.ActionHandler(queryHandler),\n)\n```\n\n---\n\n## Server Context\n\nRegister resolvers that inject shared parameters into tool handlers automatically.\n\n```go\nprotomcp.ServerContext(\"project_dir\",\n\tprotomcp.Expose(false),\n\tprotomcp.Resolver(func(args map[string]interface{}) interface{} {\n\t\tdir, _ := os.Getwd()\n\t\treturn dir\n\t}),\n)\n```\n\nWith `Expose(false)`, the parameter is hidden from the tool schema sent to the MCP host but still injected into the handler's `args` map.\n\n---\n\n## Local Middleware\n\nWrap tool handlers with in-process middleware for cross-cutting concerns.\n\n```go\nprotomcp.LocalMiddleware(10, func(ctx protomcp.ToolContext, toolName string, args map[string]interface{}, next protomcp.NextHandler) protomcp.ToolResult {\n\tstart := time.Now()\n\tresult := next(ctx, args)\n\tlog.Printf(\"%s took %v\", toolName, time.Since(start))\n\treturn result\n})\n```\n\nMiddleware is sorted by priority (lowest first = outermost). Return a `ToolResult` directly to short-circuit the chain.\n\n### Local vs Go-bridge middleware\n\nLocal middleware runs in-process. Go-bridge middleware runs at the transport layer between the MCP host and the server process.\n\n---\n\n## Telemetry\n\nObserve tool calls with fail-safe telemetry sinks.\n\n```go\nprotomcp.TelemetrySink(func(event protomcp.ToolCallEvent) {\n\tlog.Printf(\"[%s] %s: %s\", event.Phase, event.ToolName, event.Message)\n})\n```\n\n### `ToolCallEvent` phases\n\n| Phase | When |\n|-------|------|\n| `\"start\"` | Before the handler runs |\n| `\"success\"` | After the handler returns successfully |\n| `\"error\"` | After the handler raises or returns an error |\n| `\"progress\"` | When `ReportProgress` is called |\n\nExceptions in sinks are silently swallowed and do not affect tool execution.\n\n---\n\n## Sidecar Management\n\nDeclare companion processes that protomcp manages alongside your server.\n\n```go\nprotomcp.Sidecar(\"redis\",\n\tprotomcp.Command(\"redis-server\", \"--port\", \"6380\"),\n\tprotomcp.HealthCheck(\"http://localhost:6380/ping\"),\n\tprotomcp.StartOn(\"server_start\"),\n\tprotomcp.HealthTimeout(30*time.Second),\n)\n```\n\n| Option | Description |\n|--------|-------------|\n| `Command(args...)` | Process command and arguments |\n| `HealthCheck(url)` | URL to poll for health (HTTP 200 = healthy) |\n| `StartOn(trigger)` | `\"server_start\"` or `\"first_tool_call\"` |\n| `HealthTimeout(d)` | Duration to wait for health check to pass |\n\nPID files are stored in `~/.protomcp/sidecars/`. Processes receive `SIGTERM` on shutdown, followed by `SIGKILL` if they do not stop within the timeout.\n```","src/content/docs/guides/writing-tools-go.mdx","a6c93af0edfca62e","guides/writing-tools-go.mdx","guides/writing-tools-rust",{"id":359,"data":361,"body":367,"filePath":368,"digest":369,"legacyId":370,"deferredRender":16},{"title":362,"description":363,"editUrl":16,"head":364,"template":35,"sidebar":365,"pagefind":16,"draft":23},"Writing Tools (Rust)","Use the builder pattern with tool(), ToolResult, ToolContext, and ServerLogger in Rust.",[],{"hidden":23,"attrs":366},{},"## Installation\n\nAdd `protomcp` to your `Cargo.toml`:\n\n```toml\n[dependencies]\nprotomcp = \"0.1\"\ntokio = { version = \"1\", features = [\"full\"] }\nserde_json = \"1\"\n```\n\n---\n\n## The `tool()` builder\n\nRegister tools using the builder pattern. Call `tool(\"name\")` to start, chain methods, and finish with `.register()`.\n\n```rust\nuse protomcp::{tool, ToolResult, ArgDef};\n\n#[tokio::main]\nasync fn main() {\n tool(\"add\")\n .description(\"Add two numbers\")\n .arg(ArgDef::int(\"a\"))\n .arg(ArgDef::int(\"b\"))\n .handler(|_ctx, args| {\n let a = args[\"a\"].as_i64().unwrap_or(0);\n let b = args[\"b\"].as_i64().unwrap_or(0);\n ToolResult::new(format!(\"{}\", a + b))\n })\n .register();\n\n protomcp::run().await;\n}\n```\n\n### Tool metadata\n\nChain hint methods before `.register()`:\n\n```rust\ntool(\"delete_file\")\n .description(\"Delete a file from disk\")\n .destructive_hint(true)\n .idempotent_hint(false)\n .read_only_hint(false)\n .open_world_hint(false)\n .arg(ArgDef::string(\"path\"))\n .handler(|_ctx, args| {\n let path = args[\"path\"].as_str().unwrap_or(\"\");\n std::fs::remove_file(path).ok();\n ToolResult::new(format!(\"Deleted {}\", path))\n })\n .register();\n```\n\n| Method | Description |\n|--------|-------------|\n| `.description(s)` | Human-readable description (required) |\n| `.destructive_hint(bool)` | Hint: the tool has destructive side effects |\n| `.idempotent_hint(bool)` | Hint: calling multiple times has the same effect as once |\n| `.read_only_hint(bool)` | Hint: the tool does not modify state |\n| `.open_world_hint(bool)` | Hint: the tool may access external resources |\n| `.task_support_hint(bool)` | Hint: the tool supports long-running async task semantics |\n\n### Argument types\n\n| Function | JSON Schema type |\n|----------|-----------------|\n| `ArgDef::int(name)` | `\"integer\"` |\n| `ArgDef::string(name)` | `\"string\"` |\n| `ArgDef::number(name)` | `\"number\"` |\n| `ArgDef::boolean(name)` | `\"boolean\"` |\n| `ArgDef::array(name, item_type)` | `{\"type\": \"array\", \"items\": \u003Citem_type>}` |\n| `ArgDef::union(name, types...)` | `{\"anyOf\": [...]}` |\n| `ArgDef::literal(name, values...)` | `{\"type\": \"string\", \"enum\": [...]}` |\n\nArguments are passed as `serde_json::Value`. Use `.as_i64()`, `.as_f64()`, `.as_str()`, `.as_bool()` to extract values.\n\n---\n\n## `ToolResult`\n\nAll handlers return a `ToolResult`.\n\n### Success\n\n```rust\nToolResult::new(\"done\")\n```\n\n### Structured error\n\n```rust\nToolResult::error(\n \"The file /tmp/data.csv does not exist\",\n \"NOT_FOUND\",\n \"Check the path and try again\",\n false,\n)\n```\n\n| Field | Type | Description |\n|-------|------|-------------|\n| `result_text` | `String` | The result string |\n| `is_error` | `bool` | Whether this is an error |\n| `error_code` | `String` | Machine-readable error code |\n| `message` | `String` | Human-readable error message |\n| `suggestion` | `String` | Actionable suggestion for the caller |\n| `retryable` | `bool` | Whether the caller should retry |\n| `enable_tools` | `Vec\u003CString>` | Tool names to enable after this call |\n| `disable_tools` | `Vec\u003CString>` | Tool names to disable after this call |\n\n### Enabling / disabling tools from a result\n\n```rust\ntool(\"login\")\n .description(\"Log in and unlock tools\")\n .arg(ArgDef::string(\"username\"))\n .arg(ArgDef::string(\"password\"))\n .handler(|_ctx, args| {\n let username = args[\"username\"].as_str().unwrap_or(\"\");\n let password = args[\"password\"].as_str().unwrap_or(\"\");\n if !authenticate(username, password) {\n return ToolResult::error(\"Authentication failed\", \"AUTH_FAILED\", \"\", false);\n }\n let mut r = ToolResult::new(\"Logged in\");\n r.enable_tools = vec![\"delete_file\".to_string(), \"write_file\".to_string()];\n r\n })\n .register();\n```\n\n---\n\n## Progress Reporting\n\nUse `ToolContext` to report progress during long-running tool calls.\n\n```rust\ntool(\"process_data\")\n .description(\"Process a large dataset\")\n .arg(ArgDef::string(\"file_path\"))\n .handler(|ctx, args| {\n let file_path = args[\"file_path\"].as_str().unwrap_or(\"\");\n let rows = load_rows(file_path);\n let total = rows.len();\n for (i, row) in rows.iter().enumerate() {\n if ctx.is_cancelled() {\n return ToolResult::error(\"Cancelled\", \"CANCELLED\", \"\", false);\n }\n process_row(row);\n ctx.report_progress((i + 1) as i64, total as i64);\n }\n ToolResult::new(format!(\"Processed {} rows\", total))\n })\n .register();\n```\n\n### `ToolContext` API\n\n| Method | Description |\n|--------|-------------|\n| `report_progress(progress, total)` | Send a progress notification to the MCP host |\n| `is_cancelled() -> bool` | Returns `true` if the MCP host has cancelled this call |\n\n---\n\n## Cancellation\n\nCheck `ctx.is_cancelled()` periodically in long-running tools to stop early when the MCP host cancels the request.\n\n```rust\ntool(\"slow_compute\")\n .description(\"Run a slow computation\")\n .arg(ArgDef::int(\"n\"))\n .handler(|ctx, args| {\n let n = args[\"n\"].as_i64().unwrap_or(0);\n let mut result = 0i64;\n for i in 0..n {\n if ctx.is_cancelled() {\n return ToolResult::error(\"Cancelled by host\", \"CANCELLED\", \"\", false);\n }\n result += expensive_step(i);\n }\n ToolResult::new(format!(\"{}\", result))\n })\n .register();\n```\n\nCancellation is cooperative — protomcp sets the cancelled flag and your tool is responsible for checking it.\n\n---\n\n## Testing tools\n\nUse `clear_registry()` between tests:\n\n```rust\nuse protomcp::{tool, ToolResult, ArgDef, clear_registry};\nuse protomcp::tool::with_registry;\n\n#[test]\nfn test_add() {\n clear_registry();\n tool(\"add\")\n .description(\"Add two numbers\")\n .arg(ArgDef::int(\"a\"))\n .arg(ArgDef::int(\"b\"))\n .handler(|_, _| ToolResult::new(\"3\"))\n .register();\n\n with_registry(|tools| {\n assert_eq!(tools.len(), 1);\n assert_eq!(tools[0].name, \"add\");\n });\n clear_registry();\n}\n```\n\n---\n\n## Tool Groups\n\nGroup related actions under a single tool using `tool_group()`.\n\n```rust\nuse protomcp::{tool_group, action, ToolResult, ArgDef};\n\ntool_group(\"files\")\n .description(\"File operations\")\n .strategy(\"union\")\n .action(\n action(\"read\")\n .description(\"Read a file\")\n .arg(ArgDef::string(\"path\"))\n .handler(|_ctx, args| {\n let path = args[\"path\"].as_str().unwrap_or(\"\");\n ToolResult::new(std::fs::read_to_string(path).unwrap_or_default())\n }),\n )\n .action(\n action(\"write\")\n .description(\"Write a file\")\n .arg(ArgDef::string(\"path\"))\n .arg(ArgDef::string(\"content\"))\n .handler(|_ctx, args| {\n ToolResult::new(\"written\")\n }),\n )\n .register();\n```\n\n### Strategy: union (default)\n\nWith `.strategy(\"union\")`, the group registers as a single tool with a discriminated `oneOf` schema. The caller passes an `action` field to select the action.\n\n### Strategy: separate\n\nWith `.strategy(\"separate\")`, each action becomes its own tool, namespaced as `group.action` (e.g. `files.read`, `files.write`).\n\n### Dispatch and fuzzy matching\n\nWhen an unknown action is passed, the dispatcher returns an error with a fuzzy \"Did you mean?\" suggestion.\n\n---\n\n## Declarative Validation\n\nDeclare validation rules on actions to validate input before the handler runs.\n\n### `requires`\n\n```rust\naction(\"deploy\")\n .requires(&[\"env\", \"version\"])\n .handler(deploy_handler)\n```\n\n### `enum_fields`\n\nRestrict a field to a set of valid values. Invalid values trigger a \"Did you mean?\" suggestion.\n\n```rust\naction(\"set_env\")\n .enum_field(\"env\", &[\"dev\", \"staging\", \"prod\"])\n .handler(set_env_handler)\n```\n\n### `cross_rules`\n\nValidate relationships between parameters.\n\n```rust\naction(\"scale\")\n .cross_rule(|args| {\n let min = args[\"min\"].as_f64().unwrap_or(0.0);\n let max = args[\"max\"].as_f64().unwrap_or(0.0);\n min > max\n }, \"min must be \u003C= max\")\n .handler(scale_handler)\n```\n\n### `hints`\n\nNon-blocking advisory messages appended to the result when a condition is met.\n\n```rust\naction(\"query\")\n .hint(\"slow_warning\", |args| {\n args[\"limit\"].as_f64().unwrap_or(0.0) > 1000.0\n }, \"Large limit may cause slow queries\")\n .handler(query_handler)\n```\n\n---\n\n## Server Context\n\nRegister resolvers that inject shared parameters into tool handlers automatically.\n\n```rust\nuse protomcp::server_context;\n\nserver_context(\"project_dir\")\n .expose(false)\n .resolver(|_args| {\n std::env::current_dir()\n .map(|p| p.display().to_string())\n .unwrap_or_default()\n .into()\n })\n .register();\n```\n\nWith `.expose(false)`, the parameter is hidden from the tool schema sent to the MCP host but still injected into the handler's `args`.\n\n---\n\n## Local Middleware\n\nWrap tool handlers with in-process middleware for cross-cutting concerns.\n\n```rust\nuse protomcp::local_middleware;\n\nlocal_middleware(10, |ctx, tool_name, args, next| {\n let start = std::time::Instant::now();\n let result = next(ctx, args);\n println!(\"{} took {:?}\", tool_name, start.elapsed());\n result\n});\n```\n\nMiddleware is sorted by priority (lowest first = outermost). Return a `ToolResult` directly to short-circuit the chain.\n\n### Local vs Go-bridge middleware\n\nLocal middleware runs in-process. Go-bridge middleware runs at the transport layer between the MCP host and the server process.\n\n---\n\n## Telemetry\n\nObserve tool calls with fail-safe telemetry sinks. Exceptions in sinks are silently swallowed.\n\n```rust\nuse protomcp::{telemetry_sink, ToolCallEvent};\n\ntelemetry_sink(|event: &ToolCallEvent| {\n println!(\"[{}] {}: {}\", event.phase, event.tool_name, event.message);\n});\n```\n\n### `ToolCallEvent` phases\n\n| Phase | When |\n|-------|------|\n| `\"start\"` | Before the handler runs |\n| `\"success\"` | After the handler returns successfully |\n| `\"error\"` | After the handler raises or returns an error |\n| `\"progress\"` | When `report_progress` is called |\n\n---\n\n## Sidecar Management\n\nDeclare companion processes that protomcp manages alongside your server.\n\n```rust\nuse protomcp::sidecar;\n\nsidecar(\"redis\")\n .command(&[\"redis-server\", \"--port\", \"6380\"])\n .health_check(\"http://localhost:6380/ping\")\n .start_on(\"server_start\")\n .health_timeout(std::time::Duration::from_secs(30))\n .register();\n```\n\n| Method | Description |\n|--------|-------------|\n| `.command(args)` | Process command and arguments |\n| `.health_check(url)` | URL to poll for health (HTTP 200 = healthy) |\n| `.start_on(trigger)` | `\"server_start\"` or `\"first_tool_call\"` |\n| `.health_timeout(d)` | Duration to wait for health check to pass |\n\nPID files are stored in `~/.protomcp/sidecars/`. Processes receive `SIGTERM` on shutdown, followed by `SIGKILL` if they do not stop within the timeout.\n```","src/content/docs/guides/writing-tools-rust.mdx","88dd2a57f5b6f895","guides/writing-tools-rust.mdx"] \ No newline at end of file diff --git a/docs/.astro/settings.json b/docs/.astro/settings.json deleted file mode 100644 index 240e674..0000000 --- a/docs/.astro/settings.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "_variables": { - "lastUpdateCheck": 1773360635774 - } -} \ No newline at end of file diff --git a/docs/.astro/types.d.ts b/docs/.astro/types.d.ts deleted file mode 100644 index 03d7cc4..0000000 --- a/docs/.astro/types.d.ts +++ /dev/null @@ -1,2 +0,0 @@ -/// -/// \ No newline at end of file diff --git a/protomcp b/protomcp deleted file mode 100755 index a1d0aa8..0000000 Binary files a/protomcp and /dev/null differ diff --git a/sdk/python/src/protomcp/completion.py b/sdk/python/src/protomcp/completion.py index de93d2e..94fdb5b 100644 --- a/sdk/python/src/protomcp/completion.py +++ b/sdk/python/src/protomcp/completion.py @@ -23,3 +23,6 @@ def decorator(func: Callable) -> Callable: def get_completion_handler(ref_type: str, ref_name: str, argument_name: str): return _completion_registry.get((ref_type, ref_name, argument_name)) + +def clear_completion_registry(): + _completion_registry.clear() diff --git a/sdk/python/src/protomcp/discovery.py b/sdk/python/src/protomcp/discovery.py index 620ec18..a503983 100644 --- a/sdk/python/src/protomcp/discovery.py +++ b/sdk/python/src/protomcp/discovery.py @@ -33,11 +33,24 @@ def discover_handlers(): from protomcp.workflow import clear_workflow_registry from protomcp.server_context import clear_context_registry from protomcp.local_middleware import clear_local_middleware + from protomcp.resource import clear_resource_registry, clear_template_registry + from protomcp.prompt import clear_prompt_registry + from protomcp.completion import clear_completion_registry + from protomcp.telemetry import clear_telemetry_sinks + from protomcp.sidecar import clear_sidecar_registry + from protomcp.middleware import clear_middleware_registry clear_registry() clear_group_registry() clear_workflow_registry() clear_context_registry() clear_local_middleware() + clear_resource_registry() + clear_template_registry() + clear_prompt_registry() + clear_completion_registry() + clear_telemetry_sinks() + clear_sidecar_registry() + clear_middleware_registry() _loaded_modules.clear() for py_file in sorted(handlers_path.glob("*.py")): if py_file.name.startswith("_"): diff --git a/sdk/python/src/protomcp/prompt.py b/sdk/python/src/protomcp/prompt.py index 7e7d23b..543b5be 100644 --- a/sdk/python/src/protomcp/prompt.py +++ b/sdk/python/src/protomcp/prompt.py @@ -36,3 +36,6 @@ def decorator(func: Callable) -> Callable: def get_registered_prompts() -> list[PromptDef]: return list(_prompt_registry) + +def clear_prompt_registry(): + _prompt_registry.clear() diff --git a/sdk/python/src/protomcp/resource.py b/sdk/python/src/protomcp/resource.py index f71a298..0c7769a 100644 --- a/sdk/python/src/protomcp/resource.py +++ b/sdk/python/src/protomcp/resource.py @@ -59,3 +59,9 @@ def get_registered_resources() -> list[ResourceDef]: def get_registered_resource_templates() -> list[ResourceTemplateDef]: return list(_resource_template_registry) + +def clear_resource_registry(): + _resource_registry.clear() + +def clear_template_registry(): + _resource_template_registry.clear() diff --git a/sdk/python/src/protomcp/tool.py b/sdk/python/src/protomcp/tool.py index fd80b17..9cfd3c8 100644 --- a/sdk/python/src/protomcp/tool.py +++ b/sdk/python/src/protomcp/tool.py @@ -59,7 +59,7 @@ def get_registered_tools() -> list[ToolDef]: return list(_registry) + groups_to_tool_defs() + workflows_to_tool_defs() def get_hidden_tool_names() -> list[str]: - return [t.name for t in _registry if t.hidden] + return [t.name for t in get_registered_tools() if t.hidden] def clear_registry(): _registry.clear() diff --git a/sdk/rust/README.md b/sdk/rust/README.md new file mode 100644 index 0000000..9a30d1a --- /dev/null +++ b/sdk/rust/README.md @@ -0,0 +1,89 @@ +# protomcp + +[![CI](https://github.com/msilverblatt/protomcp/actions/workflows/ci.yml/badge.svg)](https://github.com/msilverblatt/protomcp/actions/workflows/ci.yml) +[![crates.io](https://img.shields.io/crates/v/protomcp)](https://crates.io/crates/protomcp) +[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://github.com/msilverblatt/protomcp/blob/main/LICENSE) + +Rust SDK for [protomcp](https://github.com/msilverblatt/protomcp) -- build MCP servers with tools, resources, and prompts in one file, one command. + +## Install + +Add to your `Cargo.toml`: + +```toml +[dependencies] +protomcp = "0.1" +tokio = { version = "1", features = ["full"] } +``` + +You also need the `pmcp` CLI: + +```sh +brew install msilverblatt/tap/protomcp +``` + +## Quick Start + +```rust +use protomcp::{tool, ToolResult, ArgDef}; + +#[tokio::main] +async fn main() { + tool("add") + .description("Add two numbers") + .arg(ArgDef::int("a")) + .arg(ArgDef::int("b")) + .handler(|_ctx, args| { + let a = args["a"].as_i64().unwrap_or(0); + let b = args["b"].as_i64().unwrap_or(0); + ToolResult::new(format!("{}", a + b)) + }) + .register(); + protomcp::run().await; +} +``` + +```sh +pmcp dev src/main.rs +``` + +## Tool Groups + +Group related actions under a single tool with per-action schemas: + +```rust +use protomcp::{tool_group, action, ToolResult, ArgDef}; + +tool_group("math") + .description("Math operations") + .action(action("add") + .description("Add two numbers") + .arg(ArgDef::int("a")) + .arg(ArgDef::int("b")) + .handler(|_ctx, args| { + let a = args["a"].as_i64().unwrap_or(0); + let b = args["b"].as_i64().unwrap_or(0); + ToolResult::new(format!("{}", a + b)) + })) + .action(action("multiply") + .description("Multiply two numbers") + .arg(ArgDef::int("a")) + .arg(ArgDef::int("b")) + .handler(|_ctx, args| { + let a = args["a"].as_i64().unwrap_or(0); + let b = args["b"].as_i64().unwrap_or(0); + ToolResult::new(format!("{}", a * b)) + })) + .register(); +``` + +## Documentation + +- [Full documentation](https://msilverblatt.github.io/protomcp/) +- [Rust Guide](https://msilverblatt.github.io/protomcp/guides/writing-tools-rust/) +- [CLI Reference](https://msilverblatt.github.io/protomcp/reference/cli/) +- [Examples](https://github.com/msilverblatt/protomcp/tree/main/examples/rust) + +## License + +MIT diff --git a/sdk/rust/src/runner.rs b/sdk/rust/src/runner.rs index 3b653bf..b54deef 100644 --- a/sdk/rust/src/runner.rs +++ b/sdk/rust/src/runner.rs @@ -26,6 +26,10 @@ pub async fn run() { let transport = Transport::connect(&socket_path).await .expect("failed to connect to socket"); + start_sidecars("server_start"); + + let mut first_tool_call = true; + loop { let env = match transport.recv().await { Ok(env) => env, @@ -38,8 +42,13 @@ pub async fn run() { Some(proto::envelope::Msg::ListTools(_)) => { handle_list_tools(&transport, &request_id).await; send_middleware_registrations(&transport).await; + send_disable_hidden_tools(&transport).await; } Some(proto::envelope::Msg::CallTool(req)) => { + if first_tool_call { + first_tool_call = false; + start_sidecars("first_tool_call"); + } handle_call_tool(&transport, &req, &request_id).await; } Some(proto::envelope::Msg::Reload(_)) => { @@ -115,9 +124,6 @@ async fn handle_call_tool(transport: &Transport, req: &proto::CallToolRequest, r serde_json::from_str(&req.arguments_json).unwrap_or(serde_json::json!({})) }; - // Start sidecars on first tool call - start_sidecars("first_tool_call"); - // Resolve server contexts let ctx_values = resolve_contexts(&mut args); // Inject resolved context values into args @@ -230,6 +236,25 @@ async fn handle_reload(transport: &Transport, request_id: &str) { send_middleware_registrations(transport).await; } +async fn send_disable_hidden_tools(transport: &Transport) { + let hidden: Vec = with_registry(|tools| { + tools.iter() + .filter(|t| t.hidden) + .map(|t| t.name.clone()) + .collect() + }); + if hidden.is_empty() { + return; + } + let resp = proto::Envelope { + msg: Some(proto::envelope::Msg::DisableTools(proto::DisableToolsRequest { + tool_names: hidden, + })), + ..Default::default() + }; + let _ = transport.send(&resp).await; +} + async fn send_middleware_registrations(transport: &Transport) { let names_and_priorities: Vec<(String, i32)> = with_middleware_registry(|mws| { mws.iter().map(|mw| (mw.name.clone(), mw.priority)).collect() @@ -327,8 +352,40 @@ async fn handle_list_resource_templates(transport: &Transport, request_id: &str) let _ = transport.send(&resp).await; } +fn uri_matches_template(template: &str, uri: &str) -> bool { + let parts: Vec<&str> = template.split('{').collect(); + if parts.is_empty() { + return template == uri; + } + if !uri.starts_with(parts[0]) { + return false; + } + let mut remaining = &uri[parts[0].len()..]; + for part in &parts[1..] { + if let Some(suffix) = part.split_once('}') { + let literal = suffix.1; + if literal.is_empty() { + if remaining.is_empty() { + return false; + } + remaining = ""; + } else if let Some(pos) = remaining.find(literal) { + if pos == 0 { + return false; + } + remaining = &remaining[pos + literal.len()..]; + } else { + return false; + } + } + } + remaining.is_empty() +} + async fn handle_read_resource(transport: &Transport, req: &proto::ReadResourceRequest, request_id: &str) { let uri = &req.uri; + + // First check static resources let contents: Vec = with_resources(|resources| { for r in resources { if r.uri == *uri { @@ -342,6 +399,26 @@ async fn handle_read_resource(transport: &Transport, req: &proto::ReadResourceRe } Vec::new() }); + + // If no static resource matched, try templates + let contents = if contents.is_empty() { + with_resource_templates(|templates| { + for t in templates { + if uri_matches_template(&t.uri_template, uri) { + return (t.handler)(uri).iter().map(|c| proto::ResourceContent { + uri: c.uri.clone(), + mime_type: c.mime_type.clone(), + text: c.text.clone(), + blob: c.blob.clone(), + }).collect(); + } + } + Vec::new() + }) + } else { + contents + }; + let resp = proto::Envelope { request_id: request_id.to_string(), msg: Some(proto::envelope::Msg::ReadResourceResponse(proto::ReadResourceResponse { contents })), diff --git a/sdk/typescript/src/completion.ts b/sdk/typescript/src/completion.ts index 10558bd..1155207 100644 --- a/sdk/typescript/src/completion.ts +++ b/sdk/typescript/src/completion.ts @@ -28,3 +28,7 @@ export function getCompletionHandler( ): CompletionHandler | undefined { return completionRegistry.get(makeKey(refType, refName, argumentName)); } + +export function clearCompletionRegistry(): void { + completionRegistry.clear(); +} diff --git a/sdk/typescript/src/discovery.ts b/sdk/typescript/src/discovery.ts index 6fb25b8..4bf829e 100644 --- a/sdk/typescript/src/discovery.ts +++ b/sdk/typescript/src/discovery.ts @@ -38,11 +38,24 @@ export async function discoverHandlers(): Promise { const { clearWorkflowRegistry } = await import('./workflow.js'); const { clearContextRegistry } = await import('./serverContext.js'); const { clearLocalMiddleware } = await import('./localMiddleware.js'); + const { clearResourceRegistry, clearTemplateRegistry } = await import('./resource.js'); + const { clearPromptRegistry } = await import('./prompt.js'); + const { clearCompletionRegistry } = await import('./completion.js'); + const { clearTelemetrySinks } = await import('./telemetry.js'); + const { clearSidecarRegistry } = await import('./sidecar.js'); + const { clearMiddlewareRegistry } = await import('./middleware.js'); clearRegistry(); clearGroupRegistry(); clearWorkflowRegistry(); clearContextRegistry(); clearLocalMiddleware(); + clearResourceRegistry(); + clearTemplateRegistry(); + clearPromptRegistry(); + clearCompletionRegistry(); + clearTelemetrySinks(); + clearSidecarRegistry(); + clearMiddlewareRegistry(); loadedModules.clear(); } diff --git a/sdk/typescript/src/index.ts b/sdk/typescript/src/index.ts index 20253bc..fa472ed 100644 --- a/sdk/typescript/src/index.ts +++ b/sdk/typescript/src/index.ts @@ -5,11 +5,11 @@ export { toolManager } from './manager.js'; export { ToolContext } from './context.js'; export { ServerLogger } from './log.js'; export { middleware, getRegisteredMiddleware, clearMiddlewareRegistry } from './middleware.js'; -export { resource, resourceTemplate, getRegisteredResources, getRegisteredResourceTemplates } from './resource.js'; +export { resource, resourceTemplate, getRegisteredResources, getRegisteredResourceTemplates, clearResourceRegistry, clearTemplateRegistry } from './resource.js'; export type { ResourceContent, ResourceDef, ResourceTemplateDef } from './resource.js'; -export { prompt, getRegisteredPrompts } from './prompt.js'; +export { prompt, getRegisteredPrompts, clearPromptRegistry } from './prompt.js'; export type { PromptArg, PromptMessage, PromptDef } from './prompt.js'; -export { completion, getCompletionHandler } from './completion.js'; +export { completion, getCompletionHandler, clearCompletionRegistry } from './completion.js'; export type { CompletionResult } from './completion.js'; export { serverContext, resolveContexts, clearContextRegistry } from './serverContext.js'; export { localMiddleware, buildMiddlewareChain, clearLocalMiddleware } from './localMiddleware.js'; diff --git a/sdk/typescript/src/prompt.ts b/sdk/typescript/src/prompt.ts index 962bfd3..fcafef1 100644 --- a/sdk/typescript/src/prompt.ts +++ b/sdk/typescript/src/prompt.ts @@ -39,3 +39,7 @@ export function prompt(options: PromptOptions): PromptDef { export function getRegisteredPrompts(): PromptDef[] { return [...promptRegistry]; } + +export function clearPromptRegistry(): void { + promptRegistry.length = 0; +} diff --git a/sdk/typescript/src/resource.ts b/sdk/typescript/src/resource.ts index ed89731..3067d96 100644 --- a/sdk/typescript/src/resource.ts +++ b/sdk/typescript/src/resource.ts @@ -72,3 +72,11 @@ export function getRegisteredResources(): ResourceDef[] { export function getRegisteredResourceTemplates(): ResourceTemplateDef[] { return [...templateRegistry]; } + +export function clearResourceRegistry(): void { + resourceRegistry.length = 0; +} + +export function clearTemplateRegistry(): void { + templateRegistry.length = 0; +} diff --git a/sdk/typescript/src/workflow.ts b/sdk/typescript/src/workflow.ts index 2cdfa13..f1c67fd 100644 --- a/sdk/typescript/src/workflow.ts +++ b/sdk/typescript/src/workflow.ts @@ -1,7 +1,7 @@ import { z } from 'zod'; import { zodToJsonSchema } from 'zod-to-json-schema'; import type { ToolContext } from './context.js'; -import { type ToolDef, _setWorkflowsToToolDefs } from './tool.js'; +import { type ToolDef, _setWorkflowsToToolDefs, getRegisteredTools } from './tool.js'; import { ToolResult } from './result.js'; // --------------------------------------------------------------------------- @@ -260,11 +260,14 @@ async function handleStepCall(workflowName: string, stepName: string, kwargs: Re let state = getActiveState(); if (stepDef.initial) { + // Snapshot all registered tool names minus this workflow's tools + const allTools = getRegisteredTools().map(t => t.name); + const preTools = allTools.filter(t => !t.startsWith(`${workflowName}.`)); state = { workflowName, currentStep: stepName, history: [], - preWorkflowTools: [], + preWorkflowTools: preTools, }; activeWorkflowStack.push(state); } else {