Skip to content

Commit

Permalink
Merge branch 'main' into link-new-server
Browse files Browse the repository at this point in the history
  • Loading branch information
RamXX authored Dec 2, 2024
2 parents e73d831 + f2e8f2e commit 673f7d7
Show file tree
Hide file tree
Showing 10 changed files with 667 additions and 7 deletions.
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
"@modelcontextprotocol/server-slack": "*",
"@modelcontextprotocol/server-brave-search": "*",
"@modelcontextprotocol/server-memory": "*",
"@modelcontextprotocol/server-filesystem": "*"
"@modelcontextprotocol/server-filesystem": "*",
"@modelcontextprotocol/server-everart": "*",
"@modelcontextprotocol/server-sequentialthinking": "*"
}
}
73 changes: 73 additions & 0 deletions src/everart/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# 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:
```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.
```
160 changes: 160 additions & 0 deletions src/everart/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
#!/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);
32 changes: 32 additions & 0 deletions src/everart/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"name": "@modelcontextprotocol/server-everart",
"version": "0.1.0",
"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": "^20.11.0",
"shx": "^0.3.4",
"typescript": "^5.3.3"
}
}
10 changes: 10 additions & 0 deletions src/everart/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "."
},
"include": [
"./**/*.ts"
]
}
12 changes: 6 additions & 6 deletions src/fetch/src/mcp_server_fetch/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ def extract_content_from_html(html: str) -> str:
return content


def get_robots_txt_url(url: AnyUrl | str) -> str:
def get_robots_txt_url(url: str) -> str:
"""Get the robots.txt URL for a given website URL.
Args:
Expand All @@ -54,15 +54,15 @@ def get_robots_txt_url(url: AnyUrl | str) -> str:
URL of the robots.txt file
"""
# Parse the URL into components
parsed = urlparse(str(url))
parsed = urlparse(url)

# Reconstruct the base URL with just scheme, netloc, and /robots.txt path
robots_url = urlunparse((parsed.scheme, parsed.netloc, "/robots.txt", "", "", ""))

return robots_url


async def check_may_autonomously_fetch_url(url: AnyUrl | str, user_agent: str) -> None:
async def check_may_autonomously_fetch_url(url: str, user_agent: str) -> None:
"""
Check if the URL can be fetched by the user agent according to the robots.txt file.
Raises a McpError if not.
Expand Down Expand Up @@ -106,7 +106,7 @@ async def check_may_autonomously_fetch_url(url: AnyUrl | str, user_agent: str) -


async def fetch_url(
url: AnyUrl | str, user_agent: str, force_raw: bool = False
url: str, user_agent: str, force_raw: bool = False
) -> Tuple[str, str]:
"""
Fetch the URL and return the content in a form ready for the LLM, as well as a prefix string with status information.
Expand All @@ -116,7 +116,7 @@ async def fetch_url(
async with AsyncClient() as client:
try:
response = await client.get(
str(url),
url,
follow_redirects=True,
headers={"User-Agent": user_agent},
timeout=30,
Expand Down Expand Up @@ -221,7 +221,7 @@ async def call_tool(name, arguments: dict) -> list[TextContent]:
except ValueError as e:
raise McpError(INVALID_PARAMS, str(e))

url = args.url
url = str(args.url)
if not url:
raise McpError(INVALID_PARAMS, "URL is required")

Expand Down
Loading

0 comments on commit 673f7d7

Please sign in to comment.