sveda-ruby-sdk 0.2.0 → 0.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6eeb554b62506ea6bfb1add0c5e29ebba46e8ae3bf87de7015b00bfab146a419
4
- data.tar.gz: 8cb7998b373799ae77f3548d5759ceb023c3dea9f8f525395ec117423c747fe5
3
+ metadata.gz: '00651658d2c76433a154d2242b34dee5ab7bce230eb736e40f4db82bee57e361'
4
+ data.tar.gz: 55290570fc7c2d55a1c7c5fbe2ec6a89121197806052c72074d48f17737efb09
5
5
  SHA512:
6
- metadata.gz: a63034bd24c1afa1a65a6fd980f50cad5810ef7fc0a33bcda610430c6d414de58abd73dc8ac4cbeb20d72871f12950c86d8d5a19c89b141625b986b73962342e
7
- data.tar.gz: 32ea314d6672ff68914a4d0fe148c4167dbcab3cf8bd3ece4f24329260795dbcb82fc18a03a99cbe6ecdb63c0a9c62822ac14897f7f626b9da23af8eada4c6ef
6
+ metadata.gz: d7981b49a07f64c70bfeb78ff82f4b3e6deb6b37f581c55c737f4ed70756a0bab2e47a0cb5eb19eb213aed848535cf3ba8b7ebc622775ea2ca84b6dac7443ea6
7
+ data.tar.gz: 19da124e14de0579233719d35922362b38f437de533606eb1f00980c5dfbaa1a73ffb18d4013a94d4e1a342040370dbb4e38e36b68d7bc8bcb6167a1b2882cd8
data/README.md CHANGED
@@ -45,7 +45,8 @@ server = Sveda::Host::Server.new(
45
45
  server_name: "My App",
46
46
  instructions: "Tools for the current user."
47
47
  )
48
- server.resolve_tools_using { [SearchPostsTool.new] }
48
+ server.resolve_tools_using { |user| [SearchPostsTool.new] }
49
+ server.policy_using { |_user| "agent" }
49
50
 
50
51
  # Rails routes.rb
51
52
  post "/sveda/session", to: "sveda_sessions#create"
data/lib/sveda/client.rb CHANGED
@@ -23,7 +23,7 @@ module Sveda
23
23
  Histories.new(@transport)
24
24
  end
25
25
 
26
- def self.start_host_session(base_url:, host_api_key:, visitor_id:, host_mcp_url: nil, host_mcp_token: nil, **client_kwargs)
26
+ def self.start_host_session(base_url:, host_api_key:, visitor_id:, host_mcp_url: nil, host_mcp_token: nil, policy: nil, grants: nil, **client_kwargs)
27
27
  origin = base_url.to_s.strip.sub(%r{/\z}, "")
28
28
  key = host_api_key.to_s.strip
29
29
  if origin.empty? || key.empty?
@@ -34,7 +34,9 @@ module Sveda
34
34
  token = client.embed.create_token(
35
35
  visitor_id: visitor_id,
36
36
  host_mcp_url: host_mcp_url,
37
- host_mcp_token: host_mcp_token
37
+ host_mcp_token: host_mcp_token,
38
+ policy: policy,
39
+ grants: grants
38
40
  )
39
41
  if token.token.empty?
40
42
  raise Error, "Sidecar returned an empty embed token."
data/lib/sveda/embed.rb CHANGED
@@ -6,13 +6,17 @@ module Sveda
6
6
  @transport = transport
7
7
  end
8
8
 
9
- def create_token(visitor_id: nil, host_mcp_url: nil, host_mcp_token: nil)
9
+ def create_token(visitor_id: nil, host_mcp_url: nil, host_mcp_token: nil, policy: nil, grants: nil)
10
10
  payload = {}
11
11
  payload["visitor_id"] = visitor_id if visitor_id.to_s != ""
12
12
  if host_mcp_url.to_s != "" && host_mcp_token.to_s != ""
13
13
  payload["host_mcp_url"] = host_mcp_url
14
14
  payload["host_mcp_token"] = host_mcp_token
15
15
  end
16
+ if !policy.nil? && policy.to_s.strip != ""
17
+ payload["policy"] = policy.to_s.strip
18
+ end
19
+ payload["grants"] = grants if grants.is_a?(Hash)
16
20
 
17
21
  EmbedToken.from_payload(@transport.request_json("POST", "/sveda/embed/token", payload))
18
22
  end
@@ -15,9 +15,10 @@ module Sveda
15
15
  return method_not_allowed unless request.post?
16
16
 
17
17
  token = bearer_token(request)
18
- return unauthorized unless @server.authenticate_token(token)
19
- return forbidden unless @server.authorized?
20
- @server.run_after_authenticate
18
+ user = @server.authenticate_token(token)
19
+ return unauthorized if user.nil?
20
+ return forbidden unless @server.authorized?(user)
21
+ @server.run_after_authenticate(user)
21
22
 
22
23
  payload = JSON.parse(request.body.read)
23
24
  return bad_request unless payload["jsonrpc"] == "2.0"
@@ -32,9 +33,9 @@ module Sveda
32
33
  when "initialize"
33
34
  json_rpc(id, initialize_result)
34
35
  when "tools/list"
35
- json_rpc(id, { tools: list_tools })
36
+ json_rpc(id, { tools: list_tools(user) })
36
37
  when "tools/call"
37
- json_rpc(id, call_tool(params))
38
+ json_rpc(id, call_tool(params, user))
38
39
  else
39
40
  [202, {}, []]
40
41
  end
@@ -58,8 +59,8 @@ module Sveda
58
59
  result
59
60
  end
60
61
 
61
- def list_tools
62
- @server.tools.map do |tool|
62
+ def list_tools(user)
63
+ @server.tools(user).map do |tool|
63
64
  {
64
65
  name: tool.name,
65
66
  title: tool.name,
@@ -73,10 +74,10 @@ module Sveda
73
74
  end
74
75
  end
75
76
 
76
- def call_tool(params)
77
+ def call_tool(params, user)
77
78
  name = params["name"].to_s
78
79
  arguments = params["arguments"].is_a?(Hash) ? params["arguments"] : {}
79
- tool = @server.tools.find { |candidate| candidate.name == name }
80
+ tool = @server.tools(user).find { |candidate| candidate.name == name }
80
81
  return tool_error("unknown tool: #{name}") unless tool
81
82
 
82
83
  result = tool.handle(arguments)
@@ -23,17 +23,23 @@ module Sveda
23
23
  }
24
24
  @token_store = MemoryTokenStore.new(ttl_seconds: token_ttl_seconds)
25
25
  @resolve_tools = nil
26
+ @policy_using = nil
26
27
  @tools = []
27
- @mint_token = -> { @token_store.mint }
28
- @authenticate = ->(token) { @token_store.valid?(token) }
28
+ @mint_token = ->(user = nil) { @token_store.mint(user_id: user_id_for(user)) }
29
+ @authenticate = ->(token) { @token_store.lookup(token) }
29
30
  @authorize = nil
30
31
  @after_authenticate = nil
32
+ @current_user = nil
31
33
  end
32
34
 
33
35
  def resolve_tools_using(&block)
34
36
  @resolve_tools = block
35
37
  end
36
38
 
39
+ def policy_using(&block)
40
+ @policy_using = block
41
+ end
42
+
37
43
  def register_tool(tool)
38
44
  @tools << tool
39
45
  end
@@ -64,35 +70,60 @@ module Sveda
64
70
  @config[:mcp_path]
65
71
  end
66
72
 
67
- def tools
68
- @resolve_tools ? Array(@resolve_tools.call) : @tools
73
+ def tools(user = nil)
74
+ if @resolve_tools
75
+ return Array(invoke_resolve_tools(user))
76
+ end
77
+
78
+ @tools
79
+ end
80
+
81
+ def policy_for(user)
82
+ return nil unless @policy_using
83
+
84
+ value = @policy_using.call(user)
85
+ return nil if value.nil?
86
+
87
+ policy = value.to_s.strip
88
+ policy.empty? ? nil : policy
69
89
  end
70
90
 
71
91
  def authenticate_token(token)
72
- @authenticate.call(token)
92
+ result = @authenticate.call(token)
93
+ return nil if result.nil? || result == false
94
+
95
+ if result == true
96
+ { "id" => "anonymous" }
97
+ elsif result.is_a?(Hash)
98
+ result
99
+ else
100
+ { "id" => result.to_s }
101
+ end
73
102
  end
