logoapi-mcp 0.0.0-stage → 0.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/LICENSE-MIT +9 -0
  2. package/README.md +27 -2
  3. package/bin.js +189 -0
  4. package/package.json +38 -4
package/LICENSE-MIT ADDED
@@ -0,0 +1,9 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 H. Tech Innovation LLC
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,28 @@
1
- # Temporary Holding Version
1
+ # logoapi-mcp
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Company logos and favicons by domain, as an MCP server for stdio clients. A small bridge to the hosted LogoAPI MCP server at https://logoapi.sh/mcp. Free, no key, no dependencies.
4
+
5
+ Tools: `get_logo`, `get_favicon`, `search_logos`. They return URLs to hot-link in `<img>` tags, never image bytes.
6
+
7
+ ## Use
8
+
9
+ Claude Desktop, Cursor or any client that starts MCP servers over stdio:
10
+
11
+ ```json
12
+ {
13
+ "mcpServers": {
14
+ "logoapi": { "command": "npx", "args": ["-y", "logoapi-mcp"] }
15
+ }
16
+ }
17
+ ```
18
+
19
+ Clients that support remote servers can connect to `https://logoapi.sh/mcp` directly and skip this package.
20
+
21
+ ## Notes
22
+
23
+ - Node 18 or newer.
24
+ - `LOGOAPI_MCP_URL` overrides the server URL.
25
+ - Requests time out after 30 seconds. Any failure comes back as a JSON-RPC error with the request id; stdout carries JSON-RPC only, logs go to stderr.
26
+ - Docs: [logoapi.sh/docs/mcp](https://logoapi.sh/docs/mcp). Source: [github.com/logoapi/logo-api](https://github.com/logoapi/logo-api).
27
+
28
+ MIT.
package/bin.js ADDED
@@ -0,0 +1,189 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+ // Stdio-to-HTTP bridge for the LogoAPI MCP server.
4
+ // Reads newline-delimited JSON-RPC on stdin, POSTs each message to the server,
5
+ // writes JSON-RPC replies on stdout. Logs go to stderr only.
6
+
7
+ const pkg = require('./package.json');
8
+ const readline = require('readline');
9
+
10
+ const args = process.argv.slice(2);
11
+ if (args.includes('--version') || args.includes('-v')) {
12
+ process.stdout.write(pkg.version + '\n');
13
+ process.exit(0);
14
+ }
15
+ if (args.includes('--help') || args.includes('-h')) {
16
+ process.stdout.write(
17
+ 'logoapi-mcp ' + pkg.version + '\n\n' +
18
+ 'Stdio bridge to the LogoAPI MCP server (company logos and favicons by domain).\n' +
19
+ 'Free, no key.\n\n' +
20
+ 'Usage: npx -y logoapi-mcp\n\n' +
21
+ 'Env: LOGOAPI_MCP_URL server URL (default https://logoapi.sh/mcp)\n' +
22
+ 'Docs: https://logoapi.sh/docs/mcp\n'
23
+ );
24
+ process.exit(0);
25
+ }
26
+
27
+ const URL_ = process.env.LOGOAPI_MCP_URL || 'https://logoapi.sh/mcp';
28
+ const TIMEOUT_MS = 30000;
29
+ const UA = 'logoapi-mcp/' + pkg.version;
30
+
31
+ let sessionId = null;
32
+ let protocolVersion = null;
33
+
34
+ function log(msg) {
35
+ process.stderr.write('[logoapi-mcp] ' + msg + '\n');
36
+ }
37
+
38
+ function send(obj) {
39
+ process.stdout.write(JSON.stringify(obj) + '\n');
40
+ }
41
+
42
+ function sendError(id, code, message) {
43
+ send({ jsonrpc: '2.0', id, error: { code, message } });
44
+ }
45
+
46
+ function hasId(m) {
47
+ return m && typeof m === 'object' && m.id !== undefined && m.id !== null && m.method !== undefined;
48
+ }
49
+
50
+ // Parse a response body into a list of JSON-RPC messages.
51
+ function parseBody(text, contentType) {
52
+ if (!text || !text.trim()) return [];
53
+ if (/text\/event-stream/i.test(contentType || '')) {
54
+ const out = [];
55
+ for (const event of text.split(/\r?\n\r?\n/)) {
56
+ const data = event
57
+ .split(/\r?\n/)
58
+ .filter((l) => l.startsWith('data:'))
59
+ .map((l) => l.slice(5).replace(/^ /, ''))
60
+ .join('\n');
61
+ if (!data.trim()) continue;
62
+ const parsed = JSON.parse(data);
63
+ if (Array.isArray(parsed)) out.push(...parsed);
64
+ else out.push(parsed);
65
+ }
66
+ return out;
67
+ }
68
+ const parsed = JSON.parse(text);
69
+ return Array.isArray(parsed) ? parsed : [parsed];
70
+ }
71
+
72
+ async function post(msg) {
73
+ const headers = {
74
+ 'Content-Type': 'application/json',
75
+ Accept: 'application/json, text/event-stream',
76
+ 'User-Agent': UA,
77
+ };
78
+ if (protocolVersion) headers['MCP-Protocol-Version'] = protocolVersion;
79
+ if (sessionId) headers['Mcp-Session-Id'] = sessionId;
80
+
81
+ const res = await fetch(URL_, {
82
+ method: 'POST',
83
+ headers,
84
+ body: JSON.stringify(msg),
85
+ signal: AbortSignal.timeout(TIMEOUT_MS),
86
+ });
87
+ const sid = res.headers.get('mcp-session-id');
88
+ if (sid) sessionId = sid;
89
+ const text = await res.text();
90
+ if (res.status === 404 && sessionId) {
91
+ // Session is gone on the server: start clean on the next request.
92
+ sessionId = null;
93
+ protocolVersion = null;
94
+ }
95
+
96
+ if (res.status === 202 || (res.ok && !text.trim())) return [];
97
+ if (!res.ok) {
98
+ // A JSON-RPC error body from the server is forwarded as is, with its own id.
99
+ try {
100
+ const msgs = parseBody(text, res.headers.get('content-type'));
101
+ if (msgs.length && msgs.every((m) => m && typeof m === 'object' && m.jsonrpc === '2.0' && (m.result !== undefined || m.error !== undefined))) return msgs;
102
+ } catch (e) { /* not JSON: use the generic error below */ }
103
+ if (res.status === 429) throw new Error('LogoAPI MCP is rate limited (HTTP 429). Try again later.');
104
+ throw new Error('LogoAPI MCP server returned HTTP ' + res.status + '.');
105
+ }
106
+ try {
107
+ return parseBody(text, res.headers.get('content-type'));
108
+ } catch (e) {
109
+ throw new Error('LogoAPI MCP server sent a reply that is not JSON.');
110
+ }
111
+ }
112
+
113
+ async function handle(msg) {
114
+ const ids = [msg].filter(hasId).map((m) => m.id);
115
+ try {
116
+ const replies = await post(msg);
117
+ if (hasId(msg)) {
118
+ // A server error with no id belongs to the one request we sent.
119
+ for (const r of replies) {
120
+ if (r && r.error !== undefined && (r.id === null || r.id === undefined)) r.id = msg.id;
121
+ }
122
+ if (!replies.some((r) => r && r.id === msg.id)) {
123
+ throw new Error('LogoAPI MCP server sent no reply to this request.');
124
+ }
125
+ }
126
+ for (const r of replies) {
127
+ send(r);
128
+ if (msg.method === 'initialize' && r && r.result && r.result.protocolVersion) {
129
+ protocolVersion = r.result.protocolVersion;
130
+ }
131
+ }
132
+ } catch (e) {
133
+ const reason = e && e.name === 'TimeoutError'
134
+ ? 'LogoAPI MCP request timed out after 30 seconds.'
135
+ : e && e.message && /^LogoAPI/.test(e.message)
136
+ ? e.message
137
+ : 'Could not reach the LogoAPI MCP server (' + ((e && e.cause && e.cause.code) || (e && e.message) || 'network error') + ').';
138
+ log(reason);
139
+ for (const id of ids) sendError(id, -32603, reason);
140
+ }
141
+ }
142
+
143
+ const inflight = new Set();
144
+ let initPromise = null;
145
+
146
+ function dispatch(msg) {
147
+ // The server rejects batches: split an array and send each element on its own.
148
+ if (Array.isArray(msg)) {
149
+ if (!msg.length) sendError(null, -32600, 'Invalid request: empty batch.');
150
+ for (const m of msg) {
151
+ if (m && typeof m === 'object' && !Array.isArray(m)) dispatch(m);
152
+ else sendError(null, -32600, 'Invalid request: expected a JSON-RPC object.');
153
+ }
154
+ return;
155
+ }
156
+ const isInit = msg.method === 'initialize';
157
+ let p;
158
+ if (isInit) {
159
+ p = handle(msg);
160
+ initPromise = p;
161
+ } else {
162
+ // Wait for initialize so the protocol version and session id are known.
163
+ p = (initPromise || Promise.resolve()).then(() => handle(msg));
164
+ }
165
+ inflight.add(p);
166
+ p.finally(() => inflight.delete(p));
167
+ }
168
+
169
+ const rl = readline.createInterface({ input: process.stdin, crlfDelay: Infinity });
170
+ rl.on('line', (line) => {
171
+ if (!line.trim()) return;
172
+ let msg;
173
+ try {
174
+ msg = JSON.parse(line);
175
+ } catch (e) {
176
+ sendError(null, -32700, 'Parse error: input line is not valid JSON.');
177
+ return;
178
+ }
179
+ if (!msg || typeof msg !== 'object') {
180
+ sendError(null, -32600, 'Invalid request: expected a JSON-RPC object.');
181
+ return;
182
+ }
183
+ dispatch(msg);
184
+ });
185
+ rl.on('close', async () => {
186
+ while (inflight.size) await Promise.allSettled([...inflight]);
187
+ // No process.exit: on a pipe stdout is async and exit could cut pending writes.
188
+ process.exitCode = 0;
189
+ });
package/package.json CHANGED
@@ -1,6 +1,40 @@
1
1
  {
2
2
  "name": "logoapi-mcp",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.3.3",
4
+ "mcpName": "sh.logoapi/logos",
5
+ "description": "Stdio bridge to the LogoAPI MCP server: company logos and favicons by domain for MCP clients. No key, zero dependencies",
6
+ "keywords": [
7
+ "mcp",
8
+ "mcp-server",
9
+ "model-context-protocol",
10
+ "logo",
11
+ "logos",
12
+ "favicon",
13
+ "logo-api",
14
+ "no-api-key"
15
+ ],
16
+ "homepage": "https://logoapi.sh/docs/mcp",
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+https://github.com/logoapi/logo-api.git",
20
+ "directory": "packages/logoapi-mcp"
21
+ },
22
+ "bugs": {
23
+ "url": "https://github.com/logoapi/logo-api/issues"
24
+ },
25
+ "license": "MIT",
26
+ "bin": {
27
+ "logoapi-mcp": "bin.js"
28
+ },
29
+ "files": [
30
+ "bin.js",
31
+ "README.md",
32
+ "LICENSE-MIT"
33
+ ],
34
+ "engines": {
35
+ "node": ">=18"
36
+ },
37
+ "scripts": {
38
+ "test": "node test.js"
39
+ }
40
+ }