mirror of
https://github.com/lighttransport/tinyusdz.git
synced 2026-01-18 01:11:17 +01:00
mcp w.i.p.
This commit is contained in:
@@ -1,9 +1,18 @@
|
||||
hostname=localhost
|
||||
port_no=8085
|
||||
entrypoint=mcp
|
||||
|
||||
sess_id=`cat sess_id.txt | tr -d '\r'`
|
||||
sess_header="mcp-session-id: ${sess_id}"
|
||||
|
||||
curl -X POST \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Accept: application/json, text/event-stream" \
|
||||
-H "${sess_header}" \
|
||||
-d '{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "tools/list",
|
||||
"params": {},
|
||||
"id": 2
|
||||
}' \
|
||||
http://localhost:8085/mcp
|
||||
http://${hostname}:${port_no}/${entrypoint}
|
||||
|
||||
1
models/suzanne-base64.txt
Normal file
1
models/suzanne-base64.txt
Normal file
File diff suppressed because one or more lines are too long
140
src/str-util.cc
140
src/str-util.cc
@@ -716,5 +716,145 @@ double atof(const std::string &s) {
|
||||
return atof(s.c_str());
|
||||
}
|
||||
|
||||
/*
|
||||
base64.cpp and base64.h
|
||||
|
||||
Copyright (C) 2004-2008 René Nyffenegger
|
||||
|
||||
This source code is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the author be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this source code must not be misrepresented; you must not
|
||||
claim that you wrote the original source code. If you use this source code
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original source code.
|
||||
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
|
||||
René Nyffenegger rene.nyffenegger@adp-gmbh.ch
|
||||
|
||||
*/
|
||||
|
||||
#ifdef __clang__
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wsign-conversion"
|
||||
#pragma clang diagnostic ignored "-Wconversion"
|
||||
#endif
|
||||
|
||||
static inline bool is_base64(unsigned char c) {
|
||||
return (isalnum(c) || (c == '+') || (c == '/'));
|
||||
}
|
||||
|
||||
std::string base64_encode(unsigned char const *bytes_to_encode,
|
||||
unsigned int in_len) {
|
||||
std::string ret;
|
||||
int i = 0;
|
||||
int j = 0;
|
||||
unsigned char char_array_3[3];
|
||||
unsigned char char_array_4[4];
|
||||
|
||||
const char *base64_chars =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
"abcdefghijklmnopqrstuvwxyz"
|
||||
"0123456789+/";
|
||||
|
||||
while (in_len--) {
|
||||
char_array_3[i++] = *(bytes_to_encode++);
|
||||
if (i == 3) {
|
||||
char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
|
||||
char_array_4[1] =
|
||||
((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
|
||||
char_array_4[2] =
|
||||
((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
|
||||
char_array_4[3] = char_array_3[2] & 0x3f;
|
||||
|
||||
for (i = 0; (i < 4); i++) ret += base64_chars[char_array_4[i]];
|
||||
i = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (i) {
|
||||
for (j = i; j < 3; j++) char_array_3[j] = '\0';
|
||||
|
||||
char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
|
||||
char_array_4[1] =
|
||||
((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
|
||||
char_array_4[2] =
|
||||
((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
|
||||
|
||||
for (j = 0; (j < i + 1); j++) ret += base64_chars[char_array_4[j]];
|
||||
|
||||
while ((i++ < 3)) ret += '=';
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::string base64_decode(std::string const &encoded_string) {
|
||||
int in_len = static_cast<int>(encoded_string.size());
|
||||
int i = 0;
|
||||
int j = 0;
|
||||
int in_ = 0;
|
||||
unsigned char char_array_4[4], char_array_3[3];
|
||||
std::string ret;
|
||||
|
||||
const std::string base64_chars =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
"abcdefghijklmnopqrstuvwxyz"
|
||||
"0123456789+/";
|
||||
|
||||
while (in_len-- && (encoded_string[in_] != '=') &&
|
||||
is_base64(encoded_string[in_])) {
|
||||
char_array_4[i++] = encoded_string[in_];
|
||||
in_++;
|
||||
if (i == 4) {
|
||||
for (i = 0; i < 4; i++)
|
||||
char_array_4[i] =
|
||||
static_cast<unsigned char>(base64_chars.find(char_array_4[i]));
|
||||
|
||||
char_array_3[0] =
|
||||
(char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
|
||||
char_array_3[1] =
|
||||
((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
|
||||
char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
|
||||
|
||||
for (i = 0; (i < 3); i++) ret += char_array_3[i];
|
||||
i = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (i) {
|
||||
for (j = i; j < 4; j++) char_array_4[j] = 0;
|
||||
|
||||
for (j = 0; j < 4; j++)
|
||||
char_array_4[j] =
|
||||
static_cast<unsigned char>(base64_chars.find(char_array_4[j]));
|
||||
|
||||
char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
|
||||
char_array_3[1] =
|
||||
((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
|
||||
char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
|
||||
|
||||
for (j = 0; (j < i - 1); j++) ret += char_array_3[j];
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
#ifdef __clang__
|
||||
#pragma clang diagnostic pop
|
||||
#endif
|
||||
|
||||
/*
|
||||
-- end base64.cpp and base64.h
|
||||
*/
|
||||
|
||||
|
||||
} // namespace tinyusdz
|
||||
|
||||
@@ -400,4 +400,8 @@ inline std::string join(const std::string& sep, It& v)
|
||||
double atof(const char *s);
|
||||
double atof(const std::string &s);
|
||||
|
||||
std::string base64_encode(unsigned char const *bytes_to_encode,
|
||||
unsigned int in_len);
|
||||
std::string base64_decode(std::string const &encoded_string);
|
||||
|
||||
} // namespace tinyusdz
|
||||
|
||||
@@ -26,7 +26,8 @@ struct Context
|
||||
// key = UUID
|
||||
std::unordered_map<std::string, USDLayer> layers;
|
||||
|
||||
std::unordered_set<std::string> resources;
|
||||
// key = URI, value = UUID
|
||||
std::unordered_map<std::string, std::string> resources;
|
||||
};
|
||||
|
||||
} // namespace mcp
|
||||
|
||||
@@ -21,51 +21,55 @@ namespace mcp {
|
||||
namespace {
|
||||
|
||||
static bool ListResourcesImpl(const Context &ctx, json &result) {
|
||||
for (const auto &res_uuid : ctx.resources) {
|
||||
|
||||
result["resources"] = nlohmann::json::array();
|
||||
|
||||
for (const auto &res : ctx.resources) {
|
||||
|
||||
if (!ctx.layers.count(res_uuid)) {
|
||||
if (!ctx.layers.count(res.second)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
json res_j;
|
||||
|
||||
|
||||
res_j["uri"] = ctx.layers.at(res_uuid).uri;
|
||||
res_j["name"] = ctx.layers.at(res_uuid).uri; // FIXME
|
||||
res_j["uri"] = ctx.layers.at(res.second).uri;
|
||||
res_j["name"] = ctx.layers.at(res.second).uri; // FIXME
|
||||
res_j["mimeType"] = "application/octet-stream"; // FIXME
|
||||
// TODO: size, title, description
|
||||
|
||||
result["resources"].push_back(res_j);
|
||||
}
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool GetResourcesList(const Context &ctx, nlohmann::json &result) {
|
||||
|
||||
return ListResourcesImpl(ctx, result);
|
||||
}
|
||||
|
||||
bool ReadResource(const Context &ctx, const std::string &uuid, nlohmann::json &result) {
|
||||
bool ReadResource(const Context &ctx, const std::string &uri, nlohmann::json &result) {
|
||||
// TODO: multiple resources
|
||||
|
||||
(void)uuid;
|
||||
(void)result;
|
||||
|
||||
if (!ctx.resources.count(uuid)) {
|
||||
if (!ctx.resources.count(uri)) {
|
||||
// TODO: report error
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::string &uuid = ctx.resources.at(uri);
|
||||
|
||||
if (!ctx.layers.count(uuid)) {
|
||||
// This should not happen though.
|
||||
return false;
|
||||
}
|
||||
|
||||
json res;
|
||||
res["uri"] = ctx.layers.at(uuid).uri;
|
||||
res["name"] = ctx.layers.at(uuid).name;
|
||||
res["uri"] = uri;
|
||||
res["name"] = uri; // FIXME
|
||||
res["mimeType"] = "text/plain";
|
||||
// TODO: title
|
||||
|
||||
|
||||
@@ -381,20 +381,20 @@ bool MCPServer::Impl::init(int port, const std::string &host) {
|
||||
return {};
|
||||
});
|
||||
|
||||
register_method("resources/list",[](const nlohmann::json ¶ms, const std::string &sess_id, std::string &err) -> nlohmann::json {
|
||||
register_method("resources/list",[this](const nlohmann::json ¶ms, const std::string &sess_id, std::string &err) -> nlohmann::json {
|
||||
|
||||
(void)err;
|
||||
(void)params;
|
||||
(void)sess_id;
|
||||
|
||||
nlohmann::json j;
|
||||
mcp::GetResourcesList(j);
|
||||
mcp::GetResourcesList(mcp_ctx_, j);
|
||||
|
||||
return j;
|
||||
|
||||
});
|
||||
|
||||
register_method("resources/read",[](const nlohmann::json ¶ms, const std::string &sess_id, std::string &err) -> nlohmann::json {
|
||||
register_method("resources/read",[this](const nlohmann::json ¶ms, const std::string &sess_id, std::string &err) -> nlohmann::json {
|
||||
|
||||
(void)err;
|
||||
(void)params;
|
||||
@@ -407,7 +407,7 @@ bool MCPServer::Impl::init(int port, const std::string &host) {
|
||||
|
||||
std::string uri = params["uri"];
|
||||
nlohmann::json result;
|
||||
if (!mcp::ReadResource(uri, result)) {
|
||||
if (!mcp::ReadResource(mcp_ctx_, uri, result)) {
|
||||
err += "Failed to read resource: " + uri;
|
||||
return {};
|
||||
}
|
||||
@@ -416,14 +416,14 @@ bool MCPServer::Impl::init(int port, const std::string &host) {
|
||||
|
||||
});
|
||||
|
||||
register_method("tools/list",[](const nlohmann::json ¶ms, const std::string &sess_id, std::string &err) -> nlohmann::json {
|
||||
register_method("tools/list",[this](const nlohmann::json ¶ms, const std::string &sess_id, std::string &err) -> nlohmann::json {
|
||||
|
||||
(void)err;
|
||||
(void)params;
|
||||
(void)sess_id;
|
||||
|
||||
nlohmann::json j;
|
||||
mcp::GetToolsList(j);
|
||||
mcp::GetToolsList(mcp_ctx_, j);
|
||||
|
||||
return j;
|
||||
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
#include <string>
|
||||
|
||||
#include "mcp-tools.hh"
|
||||
#include "mcp-server.hh"
|
||||
#include "mcp-context.hh"
|
||||
#include <string>
|
||||
#include "tinyusdz.hh"
|
||||
#include "uuid-gen.hh"
|
||||
#include "str-util.hh"
|
||||
#include "pprinter.hh"
|
||||
|
||||
#ifdef __clang__
|
||||
#pragma clang diagnostic push
|
||||
@@ -21,8 +24,30 @@ namespace tydra {
|
||||
namespace mcp {
|
||||
|
||||
namespace {
|
||||
|
||||
inline std::string decode_datauri(const std::string &data) {
|
||||
|
||||
const std::string prefix = "data:application/octet-stream;base64,";
|
||||
|
||||
if (!startsWith(data, prefix)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (data.size() <= prefix.size()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
// TODO: save memory
|
||||
std::string binary = base64_decode(removePrefix(data, prefix));
|
||||
|
||||
return binary;
|
||||
|
||||
|
||||
}
|
||||
|
||||
bool GetVersion(nlohmann::json &result);
|
||||
bool LoadUSDLayerFromFile(Context &ctx, const nlohmann::json &args, nlohmann::json &result, std::string &err);
|
||||
bool LoadUSDLayerFromDataURI(Context &ctx, const nlohmann::json &args, nlohmann::json &result, std::string &err);
|
||||
|
||||
|
||||
bool GetVersion(nlohmann::json &result) {
|
||||
@@ -83,7 +108,65 @@ bool LoadUSDLayerFromFile(Context &ctx, const nlohmann::json &args, nlohmann::js
|
||||
usd_layer.layer = std::move(layer);
|
||||
|
||||
ctx.layers.emplace(uuid, std::move(usd_layer));
|
||||
ctx.resources.insert(uuid);
|
||||
ctx.resources.emplace(uri, uuid);
|
||||
|
||||
DCOUT("loaded USD as Layer");
|
||||
|
||||
nlohmann::json content;
|
||||
content["type"] = "text";
|
||||
content["text"] = uuid;
|
||||
|
||||
result["content"] = nlohmann::json::array();
|
||||
result["content"].push_back(content);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LoadUSDLayerFromDataURI(Context &ctx, const nlohmann::json &args, nlohmann::json &result, std::string &err) {
|
||||
DCOUT("args " << args);
|
||||
if (!args.contains("uri")) {
|
||||
DCOUT("uri param not found");
|
||||
err = "`uri` param not found.\n";
|
||||
return false;
|
||||
}
|
||||
if (!args.contains("name")) {
|
||||
DCOUT("name param not found");
|
||||
err = "`name` param not found.\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string name = args["name"];
|
||||
|
||||
std::string binary = decode_datauri(args.at("uri"));
|
||||
|
||||
Layer layer;
|
||||
std::string warn;
|
||||
USDLoadOptions options;
|
||||
if (!LoadLayerFromMemory(reinterpret_cast<const uint8_t *>(binary.c_str()), binary.size(), name, &layer, &warn, &err, options)) {
|
||||
DCOUT("Failed to load layer from DataURI: " << err);
|
||||
err = "Failed to load layer from DataURI: " + err + "\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!warn.empty()) {
|
||||
result["warnings"] = warn;
|
||||
}
|
||||
|
||||
std::string uuid = generateUUID();
|
||||
|
||||
if (ctx.layers.count(uuid)) {
|
||||
DCOUT("uuid conflict");
|
||||
// This should not be happen.
|
||||
err = "Internal error. UUID conflict\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
USDLayer usd_layer;
|
||||
usd_layer.uri = name;
|
||||
usd_layer.layer = std::move(layer);
|
||||
|
||||
ctx.layers.emplace(uuid, std::move(usd_layer));
|
||||
ctx.resources.emplace(name, uuid);
|
||||
|
||||
DCOUT("loaded USD as Layer");
|
||||
|
||||
@@ -127,9 +210,47 @@ bool ListPrimSpecs(Context &ctx, const nlohmann::json &args, nlohmann::json &res
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ToUSDA(Context &ctx, const nlohmann::json &args, nlohmann::json &result, std::string &err) {
|
||||
DCOUT("args " << args);
|
||||
if (!args.contains("uri")) {
|
||||
DCOUT("uri param not found");
|
||||
err = "`uri` param not found.\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string uri = args.at("uri");
|
||||
|
||||
if (!ctx.resources.count(uri)) {
|
||||
err = "Resource not found: " + uri + "\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string uuid = ctx.resources.at(uri);
|
||||
|
||||
if (!ctx.layers.count(uuid)) {
|
||||
// This should not happen though.
|
||||
err = "Internal error. No corresponding Layer found\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
nlohmann::json content;
|
||||
content["type"] = "text";
|
||||
content["mimeType"] = "text/plain";
|
||||
|
||||
const Layer &layer = ctx.layers.at(uuid).layer;
|
||||
std::string str = to_string(layer); // to USDA
|
||||
content["text"] = str;
|
||||
|
||||
result["content"] = nlohmann::json::array();
|
||||
result["content"].push_back(content);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool GetToolsList(nlohmann::json &result) {
|
||||
bool GetToolsList(Context &ctx, nlohmann::json &result) {
|
||||
(void)ctx;
|
||||
|
||||
result["tools"] = nlohmann::json::array();
|
||||
|
||||
@@ -150,8 +271,8 @@ bool GetToolsList(nlohmann::json &result) {
|
||||
|
||||
{
|
||||
nlohmann::json j;
|
||||
j["name"] = "load_usd_layer";
|
||||
j["description"] = "Load USD as Layer from URI(currently local file only)";
|
||||
j["name"] = "load_usd_layer_from_file";
|
||||
j["description"] = "Load USD as Layer from a file(only works in C++ native binary)";
|
||||
|
||||
nlohmann::json schema;
|
||||
schema["type"] = "object";
|
||||
@@ -166,6 +287,25 @@ bool GetToolsList(nlohmann::json &result) {
|
||||
|
||||
}
|
||||
|
||||
{
|
||||
nlohmann::json j;
|
||||
j["name"] = "load_usd_layer_from_datauri";
|
||||
j["description"] = "Load USD as Layer from DataURI";
|
||||
|
||||
nlohmann::json schema;
|
||||
schema["type"] = "object";
|
||||
schema["properties"] = nlohmann::json::object();
|
||||
schema["properties"]["uri"] ={{"type", "string"}};
|
||||
schema["properties"]["name"] ={{"type", "string"}};
|
||||
|
||||
schema["required"] = nlohmann::json::array({"uri", "name"});
|
||||
|
||||
j["inputSchema"] = schema;
|
||||
|
||||
result["tools"].push_back(j);
|
||||
|
||||
}
|
||||
|
||||
{
|
||||
nlohmann::json j;
|
||||
j["name"] = "list_primspecs";
|
||||
@@ -184,6 +324,24 @@ bool GetToolsList(nlohmann::json &result) {
|
||||
|
||||
}
|
||||
|
||||
{
|
||||
nlohmann::json j;
|
||||
j["name"] = "to_usda";
|
||||
j["description"] = "Convert USD Layer to USDA text";
|
||||
|
||||
nlohmann::json schema;
|
||||
schema["type"] = "object";
|
||||
schema["properties"] = nlohmann::json::object();
|
||||
schema["properties"]["uri"] ={{"type", "string"}};
|
||||
|
||||
schema["required"] = nlohmann::json::array({"uri"});
|
||||
|
||||
j["inputSchema"] = schema;
|
||||
|
||||
result["tools"].push_back(j);
|
||||
|
||||
}
|
||||
|
||||
std::cout << result << "\n";
|
||||
|
||||
return true;
|
||||
@@ -195,9 +353,15 @@ bool CallTool(Context &ctx, const std::string &tool_name, const nlohmann::json &
|
||||
|
||||
if (tool_name == "get_version") {
|
||||
return GetVersion(result);
|
||||
} else if (tool_name == "load_usd_layer") {
|
||||
DCOUT("load_usd_layer");
|
||||
} else if (tool_name == "load_usd_layer_from_file") {
|
||||
DCOUT("load_usd_layer_from_file");
|
||||
return LoadUSDLayerFromFile(ctx, args, result, err);
|
||||
} else if (tool_name == "to_usda") {
|
||||
DCOUT("to_usda");
|
||||
return ToUSDA(ctx, args, result, err);
|
||||
} else if (tool_name == "load_usd_layer_from_datauri") {
|
||||
DCOUT("load_usd_layer_datauri");
|
||||
return LoadUSDLayerFromDataURI(ctx, args, result, err);
|
||||
} else if (tool_name == "list_primspecs") {
|
||||
DCOUT("list_primspecs");
|
||||
return ListPrimSpecs(ctx, args, result, err);
|
||||
|
||||
@@ -22,6 +22,7 @@ namespace mcp {
|
||||
|
||||
// for 'tools/list'
|
||||
bool GetToolsList(
|
||||
Context &ctx,
|
||||
nlohmann::json &result);
|
||||
|
||||
|
||||
|
||||
143
web/binding.cc
143
web/binding.cc
@@ -12,15 +12,27 @@
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
|
||||
#include "external/fast_float/include/fast_float/bigint.h"
|
||||
//#include "external/fast_float/include/fast_float/bigint.h"
|
||||
#include "tinyusdz.hh"
|
||||
#include "pprinter.hh"
|
||||
#include "tydra/render-data.hh"
|
||||
#include "tydra/scene-access.hh"
|
||||
|
||||
#include "tydra/mcp-context.hh"
|
||||
#include "tydra/mcp-resources.hh"
|
||||
#include "tydra/mcp-tools.hh"
|
||||
|
||||
#ifdef __clang__
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Weverything"
|
||||
#endif
|
||||
|
||||
#include "external/jsonhpp/nlohmann/json.hpp"
|
||||
|
||||
#ifdef __clang__
|
||||
#pragma clang diagnostic pop
|
||||
#endif
|
||||
|
||||
// Handling Asset
|
||||
// Due to the limitatrion of C++(synchronous) initiated async file(fetch) read,
|
||||
// We decided to fetch asset in JavaScript layer.
|
||||
@@ -1149,36 +1161,125 @@ class TinyUSDZLoaderNative {
|
||||
return val;
|
||||
}
|
||||
|
||||
emscripten::val mcpToolsList() const {
|
||||
emscripten::val val;
|
||||
bool mcpCreateContext(const std::string &session_id) {
|
||||
|
||||
if (mcp_ctx_.count(session_id)) {
|
||||
// Context already exists
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO
|
||||
mcp_ctx_[session_id] = tinyusdz::tydra::mcp::Context();
|
||||
mcp_session_id_ = session_id;
|
||||
|
||||
return val;
|
||||
return true;
|
||||
}
|
||||
|
||||
emscripten::val mcpToolsCall() const {
|
||||
emscripten::val val;
|
||||
bool mcpSelectContext(const std::string &session_id) {
|
||||
|
||||
if (!mcp_ctx_.count(session_id)) {
|
||||
// Context does not exist
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO
|
||||
mcp_session_id_ = session_id;
|
||||
|
||||
return val;
|
||||
return true;
|
||||
}
|
||||
|
||||
emscripten::val mcpResourcesList() const {
|
||||
emscripten::val val;
|
||||
|
||||
// TODO
|
||||
// return JSON string
|
||||
std::string mcpToolsList() {
|
||||
|
||||
return val;
|
||||
if (!mcp_ctx_.count(mcp_session_id_)) {
|
||||
// TODO: better error message
|
||||
return "{ \"error\": \"invalid session_id\"}";
|
||||
}
|
||||
|
||||
//Context &ctx = mcp_ctx_.at(mcp_session_id_);
|
||||
tinyusdz::tydra::mcp::Context &ctx = mcp_global_ctx_;
|
||||
|
||||
nlohmann::json result;
|
||||
if (!tinyusdz::tydra::mcp::GetToolsList(ctx, result)) {
|
||||
std::cerr << "[tydra:mcp:GetToolsList] failed." << "\n";
|
||||
// TODO: Report error more nice way.
|
||||
}
|
||||
|
||||
std::string s_result = result.dump();
|
||||
|
||||
return s_result;
|
||||
}
|
||||
|
||||
emscripten::val mcpResourcesRead() const {
|
||||
emscripten::val val;
|
||||
// args: JSON string
|
||||
// return JSON string
|
||||
std::string mcpToolsCall(const std::string &tool_name, const std::string &args) {
|
||||
|
||||
// TODO
|
||||
if (!mcp_ctx_.count(mcp_session_id_)) {
|
||||
// TODO: better error message
|
||||
return "{ \"error\": \"invalid session_id\"}";
|
||||
}
|
||||
|
||||
return val;
|
||||
nlohmann::json j_args = nlohmann::json::parse(args);
|
||||
|
||||
//Context &ctx = mcp_ctx_.at(mcp_session_id_);
|
||||
auto &ctx = mcp_global_ctx_;
|
||||
|
||||
nlohmann::json result;
|
||||
|
||||
std::string err;
|
||||
if (!tinyusdz::tydra::mcp::CallTool(ctx, tool_name, j_args, result, err)) {
|
||||
// TODO: Report error more nice way.
|
||||
std::cerr << "[tydra:mcp:CallTool]" << err << "\n";
|
||||
}
|
||||
|
||||
std::string s_result = result.dump();
|
||||
|
||||
return s_result;
|
||||
}
|
||||
|
||||
std::string mcpResourcesList() {
|
||||
|
||||
std::cout << "res list\n";
|
||||
|
||||
if (!mcp_ctx_.count(mcp_session_id_)) {
|
||||
// TODO: better error message
|
||||
return "{ \"error\": \"invalid session_id\"}";
|
||||
}
|
||||
|
||||
//Context &ctx = mcp_ctx_.at(mcp_session_id_);
|
||||
auto &ctx = mcp_global_ctx_;
|
||||
|
||||
nlohmann::json result;
|
||||
|
||||
if (!tinyusdz::tydra::mcp::GetResourcesList(ctx, result)) {
|
||||
// TODO: Report error more nice way.
|
||||
std::cerr << "[tydra:mcp:ListResources] failed\n";
|
||||
}
|
||||
|
||||
std::string s_result = result.dump();
|
||||
|
||||
return s_result;
|
||||
}
|
||||
|
||||
std::string mcpResourcesRead(const std::string &uri) {
|
||||
|
||||
if (!mcp_ctx_.count(mcp_session_id_)) {
|
||||
// TODO: better error message
|
||||
return "{ \"error\": \"invalid session_id\"}";
|
||||
}
|
||||
|
||||
//Context &ctx = mcp_ctx_.at(mcp_session_id_);
|
||||
auto &ctx = mcp_global_ctx_;
|
||||
|
||||
nlohmann::json content;
|
||||
|
||||
if (!tinyusdz::tydra::mcp::ReadResource(ctx, uri, content)) {
|
||||
// TODO: Report error more nice way.
|
||||
std::cerr << "[tydra:mcp:ReadResources] failed\n";
|
||||
}
|
||||
|
||||
std::string s_content = content.dump();
|
||||
|
||||
return s_content;
|
||||
}
|
||||
|
||||
// TODO: Deprecate
|
||||
@@ -1243,6 +1344,12 @@ class TinyUSDZLoaderNative {
|
||||
tinyusdz::tydra::RenderScene render_scene_;
|
||||
tinyusdz::USDZAsset usdz_asset_;
|
||||
EMAssetResolutionResolver em_resolver_;
|
||||
|
||||
// key = session_id
|
||||
std::unordered_map<std::string, tinyusdz::tydra::mcp::Context> mcp_ctx_;
|
||||
std::string mcp_session_id_;
|
||||
|
||||
tinyusdz::tydra::mcp::Context mcp_global_ctx_;
|
||||
};
|
||||
|
||||
///
|
||||
@@ -1522,6 +1629,8 @@ EMSCRIPTEN_BINDINGS(tinyusdz_module) {
|
||||
|
||||
|
||||
// MCP
|
||||
.function("mcpCreateContext", &TinyUSDZLoaderNative::mcpCreateContext)
|
||||
.function("mcpSelectContext", &TinyUSDZLoaderNative::mcpSelectContext)
|
||||
.function("mcpResourcesList", &TinyUSDZLoaderNative::mcpResourcesList)
|
||||
.function("mcpResourcesRead", &TinyUSDZLoaderNative::mcpResourcesRead)
|
||||
.function("mcpToolsList", &TinyUSDZLoaderNative::mcpToolsList)
|
||||
|
||||
@@ -4,8 +4,10 @@
|
||||
"": {
|
||||
"name": "mcp-server",
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "^1.15.1",
|
||||
"@modelcontextprotocol/sdk": "^1.16.0",
|
||||
"body-parser": "^2.2.0",
|
||||
"path": "^0.12.7",
|
||||
"uuid": "^11.1.0",
|
||||
"vite": "^7.0.4",
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -70,7 +72,7 @@
|
||||
|
||||
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.6", "", { "os": "win32", "cpu": "x64" }, "sha512-NgJPHHbEpLQgDH2MjQu90pzW/5vvXIZ7KOnPyNBm92A6WgZ/7b6fJyUBjoumLqeOQQGqY2QjQxRo97ah4Sj0cA=="],
|
||||
|
||||
"@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.15.1", "", { "dependencies": { "ajv": "^6.12.6", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", "express": "^5.0.1", "express-rate-limit": "^7.5.0", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.23.8", "zod-to-json-schema": "^3.24.1" } }, "sha512-W/XlN9c528yYn+9MQkVjxiTPgPxoxt+oczfjHBDsJx0+59+O7B75Zhsp0B16Xbwbz8ANISDajh6+V7nIcPMc5w=="],
|
||||
"@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.16.0", "", { "dependencies": { "ajv": "^6.12.6", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", "express": "^5.0.1", "express-rate-limit": "^7.5.0", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.23.8", "zod-to-json-schema": "^3.24.1" } }, "sha512-8ofX7gkZcLj9H9rSd50mCgm3SSF8C7XoclxJuLoV0Cz3rEQ1tv9MZRYYvJtm9n1BiEQQMzSmE/w2AEkNacLYfg=="],
|
||||
|
||||
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.45.0", "", { "os": "android", "cpu": "arm" }, "sha512-2o/FgACbji4tW1dzXOqAV15Eu7DdgbKsF2QKcxfG4xbh5iwU7yr5RRP5/U+0asQliSYv5M4o7BevlGIoSL0LXg=="],
|
||||
|
||||
@@ -208,7 +210,7 @@
|
||||
|
||||
"iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="],
|
||||
|
||||
"inherits": ["inherits@2.0.3", "", {}, "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw=="],
|
||||
"inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
|
||||
|
||||
"ipaddr.js": ["ipaddr.js@1.9.1", "", {}, "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="],
|
||||
|
||||
@@ -320,6 +322,8 @@
|
||||
|
||||
"util": ["util@0.10.4", "", { "dependencies": { "inherits": "2.0.3" } }, "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A=="],
|
||||
|
||||
"uuid": ["uuid@11.1.0", "", { "bin": { "uuid": "dist/esm/bin/uuid" } }, "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A=="],
|
||||
|
||||
"vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="],
|
||||
|
||||
"vite": ["vite@7.0.4", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.6", "picomatch": "^4.0.2", "postcss": "^8.5.6", "rollup": "^4.40.0", "tinyglobby": "^0.2.14" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-SkaSguuS7nnmV7mfJ8l81JGBFV7Gvzp8IzgE8A8t23+AxuNX61Q5H1Tpz5efduSN7NHC8nQXD3sKQKZAu5mNEA=="],
|
||||
@@ -332,8 +336,8 @@
|
||||
|
||||
"zod-to-json-schema": ["zod-to-json-schema@3.24.6", "", { "peerDependencies": { "zod": "^3.24.1" } }, "sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg=="],
|
||||
|
||||
"http-errors/inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
|
||||
|
||||
"http-errors/statuses": ["statuses@2.0.1", "", {}, "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ=="],
|
||||
|
||||
"util/inherits": ["inherits@2.0.3", "", {}, "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw=="],
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "mcp-server",
|
||||
"module": "index.ts",
|
||||
"module": "server-http.js",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
@@ -17,7 +17,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "^1.16.0",
|
||||
"body-parser": "^2.2.0",
|
||||
"path": "^0.12.7",
|
||||
"uuid": "^11.1.0",
|
||||
"vite": "^7.0.4"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,129 +1,219 @@
|
||||
import express from "express";
|
||||
import { randomUUID } from "node:crypto";
|
||||
import * as bodyParser from "body-parser";
|
||||
//import { randomUUID } from "node:crypto";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import { McpError, ErrorCode, ListResourceTemplatesRequestSchema, ReadResourceRequestSchema, ListToolsRequestSchema, CallToolRequestSchema, ListResourcesRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema, CompleteRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
||||
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
||||
import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
|
||||
import { z } from "zod";
|
||||
import initTinyUSDZNative from "tinyusdz/tinyusdz.js";
|
||||
|
||||
//import { TinyUSDZLoader } from "tinyusdz/TinyUSDLoader.js";
|
||||
//import { TinyUSDZMCPServer } from "tinyusdz/TinyUSDMCPServer.js";
|
||||
|
||||
const portno = 8085;
|
||||
|
||||
const app = express();
|
||||
app.use(express.json());
|
||||
//app.use(express.json());
|
||||
|
||||
// Increase limit for larger requests(e.g. DataURI representation of USD file)
|
||||
app.use(bodyParser.json({limit: '50mb'})); // Increase limit for larger requests
|
||||
app.use(bodyParser.urlencoded({ extended: true, limit: '50mb' })); // Increase limit for larger requests
|
||||
|
||||
// Map to store transports by session ID
|
||||
const transports = {};
|
||||
|
||||
const session = new Map();
|
||||
|
||||
// Handle POST requests for client-to-server communication
|
||||
app.post('/mcp', async (req, res) => {
|
||||
console.log("-- post --");
|
||||
console.log(req.headers);
|
||||
console.log(req.body);
|
||||
// Check for existing session ID
|
||||
const sessionId = req.headers['mcp-session-id'] || undefined;
|
||||
console.log("sessionId", sessionId);
|
||||
let transport = null;
|
||||
initTinyUSDZNative().then(function (TinyUSDZ) {
|
||||
|
||||
if (sessionId && transports[sessionId]) {
|
||||
// Reuse existing transport
|
||||
transport = transports[sessionId];
|
||||
} else if (!sessionId && isInitializeRequest(req.body)) {
|
||||
console.log("init");
|
||||
// New initialization request
|
||||
transport = new StreamableHTTPServerTransport({
|
||||
sessionIdGenerator: () => randomUUID(),
|
||||
onsessioninitialized: (sessionId) => {
|
||||
const tusd = new TinyUSDZ.TinyUSDZLoaderNative();
|
||||
// Handle POST requests for client-to-server communication
|
||||
app.post('/mcp', async (req, res) => {
|
||||
console.log("-- post --");
|
||||
console.log(req.headers);
|
||||
console.log(req.body);
|
||||
// Check for existing session ID
|
||||
const sessionId = req.headers['mcp-session-id'] || undefined;
|
||||
let transport = null;
|
||||
|
||||
console.log("sessionId", sessionId);
|
||||
// Store the transport by session ID
|
||||
transports[sessionId] = transport;
|
||||
},
|
||||
// DNS rebinding protection is disabled by default for backwards compatibility. If you are running this server
|
||||
// locally, make sure to set:
|
||||
// enableDnsRebindingProtection: true,
|
||||
// allowedHosts: ['127.0.0.1'],
|
||||
});
|
||||
if (sessionId && transports[sessionId]) {
|
||||
// Reuse existing transport
|
||||
transport = transports[sessionId];
|
||||
} else if (!sessionId && isInitializeRequest(req.body)) {
|
||||
// New initialization request
|
||||
transport = new StreamableHTTPServerTransport({
|
||||
sessionIdGenerator: () => uuidv4(),
|
||||
onsessioninitialized: (sessionId) => {
|
||||
|
||||
// Clean up transport when closed
|
||||
transport.onclose = () => {
|
||||
if (transport.sessionId) {
|
||||
delete transports[transport.sessionId];
|
||||
}
|
||||
};
|
||||
const server = new McpServer({
|
||||
name: "example-server",
|
||||
version: "1.0.0"
|
||||
});
|
||||
// Store the transport by session ID
|
||||
transports[sessionId] = transport;
|
||||
|
||||
server.registerTool("get_version",
|
||||
{
|
||||
title: "Get TinyUSDZ version",
|
||||
description: "Get TinyUSDZ version",
|
||||
inputSchema: {}
|
||||
},
|
||||
async ({ }) => {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: "v0.9.0"
|
||||
}
|
||||
],
|
||||
tusd.mcpCreateContext(sessionId);
|
||||
},
|
||||
// DNS rebinding protection is disabled by default for backwards compatibility. If you are running this server
|
||||
// locally, make sure to set:
|
||||
// enableDnsRebindingProtection: true,
|
||||
// allowedHosts: ['127.0.0.1'],
|
||||
});
|
||||
|
||||
// Clean up transport when closed
|
||||
transport.onclose = () => {
|
||||
if (transport.sessionId) {
|
||||
delete transports[transport.sessionId];
|
||||
}
|
||||
};
|
||||
const server = new McpServer({
|
||||
name: "tinyusdz-mcp-server",
|
||||
version: "0.9.5"
|
||||
});
|
||||
|
||||
server.server.registerCapabilities({
|
||||
resources: {
|
||||
listChanged: true
|
||||
},
|
||||
tools: {
|
||||
listChanged: true
|
||||
}
|
||||
});
|
||||
|
||||
server.registerTool("add",
|
||||
{
|
||||
title: "Addition Tool",
|
||||
description: "Add two numbers",
|
||||
inputSchema: { a: z.number(), b: z.number() }
|
||||
},
|
||||
async ({ a, b }) => ({
|
||||
content: [{ type: "text", text: String(a + b) }]
|
||||
})
|
||||
);
|
||||
server.server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
||||
const sessionId = server.server.transport.sessionId;
|
||||
console.log("list resources", sessionId);
|
||||
|
||||
// ... set up server resources, tools, and prompts ...
|
||||
tusd.mcpSelectContext(sessionId);
|
||||
const resources_str = tusd.mcpResourcesList();
|
||||
console.log("resources_str", resources_str);
|
||||
|
||||
// Connect to the MCP server
|
||||
await server.connect(transport);
|
||||
} else {
|
||||
// Invalid request
|
||||
res.status(400).json({
|
||||
jsonrpc: '2.0',
|
||||
error: {
|
||||
code: -32000,
|
||||
message: 'Bad Request: No valid session ID provided',
|
||||
},
|
||||
id: null,
|
||||
});
|
||||
return;
|
||||
}
|
||||
const j = JSON.parse(resources_str);
|
||||
return j;
|
||||
});
|
||||
|
||||
// Handle the request
|
||||
await transport.handleRequest(req, res, req.body);
|
||||
server.server.setRequestHandler(ReadResourceRequestSchema, async (request, extra) => {
|
||||
const sessionId = server.server.transport.sessionId;
|
||||
console.log("read resource", sessionId);
|
||||
|
||||
const uri = request.params.uri;
|
||||
console.log("uri", uri);
|
||||
|
||||
tusd.mcpSelectContext(sessionId);
|
||||
const resources_str = tusd.mcpResourcesRead(uri);
|
||||
console.log("resources_str", resources_str);
|
||||
|
||||
const j = JSON.parse(resources_str);
|
||||
return j;
|
||||
});
|
||||
|
||||
server.server.setRequestHandler(ListToolsRequestSchema, async () => {
|
||||
const sessionId = server.server.transport.sessionId;
|
||||
|
||||
console.log("sessId", sessionId);
|
||||
console.log("tusd", tusd);
|
||||
|
||||
tusd.mcpSelectContext(sessionId);
|
||||
console.log("listtools");
|
||||
const tools_str = tusd.mcpToolsList();
|
||||
console.log("tools_str", tools_str);
|
||||
|
||||
const j = JSON.parse(tools_str)
|
||||
return j;
|
||||
});
|
||||
|
||||
server.server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
|
||||
console.log("request", request);
|
||||
|
||||
const sessionId = server.server.transport.sessionId;
|
||||
|
||||
console.log("sessId", sessionId);
|
||||
console.log("tusd", tusd);
|
||||
|
||||
tusd.mcpSelectContext(sessionId);
|
||||
|
||||
const tool_name = request.params.name;
|
||||
const args = JSON.stringify(request.params.arguments);
|
||||
console.log("tool_name", tool_name);
|
||||
console.log("args", args);
|
||||
|
||||
const result_str = tusd.mcpToolsCall(tool_name, args);
|
||||
console.log("result_str", result_str);
|
||||
|
||||
const j = JSON.parse(result_str)
|
||||
return j;
|
||||
});
|
||||
|
||||
|
||||
/*
|
||||
server.registerTool("get_version",
|
||||
{
|
||||
title: "Get TinyUSDZ version",
|
||||
description: "Get TinyUSDZ version",
|
||||
inputSchema: {}
|
||||
},
|
||||
async ({ }) => {
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: "v0.9.0"
|
||||
}
|
||||
],
|
||||
}
|
||||
});
|
||||
|
||||
server.registerTool("load_usd_layer",
|
||||
{
|
||||
title: "Load USD as Layer from URI",
|
||||
description: "Add two numbers",
|
||||
inputSchema: { a: z.number(), b: z.number() }
|
||||
},
|
||||
async ({ a, b }) => ({
|
||||
content: [{ type: "text", text: String(a + b) }]
|
||||
})
|
||||
);
|
||||
*/
|
||||
|
||||
// ... set up server resources, tools, and prompts ...
|
||||
|
||||
// Connect to the MCP server
|
||||
await server.connect(transport);
|
||||
} else {
|
||||
// Invalid request
|
||||
res.status(400).json({
|
||||
jsonrpc: '2.0',
|
||||
error: {
|
||||
code: -32000,
|
||||
message: 'Bad Request: No valid session ID provided',
|
||||
},
|
||||
id: null,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle the request
|
||||
await transport.handleRequest(req, res, req.body);
|
||||
});
|
||||
|
||||
// Reusable handler for GET and DELETE requests
|
||||
const handleSessionRequest = async (req, res) => {
|
||||
const sessionId = req.headers['mcp-session-id'];
|
||||
if (!sessionId || !transports[sessionId]) {
|
||||
res.status(400).send('Invalid or missing session ID');
|
||||
return;
|
||||
}
|
||||
|
||||
const transport = transports[sessionId];
|
||||
await transport.handleRequest(req, res);
|
||||
};
|
||||
|
||||
// Handle GET requests for server-to-client notifications via SSE
|
||||
app.get('/mcp', handleSessionRequest);
|
||||
|
||||
// Handle DELETE requests for session termination
|
||||
app.delete('/mcp', handleSessionRequest);
|
||||
|
||||
console.log("localhost:" + portno.toString())
|
||||
app.listen(portno);
|
||||
|
||||
}).catch((error) => {
|
||||
console.error("Failed to initialize TinyUSDZLoader:", error);
|
||||
});
|
||||
|
||||
// Reusable handler for GET and DELETE requests
|
||||
const handleSessionRequest = async (req, res) => {
|
||||
const sessionId = req.headers['mcp-session-id'];
|
||||
if (!sessionId || !transports[sessionId]) {
|
||||
res.status(400).send('Invalid or missing session ID');
|
||||
return;
|
||||
}
|
||||
|
||||
const transport = transports[sessionId];
|
||||
await transport.handleRequest(req, res);
|
||||
};
|
||||
|
||||
// Handle GET requests for server-to-client notifications via SSE
|
||||
app.get('/mcp', handleSessionRequest);
|
||||
|
||||
// Handle DELETE requests for session termination
|
||||
app.delete('/mcp', handleSessionRequest);
|
||||
|
||||
console.log("localhost:" + portno.toString())
|
||||
app.listen(portno);
|
||||
|
||||
Reference in New Issue
Block a user