agentsid 0.1.0

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 796f78999826a0414ab444d8064b49b86225a2b756709b3f2fe87b3ba237232b
4
+ data.tar.gz: 358114838c3cbb6e50eb24bb51afcb6af141ee1c9bb646821dde80f3686d3cc8
5
+ SHA512:
6
+ metadata.gz: c36cc8d5ffff7db25ce08b52d9f0097cfb4332ee1c87f05591c60887e7e93fa86202e0ddc69f7d79bb873bef0562433a2022e3e5a64cd3652362ba99d4477059
7
+ data.tar.gz: 6cf73da1f2342d7a58ec313ed07fd7753b292e1e3c17306d182c7f717a4b02b48d4610078e230b15deb3f0885efb97ee640d556d8020b211f245cad88c5fd3b4
data/agentsid.gemspec ADDED
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = "agentsid"
5
+ spec.version = "0.1.0"
6
+ spec.authors = ["AgentsID"]
7
+ spec.email = ["support@agentsid.dev"]
8
+
9
+ spec.summary = "Ruby SDK for AgentsID — agent identity, permissions, and audit for AI tools"
10
+ spec.description = "Register AI agents, issue scoped tokens, enforce per-tool permissions, " \
11
+ "and query audit logs via the AgentsID API. Includes MCP middleware for " \
12
+ "validating tool calls in MCP servers."
13
+ spec.homepage = "https://agentsid.dev"
14
+ spec.license = "MIT"
15
+
16
+ spec.required_ruby_version = ">= 3.0.0"
17
+
18
+ spec.files = Dir["lib/**/*.rb"] + ["agentsid.gemspec"]
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.metadata = {
22
+ "homepage_uri" => spec.homepage,
23
+ "source_code_uri" => "https://github.com/agentsid/sdk-ruby",
24
+ "bug_tracker_uri" => "https://github.com/agentsid/sdk-ruby/issues"
25
+ }
26
+ end
@@ -0,0 +1,197 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "net/http"
4
+ require "json"
5
+ require "uri"
6
+
7
+ require_relative "errors"
8
+
9
+ module AgentsID
10
+ DEFAULT_BASE_URL = "https://agentsid.dev"
11
+ DEFAULT_TIMEOUT = 10
12
+
13
+ # AgentsID client -- register agents, validate tokens, manage permissions.
14
+ class Client
15
+ # @param project_key [String] Your AgentsID project key (aid_proj_...)
16
+ # @param base_url [String] AgentsID server URL
17
+ # @param timeout [Integer] HTTP timeout in seconds
18
+ def initialize(project_key:, base_url: DEFAULT_BASE_URL, timeout: DEFAULT_TIMEOUT)
19
+ raise AgentsIDError.new("project_key is required", code: "CONFIG_ERROR") if project_key.nil? || project_key.empty?
20
+
21
+ @project_key = project_key
22
+ @base_url = base_url.chomp("/")
23
+ @timeout = timeout
24
+ end
25
+
26
+ # -----------------------------------------------------------
27
+ # AGENTS
28
+ # -----------------------------------------------------------
29
+
30
+ # Register a new agent identity and issue a token.
31
+ #
32
+ # @param name [String]
33
+ # @param on_behalf_of [String]
34
+ # @param permissions [Array<String>, nil]
35
+ # @param ttl_hours [Integer, nil]
36
+ # @param metadata [Hash, nil]
37
+ # @return [Hash]
38
+ def register_agent(name:, on_behalf_of:, permissions: nil, ttl_hours: nil, metadata: nil)
39
+ request(:post, "/agents/", {
40
+ name: name,
41
+ on_behalf_of: on_behalf_of,
42
+ permissions: permissions,
43
+ ttl_hours: ttl_hours,
44
+ metadata: metadata
45
+ })
46
+ end
47
+
48
+ # @param agent_id [String]
49
+ # @return [Hash]
50
+ def get_agent(agent_id)
51
+ request(:get, "/agents/#{agent_id}")
52
+ end
53
+
54
+ # @param status [String, nil] Filter by status
55
+ # @param limit [Integer] Max results (default 50)
56
+ # @return [Array<Hash>]
57
+ def list_agents(status: nil, limit: 50)
58
+ params = {}
59
+ params[:status] = status if status
60
+ params[:limit] = limit if limit != 50
61
+ qs = URI.encode_www_form(params)
62
+ path = qs.empty? ? "/agents/" : "/agents/?#{qs}"
63
+ request(:get, path)
64
+ end
65
+
66
+ # @param agent_id [String]
67
+ # @return [Hash]
68
+ def revoke_agent(agent_id)
69
+ request(:delete, "/agents/#{agent_id}")
70
+ end
71
+
72
+ # -----------------------------------------------------------
73
+ # PERMISSIONS
74
+ # -----------------------------------------------------------
75
+
76
+ # Set permission rules for an agent.
77
+ #
78
+ # @param agent_id [String]
79
+ # @param rules [Array<Hash>] Each rule: { tool_pattern: "...", action: "allow"|"deny" }
80
+ # @return [Hash]
81
+ def set_permissions(agent_id, rules)
82
+ body = rules.map do |r|
83
+ {
84
+ tool_pattern: r[:tool_pattern] || r["tool_pattern"] || r[:toolPattern] || r["toolPattern"] || "",
85
+ action: r[:action] || r["action"] || "allow",
86
+ conditions: r[:conditions] || r["conditions"],
87
+ priority: r[:priority] || r["priority"] || 0
88
+ }
89
+ end
90
+ request(:put, "/agents/#{agent_id}/permissions", body)
91
+ end
92
+
93
+ # @param agent_id [String]
94
+ # @return [Array<Hash>]
95
+ def get_permissions(agent_id)
96
+ data = request(:get, "/agents/#{agent_id}/permissions")
97
+ data.fetch("rules", [])
98
+ end
99
+
100
+ # @param agent_id [String]
101
+ # @param tool [String]
102
+ # @param params [Hash, nil]
103
+ # @return [Hash]
104
+ def check_permission(agent_id, tool, params: nil)
105
+ request(:post, "/check", {
106
+ agent_id: agent_id,
107
+ tool: tool,
108
+ params: params
109
+ })
110
+ end
111
+
112
+ # -----------------------------------------------------------
113
+ # TOKEN VALIDATION
114
+ # -----------------------------------------------------------
115
+
116
+ # @param token [String]
117
+ # @param tool [String, nil]
118
+ # @param params [Hash, nil]
119
+ # @return [Hash]
120
+ def validate_token(token, tool: nil, params: nil)
121
+ request(:post, "/validate", {
122
+ token: token,
123
+ tool: tool,
124
+ params: params
125
+ })
126
+ end
127
+
128
+ # -----------------------------------------------------------
129
+ # AUDIT
130
+ # -----------------------------------------------------------
131
+
132
+ # @param agent_id [String, nil]
133
+ # @param tool [String, nil]
134
+ # @param action [String, nil]
135
+ # @param since [String, nil] ISO 8601 timestamp
136
+ # @param limit [Integer]
137
+ # @return [Hash]
138
+ def get_audit_log(agent_id: nil, tool: nil, action: nil, since: nil, limit: 100)
139
+ params = {}
140
+ params[:agent_id] = agent_id if agent_id
141
+ params[:tool] = tool if tool
142
+ params[:action] = action if action
143
+ params[:since] = since if since
144
+ params[:limit] = limit
145
+ qs = URI.encode_www_form(params)
146
+ request(:get, "/audit/?#{qs}")
147
+ end
148
+
149
+ private
150
+
151
+ def request(method, path, body = nil)
152
+ uri = URI("#{@base_url}/api/v1#{path}")
153
+
154
+ http = Net::HTTP.new(uri.host, uri.port)
155
+ http.use_ssl = uri.scheme == "https"
156
+ http.open_timeout = @timeout
157
+ http.read_timeout = @timeout
158
+
159
+ req = build_request(method, uri, body)
160
+
161
+ response = http.request(req)
162
+
163
+ handle_response(response)
164
+ end
165
+
166
+ def build_request(method, uri, body)
167
+ klass = {
168
+ get: Net::HTTP::Get,
169
+ post: Net::HTTP::Post,
170
+ put: Net::HTTP::Put,
171
+ delete: Net::HTTP::Delete
172
+ }.fetch(method)
173
+
174
+ req = klass.new(uri)
175
+ req["Authorization"] = "Bearer #{@project_key}"
176
+ req["Content-Type"] = "application/json"
177
+ req.body = JSON.generate(body) if body
178
+ req
179
+ end
180
+
181
+ def handle_response(response)
182
+ status = response.code.to_i
183
+
184
+ raise AuthenticationError.new if status == 401
185
+ return {} if status == 204
186
+
187
+ data = JSON.parse(response.body)
188
+
189
+ unless (200..299).cover?(status)
190
+ message = data["detail"] || "Request failed: #{status}"
191
+ raise AgentsIDError.new(message, code: "API_ERROR", status_code: status)
192
+ end
193
+
194
+ data
195
+ end
196
+ end
197
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AgentsID
4
+ class AgentsIDError < StandardError
5
+ attr_reader :code, :status_code
6
+
7
+ def initialize(message, code: "UNKNOWN", status_code: nil)
8
+ super(message)
9
+ @code = code
10
+ @status_code = status_code
11
+ end
12
+ end
13
+
14
+ class AuthenticationError < AgentsIDError
15
+ def initialize(message = "Invalid or missing API key")
16
+ super(message, code: "AUTH_ERROR", status_code: 401)
17
+ end
18
+ end
19
+
20
+ class PermissionDeniedError < AgentsIDError
21
+ attr_reader :tool, :reason
22
+
23
+ def initialize(tool, reason)
24
+ @tool = tool
25
+ @reason = reason
26
+ super(
27
+ "Permission denied for tool \"#{tool}\": #{reason}",
28
+ code: "PERMISSION_DENIED",
29
+ status_code: 403
30
+ )
31
+ end
32
+ end
33
+
34
+ class TokenExpiredError < AgentsIDError
35
+ def initialize
36
+ super("Agent token has expired", code: "TOKEN_EXPIRED", status_code: 401)
37
+ end
38
+ end
39
+
40
+ class TokenRevokedError < AgentsIDError
41
+ def initialize
42
+ super("Agent token has been revoked", code: "TOKEN_REVOKED", status_code: 401)
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,130 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "net/http"
4
+ require "json"
5
+ require "uri"
6
+
7
+ require_relative "errors"
8
+
9
+ module AgentsID
10
+ DEFAULT_BASE_URL_MW = "https://agentsid.dev"
11
+
12
+ # Validate a single tool call against AgentsID.
13
+ #
14
+ # @param project_key [String]
15
+ # @param token [String]
16
+ # @param tool [String]
17
+ # @param params [Hash, nil]
18
+ # @param base_url [String]
19
+ # @return [Hash]
20
+ def self.validate_tool_call(project_key:, token:, tool:, params: nil, base_url: DEFAULT_BASE_URL_MW)
21
+ uri = URI("#{base_url.chomp('/')}/api/v1/validate")
22
+
23
+ http = Net::HTTP.new(uri.host, uri.port)
24
+ http.use_ssl = uri.scheme == "https"
25
+ http.open_timeout = 10
26
+ http.read_timeout = 10
27
+
28
+ req = Net::HTTP::Post.new(uri)
29
+ req["Content-Type"] = "application/json"
30
+ req.body = JSON.generate({ token: token, tool: tool, params: params })
31
+
32
+ response = http.request(req)
33
+ status = response.code.to_i
34
+
35
+ unless (200..299).cover?(status)
36
+ return { "valid" => false, "reason" => "Validation failed: #{status}" }
37
+ end
38
+
39
+ JSON.parse(response.body)
40
+ end
41
+
42
+ # MCP middleware -- validates tool calls against AgentsID.
43
+ #
44
+ # Usage:
45
+ # middleware = AgentsID::MCPMiddleware.new(project_key: "aid_proj_...")
46
+ #
47
+ # # In your MCP tool handler:
48
+ # result = middleware.validate(bearer_token, "my_tool", params)
49
+ class MCPMiddleware
50
+ # @param project_key [String] Your AgentsID project key
51
+ # @param base_url [String] AgentsID server URL
52
+ # @param skip_tools [Array<String>, nil] Tool names to skip validation for
53
+ # @param on_denied [Proc, nil] Callback invoked on denial instead of raising
54
+ def initialize(project_key:, base_url: DEFAULT_BASE_URL_MW, skip_tools: nil, on_denied: nil)
55
+ @project_key = project_key
56
+ @base_url = base_url.chomp("/")
57
+ @skip_tools = Set.new(skip_tools || [])
58
+ @on_denied = on_denied
59
+ end
60
+
61
+ # Validate a tool call. Raises on denial unless on_denied is set.
62
+ #
63
+ # @param token [String]
64
+ # @param tool [String]
65
+ # @param params [Hash, nil]
66
+ # @return [Hash]
67
+ def validate(token, tool, params = nil)
68
+ if @skip_tools.include?(tool)
69
+ return { "valid" => true, "reason" => "Tool in skip list" }
70
+ end
71
+
72
+ result = AgentsID.validate_tool_call(
73
+ project_key: @project_key,
74
+ token: token,
75
+ tool: tool,
76
+ params: params,
77
+ base_url: @base_url
78
+ )
79
+
80
+ unless result["valid"]
81
+ reason = result["reason"] || "Unknown"
82
+ raise TokenExpiredError.new if reason.include?("expired")
83
+ raise TokenRevokedError.new if reason.include?("revoked")
84
+ end
85
+
86
+ permission = result["permission"] || {}
87
+ if permission.any? && !permission["allowed"]
88
+ denial_reason = permission["reason"] || "Denied"
89
+ if @on_denied
90
+ @on_denied.call(tool, denial_reason)
91
+ else
92
+ raise PermissionDeniedError.new(tool, denial_reason)
93
+ end
94
+ end
95
+
96
+ result
97
+ end
98
+
99
+ # Quick check -- returns true/false without raising.
100
+ #
101
+ # @param token [String]
102
+ # @param tool [String]
103
+ # @return [Boolean]
104
+ def allowed?(token, tool)
105
+ result = AgentsID.validate_tool_call(
106
+ project_key: @project_key,
107
+ token: token,
108
+ tool: tool,
109
+ base_url: @base_url
110
+ )
111
+ result["valid"] == true && result.dig("permission", "allowed") == true
112
+ rescue StandardError
113
+ false
114
+ end
115
+ end
116
+
117
+ # Factory method matching the Python SDK's create_mcp_middleware.
118
+ #
119
+ # @param project_key [String]
120
+ # @param base_url [String]
121
+ # @param skip_tools [Array<String>, nil]
122
+ # @return [MCPMiddleware]
123
+ def self.create_mcp_middleware(project_key:, base_url: DEFAULT_BASE_URL_MW, skip_tools: nil)
124
+ MCPMiddleware.new(
125
+ project_key: project_key,
126
+ base_url: base_url,
127
+ skip_tools: skip_tools
128
+ )
129
+ end
130
+ end
data/lib/agentsid.rb ADDED
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "set"
4
+
5
+ require_relative "agentsid/errors"
6
+ require_relative "agentsid/client"
7
+ require_relative "agentsid/middleware"
8
+
9
+ module AgentsID
10
+ VERSION = "0.1.0"
11
+ end
metadata ADDED
@@ -0,0 +1,53 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: agentsid
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - AgentsID
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2026-03-26 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Register AI agents, issue scoped tokens, enforce per-tool permissions,
14
+ and query audit logs via the AgentsID API. Includes MCP middleware for validating
15
+ tool calls in MCP servers.
16
+ email:
17
+ - support@agentsid.dev
18
+ executables: []
19
+ extensions: []
20
+ extra_rdoc_files: []
21
+ files:
22
+ - agentsid.gemspec
23
+ - lib/agentsid.rb
24
+ - lib/agentsid/client.rb
25
+ - lib/agentsid/errors.rb
26
+ - lib/agentsid/middleware.rb
27
+ homepage: https://agentsid.dev
28
+ licenses:
29
+ - MIT
30
+ metadata:
31
+ homepage_uri: https://agentsid.dev
32
+ source_code_uri: https://github.com/agentsid/sdk-ruby
33
+ bug_tracker_uri: https://github.com/agentsid/sdk-ruby/issues
34
+ post_install_message:
35
+ rdoc_options: []
36
+ require_paths:
37
+ - lib
38
+ required_ruby_version: !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ version: 3.0.0
43
+ required_rubygems_version: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ requirements: []
49
+ rubygems_version: 3.0.3.1
50
+ signing_key:
51
+ specification_version: 4
52
+ summary: Ruby SDK for AgentsID — agent identity, permissions, and audit for AI tools
53
+ test_files: []