Module: PostHog::MCP::SchemaMutation Private
- Defined in:
- lib/posthog/mcp/schema_mutation.rb
Overview
This module is part of a private API. You should avoid using this module if possible, as it may be removed or be changed in the future.
Injects analytics parameters (context, conversation_id, llm_model)
into advertised tool input schemas and declares _mcp_instructions on
output schemas. Always returns new hashes: the mcp gem's Tool.to_h
shares its nested schema hashes with the tool class, so in-place writes
would leak into the tool permanently.
Works on symbol- or string-keyed schemas and writes back in the input's key style.
Constant Summary collapse
- COMPLEX_KEYS =
This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.
%w[oneOf allOf anyOf].freeze
Class Method Summary collapse
- .add_context_parameter(schema, tool_name:, description: nil, required: true, options: nil) ⇒ Object private
- .add_conversation_id_parameter(schema, tool_name:, options: nil) ⇒ Object private
- .add_model_parameter(schema, tool_name:, description: nil, required: true, options: nil) ⇒ Object private
-
.add_output_instructions(schema, tool_name:, options: nil) ⇒ Array(Hash, Boolean)
private
Declare an optional
_mcp_instructionson the output schema. -
.add_parameter(schema, name, description, tool_name:, required:, options: nil, label: name) ⇒ Hash
private
Add a string property to an object schema.
- .complex?(schema) ⇒ Boolean private
-
.declarable_output?(schema) ⇒ Boolean
private
Whether
_mcp_instructionscan safely be declared on this output schema. - .declares_param?(schema, name) ⇒ Boolean private
- .deep_dup(value) ⇒ Object private
- .fetch(hash, name) ⇒ Object private
-
.injectable?(schema) ⇒ Boolean
private
Whether an analytics parameter may be injected into (and therefore owned in) this input schema.
- .key_for(hash, name) ⇒ Object private
- .our_declaration?(declaration) ⇒ Boolean private
-
.string_keys?(hash, parent) ⇒ Boolean
private
Key style of
hash, falling back toparent's whenhashis empty. - .truthy?(value) ⇒ Boolean private
Class Method Details
.add_context_parameter(schema, tool_name:, description: nil, required: true, options: nil) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
113 114 115 116 |
# File 'lib/posthog/mcp/schema_mutation.rb', line 113 def add_context_parameter(schema, tool_name:, description: nil, required: true, options: nil) add_parameter(schema, 'context', description || DEFAULT_CONTEXT_PARAMETER_DESCRIPTION, tool_name: tool_name, required: required, options: , label: 'context') end |
.add_conversation_id_parameter(schema, tool_name:, options: nil) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
118 119 120 121 |
# File 'lib/posthog/mcp/schema_mutation.rb', line 118 def add_conversation_id_parameter(schema, tool_name:, options: nil) add_parameter(schema, ConversationId::PARAM_NAME, DEFAULT_CONVERSATION_ID_DESCRIPTION, tool_name: tool_name, required: false, options: , label: 'conversation_id') end |
.add_model_parameter(schema, tool_name:, description: nil, required: true, options: nil) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
123 124 125 126 |
# File 'lib/posthog/mcp/schema_mutation.rb', line 123 def add_model_parameter(schema, tool_name:, description: nil, required: true, options: nil) add_parameter(schema, ModelCapture::PARAM_NAME, description || DEFAULT_MODEL_PARAMETER_DESCRIPTION, tool_name: tool_name, required: required, options: , label: 'llm_model') end |
.add_output_instructions(schema, tool_name:, options: nil) ⇒ Array(Hash, Boolean)
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Declare an optional _mcp_instructions on the output schema.
146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 |
# File 'lib/posthog/mcp/schema_mutation.rb', line 146 def add_output_instructions(schema, tool_name:, options: nil) return [schema, false] if schema.nil? || (schema.respond_to?(:empty?) && schema.empty?) key = ConversationId::MCP_INSTRUCTIONS_KEY unless declarable_output?(schema) properties = fetch(schema, :properties) if properties.is_a?(Hash) && declares_param?(schema, key) return [schema, true] if our_declaration?(fetch(properties, key)) Log.debug(, "WARN: Tool \"#{tool_name}\" already declares '#{key}' in its output schema. Leaving it alone.") else Log.debug(, "WARN: Tool \"#{tool_name}\" has a complex output schema (oneOf/allOf/anyOf/$ref). " \ "Skipping '#{key}' declaration; its session handle stays content-only.") end return [schema, false] end schema = deep_dup(schema) properties_key = key_for(schema, :properties) schema[properties_key] = {} unless schema[properties_key].is_a?(Hash) property_key = string_keys?(schema[properties_key], schema) ? key : key.to_sym schema[properties_key][property_key] = { type: 'object', description: ConversationId::INSTRUCTIONS_FIELD_DESCRIPTION, properties: { conversation_id: { type: 'string', description: ConversationId::CONVERSATION_ID_FIELD_DESCRIPTION } } } [schema, true] end |
.add_parameter(schema, name, description, tool_name:, required:, options: nil, label: name) ⇒ Hash
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Add a string property to an object schema. Returns the input unchanged (logging a warning) when the property exists or the schema is composed or referenced.
75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 |
# File 'lib/posthog/mcp/schema_mutation.rb', line 75 def add_parameter(schema, name, description, tool_name:, required:, options: nil, label: name) if declares_param?(schema, name) Log.debug(, "WARN: Tool \"#{tool_name}\" already has '#{name}' parameter. Skipping #{label} injection.") return schema end unless injectable?(schema) Log.debug(, "WARN: Tool \"#{tool_name}\" has a composed schema (oneOf/allOf/anyOf/$ref). " \ "Skipping #{label} injection.") return schema end if schema.nil? || (schema.respond_to?(:empty?) && schema.empty?) schema = { type: 'object', properties: {}, required: [] } end schema = deep_dup(schema) properties_key = key_for(schema, :properties) schema[properties_key] = {} unless schema[properties_key].is_a?(Hash) # `additionalProperties: false` stays: the injected name is listed under # `properties`, so it is still accepted, and relaxing the constraint would # advertise a looser schema than the dispatcher actually validates against. property_key = string_keys?(schema[properties_key], schema) ? name.to_s : name.to_sym schema[properties_key][property_key] = { type: 'string', description: description } if required required_key = key_for(schema, :required) if schema[required_key].is_a?(Array) schema[required_key] << name.to_s unless schema[required_key].map(&:to_s).include?(name.to_s) else schema[required_key] = [name.to_s] end end schema end |
.complex?(schema) ⇒ Boolean
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
37 38 39 |
# File 'lib/posthog/mcp/schema_mutation.rb', line 37 def complex?(schema) COMPLEX_KEYS.any? { |key| truthy?(fetch(schema, key)) } end |
.declarable_output?(schema) ⇒ Boolean
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Whether _mcp_instructions can safely be declared on this output schema.
129 130 131 132 133 134 135 136 137 |
# File 'lib/posthog/mcp/schema_mutation.rb', line 129 def declarable_output?(schema) return false unless schema.is_a?(Hash) return false if truthy?(fetch(schema, :$ref)) || complex?(schema) properties = fetch(schema, :properties) return false if !properties.nil? && !properties.is_a?(Hash) !truthy?(properties) || !declares_param?(schema, ConversationId::MCP_INSTRUCTIONS_KEY) end |
.declares_param?(schema, name) ⇒ Boolean
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
32 33 34 35 |
# File 'lib/posthog/mcp/schema_mutation.rb', line 32 def declares_param?(schema, name) properties = fetch(schema, :properties) properties.is_a?(Hash) && (properties.key?(name.to_sym) || properties.key?(name.to_s)) end |
.deep_dup(value) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
62 63 64 65 66 67 68 |
# File 'lib/posthog/mcp/schema_mutation.rb', line 62 def deep_dup(value) case value when Hash then value.to_h { |k, v| [k, deep_dup(v)] } when Array then value.map { |v| deep_dup(v) } else value end end |
.fetch(hash, name) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
19 20 21 22 23 |
# File 'lib/posthog/mcp/schema_mutation.rb', line 19 def fetch(hash, name) return nil unless hash.is_a?(Hash) hash.key?(name.to_sym) ? hash[name.to_sym] : hash[name.to_s] end |
.injectable?(schema) ⇒ Boolean
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Whether an analytics parameter may be injected into (and therefore owned in) this input schema. A composed (oneOf/allOf/anyOf) or referenced ($ref) schema can declare the property out of band, and a sibling property next to a reference to a closed object makes the schema unsatisfiable, so those are left alone entirely.
46 47 48 49 50 |
# File 'lib/posthog/mcp/schema_mutation.rb', line 46 def injectable?(schema) return true unless schema.is_a?(Hash) !complex?(schema) && !truthy?(fetch(schema, :$ref)) end |
.key_for(hash, name) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
25 26 27 28 29 30 |
# File 'lib/posthog/mcp/schema_mutation.rb', line 25 def key_for(hash, name) return name.to_sym if hash.key?(name.to_sym) return name.to_s if hash.key?(name.to_s) hash.keys.first.is_a?(String) ? name.to_s : name.to_sym end |
.our_declaration?(declaration) ⇒ Boolean
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
139 140 141 |
# File 'lib/posthog/mcp/schema_mutation.rb', line 139 def our_declaration?(declaration) declaration.is_a?(Hash) && fetch(declaration, :description) == ConversationId::INSTRUCTIONS_FIELD_DESCRIPTION end |
.string_keys?(hash, parent) ⇒ Boolean
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Key style of hash, falling back to parent's when hash is empty.
53 54 55 56 |
# File 'lib/posthog/mcp/schema_mutation.rb', line 53 def string_keys?(hash, parent) source = hash.empty? ? parent : hash source.keys.first.is_a?(String) end |
.truthy?(value) ⇒ Boolean
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
58 59 60 |
# File 'lib/posthog/mcp/schema_mutation.rb', line 58 def truthy?(value) !(value.nil? || value == false || (value.respond_to?(:empty?) && value.empty?)) end |