feat: mcp server first take
This commit is contained in:
78
lib/bds/mcp/proposal_store.ex
Normal file
78
lib/bds/mcp/proposal_store.ex
Normal file
@@ -0,0 +1,78 @@
|
||||
defmodule BDS.MCP.ProposalStore do
|
||||
@moduledoc false
|
||||
|
||||
use Agent
|
||||
|
||||
alias BDS.Persistence
|
||||
|
||||
@default_ttl_ms 30 * 60 * 1000
|
||||
|
||||
def ensure_started do
|
||||
case Process.whereis(__MODULE__) do
|
||||
nil ->
|
||||
case Agent.start_link(fn -> %{} end, name: __MODULE__) do
|
||||
{:ok, _pid} -> :ok
|
||||
{:error, {:already_started, _pid}} -> :ok
|
||||
{:error, reason} -> {:error, reason}
|
||||
end
|
||||
|
||||
_pid ->
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
||||
def create(kind, data, opts \\ []) when is_binary(kind) and is_map(data) do
|
||||
:ok = ensure_started()
|
||||
cleanup_expired(opts)
|
||||
|
||||
proposal = %{
|
||||
id: Ecto.UUID.generate(),
|
||||
kind: kind,
|
||||
data: data,
|
||||
created_at: Persistence.now_ms(),
|
||||
expires_at: Persistence.now_ms() + Keyword.get(opts, :ttl_ms, @default_ttl_ms)
|
||||
}
|
||||
|
||||
Agent.update(__MODULE__, &Map.put(&1, proposal.id, proposal))
|
||||
proposal
|
||||
end
|
||||
|
||||
def get(id) when is_binary(id) do
|
||||
:ok = ensure_started()
|
||||
cleanup_expired([])
|
||||
Agent.get(__MODULE__, &Map.get(&1, id))
|
||||
end
|
||||
|
||||
def remove(id) when is_binary(id) do
|
||||
:ok = ensure_started()
|
||||
Agent.update(__MODULE__, &Map.delete(&1, id))
|
||||
:ok
|
||||
end
|
||||
|
||||
def list do
|
||||
:ok = ensure_started()
|
||||
cleanup_expired([])
|
||||
|
||||
Agent.get(__MODULE__, fn proposals ->
|
||||
proposals
|
||||
|> Map.values()
|
||||
|> Enum.sort_by(& &1.created_at)
|
||||
end)
|
||||
end
|
||||
|
||||
def cleanup_expired(opts) do
|
||||
:ok = ensure_started()
|
||||
now = Persistence.now_ms()
|
||||
on_expire = Keyword.get(opts, :on_expire)
|
||||
|
||||
Agent.get_and_update(__MODULE__, fn proposals ->
|
||||
{expired, active} = Enum.split_with(proposals, fn {_id, proposal} -> proposal.expires_at <= now end)
|
||||
|
||||
Enum.each(expired, fn {_id, proposal} ->
|
||||
if is_function(on_expire, 1), do: on_expire.(proposal)
|
||||
end)
|
||||
|
||||
{Enum.map(expired, &elem(&1, 1)), Map.new(active)}
|
||||
end)
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user