Merge branch 'main' into add-jest-setup
This commit is contained in:
commit
e84c79da2d
98 changed files with 1356 additions and 10109 deletions
|
|
@ -1,22 +0,0 @@
|
|||
FROM node:22.12-alpine AS builder
|
||||
|
||||
COPY src/aws-kb-retrieval-server /app
|
||||
COPY tsconfig.json /tsconfig.json
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
RUN --mount=type=cache,target=/root/.npm npm install
|
||||
|
||||
FROM node:22-alpine AS release
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY --from=builder /app/dist /app/dist
|
||||
COPY --from=builder /app/package.json /app/package.json
|
||||
COPY --from=builder /app/package-lock.json /app/package-lock.json
|
||||
|
||||
ENV NODE_ENV=production
|
||||
|
||||
RUN npm ci --ignore-scripts --omit-dev
|
||||
|
||||
ENTRYPOINT ["node", "dist/index.js"]
|
||||
|
|
@ -1,79 +0,0 @@
|
|||
# AWS Knowledge Base Retrieval MCP Server
|
||||
|
||||
An MCP server implementation for retrieving information from the AWS Knowledge Base using the Bedrock Agent Runtime.
|
||||
|
||||
## Features
|
||||
|
||||
- **RAG (Retrieval-Augmented Generation)**: Retrieve context from the AWS Knowledge Base based on a query and a Knowledge Base ID.
|
||||
- **Supports multiple results retrieval**: Option to retrieve a customizable number of results.
|
||||
|
||||
## Tools
|
||||
|
||||
- **retrieve_from_aws_kb**
|
||||
- Perform retrieval operations using the AWS Knowledge Base.
|
||||
- Inputs:
|
||||
- `query` (string): The search query for retrieval.
|
||||
- `knowledgeBaseId` (string): The ID of the AWS Knowledge Base.
|
||||
- `n` (number, optional): Number of results to retrieve (default: 3).
|
||||
|
||||
## Configuration
|
||||
|
||||
### Setting up AWS Credentials
|
||||
|
||||
1. Obtain AWS access key ID, secret access key, and region from the AWS Management Console.
|
||||
2. Ensure these credentials have appropriate permissions for Bedrock Agent Runtime operations.
|
||||
|
||||
### Usage with Claude Desktop
|
||||
|
||||
Add this to your `claude_desktop_config.json`:
|
||||
|
||||
#### Docker
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"aws-kb-retrieval": {
|
||||
"command": "docker",
|
||||
"args": [ "run", "-i", "--rm", "-e", "AWS_ACCESS_KEY_ID", "-e", "AWS_SECRET_ACCESS_KEY", "-e", "AWS_REGION", "mcp/aws-kb-retrieval-server" ],
|
||||
"env": {
|
||||
"AWS_ACCESS_KEY_ID": "YOUR_ACCESS_KEY_HERE",
|
||||
"AWS_SECRET_ACCESS_KEY": "YOUR_SECRET_ACCESS_KEY_HERE",
|
||||
"AWS_REGION": "YOUR_AWS_REGION_HERE"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"aws-kb-retrieval": {
|
||||
"command": "npx",
|
||||
"args": [
|
||||
"-y",
|
||||
"@modelcontextprotocol/server-aws-kb-retrieval"
|
||||
],
|
||||
"env": {
|
||||
"AWS_ACCESS_KEY_ID": "YOUR_ACCESS_KEY_HERE",
|
||||
"AWS_SECRET_ACCESS_KEY": "YOUR_SECRET_ACCESS_KEY_HERE",
|
||||
"AWS_REGION": "YOUR_AWS_REGION_HERE"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Building
|
||||
|
||||
Docker:
|
||||
|
||||
```sh
|
||||
docker build -t mcp/aws-kb-retrieval -f src/aws-kb-retrieval-server/Dockerfile .
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
This MCP server is licensed under the MIT License. This means you are free to use, modify, and distribute the software, subject to the terms and conditions of the MIT License. For more details, please see the LICENSE file in the project repository.
|
||||
|
||||
This README assumes that your server package is named `@modelcontextprotocol/server-aws-kb-retrieval`. Adjust the package name and installation details if they differ in your setup. Also, ensure that your server script is correctly built and that all dependencies are properly managed in your `package.json`.
|
||||
|
|
@ -1,166 +0,0 @@
|
|||
#!/usr/bin/env node
|
||||
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
||||
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
||||
import {
|
||||
CallToolRequestSchema,
|
||||
ListToolsRequestSchema,
|
||||
Tool,
|
||||
} from "@modelcontextprotocol/sdk/types.js";
|
||||
import {
|
||||
BedrockAgentRuntimeClient,
|
||||
RetrieveCommand,
|
||||
RetrieveCommandInput,
|
||||
} from "@aws-sdk/client-bedrock-agent-runtime";
|
||||
|
||||
// AWS client initialization
|
||||
const bedrockClient = new BedrockAgentRuntimeClient({
|
||||
region: process.env.AWS_REGION,
|
||||
credentials: {
|
||||
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
|
||||
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
|
||||
},
|
||||
});
|
||||
|
||||
interface RAGSource {
|
||||
id: string;
|
||||
fileName: string;
|
||||
snippet: string;
|
||||
score: number;
|
||||
}
|
||||
|
||||
async function retrieveContext(
|
||||
query: string,
|
||||
knowledgeBaseId: string,
|
||||
n: number = 3
|
||||
): Promise<{
|
||||
context: string;
|
||||
isRagWorking: boolean;
|
||||
ragSources: RAGSource[];
|
||||
}> {
|
||||
try {
|
||||
if (!knowledgeBaseId) {
|
||||
console.error("knowledgeBaseId is not provided");
|
||||
return {
|
||||
context: "",
|
||||
isRagWorking: false,
|
||||
ragSources: [],
|
||||
};
|
||||
}
|
||||
|
||||
const input: RetrieveCommandInput = {
|
||||
knowledgeBaseId: knowledgeBaseId,
|
||||
retrievalQuery: { text: query },
|
||||
retrievalConfiguration: {
|
||||
vectorSearchConfiguration: { numberOfResults: n },
|
||||
},
|
||||
};
|
||||
|
||||
const command = new RetrieveCommand(input);
|
||||
const response = await bedrockClient.send(command);
|
||||
const rawResults = response?.retrievalResults || [];
|
||||
const ragSources: RAGSource[] = rawResults
|
||||
.filter((res) => res?.content?.text)
|
||||
.map((result, index) => {
|
||||
const uri = result?.location?.s3Location?.uri || "";
|
||||
const fileName = uri.split("/").pop() || `Source-${index}.txt`;
|
||||
return {
|
||||
id: (result.metadata?.["x-amz-bedrock-kb-chunk-id"] as string) || `chunk-${index}`,
|
||||
fileName: fileName.replace(/_/g, " ").replace(".txt", ""),
|
||||
snippet: result.content?.text || "",
|
||||
score: (result.score as number) || 0,
|
||||
};
|
||||
})
|
||||
.slice(0, 3);
|
||||
|
||||
const context = rawResults
|
||||
.filter((res): res is { content: { text: string } } => res?.content?.text !== undefined)
|
||||
.map(res => res.content.text)
|
||||
.join("\n\n");
|
||||
|
||||
return {
|
||||
context,
|
||||
isRagWorking: true,
|
||||
ragSources,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("RAG Error:", error);
|
||||
return { context: "", isRagWorking: false, ragSources: [] };
|
||||
}
|
||||
}
|
||||
|
||||
// Define the retrieval tool
|
||||
const RETRIEVAL_TOOL: Tool = {
|
||||
name: "retrieve_from_aws_kb",
|
||||
description: "Performs retrieval from the AWS Knowledge Base using the provided query and Knowledge Base ID.",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
query: { type: "string", description: "The query to perform retrieval on" },
|
||||
knowledgeBaseId: { type: "string", description: "The ID of the AWS Knowledge Base" },
|
||||
n: { type: "number", default: 3, description: "Number of results to retrieve" },
|
||||
},
|
||||
required: ["query", "knowledgeBaseId"],
|
||||
},
|
||||
};
|
||||
|
||||
// Server setup
|
||||
const server = new Server(
|
||||
{
|
||||
name: "aws-kb-retrieval-server",
|
||||
version: "0.2.0",
|
||||
},
|
||||
{
|
||||
capabilities: {
|
||||
tools: {},
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
// Request handlers
|
||||
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
||||
tools: [RETRIEVAL_TOOL],
|
||||
}));
|
||||
|
||||
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
||||
const { name, arguments: args } = request.params;
|
||||
|
||||
if (name === "retrieve_from_aws_kb") {
|
||||
const { query, knowledgeBaseId, n = 3 } = args as Record<string, any>;
|
||||
try {
|
||||
const result = await retrieveContext(query, knowledgeBaseId, n);
|
||||
if (result.isRagWorking) {
|
||||
return {
|
||||
content: [
|
||||
{ type: "text", text: `Context: ${result.context}` },
|
||||
{ type: "text", text: `RAG Sources: ${JSON.stringify(result.ragSources)}` },
|
||||
],
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
content: [{ type: "text", text: "Retrieval failed or returned no results." }],
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
return {
|
||||
content: [{ type: "text", text: `Error occurred: ${error}` }],
|
||||
};
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
content: [{ type: "text", text: `Unknown tool: ${name}` }],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
// Server startup
|
||||
async function runServer() {
|
||||
const transport = new StdioServerTransport();
|
||||
await server.connect(transport);
|
||||
console.error("AWS KB Retrieval Server running on stdio");
|
||||
}
|
||||
|
||||
runServer().catch((error) => {
|
||||
console.error("Fatal error running server:", error);
|
||||
process.exit(1);
|
||||
});
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
{
|
||||
"name": "@modelcontextprotocol/server-aws-kb-retrieval",
|
||||
"version": "0.6.2",
|
||||
"description": "MCP server for AWS Knowledge Base retrieval using Bedrock Agent Runtime",
|
||||
"license": "MIT",
|
||||
"author": "Anthropic, PBC (https://anthropic.com)",
|
||||
"homepage": "https://modelcontextprotocol.io",
|
||||
"bugs": "https://github.com/modelcontextprotocol/servers/issues",
|
||||
"type": "module",
|
||||
"bin": {
|
||||
"mcp-server-aws-kb-retrieval": "dist/index.js"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "tsc && shx chmod +x dist/*.js",
|
||||
"prepare": "npm run build",
|
||||
"watch": "tsc --watch"
|
||||
},
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "0.5.0",
|
||||
"@aws-sdk/client-bedrock-agent-runtime": "^3.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22",
|
||||
"shx": "^0.3.4",
|
||||
"typescript": "^5.6.2"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./dist",
|
||||
"rootDir": ".",
|
||||
"composite": true,
|
||||
"incremental": true,
|
||||
"tsBuildInfoFile": "./dist/.tsbuildinfo"
|
||||
},
|
||||
"include": [
|
||||
"./**/*.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"dist"
|
||||
]
|
||||
}
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
FROM node:22.12-alpine AS builder
|
||||
|
||||
# Must be entire project because `prepare` script is run during `npm install` and requires all files.
|
||||
COPY src/brave-search /app
|
||||
COPY tsconfig.json /tsconfig.json
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
RUN --mount=type=cache,target=/root/.npm npm install
|
||||
|
||||
FROM node:22-alpine AS release
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY --from=builder /app/dist /app/dist
|
||||
COPY --from=builder /app/package.json /app/package.json
|
||||
COPY --from=builder /app/package-lock.json /app/package-lock.json
|
||||
|
||||
ENV NODE_ENV=production
|
||||
|
||||
RUN npm ci --ignore-scripts --omit-dev
|
||||
|
||||
ENTRYPOINT ["node", "dist/index.js"]
|
||||
|
|
@ -1,92 +0,0 @@
|
|||
# Brave Search MCP Server
|
||||
|
||||
An MCP server implementation that integrates the Brave Search API, providing both web and local search capabilities.
|
||||
|
||||
## Features
|
||||
|
||||
- **Web Search**: General queries, news, articles, with pagination and freshness controls
|
||||
- **Local Search**: Find businesses, restaurants, and services with detailed information
|
||||
- **Flexible Filtering**: Control result types, safety levels, and content freshness
|
||||
- **Smart Fallbacks**: Local search automatically falls back to web when no results are found
|
||||
|
||||
## Tools
|
||||
|
||||
- **brave_web_search**
|
||||
- Execute web searches with pagination and filtering
|
||||
- Inputs:
|
||||
- `query` (string): Search terms
|
||||
- `count` (number, optional): Results per page (max 20)
|
||||
- `offset` (number, optional): Pagination offset (max 9)
|
||||
|
||||
- **brave_local_search**
|
||||
- Search for local businesses and services
|
||||
- Inputs:
|
||||
- `query` (string): Local search terms
|
||||
- `count` (number, optional): Number of results (max 20)
|
||||
- Automatically falls back to web search if no local results found
|
||||
|
||||
|
||||
## Configuration
|
||||
|
||||
### Getting an API Key
|
||||
1. Sign up for a [Brave Search API account](https://brave.com/search/api/)
|
||||
2. Choose a plan (Free tier available with 2,000 queries/month)
|
||||
3. Generate your API key [from the developer dashboard](https://api.search.brave.com/app/keys)
|
||||
|
||||
### Usage with Claude Desktop
|
||||
Add this to your `claude_desktop_config.json`:
|
||||
|
||||
### Docker
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"brave-search": {
|
||||
"command": "docker",
|
||||
"args": [
|
||||
"run",
|
||||
"-i",
|
||||
"--rm",
|
||||
"-e",
|
||||
"BRAVE_API_KEY",
|
||||
"mcp/brave-search"
|
||||
],
|
||||
"env": {
|
||||
"BRAVE_API_KEY": "YOUR_API_KEY_HERE"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### NPX
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"brave-search": {
|
||||
"command": "npx",
|
||||
"args": [
|
||||
"-y",
|
||||
"@modelcontextprotocol/server-brave-search"
|
||||
],
|
||||
"env": {
|
||||
"BRAVE_API_KEY": "YOUR_API_KEY_HERE"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## Build
|
||||
|
||||
Docker build:
|
||||
|
||||
```bash
|
||||
docker build -t mcp/brave-search:latest -f src/brave-search/Dockerfile .
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
This MCP server is licensed under the MIT License. This means you are free to use, modify, and distribute the software, subject to the terms and conditions of the MIT License. For more details, please see the LICENSE file in the project repository.
|
||||
|
|
@ -1,376 +0,0 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
||||
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
||||
import {
|
||||
CallToolRequestSchema,
|
||||
ListToolsRequestSchema,
|
||||
Tool,
|
||||
} from "@modelcontextprotocol/sdk/types.js";
|
||||
|
||||
const WEB_SEARCH_TOOL: Tool = {
|
||||
name: "brave_web_search",
|
||||
description:
|
||||
"Performs a web search using the Brave Search API, ideal for general queries, news, articles, and online content. " +
|
||||
"Use this for broad information gathering, recent events, or when you need diverse web sources. " +
|
||||
"Supports pagination, content filtering, and freshness controls. " +
|
||||
"Maximum 20 results per request, with offset for pagination. ",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
query: {
|
||||
type: "string",
|
||||
description: "Search query (max 400 chars, 50 words)"
|
||||
},
|
||||
count: {
|
||||
type: "number",
|
||||
description: "Number of results (1-20, default 10)",
|
||||
default: 10
|
||||
},
|
||||
offset: {
|
||||
type: "number",
|
||||
description: "Pagination offset (max 9, default 0)",
|
||||
default: 0
|
||||
},
|
||||
},
|
||||
required: ["query"],
|
||||
},
|
||||
};
|
||||
|
||||
const LOCAL_SEARCH_TOOL: Tool = {
|
||||
name: "brave_local_search",
|
||||
description:
|
||||
"Searches for local businesses and places using Brave's Local Search API. " +
|
||||
"Best for queries related to physical locations, businesses, restaurants, services, etc. " +
|
||||
"Returns detailed information including:\n" +
|
||||
"- Business names and addresses\n" +
|
||||
"- Ratings and review counts\n" +
|
||||
"- Phone numbers and opening hours\n" +
|
||||
"Use this when the query implies 'near me' or mentions specific locations. " +
|
||||
"Automatically falls back to web search if no local results are found.",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
query: {
|
||||
type: "string",
|
||||
description: "Local search query (e.g. 'pizza near Central Park')"
|
||||
},
|
||||
count: {
|
||||
type: "number",
|
||||
description: "Number of results (1-20, default 5)",
|
||||
default: 5
|
||||
},
|
||||
},
|
||||
required: ["query"]
|
||||
}
|
||||
};
|
||||
|
||||
// Server implementation
|
||||
const server = new Server(
|
||||
{
|
||||
name: "example-servers/brave-search",
|
||||
version: "0.1.0",
|
||||
},
|
||||
{
|
||||
capabilities: {
|
||||
tools: {},
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
// Check for API key
|
||||
const BRAVE_API_KEY = process.env.BRAVE_API_KEY!;
|
||||
if (!BRAVE_API_KEY) {
|
||||
console.error("Error: BRAVE_API_KEY environment variable is required");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const RATE_LIMIT = {
|
||||
perSecond: 1,
|
||||
perMonth: 15000
|
||||
};
|
||||
|
||||
let requestCount = {
|
||||
second: 0,
|
||||
month: 0,
|
||||
lastReset: Date.now()
|
||||
};
|
||||
|
||||
function checkRateLimit() {
|
||||
const now = Date.now();
|
||||
if (now - requestCount.lastReset > 1000) {
|
||||
requestCount.second = 0;
|
||||
requestCount.lastReset = now;
|
||||
}
|
||||
if (requestCount.second >= RATE_LIMIT.perSecond ||
|
||||
requestCount.month >= RATE_LIMIT.perMonth) {
|
||||
throw new Error('Rate limit exceeded');
|
||||
}
|
||||
requestCount.second++;
|
||||
requestCount.month++;
|
||||
}
|
||||
|
||||
interface BraveWeb {
|
||||
web?: {
|
||||
results?: Array<{
|
||||
title: string;
|
||||
description: string;
|
||||
url: string;
|
||||
language?: string;
|
||||
published?: string;
|
||||
rank?: number;
|
||||
}>;
|
||||
};
|
||||
locations?: {
|
||||
results?: Array<{
|
||||
id: string; // Required by API
|
||||
title?: string;
|
||||
}>;
|
||||
};
|
||||
}
|
||||
|
||||
interface BraveLocation {
|
||||
id: string;
|
||||
name: string;
|
||||
address: {
|
||||
streetAddress?: string;
|
||||
addressLocality?: string;
|
||||
addressRegion?: string;
|
||||
postalCode?: string;
|
||||
};
|
||||
coordinates?: {
|
||||
latitude: number;
|
||||
longitude: number;
|
||||
};
|
||||
phone?: string;
|
||||
rating?: {
|
||||
ratingValue?: number;
|
||||
ratingCount?: number;
|
||||
};
|
||||
openingHours?: string[];
|
||||
priceRange?: string;
|
||||
}
|
||||
|
||||
interface BravePoiResponse {
|
||||
results: BraveLocation[];
|
||||
}
|
||||
|
||||
interface BraveDescription {
|
||||
descriptions: {[id: string]: string};
|
||||
}
|
||||
|
||||
function isBraveWebSearchArgs(args: unknown): args is { query: string; count?: number } {
|
||||
return (
|
||||
typeof args === "object" &&
|
||||
args !== null &&
|
||||
"query" in args &&
|
||||
typeof (args as { query: string }).query === "string"
|
||||
);
|
||||
}
|
||||
|
||||
function isBraveLocalSearchArgs(args: unknown): args is { query: string; count?: number } {
|
||||
return (
|
||||
typeof args === "object" &&
|
||||
args !== null &&
|
||||
"query" in args &&
|
||||
typeof (args as { query: string }).query === "string"
|
||||
);
|
||||
}
|
||||
|
||||
async function performWebSearch(query: string, count: number = 10, offset: number = 0) {
|
||||
checkRateLimit();
|
||||
const url = new URL('https://api.search.brave.com/res/v1/web/search');
|
||||
url.searchParams.set('q', query);
|
||||
url.searchParams.set('count', Math.min(count, 20).toString()); // API limit
|
||||
url.searchParams.set('offset', offset.toString());
|
||||
|
||||
const response = await fetch(url, {
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Accept-Encoding': 'gzip',
|
||||
'X-Subscription-Token': BRAVE_API_KEY
|
||||
}
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Brave API error: ${response.status} ${response.statusText}\n${await response.text()}`);
|
||||
}
|
||||
|
||||
const data = await response.json() as BraveWeb;
|
||||
|
||||
// Extract just web results
|
||||
const results = (data.web?.results || []).map(result => ({
|
||||
title: result.title || '',
|
||||
description: result.description || '',
|
||||
url: result.url || ''
|
||||
}));
|
||||
|
||||
return results.map(r =>
|
||||
`Title: ${r.title}\nDescription: ${r.description}\nURL: ${r.url}`
|
||||
).join('\n\n');
|
||||
}
|
||||
|
||||
async function performLocalSearch(query: string, count: number = 5) {
|
||||
checkRateLimit();
|
||||
// Initial search to get location IDs
|
||||
const webUrl = new URL('https://api.search.brave.com/res/v1/web/search');
|
||||
webUrl.searchParams.set('q', query);
|
||||
webUrl.searchParams.set('search_lang', 'en');
|
||||
webUrl.searchParams.set('result_filter', 'locations');
|
||||
webUrl.searchParams.set('count', Math.min(count, 20).toString());
|
||||
|
||||
const webResponse = await fetch(webUrl, {
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Accept-Encoding': 'gzip',
|
||||
'X-Subscription-Token': BRAVE_API_KEY
|
||||
}
|
||||
});
|
||||
|
||||
if (!webResponse.ok) {
|
||||
throw new Error(`Brave API error: ${webResponse.status} ${webResponse.statusText}\n${await webResponse.text()}`);
|
||||
}
|
||||
|
||||
const webData = await webResponse.json() as BraveWeb;
|
||||
const locationIds = webData.locations?.results?.filter((r): r is {id: string; title?: string} => r.id != null).map(r => r.id) || [];
|
||||
|
||||
if (locationIds.length === 0) {
|
||||
return performWebSearch(query, count); // Fallback to web search
|
||||
}
|
||||
|
||||
// Get POI details and descriptions in parallel
|
||||
const [poisData, descriptionsData] = await Promise.all([
|
||||
getPoisData(locationIds),
|
||||
getDescriptionsData(locationIds)
|
||||
]);
|
||||
|
||||
return formatLocalResults(poisData, descriptionsData);
|
||||
}
|
||||
|
||||
async function getPoisData(ids: string[]): Promise<BravePoiResponse> {
|
||||
checkRateLimit();
|
||||
const url = new URL('https://api.search.brave.com/res/v1/local/pois');
|
||||
ids.filter(Boolean).forEach(id => url.searchParams.append('ids', id));
|
||||
const response = await fetch(url, {
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Accept-Encoding': 'gzip',
|
||||
'X-Subscription-Token': BRAVE_API_KEY
|
||||
}
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Brave API error: ${response.status} ${response.statusText}\n${await response.text()}`);
|
||||
}
|
||||
|
||||
const poisResponse = await response.json() as BravePoiResponse;
|
||||
return poisResponse;
|
||||
}
|
||||
|
||||
async function getDescriptionsData(ids: string[]): Promise<BraveDescription> {
|
||||
checkRateLimit();
|
||||
const url = new URL('https://api.search.brave.com/res/v1/local/descriptions');
|
||||
ids.filter(Boolean).forEach(id => url.searchParams.append('ids', id));
|
||||
const response = await fetch(url, {
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Accept-Encoding': 'gzip',
|
||||
'X-Subscription-Token': BRAVE_API_KEY
|
||||
}
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Brave API error: ${response.status} ${response.statusText}\n${await response.text()}`);
|
||||
}
|
||||
|
||||
const descriptionsData = await response.json() as BraveDescription;
|
||||
return descriptionsData;
|
||||
}
|
||||
|
||||
function formatLocalResults(poisData: BravePoiResponse, descData: BraveDescription): string {
|
||||
return (poisData.results || []).map(poi => {
|
||||
const address = [
|
||||
poi.address?.streetAddress ?? '',
|
||||
poi.address?.addressLocality ?? '',
|
||||
poi.address?.addressRegion ?? '',
|
||||
poi.address?.postalCode ?? ''
|
||||
].filter(part => part !== '').join(', ') || 'N/A';
|
||||
|
||||
return `Name: ${poi.name}
|
||||
Address: ${address}
|
||||
Phone: ${poi.phone || 'N/A'}
|
||||
Rating: ${poi.rating?.ratingValue ?? 'N/A'} (${poi.rating?.ratingCount ?? 0} reviews)
|
||||
Price Range: ${poi.priceRange || 'N/A'}
|
||||
Hours: ${(poi.openingHours || []).join(', ') || 'N/A'}
|
||||
Description: ${descData.descriptions[poi.id] || 'No description available'}
|
||||
`;
|
||||
}).join('\n---\n') || 'No local results found';
|
||||
}
|
||||
|
||||
// Tool handlers
|
||||
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
||||
tools: [WEB_SEARCH_TOOL, LOCAL_SEARCH_TOOL],
|
||||
}));
|
||||
|
||||
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
||||
try {
|
||||
const { name, arguments: args } = request.params;
|
||||
|
||||
if (!args) {
|
||||
throw new Error("No arguments provided");
|
||||
}
|
||||
|
||||
switch (name) {
|
||||
case "brave_web_search": {
|
||||
if (!isBraveWebSearchArgs(args)) {
|
||||
throw new Error("Invalid arguments for brave_web_search");
|
||||
}
|
||||
const { query, count = 10 } = args;
|
||||
const results = await performWebSearch(query, count);
|
||||
return {
|
||||
content: [{ type: "text", text: results }],
|
||||
isError: false,
|
||||
};
|
||||
}
|
||||
|
||||
case "brave_local_search": {
|
||||
if (!isBraveLocalSearchArgs(args)) {
|
||||
throw new Error("Invalid arguments for brave_local_search");
|
||||
}
|
||||
const { query, count = 5 } = args;
|
||||
const results = await performLocalSearch(query, count);
|
||||
return {
|
||||
content: [{ type: "text", text: results }],
|
||||
isError: false,
|
||||
};
|
||||
}
|
||||
|
||||
default:
|
||||
return {
|
||||
content: [{ type: "text", text: `Unknown tool: ${name}` }],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Error: ${error instanceof Error ? error.message : String(error)}`,
|
||||
},
|
||||
],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
async function runServer() {
|
||||
const transport = new StdioServerTransport();
|
||||
await server.connect(transport);
|
||||
console.error("Brave Search MCP Server running on stdio");
|
||||
}
|
||||
|
||||
runServer().catch((error) => {
|
||||
console.error("Fatal error running server:", error);
|
||||
process.exit(1);
|
||||
});
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
{
|
||||
"name": "@modelcontextprotocol/server-brave-search",
|
||||
"version": "0.6.2",
|
||||
"description": "MCP server for Brave Search API integration",
|
||||
"license": "MIT",
|
||||
"author": "Anthropic, PBC (https://anthropic.com)",
|
||||
"homepage": "https://modelcontextprotocol.io",
|
||||
"bugs": "https://github.com/modelcontextprotocol/servers/issues",
|
||||
"type": "module",
|
||||
"bin": {
|
||||
"mcp-server-brave-search": "dist/index.js"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "tsc && shx chmod +x dist/*.js",
|
||||
"prepare": "npm run build",
|
||||
"watch": "tsc --watch"
|
||||
},
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "1.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22",
|
||||
"shx": "^0.3.4",
|
||||
"typescript": "^5.6.2"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./dist",
|
||||
"rootDir": "."
|
||||
},
|
||||
"include": [
|
||||
"./**/*.ts"
|
||||
]
|
||||
}
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
FROM node:22.12-alpine AS builder
|
||||
|
||||
COPY src/everart /app
|
||||
COPY tsconfig.json /tsconfig.json
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
RUN --mount=type=cache,target=/root/.npm npm install
|
||||
|
||||
FROM node:22-alpine AS release
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY --from=builder /app/dist /app/dist
|
||||
COPY --from=builder /app/package.json /app/package.json
|
||||
COPY --from=builder /app/package-lock.json /app/package-lock.json
|
||||
|
||||
ENV NODE_ENV=production
|
||||
|
||||
RUN npm ci --ignore-scripts --omit-dev
|
||||
|
||||
ENTRYPOINT ["node", "dist/index.js"]
|
||||
|
||||
CMD ["node", "dist/index.js"]
|
||||
|
|
@ -1,97 +0,0 @@
|
|||
# EverArt MCP Server
|
||||
|
||||
Image generation server for Claude Desktop using EverArt's API.
|
||||
|
||||
## Install
|
||||
```bash
|
||||
npm install
|
||||
export EVERART_API_KEY=your_key_here
|
||||
```
|
||||
|
||||
## Config
|
||||
Add to Claude Desktop config:
|
||||
|
||||
### Docker
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"everart": {
|
||||
"command": "docker",
|
||||
"args": ["run", "-i", "--rm", "-e", "EVERART_API_KEY", "mcp/everart"],
|
||||
"env": {
|
||||
"EVERART_API_KEY": "your_key_here"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### NPX
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"everart": {
|
||||
"command": "npx",
|
||||
"args": ["-y", "@modelcontextprotocol/server-everart"],
|
||||
"env": {
|
||||
"EVERART_API_KEY": "your_key_here"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Tools
|
||||
|
||||
### generate_image
|
||||
Generates images with multiple model options. Opens result in browser and returns URL.
|
||||
|
||||
Parameters:
|
||||
```typescript
|
||||
{
|
||||
prompt: string, // Image description
|
||||
model?: string, // Model ID (default: "207910310772879360")
|
||||
image_count?: number // Number of images (default: 1)
|
||||
}
|
||||
```
|
||||
|
||||
Models:
|
||||
- 5000: FLUX1.1 (standard)
|
||||
- 9000: FLUX1.1-ultra
|
||||
- 6000: SD3.5
|
||||
- 7000: Recraft-Real
|
||||
- 8000: Recraft-Vector
|
||||
|
||||
All images generated at 1024x1024.
|
||||
|
||||
Sample usage:
|
||||
```javascript
|
||||
const result = await client.callTool({
|
||||
name: "generate_image",
|
||||
arguments: {
|
||||
prompt: "A cat sitting elegantly",
|
||||
model: "7000",
|
||||
image_count: 1
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
Response format:
|
||||
```
|
||||
Image generated successfully!
|
||||
The image has been opened in your default browser.
|
||||
|
||||
Generation details:
|
||||
- Model: 7000
|
||||
- Prompt: "A cat sitting elegantly"
|
||||
- Image URL: https://storage.googleapis.com/...
|
||||
|
||||
You can also click the URL above to view the image again.
|
||||
```
|
||||
|
||||
## Building w/ Docker
|
||||
|
||||
```sh
|
||||
docker build -t mcp/everart -f src/everart/Dockerfile .
|
||||
```
|
||||
|
|
@ -1,160 +0,0 @@
|
|||
#!/usr/bin/env node
|
||||
import EverArt from "everart";
|
||||
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
||||
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
||||
import {
|
||||
CallToolRequestSchema,
|
||||
ListToolsRequestSchema,
|
||||
ListResourcesRequestSchema,
|
||||
ReadResourceRequestSchema,
|
||||
} from "@modelcontextprotocol/sdk/types.js";
|
||||
import fetch from "node-fetch";
|
||||
import open from "open";
|
||||
|
||||
const server = new Server(
|
||||
{
|
||||
name: "example-servers/everart",
|
||||
version: "0.2.0",
|
||||
},
|
||||
{
|
||||
capabilities: {
|
||||
tools: {},
|
||||
resources: {}, // Required for image resources
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
if (!process.env.EVERART_API_KEY) {
|
||||
console.error("EVERART_API_KEY environment variable is not set");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const client = new EverArt.default(process.env.EVERART_API_KEY);
|
||||
|
||||
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
||||
tools: [
|
||||
{
|
||||
name: "generate_image",
|
||||
description:
|
||||
"Generate images using EverArt Models and returns a clickable link to view the generated image. " +
|
||||
"The tool will return a URL that can be clicked to view the image in a browser. " +
|
||||
"Available models:\n" +
|
||||
"- 5000:FLUX1.1: Standard quality\n" +
|
||||
"- 9000:FLUX1.1-ultra: Ultra high quality\n" +
|
||||
"- 6000:SD3.5: Stable Diffusion 3.5\n" +
|
||||
"- 7000:Recraft-Real: Photorealistic style\n" +
|
||||
"- 8000:Recraft-Vector: Vector art style\n" +
|
||||
"\nThe response will contain a direct link to view the generated image.",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
prompt: {
|
||||
type: "string",
|
||||
description: "Text description of desired image",
|
||||
},
|
||||
model: {
|
||||
type: "string",
|
||||
description:
|
||||
"Model ID (5000:FLUX1.1, 9000:FLUX1.1-ultra, 6000:SD3.5, 7000:Recraft-Real, 8000:Recraft-Vector)",
|
||||
default: "5000",
|
||||
},
|
||||
image_count: {
|
||||
type: "number",
|
||||
description: "Number of images to generate",
|
||||
default: 1,
|
||||
},
|
||||
},
|
||||
required: ["prompt"],
|
||||
},
|
||||
},
|
||||
],
|
||||
}));
|
||||
|
||||
server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
||||
return {
|
||||
resources: [
|
||||
{
|
||||
uri: "everart://images",
|
||||
mimeType: "image/png",
|
||||
name: "Generated Images",
|
||||
},
|
||||
],
|
||||
};
|
||||
});
|
||||
|
||||
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
||||
if (request.params.uri === "everart://images") {
|
||||
return {
|
||||
contents: [
|
||||
{
|
||||
uri: "everart://images",
|
||||
mimeType: "image/png",
|
||||
blob: "", // Empty since this is just for listing
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
throw new Error("Resource not found");
|
||||
});
|
||||
|
||||
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
||||
if (request.params.name === "generate_image") {
|
||||
try {
|
||||
const {
|
||||
prompt,
|
||||
model = "207910310772879360",
|
||||
image_count = 1,
|
||||
} = request.params.arguments as any;
|
||||
|
||||
// Use correct EverArt API method
|
||||
const generation = await client.v1.generations.create(
|
||||
model,
|
||||
prompt,
|
||||
"txt2img",
|
||||
{
|
||||
imageCount: image_count,
|
||||
height: 1024,
|
||||
width: 1024,
|
||||
},
|
||||
);
|
||||
|
||||
// Wait for generation to complete
|
||||
const completedGen = await client.v1.generations.fetchWithPolling(
|
||||
generation[0].id,
|
||||
);
|
||||
|
||||
const imgUrl = completedGen.image_url;
|
||||
if (!imgUrl) throw new Error("No image URL");
|
||||
|
||||
// Automatically open the image URL in the default browser
|
||||
await open(imgUrl);
|
||||
|
||||
// Return a formatted message with the clickable link
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Image generated successfully!\nThe image has been opened in your default browser.\n\nGeneration details:\n- Model: ${model}\n- Prompt: "${prompt}"\n- Image URL: ${imgUrl}\n\nYou can also click the URL above to view the image again.`,
|
||||
},
|
||||
],
|
||||
};
|
||||
} catch (error: unknown) {
|
||||
console.error("Detailed error:", error);
|
||||
const errorMessage =
|
||||
error instanceof Error ? error.message : "Unknown error";
|
||||
return {
|
||||
content: [{ type: "text", text: `Error: ${errorMessage}` }],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
}
|
||||
throw new Error(`Unknown tool: ${request.params.name}`);
|
||||
});
|
||||
|
||||
async function runServer() {
|
||||
const transport = new StdioServerTransport();
|
||||
await server.connect(transport);
|
||||
console.error("EverArt MCP Server running on stdio");
|
||||
}
|
||||
|
||||
runServer().catch(console.error);
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
{
|
||||
"name": "@modelcontextprotocol/server-everart",
|
||||
"version": "0.6.2",
|
||||
"description": "MCP server for EverArt API integration",
|
||||
"license": "MIT",
|
||||
"author": "Anthropic, PBC (https://anthropic.com)",
|
||||
"homepage": "https://modelcontextprotocol.io",
|
||||
"bugs": "https://github.com/modelcontextprotocol/servers/issues",
|
||||
"type": "module",
|
||||
"bin": {
|
||||
"mcp-server-everart": "dist/index.js"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "tsc && shx chmod +x dist/*.js",
|
||||
"prepare": "npm run build",
|
||||
"watch": "tsc --watch"
|
||||
},
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "0.5.0",
|
||||
"everart": "^1.0.0",
|
||||
"node-fetch": "^3.3.2",
|
||||
"open": "^9.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22",
|
||||
"shx": "^0.3.4",
|
||||
"typescript": "^5.3.3"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./dist",
|
||||
"rootDir": "."
|
||||
},
|
||||
"include": [
|
||||
"./**/*.ts"
|
||||
]
|
||||
}
|
||||
|
|
@ -126,7 +126,7 @@ The server sends random-leveled log messages every 15 seconds, e.g.:
|
|||
}
|
||||
```
|
||||
|
||||
## Usage with Claude Desktop
|
||||
## Usage with Claude Desktop (uses [stdio Transport](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#stdio))
|
||||
|
||||
Add to your `claude_desktop_config.json`:
|
||||
|
||||
|
|
@ -143,3 +143,75 @@ Add to your `claude_desktop_config.json`:
|
|||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Usage with VS Code
|
||||
|
||||
For quick installation, use of of the one-click install buttons below...
|
||||
|
||||
[](https://insiders.vscode.dev/redirect/mcp/install?name=everything&config=%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22%40modelcontextprotocol%2Fserver-everything%22%5D%7D) [](https://insiders.vscode.dev/redirect/mcp/install?name=everything&config=%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22%40modelcontextprotocol%2Fserver-everything%22%5D%7D&quality=insiders)
|
||||
|
||||
[](https://insiders.vscode.dev/redirect/mcp/install?name=everything&config=%7B%22command%22%3A%22docker%22%2C%22args%22%3A%5B%22run%22%2C%22-i%22%2C%22--rm%22%2C%22mcp%2Feverything%22%5D%7D) [](https://insiders.vscode.dev/redirect/mcp/install?name=everything&config=%7B%22command%22%3A%22docker%22%2C%22args%22%3A%5B%22run%22%2C%22-i%22%2C%22--rm%22%2C%22mcp%2Feverything%22%5D%7D&quality=insiders)
|
||||
|
||||
For manual installation, add the following JSON block to your User Settings (JSON) file in VS Code. You can do this by pressing `Ctrl + Shift + P` and typing `Preferences: Open User Settings (JSON)`.
|
||||
|
||||
Optionally, you can add it to a file called `.vscode/mcp.json` in your workspace. This will allow you to share the configuration with others.
|
||||
|
||||
> Note that the `mcp` key is not needed in the `.vscode/mcp.json` file.
|
||||
|
||||
#### NPX
|
||||
|
||||
```json
|
||||
{
|
||||
"mcp": {
|
||||
"servers": {
|
||||
"everything": {
|
||||
"command": "npx",
|
||||
"args": ["-y", "@modelcontextprotocol/server-everything"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Running from source with [HTTP+SSE Transport](https://modelcontextprotocol.io/specification/2024-11-05/basic/transports#http-with-sse) (deprecated as of [2025-03-26](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports))
|
||||
|
||||
```shell
|
||||
cd src/everything
|
||||
npm install
|
||||
npm run start:sse
|
||||
```
|
||||
|
||||
## Run from source with [Streamable HTTP Transport](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#streamable-http)
|
||||
|
||||
```shell
|
||||
cd src/everything
|
||||
npm install
|
||||
npm run start:streamableHttp
|
||||
```
|
||||
|
||||
## Running as an installed package
|
||||
### Install
|
||||
```shell
|
||||
npm install -g @modelcontextprotocol/server-everything@latest
|
||||
````
|
||||
|
||||
### Run the default (stdio) server
|
||||
```shell
|
||||
npx @modelcontextprotocol/server-everything
|
||||
```
|
||||
|
||||
### Or specify stdio explicitly
|
||||
```shell
|
||||
npx @modelcontextprotocol/server-everything stdio
|
||||
```
|
||||
|
||||
### Run the SSE server
|
||||
```shell
|
||||
npx @modelcontextprotocol/server-everything sse
|
||||
```
|
||||
|
||||
### Run the streamable HTTP server
|
||||
```shell
|
||||
npx @modelcontextprotocol/server-everything streamableHttp
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -108,6 +108,7 @@ export const createServer = () => {
|
|||
resources: { subscribe: true },
|
||||
tools: {},
|
||||
logging: {},
|
||||
completions: {},
|
||||
},
|
||||
}
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,23 +1,37 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
||||
import { createServer } from "./everything.js";
|
||||
// Parse command line arguments first
|
||||
const args = process.argv.slice(2);
|
||||
const scriptName = args[0] || 'stdio';
|
||||
|
||||
async function main() {
|
||||
const transport = new StdioServerTransport();
|
||||
const { server, cleanup } = createServer();
|
||||
|
||||
await server.connect(transport);
|
||||
|
||||
// Cleanup on exit
|
||||
process.on("SIGINT", async () => {
|
||||
await cleanup();
|
||||
await server.close();
|
||||
process.exit(0);
|
||||
});
|
||||
async function run() {
|
||||
try {
|
||||
// Dynamically import only the requested module to prevent all modules from initializing
|
||||
switch (scriptName) {
|
||||
case 'stdio':
|
||||
// Import and run the default server
|
||||
await import('./stdio.js');
|
||||
break;
|
||||
case 'sse':
|
||||
// Import and run the SSE server
|
||||
await import('./sse.js');
|
||||
break;
|
||||
case 'streamableHttp':
|
||||
// Import and run the streamable HTTP server
|
||||
await import('./streamableHttp.js');
|
||||
break;
|
||||
default:
|
||||
console.error(`Unknown script: ${scriptName}`);
|
||||
console.log('Available scripts:');
|
||||
console.log('- stdio');
|
||||
console.log('- sse');
|
||||
console.log('- streamableHttp');
|
||||
process.exit(1);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error running script:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
console.error("Server error:", error);
|
||||
process.exit(1);
|
||||
});
|
||||
run();
|
||||
|
|
|
|||
|
|
@ -18,10 +18,11 @@
|
|||
"prepare": "npm run build",
|
||||
"watch": "tsc --watch",
|
||||
"start": "node dist/index.js",
|
||||
"start:sse": "node dist/sse.js"
|
||||
"start:sse": "node dist/sse.js",
|
||||
"start:streamableHttp": "node dist/streamableHttp.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "1.0.1",
|
||||
"@modelcontextprotocol/sdk": "^1.12.0",
|
||||
"express": "^4.21.1",
|
||||
"zod": "^3.23.8",
|
||||
"zod-to-json-schema": "^3.23.5"
|
||||
|
|
@ -31,4 +32,4 @@
|
|||
"shx": "^0.3.4",
|
||||
"typescript": "^5.6.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,31 +2,52 @@ import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
|
|||
import express from "express";
|
||||
import { createServer } from "./everything.js";
|
||||
|
||||
console.error('Starting SSE server...');
|
||||
|
||||
const app = express();
|
||||
|
||||
const { server, cleanup } = createServer();
|
||||
|
||||
let transport: SSEServerTransport;
|
||||
const transports: Map<string, SSEServerTransport> = new Map<string, SSEServerTransport>();
|
||||
|
||||
app.get("/sse", async (req, res) => {
|
||||
console.log("Received connection");
|
||||
transport = new SSEServerTransport("/message", res);
|
||||
await server.connect(transport);
|
||||
let transport: SSEServerTransport;
|
||||
const { server, cleanup } = createServer();
|
||||
|
||||
if (req?.query?.sessionId) {
|
||||
const sessionId = (req?.query?.sessionId as string);
|
||||
transport = transports.get(sessionId) as SSEServerTransport;
|
||||
console.error("Client Reconnecting? This shouldn't happen; when client has a sessionId, GET /sse should not be called again.", transport.sessionId);
|
||||
} else {
|
||||
// Create and store transport for new session
|
||||
transport = new SSEServerTransport("/message", res);
|
||||
transports.set(transport.sessionId, transport);
|
||||
|
||||
// Connect server to transport
|
||||
await server.connect(transport);
|
||||
console.error("Client Connected: ", transport.sessionId);
|
||||
|
||||
// Handle close of connection
|
||||
server.onclose = async () => {
|
||||
console.error("Client Disconnected: ", transport.sessionId);
|
||||
transports.delete(transport.sessionId);
|
||||
await cleanup();
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
server.onclose = async () => {
|
||||
await cleanup();
|
||||
await server.close();
|
||||
process.exit(0);
|
||||
};
|
||||
});
|
||||
|
||||
app.post("/message", async (req, res) => {
|
||||
console.log("Received message");
|
||||
|
||||
await transport.handlePostMessage(req, res);
|
||||
const sessionId = (req?.query?.sessionId as string);
|
||||
const transport = transports.get(sessionId);
|
||||
if (transport) {
|
||||
console.error("Client Message from", sessionId);
|
||||
await transport.handlePostMessage(req, res);
|
||||
} else {
|
||||
console.error(`No transport found for sessionId ${sessionId}`)
|
||||
}
|
||||
});
|
||||
|
||||
const PORT = process.env.PORT || 3001;
|
||||
app.listen(PORT, () => {
|
||||
console.log(`Server is running on port ${PORT}`);
|
||||
console.error(`Server is running on port ${PORT}`);
|
||||
});
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue