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.
@@ -0,0 +1,4 @@
1
+ dist/
2
+ *.egg-info/
3
+ __pycache__/
4
+ .venv/
@@ -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()