{"schema_version":"verify.report.v1","generated_at":"2026-08-24T22:24:47.012799+00:00","snapshot_id":"trustsnap_434700e35885cdb3","server":{"namespace":"rakanalalami","name":"kamy","title":"Kamy","description":"Other PDF MCP servers ask you to install Chrome, manage a local renderer, and ship templates yourself. Kamy MCP is a hosted endpoint — point any MCP client at https://mcp.kamy.dev/mcp, paste an API key, and start asking your AI to \"generate an invoice for Acme Corp\". Eight production-ready templates ship with the service (invoice, quote, receipt, contract, agreement, certificate, report, shipping-","homepage_url":"https://glama.ai/mcp/servers/ywuczljwen","docs_url":null,"icon_url":null,"support_url":"https://github.com/rakanalalami/kamy","remote_url":"https://mcp.kamy.dev/mcp","server_card_url":null,"latest_version":null,"current_status":"healthy","current_score":54.69,"transport_type":"streamable-http","has_oauth":false,"has_dcr":false,"has_prompts":false,"tool_count":59,"current_validation_schema_version":"8058defc70ca932c","last_validated_at":"2026-08-24T20:34:25.893479+00:00","registry_source":"glama_registry","registry_identifier":"glama_registry:ywuczljwen","canonical_identifier":null,"current_score_components":{"auth_operability_score":2.0,"error_contract_score":1.0,"rate_limit_semantics_score":2.0,"schema_completeness_score":3.0,"backward_compatibility_score":4.0,"slo_health_score":3.0,"security_hygiene_score":2.0,"task_success_score":4.0,"trust_confidence_score":3.0,"abuse_noise_ratio_score":4.0,"prompt_contract_score":0.0,"resource_contract_score":0.0,"discovery_metadata_score":2.0,"registry_consistency_score":2.0,"installability_score":4.0,"session_semantics_score":4.0,"tool_surface_design_score":3.0,"result_shape_stability_score":3.0,"oauth_interop_score":0.0,"recovery_semantics_score":0.0,"maintenance_signal_score":2.0,"adoption_signal_score":2.0,"freshness_confidence_score":4.0,"transport_fidelity_score":4.0,"spec_recency_score":2.0,"session_resume_score":3.0,"step_up_auth_score":0.0,"transport_compliance_score":2.0,"utility_coverage_score":1.0,"advanced_capability_coverage_score":0.0,"connector_publishability_score":1.0,"tool_snapshot_churn_score":4.0,"connector_replay_score":4.0,"request_association_score":0.0,"interactive_flow_safety_score":4.0,"action_safety_score":1.0,"official_registry_presence_score":2.0,"safety_transparency_score":2.0,"tool_capability_clarity_score":4.0,"destructive_operation_safety_score":2.0,"egress_ssrf_resilience_score":1.0,"execution_sandbox_safety_score":1.0,"data_exfiltration_resilience_score":2.0,"least_privilege_scope_score":1.0,"secret_handling_hygiene_score":3.0,"dependency_supply_chain_signal_score":0.0,"input_sanitization_safety_score":3.0,"tool_namespace_clarity_score":4.0},"capability_taxonomy":[],"machine_summary":{},"taxonomy_tags":[],"score_decomposition":[],"validation_diff":null,"tool_snapshot_diff":null,"connector_replay":{},"request_association":{},"production_readiness":{"code":"needs_remediation","label":"Needs remediation","reason":"Current validation evidence shows operational or discovery gaps that should be fixed first.","badge":"score-low","critical_alerts":0,"high_or_critical_alerts":0,"degraded_by_active_alerts":false},"recommended_for":[],"history_summary":{},"validation_timeline":[],"evidence_confidence":{"score":100.0,"label":"high","reason":"Based on 20 recent validations, 13 captured checks, and validation age of 1.8 hours.","live_check_count":13,"validation_age_hours":1.84,"basis":{"evidence_bearing_validations":20,"affirmative_live_check_count":13,"validation_age_hours":1.84,"freshness_threshold_hours":24}},"incident_feed":[],"disputes":[],"remediations":[],"client_remediation_modes":[],"client_profiles":[],"client_readiness_verdicts":[],"publishability_policy_profiles":[],"compatibility_fixtures":[],"install_snippets":{},"aliases":[],"raw_evidence":{"checks":{"probe_noise_resilience":{"status":"ok","latency_ms":99.78,"details":{"url":"https://mcp.kamy.dev/robots.txt","http_status":200,"headers":{"content-type":"text/plain; charset=utf-8"},"validation_disallowed":false,"consent_error":null}},"server_card":{"status":"error","latency_ms":10.32,"details":{"url":"https://mcp.kamy.dev/.well-known/mcp/server-card.json","error":"Client error '404 Not Found' for url 'https://mcp.kamy.dev/.well-known/mcp/server-card.json'\nFor more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404"}},"oauth_protected_resource":{"status":"error","latency_ms":256.96,"details":{"url":"https://mcp.kamy.dev/.well-known/oauth-protected-resource","error":"Client error '404 Not Found' for url 'https://mcp.kamy.dev/.well-known/oauth-protected-resource'\nFor more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404"}},"oauth_authorization_server":{"status":"missing","latency_ms":null,"details":{"reason":"no_authorization_server"}},"openid_configuration":{"status":"missing","latency_ms":null,"details":{"reason":"no_authorization_server"}},"initialize":{"status":"ok","latency_ms":320.17,"details":{"url":"https://mcp.kamy.dev/mcp","payload":{"result":{"protocolVersion":"2025-03-26","capabilities":{"tools":{"listChanged":true}},"serverInfo":{"name":"kamy","version":"1.5.0"}},"jsonrpc":"2.0","id":1},"http_status":200,"headers":{"content-type":"text/event-stream"}}},"protocol_version_probe":{"status":"warning","latency_ms":null,"details":{"claimed_version":"2025-03-26","validator_protocol_version":"2025-03-26","latest_known_version":"2025-11-25","releases_behind":2,"lag_days":244}},"tools_list":{"status":"ok","latency_ms":161.92,"details":{"url":"https://mcp.kamy.dev/mcp","payload":{"result":{"tools":[{"name":"list_templates","title":"List templates","description":"List Kamy's public system PDF templates. No authentication required.","inputSchema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"execution":{"taskSupport":"forbidden"},"_meta":{"securitySchemes":[{"type":"noauth"}]},"securitySchemes":[{"type":"noauth"}]},{"name":"get_template_schema","title":"Get template schema","description":"Fetch the JSON Schema (exact data shape) and a copy-pasteable sample payload for a Kamy system template by slug. Call this before render_pdf so you fill the right fields with the right types instead of guessing. No authentication required.","inputSchema":{"type":"object","properties":{"slug":{"type":"string","description":"Template slug — e.g. 'invoice', 'receipt', 'uae-tax-invoice'."}},"required":["slug"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"execution":{"taskSupport":"forbidden"},"_meta":{"securitySchemes":[{"type":"noauth"}]},"securitySchemes":[{"type":"noauth"}]},{"name":"create_template","title":"Create a template","description":"Create a new custom PDF template owned by this account: a name, a unique slug, a Handlebars/HTML body, and optional CSS and JSON Schema. The slug is what render_pdf then takes as `template`. Custom templates are a paid feature — on the Free plan this returns 403 FORBIDDEN before anything is created. The slug must be unique within the account and must not collide with a built-in system slug (invoice, receipt, quote, …); both cases come back 422, and the fix for an already-taken slug is update_template rather than a retry. A brand-new template has no published version, so it renders straight from what you supply here — publish_template only becomes necessary once you start cutting versions. Requires a Kamy API key with the `templates:write` scope; without a key, returns dashboard setup instructions.","inputSchema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":100,"description":"Human-readable template name."},"slug":{"type":"string","minLength":1,"maxLength":80,"pattern":"^[a-z0-9-]+$","description":"Stable identifier the render tools take as `template`. Unique per account, and it may not collide with a built-in system slug (invoice, receipt, quote, …) — both cases are rejected with 422."},"html":{"type":"string","minLength":1,"maxLength":5000000,"description":"Handlebars/HTML body of the document. Max 5 MB."},"css":{"type":"string","maxLength":1000000,"description":"Stylesheet applied at render time. Max 1 MB."},"schema":{"type":"object","additionalProperties":{},"description":"JSON Schema describing the data payload the template expects. Defaults to {}."},"description":{"type":"string","maxLength":500,"description":"What this template is for."},"isPublic":{"type":"boolean","description":"When true, other accounts can list and render it. Defaults to false."},"tags":{"type":"array","items":{"type":"string"},"maxItems":10,"description":"Up to 10 free-form labels."}},"required":["name","slug","html"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"execution":{"taskSupport":"forbidden"},"_meta":{"securitySchemes":[{"type":"noauth"}]},"securitySchemes":[{"type":"noauth"}]},{"name":"update_template","title":"Update a template draft","description":"Edit an existing custom template's DRAFT — name, html, css, schema, tags, visibility — addressed by UUID or slug. Only the fields you pass are changed. Crucially, this does not change what renders once the template has been published even once: while published_version is set, render_pdf serves that frozen snapshot and your edits stay invisible until publish_template moves the pointer, so an edit that appears to do nothing usually needs a publish. System templates, and templates belonging to another account, return 403. Requires a Kamy API key with the `templates:write` scope; without a key, returns dashboard setup instructions.","inputSchema":{"type":"object","properties":{"template":{"type":"string","minLength":1,"description":"Template UUID or slug. This route accepts either."},"name":{"type":"string","minLength":1,"maxLength":100},"description":{"type":"string","maxLength":500},"html":{"type":"string","minLength":1,"maxLength":5000000,"description":"Replaces the draft HTML entirely."},"css":{"type":"string","maxLength":1000000,"description":"Replaces the draft CSS entirely."},"schema":{"type":"object","additionalProperties":{},"description":"Replaces the draft JSON Schema entirely."},"isPublic":{"type":"boolean"},"tags":{"type":"array","items":{"type":"string"},"maxItems":10}},"required":["template"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"execution":{"taskSupport":"forbidden"},"_meta":{"securitySchemes":[{"type":"noauth"}]},"securitySchemes":[{"type":"noauth"}]},{"name":"publish_template","title":"Publish a template version","description":"Make a template version live for rendering. Called with no `version`, it snapshots the current draft into a new version and points published_version at it — this is how you ship an edit made with update_template. Called with an existing `version`, it republishes that earlier snapshot and leaves the draft alone. Reach for rollback_template instead when you are reverting a bad release: only that tool offers the concurrency fence and the option to restore the draft as well. Takes a template UUID or slug, same as every other /v1/templates route. Returns { templateId, publishedVersion, publishedVersionId, latestVersion, publishedAt }. Requires a Kamy API key with the `templates:write` scope; without a key, returns dashboard setup instructions.","inputSchema":{"type":"object","properties":{"templateId":{"type":"string","minLength":1,"description":"Template UUID or slug — every /v1/templates route resolves either."},"version":{"type":"integer","exclusiveMinimum":0,"description":"Omit to snapshot the current draft into a new version and publish that. Supply an existing version number to republish a known-good earlier snapshot, leaving the draft untouched."}},"required":["templateId"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"execution":{"taskSupport":"forbidden"},"_meta":{"securitySchemes":[{"type":"noauth"}]},"securitySchemes":[{"type":"noauth"}]},{"name":"rollback_template","title":"Roll a template back to an earlier version","description":"Revert a template after a bad release: repoints published_version at the version you name, so every subsequent render immediately serves that snapshot again. Prefer this over publish_template's `version` argument whenever you are reverting, because only this tool takes expectedPublishedVersion — an optimistic fence that rejects with 409 VERSION_CONFLICT if someone moved the pointer since you read it — and only this tool can restore the working draft too. restoreDraft: true overwrites the draft html/css/schema with that version's content, auto-snapshotting the existing draft into a fresh version first so unsaved work is recoverable; it defaults to false. Destructive: it changes what production renders, and there is no undo beyond rolling forward again. Takes a template UUID or slug. Requires a Kamy API key with the `templates:write` scope; without a key, returns dashboard setup instructions.","inputSchema":{"type":"object","properties":{"templateId":{"type":"string","minLength":1,"description":"Template UUID or slug — every /v1/templates route resolves either."},"version":{"type":"integer","exclusiveMinimum":0,"description":"The known-good version to make live again. Get it from list_template_versions."},"restoreDraft":{"type":"boolean","description":"When true, also overwrite the working draft (html/css/schema) with that version's content. The current draft is auto-snapshotted into a new version first, unless it is already byte-identical. Defaults to false, which leaves in-flight edits alone."},"expectedPublishedVersion":{"type":"integer","minimum":0,"description":"Optimistic fence: the published_version you believe is live (0 if never published). The call is rejected with 409 VERSION_CONFLICT if someone moved the pointer in the meantime. Pass it whenever you read the state in an earlier step."}},"required":["templateId","version"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"},"annotations":{"readOnlyHint":false,"destructiveHint":true,"openWorldHint":false},"execution":{"taskSupport":"forbidden"},"_meta":{"securitySchemes":[{"type":"noauth"}]},"securitySchemes":[{"type":"noauth"}]},{"name":"list_template_versions","title":"List template versions","description":"List a template's immutable version snapshots, newest first, as { id, version, createdAt }. This is where the version number that publish_template and rollback_template take comes from. Bodies are deliberately not included — call get_template_version when you need one snapshot's html, css and schema. Works on system and public templates as well as your own, by UUID or slug. Read-only and spends no quota. Requires a Kamy API key with the `templates:read` scope; without a key, returns dashboard setup instructions.","inputSchema":{"type":"object","properties":{"templateId":{"type":"string","minLength":1,"description":"Template UUID or slug — every /v1/templates route resolves either."}},"required":["templateId"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"execution":{"taskSupport":"forbidden"},"_meta":{"securitySchemes":[{"type":"noauth"}]},"securitySchemes":[{"type":"noauth"}]},{"name":"get_template_version","title":"Get a template version","description":"Fetch one template version snapshot in full — html, css, schema, createdBy, createdAt — exactly as it was frozen when that version was cut. Use list_template_versions first to find the number. Use this to inspect what a released version actually contained, or to recover content a later draft edit overwrote; it changes nothing on its own, so pair it with rollback_template when you want that version live again. Takes a template UUID or slug. Read-only and spends no quota. Requires a Kamy API key with the `templates:read` scope; without a key, returns dashboard setup instructions.","inputSchema":{"type":"object","properties":{"templateId":{"type":"string","minLength":1,"description":"Template UUID or slug — every /v1/templates route resolves either."},"version":{"type":"integer","exclusiveMinimum":0,"description":"Version number from list_template_versions."}},"required":["templateId","version"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"execution":{"taskSupport":"forbidden"},"_meta":{"securitySchemes":[{"type":"noauth"}]},"securitySchemes":[{"type":"noauth"}]},{"name":"render_pdf","title":"Render PDF","description":"Render a PDF from a Kamy template and data, and wait for it. This is the default document tool: it blocks until the file exists and hands back { id, url, bytes, durationMs, templateId, createdAt } in one call, where url is a signed download link valid for one hour and id is the render id every later tool takes. Reach for render_async instead when waiting is not acceptable, and render_batch when several documents are wanted at once. Call get_template_schema first if you are unsure what fields the template expects. Counts one render against the monthly quota — get_account tells you what is left before this fails with 402. Requires a Kamy API key with the `render` scope; without a key, returns dashboard setup instructions.","inputSchema":{"type":"object","properties":{"template":{"type":"string","description":"Template slug (e.g., 'invoice') or template UUID"},"data":{"type":"object","additionalProperties":{},"description":"Data to populate the template"},"format":{"type":"string","enum":["a4","letter"],"default":"a4"}},"required":["template","data"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"execution":{"taskSupport":"forbidden"},"_meta":{"securitySchemes":[{"type":"noauth"}]},"securitySchemes":[{"type":"noauth"}]},{"name":"render_docx","title":"Render Word document","description":"Render an editable Word (.docx) document from a Kamy template and data. Takes the same { template, data } payload as render_pdf but produces a different container — reach for it when the recipient has to EDIT the document (legal redlines, Word-based intake, corporate templates) rather than receive a fixed artifact. Only five slugs have a Word implementation — invoice, receipt, quote, contract, agreement — and any other template is rejected with a validation error; use render_pdf for those. Returns { id, url, bytes, durationMs, format: 'docx' }, where url is a signed download link valid for one hour and id is a normal render id. Counts one render against the monthly quota. Requires a Kamy API key with the `render` scope; without a key, returns dashboard setup instructions.","inputSchema":{"type":"object","properties":{"template":{"type":"string","enum":["invoice","receipt","quote","contract","agreement"],"description":"Which built-in document to build. These five are the only slugs with a Word implementation; custom templates and other system templates are PDF-only (use render_pdf)."},"data":{"type":"object","additionalProperties":{},"default":{},"description":"Template data, identical in shape to the render_pdf payload for the same slug — call get_template_schema first to get the exact fields."},"name":{"type":"string","minLength":1,"maxLength":120,"description":"Label stored on the render row so the document is identifiable in the dashboard."}},"required":["template"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"execution":{"taskSupport":"forbidden"},"_meta":{"securitySchemes":[{"type":"noauth"}]},"securitySchemes":[{"type":"noauth"}]},{"name":"render_pptx","title":"Render PowerPoint deck","description":"Build a PowerPoint (.pptx) deck from a slide spec: an ordered array of slides, each tagged with one of five fixed layouts (title, bullets, two-column, table, quote). This is NOT a template renderer like render_pdf / render_docx — there is no template slug and no free-form layout, so content has to be shaped into those five. It also converts nothing; use convert_document to turn a file you already have into a PDF. Returns a stored render { id, url, bytes, durationMs, format } where url is a signed download link valid for one hour; the deck is a .pptx, so feed the id to convert_document if the next step needs a PDF (merge_pdfs, split_pdf and the signature tools take PDFs only). Counts one render against the monthly quota. Requires a Kamy API key with the `render` scope; without a key, returns dashboard setup instructions.","inputSchema":{"type":"object","properties":{"title":{"type":"string","minLength":1,"maxLength":120,"description":"Deck title — used as document metadata and as the returned filename stem."},"format":{"type":"string","enum":["WIDE","STANDARD"],"default":"WIDE","description":"WIDE = 16:9 (default), STANDARD = 4:3."},"theme":{"type":"object","properties":{"accentHex":{"type":"string","pattern":"^#[0-9a-fA-F]{6}$","description":"Accent colour for the title bar, table header fill and column headings."},"fontFace":{"type":"string","maxLength":60,"description":"Font family name. Defaults to Calibri."}},"additionalProperties":false},"slides":{"type":"array","items":{"anyOf":[{"type":"object","properties":{"layout":{"type":"string","const":"title"},"title":{"type":"string","minLength":1,"maxLength":160},"subtitle":{"type":"string","maxLength":200}},"required":["layout","title"],"additionalProperties":false},{"type":"object","properties":{"layout":{"type":"string","const":"bullets"},"title":{"type":"string","minLength":1,"maxLength":160},"bullets":{"type":"array","items":{"type":"string","minLength":1,"maxLength":400},"minItems":1,"maxItems":20}},"required":["layout","title","bullets"],"additionalProperties":false},{"type":"object","properties":{"layout":{"type":"string","const":"two-column"},"title":{"type":"string","minLength":1,"maxLength":160},"left":{"type":"object","properties":{"title":{"type":"string","maxLength":80},"bullets":{"type":"array","items":{"type":"string","maxLength":400},"maxItems":12}},"required":["bullets"],"additionalProperties":false},"right":{"$ref":"#/properties/slides/items/anyOf/2/properties/left"}},"required":["layout","title","left","right"],"additionalProperties":false},{"type":"object","properties":{"layout":{"type":"string","const":"table"},"title":{"type":"string","minLength":1,"maxLength":160},"headers":{"type":"array","items":{"type":"string","minLength":1,"maxLength":60},"minItems":1,"maxItems":8},"rows":{"type":"array","items":{"type":"array","items":{"type":"string","maxLength":120},"minItems":1},"maxItems":20,"description":"Body rows, each an array of cell strings aligned to `headers`."}},"required":["layout","title","headers","rows"],"additionalProperties":false},{"type":"object","properties":{"layout":{"type":"string","const":"quote"},"quote":{"type":"string","minLength":1,"maxLength":400},"attribution":{"type":"string","maxLength":120}},"required":["layout","quote"],"additionalProperties":false}]},"minItems":1,"maxItems":60,"description":"Ordered slides. Each carries a `layout` discriminator: 'title', 'bullets', 'two-column', 'table' or 'quote'. There is no free-form layout — content that doesn't fit one of the five should be reshaped into bullets or a table."}},"required":["slides"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"execution":{"taskSupport":"forbidden"},"_meta":{"securitySchemes":[{"type":"noauth"}]},"securitySchemes":[{"type":"noauth"}]},{"name":"render_xlsx","title":"Render Excel workbook","description":"Build an Excel (.xlsx) workbook from a sheet spec: columns with keys, row objects keyed to those columns, optional Excel number formats and a formula-aware total row (bare 'SUM' / 'AVG' / 'COUNT' / 'MIN' / 'MAX' expands into a real formula over the column's data range). Choose this over render_pdf when the recipient will sort, filter or recompute the numbers, and over render_docx when the content is tabular rather than prose. Returns a stored render { id, url, bytes, durationMs, format } where url is a signed download link valid for one hour; the workbook is a .xlsx, so feed the id to convert_document if the next step needs a PDF. Header rows are always bold on a tinted fill — there is no flag for it. Counts one render against the monthly quota. Requires a Kamy API key with the `render` scope; without a key, returns dashboard setup instructions.","inputSchema":{"type":"object","properties":{"title":{"type":"string","minLength":1,"maxLength":120,"description":"Workbook title — document metadata and the returned filename stem."},"sheets":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":31,"description":"Sheet tab name. Excel caps this at 31 characters."},"columns":{"type":"array","items":{"type":"object","properties":{"header":{"type":"string","minLength":1,"maxLength":120,"description":"Header label rendered in row 1."},"key":{"type":"string","minLength":1,"maxLength":120,"description":"Property name read off each row object — `row[key]` fills this column."},"width":{"type":"number","minimum":2,"maximum":120,"description":"Column width in characters."},"numFmt":{"type":"string","maxLength":40,"description":"Excel number-format string applied to every cell in the column, e.g. '#,##0.00' for money, '0.00%' for percent, 'yyyy-mm-dd' for dates."}},"required":["header","key"],"additionalProperties":false},"minItems":1,"maxItems":64},"rows":{"type":"array","items":{"type":"object","additionalProperties":{}},"maxItems":50000,"description":"Data rows as objects keyed by the column `key`s."},"totalRow":{"type":"object","additionalProperties":{"type":["string","number","boolean","null"]},"description":"Optional bold, top-bordered footer row keyed by column key. A bare aggregator keyword ('SUM', 'AVG', 'COUNT', 'MIN', 'MAX') is expanded into a real formula over that column's data range; a string starting with '=' is passed to Excel verbatim; anything else lands as a literal, which is how you write a label like { name: 'Total' }."},"freeze":{"type":"boolean","default":true,"description":"Freeze the header row and first column. Default true."}},"required":["name","columns","rows"],"additionalProperties":false},"minItems":1,"maxItems":20,"description":"One or more sheets, in tab order."}},"required":["sheets"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"execution":{"taskSupport":"forbidden"},"_meta":{"securitySchemes":[{"type":"noauth"}]},"securitySchemes":[{"type":"noauth"}]},{"name":"render_html","title":"Render template to HTML","description":"Compile a Kamy template — or raw Handlebars source you pass inline — against a data payload and get the rendered HTML string back. No browser runs, no PDF is produced and no file is stored, so this is the tool for piping a template into a transactional email provider, or for inspecting the markup before committing to render_pdf. Use render_pdf instead whenever the output has to be a paginated, printable artifact. Supply exactly one of template or html. Returns { format: 'html', html, bytes }. Paid-tier system templates are refused on the free plan. Counts one render against the monthly quota, since the compile step is the shared cost. Requires a Kamy API key with the `render` scope; without a key, returns dashboard setup instructions.","inputSchema":{"type":"object","properties":{"template":{"type":"string","minLength":1,"description":"Template slug or UUID — a Kamy system template or one of your own. Supply exactly one of template or html."},"html":{"type":"string","minLength":1,"maxLength":5000000,"description":"Raw Handlebars/HTML source to compile instead of a stored template. Supply exactly one of template or html."},"data":{"type":"object","additionalProperties":{},"default":{},"description":"Values substituted into the template's Handlebars expressions."},"direction":{"type":"string","enum":["auto","ltr","rtl"],"description":"Force the document's lang/dir attributes for RTL/bidi mail clients. 'auto' (default) leaves the template's own <html lang> untouched."}},"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"execution":{"taskSupport":"forbidden"},"_meta":{"securitySchemes":[{"type":"noauth"}]},"securitySchemes":[{"type":"noauth"}]},{"name":"convert_document","title":"Convert file to PDF","description":"Convert a file you already hold — .docx, .xlsx or .csv — into a PDF, preserving its existing content. Pass the bytes base64-encoded together with the original filename, which is what the API uses to detect the input type. This is the inbound direction: it consumes an existing document, whereas render_docx / render_xlsx / render_pptx GENERATE new documents from structured data, and render_pdf builds one from a template. Returns a stored render { id, url, bytes, durationMs, name } whose id can be fed straight into merge_pdfs, split_pdf, edit_pdf, create_signature_request or create_envelope. Counts one render against the monthly quota. Files over 10 MB are refused by this tool because the bytes travel through the tool call. Requires a Kamy API key with the `render` scope; without a key, returns dashboard setup instructions.","inputSchema":{"type":"object","properties":{"filename":{"type":"string","minLength":1,"maxLength":255,"description":"Source filename including its extension — the route detects the input type from it. Must end in .docx, .xlsx or .csv."},"fileBase64":{"type":"string","minLength":1,"description":"Base64-encoded bytes of the source file."},"name":{"type":"string","minLength":1,"maxLength":120,"description":"Label for the resulting render row. Defaults to the source filename."}},"required":["filename","fileBase64"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"execution":{"taskSupport":"forbidden"},"_meta":{"securitySchemes":[{"type":"noauth"}]},"securitySchemes":[{"type":"noauth"}]},{"name":"merge_pdfs","title":"Merge renders into one PDF","description":"Concatenate 2–20 existing renders into a single new PDF, in exactly the order the ids are given. Both the inputs and the output are Kamy render ids, so this is the composition step after several render_pdf / convert_document / edit_pdf calls — it cannot merge arbitrary URLs or raw bytes, and every id must belong to this account and point at a completed render or the whole call fails. The source renders are left untouched. Returns a new render { id, url, bytes, durationMs }. Billed as one additional render. Requires a Kamy API key with the `render` scope; without a key, returns dashboard setup instructions.","inputSchema":{"type":"object","properties":{"renderIds":{"type":"array","items":{"type":"string","format":"uuid"},"minItems":2,"maxItems":20,"description":"2–20 render UUIDs owned by this account, in the page order you want. Any id that isn't yours, or whose render didn't complete, fails the whole call."},"name":{"type":"string","minLength":1,"maxLength":120,"description":"Label for the merged render, e.g. 'Q3 statement bundle'."}},"required":["renderIds"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"execution":{"taskSupport":"forbidden"},"_meta":{"securitySchemes":[{"type":"noauth"}]},"securitySchemes":[{"type":"noauth"}]},{"name":"split_pdf","title":"Split a render into page ranges","description":"Extract page ranges from one existing render into separate new PDFs — the inverse of merge_pdfs. Each range you pass produces its own render, returned in the same order, so one call can both halve a contract and peel off single pages. Omit a range's `to` to run to the end of the document; a range starting past the last page fails the entire call. The source render is left untouched. Returns { renders: [...], count }, each entry a normal render object usable with merge_pdfs, edit_pdf or the signature tools. Requires a Kamy API key with the `render` scope; without a key, returns dashboard setup instructions.","inputSchema":{"type":"object","properties":{"renderId":{"type":"string","format":"uuid","description":"UUID of the completed render to split. Must belong to this account."},"ranges":{"type":"array","items":{"type":"object","properties":{"from":{"type":"integer","minimum":1,"description":"1-based first page, inclusive."},"to":{"type":"integer","minimum":1,"description":"1-based last page, inclusive. Omit to run to the end of the document."},"name":{"type":"string","minLength":1,"maxLength":120,"description":"Label for this output render."}},"required":["from"],"additionalProperties":false},"minItems":1,"maxItems":50,"description":"1–50 page ranges; each produces one output render, returned in this order. A range that starts past the last page fails the whole call."}},"required":["renderId","ranges"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"execution":{"taskSupport":"forbidden"},"_meta":{"securitySchemes":[{"type":"noauth"}]},"securitySchemes":[{"type":"noauth"}]},{"name":"edit_pdf","title":"Edit an existing PDF","description":"Modify an existing PDF: fill AcroForm fields by name, stamp text at absolute coordinates, or paint opaque boxes over regions. Operations apply in the order given, to either a render you own (renderId) or a PDF Kamy downloads from a public URL (pdfUrl) — supply exactly one. Use this when a document already exists and needs values or marks; use render_pdf when the document should be generated from a template instead. Nothing is overwritten: the source is untouched and the result is a NEW render whose id feeds directly into create_signature_request, create_envelope, merge_pdfs or split_pdf. Coordinates are PDF points with the origin at the BOTTOM-left. AcroForm values are flattened by default so they cannot be edited after signing. 'cover' paints an opaque rectangle over the region — it hides content visually but does NOT delete the underlying bytes, and the response carries a COVER_VISUAL_ONLY warning for every cover op. Anyone can still copy the text out from under the box. It is NOT redaction: never use it to hide secrets or personal data in a document you are about to hand out, and do not describe the result as redacted. There is no redaction operation — 'op: redact' is rejected with REDACTION_NOT_SUPPORTED. To remove sensitive data, regenerate the document without it. Returns { id, url, bytes, durationMs, name, warnings } — always read warnings, since out-of-range pages and unmatched field names are reported there rather than failing the call. Requires a Kamy API key with the `render` scope; without a key, returns dashboard setup instructions.","inputSchema":{"type":"object","properties":{"renderId":{"type":"string","format":"uuid","description":"UUID of an existing render owned by this account. Supply exactly one of renderId or pdfUrl."},"pdfUrl":{"type":"string","format":"uri","description":"Publicly fetchable URL of a PDF Kamy has never seen. Kamy downloads it, applies the operations and stores only the edited result — the source does not become a separate render, and the call costs one render either way. Supply exactly one of renderId or pdfUrl."},"operations":{"type":"array","items":{"anyOf":[{"type":"object","properties":{"op":{"type":"string","const":"fill_field"},"field":{"type":"string","minLength":1,"maxLength":200,"description":"AcroForm field name."},"value":{"anyOf":[{"type":"string","maxLength":2000},{"type":"boolean"}],"description":"Text value, or a boolean for checkbox widgets."}},"required":["op","field","value"],"additionalProperties":false},{"type":"object","properties":{"op":{"type":"string","const":"stamp_text"},"page":{"type":"integer","minimum":1,"maximum":500,"description":"1-based page number."},"x":{"type":"number","minimum":0,"maximum":10000,"description":"X in PDF points from the left edge."},"y":{"type":"number","minimum":0,"maximum":10000,"description":"Y in PDF points from the BOTTOM edge."},"text":{"type":"string","minLength":1,"maxLength":2000},"fontSize":{"type":"number","minimum":1,"maximum":300},"color":{"type":"string","pattern":"^#[0-9a-fA-F]{6}$","description":"6-digit hex colour, e.g. '#101014'."}},"required":["op","page","x","y","text"],"additionalProperties":false},{"type":"object","properties":{"op":{"type":"string","const":"cover"},"page":{"type":"integer","minimum":1,"maximum":500,"description":"1-based page number."},"x":{"type":"number","minimum":0,"maximum":10000,"description":"X in PDF points from the left edge."},"y":{"type":"number","minimum":0,"maximum":10000,"description":"Y in PDF points from the BOTTOM edge."},"w":{"type":"number","minimum":1,"maximum":10000,"description":"Rectangle width in points."},"h":{"type":"number","minimum":1,"maximum":10000,"description":"Rectangle height in points."},"color":{"$ref":"#/properties/operations/items/anyOf/1/properties/color","description":"Fill colour of the box. Defaults to black."}},"required":["op","page","x","y","w","h"],"additionalProperties":false}]},"minItems":1,"maxItems":200,"description":"1–200 operations applied in the order given. 'fill_field' writes into an AcroForm widget; 'stamp_text' draws text at an absolute position; 'cover' paints an opaque rectangle over the region — it hides content visually but does NOT delete the underlying bytes, and the response carries a COVER_VISUAL_ONLY warning for every cover op. Anyone can still copy the text out from under the box. It is NOT redaction: never use it to hide secrets or personal data in a document you are about to hand out, and do not describe the result as redacted. There is no redaction operation — 'op: redact' is rejected with REDACTION_NOT_SUPPORTED. To remove sensitive data, regenerate the document without it."},"name":{"type":"string","minLength":1,"maxLength":120,"description":"Label for the resulting render row."},"flattenFields":{"type":"boolean","default":true,"description":"Bake AcroForm values into the page content stream after the fill_field ops so they can no longer be edited. Leave true before e-signing. Pass false only when something downstream still needs the live form."}},"required":["operations"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":true},"execution":{"taskSupport":"forbidden"},"_meta":{"securitySchemes":[{"type":"noauth"}]},"securitySchemes":[{"type":"noauth"}]},{"name":"upload_file","title":"Upload an asset","description":"Store an image, font or PDF in the account's asset bucket and get back a `kamy://asset/<id>` reference you can drop anywhere inside a render_pdf / render_html data payload — Kamy swaps it for a fresh signed URL at render time. That reference is the point of this tool: it is how a logo, signature image or custom font gets into a template without hosting it yourself. Pass contentBase64 and this server performs the upload for you, returning { id, assetRef, bytes, uploaded: true }. Omit contentBase64 for files too big to pass through a tool call and you get the raw slot instead — { uploadUrl, uploadMethod, uploadHeaders, expiresAt, uploaded: false } — then PUT the bytes yourself within 15 minutes. Inline uploads are capped at 5 MB here; the API itself allows 100 MB via that URL. Only the listed MIME types are accepted. Requires a Kamy API key with the `uploads:write` scope; without a key, returns dashboard setup instructions.","inputSchema":{"type":"object","properties":{"filename":{"type":"string","minLength":1,"maxLength":255,"description":"Name to store the asset under. Characters outside [A-Za-z0-9._-] are replaced with '_'."},"contentType":{"type":"string","enum":["image/png","image/jpeg","image/webp","image/gif","image/svg+xml","application/pdf","font/woff","font/woff2","font/ttf","font/otf","application/octet-stream"],"description":"MIME type. The API rejects anything outside this list."},"contentBase64":{"type":"string","minLength":1,"description":"Base64 bytes to upload. When supplied, this server PUTs them to the pre-signed URL and the asset is immediately usable. Omit for files too large to pass through a tool call — you then get uploadUrl back and PUT the bytes yourself within 15 minutes."},"sizeBytes":{"type":"integer","exclusiveMinimum":0,"maximum":104857600,"description":"Declared size in bytes. Computed automatically when contentBase64 is supplied. Max 100 MB."}},"required":["filename","contentType"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"execution":{"taskSupport":"forbidden"},"_meta":{"securitySchemes":[{"type":"noauth"}]},"securitySchemes":[{"type":"noauth"}]},{"name":"get_upload","title":"Get an uploaded asset","description":"Look up a stored asset by its upload id: filename, contentType, sizeBytes, status, its `kamy://asset/<id>` reference, and a freshly signed downloadUrl valid for one hour once the bytes exist. Its main use is confirming an out-of-band transfer landed — upload_file returns uploaded:false when the file was too large to pass inline and you PUT it yourself — because this read reconciles a still-'pending' row against storage and flips it to 'uploaded' the first time it sees the object. Also the way to mint a fresh download link after an earlier signed URL expired. Read-only and spends no quota. Requires a Kamy API key with the `uploads:read` scope; without a key, returns dashboard setup instructions.","inputSchema":{"type":"object","properties":{"uploadId":{"type":"string","format":"uuid","description":"Upload UUID returned by upload_file. The bare UUID, not the kamy://asset/... ref."}},"required":["uploadId"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"execution":{"taskSupport":"forbidden"},"_meta":{"securitySchemes":[{"type":"noauth"}]},"securitySchemes":[{"type":"noauth"}]},{"name":"validate_payload","title":"Validate payload","description":"Dry-run a render payload against a template's schema WITHOUT producing a PDF or using quota. Returns per-field self-healing errors (expected type, value received, allowed values, an example) so you can fix the data before render_pdf. Requires a Kamy API key.","inputSchema":{"type":"object","properties":{"template":{"type":"string","description":"Template slug (e.g. 'invoice') or template UUID."},"data":{"type":"object","additionalProperties":{},"description":"The data payload to validate against the template schema."}},"required":["template","data"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"execution":{"taskSupport":"forbidden"},"_meta":{"securitySchemes":[{"type":"noauth"}]},"securitySchemes":[{"type":"noauth"}]},{"name":"extract_document","title":"Extract document (Kamy Ingest)","description":"Extract structured data from a PDF (invoice, receipt, contract, ID document, or any form). Returns the parsed JSON plus a public verify URL that proves the extraction matches the source. Use this when an agent needs to read an inbound document and act on it.","inputSchema":{"type":"object","properties":{"template":{"type":"string","enum":["invoice","receipt","contract","id_document","generic_form"],"description":"Predefined template id. invoice/receipt for AP and POS docs, contract for legal agreements, id_document for passports/IDs, generic_form for anything else."},"source_url":{"type":"string","format":"uri","description":"Public URL to a PDF (preferred). One of source_url or source_base64 is required."},"source_base64":{"type":"string","description":"Base64-encoded PDF bytes. Use when the source isn't publicly fetchable."}},"required":["template"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"execution":{"taskSupport":"forbidden"},"_meta":{"securitySchemes":[{"type":"noauth"}]},"securitySchemes":[{"type":"noauth"}]},{"name":"ask_kamy","title":"Ask Kamy","description":"Ask Kamy Brain a question about Kamy usage, templates, plans, or errors. Sends the question to Kamy's public assistant endpoint and returns a paragraph answer.","inputSchema":{"type":"object","properties":{"question":{"type":"string","minLength":1,"maxLength":2000,"description":"The question to ask Kamy about — how to render a template, why a render failed, what plan to pick, etc."}},"required":["question"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"execution":{"taskSupport":"forbidden"},"_meta":{"securitySchemes":[{"type":"noauth"}]},"securitySchemes":[{"type":"noauth"}]},{"name":"create_signature_request","title":"Send for e-signature","description":"Send a previously rendered PDF to a signer for e-signature when a Kamy API key is configured. Without a key, returns dashboard setup instructions.","inputSchema":{"type":"object","properties":{"renderId":{"type":"string","format":"uuid","description":"Render UUID returned by render_pdf or any /v1/render call. The render's PDF is the document the signer will receive."},"signerEmail":{"type":"string","format":"email","description":"Recipient email address."},"signerName":{"type":"string","minLength":1,"maxLength":120,"description":"Recipient full name. Must be typed verbatim by the signer to confirm intent."},"position":{"type":"object","properties":{"page":{"type":"integer","minimum":1,"maximum":500},"x":{"type":"number","minimum":0,"maximum":2000},"y":{"type":"number","minimum":0,"maximum":2000},"w":{"type":"number","minimum":40,"maximum":800},"h":{"type":"number","minimum":20,"maximum":400}},"additionalProperties":false,"description":"Optional sender-chosen signature placement in PDF points (72 dpi, origin bottom-left). Defaults to bottom-right of the last page sized 220×64 pt; when omitted, the signer can drag the placeholder on the sign page."},"message":{"type":"string","maxLength":500,"description":"Optional message rendered in the email invitation body."},"authMethod":{"type":"string","enum":["link","sms_otp","email_otp"],"description":"Identity-verification mode. `link` (default) — possession of the secret URL is the only check. `email_otp` — sign page renders a 6-digit OTP gate; code emailed to `signerEmail`. `sms_otp` — same gate, code texted to `signerPhone` (which must then be supplied). Recommend `email_otp` for higher-value transactions."},"signerPhone":{"type":"string","pattern":"^\\+[1-9]\\d{6,14}$","description":"E.164 phone number. Required when `authMethod` is `sms_otp`; ignored otherwise. Example: `+14155551234`."},"ccEmails":{"type":"array","items":{"type":"string","format":"email"},"maxItems":10,"description":"Up to 10 observer addresses CC'd on the invite and the completion notice. Not signers themselves."},"expiresIn":{"type":"integer","minimum":3600,"maximum":2592000,"description":"How long (seconds) the sign link stays valid. Defaults to 30 days. Min 1 hour, max 30 days."},"reminderCadenceHours":{"type":"integer","minimum":24,"maximum":168,"description":"When set (24–168), the auto-reminder cron resends the invite every N hours while the request stays pending, up to 3 reminders. Omit for no auto-reminders."},"signOnEveryPage":{"type":"boolean","description":"When true, the server stamps the signer's signature on every page of the source PDF instead of only the configured position. Common B2B contract pattern."},"requireStamp":{"type":"boolean","description":"Require the signer to upload a company stamp / seal alongside their personal signature (UAE, KSA, JP, KR, IN, CN B2B workflows). Server refuses to seal without one."},"signatureTemplateId":{"type":"string","format":"uuid","description":"Apply a signature template's defaults (placedFields, position, message, expiresIn, ccEmails). Request-level fields override the template."},"placedFields":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":120,"description":"Unique field name within the request."},"type":{"type":"string","enum":["text","textarea","checkbox","date","initials","radio","dropdown"],"description":"Widget the signer sees. `radio` and `dropdown` also need `options`. Every type here is accepted by both create_signature_request and preview_field_placement."},"page":{"type":"integer","minimum":1,"maximum":500,"description":"1-indexed page number."},"x":{"type":"number","minimum":0,"maximum":2000,"description":"Bottom-left x in PDF points — an offset from the anchor when `anchor` is set."},"y":{"type":"number","minimum":0,"maximum":2000,"description":"Bottom-left y in PDF points — an offset from the anchor when `anchor` is set."},"w":{"type":"number","minimum":8,"maximum":800,"description":"Width in PDF points."},"h":{"type":"number","minimum":8,"maximum":800,"description":"Height in PDF points."},"required":{"type":"boolean"},"defaultValue":{"type":"string","maxLength":2000},"signerLabel":{"type":"string","maxLength":200,"description":"Which signer this field belongs to."},"options":{"type":"array","items":{"type":"string","minLength":1,"maxLength":200},"minItems":1,"maxItems":50,"description":"Valid choices for radio / dropdown. Ignored for other types."},"sourcePageWidth":{"type":"number","minimum":72,"maximum":7200,"description":"Page width the coordinates were measured against. Supply with sourcePageHeight."},"sourcePageHeight":{"type":"number","minimum":72,"maximum":7200,"description":"Page height the coordinates were measured against. Supply with sourcePageWidth."},"anchor":{"type":"string","minLength":1,"maxLength":200,"description":"Text to position this field against. The server searches only the page named in `page` (case-insensitive, first match wins) and uses that text's bottom-left corner as the origin, adding x/y as offsets. If the text is not on that page the field silently falls back to the raw x/y — call preview_field_placement first to find out, where the miss comes back as an ANCHOR_NOT_FOUND issue."}},"required":["name","type","page","x","y","w","h"],"additionalProperties":false},"maxItems":100,"description":"Up to 100 sender-defined fillable fields stamped onto the PDF at sign time. Use for flat PDFs that don't ship AcroForm widgets. Names must be unique."}},"required":["renderId","signerEmail","signerName"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":true},"execution":{"taskSupport":"forbidden"},"_meta":{"securitySchemes":[{"type":"noauth"}]},"securitySchemes":[{"type":"noauth"}]},{"name":"create_envelope","title":"Send one PDF to multiple signers","description":"Send a single PDF to 2–10 signers as one envelope. This is the multi-signer counterpart to create_signature_request, which handles exactly one signer — the API rejects an envelope with fewer than two recipients, so pick the tool by signer count. routing 'parallel' (default) emails everyone at once and each signs independently; routing 'sequential' emails only the lowest-order recipient and activates the rest one at a time as each preceding signer finishes, which is what you want for approve-then-countersign chains. The source is either a render you own (renderId) or a PDF fetched from a public URL (pdfUrl) — supply exactly one. Pass preview: true to create the envelope and get every sign URL back WITHOUT sending any email and WITHOUT consuming quota; do that first if you are unsure about field placement. A real send emails the recipients immediately and consumes one signature from the monthly quota PER recipient (free tier: 10 signatures/month, then per-signature billing). Returns { envelope, recipients: [...] } with a sign_url per recipient. Requires a Kamy API key with the `signatures:write` scope; without a key, returns dashboard setup instructions.","inputSchema":{"type":"object","properties":{"renderId":{"type":"string","format":"uuid","description":"UUID of a completed render owned by this account. Supply exactly one of renderId or pdfUrl."},"pdfUrl":{"type":"string","format":"uri","description":"Publicly fetchable PDF URL. Kamy downloads it and stores it as a render first. Supply exactly one of renderId or pdfUrl."},"recipients":{"type":"array","items":{"type":"object","properties":{"email":{"type":"string","format":"email"},"name":{"type":"string","minLength":1,"maxLength":120,"description":"Signer's full name. They must type it verbatim to confirm intent."},"order":{"type":"integer","minimum":1,"description":"Signing position. Drives who goes first under sequential routing; ignored for ordering purposes under parallel routing. Defaults to array order."},"message":{"type":"string","maxLength":500,"description":"Per-recipient note in the invitation email, overriding the envelope-level message."}},"required":["email","name"],"additionalProperties":false},"minItems":2,"maxItems":10,"description":"2–10 signers. The route rejects a single recipient — use create_signature_request for one signer."},"routing":{"type":"string","enum":["parallel","sequential"],"default":"parallel","description":"'parallel' (default) emails everyone at once and each signs independently. 'sequential' emails only the lowest-order recipient; the rest are created with status 'waiting' and activated one at a time as the preceding signer completes."},"position":{"type":"object","properties":{"page":{"type":"integer","minimum":1,"maximum":500},"x":{"type":"number","minimum":0,"maximum":2000},"y":{"type":"number","minimum":0,"maximum":2000},"w":{"type":"number","minimum":40,"maximum":800},"h":{"type":"number","minimum":20,"maximum":400}},"additionalProperties":false,"description":"Default signature placement in PDF points, origin bottom-left."},"message":{"type":"string","maxLength":500,"description":"Message included in every invitation email."},"placedFields":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":120,"description":"Unique field name within the envelope."},"type":{"type":"string","enum":["text","textarea","checkbox","date","initials","radio","dropdown"]},"page":{"type":"integer","minimum":1,"maximum":500,"description":"1-indexed page number."},"x":{"type":"number","minimum":0,"maximum":2000,"description":"Bottom-left x in PDF points."},"y":{"type":"number","minimum":0,"maximum":2000,"description":"Bottom-left y in PDF points."},"w":{"type":"number","minimum":8,"maximum":800,"description":"Width in PDF points."},"h":{"type":"number","minimum":8,"maximum":800,"description":"Height in PDF points."},"required":{"type":"boolean"},"defaultValue":{"type":"string","maxLength":2000},"signerLabel":{"type":"string","maxLength":200,"description":"Which signer this field belongs to."},"options":{"type":"array","items":{"type":"string","minLength":1,"maxLength":200},"minItems":1,"maxItems":50,"description":"Valid choices for radio / dropdown."},"anchor":{"type":"string","minLength":1,"maxLength":200,"description":"Text anchor — the server locates this string in the PDF and uses its position."},"sourcePageWidth":{"type":"number","minimum":72,"maximum":7200,"description":"Page width the coordinates were measured against, for rescaling."},"sourcePageHeight":{"type":"number","minimum":72,"maximum":7200,"description":"Page height the coordinates were measured against, for rescaling."}},"required":["name","type","page","x","y","w","h"],"additionalProperties":false},"maxItems":100,"description":"Up to 100 sender-placed form fields for signers to complete."},"expiresIn":{"type":"integer","minimum":3600,"maximum":2592000,"description":"Sign-link lifetime in seconds. Min 1 hour, max 30 days."},"ccEmails":{"type":"array","items":{"type":"string","format":"email"},"maxItems":10,"description":"Up to 10 observers CC'd on the invitations. Not signers."},"preview":{"type":"boolean","description":"When true, creates the envelope and returns every sign URL WITHOUT emailing anyone and without consuming signature quota. Use it to check placement and routing before the real send."}},"required":["recipients"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":true},"execution":{"taskSupport":"forbidden"},"_meta":{"securitySchemes":[{"type":"noauth"}]},"securitySchemes":[{"type":"noauth"}]},{"name":"list_signature_requests","title":"List signature requests","description":"List signature requests created by the configured Kamy account, newest first. Without a key, returns dashboard setup instructions.","inputSchema":{"type":"object","properties":{"limit":{"type":"integer","minimum":1,"maximum":100,"description":"Default 50."},"offset":{"type":"integer","minimum":0,"description":"Default 0."}},"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"execution":{"taskSupport":"forbidden"},"_meta":{"securitySchemes":[{"type":"noauth"}]},"securitySchemes":[{"type":"noauth"}]},{"name":"get_signature_certificate","title":"Get Certificate of Completion URL","description":"Returns the authenticated download URL for a signature request's Certificate of Completion PDF — the process audit trail (invite → opened → consent → signed, with IP / user-agent) that legal teams expect. Only available after the request reaches a terminal state. Without a key, returns dashboard setup instructions.","inputSchema":{"type":"object","properties":{"signatureRequestId":{"type":"string","format":"uuid","description":"ID of the signature_request to fetch the Certificate of Completion for. Must be in a terminal state (signed / declined / delegated / voided / expired); pending requests return 409."}},"required":["signatureRequestId"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"execution":{"taskSupport":"forbidden"},"_meta":{"securitySchemes":[{"type":"noauth"}]},"securitySchemes":[{"type":"noauth"}]},{"name":"get_signature_request","title":"Get a signature request","description":"Fetch one signature request by id, with the detail list_signature_requests leaves out: the placed_fields layout, signed_at plus signed_ip and signed_user_agent, last_reminded_at, cc_emails, and envelope_id + recipient_order when it is part of an envelope. Use list_signature_requests to find an id and this to inspect it; use get_signature_certificate when what you actually want is the legal audit-trail PDF rather than the row. `status` is one of pending, waiting, signed, declined, delegated, voided or expired — `waiting` means a sequential envelope has not reached this signer yet, so nothing is wrong and no reminder is due. Read-only and spends no quota. Requires a Kamy API key with the `signatures:read` scope; without a key, returns dashboard setup instructions.","inputSchema":{"type":"object","properties":{"signatureRequestId":{"type":"string","format":"uuid","description":"ID returned by create_signature_request, bulk_signature_requests, or a list call."}},"required":["signatureRequestId"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"execution":{"taskSupport":"forbidden"},"_meta":{"securitySchemes":[{"type":"noauth"}]},"securitySchemes":[{"type":"noauth"}]},{"name":"get_envelope","title":"Get an envelope","description":"Fetch one multi-signer envelope with every recipient in signing order — each with a live sign_url, status, recipient_order, expires_at and last_reminded_at — plus the envelope's own status and routing. This is the tool for 'who still has not signed?' after a create_envelope send; get_signature_request answers that for a single standalone request and returns no recipient list. Under sequential routing, recipients whose turn has not arrived show status 'waiting'. Read-only and spends no quota. Requires a Kamy API key with the `signatures:read` scope; without a key, returns dashboard setup instructions.","inputSchema":{"type":"object","properties":{"envelopeId":{"type":"string","format":"uuid","description":"Envelope ID returned by create_envelope."}},"required":["envelopeId"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"execution":{"taskSupport":"forbidden"},"_meta":{"securitySchemes":[{"type":"noauth"}]},"securitySchemes":[{"type":"noauth"}]},{"name":"remind_signature","title":"Remind a signer or an envelope","description":"Resend the signature invitation email. Pass signatureRequestId to nudge one signer, or envelopeId to nudge every currently-pending recipient of an envelope — supply exactly one; there is no separate envelope-reminder tool. This sends real email to third parties. Each recipient is capped at one reminder per hour: the single-request form returns 429 REMIND_TOO_SOON with a Retry-After header, while the envelope form silently skips capped recipients and reports skipped_reason per row, so read the per-recipient results rather than assuming everyone was mailed. Only pending recipients are reminded — signed, voided and expired requests return 409, and sequential recipients still in 'waiting' are skipped because it is not their turn. A manual nudge also counts toward the three-reminder auto-cadence cap. Requires a Kamy API key with the `signatures:write` scope; without a key, returns dashboard setup instructions.","inputSchema":{"type":"object","properties":{"signatureRequestId":{"type":"string","format":"uuid","description":"Nudge this one signer. Supply exactly one of signatureRequestId or envelopeId."},"envelopeId":{"type":"string","format":"uuid","description":"Nudge every currently-pending recipient of this envelope. Supply exactly one of signatureRequestId or envelopeId."}},"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":true},"execution":{"taskSupport":"forbidden"},"_meta":{"securitySchemes":[{"type":"noauth"}]},"securitySchemes":[{"type":"noauth"}]},{"name":"bulk_signature_requests","title":"Send one PDF to many separate signers","description":"Fan one already-rendered PDF out to up to 100 independent signers in a single call — the 'send this NDA to everyone on the list' path. Each signer gets their own request, their own sign link and their own invitation email, but they all share one source render, so only one document is produced and stored. Choose create_envelope instead when the recipients are signing the SAME document together and you need routing, ordering and one envelope status; choose this when they are unrelated parties each signing their own copy. Duplicate signer emails within one batch are rejected up front with 422. Per-signer failures do not abort the batch: the response is { bulkId, count, successCount, failureCount, results } in input order, each row either ok with signatureId and signUrl or ok:false with a reason, and the call returns 207 when any row failed. Consumes one signature from the monthly quota PER signer (free tier: 10 per month, then per-signature billing), so a large batch can exhaust a small plan part-way through and the remaining rows come back failed. Requires a Kamy API key with the `signatures:write` scope; without a key, returns dashboard setup instructions.","inputSchema":{"type":"object","properties":{"renderId":{"type":"string","format":"uuid","description":"Render UUID every signer receives. One render, one storage object, many requests."},"signers":{"type":"array","items":{"type":"object","properties":{"signerEmail":{"type":"string","format":"email","maxLength":200,"description":"Recipient email. Must be unique in the batch."},"signerName":{"type":"string","minLength":1,"maxLength":200,"description":"Recipient full name. They must type it verbatim to confirm intent."},"ccEmails":{"type":"array","items":{"type":"string","format":"email","maxLength":200},"maxItems":10,"description":"Up to 10 observers CC'd on this signer's invite. Not signers themselves."}},"required":["signerEmail","signerName"],"additionalProperties":false},"minItems":1,"maxItems":100,"description":"1–100 signers. Duplicate emails are rejected up front with 422."},"message":{"type":"string","maxLength":2000,"description":"Shared invite message. `{{signerName}}` is substituted per recipient."},"expiresIn":{"type":"integer","minimum":3600,"maximum":2592000,"description":"Sign-link lifetime in seconds. Min 1 hour, max 30 days. Defaults to 7 days."},"reminderCadenceHours":{"type":"integer","minimum":24,"maximum":168,"description":"Auto-reminder cadence in hours, shared across the batch. Up to 3 reminders."},"position":{"type":"object","properties":{"page":{"type":"integer","minimum":1,"maximum":500},"x":{"type":"number","minimum":0,"maximum":2000},"y":{"type":"number","minimum":0,"maximum":2000},"w":{"type":"number","minimum":40,"maximum":800},"h":{"type":"number","minimum":20,"maximum":400}},"additionalProperties":false,"description":"One signature rectangle in PDF points (origin bottom-left) shared by every signer."}},"required":["renderId","signers"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":true},"execution":{"taskSupport":"forbidden"},"_meta":{"securitySchemes":[{"type":"noauth"}]},"securitySchemes":[{"type":"noauth"}]},{"name":"preview_field_placement","title":"Preview signature field placement","description":"Check a placedFields layout against a real render's page geometry before anything is sent: no signature request is created, no email goes out, and no signature quota is spent. It accepts exactly what create_signature_request accepts — all seven field types, `options`, and anchor-positioned fields — so anything that previews clean will send. Returns each page's true width and height, so a sender UI can draw a preview at the right aspect ratio, plus per field valid / issues / the resolved coordinates the request would actually be stored with, after sourcePage scaling and anchor substitution. Issues are PAGE_OUT_OF_RANGE, OFF_PAGE_RIGHT, OFF_PAGE_TOP, DUPLICATE_NAME, ANCHOR_NOT_FOUND (the anchor text is not on that page, so the field falls back to the raw x/y) and OPTIONS_REQUIRED. None of these make create_signature_request fail — that is the point of checking here, because a field that lands off the page is accepted and emailed. The source render must have status 'success'; if its PDF has aged out of storage the call returns 410 and the fix is to re-render. Read-only. Requires a Kamy API key with the `signatures:read` scope; without a key, returns dashboard setup instructions.","inputSchema":{"type":"object","properties":{"renderId":{"type":"string","format":"uuid","description":"Render UUID whose real page sizes the fields are checked against."},"placedFields":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":120,"description":"Unique field name within the request."},"type":{"type":"string","enum":["text","textarea","checkbox","date","initials","radio","dropdown"],"description":"Widget the signer sees. `radio` and `dropdown` also need `options`. Every type here is accepted by both create_signature_request and preview_field_placement."},"page":{"type":"integer","minimum":1,"maximum":500,"description":"1-indexed page number."},"x":{"type":"number","minimum":0,"maximum":2000,"description":"Bottom-left x in PDF points — an offset from the anchor when `anchor` is set."},"y":{"type":"number","minimum":0,"maximum":2000,"description":"Bottom-left y in PDF points — an offset from the anchor when `anchor` is set."},"w":{"type":"number","minimum":8,"maximum":800,"description":"Width in PDF points."},"h":{"type":"number","minimum":8,"maximum":800,"description":"Height in PDF points."},"required":{"type":"boolean"},"defaultValue":{"type":"string","maxLength":2000},"signerLabel":{"type":"string","maxLength":200,"description":"Which signer this field belongs to."},"options":{"type":"array","items":{"type":"string","minLength":1,"maxLength":200},"minItems":1,"maxItems":50,"description":"Valid choices for radio / dropdown. Ignored for other types."},"sourcePageWidth":{"type":"number","minimum":72,"maximum":7200,"description":"Page width the coordinates were measured against. Supply with sourcePageHeight."},"sourcePageHeight":{"type":"number","minimum":72,"maximum":7200,"description":"Page height the coordinates were measured against. Supply with sourcePageWidth."},"anchor":{"type":"string","minLength":1,"maxLength":200,"description":"Text to position this field against. The server searches only the page named in `page` (case-insensitive, first match wins) and uses that text's bottom-left corner as the origin, adding x/y as offsets. If the text is not on that page the field silently falls back to the raw x/y — call preview_field_placement first to find out, where the miss comes back as an ANCHOR_NOT_FOUND issue."}},"required":["name","type","page","x","y","w","h"],"additionalProperties":false},"maxItems":100,"description":"Up to 100 fields to validate. Exactly the shape create_signature_request takes — same seven types, same `anchor` and `options` support — so a layout that previews clean is a layout that sends."}},"required":["renderId","placedFields"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"execution":{"taskSupport":"forbidden"},"_meta":{"securitySchemes":[{"type":"noauth"}]},"securitySchemes":[{"type":"noauth"}]},{"name":"list_signature_templates","title":"List signature templates","description":"List this account's reusable e-signature presets — saved field placements, default invite message, default link lifetime and CC list — newest first. These are signing presets, NOT the document catalog: list_templates is what render_pdf draws from. Rows here carry only id, name, description, expires_in and cc_emails; call get_signature_template for the placed_fields and position. Pass an id as create_signature_request's signatureTemplateId to apply a preset instead of re-specifying the layout every time. Returns { templates, total, limit, offset }. Read-only and spends no quota. Requires a Kamy API key with the `signatures:read` scope; without a key, returns dashboard setup instructions.","inputSchema":{"type":"object","properties":{"limit":{"type":"integer","minimum":1,"maximum":100,"description":"Page size, 1–100. Default 50."},"offset":{"type":"integer","minimum":0,"description":"Rows to skip. Default 0."}},"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"execution":{"taskSupport":"forbidden"},"_meta":{"securitySchemes":[{"type":"noauth"}]},"securitySchemes":[{"type":"noauth"}]},{"name":"get_signature_template","title":"Get a signature template","description":"Fetch one e-signature preset in full, including the placed_fields array and position that list_signature_templates omits. Use it to inspect or copy an existing field layout before applying it through create_signature_request's signatureTemplateId, or as the starting point for a preview_field_placement check against a new render. Read-only and spends no quota. Requires a Kamy API key with the `signatures:read` scope; without a key, returns dashboard setup instructions.","inputSchema":{"type":"object","properties":{"signatureTemplateId":{"type":"string","format":"uuid","description":"ID from list_signature_templates."}},"required":["signatureTemplateId"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"execution":{"taskSupport":"forbidden"},"_meta":{"securitySchemes":[{"type":"noauth"}]},"securitySchemes":[{"type":"noauth"}]},{"name":"pki_sign_pdf","title":"PKI-sign PDF","description":"Cryptographically sign an existing render with PAdES when a Kamy API key is configured. Without a key, returns dashboard setup instructions.","inputSchema":{"type":"object","properties":{"renderId":{"type":"string","format":"uuid","description":"Render UUID returned by render_pdf or any /v1/render call. The PDF will be sealed with a Kamy-issued X.509 leaf certificate."},"reason":{"type":"string","maxLength":200,"description":"Optional /Sig dictionary Reason — surfaced in Acrobat's signature panel. ASCII-coerced server-side."},"location":{"type":"string","maxLength":120,"description":"Optional /Sig dictionary Location."},"signerName":{"type":"string","maxLength":120,"description":"Override the signer display name. Defaults to the account's full_name."},"signerEmail":{"type":"string","format":"email","description":"Override the signer email. Defaults to the account's email."},"withTimestamp":{"type":"boolean","description":"When false, skip the RFC 3161 timestamp call (PAdES-B-B instead of B-T). Default: true."},"withRevocationInfo":{"type":"boolean","description":"When false, skip embedding the Kamy CA CRL into the PKCS#7 SignedData (PAdES-B-T instead of B-LT). Online verifiers can still fetch the CRL via the Distribution Point on the leaf cert. Default: true."}},"required":["renderId"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"execution":{"taskSupport":"forbidden"},"_meta":{"securitySchemes":[{"type":"noauth"}]},"securitySchemes":[{"type":"noauth"}]},{"name":"verify_pdf_signature","title":"Hash a PDF and build its verify URL","description":"Turn PDF bytes you are holding into their SHA-256 digest and the matching kamy.dev/verify/{sha256} page URL. Purely local: the MCP Worker hashes the base64 in memory, makes no Kamy API call, stores nothing and forwards nothing, so it works with no key and never leaves a trace. Note it returns no verdict — it does not tell you whether the document is genuine, signed, or on record anywhere. It is the first half of a check: take the sha256 it returns and pass it to verify_attestation for the actual yes/no, or hand a person the verify_url to open. Use this whenever you have the file itself; use verify_attestation directly when someone has already given you a digest.","inputSchema":{"type":"object","properties":{"pdfBase64":{"type":"string","minLength":1,"description":"Base64-encoded PDF bytes. The MCP Worker hashes the file in-memory and does not store or forward it."}},"required":["pdfBase64"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"execution":{"taskSupport":"forbidden"},"_meta":{"securitySchemes":[{"type":"noauth"}]},"securitySchemes":[{"type":"noauth"}]},{"name":"attest_artifact","title":"Attest artifact","description":"Cryptographically sign an output your agent produced — a PDF, report, code patch, dataset, email — so a third party can later confirm those exact bytes are the ones that were recorded, unmodified. Pass content_text or content_base64 and Kamy hashes the bytes for you; pass content_sha256 alone when the content itself must never leave your environment. Returns { attestation_id, content_sha256, signature, recorded_at, verify_url } — hand verify_url to whoever needs to check the artifact. This proves the bytes match what this account recorded at that time; it makes no claim about whether the content is correct. Requires a Kamy API key.","inputSchema":{"type":"object","properties":{"artifact_type":{"type":"string","minLength":1,"maxLength":64,"description":"What kind of output this is, e.g. 'pdf', 'report', 'code_patch', 'dataset', 'email', 'llm_output'. Free-form label used for filtering later."},"content_sha256":{"type":"string","pattern":"^[0-9a-f]{64}$","description":"SHA-256 of the artifact bytes, when you'd rather not send the content itself. Supply exactly one of content_sha256, content_base64, or content_text."},"content_base64":{"type":"string","minLength":1,"description":"Base64-encoded artifact bytes. Use for binary output (PDF, image, archive). Supply exactly one of content_sha256, content_base64, or content_text."},"content_text":{"type":"string","minLength":1,"description":"UTF-8 text artifact. Convenience input: this MCP server base64-encodes it locally and sends it as content_base64 — no other transformation is applied. Supply exactly one of content_sha256, content_base64, or content_text."},"metadata":{"type":"object","additionalProperties":{},"description":"Arbitrary JSON stored alongside the attestation (model, source URL, reviewer, ...)."},"feature":{"type":"string","maxLength":120,"description":"Product area or pipeline name, for grouping attestations."},"tags":{"type":"array","items":{"type":"string","maxLength":64},"maxItems":20,"description":"Up to 20 free-form labels."},"run_id":{"type":"string","maxLength":200,"description":"Your identifier for the agent run this artifact belongs to. Pass the same run_id across attestations and agent-action records to build one provenance chain readable via get_provenance_chain."},"parent_sha256":{"$ref":"#/properties/content_sha256","description":"content_sha256 of the preceding record in the chain, when this artifact was derived from an earlier one."}},"required":["artifact_type"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"execution":{"taskSupport":"forbidden"},"_meta":{"securitySchemes":[{"type":"noauth"}]},"securitySchemes":[{"type":"noauth"}]},{"name":"verify_attestation","title":"Verify attestation","description":"Ask Kamy whether a SHA-256 digest has an attestation on record — the tool that actually returns a verdict. Takes a digest, not a file: if what you hold is a PDF, run verify_pdf_signature over the bytes first and pass the sha256 it gives you. A match returns { verified: true, artifact_type, recorded_at, signature, public_key }. A false result means no attestation exists for those exact bytes, which happens both when content was altered after attestation and when it was simply never attested; it does not by itself identify tampering or a culprit. Public surface — like the extract_document verify URL, no API key is required, so a recipient can confirm an artifact independently of whoever sent it.","inputSchema":{"type":"object","properties":{"hash":{"type":"string","pattern":"^[0-9a-f]{64}$","description":"SHA-256 of the artifact you want to check, as hex. Hash the bytes you actually hold — if they were modified after attestation, the digest won't match any record and `verified` comes back false."}},"required":["hash"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"execution":{"taskSupport":"forbidden"},"_meta":{"securitySchemes":[{"type":"noauth"}]},"securitySchemes":[{"type":"noauth"}]},{"name":"record_agent_action","title":"Record agent action","description":"Append one signed tool_call -> tool_result pair to a tamper-evident ledger. Call it after any consequential tool invocation — a payment, an outbound email, a write into a customer system — so there is a hash-chained record of what the agent asked for and what came back, signed at write time. Pass a stable run_id to keep an entire agent run in one chain, and parent_sha256 to link records explicitly; read the result back with get_provenance_chain. Returns { record_id, content_sha256, signature, recorded_at, verify_url }. Requires a Kamy API key.","inputSchema":{"type":"object","properties":{"server":{"type":"string","minLength":1,"maxLength":200,"description":"Identifier of the MCP server (or tool provider) the call went to, e.g. 'kamy'."},"tool":{"type":"string","minLength":1,"maxLength":200,"description":"Name of the tool that was invoked."},"tool_call":{"description":"The request you sent — typically the arguments object, verbatim."},"tool_result":{"description":"The result you received back, verbatim."},"run_id":{"type":"string","maxLength":200,"description":"Your identifier for this agent run. Reuse it across records to build one chain."},"parent_sha256":{"type":"string","pattern":"^[0-9a-f]{64}$","description":"content_sha256 of the previous record in this run, to link the chain explicitly."},"status":{"type":"string","enum":["ok","error","flagged"],"description":"Outcome of the call. Defaults to ok."},"latency_ms":{"type":"integer","minimum":0,"description":"Wall-clock duration of the call."}},"required":["server","tool"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"execution":{"taskSupport":"forbidden"},"_meta":{"securitySchemes":[{"type":"noauth"}]},"securitySchemes":[{"type":"noauth"}]},{"name":"get_provenance_chain","title":"Get provenance chain","description":"Replay everything recorded under one run_id — artifacts from attest_artifact and calls from record_agent_action — in order, with each record's link to its parent hash. Use it to answer 'what did this agent actually do, and in what sequence?' during an incident review, a handover, or an audit. Returns { run_id, chain_intact, records: [...] }, where chain_intact is the server's verdict on whether the parent-hash links are unbroken across the run. Read-only. Requires a Kamy API key.","inputSchema":{"type":"object","properties":{"run_id":{"type":"string","minLength":1,"maxLength":200,"description":"The run_id you passed to attest_artifact / record_agent_action."}},"required":["run_id"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"execution":{"taskSupport":"forbidden"},"_meta":{"securitySchemes":[{"type":"noauth"}]},"securitySchemes":[{"type":"noauth"}]},{"name":"trace_record","title":"Record LLM call (Kamy Trace)","description":"Record one LLM call — prompt, output, provider, model, tokens, latency — into Kamy Trace, a signed retention ledger. Each record is hashed and signed at write time, so it can be produced later without the 'you could have edited this' objection. Reach for it when model calls need a reviewable history: regulated workflows, customer-facing generations, anything you may have to explain months later. Set status 'flagged' with status_detail on calls a human should revisit. Returns { id, content_sha256, signature, recorded_at, verify_url }. Requires a Kamy API key with the trace:record scope; each call consumes monthly Trace quota.","inputSchema":{"type":"object","properties":{"provider":{"type":"string","enum":["anthropic","openai","custom"],"description":"Which model vendor served the call. Use 'custom' for anything self-hosted."},"model":{"type":"string","minLength":1,"maxLength":200,"description":"Model identifier exactly as the provider reports it, e.g. 'claude-sonnet-4-6'."},"prompt":{"description":"The prompt / request you sent, verbatim. Any JSON value (string, array, object)."},"output":{"description":"The model's response, verbatim. Any JSON value."},"feature":{"type":"string","maxLength":120,"description":"Product area the call belongs to, e.g. 'support_reply'. Filterable in trace_search."},"tags":{"type":"array","items":{"type":"string","maxLength":64},"maxItems":20,"description":"Up to 20 free-form labels. trace_search can filter on any one of them."},"latency_ms":{"type":"integer","minimum":0,"description":"Wall-clock duration of the call."},"input_tokens":{"type":"integer","minimum":0,"description":"Prompt tokens billed."},"output_tokens":{"type":"integer","minimum":0,"description":"Completion tokens billed."},"status":{"type":"string","enum":["ok","flagged","error"],"description":"Outcome. 'flagged' marks a call you want a human to review later. Defaults to 'ok'."},"status_detail":{"type":"string","maxLength":500,"description":"Why the call was flagged or failed. Free text."}},"required":["provider","model"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"execution":{"taskSupport":"forbidden"},"_meta":{"securitySchemes":[{"type":"noauth"}]},"securitySchemes":[{"type":"noauth"}]},{"name":"trace_record_batch","title":"Record a batch of LLM calls (Kamy Trace)","description":"Write up to 100 Kamy Trace records in one call. Reach for this over trace_record whenever you have more than a couple of buffered LLM calls to persist — it is one auth, one quota check and one round trip instead of N. Each element takes exactly the shape trace_record takes. The whole batch is counted against the monthly Trace quota up front, so a batch that would cross the plan cap is rejected in full with 402 and nothing is stored; split it or upgrade rather than retrying. Returns { records: [{ id, content_sha256, signature, recorded_at, verify_url }] } in input order. Requires a Kamy API key with the `trace:record` scope; without a key, returns dashboard setup instructions.","inputSchema":{"type":"object","properties":{"records":{"type":"array","items":{"type":"object","properties":{"provider":{"type":"string","enum":["anthropic","openai","custom"],"description":"Which model vendor served the call. Use 'custom' for anything self-hosted."},"model":{"type":"string","minLength":1,"maxLength":200,"description":"Model identifier exactly as the provider reports it, e.g. 'claude-sonnet-4-6'."},"prompt":{"description":"The prompt / request you sent, verbatim. Any JSON value (string, array, object)."},"output":{"description":"The model's response, verbatim. Any JSON value."},"feature":{"type":"string","maxLength":120,"description":"Product area the call belongs to, e.g. 'support_reply'. Filterable in trace_search."},"tags":{"type":"array","items":{"type":"string","maxLength":64},"maxItems":20,"description":"Up to 20 free-form labels. trace_search can filter on any one of them."},"latency_ms":{"type":"integer","minimum":0,"description":"Wall-clock duration of the call."},"input_tokens":{"type":"integer","minimum":0,"description":"Prompt tokens billed."},"output_tokens":{"type":"integer","minimum":0,"description":"Completion tokens billed."},"status":{"type":"string","enum":["ok","flagged","error"],"description":"Outcome. 'flagged' marks a call you want a human to review later. Defaults to 'ok'."},"status_detail":{"type":"string","maxLength":500,"description":"Why the call was flagged or failed. Free text."}},"required":["provider","model"],"additionalProperties":false},"minItems":1,"maxItems":100,"description":"1–100 records, each exactly the shape trace_record takes."}},"required":["records"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"execution":{"taskSupport":"forbidden"},"_meta":{"securitySchemes":[{"type":"noauth"}]},"securitySchemes":[{"type":"noauth"}]},{"name":"trace_search","title":"Search Trace records","description":"Query this account's Kamy Trace records, newest first, filtered by feature, status, provider, tag, or time window. Returns record metadata — model, tokens, latency, status, content hash, recorded_at — plus next_cursor for paging; it does not return the stored prompt and output bodies. Use it to answer questions like 'how many flagged calls last week?' or to locate a specific record's id before opening it in the dashboard. Read-only. Requires a Kamy API key with the trace:read scope.","inputSchema":{"type":"object","properties":{"feature":{"type":"string","maxLength":120,"description":"Exact-match filter on the feature label."},"status":{"type":"string","enum":["ok","flagged","error"],"description":"Exact-match filter on outcome."},"provider":{"type":"string","enum":["anthropic","openai","custom"],"description":"Exact-match filter on provider."},"tag":{"type":"string","maxLength":64,"description":"Return records whose tags array contains this tag."},"since":{"type":"string","description":"ISO-8601 timestamp, inclusive lower bound."},"until":{"type":"string","description":"ISO-8601 timestamp, exclusive upper bound."},"limit":{"type":"integer","minimum":1,"maximum":100,"description":"Page size, 1-100. Default 25."},"cursor":{"type":"string","description":"Pass the `next_cursor` from the previous response to fetch the next page."}},"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"execution":{"taskSupport":"forbidden"},"_meta":{"securitySchemes":[{"type":"noauth"}]},"securitySchemes":[{"type":"noauth"}]},{"name":"verify_mcp_server","title":"Verify MCP server manifest","description":"Detect when an MCP server changes its tool manifest after you adopted it — the 'rug pull' case, where a server you already trusted silently rewrites a tool's description or input schema. Pass server_url and Kamy fetches that third-party server's manifest itself, or pass manifest when you already hold it (local or private servers). Returns status 'new' (first fingerprint for this account — nothing to compare against yet), 'unchanged', or 'mutated', plus a per-tool changes list with previous and current hashes. 'unchanged' means only that it matches what Kamy recorded previously; it is not a judgement that the server is trustworthy, and a first-ever 'new' result establishes a baseline rather than clearing anything. Requires a Kamy API key.","inputSchema":{"type":"object","properties":{"server_url":{"type":"string","format":"uri","description":"URL of the MCP server to fingerprint. Kamy fetches its tool manifest server-side. Supply either server_url or manifest."},"manifest":{"description":"A tool manifest you already hold — the `tools/list` result, or an object with a `tools` array. Use this when the server isn't reachable from Kamy (local stdio server, private network). Supply either server_url or manifest."}},"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":true},"execution":{"taskSupport":"forbidden"},"_meta":{"securitySchemes":[{"type":"noauth"}]},"securitySchemes":[{"type":"noauth"}]},{"name":"scan_tool_description","title":"Scan tool description for prompt injection","description":"Heuristic pattern scan of MCP tool description text for prompt-injection tells — instructions addressed at the reading model, data-exfiltration hints, attempts to override your system prompt or hide content. Run it on descriptions from third-party MCP servers before you act on what they say. Returns risk 'low' | 'medium' | 'high' and the matched findings with excerpts. This is a heuristic aid, NOT a security boundary: a 'low' verdict is not evidence that a tool is safe, and an injection phrased to avoid the patterns will score low. Do not treat any result here as clearance to trust an untrusted tool — keep your own judgement and human review in the loop. Read-only: it analyses only the text you pass in and fetches nothing. Requires a Kamy API key.","inputSchema":{"type":"object","properties":{"description":{"type":"string","minLength":1,"maxLength":20000,"description":"A single tool description to scan. Supply description, tools, or both."},"tools":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","maxLength":200,"description":"Tool name, echoed back in findings."},"description":{"type":"string","maxLength":20000,"description":"The tool's description text."}},"required":["description"],"additionalProperties":true},"maxItems":200,"description":"Several tools at once — e.g. the entries of a `tools/list` result. Supply description, tools, or both."}},"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"execution":{"taskSupport":"forbidden"},"_meta":{"securitySchemes":[{"type":"noauth"}]},"securitySchemes":[{"type":"noauth"}]},{"name":"get_started","title":"Get started with Kamy","description":"Everything needed to go from nothing to a rendered document in one call: the steps to create an API key in the dashboard, and — when you name a `framework` — the install command for the Kamy SDK, the environment variable it reads, and the client-setup snippet for that stack. Add a `template` slug and it also returns a ready-to-paste route handler that renders it. Every argument is optional and each one only adds a section, so calling this with no arguments is the right move when a user has no key yet, and calling it with framework + template is the right move when they are wiring the first endpoint. This replaces the separate install_sdk, generate_integration_code and get_api_key_instructions tools removed in 1.5.0. Pure text: it makes no API call, reads no account state, and needs no API key.","inputSchema":{"type":"object","properties":{"framework":{"type":"string","enum":["nextjs","remix","sveltekit","astro","express","fastify","hono","fastapi","flask"],"description":"Stack being integrated. Omit to get only the API-key steps; supply it to also get the install command, the environment variable, and the client-setup snippet."},"packageManager":{"type":"string","enum":["npm","pnpm","yarn","bun"],"description":"Package manager for the install command. Default npm. Ignored for Python stacks."},"template":{"type":"string","minLength":1,"maxLength":120,"description":"Template slug (e.g. 'invoice') to also emit a ready-to-paste route handler that renders it. Requires `framework`. Call list_templates first if you do not know the slug."}},"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"execution":{"taskSupport":"forbidden"},"_meta":{"securitySchemes":[{"type":"noauth"}]},"securitySchemes":[{"type":"noauth"}]},{"name":"get_account","title":"Get account","description":"Read everything about the authenticated Kamy account in one call: profile, plan and plan status, the plan's limits (renders per month, API keys, seats, custom templates, overage pricing, priority queue), month-to-date render usage, and whether documents rendered on this plan carry Kamy's own watermark. This is the only tool that answers any of those questions — there is no separate quota tool. Call it before render_batch, create_schedule, or any long series of renders: usage.renders.remaining is how many the API will still accept, and every render tool fails with 402 QUOTA_EXCEEDED once it hits zero, a failure nothing can recover from within the same calendar month. quota and remaining are null on unmetered plans, which means unlimited, not zero. Also check watermarkPolicy.appliedToRenders before generating something the user intends to send on: it is true on the free plan and cannot be turned off per render. Read-only, spends nothing, and works with any valid API key regardless of its scopes.","inputSchema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"execution":{"taskSupport":"forbidden"},"_meta":{"securitySchemes":[{"type":"noauth"}]},"securitySchemes":[{"type":"noauth"}]},{"name":"list_renders","title":"List renders","description":"List the documents this account has already produced, newest first. This is how you find a PDF made earlier — in a previous turn, by a schedule, or by another process — when you no longer hold its URL. Download URLs returned by the render tools are signed and expire after an hour, so a link from earlier in the conversation is probably dead; find the render here, then call get_render with its id to mint a fresh one. Returns { renders: [{ id, name, templateId, templateName, status, bytes, durationMs, cost, createdAt }], total, page, pageSize, hasMore, currency }. The `name` field is whatever label was passed at render time, which is the fastest way to identify the right document. Read-only and spends no quota.","inputSchema":{"type":"object","properties":{"page":{"type":"integer","minimum":1,"description":"1-based page number. Defaults to 1 (the most recent renders)."},"page_size":{"type":"integer","minimum":1,"maximum":100,"description":"Rows per page, 1-100. Defaults to 20."}},"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"execution":{"taskSupport":"forbidden"},"_meta":{"securitySchemes":[{"type":"noauth"}]},"securitySchemes":[{"type":"noauth"}]},{"name":"get_render","title":"Get render","description":"Fetch one previously produced document by its render id, with a freshly signed download URL valid for the next hour. This is the recovery path for an expired link: the URL is minted at read time, so calling this again on an old render always yields a working download. Use list_renders first if you do not have the id. Returns { id, name, templateId, templateName, status, bytes, durationMs, url, createdAt }, where url is null when the render failed, has not finished yet, or its file has aged out of retention — check status before assuming a download exists. Read-only and spends no quota.","inputSchema":{"type":"object","properties":{"render_id":{"type":"string","minLength":1,"maxLength":64,"description":"Render id (UUID) as returned by render_pdf, render_async/get_job, or list_renders."}},"required":["render_id"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"execution":{"taskSupport":"forbidden"},"_meta":{"securitySchemes":[{"type":"noauth"}]},"securitySchemes":[{"type":"noauth"}]},{"name":"get_render_pages","title":"Get render pages as images","description":"Rasterise every page of an existing render to a PNG image and return one signed 1-hour URL per page, with pixel width and height. Use this when a page has to be looked at rather than read — thumbnails, previews, visual QA of a layout, or an image attachment — and use extract_from_render when you want the text. The render must already have status 'success'; a queued or failed one comes back as 409 RENDER_NOT_READY. Repeated calls overwrite the same page images, so it is safe to retry. Rasterisation costs no render quota. Returns { pages: [{ page, width, height, url }], count, dpi }.","inputSchema":{"type":"object","properties":{"render_id":{"type":"string","minLength":1,"maxLength":64,"description":"Render id (UUID) as returned by render_pdf, render_async/get_job, or list_renders."},"dpi":{"type":"integer","minimum":72,"maximum":300,"description":"Raster resolution. Defaults to 150 (screen quality); 300 for print-quality thumbnails. Clamped to 72-300 server-side."}},"required":["render_id"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"execution":{"taskSupport":"forbidden"},"_meta":{"securitySchemes":[{"type":"noauth"}]},"securitySchemes":[{"type":"noauth"}]},{"name":"extract_from_render","title":"Extract from a render","description":"Read the text or the form fields back out of a document this account already rendered, without uploading anything. Pass type='text' (the default) for per-page text plus a joined fullText string, or type='fields' for the PDF's AcroForm field names, types and current values — the latter is how you discover what edit_pdf can fill in. Choose extract_document instead when the PDF came from outside Kamy or when you need AI-structured JSON against a schema; this tool is a plain mechanical read of an existing render, spends no render quota and no extraction credits. The render must have status 'success' or the call returns 409 RENDER_NOT_READY.","inputSchema":{"type":"object","properties":{"render_id":{"type":"string","minLength":1,"maxLength":64,"description":"Render id (UUID) as returned by render_pdf, render_async/get_job, or list_renders."},"type":{"type":"string","enum":["text","fields"],"description":"'text' (default) returns { pages: [{ page, text }], fullText, pageCount }. 'fields' returns the AcroForm fields as { name, type, value }."}},"required":["render_id"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"execution":{"taskSupport":"forbidden"},"_meta":{"securitySchemes":[{"type":"noauth"}]},"securitySchemes":[{"type":"noauth"}]},{"name":"render_async","title":"Render PDF asynchronously","description":"Queue one PDF render and return immediately with { jobId, status: 'queued' } instead of waiting for the document. Choose this over render_pdf when blocking is not acceptable — a heavy template, a large data set, or a turn where you have other work to do — and poll get_job with the returned jobId until status is 'completed' (the finished render, including its download URL, arrives on the job) or 'failed'. Choose render_pdf when a single document is small enough that waiting a few seconds is fine and you want the URL in one call, and render_batch when you have many documents to make at once. Same template, data and page options as render_pdf, and the same one render from the monthly quota — the quota is checked when the job is accepted, so an over-quota call fails here with 402 rather than silently queueing.","inputSchema":{"type":"object","properties":{"template":{"type":"string","minLength":1,"description":"Template slug (e.g. 'invoice') or template UUID. Same values render_pdf accepts."},"data":{"type":"object","additionalProperties":{},"description":"Data used to populate the template."},"name":{"type":"string","minLength":1,"maxLength":120,"description":"Your own label for this document. Echoed back on the job result and stored on the render, so list_renders can be grepped by it later."},"format":{"type":"string","enum":["a4","letter"],"description":"Paper size. Defaults to a4."},"margin":{"type":"object","properties":{"top":{"type":"string","maxLength":20},"right":{"type":"string","maxLength":20},"bottom":{"type":"string","maxLength":20},"left":{"type":"string","maxLength":20}},"additionalProperties":false,"description":"CSS lengths, e.g. { top: '20mm', bottom: '20mm' }."},"metadata":{"type":"object","properties":{"title":{"type":"string","maxLength":500},"author":{"type":"string","maxLength":200},"subject":{"type":"string","maxLength":500},"keywords":{"type":"array","items":{"type":"string","maxLength":100},"maxItems":50},"creator":{"type":"string","maxLength":200}},"additionalProperties":false,"description":"PDF document properties written into the file's metadata dictionary."},"watermark":{"type":"object","properties":{"text":{"type":"string","minLength":1,"maxLength":100},"opacity":{"type":"number","minimum":0,"maximum":1},"fontSize":{"type":"number","minimum":4,"maximum":400},"angle":{"type":"number","minimum":-180,"maximum":180}},"required":["text"],"additionalProperties":false,"description":"Draws your own diagonal watermark over every page. Unrelated to the free-plan Kamy watermark, which is applied regardless — see get_account.watermarkPolicy."},"pdf_a":{"type":"string","enum":["1b","2b","3b"],"description":"Convert the output to a PDF/A archival conformance level."}},"required":["template","data"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"execution":{"taskSupport":"forbidden"},"_meta":{"securitySchemes":[{"type":"noauth"}]},"securitySchemes":[{"type":"noauth"}]},{"name":"get_job","title":"Get job status","description":"Poll an asynchronous render job started by render_async. Returns { jobId, status } where status is 'queued', 'processing', 'completed' or 'failed'; on 'completed' the response also carries `render` with the finished document's id, signed URL, size and duration, and on 'failed' it carries `error` with the reason. Poll every few seconds rather than in a tight loop — a typical render finishes in seconds, a heavy one can take a minute. If a job's signed URL has since expired, pass its render id to get_render for a fresh one. Read-only and spends no quota.","inputSchema":{"type":"object","properties":{"job_id":{"type":"string","minLength":1,"maxLength":64,"description":"The jobId render_async returned."}},"required":["job_id"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"execution":{"taskSupport":"forbidden"},"_meta":{"securitySchemes":[{"type":"noauth"}]},"securitySchemes":[{"type":"noauth"}]},{"name":"render_batch","title":"Render a batch of PDFs","description":"Render up to 100 documents in a single blocking call, each from its own template, HTML or URL, and get every result back in one response. Choose this over calling render_pdf in a loop whenever you have more than a couple of documents — it is one round trip, one quota reservation and one rate-limit charge. It does block: items render sequentially inside a 300-second budget, so expect to wait, and reach for render_async instead when you cannot. Returns { results: [...] } in request order, where each entry is either a finished render or an { error: { code, message } } — a partial batch is normal and successful items are still yours. The whole batch's quota is reserved up front, so a batch that would cross the monthly quota is rejected in full with 402 and nothing is rendered; call get_account first if you are near the limit. Items that would overrun the time budget come back as SERVICE_UNAVAILABLE having been neither rendered nor billed — retry just those in a smaller batch.","inputSchema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"template":{"type":"string","minLength":1,"description":"Template slug or UUID. Supply exactly one of template, html, or url for each item."},"html":{"type":"string","minLength":1,"maxLength":5000000,"description":"Raw HTML to render. Handlebars expressions in it are populated from `data`. Supply exactly one of template, html, or url."},"url":{"type":"string","format":"uri","description":"Public https URL to render. Private, loopback and link-local hosts are rejected. Supply exactly one of template, html, or url."},"data":{"type":"object","additionalProperties":{},"description":"Data for the template or HTML. Ignored when rendering a url."},"name":{"type":"string","minLength":1,"maxLength":120,"description":"Your own label for this one document, stored on the render and shown by list_renders."},"format":{"type":"string","enum":["a4","a3","letter","legal"],"description":"Paper size. Defaults to a4."},"orientation":{"type":"string","enum":["portrait","landscape"],"description":"Defaults to portrait."},"direction":{"type":"string","enum":["auto","ltr","rtl"],"description":"Text direction. 'auto' detects RTL scripts. Per item, so one batch can mix both."},"margin":{"type":"object","properties":{"top":{"type":"string","maxLength":20},"right":{"type":"string","maxLength":20},"bottom":{"type":"string","maxLength":20},"left":{"type":"string","maxLength":20}},"additionalProperties":false,"description":"CSS lengths, e.g. { top: '20mm' }."},"page_numbers":{"type":"boolean","description":"Print page numbers in the footer."}},"additionalProperties":false},"minItems":1,"maxItems":100,"description":"1-100 documents to render, in order. Results come back in this same order."}},"required":["items"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"execution":{"taskSupport":"forbidden"},"_meta":{"securitySchemes":[{"type":"noauth"}]},"securitySchemes":[{"type":"noauth"}]},{"name":"create_schedule","title":"Create a schedule","description":"Set up a recurring render: a cron expression, a template, and where each document goes. Use this instead of render_pdf when the user wants a document produced repeatedly on a calendar — a weekly report, a monthly invoice run — so nothing has to stay running. Delivery channel is 'email' or 'whatsapp' (recipients required) or 'download' (stored only; retrieve later with list_renders). The cron is evaluated in the given IANA timezone, and how often it may fire is plan-gated: at least 60 minutes apart on Free, 15 on Starter, 5 on Pro and above — a tighter expression is rejected with a validation error naming the limit. Every firing spends one render from the monthly quota, so a frequent schedule can exhaust a small plan on its own. Returns the created schedule including its id and next_run_at.","inputSchema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":120,"description":"Human label for this schedule, shown in the dashboard."},"template":{"type":"string","minLength":1,"maxLength":80,"description":"Template slug or UUID rendered on every firing. Same values render_pdf accepts."},"schedule":{"type":"string","minLength":1,"maxLength":100,"description":"Standard 5-field cron expression, e.g. '0 9 * * 1' for 09:00 every Monday. Rejected with a validation error if it fires more often than the plan's minimum interval."},"channel":{"type":"string","enum":["email","whatsapp","download"],"description":"Where each rendered document goes. 'download' just stores it (find it later with list_renders); 'email' and 'whatsapp' require recipients."},"recipients":{"type":"array","items":{"type":"string","minLength":1,"maxLength":200},"description":"Email addresses or phone numbers, matching the channel. Required and non-empty for email and whatsapp; ignored for download."},"data":{"type":"object","additionalProperties":{},"description":"Template data, used verbatim on every firing. Defaults to {}."},"options":{"type":"object","additionalProperties":{},"description":"Render options passed straight through to the render, in the nested /v1/render shape, e.g. { format: 'letter', margin: { top: '20mm' } }."},"timezone":{"type":"string","minLength":1,"maxLength":64,"description":"IANA timezone the cron expression is evaluated in, e.g. 'Asia/Dubai'. Defaults to UTC."},"enabled":{"type":"boolean","description":"Defaults to true. Create it disabled to set it up now and start it later."}},"required":["name","template","schedule","channel"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"execution":{"taskSupport":"forbidden"},"_meta":{"securitySchemes":[{"type":"noauth"}]},"securitySchemes":[{"type":"noauth"}]},{"name":"list_schedules","title":"List schedules","description":"List this account's recurring render schedules, newest first, with the id needed to delete one. Use it to answer what is already automated before creating a duplicate, and to diagnose a schedule that is not producing documents: each row carries enabled, schedule, timezone, next_run_at, and last_run_at / last_run_status / last_run_error from the most recent firing — last_run_error is where a delivery or quota failure shows up. Returns { schedules, total, limit, offset }. Read-only and spends no quota.","inputSchema":{"type":"object","properties":{"limit":{"type":"integer","minimum":1,"maximum":100,"description":"Rows to return, 1-100. Defaults to 50."},"offset":{"type":"integer","minimum":0,"description":"Rows to skip. Defaults to 0."}},"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"execution":{"taskSupport":"forbidden"},"_meta":{"securitySchemes":[{"type":"noauth"}]},"securitySchemes":[{"type":"noauth"}]},{"name":"delete_schedule","title":"Delete a schedule","description":"Permanently delete a recurring schedule so it stops firing. There is no undo and no trash — recreate it with create_schedule if it is deleted by mistake, so confirm with the user before calling this on a schedule you did not just create. If the intent is only to pause it, do not use this tool: a schedule can be switched off and back on again via its `enabled` flag on the REST endpoint (PATCH /v1/schedules/{id}), which this MCP surface does not expose. Documents already produced by past firings are unaffected and remain available through list_renders. Returns { deleted: true }, or 404 if the id does not belong to this account.","inputSchema":{"type":"object","properties":{"schedule_id":{"type":"string","minLength":1,"maxLength":64,"description":"Schedule id (UUID) from create_schedule or list_schedules."}},"required":["schedule_id"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"},"annotations":{"readOnlyHint":false,"destructiveHint":true,"openWorldHint":false},"execution":{"taskSupport":"forbidden"},"_meta":{"securitySchemes":[{"type":"noauth"}]},"securitySchemes":[{"type":"noauth"}]},{"name":"create_webhook","title":"Create a webhook","description":"Register an HTTPS endpoint that Kamy POSTs to when work finishes, so a long render or an e-signature does not have to be polled. Use this when the caller owns a server that can receive callbacks; use get_job or list_renders when it cannot. The response includes a one-time signing secret used to verify delivery signatures — it is shown here and never again, so surface it to the user immediately and tell them to store it. Defaults to the render.completed and render.failed events; the full set is render.completed, render.failed, signature.signed, signature.voided, signature.expired, signature.envelope_completed, signature.envelope_voided, test.ping, and any other string is accepted so new events can be subscribed to without an upgrade. Endpoint URLs on private, loopback, link-local or cloud-metadata hosts are rejected at registration, so a localhost tunnel will not work — use a public URL.","inputSchema":{"type":"object","properties":{"url":{"type":"string","format":"uri","maxLength":2000,"description":"HTTPS endpoint Kamy POSTs each event to. Private, loopback, link-local and cloud-metadata hosts are rejected at registration time, so localhost and 10.x/192.168.x addresses will not work."},"events":{"type":"array","items":{"type":"string","minLength":1,"maxLength":120},"description":"Event names to receive. Defaults to [\"render.completed\", \"render.failed\"]. Known events: render.completed, render.failed, signature.signed, signature.voided, signature.expired, signature.envelope_completed, signature.envelope_voided, test.ping."},"enabled":{"type":"boolean","description":"Defaults to true. Register it disabled to wire it up before switching it on."}},"required":["url"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"execution":{"taskSupport":"forbidden"},"_meta":{"securitySchemes":[{"type":"noauth"}]},"securitySchemes":[{"type":"noauth"}]},{"name":"list_webhooks","title":"List webhooks","description":"List the webhook endpoints registered on this account, with the id needed to test one. Each row carries url, events, enabled, and lastDeliveryAt / lastStatus from the most recent delivery — lastStatus is the HTTP code the caller's own server returned, so this is where a silently broken endpoint shows up as a 4xx or 5xx. Signing secrets are never returned here; they are shown only once, by create_webhook. Read-only and spends no quota.","inputSchema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"execution":{"taskSupport":"forbidden"},"_meta":{"securitySchemes":[{"type":"noauth"}]},"securitySchemes":[{"type":"noauth"}]},{"name":"test_webhook","title":"Test a webhook","description":"Send a real test.ping event to a registered webhook endpoint — an actual outbound HTTP POST to whatever URL the user configured, signed like a genuine delivery. Use it to prove an endpoint is reachable and that signature verification works before relying on it. Delivery is dispatched in the background, so the { message: 'Test ping dispatched' } you get back means accepted for sending, not that the endpoint answered: wait a few seconds and call list_webhooks to read lastStatus and lastDeliveryAt for the real outcome. The ping is delivered regardless of which events the endpoint subscribes to.","inputSchema":{"type":"object","properties":{"webhook_id":{"type":"string","minLength":1,"maxLength":64,"description":"Webhook endpoint id (UUID) from create_webhook or list_webhooks."}},"required":["webhook_id"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":true},"execution":{"taskSupport":"forbidden"},"_meta":{"securitySchemes":[{"type":"noauth"}]},"securitySchemes":[{"type":"noauth"}]}]},"jsonrpc":"2.0","id":2},"http_status":200,"headers":{"content-type":"text/event-stream"}}},"prompts_list":{"status":"missing","latency_ms":34.19,"details":{"url":"https://mcp.kamy.dev/mcp","payload":{"jsonrpc":"2.0","id":3,"error":{"code":-32601,"message":"Method not found"}},"http_status":200,"headers":{"content-type":"text/event-stream"},"reason":"not_supported"}},"prompt_get":{"status":"missing","latency_ms":null,"details":{"reason":"not_advertised"}},"resources_list":{"status":"missing","latency_ms":111.31,"details":{"url":"https://mcp.kamy.dev/mcp","payload":{"jsonrpc":"2.0","id":5,"error":{"code":-32601,"message":"Method not found"}},"http_status":200,"headers":{"content-type":"text/event-stream"},"reason":"not_supported"}},"resource_read":{"status":"missing","latency_ms":null,"details":{"reason":"not_advertised"}},"determinism_probe":{"status":"ok","latency_ms":168.61,"details":{"attempts":2,"successful":2,"matches":2,"stable_ratio":1.0,"baseline_signature":"44656e50fdbaf507d612d4fca0f1c8cf56e4906d65407f99ffa9ce3e1262b1f2","errors":[]}},"instruction_tool_reference_probe":{"status":"ok","latency_ms":null,"details":{"referenced_tools":[],"missing_tools":[],"observed_tool_count":59}},"session_resume_probe":{"status":"warning","latency_ms":null,"details":{"reason":"no_session_id","transport":"streamable-http","resume_expected":true,"protocol_version":"2025-03-26"}},"step_up_auth_probe":{"status":"missing","latency_ms":null,"details":{"oauth_present":false,"auth_required_checks":[],"supported_scopes":[],"scope_specificity_ratio":0.0,"broad_scopes":[],"challenge_headers":[],"step_up_signals":[],"minimal_scope_documented":false}},"transport_compliance_probe":{"status":"warning","latency_ms":37.7,"details":{"transport":"streamable-http","session_id_present":false,"protocol_header_present":false,"last_event_id_visible":false,"requested_protocol_version":"2025-03-26","bad_protocol_status_code":400,"bad_protocol_payload":{"jsonrpc":"2.0","error":{"code":-32000,"message":"Bad Request: Unsupported protocol version: 1999-99-99 (supported versions: 2025-11-25, 2025-06-18, 2025-03-26, 2024-11-05, 2024-10-07)"},"id":null},"bad_protocol_headers":{"content-type":"application/json"},"bad_protocol_error":null,"delete_status_code":null,"delete_error":null,"expired_session_status_code":null,"expired_session_error":null,"issues":["missing_session_id","missing_protocol_header"]}},"utility_coverage_probe":{"status":"ok","latency_ms":12.97,"details":{"completions":{"advertised":false,"sample_target":null,"live_probe":"not_executed"},"pagination":{"supported":false,"next_cursor_methods":[],"metadata_signal":false},"tasks":{"advertised":false,"probe_status":"auth_required","http_status":401},"initialize_capability_keys":["tools"]}},"advanced_capabilities_probe":{"status":"missing","latency_ms":null,"details":{"capabilities":{"prompts":false,"resources":false,"completions":false,"roots":false,"sampling":false,"elicitation":false,"structured_outputs":false,"resource_links":false},"enabled_count":0,"enabled":[],"initialize_capability_keys":["tools"]}},"tool_snapshot_probe":{"status":"ok","latency_ms":null,"details":{"current_tool_count":59,"previous_tool_count":59,"similarity":1.0,"added":[],"removed":[],"changed_outputs":[]}},"connector_replay_probe":{"status":"ok","latency_ms":null,"details":{"backward_compatible":true,"would_break_after_refresh":false,"added_tools":[],"removed_tools":[],"required_arg_breaks":[],"output_breaks":[],"additive_output_changes":[]}},"request_association_probe":{"status":"missing","latency_ms":null,"details":{"reason":"no_request_association_capabilities_advertised"}},"interactive_flow_probe":{"status":"ok","latency_ms":null,"details":{"risk_hits":[],"safe_hits":["browser","consent"],"oauth_supported":false,"prompt_available":false}},"action_safety_probe":{"status":"error","latency_ms":null,"details":{"summary":{"tool_count":59,"high_risk_tools":2,"destructive_tools":2,"exec_tools":3,"egress_tools":7,"secret_tools":0,"bulk_access_tools":1,"declared_non_read_only_tools":31,"annotation_conflict_tools":1,"risk_distribution":{"low":40,"medium":17,"high":2,"critical":0},"capability_distribution":{"read":28,"write":17,"exec":3,"delete":2,"undetermined":11,"filesystem":2,"network":7,"export":1},"has_mutating_capability":true,"has_non_read_capability":true},"auth_present":false,"safeguard_count":24,"confirmation_signals":["rollback_template","get_upload","validate_payload","create_envelope","preview_field_placement","get_signature_template","attest_artifact","verify_attestation","get_render_pages","delete_schedule"],"reason":null}},"official_registry_probe":{"status":"missing","latency_ms":null,"details":{"registry_source":"glama_registry","direct_match":false,"official_peer_count":0}},"provenance_divergence_probe":{"status":"not_assessed","latency_ms":null,"details":{"direct_official_match":false,"registry_title":null,"server_card_title":null,"registry_version":null,"server_card_version":null,"registry_homepage":null,"server_card_homepage":null,"registry_repository":null,"server_card_repository":null,"drift_fields":[],"metadata_document_count":1,"compared_fields":["title","version","homepage","repository"],"readable_sources":[],"comparable_field_count":0}},"schema_divergence_probe":{"status":"missing","latency_ms":null,"details":{"reason":"no_server_card_tools","compared_tool_count":0,"compared_dimensions":["server_name","server_version","declared_vs_observed_auth","tool_membership","parameter_names","required_parameters","parameter_types","output_schema_presence"],"server_name_mismatch":false,"card_server_name":null,"live_server_name":"kamy","server_version_mismatch":false,"card_server_version":null,"live_server_version":"1.5.0","auth_scheme_mismatch":false}},"connector_publishability_probe":{"status":"warning","latency_ms":null,"details":{"transport":"streamable-http","tool_count":59,"high_risk_tools":2,"blockers":["protocol_version","session_resume","step_up_auth","transport_compliance","request_association","action_safety","server_card"],"criteria":{"remote_transport":true,"initialize":true,"tools_list":true,"protocol_version":false,"session_resume":false,"step_up_auth":false,"transport_compliance":false,"connector_replay":true,"request_association":false,"action_safety":false,"server_card":false,"tool_surface":true,"auth_flow":true}}}}},"active_alerts":[],"maintainer_analytics":{},"public_server_reputation":{},"maintainer_response_quality":{},"maintainer_annotations":[],"maintainer_rebuttals":[],"security_posture_summary":{},"tool_security_inventory":[],"transport_compliance":{},"utility_coverage":{},"write_action_governance":{},"provenance_divergence":{},"alias_consolidation":{},"alert_routing":{},"authenticated_validation":{},"hosted_runtime":{},"action_controls_diff":null,"benchmark_tasks":[],"latest_capability_counts":{},"point_loss_breakdown":[],"verdict_traces":{},"current_snapshot":{"schema_version":"verify.trust_snapshot.v1","snapshot_id":"trustsnap_434700e35885cdb3","generated_at":"2026-08-24T22:24:47.030005+00:00","trust_evaluated_at":"2026-08-24T22:24:47.030005+00:00","evidence_revision":"21f93c0fc5924734592065af","source":"current_snapshot","server":"rakanalalami/kamy","last_validated_at":"2026-08-24T20:34:25.893479+00:00","validation_age_hours":1.84,"freshness":{"schema_version":"verify.freshness_profile.v1","last_validated_at":"2026-08-24T20:34:25.893479+00:00","age_hours":1.84,"bucket":"verified_last_24h","label":"Verified in last 24h","badges":["verified_last_24h"],"freshness_sla_hours":720.0,"freshness_sla_status":"met","stale_score_suppressed":false,"display_score":54.69,"raw_score":54.69,"confidence_score":100.0,"confidence_weighted_score":54.7,"tier_status":[{"tier":"community","label":"Community","freshness_sla_hours":720,"met":true,"priority_revalidation":false},{"tier":"pro","label":"Pro","freshness_sla_hours":168,"met":true,"priority_revalidation":true},{"tier":"enterprise","label":"Enterprise","freshness_sla_hours":24,"met":true,"priority_revalidation":true}]},"current_status":"healthy","current_score":54.69,"display_score":54.69,"stale_score_suppressed":false,"production_trust_decision":{"schema_version":"verify.executive_verdict.v1","decision":"Block for production","why":"write/admin actions need confirmation safeguards + score below evaluation threshold","next_action":"revalidate, add safeguards, export policy","reason_count":2},"production_readiness_class":{"code":"needs_remediation","label":"Needs remediation","reason":"Current validation evidence shows operational or discovery gaps that should be fixed first."},"evidence_confidence":{"score":100.0,"label":"high","validation_age_hours":1.84,"live_check_count":13,"basis":{"evidence_bearing_validations":20,"affirmative_live_check_count":13,"validation_age_hours":1.84,"freshness_threshold_hours":24}},"active_alerts":[],"active_alert_summary":{"critical":0,"high":0,"medium":0,"low":0,"high_or_critical":0,"total":0},"materialization":{"state":"partial","trust_core_complete":true,"fields_unavailable":["tool_security_inventory","security_posture_summary","write_action_governance","capability_taxonomy","remediations"],"materialized_at":"2026-08-24T22:24:47.030005+00:00"}},"trust_snapshot":{"schema_version":"verify.trust_snapshot.v1","snapshot_id":"trustsnap_434700e35885cdb3","generated_at":"2026-08-24T22:24:47.030005+00:00","trust_evaluated_at":"2026-08-24T22:24:47.030005+00:00","evidence_revision":"21f93c0fc5924734592065af","source":"current_snapshot","server":"rakanalalami/kamy","last_validated_at":"2026-08-24T20:34:25.893479+00:00","validation_age_hours":1.84,"freshness":{"schema_version":"verify.freshness_profile.v1","last_validated_at":"2026-08-24T20:34:25.893479+00:00","age_hours":1.84,"bucket":"verified_last_24h","label":"Verified in last 24h","badges":["verified_last_24h"],"freshness_sla_hours":720.0,"freshness_sla_status":"met","stale_score_suppressed":false,"display_score":54.69,"raw_score":54.69,"confidence_score":100.0,"confidence_weighted_score":54.7,"tier_status":[{"tier":"community","label":"Community","freshness_sla_hours":720,"met":true,"priority_revalidation":false},{"tier":"pro","label":"Pro","freshness_sla_hours":168,"met":true,"priority_revalidation":true},{"tier":"enterprise","label":"Enterprise","freshness_sla_hours":24,"met":true,"priority_revalidation":true}]},"current_status":"healthy","current_score":54.69,"display_score":54.69,"stale_score_suppressed":false,"production_trust_decision":{"schema_version":"verify.executive_verdict.v1","decision":"Block for production","why":"write/admin actions need confirmation safeguards + score below evaluation threshold","next_action":"revalidate, add safeguards, export policy","reason_count":2},"production_readiness_class":{"code":"needs_remediation","label":"Needs remediation","reason":"Current validation evidence shows operational or discovery gaps that should be fixed first."},"evidence_confidence":{"score":100.0,"label":"high","validation_age_hours":1.84,"live_check_count":13,"basis":{"evidence_bearing_validations":20,"affirmative_live_check_count":13,"validation_age_hours":1.84,"freshness_threshold_hours":24}},"active_alerts":[],"active_alert_summary":{"critical":0,"high":0,"medium":0,"low":0,"high_or_critical":0,"total":0},"materialization":{"state":"partial","trust_core_complete":true,"fields_unavailable":["tool_security_inventory","security_posture_summary","write_action_governance","capability_taxonomy","remediations"],"materialized_at":"2026-08-24T22:24:47.030005+00:00"}},"agent_commerce":{},"latest_claim":null,"maintainer_profile_slug":null,"watch_summary":{},"partial":true,"fields_unavailable":["tool_security_inventory","security_posture_summary","write_action_governance","capability_taxonomy","remediations"],"trust_evaluated_at":"2026-08-24T22:24:47.030005+00:00","evidence_revision":"21f93c0fc5924734592065af","active_alert_summary":{"critical":0,"high":0,"medium":0,"low":0,"high_or_critical":0,"total":0},"materialization":{"state":"partial","trust_core_complete":true,"fields_unavailable":["tool_security_inventory","security_posture_summary","write_action_governance","capability_taxonomy","remediations"],"materialized_at":"2026-08-24T22:24:47.030005+00:00"},"owner_opted_out":false},"latest_validation":null,"current_snapshot":{"schema_version":"verify.trust_snapshot.v1","snapshot_id":"trustsnap_434700e35885cdb3","generated_at":"2026-08-24T22:24:47.030005+00:00","trust_evaluated_at":"2026-08-24T22:24:47.030005+00:00","evidence_revision":"21f93c0fc5924734592065af","source":"current_snapshot","server":"rakanalalami/kamy","last_validated_at":"2026-08-24T20:34:25.893479+00:00","validation_age_hours":1.84,"freshness":{"schema_version":"verify.freshness_profile.v1","last_validated_at":"2026-08-24T20:34:25.893479+00:00","age_hours":1.84,"bucket":"verified_last_24h","label":"Verified in last 24h","badges":["verified_last_24h"],"freshness_sla_hours":720.0,"freshness_sla_status":"met","stale_score_suppressed":false,"display_score":54.69,"raw_score":54.69,"confidence_score":100.0,"confidence_weighted_score":54.7,"tier_status":[{"tier":"community","label":"Community","freshness_sla_hours":720,"met":true,"priority_revalidation":false},{"tier":"pro","label":"Pro","freshness_sla_hours":168,"met":true,"priority_revalidation":true},{"tier":"enterprise","label":"Enterprise","freshness_sla_hours":24,"met":true,"priority_revalidation":true}]},"current_status":"healthy","current_score":54.69,"display_score":54.69,"stale_score_suppressed":false,"production_trust_decision":{"schema_version":"verify.executive_verdict.v1","decision":"Block for production","why":"write/admin actions need confirmation safeguards + score below evaluation threshold","next_action":"revalidate, add safeguards, export policy","reason_count":2},"production_readiness_class":{"code":"needs_remediation","label":"Needs remediation","reason":"Current validation evidence shows operational or discovery gaps that should be fixed first."},"evidence_confidence":{"score":100.0,"label":"high","validation_age_hours":1.84,"live_check_count":13,"basis":{"evidence_bearing_validations":20,"affirmative_live_check_count":13,"validation_age_hours":1.84,"freshness_threshold_hours":24}},"active_alerts":[],"active_alert_summary":{"critical":0,"high":0,"medium":0,"low":0,"high_or_critical":0,"total":0},"materialization":{"state":"partial","trust_core_complete":true,"fields_unavailable":["tool_security_inventory","security_posture_summary","write_action_governance","capability_taxonomy","remediations"],"materialized_at":"2026-08-24T22:24:47.030005+00:00"}},"trust_snapshot":{"schema_version":"verify.trust_snapshot.v1","snapshot_id":"trustsnap_434700e35885cdb3","generated_at":"2026-08-24T22:24:47.030005+00:00","trust_evaluated_at":"2026-08-24T22:24:47.030005+00:00","evidence_revision":"21f93c0fc5924734592065af","source":"current_snapshot","server":"rakanalalami/kamy","last_validated_at":"2026-08-24T20:34:25.893479+00:00","validation_age_hours":1.84,"freshness":{"schema_version":"verify.freshness_profile.v1","last_validated_at":"2026-08-24T20:34:25.893479+00:00","age_hours":1.84,"bucket":"verified_last_24h","label":"Verified in last 24h","badges":["verified_last_24h"],"freshness_sla_hours":720.0,"freshness_sla_status":"met","stale_score_suppressed":false,"display_score":54.69,"raw_score":54.69,"confidence_score":100.0,"confidence_weighted_score":54.7,"tier_status":[{"tier":"community","label":"Community","freshness_sla_hours":720,"met":true,"priority_revalidation":false},{"tier":"pro","label":"Pro","freshness_sla_hours":168,"met":true,"priority_revalidation":true},{"tier":"enterprise","label":"Enterprise","freshness_sla_hours":24,"met":true,"priority_revalidation":true}]},"current_status":"healthy","current_score":54.69,"display_score":54.69,"stale_score_suppressed":false,"production_trust_decision":{"schema_version":"verify.executive_verdict.v1","decision":"Block for production","why":"write/admin actions need confirmation safeguards + score below evaluation threshold","next_action":"revalidate, add safeguards, export policy","reason_count":2},"production_readiness_class":{"code":"needs_remediation","label":"Needs remediation","reason":"Current validation evidence shows operational or discovery gaps that should be fixed first."},"evidence_confidence":{"score":100.0,"label":"high","validation_age_hours":1.84,"live_check_count":13,"basis":{"evidence_bearing_validations":20,"affirmative_live_check_count":13,"validation_age_hours":1.84,"freshness_threshold_hours":24}},"active_alerts":[],"active_alert_summary":{"critical":0,"high":0,"medium":0,"low":0,"high_or_critical":0,"total":0},"materialization":{"state":"partial","trust_core_complete":true,"fields_unavailable":["tool_security_inventory","security_posture_summary","write_action_governance","capability_taxonomy","remediations"],"materialized_at":"2026-08-24T22:24:47.030005+00:00"}},"partial":true,"fields_unavailable":["tool_security_inventory","security_posture_summary","write_action_governance","capability_taxonomy","remediations"],"snapshot_invariant":{"schema_version":"verify.snapshot_invariant.v1","server":"rakanalalami/kamy","ok":true,"surface_snapshot_ids":{"page":"trustsnap_434700e35885cdb3","badge":null,"report":"trustsnap_434700e35885cdb3","policy":null},"checked_surfaces":["page","report"],"unchecked_surfaces":["badge","policy"],"checked_count_surfaces":[],"count_mismatches":{},"checked_at":"2026-08-24T22:24:47.014953+00:00"},"history":{},"production_readiness":{"code":"needs_remediation","label":"Needs remediation","reason":"Current validation evidence shows operational or discovery gaps that should be fixed first.","badge":"score-low","critical_alerts":0,"high_or_critical_alerts":0,"degraded_by_active_alerts":false},"agent_commerce_readiness":{},"evidence_confidence":{"score":100.0,"label":"high","reason":"Based on 20 recent validations, 13 captured checks, and validation age of 1.8 hours.","live_check_count":13,"validation_age_hours":1.84,"basis":{"evidence_bearing_validations":20,"affirmative_live_check_count":13,"validation_age_hours":1.84,"freshness_threshold_hours":24}},"recommended_for":[],"active_alerts":[],"remediations":[],"cache_note":"Fast fallback response; full report evidence is deferred to protect web capacity. Fields listed in fields_unavailable (including tool_security_inventory, and write_action_governance) are not yet computed on this response -- an empty value means unchecked, not confirmed clean. Treat partial responses as indeterminate.","publisher_claim":{"verified":false,"status":"unclaimed","claim_url":"https://verify.sentinelsignal.io/claim?server=rakanalalami%2Fkamy&source=report_json","reason_code":"claim_to_publish_metadata","reason":"Claim this profile to verify publisher identity, add evidence, and manage trust metadata.","score_neutral":true},"observed_attention":{"schema":"verify.observed_attention.v1","window_days":30,"level":"none","label":"No observed attention","summary":"No recent machine-readable trust or discovery activity observed for this server.","segments":{"useful_ai_user":{"level":"none","observed":false,"description":"AI-assisted user sessions such as ChatGPT/User or Claude/User."},"machine_trust_evaluator":{"level":"none","observed":false,"description":"Synthetic sessions inspecting multiple trust surfaces such as report, policy, ledger, badge, trust-summary, or compare."},"possible_agent_or_script":{"level":"none","observed":false,"description":"Structured direct sessions with rapid profile, compare, report, policy, badge, or trust-surface fan-out."},"isolated_machine_surface":{"level":"none","observed":false,"description":"Aged-out direct synthetic singleton sessions that touched a machine-readable trust surface without becoming a broader evaluator."},"ai_crawler":{"level":"none","observed":false,"description":"Known AI crawler activity such as ClaudeBot, GPTBot, or similar crawlers."},"search_crawler":{"level":"none","observed":false,"description":"Search and SEO crawler activity."},"browser_like_automation":{"level":"none","observed":false,"description":"Browser-like synthetic sessions with rapid structured endpoint activity."},"confirmed_human":{"level":"none","observed":false,"description":"Confirmed browser-session human activity."}},"surfaces_observed":{"server_profile":false,"compare":false,"compare_json":false,"compare_api":false,"report_json":false,"policy":false,"ledger":false,"badge_metadata":false,"badge_svg":false,"trust_summary":false,"mcp_tool":false},"claim_prompt":{"recommended":false,"reason":"No claim prompt is recommended from observed attention in the current window."},"notes":["Observed attention is based on segmented first-party telemetry.","Crawler and evaluator activity is not treated as confirmed human demand.","Public levels are bucketed to avoid exposing raw traffic counts."]},"owner_activation":{"claim_recommended":false,"reason":"no_observed_attention"},"related_machine_surfaces":{"compare_index":"/compare.json","compare_api":"/v1/compare?server=rakanalalami%2Fkamy","trust_summary":"/v1/servers/rakanalalami/kamy/trust-summary","ledger":"/v1/servers/rakanalalami/kamy/ledger","policy":"/v1/servers/rakanalalami/kamy/policy","report":"/v1/servers/rakanalalami/kamy/report"},"intelligence_api":{"available":true,"signup_url":"https://verify.sentinelsignal.io/verify-intelligence-api","use_case":"Programmatic MCP server trust, comparison, policy, and evidence enrichment."},"trust_evaluated_at":"2026-08-24T22:24:47.030005+00:00","evidence_revision":"21f93c0fc5924734592065af","active_alert_summary":{"critical":0,"high":0,"medium":0,"low":0,"high_or_critical":0,"total":0},"materialization":{"state":"partial","trust_core_complete":true,"fields_unavailable":["tool_security_inventory","security_posture_summary","write_action_governance","capability_taxonomy","remediations"],"materialized_at":"2026-08-24T22:24:47.030005+00:00"}}