From 6f0e787967300daad9a019af6aad94a2cc8fc381 Mon Sep 17 00:00:00 2001 From: mevanlc Date: Sat, 6 Sep 2025 10:08:21 -0600 Subject: [PATCH 1/3] Add guide for custom connectors and MCP server setup --- apache/CHATGPT-CONNECTOR.md | 112 ++++++++++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 apache/CHATGPT-CONNECTOR.md diff --git a/apache/CHATGPT-CONNECTOR.md b/apache/CHATGPT-CONNECTOR.md new file mode 100644 index 0000000..3b44f9a --- /dev/null +++ b/apache/CHATGPT-CONNECTOR.md @@ -0,0 +1,112 @@ +Custom connectors +Custom connectors are also available for (a) ChatGPT Pro users and (b) ChatGPT Business, Enterprise, and Edu workspaces. With this feature, you can add custom connectors that follow the Model Context Protocol (MCP) to connect to custom third-party apps and your internal sources. + +Note: In Business, Enterprise, and Edu workspaces, only workspace owners, admins, and users with the respective setting enabled (for Enterprise/Edu) can add custom connectors. Users with a regular member role do not have the ability to add custom connectors themselves. + +Once a connector is added and enabled by an owner or admin user, it becomes available for all members of the workspace to use. + +As with other connectors, end users must authenticate with each connector themselves before first use. For custom connectors configured with no authentication, end users must still individually search and connect to the connector within their settings before first use. + +Please note that custom connectors are not verified by OpenAI and are intended for developer use only. You should only add custom connectors to your workspace if you know and trust the underlying application. Learn more. + +For additional information regarding how to setup a custom connector using MCP, please refer to our documentation here: http://platform.openai.com/docs/mcp + +# http://platform.openai.com/docs/mcp + + + +Build an MCP server to use with ChatGPT connectors, deep research, or API integrations. + +[Model Context Protocol](https://modelcontextprotocol.io/introduction) (MCP) is an open protocol that's becoming the industry standard for extending AI models with additional tools and knowledge. Remote MCP servers can be used to connect models over the Internet to new data sources and capabilities. + +In this guide, we'll cover how to build a remote MCP server that reads data from a private data source (a [vector store](https://platform.openai.com/docs/guides/retrieval)) and makes it available in ChatGPT via connectors in chat and deep research, as well as [via API](https://platform.openai.com/docs/guides/deep-research). + +## Configure a data source + +You can use data from any source to power a remote MCP server, but for simplicity, we will use [vector stores](https://platform.openai.com/docs/guides/retrieval) in the OpenAI API. Begin by uploading a PDF document to a new vector store - [you can use this public domain 19th century book about cats](https://cdn.openai.com/API/docs/cats.pdf) for an example. + +You can upload files and create a vector store [in the dashboard here](https://platform.openai.com/storage/vector_stores), or you can create vector stores and upload files via API. [Follow the vector store guide](https://platform.openai.com/docs/guides/retrieval) to set up a vector store and upload a file to it. + +Make a note of the vector store's unique ID to use in the example to follow. + +![vector store configuration](https://cdn.openai.com/API/docs/images/vector_store.png) + +## Create an MCP server +Create an MCP server + +Next, let's create a remote MCP server that will do search queries against our vector store, and be able to return document content for files with a given ID. + +In this example, we are going to build our MCP server using Python and FastMCP. A full implementation of the server will be provided at the end of this section, along with instructions for running it on Replit. + +Note that there are a number of other MCP server frameworks you can use in a variety of programming languages. Whichever framework you use though, the tool definitions in your server will need to conform to the shape described here. + +To work with ChatGPT Connectors or deep research (in ChatGPT or via API), your MCP server must implement two tools - search and fetch. +search tool + +The search tool is responsible for returning a list of relevant search results from your MCP server's data source, given a user's query. + +Arguments: + +A single query string. + +Returns: + +An object with a single key, results, whose value is an array of result objects. Each result object should include: + + id - a unique ID for the document or search result item + title - human-readable title. + url - canonical URL for citation. + +In MCP, tool results must be returned as a content array containing one or more "content items." Each content item has a type (such as text, image, or resource) and a payload. + +For the search tool, you should return exactly one content item with: + + type: "text" + text: a JSON-encoded string matching the results array schema above. + +The final tool response should look like: + +{ + "content": [ + { + "type": "text", + "text": "{\"results\":[{\"id\":\"doc-1\",\"title\":\"...\",\"url\":\"...\"}]}" + } + ] +} + +fetch tool + +The fetch tool is used to retrieve the full contents of a search result document or item. + +Arguments: + +A string which is a unique identifier for the search document. + +Returns: + +A single object with the following properties: + + id - a unique ID for the document or search result item + title - a string title for the search result item + text - The full text of the document or item + url - a URL to the document or search result item. Useful for citing specific resources in research. + metadata - an optional key/value pairing of data about the result + +In MCP, tool results must be returned as a content array containing one or more "content items." Each content item has a type (such as text, image, or resource) and a payload. + +In this case, the fetch tool must return exactly one content item with +type: "text" +. The text field should be a JSON-encoded string of the document object following the schema above. + +The final tool response should look like: + +{ + "content": [ + { + "type": "text", + "text": "{\"id\":\"doc-1\",\"title\":\"...\",\"text\":\"full text...\",\"url\":\"https://example.com/doc/",\"metadata\":{\"source\":\"vector_store\"}}" + } + ] +} + From 66944da9cf5ea23c1f4cf9c5f801aeff71231d08 Mon Sep 17 00:00:00 2001 From: mevanlc Date: Sat, 6 Sep 2025 17:15:00 -0600 Subject: [PATCH 2/3] Use JSON-RPC front controller for Apache example --- apache/Context7Tools.php | 61 +++++++++++++++++++++ apache/README.md | 30 +++++++++++ apache/bootstrap.php | 27 ++++++++++ apache/index.php | 113 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 231 insertions(+) create mode 100644 apache/Context7Tools.php create mode 100644 apache/README.md create mode 100644 apache/bootstrap.php create mode 100644 apache/index.php diff --git a/apache/Context7Tools.php b/apache/Context7Tools.php new file mode 100644 index 0000000..b0cbb3b --- /dev/null +++ b/apache/Context7Tools.php @@ -0,0 +1,61 @@ +baseUrl . '/search?query=' . urlencode($query); + return $this->fetchJson($url); + } + + /** + * Fetch documentation for a result id. + */ + #[McpTool(name: 'fetch')] + public function fetch( + #[Schema(description: 'Result identifier')] + string $id + ): array { + $url = $this->baseUrl . '/docs?ids=' . urlencode($id); + return $this->fetchJson($url); + } + + private function fetchJson(string $url): array + { + $context = stream_context_create([ + 'http' => [ + 'timeout' => 10, + 'ignore_errors' => true, + ], + ]); + $data = @file_get_contents($url, false, $context); + if ($data === false) { + throw McpServerException::internalError('Context7 request failed'); + } + + $decoded = json_decode($data, true); + if (! is_array($decoded)) { + throw McpServerException::internalError('Invalid JSON from Context7'); + } + + return $decoded; + } +} diff --git a/apache/README.md b/apache/README.md new file mode 100644 index 0000000..8207720 --- /dev/null +++ b/apache/README.md @@ -0,0 +1,30 @@ +# Apache MCP Example + +This folder contains a minimal synchronous example for using the +`php-mcp/server` components in a traditional Apache + PHP environment. + +A single `index.php` endpoint accepts JSON-RPC 2.0 requests to both list +available tools and invoke them. The tools themselves (`Context7Tools`) +proxy to the public [Context7](https://context7.com) API and are discovered +via attributes. + +## Example requests + +List tools: + +```bash +curl -X POST http://localhost/apache/index.php \ + -H 'Content-Type: application/json' \ + -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' +``` + +Call search tool: + +```bash +curl -X POST http://localhost/apache/index.php \ + -H 'Content-Type: application/json' \ + -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"search","arguments":{"query":"react"}}}' +``` + +These scripts intentionally avoid any event loop or SSE transports and are +intended for "plain" request/response PHP setups. diff --git a/apache/bootstrap.php b/apache/bootstrap.php new file mode 100644 index 0000000..34f40d6 --- /dev/null +++ b/apache/bootstrap.php @@ -0,0 +1,27 @@ +withServerInfo('Context7 Proxy', '1.0') + ->build(); + $server->discover(__DIR__); + + return $server; +} diff --git a/apache/index.php b/apache/index.php new file mode 100644 index 0000000..c661761 --- /dev/null +++ b/apache/index.php @@ -0,0 +1,113 @@ +getRegistry(); +$schemaGen = new SchemaGenerator(new DocBlockParser()); + +$raw = file_get_contents('php://input'); +$decoded = json_decode($raw, true); + +if ($decoded === null) { + http_response_code(400); + echo json_encode([ + 'jsonrpc' => '2.0', + 'id' => null, + 'error' => ['code' => -32700, 'message' => 'Parse error'], + ]); + return; +} + +$requests = is_array($decoded) && array_is_list($decoded) ? $decoded : [$decoded]; +$responses = []; + +foreach ($requests as $req) { + $responses[] = handle_request($req, $registry, $server, $schemaGen); +} + +header('Content-Type: application/json'); +echo json_encode(array_is_list($decoded) ? $responses : $responses[0], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); + +function handle_request(array $req, $registry, $server, $schemaGen): array +{ + $id = $req['id'] ?? null; + $method = $req['method'] ?? ''; + + switch ($method) { + case 'tools/list': + $tools = []; + foreach ($registry->getTools() as $name => $toolSchema) { + $registered = $registry->getTool($name); + $handler = $registered->handler; + + if (is_array($handler)) { + [$class, $method] = $handler; + $reflection = new ReflectionMethod($class, $method); + } elseif (is_string($handler) && function_exists($handler)) { + $reflection = new ReflectionFunction($handler); + } else { + $reflection = new ReflectionMethod($handler, '__invoke'); + } + + $tools[] = [ + 'name' => $toolSchema->name, + 'description' => $toolSchema->description, + 'parameters' => $schemaGen->generate($reflection), + ]; + } + + return [ + 'jsonrpc' => '2.0', + 'id' => $id, + 'result' => ['tools' => $tools], + ]; + + case 'tools/call': + $params = $req['params'] ?? []; + $toolName = $params['name'] ?? ''; + $args = $params['arguments'] ?? []; + + $registered = $registry->getTool($toolName); + if ($registered === null) { + return [ + 'jsonrpc' => '2.0', + 'id' => $id, + 'error' => ['code' => -32601, 'message' => 'Tool not found'], + ]; + } + + $session = new Session(new ArraySessionHandler()); + $context = new Context($session); + + try { + $content = $registered->call($server->getConfiguration()->container, $args, $context); + $result = ['content' => array_map(fn($c) => $c->toArray(), $content)]; + + return [ + 'jsonrpc' => '2.0', + 'id' => $id, + 'result' => $result, + ]; + } catch (Throwable $e) { + return [ + 'jsonrpc' => '2.0', + 'id' => $id, + 'error' => ['code' => -32000, 'message' => $e->getMessage()], + ]; + } + + default: + return [ + 'jsonrpc' => '2.0', + 'id' => $id, + 'error' => ['code' => -32601, 'message' => 'Method not found'], + ]; + } +} From 795e97505ff06ab86d2ace13ff7d947fe29ec907 Mon Sep 17 00:00:00 2001 From: mevanlc Date: Sat, 6 Sep 2025 17:23:06 -0600 Subject: [PATCH 3/3] sync --- apache/{ => docs}/CHATGPT-CONNECTOR.md | 0 apache/{ => www}/Context7Tools.php | 0 apache/{ => www}/README.md | 0 apache/{ => www}/bootstrap.php | 0 apache/{ => www}/index.php | 0 5 files changed, 0 insertions(+), 0 deletions(-) rename apache/{ => docs}/CHATGPT-CONNECTOR.md (100%) rename apache/{ => www}/Context7Tools.php (100%) rename apache/{ => www}/README.md (100%) rename apache/{ => www}/bootstrap.php (100%) rename apache/{ => www}/index.php (100%) diff --git a/apache/CHATGPT-CONNECTOR.md b/apache/docs/CHATGPT-CONNECTOR.md similarity index 100% rename from apache/CHATGPT-CONNECTOR.md rename to apache/docs/CHATGPT-CONNECTOR.md diff --git a/apache/Context7Tools.php b/apache/www/Context7Tools.php similarity index 100% rename from apache/Context7Tools.php rename to apache/www/Context7Tools.php diff --git a/apache/README.md b/apache/www/README.md similarity index 100% rename from apache/README.md rename to apache/www/README.md diff --git a/apache/bootstrap.php b/apache/www/bootstrap.php similarity index 100% rename from apache/bootstrap.php rename to apache/www/bootstrap.php diff --git a/apache/index.php b/apache/www/index.php similarity index 100% rename from apache/index.php rename to apache/www/index.php