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 +4 -4
- data/CHANGELOG.md +23 -1
- data/app/lib/administrate/mcp/actions.rb +9 -1
- data/app/lib/administrate/mcp/base_tool.rb +22 -1
- data/lib/administrate/mcp/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f3f38d54e63d5f8d6b88750c73f284d10d5e0500a0a7f47cae82556be264e8fe
|
|
4
|
+
data.tar.gz: 1b1d5fda201d08915bdddd2c6eb92fb97f9698ece7f17cbff1a1d563f280eea9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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.
|
|
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
|
-
|
|
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)
|