Skip to content

Commit 77ce85e

Browse files
committed
feat: init
0 parents  commit 77ce85e

10 files changed

Lines changed: 2224 additions & 0 deletions

File tree

.gitattributes

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Auto detect text files and perform LF normalization
2+
* text=auto

.github/workflows/release.yml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
name: Release
2+
3+
permissions:
4+
id-token: write
5+
contents: write
6+
7+
on:
8+
push:
9+
tags:
10+
- 'v*'
11+
12+
jobs:
13+
release:
14+
runs-on: ubuntu-latest
15+
steps:
16+
- uses: actions/checkout@v4
17+
with:
18+
fetch-depth: 0
19+
- uses: pnpm/action-setup@v4
20+
- uses: actions/setup-node@v4
21+
with:
22+
node-version: lts/*
23+
registry-url: https://registry.npmjs.org/
24+
25+
- run: pnpm dlx changelogithub
26+
env:
27+
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
28+
29+
- run: pnpm install
30+
- run: pnpm publish --no-git-checks --access public
31+
env:
32+
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
33+
NPM_CONFIG_PROVENANCE: true

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
.DS_Store
2+
node_modules

.vscode/settings.json

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"editor.codeActionsOnSave": {
3+
"source.fixAll.eslint": "explicit",
4+
"quickfix.biome": "explicit",
5+
"source.organizeImports.biome": "explicit"
6+
},
7+
"editor.defaultFormatter": "biomejs.biome",
8+
"[json]": {
9+
"editor.defaultFormatter": "biomejs.biome"
10+
},
11+
"[html]": {
12+
"editor.defaultFormatter": "biomejs.biome"
13+
},
14+
"[javascriptreact]": {
15+
"editor.defaultFormatter": "biomejs.biome"
16+
},
17+
"[javascript]": {
18+
"editor.defaultFormatter": "biomejs.biome"
19+
},
20+
"[typescript]": {
21+
"editor.defaultFormatter": "biomejs.biome"
22+
},
23+
"[typescriptreact]": {
24+
"editor.defaultFormatter": "biomejs.biome"
25+
}
26+
}

LICENSE.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2025 RSS3 Network <https://github.com/rss3-network>
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.

README.md

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# MCP Server for RSS3
2+
3+
An MCP server implementation that integrates the RSS3 API. Query the Open Web like a charm.
4+
5+
## Features
6+
7+
Anything in <https://docs.rss3.io/guide/developer/api>.
8+
9+
## Usage
10+
11+
### Usage with Claude Desktop
12+
13+
Add this to your `claude_desktop_config.json`:
14+
15+
```json
16+
{
17+
"mcpServers": {
18+
"rss3": {
19+
"command": "npx",
20+
"args": [
21+
"mcp-server-rss3"
22+
]
23+
}
24+
}
25+
}
26+
```
27+
28+
### Usage with Cursor
29+
30+
1. Open Settings -> Cursor Settings
31+
2. Click on "MCP"
32+
3. Add new MCP Server with this:
33+
34+
```json
35+
{
36+
"mcpServers": {
37+
"rss3": {
38+
"command": "npx",
39+
"args": [
40+
"mcp-server-rss3"
41+
]
42+
}
43+
}
44+
}
45+
```
46+
47+
### Usage with ChatWise
48+
49+
1. Open Settings -> Tools
50+
2. Add new tool with this command:
51+
52+
```
53+
npx mcp-server-rss3
54+
```

biome.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
3+
"vcs": {
4+
"enabled": true,
5+
"clientKind": "git",
6+
"useIgnoreFile": true
7+
},
8+
"files": {
9+
"ignore": []
10+
}
11+
}

index.js

Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
#!/usr/bin/env node
2+
import "tsx";
3+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
4+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
5+
import {
6+
CallToolRequestSchema,
7+
ListToolsRequestSchema,
8+
} from "@modelcontextprotocol/sdk/types.js";
9+
const { HttpClient, OpenAPIToMCPConverter } = await import(
10+
"openapi-mcp-server"
11+
);
12+
13+
/**
14+
* Error thrown by HttpClient when a request fails
15+
* @extends Error
16+
*/
17+
class HttpClientError extends Error {
18+
constructor(
19+
/** @param {string} message - Error message */
20+
message,
21+
/** @param {number} status - HTTP status code */
22+
status,
23+
/** @param {*} data - Response data */
24+
data,
25+
/** @param {Headers} [headers] - Response headers */
26+
headers,
27+
) {
28+
super(`${status} ${message}`);
29+
this.name = "HttpClientError";
30+
this.status = status;
31+
this.data = data;
32+
this.headers = headers;
33+
}
34+
}
35+
36+
/**
37+
* @typedef {import("openapi-mcp-server").OpenAPIV3.Document} OpenApiSpec
38+
* @type {{ client: HttpClient, spec: OpenApiSpec }[]}
39+
*/
40+
const openApiSpecs = (
41+
await Promise.allSettled([
42+
fetch("https://gi.rss3.io/docs/openapi.json").then(async (res) => {
43+
if (!res.ok) throw new Error(`HTTP error! status: ${res.status}`);
44+
return res.json();
45+
}),
46+
fetch("https://ai.rss3.io/openapi.json").then(async (res) => {
47+
if (!res.ok) throw new Error(`HTTP error! status: ${res.status}`);
48+
return res.json();
49+
}),
50+
]).then((results) => {
51+
return results.map((result) => {
52+
if (result.status === "fulfilled") {
53+
const client = new HttpClient(
54+
{
55+
baseUrl: result.value.servers[0].url,
56+
},
57+
result.value,
58+
);
59+
return {
60+
spec: result.value,
61+
client,
62+
};
63+
}
64+
65+
console.error("Failed to fetch openapi spec", result.reason);
66+
return null;
67+
});
68+
})
69+
).filter(Boolean);
70+
71+
const converterWithClients = openApiSpecs.map((o) => {
72+
const converter = new OpenAPIToMCPConverter(o.spec);
73+
return {
74+
converter,
75+
client: o.client,
76+
};
77+
});
78+
const mcpToolWithClients = converterWithClients.map((cwc) => {
79+
const mcpTools = cwc.converter.convertToMCPTools();
80+
return {
81+
mcpTools,
82+
client: cwc.client,
83+
};
84+
});
85+
86+
// implement server
87+
const server = new Server(
88+
{
89+
name: "rss3",
90+
version: "0.1.0",
91+
},
92+
{
93+
capabilities: {
94+
tools: {},
95+
},
96+
},
97+
);
98+
99+
// handle tool listing
100+
server.setRequestHandler(ListToolsRequestSchema, async () => {
101+
console.error("list tools");
102+
/**
103+
* @typedef {import("@modelcontextprotocol/sdk/types.js").Tool} Tool
104+
* @type {Tool[]}
105+
*/
106+
const tools = [];
107+
108+
for (const mcpToolWithClient of mcpToolWithClients) {
109+
for (const [toolName, def] of Object.entries(
110+
mcpToolWithClient.mcpTools.tools,
111+
)) {
112+
for (const method of def.methods) {
113+
console.error("method", method);
114+
const toolNameWithMethod = `${toolName}-${method.name}`;
115+
const truncatedToolName = toolNameWithMethod.slice(0, 64);
116+
const trimmedDescription = method.description.split("Error")[0].trim();
117+
tools.push({
118+
name: truncatedToolName,
119+
description: trimmedDescription,
120+
inputSchema: {
121+
type: "object",
122+
properties: {},
123+
},
124+
});
125+
}
126+
}
127+
}
128+
129+
tools.unshift({
130+
name: "API-get-input-schema",
131+
description:
132+
"Get the input schema for a given API. We should always use this tool to get the input schema for a given API before calling the API.",
133+
inputSchema: {
134+
type: "object",
135+
properties: {
136+
toolName: {
137+
type: "string",
138+
description: "The name of the tool to get the input schema for",
139+
},
140+
},
141+
},
142+
});
143+
144+
console.error("tools", tools);
145+
146+
return { tools };
147+
});
148+
149+
// handle tool calling
150+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
151+
// console.error("call tool", request.params);
152+
const { name, arguments: params } = request.params;
153+
154+
console.error("name", name);
155+
156+
if (name === "API-get-input-schema") {
157+
for (const mcpToolWithClient of mcpToolWithClients) {
158+
for (const [toolName, def] of Object.entries(
159+
mcpToolWithClient.mcpTools.tools,
160+
)) {
161+
for (const method of def.methods) {
162+
const toolNameWithMethod = `${toolName}-${method.name}`;
163+
const truncatedToolName = toolNameWithMethod.slice(0, 64);
164+
if (truncatedToolName === params.toolName) {
165+
return {
166+
content: [
167+
{ type: "text", text: JSON.stringify(method.inputSchema) },
168+
],
169+
};
170+
}
171+
}
172+
}
173+
}
174+
throw new Error(`Method ${params.toolName} not found`);
175+
}
176+
177+
// find operation
178+
const mcpToolWithClient = mcpToolWithClients.find(
179+
(t) => t.mcpTools.openApiLookup[name],
180+
);
181+
if (!mcpToolWithClient) {
182+
throw new Error(`Method ${name} not found`);
183+
}
184+
185+
const operation = mcpToolWithClient.mcpTools.openApiLookup[name];
186+
187+
// execute
188+
try {
189+
const response = await mcpToolWithClient.client.executeOperation(
190+
operation,
191+
params,
192+
);
193+
return {
194+
content: [
195+
{
196+
type: "text", // currently this is the only type that seems to be used by mcp server
197+
text: JSON.stringify(response.data), // TODO: pass through the http status code text?
198+
},
199+
],
200+
};
201+
} catch (error) {
202+
console.error("Error in tool call", error);
203+
if (error instanceof HttpClientError) {
204+
console.error(
205+
"HttpClientError encountered, returning structured error",
206+
error,
207+
);
208+
const data = error.data?.response?.data ?? error.data ?? {};
209+
return {
210+
content: [
211+
{
212+
type: "text",
213+
text: JSON.stringify({
214+
status: "error", // TODO: get this from http status code?
215+
...(typeof data === "object" ? data : { data: data }),
216+
}),
217+
},
218+
],
219+
};
220+
}
221+
throw error;
222+
}
223+
});
224+
225+
// run server
226+
const transport = new StdioServerTransport();
227+
await server.connect(transport);
228+
console.error("RSS3 MCP Server running on stdio");

0 commit comments

Comments
 (0)