feat: mcp server first take
This commit is contained in:
110
lib/bds/scripting/capabilities.ex
Normal file
110
lib/bds/scripting/capabilities.ex
Normal file
@@ -0,0 +1,110 @@
|
||||
defmodule BDS.Scripting.Capabilities do
|
||||
@moduledoc false
|
||||
|
||||
import Ecto.Query
|
||||
|
||||
alias BDS.Metadata
|
||||
alias BDS.PostLinks
|
||||
alias BDS.Posts.Post
|
||||
alias BDS.Repo
|
||||
alias BDS.Tags
|
||||
|
||||
def for_project(project_id) when is_binary(project_id) do
|
||||
metadata = preload_metadata(project_id)
|
||||
posts = preload_posts(project_id)
|
||||
posts_by_id = Map.new(posts, &{&1["id"], &1})
|
||||
posts_by_slug = Map.new(posts, &{&1["slug"], &1})
|
||||
tags = preload_tags(project_id)
|
||||
|
||||
%{
|
||||
meta: %{
|
||||
get_project_metadata: unary(fn -> metadata end)
|
||||
},
|
||||
posts: %{
|
||||
get: unary(fn post_id -> Map.get(posts_by_id, post_id) end),
|
||||
get_by_slug: unary(fn slug -> Map.get(posts_by_slug, slug) end)
|
||||
},
|
||||
tags: %{
|
||||
get_all: unary(fn -> tags end)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
defp preload_metadata(project_id) do
|
||||
{:ok, metadata} = Metadata.get_project_metadata(project_id)
|
||||
sanitize(metadata)
|
||||
end
|
||||
|
||||
defp preload_posts(project_id) do
|
||||
Repo.all(from(post in Post, where: post.project_id == ^project_id))
|
||||
|> Enum.map(&post_payload/1)
|
||||
end
|
||||
|
||||
defp preload_tags(project_id) do
|
||||
project_id
|
||||
|> Tags.list_tags()
|
||||
|> Enum.map(&sanitize/1)
|
||||
end
|
||||
|
||||
defp unary(callback) when is_function(callback, 0) do
|
||||
fn args, state ->
|
||||
_decoded_args = :luerl.decode_list(args, state)
|
||||
:luerl.encode_list([callback.()], state)
|
||||
end
|
||||
end
|
||||
|
||||
defp unary(callback) when is_function(callback, 1) do
|
||||
fn args, state ->
|
||||
decoded_args = :luerl.decode_list(args, state)
|
||||
|
||||
value =
|
||||
case decoded_args do
|
||||
[first | _rest] -> callback.(sanitize(first))
|
||||
[] -> callback.(nil)
|
||||
end
|
||||
|
||||
:luerl.encode_list([value], state)
|
||||
end
|
||||
end
|
||||
|
||||
defp post_payload(%Post{} = post) do
|
||||
post
|
||||
|> sanitize()
|
||||
|> Map.put("backlinks", linked_posts(post.id, :incoming))
|
||||
|> Map.put("links_to", linked_posts(post.id, :outgoing))
|
||||
end
|
||||
|
||||
defp linked_posts(post_id, :incoming) do
|
||||
PostLinks.list_incoming_links(post_id)
|
||||
|> Enum.map(&load_linked_post(&1.source_post_id))
|
||||
|> Enum.reject(&is_nil/1)
|
||||
end
|
||||
|
||||
defp linked_posts(post_id, :outgoing) do
|
||||
PostLinks.list_outgoing_links(post_id)
|
||||
|> Enum.map(&load_linked_post(&1.target_post_id))
|
||||
|> Enum.reject(&is_nil/1)
|
||||
end
|
||||
|
||||
defp load_linked_post(post_id) do
|
||||
case Repo.get(Post, post_id) do
|
||||
%Post{} = post -> %{"id" => post.id, "title" => post.title, "slug" => post.slug}
|
||||
nil -> nil
|
||||
end
|
||||
end
|
||||
|
||||
defp sanitize(%_struct{} = struct) do
|
||||
struct
|
||||
|> Map.from_struct()
|
||||
|> Map.drop([:__meta__, :post, :project, :media])
|
||||
|> sanitize()
|
||||
end
|
||||
|
||||
defp sanitize(map) when is_map(map) do
|
||||
Map.new(map, fn {key, value} -> {to_string(key), sanitize(value)} end)
|
||||
end
|
||||
|
||||
defp sanitize(list) when is_list(list), do: Enum.map(list, &sanitize/1)
|
||||
defp sanitize(value) when is_atom(value), do: Atom.to_string(value)
|
||||
defp sanitize(value), do: value
|
||||
end
|
||||
@@ -27,8 +27,8 @@ defmodule BDS.Scripting.Lua do
|
||||
when is_binary(source) and is_binary(entrypoint) and is_list(args) and is_list(opts) do
|
||||
with {:ok, state} <- initial_state(opts),
|
||||
{:ok, state} <- put_args(state, args),
|
||||
{:ok, result, _state} <- run_entrypoint(source, entrypoint, state, opts) do
|
||||
{:ok, unwrap_result(result)}
|
||||
{:ok, result, next_state} <- run_entrypoint(source, entrypoint, state, opts) do
|
||||
{:ok, decode_result(result, next_state)}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -72,12 +72,10 @@ defmodule BDS.Scripting.Lua do
|
||||
defp install_capabilities(state, capabilities) when capabilities in [%{}, []], do: {:ok, state}
|
||||
|
||||
defp install_capabilities(state, capabilities) when is_map(capabilities) do
|
||||
Enum.reduce_while(capabilities, {:ok, state}, fn {name, function}, {:ok, current_state} ->
|
||||
path = ["bds", to_string(name)]
|
||||
|
||||
case :luerl.set_table_keys_dec(path, function, current_state) do
|
||||
Enum.reduce_while(capabilities, {:ok, state}, fn {name, value}, {:ok, current_state} ->
|
||||
case install_capability(["bds", to_string(name)], value, current_state) do
|
||||
{:ok, next_state} -> {:cont, {:ok, next_state}}
|
||||
error -> {:halt, {:error, {:capability_install_failed, path, error}}}
|
||||
{:error, reason} -> {:halt, {:error, reason}}
|
||||
end
|
||||
end)
|
||||
end
|
||||
@@ -85,6 +83,28 @@ defmodule BDS.Scripting.Lua do
|
||||
defp install_capabilities(_state, capabilities),
|
||||
do: {:error, {:invalid_capabilities, capabilities}}
|
||||
|
||||
defp install_capability(path, value, state) when is_map(value) do
|
||||
with {:ok, seeded_state} <- set_capability(path, %{}, state) do
|
||||
Enum.reduce_while(value, {:ok, seeded_state}, fn {name, nested_value}, {:ok, current_state} ->
|
||||
case install_capability(path ++ [to_string(name)], nested_value, current_state) do
|
||||
{:ok, next_state} -> {:cont, {:ok, next_state}}
|
||||
{:error, reason} -> {:halt, {:error, reason}}
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
defp install_capability(path, value, state) do
|
||||
set_capability(path, value, state)
|
||||
end
|
||||
|
||||
defp set_capability(path, value, state) do
|
||||
case :luerl.set_table_keys_dec(path, value, state) do
|
||||
{:ok, next_state} -> {:ok, next_state}
|
||||
error -> {:error, {:capability_install_failed, path, error}}
|
||||
end
|
||||
end
|
||||
|
||||
defp normalize_progress_payload(payload) when is_list(payload) do
|
||||
if Enum.all?(payload, &match?({key, _value} when is_binary(key) or is_atom(key), &1)) do
|
||||
Map.new(payload, fn {key, value} -> {to_string(key), value} end)
|
||||
@@ -149,6 +169,28 @@ defmodule BDS.Scripting.Lua do
|
||||
]
|
||||
end
|
||||
|
||||
defp decode_result(values, state) when is_list(values) do
|
||||
values
|
||||
|> Enum.map(&decode_result(&1, state))
|
||||
|> unwrap_result()
|
||||
end
|
||||
|
||||
defp decode_result(value, state) do
|
||||
value
|
||||
|> :luerl.decode(state)
|
||||
|> normalize_decoded_value()
|
||||
end
|
||||
|
||||
defp normalize_decoded_value(values) when is_list(values) do
|
||||
if Enum.all?(values, &match?({key, _value} when is_binary(key) or is_atom(key), &1)) do
|
||||
Map.new(values, fn {key, value} -> {to_string(key), value} end)
|
||||
else
|
||||
Enum.map(values, &normalize_decoded_value/1)
|
||||
end
|
||||
end
|
||||
|
||||
defp normalize_decoded_value(value), do: value
|
||||
|
||||
defp unwrap_result(values) when is_list(values) do
|
||||
case values do
|
||||
[] -> nil
|
||||
|
||||
Reference in New Issue
Block a user