resolve-nvs-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.
- resolve_nvs_mcp-0.1.0/.gitignore +4 -0
- resolve_nvs_mcp-0.1.0/PKG-INFO +54 -0
- resolve_nvs_mcp-0.1.0/README.md +42 -0
- resolve_nvs_mcp-0.1.0/pyproject.toml +25 -0
- resolve_nvs_mcp-0.1.0/src/resolve_mcp/__init__.py +1 -0
- resolve_nvs_mcp-0.1.0/src/resolve_mcp/server.py +168 -0
- resolve_nvs_mcp-0.1.0/uv.lock +900 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: resolve-nvs-mcp
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Resolve AI Copilot MCP server — connects Claude (and other MCP clients) to the Resolve API over stdio.
|
|
5
|
+
Author-email: Resolve <team@jobescape.me>
|
|
6
|
+
License: MIT
|
|
7
|
+
Requires-Python: >=3.10
|
|
8
|
+
Requires-Dist: anyio>=4.0
|
|
9
|
+
Requires-Dist: httpx>=0.27
|
|
10
|
+
Requires-Dist: mcp>=1.27.2
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
|
|
13
|
+
# resolve-nvs-mcp
|
|
14
|
+
|
|
15
|
+
Resolve AI Copilot MCP server. Connects Claude (and any MCP-compatible client) to the [Resolve](https://resolve.nvs.team) support platform over stdio — no repo clone, no database required.
|
|
16
|
+
|
|
17
|
+
## Requirements
|
|
18
|
+
|
|
19
|
+
- [`uv`](https://docs.astral.sh/uv/) installed (for `uvx`)
|
|
20
|
+
- A Resolve API token (issue one at **Settings → API Keys** inside Resolve)
|
|
21
|
+
|
|
22
|
+
## Add to Claude Desktop / Claude Code
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
claude mcp add resolve \
|
|
26
|
+
-e RESOLVE_API_TOKEN=rsv_... \
|
|
27
|
+
-e RESOLVE_API_URL=https://api.resolve.nvs.team \
|
|
28
|
+
-- uvx resolve-nvs-mcp
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Replace `rsv_...` with your actual token. `RESOLVE_API_URL` is optional and defaults to `https://api.resolve.nvs.team`.
|
|
32
|
+
|
|
33
|
+
## Local development
|
|
34
|
+
|
|
35
|
+
Point the server at a locally running API instance:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
RESOLVE_API_TOKEN=rsv_... RESOLVE_API_URL=http://localhost:8080 uvx resolve-nvs-mcp
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Environment variables
|
|
42
|
+
|
|
43
|
+
| Variable | Required | Default | Description |
|
|
44
|
+
|---|---|---|---|
|
|
45
|
+
| `RESOLVE_API_TOKEN` | Yes | — | Plaintext API token (`rsv_...` prefix). Issue at `/settings/api-keys`. |
|
|
46
|
+
| `RESOLVE_API_URL` | No | `https://api.resolve.nvs.team` | Base URL for the Resolve API. Use `http://localhost:8080` for local dev. |
|
|
47
|
+
|
|
48
|
+
## How it works
|
|
49
|
+
|
|
50
|
+
On startup the server fetches the available tool list from the API (`GET /api/v1/mcp/tools`), validates your token, and then serves those tools over the MCP stdio protocol. Every tool call is forwarded to the API (`POST /api/v1/mcp/tools/{name}`) — no local state or database access is involved.
|
|
51
|
+
|
|
52
|
+
## License
|
|
53
|
+
|
|
54
|
+
MIT
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# resolve-nvs-mcp
|
|
2
|
+
|
|
3
|
+
Resolve AI Copilot MCP server. Connects Claude (and any MCP-compatible client) to the [Resolve](https://resolve.nvs.team) support platform over stdio — no repo clone, no database required.
|
|
4
|
+
|
|
5
|
+
## Requirements
|
|
6
|
+
|
|
7
|
+
- [`uv`](https://docs.astral.sh/uv/) installed (for `uvx`)
|
|
8
|
+
- A Resolve API token (issue one at **Settings → API Keys** inside Resolve)
|
|
9
|
+
|
|
10
|
+
## Add to Claude Desktop / Claude Code
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
claude mcp add resolve \
|
|
14
|
+
-e RESOLVE_API_TOKEN=rsv_... \
|
|
15
|
+
-e RESOLVE_API_URL=https://api.resolve.nvs.team \
|
|
16
|
+
-- uvx resolve-nvs-mcp
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Replace `rsv_...` with your actual token. `RESOLVE_API_URL` is optional and defaults to `https://api.resolve.nvs.team`.
|
|
20
|
+
|
|
21
|
+
## Local development
|
|
22
|
+
|
|
23
|
+
Point the server at a locally running API instance:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
RESOLVE_API_TOKEN=rsv_... RESOLVE_API_URL=http://localhost:8080 uvx resolve-nvs-mcp
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Environment variables
|
|
30
|
+
|
|
31
|
+
| Variable | Required | Default | Description |
|
|
32
|
+
|---|---|---|---|
|
|
33
|
+
| `RESOLVE_API_TOKEN` | Yes | — | Plaintext API token (`rsv_...` prefix). Issue at `/settings/api-keys`. |
|
|
34
|
+
| `RESOLVE_API_URL` | No | `https://api.resolve.nvs.team` | Base URL for the Resolve API. Use `http://localhost:8080` for local dev. |
|
|
35
|
+
|
|
36
|
+
## How it works
|
|
37
|
+
|
|
38
|
+
On startup the server fetches the available tool list from the API (`GET /api/v1/mcp/tools`), validates your token, and then serves those tools over the MCP stdio protocol. Every tool call is forwarded to the API (`POST /api/v1/mcp/tools/{name}`) — no local state or database access is involved.
|
|
39
|
+
|
|
40
|
+
## License
|
|
41
|
+
|
|
42
|
+
MIT
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "resolve-nvs-mcp"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Resolve AI Copilot MCP server — connects Claude (and other MCP clients) to the Resolve API over stdio."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Resolve", email = "team@jobescape.me" },
|
|
14
|
+
]
|
|
15
|
+
dependencies = [
|
|
16
|
+
"mcp>=1.27.2",
|
|
17
|
+
"httpx>=0.27",
|
|
18
|
+
"anyio>=4.0",
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
[project.scripts]
|
|
22
|
+
resolve-nvs-mcp = "resolve_mcp.server:main"
|
|
23
|
+
|
|
24
|
+
[tool.hatch.build.targets.wheel]
|
|
25
|
+
packages = ["src/resolve_mcp"]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
"""Stdio MCP server — authenticates via RESOLVE_API_TOKEN, proxies tool calls to the API.
|
|
2
|
+
|
|
3
|
+
Run as:
|
|
4
|
+
RESOLVE_API_TOKEN=rsv_... uvx resolve-mcp
|
|
5
|
+
|
|
6
|
+
The server reads the token and API base URL from the environment, validates the
|
|
7
|
+
token by fetching the tool list from the API, and then serves tool calls over
|
|
8
|
+
stdio using the low-level mcp.server.Server API. All data access goes through
|
|
9
|
+
the HTTPS API — no database or Redis connection is required.
|
|
10
|
+
|
|
11
|
+
Environment variables:
|
|
12
|
+
RESOLVE_API_TOKEN Required. The plaintext API token (prefix rsv_...).
|
|
13
|
+
RESOLVE_API_URL Optional. Base URL for the Resolve API.
|
|
14
|
+
Defaults to "https://api.resolve.nvs.team".
|
|
15
|
+
"""
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import json
|
|
19
|
+
import os
|
|
20
|
+
import sys
|
|
21
|
+
|
|
22
|
+
import anyio
|
|
23
|
+
import httpx
|
|
24
|
+
import mcp.server.stdio
|
|
25
|
+
import mcp.types as types
|
|
26
|
+
from mcp.server.lowlevel import Server
|
|
27
|
+
from mcp.server.models import InitializationOptions
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# ---------------------------------------------------------------------------
|
|
31
|
+
# Small, testable HTTP helpers
|
|
32
|
+
# ---------------------------------------------------------------------------
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
async def fetch_tool_specs(base_url: str, token: str) -> list[dict]:
|
|
36
|
+
"""GET {base_url}/api/v1/mcp/tools and return the list of tool spec dicts.
|
|
37
|
+
|
|
38
|
+
Raises RuntimeError on 401, connection errors, or unexpected responses.
|
|
39
|
+
"""
|
|
40
|
+
url = f"{base_url}/api/v1/mcp/tools"
|
|
41
|
+
try:
|
|
42
|
+
async with httpx.AsyncClient() as client:
|
|
43
|
+
resp = await client.get(
|
|
44
|
+
url,
|
|
45
|
+
headers={"Authorization": f"Bearer {token}"},
|
|
46
|
+
timeout=30.0,
|
|
47
|
+
)
|
|
48
|
+
except httpx.ConnectError as exc:
|
|
49
|
+
raise RuntimeError(
|
|
50
|
+
f"Could not connect to Resolve API at {base_url}: {exc}"
|
|
51
|
+
) from exc
|
|
52
|
+
except httpx.RequestError as exc:
|
|
53
|
+
raise RuntimeError(
|
|
54
|
+
f"HTTP error fetching tool specs from {url}: {exc}"
|
|
55
|
+
) from exc
|
|
56
|
+
|
|
57
|
+
if resp.status_code == 401:
|
|
58
|
+
raise RuntimeError(
|
|
59
|
+
"RESOLVE_API_TOKEN is invalid or has been revoked (401 from API)."
|
|
60
|
+
)
|
|
61
|
+
if not resp.is_success:
|
|
62
|
+
raise RuntimeError(
|
|
63
|
+
f"Unexpected response from {url}: {resp.status_code} {resp.text[:200]}"
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
data = resp.json()
|
|
67
|
+
return data["tools"]
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
async def call_tool_http(
|
|
71
|
+
base_url: str, token: str, name: str, args: dict
|
|
72
|
+
) -> dict:
|
|
73
|
+
"""POST {base_url}/api/v1/mcp/tools/{name} with JSON args and return the result dict.
|
|
74
|
+
|
|
75
|
+
Raises RuntimeError on non-2xx responses (with the response body included).
|
|
76
|
+
"""
|
|
77
|
+
url = f"{base_url}/api/v1/mcp/tools/{name}"
|
|
78
|
+
try:
|
|
79
|
+
async with httpx.AsyncClient() as client:
|
|
80
|
+
resp = await client.post(
|
|
81
|
+
url,
|
|
82
|
+
json=args,
|
|
83
|
+
headers={"Authorization": f"Bearer {token}"},
|
|
84
|
+
timeout=120.0,
|
|
85
|
+
)
|
|
86
|
+
except httpx.RequestError as exc:
|
|
87
|
+
raise RuntimeError(
|
|
88
|
+
f"HTTP error calling tool {name!r} at {url}: {exc}"
|
|
89
|
+
) from exc
|
|
90
|
+
|
|
91
|
+
if not resp.is_success:
|
|
92
|
+
detail = resp.text[:400]
|
|
93
|
+
raise RuntimeError(
|
|
94
|
+
f"API returned {resp.status_code} for tool {name!r}: {detail}"
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
return resp.json()
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
# ---------------------------------------------------------------------------
|
|
101
|
+
# Main entry point
|
|
102
|
+
# ---------------------------------------------------------------------------
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
async def _async_main() -> None:
|
|
106
|
+
token = os.environ.get("RESOLVE_API_TOKEN", "").strip()
|
|
107
|
+
if not token:
|
|
108
|
+
print(
|
|
109
|
+
"ERROR: RESOLVE_API_TOKEN environment variable is not set.",
|
|
110
|
+
file=sys.stderr,
|
|
111
|
+
)
|
|
112
|
+
sys.exit(1)
|
|
113
|
+
|
|
114
|
+
base_url = os.environ.get("RESOLVE_API_URL", "https://api.resolve.nvs.team").rstrip("/")
|
|
115
|
+
|
|
116
|
+
# -------------------------------------------------------------------------
|
|
117
|
+
# Validate the token and fetch tool specs in one shot (startup HTTP call).
|
|
118
|
+
# -------------------------------------------------------------------------
|
|
119
|
+
try:
|
|
120
|
+
tool_specs = await fetch_tool_specs(base_url, token)
|
|
121
|
+
except RuntimeError as exc:
|
|
122
|
+
print(f"ERROR: {exc}", file=sys.stderr)
|
|
123
|
+
sys.exit(1)
|
|
124
|
+
|
|
125
|
+
print(
|
|
126
|
+
f"resolve-copilot MCP server: connected to {base_url} ({len(tool_specs)} tools)",
|
|
127
|
+
file=sys.stderr,
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
# -------------------------------------------------------------------------
|
|
131
|
+
# Build the MCP server and register handlers.
|
|
132
|
+
# -------------------------------------------------------------------------
|
|
133
|
+
server = Server("resolve-copilot")
|
|
134
|
+
|
|
135
|
+
@server.list_tools()
|
|
136
|
+
async def handle_list_tools() -> list[types.Tool]:
|
|
137
|
+
return [
|
|
138
|
+
types.Tool(
|
|
139
|
+
name=spec["name"],
|
|
140
|
+
description=spec["description"],
|
|
141
|
+
inputSchema=spec["inputSchema"],
|
|
142
|
+
)
|
|
143
|
+
for spec in tool_specs
|
|
144
|
+
]
|
|
145
|
+
|
|
146
|
+
@server.call_tool()
|
|
147
|
+
async def handle_call_tool(
|
|
148
|
+
name: str, arguments: dict | None
|
|
149
|
+
) -> list[types.TextContent]:
|
|
150
|
+
result = await call_tool_http(base_url, token, name, arguments or {})
|
|
151
|
+
return [types.TextContent(type="text", text=json.dumps(result, indent=2))]
|
|
152
|
+
|
|
153
|
+
# -------------------------------------------------------------------------
|
|
154
|
+
# Run over stdio.
|
|
155
|
+
# -------------------------------------------------------------------------
|
|
156
|
+
init_options: InitializationOptions = server.create_initialization_options()
|
|
157
|
+
|
|
158
|
+
async with mcp.server.stdio.stdio_server() as (read_stream, write_stream):
|
|
159
|
+
await server.run(read_stream, write_stream, init_options)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def main() -> None:
|
|
163
|
+
"""Console entry point — invoked by `resolve-mcp` / `uvx resolve-mcp`."""
|
|
164
|
+
anyio.run(_async_main)
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
if __name__ == "__main__":
|
|
168
|
+
main()
|