Merge remote-tracking branch 'upstream/main' into add-sequentialthinking-tests-update-sdk
This commit is contained in:
commit
f6fbfc4c5e
11 changed files with 430 additions and 191 deletions
|
|
@ -1,10 +1,13 @@
|
|||
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
||||
import type { RequestHandlerExtra } from "@modelcontextprotocol/sdk/shared/protocol.js";
|
||||
import {
|
||||
CallToolRequestSchema,
|
||||
ClientCapabilities,
|
||||
CompleteRequestSchema,
|
||||
CreateMessageRequest,
|
||||
CreateMessageResultSchema,
|
||||
ElicitRequest,
|
||||
ElicitResultSchema,
|
||||
GetPromptRequestSchema,
|
||||
ListPromptsRequestSchema,
|
||||
ListResourcesRequestSchema,
|
||||
|
|
@ -14,18 +17,20 @@ import {
|
|||
ReadResourceRequestSchema,
|
||||
Resource,
|
||||
RootsListChangedNotificationSchema,
|
||||
SetLevelRequestSchema,
|
||||
ServerNotification,
|
||||
ServerRequest,
|
||||
SubscribeRequestSchema,
|
||||
Tool,
|
||||
ToolSchema,
|
||||
UnsubscribeRequestSchema,
|
||||
type Root,
|
||||
type Root
|
||||
} from "@modelcontextprotocol/sdk/types.js";
|
||||
import { z } from "zod";
|
||||
import { zodToJsonSchema } from "zod-to-json-schema";
|
||||
import { readFileSync } from "fs";
|
||||
import { fileURLToPath } from "url";
|
||||
import { dirname, join } from "path";
|
||||
import JSZip from "jszip";
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
|
@ -37,6 +42,8 @@ type ToolInput = z.infer<typeof ToolInputSchema>;
|
|||
const ToolOutputSchema = ToolSchema.shape.outputSchema;
|
||||
type ToolOutput = z.infer<typeof ToolOutputSchema>;
|
||||
|
||||
type SendRequest = RequestHandlerExtra<ServerRequest, ServerNotification>["sendRequest"];
|
||||
|
||||
/* Input schemas for tools implemented in this server */
|
||||
const EchoSchema = z.object({
|
||||
message: z.string().describe("Message to echo"),
|
||||
|
|
@ -123,6 +130,10 @@ const StructuredContentSchema = {
|
|||
})
|
||||
};
|
||||
|
||||
const ZipResourcesInputSchema = z.object({
|
||||
files: z.record(z.string().url().describe("URL of the file to include in the zip")).describe("Mapping of file names to URLs to include in the zip"),
|
||||
});
|
||||
|
||||
enum ToolName {
|
||||
ECHO = "echo",
|
||||
ADD = "add",
|
||||
|
|
@ -135,6 +146,7 @@ enum ToolName {
|
|||
ELICITATION = "startElicitation",
|
||||
GET_RESOURCE_LINKS = "getResourceLinks",
|
||||
STRUCTURED_CONTENT = "structuredContent",
|
||||
ZIP_RESOURCES = "zip",
|
||||
LIST_ROOTS = "listRoots"
|
||||
}
|
||||
|
||||
|
|
@ -174,7 +186,6 @@ export const createServer = () => {
|
|||
let subsUpdateInterval: NodeJS.Timeout | undefined;
|
||||
let stdErrUpdateInterval: NodeJS.Timeout | undefined;
|
||||
|
||||
let logLevel: LoggingLevel = "debug";
|
||||
let logsUpdateInterval: NodeJS.Timeout | undefined;
|
||||
// Store client capabilities
|
||||
let clientCapabilities: ClientCapabilities | undefined;
|
||||
|
|
@ -182,55 +193,48 @@ export const createServer = () => {
|
|||
// Roots state management
|
||||
let currentRoots: Root[] = [];
|
||||
let clientSupportsRoots = false;
|
||||
const messages = [
|
||||
{ level: "debug", data: "Debug-level message" },
|
||||
{ level: "info", data: "Info-level message" },
|
||||
{ level: "notice", data: "Notice-level message" },
|
||||
{ level: "warning", data: "Warning-level message" },
|
||||
{ level: "error", data: "Error-level message" },
|
||||
{ level: "critical", data: "Critical-level message" },
|
||||
{ level: "alert", data: "Alert level-message" },
|
||||
{ level: "emergency", data: "Emergency-level message" },
|
||||
];
|
||||
let sessionId: string | undefined;
|
||||
|
||||
const isMessageIgnored = (level: LoggingLevel): boolean => {
|
||||
const currentLevel = messages.findIndex((msg) => logLevel === msg.level);
|
||||
const messageLevel = messages.findIndex((msg) => level === msg.level);
|
||||
return messageLevel < currentLevel;
|
||||
};
|
||||
// Function to start notification intervals when a client connects
|
||||
const startNotificationIntervals = (sid?: string|undefined) => {
|
||||
sessionId = sid;
|
||||
if (!subsUpdateInterval) {
|
||||
subsUpdateInterval = setInterval(() => {
|
||||
for (const uri of subscriptions) {
|
||||
server.notification({
|
||||
method: "notifications/resources/updated",
|
||||
params: { uri },
|
||||
});
|
||||
}
|
||||
}, 10000);
|
||||
}
|
||||
|
||||
// Function to start notification intervals when a client connects
|
||||
const startNotificationIntervals = () => {
|
||||
if (!subsUpdateInterval) {
|
||||
subsUpdateInterval = setInterval(() => {
|
||||
for (const uri of subscriptions) {
|
||||
server.notification({
|
||||
method: "notifications/resources/updated",
|
||||
params: { uri },
|
||||
});
|
||||
}
|
||||
}, 10000);
|
||||
}
|
||||
const maybeAppendSessionId = sessionId ? ` - SessionId ${sessionId}`: "";
|
||||
const messages: { level: LoggingLevel; data: string }[] = [
|
||||
{ level: "debug", data: `Debug-level message${maybeAppendSessionId}` },
|
||||
{ level: "info", data: `Info-level message${maybeAppendSessionId}` },
|
||||
{ level: "notice", data: `Notice-level message${maybeAppendSessionId}` },
|
||||
{ level: "warning", data: `Warning-level message${maybeAppendSessionId}` },
|
||||
{ level: "error", data: `Error-level message${maybeAppendSessionId}` },
|
||||
{ level: "critical", data: `Critical-level message${maybeAppendSessionId}` },
|
||||
{ level: "alert", data: `Alert level-message${maybeAppendSessionId}` },
|
||||
{ level: "emergency", data: `Emergency-level message${maybeAppendSessionId}` },
|
||||
];
|
||||
|
||||
if (!logsUpdateInterval) {
|
||||
logsUpdateInterval = setInterval(() => {
|
||||
let message = {
|
||||
method: "notifications/message",
|
||||
params: messages[Math.floor(Math.random() * messages.length)],
|
||||
};
|
||||
if (!isMessageIgnored(message.params.level as LoggingLevel))
|
||||
server.notification(message);
|
||||
}, 20000);
|
||||
if (!logsUpdateInterval) {
|
||||
console.error("Starting logs update interval");
|
||||
logsUpdateInterval = setInterval(async () => {
|
||||
await server.sendLoggingMessage( messages[Math.floor(Math.random() * messages.length)], sessionId);
|
||||
}, 15000);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
// Helper method to request sampling from client
|
||||
const requestSampling = async (
|
||||
context: string,
|
||||
uri: string,
|
||||
maxTokens: number = 100
|
||||
maxTokens: number = 100,
|
||||
sendRequest: SendRequest
|
||||
) => {
|
||||
const request: CreateMessageRequest = {
|
||||
method: "sampling/createMessage",
|
||||
|
|
@ -251,22 +255,24 @@ export const createServer = () => {
|
|||
},
|
||||
};
|
||||
|
||||
return await server.request(request, CreateMessageResultSchema);
|
||||
return await sendRequest(request, CreateMessageResultSchema);
|
||||
|
||||
};
|
||||
|
||||
const requestElicitation = async (
|
||||
message: string,
|
||||
requestedSchema: any
|
||||
requestedSchema: any,
|
||||
sendRequest: SendRequest
|
||||
) => {
|
||||
const request = {
|
||||
const request: ElicitRequest = {
|
||||
method: 'elicitation/create',
|
||||
params: {
|
||||
message,
|
||||
requestedSchema
|
||||
}
|
||||
requestedSchema,
|
||||
},
|
||||
};
|
||||
|
||||
return await server.request(request, z.any());
|
||||
return await sendRequest(request, ElicitResultSchema);
|
||||
};
|
||||
|
||||
const ALL_RESOURCES: Resource[] = Array.from({ length: 100 }, (_, i) => {
|
||||
|
|
@ -344,12 +350,9 @@ export const createServer = () => {
|
|||
throw new Error(`Unknown resource: ${uri}`);
|
||||
});
|
||||
|
||||
server.setRequestHandler(SubscribeRequestSchema, async (request) => {
|
||||
server.setRequestHandler(SubscribeRequestSchema, async (request, extra) => {
|
||||
const { uri } = request.params;
|
||||
subscriptions.add(uri);
|
||||
|
||||
// Request sampling from client when someone subscribes
|
||||
await requestSampling("A new subscription was started", uri);
|
||||
return {};
|
||||
});
|
||||
|
||||
|
|
@ -535,6 +538,11 @@ export const createServer = () => {
|
|||
inputSchema: zodToJsonSchema(StructuredContentSchema.input) as ToolInput,
|
||||
outputSchema: zodToJsonSchema(StructuredContentSchema.output) as ToolOutput,
|
||||
},
|
||||
{
|
||||
name: ToolName.ZIP_RESOURCES,
|
||||
description: "Compresses the provided resource files (mapping of name to URI, which can be a data URI) to a zip file, which it returns as a data URI resource link.",
|
||||
inputSchema: zodToJsonSchema(ZipResourcesInputSchema) as ToolInput,
|
||||
}
|
||||
];
|
||||
if (clientCapabilities!.roots) tools.push ({
|
||||
name: ToolName.LIST_ROOTS,
|
||||
|
|
@ -551,7 +559,7 @@ export const createServer = () => {
|
|||
return { tools };
|
||||
});
|
||||
|
||||
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
||||
server.setRequestHandler(CallToolRequestSchema, async (request,extra) => {
|
||||
const { name, arguments: args } = request.params;
|
||||
|
||||
if (name === ToolName.ECHO) {
|
||||
|
|
@ -593,7 +601,7 @@ export const createServer = () => {
|
|||
total: steps,
|
||||
progressToken,
|
||||
},
|
||||
});
|
||||
},{relatedRequestId: extra.requestId});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -625,7 +633,8 @@ export const createServer = () => {
|
|||
const result = await requestSampling(
|
||||
prompt,
|
||||
ToolName.SAMPLE_LLM,
|
||||
maxTokens
|
||||
maxTokens,
|
||||
extra.sendRequest
|
||||
);
|
||||
return {
|
||||
content: [
|
||||
|
|
@ -744,14 +753,20 @@ export const createServer = () => {
|
|||
type: 'object',
|
||||
properties: {
|
||||
color: { type: 'string', description: 'Favorite color' },
|
||||
number: { type: 'integer', description: 'Favorite number', minimum: 1, maximum: 100 },
|
||||
number: {
|
||||
type: 'integer',
|
||||
description: 'Favorite number',
|
||||
minimum: 1,
|
||||
maximum: 100,
|
||||
},
|
||||
pets: {
|
||||
type: 'string',
|
||||
enum: ['cats', 'dogs', 'birds', 'fish', 'reptiles'],
|
||||
description: 'Favorite pets'
|
||||
description: 'Favorite pets',
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
extra.sendRequest
|
||||
);
|
||||
|
||||
// Handle different response actions
|
||||
|
|
@ -840,6 +855,37 @@ export const createServer = () => {
|
|||
};
|
||||
}
|
||||
|
||||
if (name === ToolName.ZIP_RESOURCES) {
|
||||
const { files } = ZipResourcesInputSchema.parse(args);
|
||||
|
||||
const zip = new JSZip();
|
||||
|
||||
for (const [fileName, fileUrl] of Object.entries(files)) {
|
||||
try {
|
||||
const response = await fetch(fileUrl);
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch ${fileUrl}: ${response.statusText}`);
|
||||
}
|
||||
const arrayBuffer = await response.arrayBuffer();
|
||||
zip.file(fileName, arrayBuffer);
|
||||
} catch (error) {
|
||||
throw new Error(`Error fetching file ${fileUrl}: ${error instanceof Error ? error.message : String(error)}`);
|
||||
}
|
||||
}
|
||||
|
||||
const uri = `data:application/zip;base64,${await zip.generateAsync({ type: "base64" })}`;
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "resource_link",
|
||||
mimeType: "application/zip",
|
||||
uri,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
if (name === ToolName.LIST_ROOTS) {
|
||||
ListRootsSchema.parse(args);
|
||||
|
||||
|
|
@ -918,23 +964,6 @@ export const createServer = () => {
|
|||
throw new Error(`Unknown reference type`);
|
||||
});
|
||||
|
||||
server.setRequestHandler(SetLevelRequestSchema, async (request) => {
|
||||
const { level } = request.params;
|
||||
logLevel = level;
|
||||
|
||||
// Demonstrate different log levels
|
||||
await server.notification({
|
||||
method: "notifications/message",
|
||||
params: {
|
||||
level: "debug",
|
||||
logger: "test-server",
|
||||
data: `Logging level set to: ${logLevel}`,
|
||||
},
|
||||
});
|
||||
|
||||
return {};
|
||||
});
|
||||
|
||||
// Roots protocol handlers
|
||||
server.setNotificationHandler(RootsListChangedNotificationSchema, async () => {
|
||||
try {
|
||||
|
|
@ -944,24 +973,18 @@ export const createServer = () => {
|
|||
currentRoots = response.roots;
|
||||
|
||||
// Log the roots update for demonstration
|
||||
await server.notification({
|
||||
method: "notifications/message",
|
||||
params: {
|
||||
await server.sendLoggingMessage({
|
||||
level: "info",
|
||||
logger: "everything-server",
|
||||
data: `Roots updated: ${currentRoots.length} root(s) received from client`,
|
||||
},
|
||||
});
|
||||
}, sessionId);
|
||||
}
|
||||
} catch (error) {
|
||||
await server.notification({
|
||||
method: "notifications/message",
|
||||
params: {
|
||||
await server.sendLoggingMessage({
|
||||
level: "error",
|
||||
logger: "everything-server",
|
||||
data: `Failed to request roots from client: ${error instanceof Error ? error.message : String(error)}`,
|
||||
},
|
||||
});
|
||||
}, sessionId);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -976,43 +999,31 @@ export const createServer = () => {
|
|||
if (response && 'roots' in response) {
|
||||
currentRoots = response.roots;
|
||||
|
||||
await server.notification({
|
||||
method: "notifications/message",
|
||||
params: {
|
||||
await server.sendLoggingMessage({
|
||||
level: "info",
|
||||
logger: "everything-server",
|
||||
data: `Initial roots received: ${currentRoots.length} root(s) from client`,
|
||||
},
|
||||
});
|
||||
}, sessionId);
|
||||
} else {
|
||||
await server.notification({
|
||||
method: "notifications/message",
|
||||
params: {
|
||||
await server.sendLoggingMessage({
|
||||
level: "warning",
|
||||
logger: "everything-server",
|
||||
data: "Client returned no roots set",
|
||||
},
|
||||
});
|
||||
}, sessionId);
|
||||
}
|
||||
} catch (error) {
|
||||
await server.notification({
|
||||
method: "notifications/message",
|
||||
params: {
|
||||
await server.sendLoggingMessage({
|
||||
level: "error",
|
||||
logger: "everything-server",
|
||||
data: `Failed to request initial roots from client: ${error instanceof Error ? error.message : String(error)}`,
|
||||
},
|
||||
});
|
||||
}, sessionId);
|
||||
}
|
||||
} else {
|
||||
await server.notification({
|
||||
method: "notifications/message",
|
||||
params: {
|
||||
await server.sendLoggingMessage({
|
||||
level: "info",
|
||||
logger: "everything-server",
|
||||
data: "Client does not support MCP roots protocol",
|
||||
},
|
||||
});
|
||||
}, sessionId);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -24,10 +24,12 @@
|
|||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "^1.19.1",
|
||||
"express": "^4.21.1",
|
||||
"jszip": "^3.10.1",
|
||||
"zod": "^3.23.8",
|
||||
"zod-to-json-schema": "^3.23.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/cors": "^2.8.19",
|
||||
"@types/express": "^5.0.0",
|
||||
"shx": "^0.3.4",
|
||||
"typescript": "^5.6.2"
|
||||
|
|
|
|||
|
|
@ -1,11 +1,17 @@
|
|||
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
|
||||
import express from "express";
|
||||
import { createServer } from "./everything.js";
|
||||
import cors from 'cors';
|
||||
|
||||
console.error('Starting SSE server...');
|
||||
|
||||
const app = express();
|
||||
|
||||
app.use(cors({
|
||||
"origin": "*", // use "*" with caution in production
|
||||
"methods": "GET,POST",
|
||||
"preflightContinue": false,
|
||||
"optionsSuccessStatus": 204,
|
||||
})); // Enable CORS for all routes so Inspector can connect
|
||||
const transports: Map<string, SSEServerTransport> = new Map<string, SSEServerTransport>();
|
||||
|
||||
app.get("/sse", async (req, res) => {
|
||||
|
|
@ -26,7 +32,7 @@ app.get("/sse", async (req, res) => {
|
|||
console.error("Client Connected: ", transport.sessionId);
|
||||
|
||||
// Start notification intervals after client connects
|
||||
startNotificationIntervals();
|
||||
startNotificationIntervals(transport.sessionId);
|
||||
|
||||
// Handle close of connection
|
||||
server.onclose = async () => {
|
||||
|
|
|
|||
|
|
@ -6,17 +6,18 @@ import { createServer } from "./everything.js";
|
|||
console.error('Starting default (STDIO) server...');
|
||||
|
||||
async function main() {
|
||||
const transport = new StdioServerTransport();
|
||||
const {server, cleanup} = createServer();
|
||||
const transport = new StdioServerTransport();
|
||||
const {server, cleanup, startNotificationIntervals} = createServer();
|
||||
|
||||
await server.connect(transport);
|
||||
await server.connect(transport);
|
||||
startNotificationIntervals();
|
||||
|
||||
// Cleanup on exit
|
||||
process.on("SIGINT", async () => {
|
||||
await cleanup();
|
||||
await server.close();
|
||||
process.exit(0);
|
||||
});
|
||||
// Cleanup on exit
|
||||
process.on("SIGINT", async () => {
|
||||
await cleanup();
|
||||
await server.close();
|
||||
process.exit(0);
|
||||
});
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
|
|
|
|||
|
|
@ -3,10 +3,22 @@ import { InMemoryEventStore } from '@modelcontextprotocol/sdk/examples/shared/in
|
|||
import express, { Request, Response } from "express";
|
||||
import { createServer } from "./everything.js";
|
||||
import { randomUUID } from 'node:crypto';
|
||||
import cors from 'cors';
|
||||
|
||||
console.error('Starting Streamable HTTP server...');
|
||||
|
||||
const app = express();
|
||||
app.use(cors({
|
||||
"origin": "*", // use "*" with caution in production
|
||||
"methods": "GET,POST,DELETE",
|
||||
"preflightContinue": false,
|
||||
"optionsSuccessStatus": 204,
|
||||
"exposedHeaders": [
|
||||
'mcp-session-id',
|
||||
'last-event-id',
|
||||
'mcp-protocol-version'
|
||||
]
|
||||
})); // Enable CORS for all routes so Inspector can connect
|
||||
|
||||
const transports: Map<string, StreamableHTTPServerTransport> = new Map<string, StreamableHTTPServerTransport>();
|
||||
|
||||
|
|
@ -15,6 +27,7 @@ app.post('/mcp', async (req: Request, res: Response) => {
|
|||
try {
|
||||
// Check for existing session ID
|
||||
const sessionId = req.headers['mcp-session-id'] as string | undefined;
|
||||
|
||||
let transport: StreamableHTTPServerTransport;
|
||||
|
||||
if (sessionId && transports.has(sessionId)) {
|
||||
|
|
@ -22,7 +35,7 @@ app.post('/mcp', async (req: Request, res: Response) => {
|
|||
transport = transports.get(sessionId)!;
|
||||
} else if (!sessionId) {
|
||||
|
||||
const { server, cleanup } = createServer();
|
||||
const { server, cleanup, startNotificationIntervals } = createServer();
|
||||
|
||||
// New initialization request
|
||||
const eventStore = new InMemoryEventStore();
|
||||
|
|
@ -53,7 +66,11 @@ app.post('/mcp', async (req: Request, res: Response) => {
|
|||
await server.connect(transport);
|
||||
|
||||
await transport.handleRequest(req, res);
|
||||
return; // Already handled
|
||||
|
||||
// Wait until initialize is complete and transport will have a sessionId
|
||||
startNotificationIntervals(transport.sessionId);
|
||||
|
||||
return; // Already handled
|
||||
} else {
|
||||
// Invalid request - no session ID or not initialization request
|
||||
res.status(400).json({
|
||||
|
|
|
|||
|
|
@ -128,15 +128,6 @@ The server's directory access control follows this flow:
|
|||
- Returns detailed listing with file sizes and summary statistics
|
||||
- Shows total files, directories, and combined size
|
||||
|
||||
- **directory_tree**
|
||||
- Get a recursive tree view of files and directories as a JSON structure
|
||||
- Input: `path` (string): Starting directory path
|
||||
- Returns JSON structure with:
|
||||
- `name`: File/directory name
|
||||
- `type`: "file" or "directory"
|
||||
- `children`: Array of child entries (for directories only)
|
||||
- Output is formatted with 2-space indentation for readability
|
||||
|
||||
- **move_file**
|
||||
- Move or rename files and directories
|
||||
- Inputs:
|
||||
|
|
@ -165,6 +156,7 @@ The server's directory access control follows this flow:
|
|||
- `children` (array): Present only for directories
|
||||
- Empty array for empty directories
|
||||
- Omitted for files
|
||||
- Output is formatted with 2-space indentation for readability
|
||||
|
||||
- **get_file_info**
|
||||
- Get detailed file/directory metadata
|
||||
|
|
|
|||
|
|
@ -84,13 +84,8 @@ Please note that mcp-server-git is currently in early development. The functiona
|
|||
- `repo_path` (string): Path to Git repository
|
||||
- `revision` (string): The revision (commit hash, branch name, tag) to show
|
||||
- Returns: Contents of the specified commit
|
||||
12. `git_init`
|
||||
- Initializes a Git repository
|
||||
- Inputs:
|
||||
- `repo_path` (string): Path to directory to initialize git repo
|
||||
- Returns: Confirmation of repository initialization
|
||||
|
||||
13. `git_branch`
|
||||
12. `git_branch`
|
||||
- List Git branches
|
||||
- Inputs:
|
||||
- `repo_path` (string): Path to the Git repository.
|
||||
|
|
|
|||
|
|
@ -70,8 +70,7 @@ class GitShow(BaseModel):
|
|||
repo_path: str
|
||||
revision: str
|
||||
|
||||
class GitInit(BaseModel):
|
||||
repo_path: str
|
||||
|
||||
|
||||
class GitBranch(BaseModel):
|
||||
repo_path: str = Field(
|
||||
|
|
@ -104,7 +103,7 @@ class GitTools(str, Enum):
|
|||
CREATE_BRANCH = "git_create_branch"
|
||||
CHECKOUT = "git_checkout"
|
||||
SHOW = "git_show"
|
||||
INIT = "git_init"
|
||||
|
||||
BRANCH = "git_branch"
|
||||
|
||||
def git_status(repo: git.Repo) -> str:
|
||||
|
|
@ -183,12 +182,7 @@ def git_checkout(repo: git.Repo, branch_name: str) -> str:
|
|||
repo.git.checkout(branch_name)
|
||||
return f"Switched to branch '{branch_name}'"
|
||||
|
||||
def git_init(repo_path: str) -> str:
|
||||
try:
|
||||
repo = git.Repo.init(path=repo_path, mkdir=True)
|
||||
return f"Initialized empty Git repository in {repo.git_dir}"
|
||||
except Exception as e:
|
||||
return f"Error initializing repository: {str(e)}"
|
||||
|
||||
|
||||
def git_show(repo: git.Repo, revision: str) -> str:
|
||||
commit = repo.commit(revision)
|
||||
|
|
@ -308,11 +302,7 @@ async def serve(repository: Path | None) -> None:
|
|||
description="Shows the contents of a commit",
|
||||
inputSchema=GitShow.model_json_schema(),
|
||||
),
|
||||
Tool(
|
||||
name=GitTools.INIT,
|
||||
description="Initialize a new Git repository",
|
||||
inputSchema=GitInit.model_json_schema(),
|
||||
),
|
||||
|
||||
Tool(
|
||||
name=GitTools.BRANCH,
|
||||
description="List Git branches",
|
||||
|
|
@ -354,15 +344,7 @@ async def serve(repository: Path | None) -> None:
|
|||
async def call_tool(name: str, arguments: dict) -> list[TextContent]:
|
||||
repo_path = Path(arguments["repo_path"])
|
||||
|
||||
# Handle git init separately since it doesn't require an existing repo
|
||||
if name == GitTools.INIT:
|
||||
result = git_init(str(repo_path))
|
||||
return [TextContent(
|
||||
type="text",
|
||||
text=result
|
||||
)]
|
||||
|
||||
# For all other commands, we need an existing repo
|
||||
# For all commands, we need an existing repo
|
||||
repo = git.Repo(repo_path)
|
||||
|
||||
match name:
|
||||
|
|
|
|||
|
|
@ -60,8 +60,18 @@ class KnowledgeGraphManager {
|
|||
|
||||
private async saveGraph(graph: KnowledgeGraph): Promise<void> {
|
||||
const lines = [
|
||||
...graph.entities.map(e => JSON.stringify({ type: "entity", ...e })),
|
||||
...graph.relations.map(r => JSON.stringify({ type: "relation", ...r })),
|
||||
...graph.entities.map(e => JSON.stringify({
|
||||
type: "entity",
|
||||
name: e.name,
|
||||
entityType: e.entityType,
|
||||
observations: e.observations
|
||||
})),
|
||||
...graph.relations.map(r => JSON.stringify({
|
||||
type: "relation",
|
||||
from: r.from,
|
||||
to: r.to,
|
||||
relationType: r.relationType
|
||||
})),
|
||||
];
|
||||
await fs.writeFile(MEMORY_FILE_PATH, lines.join("\n"));
|
||||
}
|
||||
|
|
@ -219,10 +229,12 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|||
},
|
||||
},
|
||||
required: ["name", "entityType", "observations"],
|
||||
additionalProperties: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
required: ["entities"],
|
||||
additionalProperties: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -241,10 +253,12 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|||
relationType: { type: "string", description: "The type of the relation" },
|
||||
},
|
||||
required: ["from", "to", "relationType"],
|
||||
additionalProperties: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
required: ["relations"],
|
||||
additionalProperties: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -266,10 +280,12 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|||
},
|
||||
},
|
||||
required: ["entityName", "contents"],
|
||||
additionalProperties: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
required: ["observations"],
|
||||
additionalProperties: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -285,6 +301,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|||
},
|
||||
},
|
||||
required: ["entityNames"],
|
||||
additionalProperties: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -306,10 +323,12 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|||
},
|
||||
},
|
||||
required: ["entityName", "observations"],
|
||||
additionalProperties: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
required: ["deletions"],
|
||||
additionalProperties: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -328,11 +347,13 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|||
relationType: { type: "string", description: "The type of the relation" },
|
||||
},
|
||||
required: ["from", "to", "relationType"],
|
||||
additionalProperties: false,
|
||||
},
|
||||
description: "An array of relations to delete"
|
||||
},
|
||||
},
|
||||
required: ["relations"],
|
||||
additionalProperties: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -341,6 +362,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {},
|
||||
additionalProperties: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -352,6 +374,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|||
query: { type: "string", description: "The search query to match against entity names, types, and observation content" },
|
||||
},
|
||||
required: ["query"],
|
||||
additionalProperties: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -367,6 +390,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|||
},
|
||||
},
|
||||
required: ["names"],
|
||||
additionalProperties: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
|
|
|
|||
|
|
@ -46,7 +46,8 @@ def get_local_tz(local_tz_override: str | None = None) -> ZoneInfo:
|
|||
local_tzname = get_localzone_name()
|
||||
if local_tzname is not None:
|
||||
return ZoneInfo(local_tzname)
|
||||
raise McpError("Could not determine local timezone - tzinfo is None")
|
||||
# Default to UTC if local timezone cannot be determined
|
||||
return ZoneInfo("UTC")
|
||||
|
||||
|
||||
def get_zoneinfo(timezone_name: str) -> ZoneInfo:
|
||||
|
|
|
|||
|
|
@ -486,10 +486,10 @@ def test_get_local_tz_with_valid_iana_name(mock_get_localzone):
|
|||
|
||||
@patch('mcp_server_time.server.get_localzone_name')
|
||||
def test_get_local_tz_when_none_returned(mock_get_localzone):
|
||||
"""Test error when tzlocal returns None."""
|
||||
"""Test default to UTC when tzlocal returns None."""
|
||||
mock_get_localzone.return_value = None
|
||||
with pytest.raises(McpError, match="Could not determine local timezone"):
|
||||
get_local_tz()
|
||||
result = get_local_tz()
|
||||
assert str(result) == "UTC"
|
||||
|
||||
|
||||
@patch('mcp_server_time.server.get_localzone_name')
|
||||
|
|
|
|||
Loading…
Reference in a new issue