chore: added more @spec
This commit is contained in:
@@ -30,7 +30,9 @@ defmodule BDS.MCP.AgentConfig do
|
||||
end
|
||||
|
||||
def config_path(:claude_code, home_dir), do: Path.join(home_dir, ".claude.json")
|
||||
def config_path(:github_copilot, home_dir), do: Path.join([home_dir, "Library", "Application Support", "Code", "User", "mcp.json"])
|
||||
|
||||
def config_path(:github_copilot, home_dir),
|
||||
do: Path.join([home_dir, "Library", "Application Support", "Code", "User", "mcp.json"])
|
||||
|
||||
def packaged_executable_path(install_root, platform) when is_binary(install_root) do
|
||||
executable_name =
|
||||
@@ -90,12 +92,21 @@ defmodule BDS.MCP.AgentConfig do
|
||||
defp merge_config(:github_copilot, config, command, args) do
|
||||
servers = Map.get(config, "servers", %{})
|
||||
|
||||
Map.put(config, "servers", Map.put(servers, @server_name, %{"type" => "stdio", "command" => command, "args" => args}))
|
||||
Map.put(
|
||||
config,
|
||||
"servers",
|
||||
Map.put(servers, @server_name, %{"type" => "stdio", "command" => command, "args" => args})
|
||||
)
|
||||
end
|
||||
|
||||
defp merge_config(:claude_code, config, command, args) do
|
||||
servers = Map.get(config, "mcpServers", %{})
|
||||
Map.put(config, "mcpServers", Map.put(servers, @server_name, %{"command" => command, "args" => args}))
|
||||
|
||||
Map.put(
|
||||
config,
|
||||
"mcpServers",
|
||||
Map.put(servers, @server_name, %{"command" => command, "args" => args})
|
||||
)
|
||||
end
|
||||
|
||||
defp remove_server_entry(:github_copilot, config) do
|
||||
|
||||
@@ -8,7 +8,11 @@ defmodule BDS.MCP.Proposal do
|
||||
|
||||
schema "mcp_proposals" do
|
||||
field :kind, :string
|
||||
field :status, Ecto.Enum, values: [:pending, :accepted, :discarded, :expired], default: :pending
|
||||
|
||||
field :status, Ecto.Enum,
|
||||
values: [:pending, :accepted, :discarded, :expired],
|
||||
default: :pending
|
||||
|
||||
field :entity_id, :string
|
||||
field :data, :map
|
||||
field :created_at, :integer
|
||||
@@ -17,7 +21,9 @@ defmodule BDS.MCP.Proposal do
|
||||
|
||||
def changeset(proposal, attrs) do
|
||||
proposal
|
||||
|> cast(attrs, [:id, :kind, :status, :entity_id, :data, :created_at, :expires_at], empty_values: [nil])
|
||||
|> cast(attrs, [:id, :kind, :status, :entity_id, :data, :created_at, :expires_at],
|
||||
empty_values: [nil]
|
||||
)
|
||||
|> validate_required([:id, :kind, :status, :entity_id, :data, :created_at, :expires_at])
|
||||
|> unique_constraint(:status, name: :mcp_proposals_entity_idx)
|
||||
end
|
||||
|
||||
@@ -74,12 +74,15 @@ defmodule BDS.MCP.ProposalStore do
|
||||
|
||||
defp mark_status(id, status) do
|
||||
case Repo.get(Proposal, id) do
|
||||
nil -> nil
|
||||
nil ->
|
||||
nil
|
||||
|
||||
proposal ->
|
||||
Repo.delete_all(
|
||||
from other in Proposal,
|
||||
where:
|
||||
other.id != ^id and other.kind == ^proposal.kind and other.entity_id == ^proposal.entity_id and
|
||||
other.id != ^id and other.kind == ^proposal.kind and
|
||||
other.entity_id == ^proposal.entity_id and
|
||||
other.status == ^status
|
||||
)
|
||||
|
||||
@@ -90,6 +93,7 @@ defmodule BDS.MCP.ProposalStore do
|
||||
end
|
||||
|
||||
defp derive_entity_id(data) do
|
||||
data["post_id"] || data["script_id"] || data["template_id"] || data["media_id"] || Ecto.UUID.generate()
|
||||
data["post_id"] || data["script_id"] || data["template_id"] || data["media_id"] ||
|
||||
Ecto.UUID.generate()
|
||||
end
|
||||
end
|
||||
|
||||
@@ -138,8 +138,11 @@ defmodule BDS.MCP.Server do
|
||||
case URI.parse(target) do
|
||||
%URI{path: "/mcp"} ->
|
||||
case GenServer.call(__MODULE__, {:http_request, request}, 5_000) do
|
||||
{:ok, status, body} -> http_response(status, Jason.encode!(body), "application/json", request.headers)
|
||||
{:error, status, body} -> http_response(status, body, "text/plain", request.headers)
|
||||
{:ok, status, body} ->
|
||||
http_response(status, Jason.encode!(body), "application/json", request.headers)
|
||||
|
||||
{:error, status, body} ->
|
||||
http_response(status, body, "text/plain", request.headers)
|
||||
end
|
||||
|
||||
_other ->
|
||||
@@ -170,7 +173,10 @@ defmodule BDS.MCP.Server do
|
||||
success_response(id, %{
|
||||
"protocolVersion" => Map.get(params, "protocolVersion", "2025-03-26"),
|
||||
"capabilities" => %{"tools" => %{}, "resources" => %{}},
|
||||
"serverInfo" => %{"name" => @server_name, "version" => Application.spec(:bds, :vsn) |> to_string()}
|
||||
"serverInfo" => %{
|
||||
"name" => @server_name,
|
||||
"version" => Application.spec(:bds, :vsn) |> to_string()
|
||||
}
|
||||
})}
|
||||
|
||||
"tools/list" ->
|
||||
@@ -196,10 +202,17 @@ defmodule BDS.MCP.Server do
|
||||
arguments = Map.get(params, "arguments", %{})
|
||||
|
||||
case BDS.MCP.call_tool(name, arguments) do
|
||||
{:ok, result} -> {:ok, success_response(id, %{"content" => [%{"type" => "json", "json" => result}]})}
|
||||
{:error, :unknown_tool} -> {:error, error_response(id, -32601, "Unknown tool")}
|
||||
{:error, :not_found} -> {:error, error_response(id, -32004, "Not found")}
|
||||
{:error, reason} -> {:error, error_response(id, -32000, inspect(reason))}
|
||||
{:ok, result} ->
|
||||
{:ok, success_response(id, %{"content" => [%{"type" => "json", "json" => result}]})}
|
||||
|
||||
{:error, :unknown_tool} ->
|
||||
{:error, error_response(id, -32601, "Unknown tool")}
|
||||
|
||||
{:error, :not_found} ->
|
||||
{:error, error_response(id, -32004, "Not found")}
|
||||
|
||||
{:error, reason} ->
|
||||
{:error, error_response(id, -32000, inspect(reason))}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -286,7 +299,8 @@ defmodule BDS.MCP.Server do
|
||||
|> IO.iodata_to_binary()
|
||||
end
|
||||
|
||||
defp http_error_response(status, headers \\ %{}), do: http_response(status, reason_body(status), "text/plain", headers)
|
||||
defp http_error_response(status, headers \\ %{}),
|
||||
do: http_response(status, reason_body(status), "text/plain", headers)
|
||||
|
||||
defp reason_body(400), do: "Bad Request"
|
||||
defp reason_body(404), do: "Not Found"
|
||||
|
||||
@@ -9,8 +9,15 @@ defmodule BDS.MCP.Stdio do
|
||||
if line != "" do
|
||||
response =
|
||||
case Jason.decode(line) do
|
||||
{:ok, payload} -> handle_payload(payload)
|
||||
{:error, _reason} -> %{"jsonrpc" => "2.0", "id" => nil, "error" => %{"code" => -32700, "message" => "Parse error"}}
|
||||
{:ok, payload} ->
|
||||
handle_payload(payload)
|
||||
|
||||
{:error, _reason} ->
|
||||
%{
|
||||
"jsonrpc" => "2.0",
|
||||
"id" => nil,
|
||||
"error" => %{"code" => -32700, "message" => "Parse error"}
|
||||
}
|
||||
end
|
||||
|
||||
IO.write(Jason.encode!(response) <> "\n")
|
||||
@@ -18,14 +25,22 @@ defmodule BDS.MCP.Stdio do
|
||||
end)
|
||||
end
|
||||
|
||||
defp handle_payload(%{"jsonrpc" => "2.0", "id" => id, "method" => "initialize", "params" => params}) do
|
||||
defp handle_payload(%{
|
||||
"jsonrpc" => "2.0",
|
||||
"id" => id,
|
||||
"method" => "initialize",
|
||||
"params" => params
|
||||
}) do
|
||||
%{
|
||||
"jsonrpc" => "2.0",
|
||||
"id" => id,
|
||||
"result" => %{
|
||||
"protocolVersion" => Map.get(params, "protocolVersion", "2025-03-26"),
|
||||
"capabilities" => %{"tools" => %{}, "resources" => %{}},
|
||||
"serverInfo" => %{"name" => "Blogging Desktop Server", "version" => Application.spec(:bds, :vsn) |> to_string()}
|
||||
"serverInfo" => %{
|
||||
"name" => "Blogging Desktop Server",
|
||||
"version" => Application.spec(:bds, :vsn) |> to_string()
|
||||
}
|
||||
}
|
||||
}
|
||||
end
|
||||
@@ -34,10 +49,26 @@ defmodule BDS.MCP.Stdio do
|
||||
%{"jsonrpc" => "2.0", "id" => id, "result" => %{"tools" => BDS.MCP.list_tools()}}
|
||||
end
|
||||
|
||||
defp handle_payload(%{"jsonrpc" => "2.0", "id" => id, "method" => "tools/call", "params" => %{"name" => name} = params}) do
|
||||
defp handle_payload(%{
|
||||
"jsonrpc" => "2.0",
|
||||
"id" => id,
|
||||
"method" => "tools/call",
|
||||
"params" => %{"name" => name} = params
|
||||
}) do
|
||||
case BDS.MCP.call_tool(name, Map.get(params, "arguments", %{})) do
|
||||
{:ok, result} -> %{"jsonrpc" => "2.0", "id" => id, "result" => %{"content" => [%{"type" => "json", "json" => result}]}}
|
||||
{:error, reason} -> %{"jsonrpc" => "2.0", "id" => id, "error" => %{"code" => -32000, "message" => inspect(reason)}}
|
||||
{:ok, result} ->
|
||||
%{
|
||||
"jsonrpc" => "2.0",
|
||||
"id" => id,
|
||||
"result" => %{"content" => [%{"type" => "json", "json" => result}]}
|
||||
}
|
||||
|
||||
{:error, reason} ->
|
||||
%{
|
||||
"jsonrpc" => "2.0",
|
||||
"id" => id,
|
||||
"error" => %{"code" => -32000, "message" => inspect(reason)}
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -45,17 +76,38 @@ defmodule BDS.MCP.Stdio do
|
||||
%{"jsonrpc" => "2.0", "id" => id, "result" => %{"resources" => BDS.MCP.list_resources()}}
|
||||
end
|
||||
|
||||
defp handle_payload(%{"jsonrpc" => "2.0", "id" => id, "method" => "resources/read", "params" => %{"uri" => uri}}) do
|
||||
defp handle_payload(%{
|
||||
"jsonrpc" => "2.0",
|
||||
"id" => id,
|
||||
"method" => "resources/read",
|
||||
"params" => %{"uri" => uri}
|
||||
}) do
|
||||
case BDS.MCP.read_resource(uri) do
|
||||
{:ok, result} ->
|
||||
%{"jsonrpc" => "2.0", "id" => id, "result" => %{"contents" => [%{"uri" => uri, "mimeType" => "application/json", "text" => Jason.encode!(result)}]}}
|
||||
%{
|
||||
"jsonrpc" => "2.0",
|
||||
"id" => id,
|
||||
"result" => %{
|
||||
"contents" => [
|
||||
%{"uri" => uri, "mimeType" => "application/json", "text" => Jason.encode!(result)}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
{:error, reason} ->
|
||||
%{"jsonrpc" => "2.0", "id" => id, "error" => %{"code" => -32000, "message" => inspect(reason)}}
|
||||
%{
|
||||
"jsonrpc" => "2.0",
|
||||
"id" => id,
|
||||
"error" => %{"code" => -32000, "message" => inspect(reason)}
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
defp handle_payload(%{"jsonrpc" => "2.0", "id" => id}) do
|
||||
%{"jsonrpc" => "2.0", "id" => id, "error" => %{"code" => -32601, "message" => "Method not found"}}
|
||||
%{
|
||||
"jsonrpc" => "2.0",
|
||||
"id" => id,
|
||||
"error" => %{"code" => -32601, "message" => "Method not found"}
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -60,8 +60,11 @@ defmodule BDS.MCP.Tools do
|
||||
@spec validate_template(String.t()) :: {:ok, %{valid: boolean(), errors: [String.t()]}}
|
||||
def validate_template(source) when is_binary(source) do
|
||||
case Liquex.parse(source) do
|
||||
{:ok, _ast} -> {:ok, %{valid: true, errors: []}}
|
||||
{:error, reason, line} -> {:ok, %{valid: false, errors: ["#{inspect(reason)} at line #{line}"]}}
|
||||
{:ok, _ast} ->
|
||||
{:ok, %{valid: true, errors: []}}
|
||||
|
||||
{:error, reason, line} ->
|
||||
{:ok, %{valid: false, errors: ["#{inspect(reason)} at line #{line}"]}}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -276,7 +279,8 @@ defmodule BDS.MCP.Tools do
|
||||
ttl_ms: @proposal_ttl_app_ms
|
||||
)
|
||||
|
||||
{:ok, %{"proposal_id" => proposal.id, "current" => sanitize(media), "proposed" => changes}}
|
||||
{:ok,
|
||||
%{"proposal_id" => proposal.id, "current" => sanitize(media), "proposed" => changes}}
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
Reference in New Issue
Block a user