administrate-mcp 0.3.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c22f6f3b6febbe8e86b8cb721e30f63e4e9232edbd668a3a311e44ff180b9117
4
- data.tar.gz: cd60a0dad70e15f8235e1ed6c3c2385b5d8cc56efe2a66e1b84a18b3b5fe4087
3
+ metadata.gz: f3f38d54e63d5f8d6b88750c73f284d10d5e0500a0a7f47cae82556be264e8fe
4
+ data.tar.gz: 1b1d5fda201d08915bdddd2c6eb92fb97f9698ece7f17cbff1a1d563f280eea9
5
5
  SHA512:
6
- metadata.gz: 65ea611de7b893d7b4aaec605cc91a3bd742187ca3ff2ef340229009daa130a7bc7abb79e1f4bda2d4995670ff0dadfce95252f659366ada2713458a4d70a60c
7
- data.tar.gz: 23a0677acecdb975efeb760a6f797f9319e0ae95b607be57db1dd5a5be5c1db21141aaa3cfcfbf90625472059b65a3263ae8b10ec0ab00d002414e19d5c67215
6
+ metadata.gz: bdc5883ed9f4d6cadab89dd8be6aca8f890e25cfc39d0a40ef6b2b7e9fd6b11af6de5e817d4d8c6047a0bcd9d3ad1a6bd05b4af095bd64c256ad6d65198bbf7e
7
+ data.tar.gz: 21bd4c4e668bad2bd5d9050bc6e86abae5800a605be3604d2683687bb3b52bcf4dae42cc2423f5df2fe09713ac14784cc5188a5d76ba28694fe2f41581fa895e
data/CHANGELOG.md CHANGED
@@ -8,6 +8,27 @@ minor release may change behaviour a host depends on; the entry says so when it
8
8
 
9
9
  ## [Unreleased]
10
10
 
11
+ ## [0.3.1] - 2026-10-20
12
+
13
+ ### Security
14
+
15
+ - A `tools/call` argument named `admin` no longer replaces the authenticated admin passed to a
16
+ tool's `execute` and to `mcp_action` blocks
17
+ ([GHSA-vv7c-hhjc-fhq8](https://github.com/sorare/administrate-mcp/security/advisories/GHSA-vv7c-hhjc-fhq8)).
18
+ The call is now refused with a tool error. For built-in and host-defined tools, the refusal comes
19
+ after the scope and role checks, and the `on_tool_call` hook still reports the call under the
20
+ authenticated admin, with the refused argument in `arguments`. The tools generated from
21
+ `mcp_action` refuse it while validating arguments, before the hook runs.
22
+
23
+ ### Changed
24
+
25
+ - **Behaviour change for hosts:** a tool that declares an input property named `admin` now raises
26
+ `ArgumentError` when its class is loaded, and an `mcp_action` param named `admin` or `id` raises
27
+ `ArgumentError` when the dashboard is loaded. Rename the property or param.
28
+ - **Behaviour change for clients:** the tools generated from `mcp_action` declarations now reject
29
+ arguments they do not declare, with an `Invalid arguments` tool error, instead of passing them to
30
+ the block in `params`.
31
+
11
32
  ## [0.3.0] - 2026-10-02
12
33
 
13
34
  ### Changed
@@ -117,7 +138,8 @@ minor release may change behaviour a host depends on; the entry says so when it
117
138
  took the gem from git before 0.1.0 must reinstall its migrations rather than migrate
118
139
  incrementally.
119
140
 
120
- [Unreleased]: https://github.com/sorare/administrate-mcp/compare/v0.3.0...HEAD
141
+ [Unreleased]: https://github.com/sorare/administrate-mcp/compare/v0.3.1...HEAD
142
+ [0.3.1]: https://github.com/sorare/administrate-mcp/compare/v0.3.0...v0.3.1
121
143
  [0.3.0]: https://github.com/sorare/administrate-mcp/compare/v0.2.0...v0.3.0
122
144
  [0.2.0]: https://github.com/sorare/administrate-mcp/compare/v0.1.0...v0.2.0
123
145
  [0.1.0]: https://github.com/sorare/administrate-mcp/releases/tag/v0.1.0
@@ -27,6 +27,8 @@ module Administrate
27
27
  keyword_init: true
28
28
  )
29
29
 
30
+ RESERVED_PARAMS = %i[admin id].freeze
31
+
30
32
  class << self
31
33
  # Normalizes the arguments of a `mcp_action` declaration into a spec (everything but the
32
34
  # model, which is inferred from the owning dashboard when definitions are built).
@@ -34,6 +36,7 @@ module Administrate
34
36
  destructive: true, &invoke)
35
37
  raise ArgumentError, "#{name}: a block is required to invoke the action" unless invoke
36
38
 
39
+ reject_reserved_params!(name, params)
37
40
  normalized = normalize_params(params)
38
41
 
39
42
  {
@@ -97,6 +100,11 @@ module Administrate
97
100
 
98
101
  private
99
102
 
103
+ def reject_reserved_params!(name, params)
104
+ reserved = RESERVED_PARAMS & params.keys.map(&:to_sym)
105
+ raise ArgumentError, "#{name}: reserved param name(s): #{reserved.join(', ')}" if reserved.any?
106
+ end
107
+
100
108
  def authorized_for_discovery?(admin, definition)
101
109
  Administrate::MCP.config.authorization.authorized?(admin, definition.model_class, definition.predicate)
102
110
  rescue StandardError
@@ -130,7 +138,7 @@ module Administrate
130
138
  def schema_for(defn, resource)
131
139
  properties = { id: { type: 'string', description: "ID or slug of the #{resource}" } }
132
140
  defn.params.each { |name, spec| properties[name] = spec }
133
- { properties:, required: %w[id] + defn.required.map(&:to_s) }
141
+ { properties:, required: %w[id] + defn.required.map(&:to_s), additionalProperties: false }
134
142
  end
135
143
 
136
144
  # Replaces the role-based default with instance-level authorization on the loaded record.
@@ -8,7 +8,14 @@ module Administrate
8
8
  ResourceForbiddenError = Administrate::MCP::ResourceForbiddenError
9
9
  InvalidArgumentError = Administrate::MCP::InvalidArgumentError
10
10
 
11
+ RESERVED_ARGUMENTS = %i[admin].freeze
12
+
11
13
  class << self
14
+ def input_schema(value = NOT_SET)
15
+ reject_reserved_properties!(value) if value.is_a?(Hash)
16
+ super
17
+ end
18
+
12
19
  def requires_roles(*roles)
13
20
  @required_roles = roles.flatten
14
21
  end
@@ -47,7 +54,21 @@ module Administrate
47
54
  check_scopes!(server_context)
48
55
  check_roles!(admin, **args)
49
56
  audit!(admin, args, server_context)
50
- execute(admin:, **args)
57
+ reject_reserved_arguments!(args)
58
+ execute(**args, admin:)
59
+ end
60
+
61
+ def reject_reserved_arguments!(args)
62
+ reserved = args.keys & RESERVED_ARGUMENTS
63
+ return if reserved.empty?
64
+
65
+ raise InvalidArgumentError, "Reserved argument(s) cannot be passed: #{reserved.join(', ')}"
66
+ end
67
+
68
+ def reject_reserved_properties!(schema)
69
+ properties = (schema[:properties] || schema['properties'] || {}).keys.map(&:to_sym)
70
+ reserved = properties & RESERVED_ARGUMENTS
71
+ raise ArgumentError, "#{name}: reserved input property name(s): #{reserved.join(', ')}" if reserved.any?
51
72
  end
52
73
 
53
74
  def check_scopes!(server_context)
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Administrate
4
4
  module MCP
5
- VERSION = '0.3.0'
5
+ VERSION = '0.3.1'
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: administrate-mcp
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sorare