ask-anychat-mcp 0.1.1
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 +7 -0
- data/CHANGELOG.md +26 -0
- data/LICENSE +21 -0
- data/README.md +74 -0
- data/bin/ask-anychat-mcp +6 -0
- data/lib/ask/anychat/mcp/tool.rb +105 -0
- data/lib/ask/anychat/mcp/tools/create_agent.rb +55 -0
- data/lib/ask/anychat/mcp/tools/delete_agent.rb +42 -0
- data/lib/ask/anychat/mcp/tools/get_agent.rb +42 -0
- data/lib/ask/anychat/mcp/tools/list_agents.rb +43 -0
- data/lib/ask/anychat/mcp/tools/update_agent.rb +67 -0
- data/lib/ask/anychat/mcp/tools.rb +25 -0
- data/lib/ask/anychat/mcp/version.rb +9 -0
- data/lib/ask/anychat/mcp.rb +41 -0
- data/lib/ask-anychat-mcp.rb +3 -0
- metadata +119 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: e4c8dd1fbdd06af3eea37bc761e1181b0b1fd6b8a4b9a192d87cbdce9ec76bd6
|
|
4
|
+
data.tar.gz: a028698b40e423aa5494609fae8674f6a0cc986d6d07c16f0771f653022b5bb6
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 6bc18d87d53dbe770e28801ed7ea9e497adc8aa7181147255a64ed824114a12abd3d3ea27efaba1c26ae1a2d4d9171dbfc16f02890f8ba5d5893d317463afe6b
|
|
7
|
+
data.tar.gz: ab7a9c27147b4688673098b9def67d76bd5085b51c8139e81bc9c26f414382a0fbf47520075aa353d34ab76d92c77dc5c56df2175179db9d28dff6199910205c
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
## [0.1.0] - 2026-09-17
|
|
2
|
+
|
|
3
|
+
### Added
|
|
4
|
+
|
|
5
|
+
- **An MCP server for Anychat**, served over stdio —
|
|
6
|
+
`Ask::AnyChat::MCP.start` and the `ask-anychat-mcp` executable. Tool names are
|
|
7
|
+
prefixed `ask_anychat_` so they cannot collide with a client's own tools of the
|
|
8
|
+
same name, as the other ask MCP servers are.
|
|
9
|
+
|
|
10
|
+
- **Five tools, one per operation** — `ask_anychat_list_agents`,
|
|
11
|
+
`ask_anychat_get_agent`, `ask_anychat_create_agent`,
|
|
12
|
+
`ask_anychat_update_agent` and `ask_anychat_delete_agent`. Every one names a
|
|
13
|
+
workspace, because a workspace is what a token reaches.
|
|
14
|
+
|
|
15
|
+
- **Arguments checked before the call** — a missing or misspelled argument is
|
|
16
|
+
named, with the ones that were on offer, rather than being dropped and
|
|
17
|
+
surfacing later as an unexplained result. The schema each tool declares is
|
|
18
|
+
also enforced at the protocol boundary.
|
|
19
|
+
|
|
20
|
+
- **Refusals come back as text** — the sentence the API wrote, prefixed with
|
|
21
|
+
`Error:`, so a model can act on it instead of being handed an exception that
|
|
22
|
+
ends the conversation.
|
|
23
|
+
|
|
24
|
+
- **`Ask::AnyChat::MCP.tools(client:)`** — the client can be supplied, which is
|
|
25
|
+
how a host points the server at a particular deployment and how the tests hold
|
|
26
|
+
the API still.
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Kaka Ruto
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# Ask::AnyChat::MCP
|
|
2
|
+
|
|
3
|
+
MCP server for [Anychat](https://anywaye.com), for the
|
|
4
|
+
[ask-rb](https://github.com/ask-rb) ecosystem.
|
|
5
|
+
|
|
6
|
+
Exposes the agents a workspace owns as callable tools over stdio, so a client
|
|
7
|
+
that speaks the Model Context Protocol can create and manage them without
|
|
8
|
+
knowing anything about the API underneath. The tool shell lives here; the API
|
|
9
|
+
client is [ask-anychat](https://github.com/ask-rb/ask-anychat).
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
```ruby
|
|
14
|
+
gem "ask-anychat-mcp"
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Usage
|
|
18
|
+
|
|
19
|
+
Register the executable with an MCP client:
|
|
20
|
+
|
|
21
|
+
```json
|
|
22
|
+
{
|
|
23
|
+
"mcp": {
|
|
24
|
+
"servers": {
|
|
25
|
+
"ask-anychat-mcp": {
|
|
26
|
+
"type": "stdio",
|
|
27
|
+
"command": "ask-anychat-mcp",
|
|
28
|
+
"env": { "ANYCHAT_TOKEN": "..." }
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Set `ANYCHAT_BASE_URL` too when pointing at a deployment that is not the hosted
|
|
36
|
+
app. Mint a token in Anychat under Settings → API tokens.
|
|
37
|
+
|
|
38
|
+
### Tools
|
|
39
|
+
|
|
40
|
+
| Tool | What it does |
|
|
41
|
+
| --- | --- |
|
|
42
|
+
| `ask_anychat_list_agents` | Every agent a workspace owns, with the address each answers on |
|
|
43
|
+
| `ask_anychat_get_agent` | One agent, by the address it answers on |
|
|
44
|
+
| `ask_anychat_create_agent` | Bring a new agent into being — a name is all it takes |
|
|
45
|
+
| `ask_anychat_update_agent` | Change one; only the arguments you pass are written |
|
|
46
|
+
| `ask_anychat_delete_agent` | Retire one; its address becomes free |
|
|
47
|
+
|
|
48
|
+
Every tool names a workspace, because a workspace is what a token reaches.
|
|
49
|
+
|
|
50
|
+
Names are prefixed `ask_anychat_` to keep them from colliding with a client's own
|
|
51
|
+
tools of the same name, as the other ask MCP servers are.
|
|
52
|
+
|
|
53
|
+
### What comes back
|
|
54
|
+
|
|
55
|
+
A refusal is text the model can act on rather than an exception that ends the
|
|
56
|
+
conversation, and it carries the sentence the API wrote:
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
Error: No agent at /anywaye/nope.
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
The same is true of an argument that was missing or misspelled — a model that
|
|
63
|
+
typed `name` where `display_name` was wanted is told so, rather than having the
|
|
64
|
+
value quietly dropped.
|
|
65
|
+
|
|
66
|
+
## Contributing
|
|
67
|
+
|
|
68
|
+
Run `bin/setup` once, then `bundle exec rake test`. Until `ask-anychat` and
|
|
69
|
+
`ask-mcp` are released, the Gemfile consumes both from the working copies
|
|
70
|
+
alongside this one.
|
|
71
|
+
|
|
72
|
+
## License
|
|
73
|
+
|
|
74
|
+
MIT.
|
data/bin/ask-anychat-mcp
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ask
|
|
4
|
+
module AnyChat
|
|
5
|
+
module MCP
|
|
6
|
+
# The duck type the MCP adapter expects: a name, a description, a JSON
|
|
7
|
+
# Schema, and a call.
|
|
8
|
+
#
|
|
9
|
+
# Arguments are checked here rather than at the API, so a model that
|
|
10
|
+
# misspells a parameter is told which one it typed and which were on
|
|
11
|
+
# offer — instead of being handed a status code it cannot read. Failures
|
|
12
|
+
# come back as text for the same reason: this is a conversation.
|
|
13
|
+
class Tool
|
|
14
|
+
class << self
|
|
15
|
+
# Not `name`: that is already how a class answers to itself, and
|
|
16
|
+
# taking it over breaks more than it tells.
|
|
17
|
+
def tool_name(value = nil)
|
|
18
|
+
@tool_name = value if value
|
|
19
|
+
@tool_name
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def description(text = nil)
|
|
23
|
+
@description = text if text
|
|
24
|
+
@description
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def params(schema = nil)
|
|
28
|
+
@params = schema if schema
|
|
29
|
+
@params || {}
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def initialize(client: nil)
|
|
34
|
+
@client = client
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def name
|
|
38
|
+
self.class.tool_name
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def description
|
|
42
|
+
self.class.description
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def params_schema
|
|
46
|
+
self.class.params
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def call(arguments = {})
|
|
50
|
+
args = stringify(arguments)
|
|
51
|
+
violation = violation(args)
|
|
52
|
+
return violation if violation
|
|
53
|
+
|
|
54
|
+
run(args)
|
|
55
|
+
rescue AnyChat::Error => e
|
|
56
|
+
"Error: #{e.message}"
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
private
|
|
60
|
+
|
|
61
|
+
attr_reader :client
|
|
62
|
+
|
|
63
|
+
# Built once per call and only if a tool needs it, so the server can be
|
|
64
|
+
# started without a token in the environment.
|
|
65
|
+
def anychat
|
|
66
|
+
@anychat ||= client || AnyChat.client
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def run(_args)
|
|
70
|
+
raise NotImplementedError, "#{self.class} must implement #run"
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def stringify(arguments)
|
|
74
|
+
arguments.each_with_object({}) { |(key, value), out| out[key.to_s] = value }
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Unknown and missing arguments are named, not ignored: a model that
|
|
78
|
+
# typed `name` where `display_name` was wanted should hear about it.
|
|
79
|
+
def violation(args)
|
|
80
|
+
allowed = params_schema[:properties].to_h.keys.map(&:to_s)
|
|
81
|
+
required = Array(params_schema[:required]).map(&:to_s)
|
|
82
|
+
|
|
83
|
+
missing = required - args.keys
|
|
84
|
+
if missing.any?
|
|
85
|
+
return "Error: missing required argument(s): #{missing.join(', ')}. Expected: #{allowed.join(', ')}"
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
extra = args.keys - allowed
|
|
89
|
+
return "Error: unknown argument(s): #{extra.join(', ')}. Expected: #{allowed.join(', ')}" if extra.any?
|
|
90
|
+
|
|
91
|
+
nil
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Only what the caller actually sent: an omitted optional argument is an
|
|
95
|
+
# omission, not a nil to write over what is already there.
|
|
96
|
+
def writable(args, *names)
|
|
97
|
+
names.each_with_object({}) do |name, out|
|
|
98
|
+
key = name.to_s
|
|
99
|
+
out[name.to_sym] = args[key] if args.key?(key)
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ask
|
|
4
|
+
module AnyChat
|
|
5
|
+
module MCP
|
|
6
|
+
module Tools
|
|
7
|
+
# A new agent for a workspace.
|
|
8
|
+
class CreateAgent < Tool
|
|
9
|
+
tool_name "ask_anychat_create_agent"
|
|
10
|
+
description "Bring a new agent into being in a workspace. Only a name is needed; " \
|
|
11
|
+
"the address it answers on is derived from that name, and a taken one " \
|
|
12
|
+
"is handed the first free variant rather than failing. Pass handle to " \
|
|
13
|
+
"choose the address yourself."
|
|
14
|
+
params(
|
|
15
|
+
type: "object",
|
|
16
|
+
properties: {
|
|
17
|
+
workspace: {
|
|
18
|
+
type: "string",
|
|
19
|
+
description: "The workspace's username, the first half of /<workspace>/<agent>"
|
|
20
|
+
},
|
|
21
|
+
display_name: {
|
|
22
|
+
type: "string",
|
|
23
|
+
description: "What the agent is called, e.g. \"Support\""
|
|
24
|
+
},
|
|
25
|
+
handle: {
|
|
26
|
+
type: "string",
|
|
27
|
+
description: "The address to answer on. Defaults to one derived from the name."
|
|
28
|
+
},
|
|
29
|
+
description: {
|
|
30
|
+
type: "string",
|
|
31
|
+
description: "One line about what this agent is for."
|
|
32
|
+
},
|
|
33
|
+
public: {
|
|
34
|
+
type: "boolean",
|
|
35
|
+
description: "Whether the world can find it. Defaults to true."
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
required: %w[workspace display_name]
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
def run(args)
|
|
44
|
+
agent = anychat.create_workspace_agent(
|
|
45
|
+
args["workspace"],
|
|
46
|
+
**writable(args, :display_name, :handle, :description, :public)
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
"Created #{agent['display_name']} at #{agent['address']}."
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ask
|
|
4
|
+
module AnyChat
|
|
5
|
+
module MCP
|
|
6
|
+
module Tools
|
|
7
|
+
# An agent leaves the workspace, and its address becomes free.
|
|
8
|
+
#
|
|
9
|
+
# Conversations customers had with it are kept, so a retired agent still
|
|
10
|
+
# appears in that history — which is worth saying, because an owner who
|
|
11
|
+
# expects the record gone would be wrong to.
|
|
12
|
+
class DeleteAgent < Tool
|
|
13
|
+
tool_name "ask_anychat_delete_agent"
|
|
14
|
+
description "Retire an agent from a workspace. Its address becomes free for another " \
|
|
15
|
+
"agent to take. Conversations it already had are kept."
|
|
16
|
+
params(
|
|
17
|
+
type: "object",
|
|
18
|
+
properties: {
|
|
19
|
+
workspace: {
|
|
20
|
+
type: "string",
|
|
21
|
+
description: "The workspace's username, the first half of /<workspace>/<agent>"
|
|
22
|
+
},
|
|
23
|
+
agent: {
|
|
24
|
+
type: "string",
|
|
25
|
+
description: "The address it answers on, the second half of /<workspace>/<agent>"
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
required: %w[workspace agent]
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
def run(args)
|
|
34
|
+
anychat.destroy_workspace_agent(args["workspace"], args["agent"])
|
|
35
|
+
|
|
36
|
+
"Retired the agent at /#{args['workspace']}/#{args['agent']}. The address is free again."
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ask
|
|
4
|
+
module AnyChat
|
|
5
|
+
module MCP
|
|
6
|
+
module Tools
|
|
7
|
+
# One agent, by the address it answers on.
|
|
8
|
+
class GetAgent < Tool
|
|
9
|
+
tool_name "ask_anychat_get_agent"
|
|
10
|
+
description "Read one agent of a workspace, by the address it answers on. Use " \
|
|
11
|
+
"ask_anychat_list_agents first if you do not know the address."
|
|
12
|
+
params(
|
|
13
|
+
type: "object",
|
|
14
|
+
properties: {
|
|
15
|
+
workspace: {
|
|
16
|
+
type: "string",
|
|
17
|
+
description: "The workspace's username, the first half of /<workspace>/<agent>"
|
|
18
|
+
},
|
|
19
|
+
agent: {
|
|
20
|
+
type: "string",
|
|
21
|
+
description: "The address it answers on, the second half of /<workspace>/<agent>"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
required: %w[workspace agent]
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def run(args)
|
|
30
|
+
agent = anychat.workspace_agent(args["workspace"], args["agent"])
|
|
31
|
+
|
|
32
|
+
[
|
|
33
|
+
"#{agent['display_name']} at #{agent['address']}",
|
|
34
|
+
agent["description"].to_s.strip.empty? ? nil : agent["description"],
|
|
35
|
+
agent["public"] ? "Public" : "Private"
|
|
36
|
+
].compact.join("\n")
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ask
|
|
4
|
+
module AnyChat
|
|
5
|
+
module MCP
|
|
6
|
+
module Tools
|
|
7
|
+
# The agents a workspace owns, oldest first.
|
|
8
|
+
class ListAgents < Tool
|
|
9
|
+
tool_name "ask_anychat_list_agents"
|
|
10
|
+
description "List the agents a workspace owns, oldest first, with the address each " \
|
|
11
|
+
"answers on. Read this before creating or changing one — it is how you " \
|
|
12
|
+
"learn which addresses are already taken."
|
|
13
|
+
params(
|
|
14
|
+
type: "object",
|
|
15
|
+
properties: {
|
|
16
|
+
workspace: {
|
|
17
|
+
type: "string",
|
|
18
|
+
description: "The workspace's username, the first half of /<workspace>/<agent>"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
required: ["workspace"]
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def run(args)
|
|
27
|
+
agents = anychat.workspace_agents(args["workspace"])
|
|
28
|
+
return "This workspace has no agents yet." if agents.empty?
|
|
29
|
+
|
|
30
|
+
agents.map { |agent| line(args["workspace"], agent) }.join("\n")
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def line(workspace, agent)
|
|
34
|
+
parts = ["#{agent['display_name']} at /#{workspace}/#{agent['handle']}"]
|
|
35
|
+
parts << "— #{agent['description']}" if agent["description"].to_s.strip.length.positive?
|
|
36
|
+
parts << "(private)" unless agent["public"]
|
|
37
|
+
parts.join(" ")
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ask
|
|
4
|
+
module AnyChat
|
|
5
|
+
module MCP
|
|
6
|
+
module Tools
|
|
7
|
+
# What an existing agent becomes.
|
|
8
|
+
#
|
|
9
|
+
# The address being changed and the address being changed *to* are two
|
|
10
|
+
# different things, so they are two different arguments: `agent` says
|
|
11
|
+
# which one, `handle` says what to move it to. One name for both would
|
|
12
|
+
# be a sentence that cannot be read.
|
|
13
|
+
class UpdateAgent < Tool
|
|
14
|
+
tool_name "ask_anychat_update_agent"
|
|
15
|
+
description "Change an agent a workspace already owns. Only the arguments you pass " \
|
|
16
|
+
"are written — anything left out keeps its value. Pass handle to move " \
|
|
17
|
+
"the address it answers on."
|
|
18
|
+
params(
|
|
19
|
+
type: "object",
|
|
20
|
+
properties: {
|
|
21
|
+
workspace: {
|
|
22
|
+
type: "string",
|
|
23
|
+
description: "The workspace's username, the first half of /<workspace>/<agent>"
|
|
24
|
+
},
|
|
25
|
+
agent: {
|
|
26
|
+
type: "string",
|
|
27
|
+
description: "The address it answers on now, the second half of /<workspace>/<agent>"
|
|
28
|
+
},
|
|
29
|
+
display_name: {
|
|
30
|
+
type: "string",
|
|
31
|
+
description: "What the agent is called."
|
|
32
|
+
},
|
|
33
|
+
handle: {
|
|
34
|
+
type: "string",
|
|
35
|
+
description: "The address to move it to. A taken one is given the first free variant."
|
|
36
|
+
},
|
|
37
|
+
description: {
|
|
38
|
+
type: "string",
|
|
39
|
+
description: "One line about what this agent is for."
|
|
40
|
+
},
|
|
41
|
+
public: {
|
|
42
|
+
type: "boolean",
|
|
43
|
+
description: "Whether the world can find it."
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
required: %w[workspace agent]
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
def run(args)
|
|
52
|
+
attributes = writable(args, :display_name, :handle, :description, :public)
|
|
53
|
+
return nothing_to_change if attributes.empty?
|
|
54
|
+
|
|
55
|
+
agent = anychat.update_workspace_agent(args["workspace"], args["agent"], **attributes)
|
|
56
|
+
|
|
57
|
+
"Updated #{agent['display_name']} at #{agent['address']}."
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def nothing_to_change
|
|
61
|
+
"Error: nothing to change. Pass at least one of display_name, handle, description, public."
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "tools/list_agents"
|
|
4
|
+
require_relative "tools/get_agent"
|
|
5
|
+
require_relative "tools/create_agent"
|
|
6
|
+
require_relative "tools/update_agent"
|
|
7
|
+
require_relative "tools/delete_agent"
|
|
8
|
+
|
|
9
|
+
module Ask
|
|
10
|
+
module AnyChat
|
|
11
|
+
module MCP
|
|
12
|
+
# Every tool this server exposes, in the order a model should reach for
|
|
13
|
+
# them: read, then write, then retire.
|
|
14
|
+
module Tools
|
|
15
|
+
ALL = [
|
|
16
|
+
ListAgents,
|
|
17
|
+
GetAgent,
|
|
18
|
+
CreateAgent,
|
|
19
|
+
UpdateAgent,
|
|
20
|
+
DeleteAgent
|
|
21
|
+
].freeze
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "ask/mcp"
|
|
4
|
+
require "ask-anychat"
|
|
5
|
+
require_relative "mcp/version"
|
|
6
|
+
require_relative "mcp/tool"
|
|
7
|
+
require_relative "mcp/tools"
|
|
8
|
+
|
|
9
|
+
module Ask
|
|
10
|
+
module AnyChat
|
|
11
|
+
# MCP (Model Context Protocol) server for Anychat.
|
|
12
|
+
#
|
|
13
|
+
# Exposes workspace agent CRUD over MCP, so a client that speaks the
|
|
14
|
+
# protocol can create and manage a workspace's agents without knowing
|
|
15
|
+
# anything about this API. Tool names are prefixed `ask_anychat_` to keep
|
|
16
|
+
# them from colliding with a client's own tools of the same name.
|
|
17
|
+
module MCP
|
|
18
|
+
# Every tool, each holding the client it was given — or building one from
|
|
19
|
+
# the environment on first use, which is what the executable relies on.
|
|
20
|
+
def self.tools(client: nil)
|
|
21
|
+
Tools::ALL.map { |tool| tool.new(client: client) }
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Serve over stdio. Blocking — the last line of an entry-point script.
|
|
25
|
+
#
|
|
26
|
+
# $ ask-anychat-mcp
|
|
27
|
+
#
|
|
28
|
+
# Register it with an MCP client by command; set ANYCHAT_TOKEN and, if it
|
|
29
|
+
# is not the hosted app, ANYCHAT_BASE_URL in the server's environment.
|
|
30
|
+
def self.start(client: nil)
|
|
31
|
+
Ask::MCP::Server.start_stdio(
|
|
32
|
+
name: "ask-anychat-mcp",
|
|
33
|
+
version: VERSION,
|
|
34
|
+
tools: tools(client: client),
|
|
35
|
+
capabilities: { tools: {} },
|
|
36
|
+
debug: ENV["DEBUG"] == "1"
|
|
37
|
+
)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: ask-anychat-mcp
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.1
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Kaka Ruto
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: ask-anychat
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: 0.1.0
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: 0.1.0
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: ask-mcp
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: 0.5.0
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: 0.5.0
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: minitest
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - "~>"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '5.25'
|
|
47
|
+
type: :development
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - "~>"
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '5.25'
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: rake
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - "~>"
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '13.0'
|
|
61
|
+
type: :development
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - "~>"
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '13.0'
|
|
68
|
+
description: A minimal MCP (Model Context Protocol) server that exposes the agents
|
|
69
|
+
an Anychat workspace owns as callable tools over stdio — list them, read one, bring
|
|
70
|
+
a new one into being, change one, or retire one. Designed for clients that speak
|
|
71
|
+
MCP (ZCode, Claude Code, and the like). The tool shell (name, schema, call) lives
|
|
72
|
+
here, wrapping ask-anychat; the API client itself is that gem's.
|
|
73
|
+
email:
|
|
74
|
+
- kaka@myrrlabs.com
|
|
75
|
+
executables:
|
|
76
|
+
- ask-anychat-mcp
|
|
77
|
+
extensions: []
|
|
78
|
+
extra_rdoc_files: []
|
|
79
|
+
files:
|
|
80
|
+
- CHANGELOG.md
|
|
81
|
+
- LICENSE
|
|
82
|
+
- README.md
|
|
83
|
+
- bin/ask-anychat-mcp
|
|
84
|
+
- lib/ask-anychat-mcp.rb
|
|
85
|
+
- lib/ask/anychat/mcp.rb
|
|
86
|
+
- lib/ask/anychat/mcp/tool.rb
|
|
87
|
+
- lib/ask/anychat/mcp/tools.rb
|
|
88
|
+
- lib/ask/anychat/mcp/tools/create_agent.rb
|
|
89
|
+
- lib/ask/anychat/mcp/tools/delete_agent.rb
|
|
90
|
+
- lib/ask/anychat/mcp/tools/get_agent.rb
|
|
91
|
+
- lib/ask/anychat/mcp/tools/list_agents.rb
|
|
92
|
+
- lib/ask/anychat/mcp/tools/update_agent.rb
|
|
93
|
+
- lib/ask/anychat/mcp/version.rb
|
|
94
|
+
homepage: https://github.com/ask-rb/ask-anychat-mcp
|
|
95
|
+
licenses:
|
|
96
|
+
- MIT
|
|
97
|
+
metadata:
|
|
98
|
+
homepage_uri: https://github.com/ask-rb/ask-anychat-mcp
|
|
99
|
+
source_code_uri: https://github.com/ask-rb/ask-anychat-mcp
|
|
100
|
+
changelog_uri: https://github.com/ask-rb/ask-anychat-mcp/blob/master/CHANGELOG.md
|
|
101
|
+
rubygems_mfa_required: 'true'
|
|
102
|
+
rdoc_options: []
|
|
103
|
+
require_paths:
|
|
104
|
+
- lib
|
|
105
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
106
|
+
requirements:
|
|
107
|
+
- - ">="
|
|
108
|
+
- !ruby/object:Gem::Version
|
|
109
|
+
version: '3.2'
|
|
110
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
111
|
+
requirements:
|
|
112
|
+
- - ">="
|
|
113
|
+
- !ruby/object:Gem::Version
|
|
114
|
+
version: '0'
|
|
115
|
+
requirements: []
|
|
116
|
+
rubygems_version: 4.0.18
|
|
117
|
+
specification_version: 4
|
|
118
|
+
summary: MCP server for Anychat
|
|
119
|
+
test_files: []
|