74
103
 
75
- def authorized?
104
+ def authorized?(user = nil)
76
105
  return true unless @authorize
77
106
 
78
- @authorize.call != false
107
+ @authorize.call(user) != false
79
108
  end
80
109
 
81
- def run_after_authenticate
82
- @after_authenticate&.call
110
+ def run_after_authenticate(user = nil)
111
+ @after_authenticate&.call(user)
83
112
  end
84
113
 
85
- def start_session(visitor_id:, mcp_url: nil)
114
+ def start_session(visitor_id:, mcp_url: nil, user: nil)
86
115
  raise ConfigurationError, "Задайте SVEDA_CLIENT_BASE_URL и SVEDA_CLIENT_HOST_API_KEY" unless configured?
87
116
 
117
+ session_user = user.nil? ? visitor_id : user
88
118
  mcp = (mcp_url || self.mcp_url).to_s.strip.sub(%r{/\z}, "")
89
- mcp_token = @mint_token.call
119
+ mcp_token = @mint_token.arity.zero? ? @mint_token.call : @mint_token.call(session_user)
90
120
  Sveda::Client.start_host_session(
91
121
  base_url: @config[:base_url],
92
122
  host_api_key: @config[:host_api_key],
93
123
  visitor_id: visitor_id,
94
124
  host_mcp_url: mcp.empty? ? nil : mcp,
95
125
  host_mcp_token: mcp_token,
126
+ policy: policy_for(session_user),
96
127
  **@client_kwargs
97
128
  )
98
129
  end
@@ -103,6 +134,24 @@ module Sveda
103
134
 
104
135
  private
105
136
 
137
+ def invoke_resolve_tools(user)
138
+ if @resolve_tools.arity.zero?
139
+ @resolve_tools.call
140
+ else
141
+ @resolve_tools.call(user)
142
+ end
143
+ end
144
+
145
+ def user_id_for(user)
146
+ return "anonymous" if user.nil?
147
+
148
+ if user.is_a?(Hash)
149
+ (user[:id] || user["id"] || "anonymous").to_s
150
+ else
151
+ user.to_s
152
+ end
153
+ end
154
+
106
155
  def normalize_path(path)
107
156
  value = path.to_s.strip
108
157
  value = "/mcp/sveda" if value.empty?
@@ -11,23 +11,29 @@ module Sveda
11
11
  @mutex = Mutex.new
12
12
  end
13
13
 
14
- def mint
14
+ def mint(user_id: "anonymous")
15
15
  token = SecureRandom.hex(32)
16
16
  expires_at = Time.now + @ttl_seconds
17
17
  @mutex.synchronize do
18
18
  prune_locked
19
- @tokens[token] = expires_at
19
+ @tokens[token] = { expires_at: expires_at, user_id: user_id.to_s }
20
20
  end
21
21
  token
22
22
  end
23
23
 
24
24
  def valid?(token)
25
- return false if token.to_s.empty?
25
+ !lookup(token).nil?
26
+ end
27
+
28
+ def lookup(token)
29
+ return nil if token.to_s.empty?
26
30
 
27
31
  @mutex.synchronize do
28
32
  prune_locked
29
- expires_at = @tokens[token.to_s]
30
- expires_at && Time.now < expires_at
33
+ record = @tokens[token.to_s]
34
+ return nil unless record && Time.now < record[:expires_at]
35
+
36
+ { "id" => record[:user_id] }
31
37
  end
32
38
  end
33
39
 
@@ -35,7 +41,7 @@ module Sveda
35
41
 
36
42
  def prune_locked
37
43
  now = Time.now
38
- @tokens.delete_if { |_token, expires_at| expires_at <= now }
44
+ @tokens.delete_if { |_token, record| record[:expires_at] <= now }
39
45
  end
40
46
  end
41
47
  end
data/lib/sveda/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Sveda
4
- VERSION = "0.2.0"
4
+ VERSION = "0.3.0"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sveda-ruby-sdk
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Neresson
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-09-20 00:00:00.000000000 Z
11
+ date: 2026-09-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack