geniete-mcp 0.1.0__tar.gz
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.
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Genie, Inc.
|
|
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.
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: geniete-mcp
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: GenieTé MCP server — screens, voice, and channels tools for AI agents
|
|
5
|
+
License: MIT
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Keywords: mcp,agents,geniete,tts,pubsub,screens
|
|
8
|
+
Author: Genie, Inc.
|
|
9
|
+
Author-email: renat@geniete.com
|
|
10
|
+
Requires-Python: >=3.10
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Requires-Dist: httpx (>=0.27.0)
|
|
16
|
+
Requires-Dist: mcp (>=1.0.0)
|
|
17
|
+
Project-URL: Company, https://www.genieinc.com/
|
|
18
|
+
Project-URL: Homepage, https://www.geniete.com/
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
|
|
21
|
+
# geniete-mcp
|
|
22
|
+
|
|
23
|
+
One MCP server for the whole [GenieTé](https://www.geniete.com/) suite —
|
|
24
|
+
give an AI agent a live screen, a voice, and a realtime channel with one
|
|
25
|
+
install. Tools appear only for the services you configure.
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
uv tool install geniete-mcp # or: pip install geniete-mcp
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
```json
|
|
32
|
+
{
|
|
33
|
+
"mcpServers": {
|
|
34
|
+
"geniete": {
|
|
35
|
+
"command": "geniete-mcp",
|
|
36
|
+
"env": {
|
|
37
|
+
"AGENTSCREEN_API_KEY": "as_key_...",
|
|
38
|
+
"AGENTSCREEN_SCREEN_ID": "scr_...",
|
|
39
|
+
"VOICE_API_KEY": "vk_...",
|
|
40
|
+
"VOICE_SPEAKER_ID": "spk_...",
|
|
41
|
+
"CHANNELS_TOKEN": "tok_...",
|
|
42
|
+
"CHANNELS_CHANNEL_ID": "chan_..."
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Configure any subset — each pair of env vars switches on its tool group:
|
|
50
|
+
|
|
51
|
+
| Tools | Service |
|
|
52
|
+
|-------|---------|
|
|
53
|
+
| `screen_message` `screen_metric` `screen_terminal` `screen_clear` `screen_link` | [Screens](https://screens.geniete.com/docs/screens/) |
|
|
54
|
+
| `voice_say` `voice_speak_url` `voice_listen_link` `voice_voices` | [Voice](https://voice.geniete.com/docs/voice/) |
|
|
55
|
+
| `channel_publish` `channel_ticket` | [Channels](https://channels.geniete.com/docs/channels/) |
|
|
56
|
+
|
|
57
|
+
Keys come from each service's console; one GenieTé account and one
|
|
58
|
+
shared credit balance cover all of them (account.geniete.com).
|
|
59
|
+
|
|
60
|
+
MIT © Genie, Inc.
|
|
61
|
+
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# geniete-mcp
|
|
2
|
+
|
|
3
|
+
One MCP server for the whole [GenieTé](https://www.geniete.com/) suite —
|
|
4
|
+
give an AI agent a live screen, a voice, and a realtime channel with one
|
|
5
|
+
install. Tools appear only for the services you configure.
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
uv tool install geniete-mcp # or: pip install geniete-mcp
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
```json
|
|
12
|
+
{
|
|
13
|
+
"mcpServers": {
|
|
14
|
+
"geniete": {
|
|
15
|
+
"command": "geniete-mcp",
|
|
16
|
+
"env": {
|
|
17
|
+
"AGENTSCREEN_API_KEY": "as_key_...",
|
|
18
|
+
"AGENTSCREEN_SCREEN_ID": "scr_...",
|
|
19
|
+
"VOICE_API_KEY": "vk_...",
|
|
20
|
+
"VOICE_SPEAKER_ID": "spk_...",
|
|
21
|
+
"CHANNELS_TOKEN": "tok_...",
|
|
22
|
+
"CHANNELS_CHANNEL_ID": "chan_..."
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Configure any subset — each pair of env vars switches on its tool group:
|
|
30
|
+
|
|
31
|
+
| Tools | Service |
|
|
32
|
+
|-------|---------|
|
|
33
|
+
| `screen_message` `screen_metric` `screen_terminal` `screen_clear` `screen_link` | [Screens](https://screens.geniete.com/docs/screens/) |
|
|
34
|
+
| `voice_say` `voice_speak_url` `voice_listen_link` `voice_voices` | [Voice](https://voice.geniete.com/docs/voice/) |
|
|
35
|
+
| `channel_publish` `channel_ticket` | [Channels](https://channels.geniete.com/docs/channels/) |
|
|
36
|
+
|
|
37
|
+
Keys come from each service's console; one GenieTé account and one
|
|
38
|
+
shared credit balance cover all of them (account.geniete.com).
|
|
39
|
+
|
|
40
|
+
MIT © Genie, Inc.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Publishes to PyPI as "geniete-mcp"
|
|
2
|
+
# Install: uv tool install geniete-mcp
|
|
3
|
+
#
|
|
4
|
+
# Public package: NO GenieForge dependencies. One MCP server for the whole
|
|
5
|
+
# GenieTé suite — tools appear only for the services you configure via env.
|
|
6
|
+
[project]
|
|
7
|
+
name = "geniete-mcp"
|
|
8
|
+
version = "0.1.0"
|
|
9
|
+
description = "GenieTé MCP server — screens, voice, and channels tools for AI agents"
|
|
10
|
+
readme = "README.md"
|
|
11
|
+
requires-python = ">=3.10"
|
|
12
|
+
license = { text = "MIT" }
|
|
13
|
+
keywords = ["mcp", "agents", "geniete", "tts", "pubsub", "screens"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 4 - Beta",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"License :: OSI Approved :: MIT License",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
]
|
|
20
|
+
dependencies = [
|
|
21
|
+
"mcp>=1.0.0",
|
|
22
|
+
"httpx>=0.27.0",
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
[project.scripts]
|
|
26
|
+
geniete-mcp = "geniete_mcp.server:main"
|
|
27
|
+
|
|
28
|
+
[project.urls]
|
|
29
|
+
Homepage = "https://www.geniete.com/"
|
|
30
|
+
Company = "https://www.genieinc.com/"
|
|
31
|
+
|
|
32
|
+
[build-system]
|
|
33
|
+
requires = ["poetry-core"]
|
|
34
|
+
build-backend = "poetry.core.masonry.api"
|
|
35
|
+
|
|
36
|
+
[tool.poetry]
|
|
37
|
+
name = "geniete-mcp"
|
|
38
|
+
version = "0.1.0"
|
|
39
|
+
description = "GenieTé MCP server"
|
|
40
|
+
authors = ["Genie, Inc. <renat@geniete.com>"]
|
|
41
|
+
packages = [{ include = "geniete_mcp", from = "src" }]
|
|
42
|
+
|
|
43
|
+
[tool.poetry.dependencies]
|
|
44
|
+
python = ">=3.10"
|
|
45
|
+
mcp = ">=1.0.0"
|
|
46
|
+
httpx = ">=0.27.0"
|
|
47
|
+
|
|
48
|
+
[tool.poetry.scripts]
|
|
49
|
+
geniete-mcp = "geniete_mcp.server:main"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
"""
|
|
2
|
+
GenieTé MCP server — one server, the whole suite. Tools appear only for
|
|
3
|
+
the services you configure:
|
|
4
|
+
|
|
5
|
+
{
|
|
6
|
+
"mcpServers": {
|
|
7
|
+
"geniete": {
|
|
8
|
+
"command": "geniete-mcp",
|
|
9
|
+
"env": {
|
|
10
|
+
"AGENTSCREEN_API_KEY": "as_key_...", // screens tools
|
|
11
|
+
"AGENTSCREEN_SCREEN_ID": "scr_...",
|
|
12
|
+
"VOICE_API_KEY": "vk_...", // voice tools
|
|
13
|
+
"VOICE_SPEAKER_ID": "spk_...",
|
|
14
|
+
"CHANNELS_TOKEN": "tok_...", // channels tools
|
|
15
|
+
"CHANNELS_CHANNEL_ID": "chan_..."
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
Tools (per configured service):
|
|
22
|
+
screen_message / screen_metric / screen_terminal / screen_clear / screen_link
|
|
23
|
+
voice_say / voice_speak_url / voice_listen_link / voice_voices
|
|
24
|
+
channel_publish / channel_ticket
|
|
25
|
+
"""
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
import asyncio
|
|
29
|
+
import json
|
|
30
|
+
import os
|
|
31
|
+
import uuid
|
|
32
|
+
from typing import Any
|
|
33
|
+
|
|
34
|
+
import httpx
|
|
35
|
+
from mcp.server import Server
|
|
36
|
+
from mcp.server.stdio import stdio_server
|
|
37
|
+
from mcp.types import TextContent, Tool
|
|
38
|
+
|
|
39
|
+
_TONES = ("info", "success", "warning", "error")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _env(name: str, default: str = "") -> str:
|
|
43
|
+
return os.environ.get(name, default).strip()
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class Config:
|
|
47
|
+
"""Read once; each service group is on when its env pair is set."""
|
|
48
|
+
|
|
49
|
+
def __init__(self) -> None:
|
|
50
|
+
self.screens_key = _env("AGENTSCREEN_API_KEY")
|
|
51
|
+
self.screens_id = _env("AGENTSCREEN_SCREEN_ID")
|
|
52
|
+
self.screens_base = _env("AGENTSCREEN_BASE_URL", "https://screens.geniete.com").rstrip("/")
|
|
53
|
+
self.voice_key = _env("VOICE_API_KEY")
|
|
54
|
+
self.voice_id = _env("VOICE_SPEAKER_ID")
|
|
55
|
+
self.voice_base = _env("VOICE_BASE_URL", "https://voice.geniete.com").rstrip("/")
|
|
56
|
+
self.channels_token = _env("CHANNELS_TOKEN")
|
|
57
|
+
self.channels_id = _env("CHANNELS_CHANNEL_ID")
|
|
58
|
+
self.channels_base = _env("CHANNELS_BASE_URL", "https://channels.geniete.com").rstrip("/")
|
|
59
|
+
|
|
60
|
+
@property
|
|
61
|
+
def screens_on(self) -> bool:
|
|
62
|
+
return bool(self.screens_key and self.screens_id)
|
|
63
|
+
|
|
64
|
+
@property
|
|
65
|
+
def voice_on(self) -> bool:
|
|
66
|
+
return bool(self.voice_key and self.voice_id)
|
|
67
|
+
|
|
68
|
+
@property
|
|
69
|
+
def channels_on(self) -> bool:
|
|
70
|
+
return bool(self.channels_token and self.channels_id)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def available_tools(cfg: Config) -> list[Tool]:
|
|
74
|
+
tools: list[Tool] = []
|
|
75
|
+
if cfg.screens_on:
|
|
76
|
+
tools += [
|
|
77
|
+
Tool(name="screen_message",
|
|
78
|
+
description="Show a status card on the live screen (title, body, tone info|success|warning|error).",
|
|
79
|
+
inputSchema={"type": "object", "properties": {
|
|
80
|
+
"body": {"type": "string"}, "title": {"type": "string"},
|
|
81
|
+
"tone": {"type": "string", "enum": list(_TONES)}},
|
|
82
|
+
"required": ["body"]}),
|
|
83
|
+
Tool(name="screen_metric",
|
|
84
|
+
description="Push a named metric tile (updated in place by name).",
|
|
85
|
+
inputSchema={"type": "object", "properties": {
|
|
86
|
+
"name": {"type": "string"}, "value": {"type": ["number", "string"]},
|
|
87
|
+
"unit": {"type": "string"}}, "required": ["name", "value"]}),
|
|
88
|
+
Tool(name="screen_terminal",
|
|
89
|
+
description="Append text to the screen's terminal block (include your own newlines).",
|
|
90
|
+
inputSchema={"type": "object", "properties": {"text": {"type": "string"}},
|
|
91
|
+
"required": ["text"]}),
|
|
92
|
+
Tool(name="screen_clear",
|
|
93
|
+
description="Clear everything currently shown on the screen.",
|
|
94
|
+
inputSchema={"type": "object", "properties": {}}),
|
|
95
|
+
Tool(name="screen_link",
|
|
96
|
+
description="Mint a one-time viewer URL for the screen (share it; previews can't consume it).",
|
|
97
|
+
inputSchema={"type": "object", "properties": {
|
|
98
|
+
"ttl_seconds": {"type": "integer"}}}),
|
|
99
|
+
]
|
|
100
|
+
if cfg.voice_on:
|
|
101
|
+
tools += [
|
|
102
|
+
Tool(name="voice_say",
|
|
103
|
+
description="Speak text out loud on every open listen page of the speaker (pump mode; nothing returned but the charge).",
|
|
104
|
+
inputSchema={"type": "object", "properties": {
|
|
105
|
+
"text": {"type": "string"}, "voice_id": {"type": "string"}},
|
|
106
|
+
"required": ["text"]}),
|
|
107
|
+
Tool(name="voice_speak_url",
|
|
108
|
+
description="Turn text into speech and return a hosted MP3 link (kept ~1 day, plays anywhere).",
|
|
109
|
+
inputSchema={"type": "object", "properties": {
|
|
110
|
+
"text": {"type": "string"}, "voice_id": {"type": "string"}},
|
|
111
|
+
"required": ["text"]}),
|
|
112
|
+
Tool(name="voice_listen_link",
|
|
113
|
+
description="Mint a standing speaker-page link — open it on any device; every voice_say plays there.",
|
|
114
|
+
inputSchema={"type": "object", "properties": {
|
|
115
|
+
"ttl_seconds": {"type": "integer"}}}),
|
|
116
|
+
Tool(name="voice_voices",
|
|
117
|
+
description="List the available voice ids.",
|
|
118
|
+
inputSchema={"type": "object", "properties": {}}),
|
|
119
|
+
]
|
|
120
|
+
if cfg.channels_on:
|
|
121
|
+
tools += [
|
|
122
|
+
Tool(name="channel_publish",
|
|
123
|
+
description="Publish a JSON payload to the channel; returns how many subscribers received it.",
|
|
124
|
+
inputSchema={"type": "object", "properties": {
|
|
125
|
+
"payload": {"type": "object"}}, "required": ["payload"]}),
|
|
126
|
+
Tool(name="channel_ticket",
|
|
127
|
+
description="Mint a one-time 30-second subscribe credential for a browser client.",
|
|
128
|
+
inputSchema={"type": "object", "properties": {}}),
|
|
129
|
+
]
|
|
130
|
+
return tools
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
# ── HTTP helpers (sync httpx; called via to_thread) ──────────────────
|
|
134
|
+
|
|
135
|
+
def _post(url: str, bearer: str, body: dict, *, transport: Any = None) -> httpx.Response:
|
|
136
|
+
with httpx.Client(timeout=130.0, transport=transport) as c:
|
|
137
|
+
return c.post(url, headers={"Authorization": f"Bearer {bearer}",
|
|
138
|
+
"Content-Type": "application/json"}, json=body)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def _get(url: str, *, transport: Any = None) -> httpx.Response:
|
|
142
|
+
with httpx.Client(timeout=30.0, transport=transport) as c:
|
|
143
|
+
return c.get(url)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def dispatch(cfg: Config, name: str, args: dict, *, transport: Any = None) -> str:
|
|
147
|
+
"""Execute one tool call; returns the text result. Raises nothing —
|
|
148
|
+
API refusals come back as readable text (agents retry better on
|
|
149
|
+
words than on tracebacks)."""
|
|
150
|
+
def screens_event(event: dict) -> str:
|
|
151
|
+
r = _post(f"{cfg.screens_base}/public/api/publish", cfg.screens_key,
|
|
152
|
+
{"screen_id": cfg.screens_id, "event": event}, transport=transport)
|
|
153
|
+
return "published" if r.status_code < 400 else f"refused: {r.text[:300]}"
|
|
154
|
+
|
|
155
|
+
if name == "screen_message":
|
|
156
|
+
tone = args.get("tone", "info")
|
|
157
|
+
return screens_event({"type": "content.message", "title": args.get("title", ""),
|
|
158
|
+
"body": str(args.get("body", ""))[:8192],
|
|
159
|
+
"tone": tone if tone in _TONES else "info"})
|
|
160
|
+
if name == "screen_metric":
|
|
161
|
+
return screens_event({"type": "metric.update", "name": str(args["name"])[:100],
|
|
162
|
+
"value": args["value"], "unit": args.get("unit", "")})
|
|
163
|
+
if name == "screen_terminal":
|
|
164
|
+
return screens_event({"type": "terminal.append", "text": str(args["text"])[:32768]})
|
|
165
|
+
if name == "screen_clear":
|
|
166
|
+
return screens_event({"type": "display.clear"})
|
|
167
|
+
if name == "screen_link":
|
|
168
|
+
r = _post(f"{cfg.screens_base}/public/api/screens/{cfg.screens_id}/tokens",
|
|
169
|
+
cfg.screens_key,
|
|
170
|
+
{"type": "one-time", "ttl_seconds": int(args.get("ttl_seconds", 3600))},
|
|
171
|
+
transport=transport)
|
|
172
|
+
return r.json().get("url", "") if r.status_code < 400 else f"refused: {r.text[:300]}"
|
|
173
|
+
|
|
174
|
+
if name in ("voice_say", "voice_speak_url"):
|
|
175
|
+
body = {"speaker_id": cfg.voice_id, "voice_id": args.get("voice_id", "kanika"),
|
|
176
|
+
"text": str(args.get("text", ""))[:4000],
|
|
177
|
+
"request_id": f"mcp_{uuid.uuid4().hex[:16]}"}
|
|
178
|
+
body["deliver" if name == "voice_speak_url" else "return_audio"] = (
|
|
179
|
+
"url" if name == "voice_speak_url" else False)
|
|
180
|
+
r = _post(f"{cfg.voice_base}/public/api/speak", cfg.voice_key, body, transport=transport)
|
|
181
|
+
if r.status_code >= 400:
|
|
182
|
+
return f"refused: {r.text[:300]}"
|
|
183
|
+
d = r.json()
|
|
184
|
+
return d.get("url") or f"spoken ({d.get('charged_credits', '?')} credits)"
|
|
185
|
+
if name == "voice_listen_link":
|
|
186
|
+
body = {"ttl_seconds": int(args["ttl_seconds"])} if args.get("ttl_seconds") else {}
|
|
187
|
+
r = _post(f"{cfg.voice_base}/public/api/speakers/{cfg.voice_id}/listen-links",
|
|
188
|
+
cfg.voice_key, body, transport=transport)
|
|
189
|
+
return r.json().get("url", "") if r.status_code < 400 else f"refused: {r.text[:300]}"
|
|
190
|
+
if name == "voice_voices":
|
|
191
|
+
r = _get(f"{cfg.voice_base}/public/api/voices", transport=transport)
|
|
192
|
+
if r.status_code >= 400:
|
|
193
|
+
return f"refused: {r.text[:300]}"
|
|
194
|
+
return ", ".join(v.get("id", "") for v in r.json().get("voices", []))
|
|
195
|
+
|
|
196
|
+
if name == "channel_publish":
|
|
197
|
+
r = _post(f"{cfg.channels_base}/api/v1/publish/{cfg.channels_id}",
|
|
198
|
+
cfg.channels_token, dict(args.get("payload") or {}), transport=transport)
|
|
199
|
+
if r.status_code >= 400:
|
|
200
|
+
return f"refused: {r.text[:300]}"
|
|
201
|
+
return f"delivered to {r.json().get('receivers', 0)} subscriber(s)"
|
|
202
|
+
if name == "channel_ticket":
|
|
203
|
+
r = _post(f"{cfg.channels_base}/api/v1/channels/{cfg.channels_id}/tickets",
|
|
204
|
+
cfg.channels_token, {}, transport=transport)
|
|
205
|
+
return json.dumps(r.json()) if r.status_code < 400 else f"refused: {r.text[:300]}"
|
|
206
|
+
|
|
207
|
+
return f"unknown tool: {name}"
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def build_server(cfg: Config) -> Server:
|
|
211
|
+
server = Server("geniete")
|
|
212
|
+
|
|
213
|
+
@server.list_tools()
|
|
214
|
+
async def list_tools() -> list[Tool]:
|
|
215
|
+
return available_tools(cfg)
|
|
216
|
+
|
|
217
|
+
@server.call_tool()
|
|
218
|
+
async def call_tool(name: str, arguments: dict) -> list[TextContent]:
|
|
219
|
+
text = await asyncio.to_thread(dispatch, cfg, name, arguments or {})
|
|
220
|
+
return [TextContent(type="text", text=text)]
|
|
221
|
+
|
|
222
|
+
return server
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def main() -> None:
|
|
226
|
+
cfg = Config()
|
|
227
|
+
server = build_server(cfg)
|
|
228
|
+
|
|
229
|
+
async def run() -> None:
|
|
230
|
+
async with stdio_server() as (read, write):
|
|
231
|
+
await server.run(read, write, server.create_initialization_options())
|
|
232
|
+
|
|
233
|
+
asyncio.run(run())
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
if __name__ == "__main__":
|
|
237
|
+
main()
|