diff --git a/CHANGELOG.md b/CHANGELOG.md index b1ddaf7..f20d42a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,10 +6,18 @@ ### Feat + MCP: add main requests configuration reference (`assets/examples/https-wrench-mcp-main-config.yaml`) covering all schema options, proxy protocol v2, full certificate chain filtering, and wire debugging against os76.xyz endpoints. + + MCP: expose `https-wrench://main-config` static resource and `https-wrench://examples/mcp-main-config` template, and integrate them into `author_requests_config` prompt hints. + MCP: update all MCP tool descriptions and parameter schemas to consistently suggest `--format json` CLI examples with standard `path/to/...` placeholders, and default `build_cli_command` to `--format json` output. MCP: add rich `jsonschema` parameter annotations to `certinfoInput` and `jwtinfoInput` for agent schema discovery. +### Docs + + MCP: add concrete CLI example to `build_cli_command` tool description. + ## 0.15.3 (2026-09-15) ### Dependencies diff --git a/assets/examples/https-wrench-mcp-main-config.yaml b/assets/examples/https-wrench-mcp-main-config.yaml new file mode 100644 index 0000000..e58403d --- /dev/null +++ b/assets/examples/https-wrench-mcp-main-config.yaml @@ -0,0 +1,163 @@ +# yaml-language-server: $schema=https-wrench://schema +--- +## ============================================================================= +## HTTPS Wrench — Main MCP Configuration Reference +## ============================================================================= +## Complete reference showcasing all configuration options, edge-probing +## capabilities, and filtering rules. +## Target endpoints restricted to os76.xyz infrastructure: +## - httpbin.os76.xyz (port 443 standard, port 445 Proxy Protocol v2) +## - httpbin-alt.os76.xyz (strictly port 444 via transportOverrideUrl) + +## ----------------------------------------------------------------------------- +## 1. Global Settings +## ----------------------------------------------------------------------------- + +# verbose: [Required] Enables detailed operational output. +verbose: true + +# debug: [Optional] Enables global parser and diagnostic debugging. +debug: false + +# caBundle: [Optional] Custom CA bundle to verify server certificates. +# Accepts a filesystem path ('/etc/ssl/certs/custom-ca.pem') or inline multiline PEM. +# When omitted, the system trust store (or embedded Mozilla root certs) is used. +# caBundle: /path/to/custom-ca.pem + +## ----------------------------------------------------------------------------- +## 2. Shared Request Defaults & Anchors (DRY Configuration) +## ----------------------------------------------------------------------------- +# baseRequest: [Optional] Define a reusable YAML anchor (&standardDefaults) +# merged into requests entries via merge keys (<<: *standardDefaults). +# Ignored by https-wrench at runtime. +baseRequest: &standardDefaults + clientTimeout: 5 + followRedirects: false + userAgent: "https-wrench-agent/1.0" + requestHeaders: + - key: "Accept" + value: "application/json, text/plain, */*" + +## ----------------------------------------------------------------------------- +## 3. Requests Suite +## ----------------------------------------------------------------------------- +requests: + + # --------------------------------------------------------------------------- + # Case A: Edge Ingress Probe with Proxy Protocol v2 & Transport Dial Override + # --------------------------------------------------------------------------- + # Forces TCP/TLS connection to port 445 while preserving the logical hostname + # for HTTP Host header and TLS SNI negotiation. + - name: "EdgeIngressProxyProtocolV2" + <<: *standardDefaults + + # transportOverrideUrl: Direct dial address (https://host:port or https://ip:port). + # The logical hostname for SNI and Host header remains hosts[].name. + transportOverrideUrl: "https://httpbin.os76.xyz:445" + + # enableProxyProtocolV2: Sends an HAProxy PROXY protocol v2 header on connect. + # Note: Requires transportOverrideUrl to be set. + enableProxyProtocolV2: true + + # insecure: Set to true if dial address does not match server certificate SAN. + insecure: true + + requestMethod: "HEAD" + + hosts: + - name: "httpbin.os76.xyz" + uriList: + - "/" + - "/status/200" + + # --------------------------------------------------------------------------- + # Case B: In-Depth TLS Certificate Chain Inspection & Selective Field Filtering + # --------------------------------------------------------------------------- + # Probes TLS certificates on standard port 443 and prints only selected fields. + - name: "TLSCertificateChainInspection" + <<: *standardDefaults + requestMethod: "HEAD" + + # printResponseCertificates: Prints peer TLS certificate chain. + printResponseCertificates: true + + # responseCertificatesFilter: Selectively filter certificates by chain index + # (0 = Leaf / Server cert, 1 = Intermediate CA, 2 = Root CA) and field names. + # Supported fields: Subject, DNSNames, Issuer, NotBefore, NotAfter, Expiration, + # IsCA, AuthorityKeyId, SubjectKeyId, PublicKeyAlgorithm, SignatureAlgorithm, + # SerialNumber, Fingerprint SHA-256. + responseCertificatesFilter: + - 0: # Leaf Certificate + - Subject + - DNSNames + - Issuer + - NotBefore + - NotAfter + - Expiration + - Fingerprint SHA-256 + - 1: # Intermediate Certificate + - Subject + - Issuer + - IsCA + - NotAfter + + hosts: + - name: "httpbin.os76.xyz" + + # --------------------------------------------------------------------------- + # Case C: HTTP API Mutation with Custom Headers & Body Regex Assertions + # --------------------------------------------------------------------------- + # Executes an HTTP mutation (POST/PUT/PATCH) on port 443 with custom headers + # and verifies response headers and body content with regular expressions. + - name: "APIPostPayloadAndRegexValidation" + <<: *standardDefaults + requestMethod: "POST" + + requestHeaders: + - key: "Content-Type" + value: "application/json" + - key: "X-Trace-ID" + value: "agent-probe-98765" + + requestBody: '{"probe": "synthetic", "environment": "production"}' + + # Response inspection controls + printResponseHeaders: true + # responseHeadersFilter: Header names to include (must start with uppercase letter) + responseHeadersFilter: + - Content-Type + - Server + - X-Request-Id + + printResponseBody: true + + # responseBodyMatchRegexp: Regex assertion that the response body must satisfy. + responseBodyMatchRegexp: '.*"probe":\s*"synthetic".*' + + hosts: + - name: "httpbin.os76.xyz" + uriList: + - "/post" + + # --------------------------------------------------------------------------- + # Case D: Deep Wire Protocol Debugging on Alternative Port (Port 444) + # --------------------------------------------------------------------------- + # Dumps raw outgoing HTTP request bytes and incoming response bytes including + # low-level TLS handshake connection states on httpbin-alt.os76.xyz:444. + - name: "RawWireAndHandshakeDebugging" + <<: *standardDefaults + + # httpbin-alt.os76.xyz is strictly available on port 444 + transportOverrideUrl: "https://httpbin-alt.os76.xyz:444" + clientTimeout: 5 + + # requestDebug: Dumps the raw outgoing HTTP wire request. + requestDebug: true + + # responseDebug: Dumps the raw incoming HTTP wire response + TLS details. + responseDebug: true + + hosts: + - name: "httpbin-alt.os76.xyz" + uriList: + - "/get" diff --git a/internal/mcp/assets/examples/https-wrench-mcp-main-config.yaml b/internal/mcp/assets/examples/https-wrench-mcp-main-config.yaml new file mode 100644 index 0000000..e58403d --- /dev/null +++ b/internal/mcp/assets/examples/https-wrench-mcp-main-config.yaml @@ -0,0 +1,163 @@ +# yaml-language-server: $schema=https-wrench://schema +--- +## ============================================================================= +## HTTPS Wrench — Main MCP Configuration Reference +## ============================================================================= +## Complete reference showcasing all configuration options, edge-probing +## capabilities, and filtering rules. +## Target endpoints restricted to os76.xyz infrastructure: +## - httpbin.os76.xyz (port 443 standard, port 445 Proxy Protocol v2) +## - httpbin-alt.os76.xyz (strictly port 444 via transportOverrideUrl) + +## ----------------------------------------------------------------------------- +## 1. Global Settings +## ----------------------------------------------------------------------------- + +# verbose: [Required] Enables detailed operational output. +verbose: true + +# debug: [Optional] Enables global parser and diagnostic debugging. +debug: false + +# caBundle: [Optional] Custom CA bundle to verify server certificates. +# Accepts a filesystem path ('/etc/ssl/certs/custom-ca.pem') or inline multiline PEM. +# When omitted, the system trust store (or embedded Mozilla root certs) is used. +# caBundle: /path/to/custom-ca.pem + +## ----------------------------------------------------------------------------- +## 2. Shared Request Defaults & Anchors (DRY Configuration) +## ----------------------------------------------------------------------------- +# baseRequest: [Optional] Define a reusable YAML anchor (&standardDefaults) +# merged into requests entries via merge keys (<<: *standardDefaults). +# Ignored by https-wrench at runtime. +baseRequest: &standardDefaults + clientTimeout: 5 + followRedirects: false + userAgent: "https-wrench-agent/1.0" + requestHeaders: + - key: "Accept" + value: "application/json, text/plain, */*" + +## ----------------------------------------------------------------------------- +## 3. Requests Suite +## ----------------------------------------------------------------------------- +requests: + + # --------------------------------------------------------------------------- + # Case A: Edge Ingress Probe with Proxy Protocol v2 & Transport Dial Override + # --------------------------------------------------------------------------- + # Forces TCP/TLS connection to port 445 while preserving the logical hostname + # for HTTP Host header and TLS SNI negotiation. + - name: "EdgeIngressProxyProtocolV2" + <<: *standardDefaults + + # transportOverrideUrl: Direct dial address (https://host:port or https://ip:port). + # The logical hostname for SNI and Host header remains hosts[].name. + transportOverrideUrl: "https://httpbin.os76.xyz:445" + + # enableProxyProtocolV2: Sends an HAProxy PROXY protocol v2 header on connect. + # Note: Requires transportOverrideUrl to be set. + enableProxyProtocolV2: true + + # insecure: Set to true if dial address does not match server certificate SAN. + insecure: true + + requestMethod: "HEAD" + + hosts: + - name: "httpbin.os76.xyz" + uriList: + - "/" + - "/status/200" + + # --------------------------------------------------------------------------- + # Case B: In-Depth TLS Certificate Chain Inspection & Selective Field Filtering + # --------------------------------------------------------------------------- + # Probes TLS certificates on standard port 443 and prints only selected fields. + - name: "TLSCertificateChainInspection" + <<: *standardDefaults + requestMethod: "HEAD" + + # printResponseCertificates: Prints peer TLS certificate chain. + printResponseCertificates: true + + # responseCertificatesFilter: Selectively filter certificates by chain index + # (0 = Leaf / Server cert, 1 = Intermediate CA, 2 = Root CA) and field names. + # Supported fields: Subject, DNSNames, Issuer, NotBefore, NotAfter, Expiration, + # IsCA, AuthorityKeyId, SubjectKeyId, PublicKeyAlgorithm, SignatureAlgorithm, + # SerialNumber, Fingerprint SHA-256. + responseCertificatesFilter: + - 0: # Leaf Certificate + - Subject + - DNSNames + - Issuer + - NotBefore + - NotAfter + - Expiration + - Fingerprint SHA-256 + - 1: # Intermediate Certificate + - Subject + - Issuer + - IsCA + - NotAfter + + hosts: + - name: "httpbin.os76.xyz" + + # --------------------------------------------------------------------------- + # Case C: HTTP API Mutation with Custom Headers & Body Regex Assertions + # --------------------------------------------------------------------------- + # Executes an HTTP mutation (POST/PUT/PATCH) on port 443 with custom headers + # and verifies response headers and body content with regular expressions. + - name: "APIPostPayloadAndRegexValidation" + <<: *standardDefaults + requestMethod: "POST" + + requestHeaders: + - key: "Content-Type" + value: "application/json" + - key: "X-Trace-ID" + value: "agent-probe-98765" + + requestBody: '{"probe": "synthetic", "environment": "production"}' + + # Response inspection controls + printResponseHeaders: true + # responseHeadersFilter: Header names to include (must start with uppercase letter) + responseHeadersFilter: + - Content-Type + - Server + - X-Request-Id + + printResponseBody: true + + # responseBodyMatchRegexp: Regex assertion that the response body must satisfy. + responseBodyMatchRegexp: '.*"probe":\s*"synthetic".*' + + hosts: + - name: "httpbin.os76.xyz" + uriList: + - "/post" + + # --------------------------------------------------------------------------- + # Case D: Deep Wire Protocol Debugging on Alternative Port (Port 444) + # --------------------------------------------------------------------------- + # Dumps raw outgoing HTTP request bytes and incoming response bytes including + # low-level TLS handshake connection states on httpbin-alt.os76.xyz:444. + - name: "RawWireAndHandshakeDebugging" + <<: *standardDefaults + + # httpbin-alt.os76.xyz is strictly available on port 444 + transportOverrideUrl: "https://httpbin-alt.os76.xyz:444" + clientTimeout: 5 + + # requestDebug: Dumps the raw outgoing HTTP wire request. + requestDebug: true + + # responseDebug: Dumps the raw incoming HTTP wire response + TLS details. + responseDebug: true + + hosts: + - name: "httpbin-alt.os76.xyz" + uriList: + - "/get" diff --git a/internal/mcp/coverage_test.go b/internal/mcp/coverage_test.go index 68e7f89..6f9b7ef 100644 --- a/internal/mcp/coverage_test.go +++ b/internal/mcp/coverage_test.go @@ -214,18 +214,21 @@ func TestExampleResourceHints(t *testing.T) { t.Parallel() hints := exampleResourceHints(requestsConfigTemplateInput{Hostname: "app.example.com"}) + require.Contains(t, hints, "mcp-main-config") require.Contains(t, hints, "k3s") hints = exampleResourceHints(requestsConfigTemplateInput{ Hostname: "app.example.com", TransportOverrideURL: "https://edge.example.net", }) + require.Contains(t, hints, "mcp-main-config") require.Contains(t, hints, "proxy-protocol-v2") hints = exampleResourceHints(requestsConfigTemplateInput{ Hostname: "app.example.com", Insecure: true, }) + require.Contains(t, hints, "mcp-main-config") require.Contains(t, hints, "k3s") } diff --git a/internal/mcp/embed.go b/internal/mcp/embed.go index 58c0103..e5175eb 100644 --- a/internal/mcp/embed.go +++ b/internal/mcp/embed.go @@ -7,6 +7,7 @@ import "embed" //go:generate cp ../../assets/examples/https-wrench-k3s.yaml assets/examples/ //go:generate cp ../../assets/examples/https-wrench-response-certificates-filter.yaml assets/examples/ //go:generate cp ../../assets/examples/https-wrench-proxyProtocolV2.yaml assets/examples/ +//go:generate cp ../../assets/examples/https-wrench-mcp-main-config.yaml assets/examples/ //go:embed assets/schema.json //go:embed assets/sample-config.yaml @@ -15,6 +16,7 @@ var assets embed.FS const ( uriSchema = "https-wrench://schema" + uriMainConfig = "https-wrench://main-config" uriSampleConfig = "https-wrench://sample-config" uriDocsRequests = "https-wrench://docs/requests" uriDocsCertinfo = "https-wrench://docs/certinfo" @@ -24,6 +26,7 @@ const ( ) var exampleFiles = map[string]string{ + "mcp-main-config": "assets/examples/https-wrench-mcp-main-config.yaml", "k3s": "assets/examples/https-wrench-k3s.yaml", "response-certificates-filter": "assets/examples/https-wrench-response-certificates-filter.yaml", "proxy-protocol-v2": "assets/examples/https-wrench-proxyProtocolV2.yaml", @@ -65,8 +68,9 @@ Run probes with: ` + "`https-wrench requests --config path/to/file.yaml --format ## MCP resources - ` + uriSchema + ` +- ` + uriMainConfig + ` - ` + uriSampleConfig + ` -- ` + uriExampleTmpl + ` (names: k3s, response-certificates-filter, proxy-protocol-v2) +- ` + uriExampleTmpl + ` (names: mcp-main-config, k3s, response-certificates-filter, proxy-protocol-v2) ` const certinfoDocsMarkdown = `# https-wrench certinfo diff --git a/internal/mcp/prompts.go b/internal/mcp/prompts.go index 0f97588..59de14c 100644 --- a/internal/mcp/prompts.go +++ b/internal/mcp/prompts.go @@ -79,6 +79,7 @@ func authorRequestsConfigPrompt(_ context.Context, req *sdkmcp.GetPromptRequest) "", "Reference resources:", "- " + uriSchema, + "- " + uriMainConfig, "- " + uriSampleConfig, "- " + uriDocsRequests, exampleHints, @@ -108,6 +109,8 @@ func authorRequestsConfigPrompt(_ context.Context, req *sdkmcp.GetPromptRequest) func exampleResourceHints(input requestsConfigTemplateInput) string { var hints []string + hints = append(hints, "- https-wrench://examples/mcp-main-config") + if strings.TrimSpace(input.TransportOverrideURL) != "" { hints = append(hints, "- https-wrench://examples/k3s") hints = append(hints, "- https-wrench://examples/proxy-protocol-v2") @@ -117,7 +120,7 @@ func exampleResourceHints(input requestsConfigTemplateInput) string { hints = append(hints, "- https-wrench://examples/k3s") } - if len(hints) == 0 { + if len(hints) == 1 { hints = append(hints, "- https-wrench://examples/k3s") } diff --git a/internal/mcp/resources.go b/internal/mcp/resources.go index 6a99e6c..8b93798 100644 --- a/internal/mcp/resources.go +++ b/internal/mcp/resources.go @@ -16,6 +16,13 @@ func registerResources(server *sdkmcp.Server) { MIMEType: "application/json", }, readStaticResource("assets/schema.json", uriSchema)) + server.AddResource(&sdkmcp.Resource{ + URI: uriMainConfig, + Name: "Main requests configuration reference", + Description: "Comprehensive main YAML configuration demonstrating all https-wrench options", + MIMEType: "text/yaml", + }, readStaticResource("assets/examples/https-wrench-mcp-main-config.yaml", uriMainConfig)) + server.AddResource(&sdkmcp.Resource{ URI: uriSampleConfig, Name: "Sample requests config", @@ -63,7 +70,7 @@ func registerResources(server *sdkmcp.Server) { URITemplate: uriExampleTmpl, Name: "Example requests config", Description: "Example YAML configs from assets/examples " + - "(names: k3s, response-certificates-filter, proxy-protocol-v2)", + "(names: mcp-main-config, k3s, response-certificates-filter, proxy-protocol-v2)", MIMEType: "text/yaml", }, readExampleResource) } diff --git a/internal/mcp/server_test.go b/internal/mcp/server_test.go index 5887361..8b9a584 100644 --- a/internal/mcp/server_test.go +++ b/internal/mcp/server_test.go @@ -53,6 +53,7 @@ func TestMCPServer_listsFeatures(t *testing.T) { } require.Contains(t, resourceURIs, "https-wrench://schema") + require.Contains(t, resourceURIs, "https-wrench://main-config") require.Contains(t, resourceURIs, "https-wrench://sample-config") require.Contains(t, resourceURIs, "https-wrench://docs/requests") require.Contains(t, resourceURIs, "https-wrench://docs/certinfo") @@ -122,6 +123,42 @@ func TestResources_readExample(t *testing.T) { require.Contains(t, res.Contents[0].Text, "requests:") } +func TestResources_readMainConfig(t *testing.T) { + t.Parallel() + + ctx := context.Background() + session, cleanup, err := mcpserver.RunInMemory(ctx, "test") + require.NoError(t, err) + + defer cleanup() + + res, err := session.ReadResource(ctx, &sdkmcp.ReadResourceParams{ + URI: "https-wrench://main-config", + }) + require.NoError(t, err) + require.NotEmpty(t, res.Contents) + require.Contains(t, res.Contents[0].Text, "EdgeIngressProxyProtocolV2") + require.Contains(t, res.Contents[0].Text, "httpbin-alt.os76.xyz:444") + require.Contains(t, res.Contents[0].Text, "responseCertificatesFilter:") +} + +func TestResources_readExample_mainConfig(t *testing.T) { + t.Parallel() + + ctx := context.Background() + session, cleanup, err := mcpserver.RunInMemory(ctx, "test") + require.NoError(t, err) + + defer cleanup() + + res, err := session.ReadResource(ctx, &sdkmcp.ReadResourceParams{ + URI: "https-wrench://examples/mcp-main-config", + }) + require.NoError(t, err) + require.NotEmpty(t, res.Contents) + require.Contains(t, res.Contents[0].Text, "EdgeIngressProxyProtocolV2") +} + func TestValidateRequestsConfig_emptyRequests(t *testing.T) { t.Parallel()